Compare commits

..

1 Commits

Author SHA1 Message Date
Jonathan Jogenfors 00ec2bd525 feat: add job ids 2025-02-08 00:44:47 +01:00
77 changed files with 697 additions and 744 deletions
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.50", "version": "2.2.48",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.50", "version": "2.2.48",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
@@ -52,7 +52,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.1", "version": "1.125.7",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.50", "version": "2.2.48",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
+1 -1
View File
@@ -103,7 +103,7 @@ services:
command: ['./run.sh', '-disable-reporting'] command: ['./run.sh', '-disable-reporting']
ports: ports:
- 3000:3000 - 3000:3000
image: grafana/grafana:11.5.1-ubuntu@sha256:9a4ab78cec1a2ec7d1ca5dfd5aacec6412706a1bc9e971fc7184e2f6696a63f5 image: grafana/grafana:11.5.0-ubuntu@sha256:3c9e2b202eb933a22da5f2b5a22c98a665493f603b452263d9d6f242a87f60d7
volumes: volumes:
- grafana-data:/var/lib/grafana - grafana-data:/var/lib/grafana
+1 -1
View File
@@ -49,7 +49,7 @@ export default function VersionSwitcher(): JSX.Element {
mobile={windowSize === 'mobile'} mobile={windowSize === 'mobile'}
items={versions.map(({ label, url }) => ({ items={versions.map(({ label, url }) => ({
label, label,
to: url + location.pathname + location.hash, to: url + location.pathname,
target: '_self', target: '_self',
}))} }))}
/> />
+1 -1
View File
@@ -53,7 +53,7 @@ function HomepageHeader() {
<Link <Link
className="flex place-items-center place-content-center py-3 px-8 border bg-immich-primary/10 dark:bg-gray-300 rounded-xl hover:no-underline text-immich-primary dark:text-immich-dark-bg font-bold uppercase" className="flex place-items-center place-content-center py-3 px-8 border bg-immich-primary/10 dark:bg-gray-300 rounded-xl hover:no-underline text-immich-primary dark:text-immich-dark-bg font-bold uppercase"
to="https://immich.store" to="https://demo.immich.app/"
> >
Buy Merch Buy Merch
</Link> </Link>
-8
View File
@@ -1,12 +1,4 @@
[ [
{
"label": "v1.126.1",
"url": "https://v1.126.1.archive.immich.app"
},
{
"label": "v1.126.0",
"url": "https://v1.126.0.archive.immich.app"
},
{ {
"label": "v1.125.7", "label": "v1.125.7",
"url": "https://v1.125.7.archive.immich.app" "url": "https://v1.125.7.archive.immich.app"
+4 -4
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.126.1", "version": "1.125.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.126.1", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
@@ -45,7 +45,7 @@
}, },
"../cli": { "../cli": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.50", "version": "2.2.48",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -92,7 +92,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.1", "version": "1.125.7",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.126.1", "version": "1.125.7",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
-24
View File
@@ -150,30 +150,6 @@ describe('/shared-links', () => {
); );
}); });
it('should filter on albumId', async () => {
const { status, body } = await request(app)
.get(`/shared-links?albumId=${album.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(2);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: linkWithAlbum.id }),
expect.objectContaining({ id: linkWithPassword.id }),
]),
);
});
it('should find 0 albums', async () => {
const { status, body } = await request(app)
.get(`/shared-links?albumId=${uuidDto.notFound}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(0);
});
it('should not get shared links created by other users', async () => { it('should not get shared links created by other users', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get('/shared-links') .get('/shared-links')
+437 -437
View File
@@ -312,157 +312,157 @@
"admin_password": "رمز عبور مدیر", "admin_password": "رمز عبور مدیر",
"administration": "مدیریت", "administration": "مدیریت",
"advanced": "پیشرفته", "advanced": "پیشرفته",
"album_added": "آلبوم اضافه شد", "album_added": "",
"album_added_notification_setting_description": "", "album_added_notification_setting_description": "",
"album_cover_updated": "جلد آلبوم به‌روزرسانی شد", "album_cover_updated": "",
"album_info_updated": "اطلاعات آلبوم به‌روزرسانی شد", "album_info_updated": "",
"album_name": "نام آلبوم", "album_name": "",
"album_options": "گزینه‌های آلبوم", "album_options": "",
"album_updated": "آلبوم به‌روزرسانی شد", "album_updated": "",
"album_updated_setting_description": "", "album_updated_setting_description": "",
"albums": "آلبوم‌ها", "albums": "",
"albums_count": "", "albums_count": "",
"all": "همه", "all": "",
"all_people": "همه افراد", "all_people": "",
"allow_dark_mode": "اجازه دادن به حالت تاریک", "allow_dark_mode": "",
"allow_edits": "اجازه ویرایش", "allow_edits": "",
"api_key": "کلید API", "api_key": "",
"api_keys": "کلیدهای API", "api_keys": "",
"app_settings": "تنظیمات برنامه", "app_settings": "",
"appears_in": "ظاهر می‌شود در", "appears_in": "",
"archive": "بایگانی", "archive": "",
"archive_or_unarchive_photo": "", "archive_or_unarchive_photo": "",
"archive_size": "اندازه بایگانی", "archive_size": "",
"archive_size_description": "", "archive_size_description": "",
"asset_offline": "محتوا آفلاین", "asset_offline": "",
"assets": "محتواها", "assets": "",
"authorized_devices": "دستگاه‌های مجاز", "authorized_devices": "",
"back": "بازگشت", "back": "",
"backward": "عقب", "backward": "",
"blurred_background": "پس‌زمینه محو", "blurred_background": "",
"bulk_delete_duplicates_confirmation": "", "bulk_delete_duplicates_confirmation": "",
"bulk_keep_duplicates_confirmation": "", "bulk_keep_duplicates_confirmation": "",
"bulk_trash_duplicates_confirmation": "", "bulk_trash_duplicates_confirmation": "",
"camera": "دوربین", "camera": "",
"camera_brand": "برند دوربین", "camera_brand": "",
"camera_model": "مدل دوربین", "camera_model": "",
"cancel": "لغو", "cancel": "",
"cancel_search": "لغو جستجو", "cancel_search": "",
"cannot_merge_people": "نمی‌توان افراد را ادغام کرد", "cannot_merge_people": "",
"cannot_update_the_description": "نمی‌توان توضیحات را به‌روزرسانی کرد", "cannot_update_the_description": "",
"change_date": "تغییر تاریخ", "change_date": "",
"change_expiration_time": "تغییر زمان انقضا", "change_expiration_time": "",
"change_location": "تغییر مکان", "change_location": "",
"change_name": "تغییر نام", "change_name": "",
"change_name_successfully": "نام با موفقیت تغییر یافت", "change_name_successfully": "",
"change_password": "تغییر رمز عبور", "change_password": "",
"change_your_password": "رمز عبور خود را تغییر دهید", "change_your_password": "",
"changed_visibility_successfully": "", "changed_visibility_successfully": "",
"check_all": "انتخاب همه", "check_all": "",
"check_logs": "بررسی لاگ‌ها", "check_logs": "",
"choose_matching_people_to_merge": "", "choose_matching_people_to_merge": "",
"city": "شهر", "city": "",
"clear": "پاک کردن", "clear": "",
"clear_all": "پاک کردن همه", "clear_all": "",
"clear_message": "پاک کردن پیام", "clear_message": "",
"clear_value": "پاک کردن مقدار", "clear_value": "",
"close": "بستن", "close": "",
"collapse_all": "جمع کردن همه", "collapse_all": "",
"color_theme": "تم رنگ", "color_theme": "",
"comment_options": "گزینه‌های نظر", "comment_options": "",
"comments_are_disabled": "نظرات غیرفعال هستند", "comments_are_disabled": "",
"confirm": "تأیید", "confirm": "",
"confirm_admin_password": "تأیید رمز عبور مدیر", "confirm_admin_password": "",
"confirm_delete_shared_link": "", "confirm_delete_shared_link": "",
"confirm_password": "تأیید رمز عبور", "confirm_password": "",
"contain": "شامل", "contain": "",
"context": "زمینه", "context": "",
"continue": "ادامه", "continue": "",
"copied_image_to_clipboard": "تصویر به کلیپ‌بورد کپی شد.", "copied_image_to_clipboard": "",
"copied_to_clipboard": "به کلیپ‌بورد کپی شد!", "copied_to_clipboard": "",
"copy_error": "خطا در کپی", "copy_error": "",
"copy_file_path": "کپی مسیر فایل", "copy_file_path": "",
"copy_image": "کپی تصویر", "copy_image": "",
"copy_link": "کپی لینک", "copy_link": "",
"copy_link_to_clipboard": "کپی لینک به کلیپ‌بورد", "copy_link_to_clipboard": "",
"copy_password": "کپی رمز عبور", "copy_password": "",
"copy_to_clipboard": "کپی به کلیپ‌بورد", "copy_to_clipboard": "",
"country": "کشور", "country": "",
"cover": "جلد", "cover": "",
"covers": "جلدها", "covers": "",
"create": "ایجاد", "create": "",
"create_album": "ایجاد آلبوم", "create_album": "",
"create_library": "ایجاد کتابخانه", "create_library": "",
"create_link": "ایجاد لینک", "create_link": "",
"create_link_to_share": "ایجاد لینک برای اشتراک‌گذاری", "create_link_to_share": "",
"create_new_person": "ایجاد فرد جدید", "create_new_person": "",
"create_new_user": "ایجاد کاربر جدید", "create_new_user": "",
"create_user": "ایجاد کاربر", "create_user": "",
"created": "ایجاد شد", "created": "",
"current_device": "دستگاه فعلی", "current_device": "",
"custom_locale": "", "custom_locale": "",
"custom_locale_description": "", "custom_locale_description": "",
"dark": "تاریک", "dark": "",
"date_after": "تاریخ پس از", "date_after": "",
"date_and_time": "تاریخ و زمان", "date_and_time": "",
"date_before": "تاریخ قبل از", "date_before": "",
"date_range": "بازه زمانی", "date_range": "",
"day": "روز", "day": "",
"deduplicate_all": "حذف تکراری‌ها به صورت کامل", "deduplicate_all": "",
"default_locale": "", "default_locale": "",
"default_locale_description": "", "default_locale_description": "",
"delete": "حذف", "delete": "",
"delete_album": "حذف آلبوم", "delete_album": "",
"delete_api_key_prompt": "", "delete_api_key_prompt": "",
"delete_duplicates_confirmation": "", "delete_duplicates_confirmation": "",
"delete_key": "حذف کلید", "delete_key": "",
"delete_library": "حذف کتابخانه", "delete_library": "",
"delete_link": "حذف لینک", "delete_link": "",
"delete_shared_link": "حذف لینک اشتراکی", "delete_shared_link": "",
"delete_user": "حذف کاربر", "delete_user": "",
"deleted_shared_link": "لینک اشتراکی حذف شد", "deleted_shared_link": "",
"description": "توضیحات", "description": "",
"details": "جزئیات", "details": "",
"direction": "جهت", "direction": "",
"disabled": "غیرفعال", "disabled": "",
"disallow_edits": "عدم اجازه ویرایش", "disallow_edits": "",
"discover": "کشف کردن", "discover": "",
"dismiss_all_errors": "رد تمام خطاها", "dismiss_all_errors": "",
"dismiss_error": "رد خطا", "dismiss_error": "",
"display_options": "گزینه‌های نمایش", "display_options": "",
"display_order": "ترتیب نمایش", "display_order": "",
"display_original_photos": "نمایش عکس‌های اصلی", "display_original_photos": "",
"display_original_photos_setting_description": "", "display_original_photos_setting_description": "",
"done": "انجام شد", "done": "",
"download": "دانلود", "download": "",
"download_settings": "تنظیمات دانلود", "download_settings": "",
"download_settings_description": "مدیریت تنظیمات مرتبط با دانلود محتوا", "download_settings_description": "",
"downloading": "در حال دانلود", "downloading": "",
"duplicates": "تکراری‌ها", "duplicates": "",
"duplicates_description": "", "duplicates_description": "",
"duration": "مدت زمان", "duration": "",
"edit_album": "ویرایش آلبوم", "edit_album": "",
"edit_avatar": "ویرایش آواتار", "edit_avatar": "",
"edit_date": "ویرایش تاریخ", "edit_date": "",
"edit_date_and_time": "ویرایش تاریخ و زمان", "edit_date_and_time": "",
"edit_exclusion_pattern": "ویرایش الگوی استثناء", "edit_exclusion_pattern": "",
"edit_faces": "ویرایش چهره‌ها", "edit_faces": "",
"edit_import_path": "", "edit_import_path": "",
"edit_import_paths": "", "edit_import_paths": "",
"edit_key": "ویرایش کلید", "edit_key": "",
"edit_link": "ویرایش لینک", "edit_link": "",
"edit_location": "ویرایش مکان", "edit_location": "",
"edit_name": "ویرایش نام", "edit_name": "",
"edit_people": "ویرایش افراد", "edit_people": "",
"edit_title": "ویرایش عنوان", "edit_title": "",
"edit_user": "ویرایش کاربر", "edit_user": "",
"edited": "ویرایش شد", "edited": "",
"editor": "ویرایشگر", "editor": "",
"email": "ایمیل", "email": "",
"empty_trash": "خالی کردن سطل زباله", "empty_trash": "",
"end_date": "تاریخ پایان", "end_date": "",
"error": "خطا", "error": "",
"error_loading_image": "خطا در بارگذاری تصویر", "error_loading_image": "",
"errors": { "errors": {
"exclusion_pattern_already_exists": "", "exclusion_pattern_already_exists": "",
"import_path_already_exists": "", "import_path_already_exists": "",
@@ -530,400 +530,400 @@
"unable_to_update_timeline_display_status": "", "unable_to_update_timeline_display_status": "",
"unable_to_update_user": "" "unable_to_update_user": ""
}, },
"exit_slideshow": "خروج از نمایش اسلاید", "exit_slideshow": "",
"expand_all": "باز کردن همه", "expand_all": "",
"expire_after": "منقضی شدن بعد از", "expire_after": "",
"expired": "منقضی شده", "expired": "",
"explore": "کاوش کردن", "explore": "",
"export": "صادر کردن", "export": "",
"export_as_json": "صادر کردن به‌صورت JSON", "export_as_json": "",
"extension": "پسوند", "extension": "",
"external": "خارجی", "external": "",
"external_libraries": "کتابخانه‌های خارجی", "external_libraries": "",
"favorite": "علاقه‌مندی", "favorite": "",
"favorite_or_unfavorite_photo": "", "favorite_or_unfavorite_photo": "",
"favorites": "علاقه‌مندی‌ها", "favorites": "",
"feature_photo_updated": "", "feature_photo_updated": "",
"file_name": "نام فایل", "file_name": "",
"file_name_or_extension": "نام فایل یا پسوند", "file_name_or_extension": "",
"filename": "نام فایل", "filename": "",
"filetype": "نوع فایل", "filetype": "",
"filter_people": "فیلتر افراد", "filter_people": "",
"find_them_fast": "", "find_them_fast": "",
"fix_incorrect_match": "رفع تطابق نادرست", "fix_incorrect_match": "",
"forward": "جلو", "forward": "",
"general": "عمومی", "general": "",
"get_help": "دریافت کمک", "get_help": "",
"getting_started": "شروع به کار", "getting_started": "",
"go_back": "بازگشت", "go_back": "",
"go_to_search": "رفتن به جستجو", "go_to_search": "",
"group_albums_by": "گروه‌بندی آلبوم‌ها براساس...", "group_albums_by": "",
"has_quota": "دارای سهمیه", "has_quota": "",
"hide_gallery": "پنهان کردن گالری", "hide_gallery": "",
"hide_password": "پنهان کردن رمز عبور", "hide_password": "",
"hide_person": "پنهان کردن فرد", "hide_person": "",
"host": "میزبان", "host": "",
"hour": "ساعت", "hour": "",
"image": "تصویر", "image": "",
"immich_logo": "لوگوی Immich", "immich_logo": "",
"immich_web_interface": "رابط وب Immich", "immich_web_interface": "",
"import_from_json": "وارد کردن از JSON", "import_from_json": "",
"import_path": "مسیر وارد کردن", "import_path": "",
"in_albums": "", "in_albums": "",
"in_archive": "در بایگانی", "in_archive": "",
"include_archived": "شامل بایگانی شده‌ها", "include_archived": "",
"include_shared_albums": "شامل آلبوم‌های اشتراکی", "include_shared_albums": "",
"include_shared_partner_assets": "", "include_shared_partner_assets": "",
"individual_share": "اشتراک فردی", "individual_share": "",
"info": "اطلاعات", "info": "",
"interval": { "interval": {
"day_at_onepm": "", "day_at_onepm": "",
"hours": "", "hours": "",
"night_at_midnight": "", "night_at_midnight": "",
"night_at_twoam": "" "night_at_twoam": ""
}, },
"invite_people": "دعوت افراد", "invite_people": "",
"invite_to_album": "دعوت به آلبوم", "invite_to_album": "",
"jobs": "وظایف", "jobs": "",
"keep": "نگه داشتن", "keep": "",
"keep_all": "نگه داشتن همه", "keep_all": "",
"keyboard_shortcuts": "میانبرهای صفحه‌کلید", "keyboard_shortcuts": "",
"language": "زبان", "language": "",
"language_setting_description": "انتخاب زبان دلخواه شما", "language_setting_description": "",
"last_seen": "آخرین مشاهده", "last_seen": "",
"leave": "ترک کردن", "leave": "",
"let_others_respond": "اجازه به دیگران برای پاسخ‌گویی", "let_others_respond": "",
"level": "سطح", "level": "",
"library": "کتابخانه", "library": "",
"library_options": "گزینه‌های کتابخانه", "library_options": "",
"light": "روشن", "light": "",
"link_options": "گزینه‌های لینک", "link_options": "",
"link_to_oauth": "اتصال به OAuth", "link_to_oauth": "",
"linked_oauth_account": "حساب OAuth متصل شده", "linked_oauth_account": "",
"list": "لیست", "list": "",
"loading": "در حال بارگذاری", "loading": "",
"loading_search_results_failed": "بارگذاری نتایج جستجو ناموفق بود", "loading_search_results_failed": "",
"log_out": "خروج از سیستم", "log_out": "",
"log_out_all_devices": "خروج از همه دستگاه‌ها", "log_out_all_devices": "",
"login_has_been_disabled": "ورود غیرفعال شده است.", "login_has_been_disabled": "",
"look": "نگاه کردن", "look": "",
"loop_videos": "پخش مداوم ویدئوها", "loop_videos": "",
"loop_videos_description": "", "loop_videos_description": "",
"make": "ساختن", "make": "",
"manage_shared_links": "مدیریت لینک‌های اشتراکی", "manage_shared_links": "",
"manage_sharing_with_partners": "", "manage_sharing_with_partners": "",
"manage_the_app_settings": "مدیریت تنظیمات برنامه", "manage_the_app_settings": "",
"manage_your_account": "مدیریت حساب کاربری شما", "manage_your_account": "",
"manage_your_api_keys": "مدیریت کلیدهای API شما", "manage_your_api_keys": "",
"manage_your_devices": "مدیریت دستگاه‌های متصل", "manage_your_devices": "",
"manage_your_oauth_connection": "مدیریت اتصال OAuth شما", "manage_your_oauth_connection": "",
"map": "نقشه", "map": "",
"map_marker_with_image": "", "map_marker_with_image": "",
"map_settings": "تنظیمات نقشه", "map_settings": "",
"matches": "تطابق‌ها", "matches": "",
"media_type": "نوع رسانه", "media_type": "",
"memories": "خاطرات", "memories": "",
"memories_setting_description": "", "memories_setting_description": "",
"memory": "خاطره", "memory": "",
"menu": "منو", "menu": "",
"merge": "ادغام", "merge": "",
"merge_people": "ادغام افراد", "merge_people": "",
"merge_people_limit": "", "merge_people_limit": "",
"merge_people_prompt": "", "merge_people_prompt": "",
"merge_people_successfully": "ادغام افراد با موفقیت انجام شد", "merge_people_successfully": "",
"minimize": "کوچک کردن", "minimize": "",
"minute": "دقیقه", "minute": "",
"missing": "گمشده", "missing": "",
"model": "مدل", "model": "",
"month": "ماه", "month": "",
"more": "بیشتر", "more": "",
"moved_to_trash": "به سطل زباله منتقل شد", "moved_to_trash": "",
"my_albums": "آلبوم‌های من", "my_albums": "",
"name": "نام", "name": "",
"name_or_nickname": "نام یا لقب", "name_or_nickname": "",
"never": "هرگز", "never": "",
"new_api_key": "کلید API جدید", "new_api_key": "",
"new_password": "رمز عبور جدید", "new_password": "",
"new_person": "فرد جدید", "new_person": "",
"new_user_created": "کاربر جدید ایجاد شد", "new_user_created": "",
"newest_first": "جدیدترین ابتدا", "newest_first": "",
"next": "بعدی", "next": "",
"next_memory": "خاطره بعدی", "next_memory": "",
"no": "خیر", "no": "",
"no_albums_message": "", "no_albums_message": "",
"no_archived_assets_message": "", "no_archived_assets_message": "",
"no_assets_message": "", "no_assets_message": "",
"no_duplicates_found": "هیچ تکراری یافت نشد.", "no_duplicates_found": "",
"no_exif_info_available": "اطلاعات EXIF موجود نیست", "no_exif_info_available": "",
"no_explore_results_message": "", "no_explore_results_message": "",
"no_favorites_message": "", "no_favorites_message": "",
"no_libraries_message": "", "no_libraries_message": "",
"no_name": "بدون نام", "no_name": "",
"no_places": "مکانی یافت نشد", "no_places": "",
"no_results": "نتیجه‌ای یافت نشد", "no_results": "",
"no_shared_albums_message": "", "no_shared_albums_message": "",
"not_in_any_album": "در هیچ آلبومی نیست", "not_in_any_album": "",
"note_apply_storage_label_to_previously_uploaded assets": "", "note_apply_storage_label_to_previously_uploaded assets": "",
"note_unlimited_quota": "", "note_unlimited_quota": "",
"notes": "یادداشت‌ها", "notes": "",
"notification_toggle_setting_description": "اعلان‌های ایمیلی را فعال کنید", "notification_toggle_setting_description": "",
"notifications": "اعلان‌ها", "notifications": "",
"notifications_setting_description": "مدیریت اعلان‌ها", "notifications_setting_description": "",
"oauth": "OAuth", "oauth": "",
"offline": "آفلاین", "offline": "",
"offline_paths": "مسیرهای آفلاین", "offline_paths": "",
"offline_paths_description": "", "offline_paths_description": "",
"ok": "تأیید", "ok": "",
"oldest_first": "قدیمی‌ترین ابتدا", "oldest_first": "",
"online": "آنلاین", "online": "",
"only_favorites": "فقط علاقه‌مندی‌ها", "only_favorites": "",
"open_the_search_filters": "باز کردن فیلترهای جستجو", "open_the_search_filters": "",
"options": "گزینه‌ها", "options": "",
"organize_your_library": "کتابخانه خود را سازماندهی کنید", "organize_your_library": "",
"other": "دیگر", "other": "",
"other_devices": "دستگاه‌های دیگر", "other_devices": "",
"other_variables": "متغیرهای دیگر", "other_variables": "",
"owned": "مالکیت", "owned": "",
"owner": "مالک", "owner": "",
"partner": "شریک", "partner": "",
"partner_can_access": "{partner} می‌تواند دسترسی داشته باشد", "partner_can_access": "",
"partner_can_access_assets": "", "partner_can_access_assets": "",
"partner_can_access_location": "مکان‌هایی که عکس‌های شما گرفته شده‌اند", "partner_can_access_location": "",
"partner_sharing": "اشتراک‌گذاری با شریک", "partner_sharing": "",
"partners": "شرکا", "partners": "",
"password": "رمز عبور", "password": "",
"password_does_not_match": "رمز عبور مطابقت ندارد", "password_does_not_match": "",
"password_required": "رمز عبور مورد نیاز است", "password_required": "",
"password_reset_success": "بازنشانی رمز عبور موفقیت‌آمیز بود", "password_reset_success": "",
"past_durations": { "past_durations": {
"days": "", "days": "",
"hours": "", "hours": "",
"years": "" "years": ""
}, },
"path": "مسیر", "path": "",
"pattern": "الگو", "pattern": "",
"pause": "توقف", "pause": "",
"pause_memories": "توقف خاطرات", "pause_memories": "",
"paused": "متوقف شده", "paused": "",
"pending": "در انتظار", "pending": "",
"people": "افراد", "people": "",
"people_sidebar_description": "", "people_sidebar_description": "",
"permanent_deletion_warning": "هشدار حذف دائمی", "permanent_deletion_warning": "",
"permanent_deletion_warning_setting_description": "نمایش هشدار هنگام حذف دائمی محتواها", "permanent_deletion_warning_setting_description": "",
"permanently_delete": "حذف دائمی", "permanently_delete": "",
"permanently_deleted_asset": "محتوای حذف شده دائمی", "permanently_deleted_asset": "",
"person": "فرد", "person": "",
"photos": "عکس‌ها", "photos": "",
"photos_count": "", "photos_count": "",
"photos_from_previous_years": "عکس‌های سال‌های گذشته", "photos_from_previous_years": "",
"pick_a_location": "یک مکان انتخاب کنید", "pick_a_location": "",
"place": "مکان", "place": "",
"places": "مکان‌ها", "places": "",
"play": "پخش", "play": "",
"play_memories": "پخش خاطرات", "play_memories": "",
"play_motion_photo": "پخش عکس متحرک", "play_motion_photo": "",
"play_or_pause_video": "پخش یا توقف ویدیو", "play_or_pause_video": "",
"port": "پورت", "port": "",
"preset": "پیش‌فرض", "preset": "",
"preview": "پیش‌نمایش", "preview": "",
"previous": "قبلی", "previous": "",
"previous_memory": "خاطره قبلی", "previous_memory": "",
"previous_or_next_photo": "عکس قبلی یا بعدی", "previous_or_next_photo": "",
"primary": "اصلی", "primary": "",
"profile_picture_set": "تصویر پروفایل تنظیم شد.", "profile_picture_set": "",
"public_share": "اشتراک عمومی", "public_share": "",
"reaction_options": "گزینه‌های واکنش", "reaction_options": "",
"read_changelog": "مطالعه تغییرات نسخه", "read_changelog": "",
"recent": "اخیر", "recent": "",
"recent_searches": "جستجوهای اخیر", "recent_searches": "",
"refresh": "تازه سازی", "refresh": "",
"refreshed": "تازه سازی شد", "refreshed": "",
"refreshes_every_file": "", "refreshes_every_file": "",
"remove": "حذف", "remove": "",
"remove_deleted_assets": "حذف محتواهای حذف‌شده", "remove_deleted_assets": "",
"remove_from_album": "حذف از آلبوم", "remove_from_album": "",
"remove_from_favorites": "حذف از علاقه‌مندی‌ها", "remove_from_favorites": "",
"remove_from_shared_link": "", "remove_from_shared_link": "",
"removed_api_key": "", "removed_api_key": "",
"rename": "تغییر نام", "rename": "",
"repair": "تعمیر", "repair": "",
"repair_no_results_message": "", "repair_no_results_message": "",
"replace_with_upload": "جایگزینی با آپلود", "replace_with_upload": "",
"require_password": "", "require_password": "",
"require_user_to_change_password_on_first_login": "", "require_user_to_change_password_on_first_login": "",
"reset": "بازنشانی", "reset": "",
"reset_password": "بازنشانی رمز عبور", "reset_password": "",
"reset_people_visibility": "", "reset_people_visibility": "",
"resolved_all_duplicates": "", "resolved_all_duplicates": "",
"restore": "بازیابی", "restore": "",
"restore_all": "بازیابی همه", "restore_all": "",
"restore_user": "بازیابی کاربر", "restore_user": "",
"resume": "ادامه", "resume": "",
"retry_upload": "", "retry_upload": "",
"review_duplicates": "بررسی تکراری‌ها", "review_duplicates": "",
"role": "نقش", "role": "",
"save": "ذخیره", "save": "",
"saved_api_key": "", "saved_api_key": "",
"saved_profile": "پروفایل ذخیره شد", "saved_profile": "",
"saved_settings": "تنظیمات ذخیره شد", "saved_settings": "",
"say_something": "چیزی بگویید", "say_something": "",
"scan_all_libraries": "اسکن همه کتابخانه‌ها", "scan_all_libraries": "",
"scan_settings": "تنظیمات اسکن", "scan_settings": "",
"scanning_for_album": "", "scanning_for_album": "",
"search": "جستجو", "search": "",
"search_albums": "جستجوی آلبوم‌ها", "search_albums": "",
"search_by_context": "جستجو براساس زمینه", "search_by_context": "",
"search_camera_make": "جستجوی برند دوربین...", "search_camera_make": "",
"search_camera_model": "جستجوی مدل دوربین...", "search_camera_model": "",
"search_city": "جستجوی شهر...", "search_city": "",
"search_country": "جستجوی کشور...", "search_country": "",
"search_for_existing_person": "جستجوی فرد موجود", "search_for_existing_person": "",
"search_people": "جستجوی افراد", "search_people": "",
"search_places": "جستجوی مکان‌ها", "search_places": "",
"search_state": "جستجوی ایالت...", "search_state": "",
"search_timezone": "جستجوی منطقه زمانی...", "search_timezone": "",
"search_type": "نوع جستجو", "search_type": "",
"search_your_photos": "", "search_your_photos": "",
"searching_locales": "", "searching_locales": "",
"second": "ثانیه", "second": "",
"select_album_cover": "انتخاب جلد آلبوم", "select_album_cover": "",
"select_all": "انتخاب همه", "select_all": "",
"select_avatar_color": "انتخاب رنگ آواتار", "select_avatar_color": "",
"select_face": "انتخاب چهره", "select_face": "",
"select_featured_photo": "انتخاب عکس ویژه", "select_featured_photo": "",
"select_keep_all": "انتخاب نگهداری همه", "select_keep_all": "",
"select_library_owner": "انتخاب مالک کتابخانه", "select_library_owner": "",
"select_new_face": "انتخاب چهره جدید", "select_new_face": "",
"select_photos": "انتخاب عکس‌ها", "select_photos": "",
"select_trash_all": "", "select_trash_all": "",
"selected": "انتخاب شده", "selected": "",
"send_message": "ارسال پیام", "send_message": "",
"send_welcome_email": "ارسال ایمیل خوش‌آمدگویی", "send_welcome_email": "",
"server_stats": "آمار سرور", "server_stats": "",
"set": "تنظیم", "set": "",
"set_as_album_cover": "", "set_as_album_cover": "",
"set_as_profile_picture": "", "set_as_profile_picture": "",
"set_date_of_birth": "تنظیم تاریخ تولد", "set_date_of_birth": "",
"set_profile_picture": "تنظیم تصویر پروفایل", "set_profile_picture": "",
"set_slideshow_to_fullscreen": "", "set_slideshow_to_fullscreen": "",
"settings": "تنظیمات", "settings": "",
"settings_saved": "تنظیمات ذخیره شد", "settings_saved": "",
"share": "اشتراک‌گذاری", "share": "",
"shared": "مشترک", "shared": "",
"shared_by": "مشترک توسط", "shared_by": "",
"shared_by_you": "", "shared_by_you": "",
"shared_from_partner": "عکس‌ها از {partner}", "shared_from_partner": "",
"shared_links": "لینک‌های اشتراکی", "shared_links": "",
"shared_photos_and_videos_count": "", "shared_photos_and_videos_count": "",
"shared_with_partner": "مشترک با {partner}", "shared_with_partner": "",
"sharing": "اشتراک‌گذاری", "sharing": "",
"sharing_sidebar_description": "", "sharing_sidebar_description": "",
"show_album_options": "نمایش گزینه‌های آلبوم", "show_album_options": "",
"show_and_hide_people": "", "show_and_hide_people": "",
"show_file_location": "نمایش مسیر فایل", "show_file_location": "",
"show_gallery": "نمایش گالری", "show_gallery": "",
"show_hidden_people": "نمایش افراد پنهان", "show_hidden_people": "",
"show_in_timeline": "", "show_in_timeline": "",
"show_in_timeline_setting_description": "", "show_in_timeline_setting_description": "",
"show_keyboard_shortcuts": "", "show_keyboard_shortcuts": "",
"show_metadata": "نمایش اطلاعات متا", "show_metadata": "",
"show_or_hide_info": "", "show_or_hide_info": "",
"show_password": "نمایش رمز عبور", "show_password": "",
"show_person_options": "", "show_person_options": "",
"show_progress_bar": "نمایش نوار پیشرفت", "show_progress_bar": "",
"show_search_options": "نمایش گزینه‌های جستجو", "show_search_options": "",
"shuffle": "تصادفی", "shuffle": "",
"sign_out": "خروج", "sign_out": "",
"sign_up": "ثبت‌نام", "sign_up": "",
"size": "اندازه", "size": "",
"skip_to_content": "رفتن به محتوا", "skip_to_content": "",
"slideshow": "نمایش اسلاید", "slideshow": "",
"slideshow_settings": "تنظیمات نمایش اسلاید", "slideshow_settings": "",
"sort_albums_by": "", "sort_albums_by": "",
"stack": "پشته", "stack": "",
"stack_selected_photos": "", "stack_selected_photos": "",
"stacktrace": "", "stacktrace": "",
"start": "شروع", "start": "",
"start_date": "تاریخ شروع", "start_date": "",
"state": "ایالت", "state": "",
"status": "وضعیت", "status": "",
"stop_motion_photo": "توقف عکس متحرک", "stop_motion_photo": "",
"stop_photo_sharing": "", "stop_photo_sharing": "",
"stop_photo_sharing_description": "", "stop_photo_sharing_description": "",
"stop_sharing_photos_with_user": "", "stop_sharing_photos_with_user": "",
"storage": "فضای ذخیره‌سازی", "storage": "",
"storage_label": "برچسب فضای ذخیره‌سازی", "storage_label": "",
"storage_usage": "", "storage_usage": "",
"submit": "ارسال", "submit": "",
"suggestions": "پیشنهادات", "suggestions": "",
"sunrise_on_the_beach": "", "sunrise_on_the_beach": "",
"swap_merge_direction": "تغییر جهت ادغام", "swap_merge_direction": "",
"sync": "همگام‌سازی", "sync": "",
"template": "الگو", "template": "",
"theme": "تم", "theme": "",
"theme_selection": "انتخاب تم", "theme_selection": "",
"theme_selection_description": "", "theme_selection_description": "",
"time_based_memories": "", "time_based_memories": "",
"timezone": "منطقه زمانی", "timezone": "",
"to_archive": "بایگانی", "to_archive": "",
"to_favorite": "به علاقه‌مندی‌ها", "to_favorite": "",
"to_trash": "", "to_trash": "",
"toggle_settings": "تغییر تنظیمات", "toggle_settings": "",
"toggle_theme": "تغییر تم تاریک", "toggle_theme": "",
"total_usage": "استفاده کلی", "total_usage": "",
"trash": "سطل زباله", "trash": "",
"trash_all": "", "trash_all": "",
"trash_count": "", "trash_count": "",
"trash_no_results_message": "", "trash_no_results_message": "",
"trashed_items_will_be_permanently_deleted_after": "", "trashed_items_will_be_permanently_deleted_after": "",
"type": "نوع", "type": "",
"unarchive": "", "unarchive": "",
"unfavorite": "حذف از علاقه‌مندی‌ها", "unfavorite": "",
"unhide_person": "آشکار کردن فرد", "unhide_person": "",
"unknown": "ناشناخته", "unknown": "",
"unknown_year": "سال نامشخص", "unknown_year": "",
"unlimited": "نامحدود", "unlimited": "",
"unlink_oauth": "لغو اتصال OAuth", "unlink_oauth": "",
"unlinked_oauth_account": "", "unlinked_oauth_account": "",
"unnamed_album": "آلبوم بدون نام", "unnamed_album": "",
"unnamed_share": "اشتراک بدون نام", "unnamed_share": "",
"unselect_all": "لغو انتخاب همه", "unselect_all": "",
"unstack": "", "unstack": "",
"untracked_files": "", "untracked_files": "",
"untracked_files_decription": "", "untracked_files_decription": "",
"up_next": "مورد بعدی", "up_next": "",
"updated_password": "", "updated_password": "",
"upload": "آپلود", "upload": "",
"upload_concurrency": "تعداد آپلود همزمان", "upload_concurrency": "",
"url": "آدرس", "url": "",
"usage": "استفاده", "usage": "",
"user": "کاربر", "user": "",
"user_id": "شناسه کاربر", "user_id": "",
"user_usage_detail": "جزئیات استفاده کاربر", "user_usage_detail": "",
"username": "نام کاربری", "username": "",
"users": "کاربران", "users": "",
"utilities": "ابزارها", "utilities": "",
"validate": "اعتبارسنجی", "validate": "",
"variables": "متغیرها", "variables": "",
"version": "نسخه", "version": "",
"version_announcement_message": "", "version_announcement_message": "",
"video": "ویدیو", "video": "",
"video_hover_setting": "", "video_hover_setting": "",
"video_hover_setting_description": "", "video_hover_setting_description": "",
"videos": "ویدیوها", "videos": "",
"videos_count": "", "videos_count": "",
"view": "مشاهده", "view": "",
"view_all": "مشاهده همه", "view_all": "",
"view_all_users": "مشاهده همه کاربران", "view_all_users": "",
"view_links": "مشاهده لینک‌ها", "view_links": "",
"view_next_asset": "مشاهده محتوای بعدی", "view_next_asset": "",
"view_previous_asset": "مشاهده محتوای قبلی", "view_previous_asset": "",
"waiting": "در انتظار", "waiting": "",
"week": "هفته", "week": "",
"welcome": "خوش آمدید", "welcome": "",
"welcome_to_immich": "", "welcome_to_immich": "",
"year": "سال", "year": "",
"yes": "بله", "yes": "",
"you_dont_have_any_shared_links": "", "you_dont_have_any_shared_links": "",
"zoom_image": "بزرگنمایی تصویر" "zoom_image": "بزرگنمایی تصویر"
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "machine-learning" name = "machine-learning"
version = "1.126.1" version = "1.125.7"
description = "" description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"] authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md" readme = "README.md"
+2 -2
View File
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 184, "android.injected.version.code" => 182,
"android.injected.version.name" => "1.126.1", "android.injected.version.name" => "1.125.7",
} }
) )
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
+6 -6
View File
@@ -541,7 +541,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 190;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -685,7 +685,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 190;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -715,7 +715,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 190;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -748,7 +748,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 190;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -791,7 +791,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 190;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -831,7 +831,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 193; CURRENT_PROJECT_VERSION = 190;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
+2 -2
View File
@@ -78,7 +78,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.126.0</string> <string>1.125.2</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -93,7 +93,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>193</string> <string>190</string>
<key>FLTEnableImpeller</key> <key>FLTEnableImpeller</key>
<true/> <true/>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
+1 -1
View File
@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Release" desc "iOS Release"
lane :release do lane :release do
increment_version_number( increment_version_number(
version_number: "1.126.1" version_number: "1.125.7"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,
-1
View File
@@ -10,7 +10,6 @@ dynamic upgradeDto(dynamic value, String targetType) {
addDefault(value, 'ratings', RatingsResponse().toJson()); addDefault(value, 'ratings', RatingsResponse().toJson());
addDefault(value, 'people', PeopleResponse().toJson()); addDefault(value, 'people', PeopleResponse().toJson());
addDefault(value, 'tags', TagsResponse().toJson()); addDefault(value, 'tags', TagsResponse().toJson());
addDefault(value, 'sharedLinks', SharedLinksResponse().toJson());
} }
break; break;
case 'ServerConfigDto': case 'ServerConfigDto':
+1 -1
View File
@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.126.1 - API version: 1.125.7
- Generator version: 7.8.0 - Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen - Build package: org.openapitools.codegen.languages.DartClientCodegen
+3 -13
View File
@@ -127,10 +127,7 @@ class SharedLinksApi {
} }
/// Performs an HTTP 'GET /shared-links' operation and returns the [Response]. /// Performs an HTTP 'GET /shared-links' operation and returns the [Response].
/// Parameters: Future<Response> getAllSharedLinksWithHttpInfo() async {
///
/// * [String] albumId:
Future<Response> getAllSharedLinksWithHttpInfo({ String? albumId, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final path = r'/shared-links'; final path = r'/shared-links';
@@ -141,10 +138,6 @@ class SharedLinksApi {
final headerParams = <String, String>{}; final headerParams = <String, String>{};
final formParams = <String, String>{}; final formParams = <String, String>{};
if (albumId != null) {
queryParams.addAll(_queryParams('', 'albumId', albumId));
}
const contentTypes = <String>[]; const contentTypes = <String>[];
@@ -159,11 +152,8 @@ class SharedLinksApi {
); );
} }
/// Parameters: Future<List<SharedLinkResponseDto>?> getAllSharedLinks() async {
/// final response = await getAllSharedLinksWithHttpInfo();
/// * [String] albumId:
Future<List<SharedLinkResponseDto>?> getAllSharedLinks({ String? albumId, }) async {
final response = await getAllSharedLinksWithHttpInfo( albumId: albumId, );
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none' publish_to: 'none'
version: 1.126.1+184 version: 1.125.7+182
environment: environment:
sdk: '>=3.3.0 <4.0.0' sdk: '>=3.3.0 <4.0.0'
+2 -12
View File
@@ -5230,17 +5230,7 @@
"/shared-links": { "/shared-links": {
"get": { "get": {
"operationId": "getAllSharedLinks", "operationId": "getAllSharedLinks",
"parameters": [ "parameters": [],
{
"name": "albumId",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": { "responses": {
"200": { "200": {
"content": { "content": {
@@ -7468,7 +7458,7 @@
"info": { "info": {
"title": "Immich", "title": "Immich",
"description": "Immich API", "description": "Immich API",
"version": "1.126.1", "version": "1.125.7",
"contact": {} "contact": {}
}, },
"tags": [], "tags": [],
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.1", "version": "1.125.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.1", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.1", "version": "1.125.7",
"description": "Auto-generated TypeScript SDK for the Immich API", "description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module", "type": "module",
"main": "./build/index.js", "main": "./build/index.js",
+3 -7
View File
@@ -1,6 +1,6 @@
/** /**
* Immich * Immich
* 1.126.1 * 1.125.7
* DO NOT MODIFY - This file has been generated using oazapfts. * DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts * See https://www.npmjs.com/package/oazapfts
*/ */
@@ -2762,15 +2762,11 @@ export function deleteSession({ id }: {
method: "DELETE" method: "DELETE"
})); }));
} }
export function getAllSharedLinks({ albumId }: { export function getAllSharedLinks(opts?: Oazapfts.RequestOpts) {
albumId?: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{ return oazapfts.ok(oazapfts.fetchJson<{
status: 200; status: 200;
data: SharedLinkResponseDto[]; data: SharedLinkResponseDto[];
}>(`/shared-links${QS.query(QS.explode({ }>("/shared-links", {
albumId
}))}`, {
...opts ...opts
})); }));
} }
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich", "name": "immich",
"version": "1.126.1", "version": "1.125.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich", "name": "immich",
"version": "1.126.1", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@nestjs/bullmq": "^11.0.1", "@nestjs/bullmq": "^11.0.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich", "name": "immich",
"version": "1.126.1", "version": "1.125.7",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -9,7 +9,6 @@ import {
SharedLinkEditDto, SharedLinkEditDto,
SharedLinkPasswordDto, SharedLinkPasswordDto,
SharedLinkResponseDto, SharedLinkResponseDto,
SharedLinkSearchDto,
} from 'src/dtos/shared-link.dto'; } from 'src/dtos/shared-link.dto';
import { ImmichCookie, Permission } from 'src/enum'; import { ImmichCookie, Permission } from 'src/enum';
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
@@ -25,8 +24,8 @@ export class SharedLinkController {
@Get() @Get()
@Authenticated({ permission: Permission.SHARED_LINK_READ }) @Authenticated({ permission: Permission.SHARED_LINK_READ })
getAllSharedLinks(@Auth() auth: AuthDto, @Query() dto: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> { getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> {
return this.service.getAll(auth, dto); return this.service.getAll(auth);
} }
@Get('me') @Get('me')
+2 -1
View File
@@ -9,7 +9,8 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IMoveRepository } from 'src/interfaces/move.interface'; import { IMoveRepository } from 'src/interfaces/move.interface';
import { IPersonRepository } from 'src/interfaces/person.interface'; import { IPersonRepository } from 'src/interfaces/person.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IConfigRepository, ILoggingRepository } from 'src/types';
import { getAssetFiles } from 'src/utils/asset.util'; import { getAssetFiles } from 'src/utils/asset.util';
import { getConfig } from 'src/utils/config'; import { getConfig } from 'src/utils/config';
+2 -2
View File
@@ -1,4 +1,4 @@
import { SessionItem } from 'src/types'; import { SessionEntity } from 'src/entities/session.entity';
export class SessionResponseDto { export class SessionResponseDto {
id!: string; id!: string;
@@ -9,7 +9,7 @@ export class SessionResponseDto {
deviceOS!: string; deviceOS!: string;
} }
export const mapSession = (entity: SessionItem, currentId?: string): SessionResponseDto => ({ export const mapSession = (entity: SessionEntity, currentId?: string): SessionResponseDto => ({
id: entity.id, id: entity.id,
createdAt: entity.createdAt.toISOString(), createdAt: entity.createdAt.toISOString(),
updatedAt: entity.updatedAt.toISOString(), updatedAt: entity.updatedAt.toISOString(),
-5
View File
@@ -7,11 +7,6 @@ import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { SharedLinkType } from 'src/enum'; import { SharedLinkType } from 'src/enum';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
export class SharedLinkSearchDto {
@ValidateUUID({ optional: true })
albumId?: string;
}
export class SharedLinkCreateDto { export class SharedLinkCreateDto {
@IsEnum(SharedLinkType) @IsEnum(SharedLinkType)
@ApiProperty({ enum: SharedLinkType, enumName: 'SharedLinkType' }) @ApiProperty({ enum: SharedLinkType, enumName: 'SharedLinkType' })
-1
View File
@@ -144,7 +144,6 @@ export interface IAssetDeleteJob extends IEntityJob {
} }
export interface ILibraryFileJob extends IEntityJob { export interface ILibraryFileJob extends IEntityJob {
ownerId: string;
assetPath: string; assetPath: string;
} }
@@ -0,0 +1,25 @@
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio } from 'node:child_process';
import { Readable } from 'node:stream';
export interface ImmichReadStream {
stream: Readable;
type?: string;
length?: number;
}
export interface ImmichZipStream extends ImmichReadStream {
addFile: (inputPath: string, filename: string) => void;
finalize: () => Promise<void>;
}
export interface DiskUsage {
available: number;
free: number;
total: number;
}
export const IProcessRepository = 'IProcessRepository';
export interface IProcessRepository {
spawn(command: string, args?: readonly string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams;
}
@@ -0,0 +1,17 @@
import { Insertable, Updateable } from 'kysely';
import { Sessions } from 'src/db';
import { SessionEntity } from 'src/entities/session.entity';
export const ISessionRepository = 'ISessionRepository';
type E = SessionEntity;
export type SessionSearchOptions = { updatedBefore: Date };
export interface ISessionRepository {
search(options: SessionSearchOptions): Promise<SessionEntity[]>;
create(dto: Insertable<Sessions>): Promise<SessionEntity>;
update(id: string, dto: Updateable<Sessions>): Promise<SessionEntity>;
delete(id: string): Promise<void>;
getByToken(token: string): Promise<E | undefined>;
getByUserId(userId: string): Promise<E[]>;
}
@@ -4,13 +4,8 @@ import { SharedLinkEntity } from 'src/entities/shared-link.entity';
export const ISharedLinkRepository = 'ISharedLinkRepository'; export const ISharedLinkRepository = 'ISharedLinkRepository';
export type SharedLinkSearchOptions = {
userId: string;
albumId?: string;
};
export interface ISharedLinkRepository { export interface ISharedLinkRepository {
getAll(options: SharedLinkSearchOptions): Promise<SharedLinkEntity[]>; getAll(userId: string): Promise<SharedLinkEntity[]>;
get(userId: string, id: string): Promise<SharedLinkEntity | undefined>; get(userId: string, id: string): Promise<SharedLinkEntity | undefined>;
getByKey(key: Buffer): Promise<SharedLinkEntity | undefined>; getByKey(key: Buffer): Promise<SharedLinkEntity | undefined>;
create(entity: Insertable<SharedLinks> & { assetIds?: string[] }): Promise<SharedLinkEntity>; create(entity: Insertable<SharedLinks> & { assetIds?: string[] }): Promise<SharedLinkEntity>;
@@ -0,0 +1,10 @@
import { SystemMetadata } from 'src/entities/system-metadata.entity';
export const ISystemMetadataRepository = 'ISystemMetadataRepository';
export interface ISystemMetadataRepository {
get<T extends keyof SystemMetadata>(key: T): Promise<SystemMetadata[T] | null>;
set<T extends keyof SystemMetadata>(key: T, value: SystemMetadata[T]): Promise<void>;
delete<T extends keyof SystemMetadata>(key: T): Promise<void>;
readFile(filename: string): Promise<string>;
}
+6 -3
View File
@@ -9,10 +9,13 @@ import { IMachineLearningRepository } from 'src/interfaces/machine-learning.inte
import { IMoveRepository } from 'src/interfaces/move.interface'; import { IMoveRepository } from 'src/interfaces/move.interface';
import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface';
import { IPersonRepository } from 'src/interfaces/person.interface'; import { IPersonRepository } from 'src/interfaces/person.interface';
import { IProcessRepository } from 'src/interfaces/process.interface';
import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { IStackRepository } from 'src/interfaces/stack.interface'; import { IStackRepository } from 'src/interfaces/stack.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { ITagRepository } from 'src/interfaces/tag.interface'; import { ITagRepository } from 'src/interfaces/tag.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { AccessRepository } from 'src/repositories/access.repository'; import { AccessRepository } from 'src/repositories/access.repository';
@@ -70,10 +73,7 @@ export const repositories = [
MetadataRepository, MetadataRepository,
NotificationRepository, NotificationRepository,
OAuthRepository, OAuthRepository,
ProcessRepository,
SessionRepository,
ServerInfoRepository, ServerInfoRepository,
SystemMetadataRepository,
TelemetryRepository, TelemetryRepository,
TrashRepository, TrashRepository,
ViewRepository, ViewRepository,
@@ -92,10 +92,13 @@ export const providers = [
{ provide: IMoveRepository, useClass: MoveRepository }, { provide: IMoveRepository, useClass: MoveRepository },
{ provide: IPartnerRepository, useClass: PartnerRepository }, { provide: IPartnerRepository, useClass: PartnerRepository },
{ provide: IPersonRepository, useClass: PersonRepository }, { provide: IPersonRepository, useClass: PersonRepository },
{ provide: IProcessRepository, useClass: ProcessRepository },
{ provide: ISearchRepository, useClass: SearchRepository }, { provide: ISearchRepository, useClass: SearchRepository },
{ provide: ISessionRepository, useClass: SessionRepository },
{ provide: ISharedLinkRepository, useClass: SharedLinkRepository }, { provide: ISharedLinkRepository, useClass: SharedLinkRepository },
{ provide: IStackRepository, useClass: StackRepository }, { provide: IStackRepository, useClass: StackRepository },
{ provide: IStorageRepository, useClass: StorageRepository }, { provide: IStorageRepository, useClass: StorageRepository },
{ provide: ISystemMetadataRepository, useClass: SystemMetadataRepository },
{ provide: ITagRepository, useClass: TagRepository }, { provide: ITagRepository, useClass: TagRepository },
{ provide: IUserRepository, useClass: UserRepository }, { provide: IUserRepository, useClass: UserRepository },
]; ];
+16
View File
@@ -216,6 +216,17 @@ export class JobRepository implements IJobRepository {
private getJobOptions(item: JobItem): JobsOptions | null { private getJobOptions(item: JobItem): JobsOptions | null {
switch (item.name) { switch (item.name) {
case JobName.LIBRARY_QUEUE_SYNC_ASSETS:
case JobName.LIBRARY_QUEUE_SYNC_FILES:
case JobName.LIBRARY_SYNC_ASSET:
case JobName.LIBRARY_DELETE:
case JobName.SIDECAR_SYNC:
case JobName.SIDECAR_DISCOVERY: {
return { jobId: `${item.data.id}-${item.name}` };
}
case JobName.LIBRARY_SYNC_FILE: {
return { jobId: `${item.data.id}-${item.data.assetPath}` };
}
case JobName.NOTIFY_ALBUM_UPDATE: { case JobName.NOTIFY_ALBUM_UPDATE: {
return { jobId: item.data.id, delay: item.data?.delay }; return { jobId: item.data.id, delay: item.data?.delay };
} }
@@ -228,6 +239,11 @@ export class JobRepository implements IJobRepository {
case JobName.QUEUE_FACIAL_RECOGNITION: { case JobName.QUEUE_FACIAL_RECOGNITION: {
return { jobId: JobName.QUEUE_FACIAL_RECOGNITION }; return { jobId: JobName.QUEUE_FACIAL_RECOGNITION };
} }
case JobName.LIBRARY_QUEUE_SYNC_ALL:
case JobName.LIBRARY_QUEUE_CLEANUP: {
// These jobs are globally unique and should only have one instance running at a time
return { jobId: item.name };
}
default: { default: {
return null; return null;
} }
+3 -3
View File
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { getName } from 'i18n-iso-countries'; import { getName } from 'i18n-iso-countries';
import { Expression, Kysely, sql, SqlBool } from 'kysely'; import { Expression, Kysely, sql, SqlBool } from 'kysely';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
@@ -11,9 +11,9 @@ import { DB, GeodataPlaces, NaturalearthCountries } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity'; import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity';
import { LogLevel, SystemMetadataKey } from 'src/enum'; import { LogLevel, SystemMetadataKey } from 'src/enum';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
export interface MapMarkerSearchOptions { export interface MapMarkerSearchOptions {
isArchived?: boolean; isArchived?: boolean;
@@ -48,7 +48,7 @@ interface MapDB extends DB {
export class MapRepository { export class MapRepository {
constructor( constructor(
private configRepository: ConfigRepository, private configRepository: ConfigRepository,
private metadataRepository: SystemMetadataRepository, @Inject(ISystemMetadataRepository) private metadataRepository: ISystemMetadataRepository,
private logger: LoggingRepository, private logger: LoggingRepository,
@InjectKysely() private db: Kysely<MapDB>, @InjectKysely() private db: Kysely<MapDB>,
) { ) {
+1 -6
View File
@@ -43,12 +43,7 @@ export class OAuthRepository {
const params = client.callbackParams(url); const params = client.callbackParams(url);
try { try {
const tokens = await client.callback(redirectUrl, params, { state: params.state }); const tokens = await client.callback(redirectUrl, params, { state: params.state });
const profile = await client.userinfo<OAuthProfile>(tokens.access_token || ''); return await client.userinfo<OAuthProfile>(tokens.access_token || '');
if (!profile.sub) {
throw new Error('Unexpected profile response, no `sub`');
}
return profile;
} catch (error: Error | any) { } catch (error: Error | any) {
if (error.message.includes('unexpected JWT alg received')) { if (error.message.includes('unexpected JWT alg received')) {
this.logger.warn( this.logger.warn(
@@ -1,11 +1,13 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ChildProcessWithoutNullStreams, spawn, SpawnOptionsWithoutStdio } from 'node:child_process'; import { ChildProcessWithoutNullStreams, spawn, SpawnOptionsWithoutStdio } from 'node:child_process';
import { IProcessRepository } from 'src/interfaces/process.interface';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { StorageRepository } from 'src/repositories/storage.repository';
@Injectable() @Injectable()
export class ProcessRepository { export class ProcessRepository implements IProcessRepository {
constructor(private logger: LoggingRepository) { constructor(private logger: LoggingRepository) {
this.logger.setContext(ProcessRepository.name); this.logger.setContext(StorageRepository.name);
} }
spawn(command: string, args: readonly string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams { spawn(command: string, args: readonly string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams {
+20 -15
View File
@@ -3,37 +3,36 @@ import { Insertable, Kysely, Updateable } from 'kysely';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import { DB, Sessions } from 'src/db'; import { DB, Sessions } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { withUser } from 'src/entities/session.entity'; import { SessionEntity, withUser } from 'src/entities/session.entity';
import { ISessionRepository, SessionSearchOptions } from 'src/interfaces/session.interface';
import { asUuid } from 'src/utils/database'; import { asUuid } from 'src/utils/database';
export type SessionSearchOptions = { updatedBefore: Date };
@Injectable() @Injectable()
export class SessionRepository { export class SessionRepository implements ISessionRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: [{ updatedBefore: DummyValue.DATE }] }) @GenerateSql({ params: [{ updatedBefore: DummyValue.DATE }] })
search(options: SessionSearchOptions) { search(options: SessionSearchOptions): Promise<SessionEntity[]> {
return this.db return this.db
.selectFrom('sessions') .selectFrom('sessions')
.selectAll() .selectAll()
.where('sessions.updatedAt', '<=', options.updatedBefore) .where('sessions.updatedAt', '<=', options.updatedBefore)
.execute(); .execute() as Promise<SessionEntity[]>;
} }
@GenerateSql({ params: [DummyValue.STRING] }) @GenerateSql({ params: [DummyValue.STRING] })
getByToken(token: string) { getByToken(token: string): Promise<SessionEntity | undefined> {
return this.db return this.db
.selectFrom('sessions') .selectFrom('sessions')
.innerJoinLateral(withUser, (join) => join.onTrue()) .innerJoinLateral(withUser, (join) => join.onTrue())
.selectAll('sessions') .selectAll('sessions')
.select((eb) => eb.fn.toJson('user').as('user')) .select((eb) => eb.fn.toJson('user').as('user'))
.where('sessions.token', '=', token) .where('sessions.token', '=', token)
.executeTakeFirst(); .executeTakeFirst() as Promise<SessionEntity | undefined>;
} }
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID] })
getByUserId(userId: string) { getByUserId(userId: string): Promise<SessionEntity[]> {
return this.db return this.db
.selectFrom('sessions') .selectFrom('sessions')
.innerJoinLateral(withUser, (join) => join.onTrue()) .innerJoinLateral(withUser, (join) => join.onTrue())
@@ -42,24 +41,30 @@ export class SessionRepository {
.where('sessions.userId', '=', userId) .where('sessions.userId', '=', userId)
.orderBy('sessions.updatedAt', 'desc') .orderBy('sessions.updatedAt', 'desc')
.orderBy('sessions.createdAt', 'desc') .orderBy('sessions.createdAt', 'desc')
.execute(); .execute() as unknown as Promise<SessionEntity[]>;
} }
create(dto: Insertable<Sessions>) { async create(dto: Insertable<Sessions>): Promise<SessionEntity> {
return this.db.insertInto('sessions').values(dto).returningAll().executeTakeFirstOrThrow(); const { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } = await this.db
.insertInto('sessions')
.values(dto)
.returningAll()
.executeTakeFirstOrThrow();
return { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } as SessionEntity;
} }
update(id: string, dto: Updateable<Sessions>) { update(id: string, dto: Updateable<Sessions>): Promise<SessionEntity> {
return this.db return this.db
.updateTable('sessions') .updateTable('sessions')
.set(dto) .set(dto)
.where('sessions.id', '=', asUuid(id)) .where('sessions.id', '=', asUuid(id))
.returningAll() .returningAll()
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow() as Promise<SessionEntity>;
} }
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID] })
async delete(id: string) { async delete(id: string): Promise<void> {
await this.db.deleteFrom('sessions').where('id', '=', asUuid(id)).execute(); await this.db.deleteFrom('sessions').where('id', '=', asUuid(id)).execute();
} }
} }
@@ -7,7 +7,7 @@ import { DB, SharedLinks } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { SharedLinkType } from 'src/enum'; import { SharedLinkType } from 'src/enum';
import { ISharedLinkRepository, SharedLinkSearchOptions } from 'src/interfaces/shared-link.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
@Injectable() @Injectable()
export class SharedLinkRepository implements ISharedLinkRepository { export class SharedLinkRepository implements ISharedLinkRepository {
@@ -93,7 +93,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
} }
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID] })
getAll({ userId, albumId }: SharedLinkSearchOptions): Promise<SharedLinkEntity[]> { getAll(userId: string): Promise<SharedLinkEntity[]> {
return this.db return this.db
.selectFrom('shared_links') .selectFrom('shared_links')
.selectAll('shared_links') .selectAll('shared_links')
@@ -149,7 +149,6 @@ export class SharedLinkRepository implements ISharedLinkRepository {
) )
.select((eb) => eb.fn.toJson('album').as('album')) .select((eb) => eb.fn.toJson('album').as('album'))
.where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)])) .where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
.$if(!!albumId, (eb) => eb.where('shared_links.albumId', '=', albumId!))
.orderBy('shared_links.createdAt', 'desc') .orderBy('shared_links.createdAt', 'desc')
.distinctOn(['shared_links.createdAt']) .distinctOn(['shared_links.createdAt'])
.execute() as unknown as Promise<SharedLinkEntity[]>; .execute() as unknown as Promise<SharedLinkEntity[]>;
@@ -5,11 +5,12 @@ import { readFile } from 'node:fs/promises';
import { DB, SystemMetadata as DbSystemMetadata } from 'src/db'; import { DB, SystemMetadata as DbSystemMetadata } from 'src/db';
import { GenerateSql } from 'src/decorators'; import { GenerateSql } from 'src/decorators';
import { SystemMetadata } from 'src/entities/system-metadata.entity'; import { SystemMetadata } from 'src/entities/system-metadata.entity';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
type Upsert = Insertable<DbSystemMetadata>; type Upsert = Insertable<DbSystemMetadata>;
@Injectable() @Injectable()
export class SystemMetadataRepository { export class SystemMetadataRepository implements ISystemMetadataRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: ['metadata_key'] }) @GenerateSql({ params: ['metadata_key'] })
+1 -1
View File
@@ -9,9 +9,9 @@ import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface';
import { IStackRepository } from 'src/interfaces/stack.interface'; import { IStackRepository } from 'src/interfaces/stack.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { AssetService } from 'src/services/asset.service'; import { AssetService } from 'src/services/asset.service';
import { ISystemMetadataRepository } from 'src/types';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { faceStub } from 'test/fixtures/face.stub'; import { faceStub } from 'test/fixtures/face.stub';
+7 -5
View File
@@ -5,10 +5,12 @@ import { UserEntity } from 'src/entities/user.entity';
import { AuthType, Permission } from 'src/enum'; import { AuthType, Permission } from 'src/enum';
import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IEventRepository } from 'src/interfaces/event.interface'; import { IEventRepository } from 'src/interfaces/event.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { AuthService } from 'src/services/auth.service'; import { AuthService } from 'src/services/auth.service';
import { IApiKeyRepository, IOAuthRepository, ISessionRepository, ISystemMetadataRepository } from 'src/types'; import { IApiKeyRepository, IOAuthRepository } from 'src/types';
import { keyStub } from 'test/fixtures/api-key.stub'; import { keyStub } from 'test/fixtures/api-key.stub';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { sessionStub } from 'test/fixtures/session.stub'; import { sessionStub } from 'test/fixtures/session.stub';
@@ -256,7 +258,7 @@ describe('AuthService', () => {
it('should validate using authorization header', async () => { it('should validate using authorization header', async () => {
userMock.get.mockResolvedValue(userStub.user1); userMock.get.mockResolvedValue(userStub.user1);
sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any); sessionMock.getByToken.mockResolvedValue(sessionStub.valid);
await expect( await expect(
sut.authenticate({ sut.authenticate({
headers: { authorization: 'Bearer auth_token' }, headers: { authorization: 'Bearer auth_token' },
@@ -361,7 +363,7 @@ describe('AuthService', () => {
}); });
it('should return an auth dto', async () => { it('should return an auth dto', async () => {
sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any); sessionMock.getByToken.mockResolvedValue(sessionStub.valid);
await expect( await expect(
sut.authenticate({ sut.authenticate({
headers: { cookie: 'immich_access_token=auth_token' }, headers: { cookie: 'immich_access_token=auth_token' },
@@ -375,7 +377,7 @@ describe('AuthService', () => {
}); });
it('should throw if admin route and not an admin', async () => { it('should throw if admin route and not an admin', async () => {
sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any); sessionMock.getByToken.mockResolvedValue(sessionStub.valid);
await expect( await expect(
sut.authenticate({ sut.authenticate({
headers: { cookie: 'immich_access_token=auth_token' }, headers: { cookie: 'immich_access_token=auth_token' },
@@ -386,7 +388,7 @@ describe('AuthService', () => {
}); });
it('should update when access time exceeds an hour', async () => { it('should update when access time exceeds an hour', async () => {
sessionMock.getByToken.mockResolvedValue(sessionStub.inactive as any); sessionMock.getByToken.mockResolvedValue(sessionStub.inactive);
sessionMock.update.mockResolvedValue(sessionStub.valid); sessionMock.update.mockResolvedValue(sessionStub.valid);
await expect( await expect(
sut.authenticate({ sut.authenticate({
+1 -2
View File
@@ -17,7 +17,6 @@ import {
mapLoginResponse, mapLoginResponse,
} from 'src/dtos/auth.dto'; } from 'src/dtos/auth.dto';
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto'; import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
import { SessionEntity } from 'src/entities/session.entity';
import { UserEntity } from 'src/entities/user.entity'; import { UserEntity } from 'src/entities/user.entity';
import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum'; import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum';
import { OAuthProfile } from 'src/repositories/oauth.repository'; import { OAuthProfile } from 'src/repositories/oauth.repository';
@@ -339,7 +338,7 @@ export class AuthService extends BaseService {
await this.sessionRepository.update(session.id, { id: session.id, updatedAt: new Date() }); await this.sessionRepository.update(session.id, { id: session.id, updatedAt: new Date() });
} }
return { user: session.user as unknown as UserEntity, session: session as unknown as SessionEntity }; return { user: session.user, session };
} }
throw new UnauthorizedException('Invalid user token'); throw new UnauthorizedException('Invalid user token');
+3 -1
View File
@@ -4,9 +4,11 @@ import { StorageCore } from 'src/cores/storage.core';
import { ImmichWorker, StorageFolder } from 'src/enum'; import { ImmichWorker, StorageFolder } from 'src/enum';
import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface';
import { JobStatus } from 'src/interfaces/job.interface'; import { JobStatus } from 'src/interfaces/job.interface';
import { IProcessRepository } from 'src/interfaces/process.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { BackupService } from 'src/services/backup.service'; import { BackupService } from 'src/services/backup.service';
import { IConfigRepository, ICronRepository, IProcessRepository, ISystemMetadataRepository } from 'src/types'; import { IConfigRepository, ICronRepository } from 'src/types';
import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub';
import { mockSpawn, newTestService } from 'test/utils'; import { mockSpawn, newTestService } from 'test/utils';
import { describe, Mocked } from 'vitest'; import { describe, Mocked } from 'vitest';
+6 -6
View File
@@ -17,10 +17,13 @@ import { IMachineLearningRepository } from 'src/interfaces/machine-learning.inte
import { IMoveRepository } from 'src/interfaces/move.interface'; import { IMoveRepository } from 'src/interfaces/move.interface';
import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface';
import { IPersonRepository } from 'src/interfaces/person.interface'; import { IPersonRepository } from 'src/interfaces/person.interface';
import { IProcessRepository } from 'src/interfaces/process.interface';
import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { IStackRepository } from 'src/interfaces/stack.interface'; import { IStackRepository } from 'src/interfaces/stack.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { ITagRepository } from 'src/interfaces/tag.interface'; import { ITagRepository } from 'src/interfaces/tag.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { AccessRepository } from 'src/repositories/access.repository'; import { AccessRepository } from 'src/repositories/access.repository';
@@ -37,10 +40,7 @@ import { MemoryRepository } from 'src/repositories/memory.repository';
import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository';
import { NotificationRepository } from 'src/repositories/notification.repository'; import { NotificationRepository } from 'src/repositories/notification.repository';
import { OAuthRepository } from 'src/repositories/oauth.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository';
import { ProcessRepository } from 'src/repositories/process.repository';
import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository';
import { SessionRepository } from 'src/repositories/session.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository';
import { TrashRepository } from 'src/repositories/trash.repository'; import { TrashRepository } from 'src/repositories/trash.repository';
import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
@@ -77,14 +77,14 @@ export class BaseService {
protected oauthRepository: OAuthRepository, protected oauthRepository: OAuthRepository,
@Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository, @Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository,
@Inject(IPersonRepository) protected personRepository: IPersonRepository, @Inject(IPersonRepository) protected personRepository: IPersonRepository,
protected processRepository: ProcessRepository, @Inject(IProcessRepository) protected processRepository: IProcessRepository,
@Inject(ISearchRepository) protected searchRepository: ISearchRepository, @Inject(ISearchRepository) protected searchRepository: ISearchRepository,
protected serverInfoRepository: ServerInfoRepository, protected serverInfoRepository: ServerInfoRepository,
protected sessionRepository: SessionRepository, @Inject(ISessionRepository) protected sessionRepository: ISessionRepository,
@Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository, @Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository,
@Inject(IStackRepository) protected stackRepository: IStackRepository, @Inject(IStackRepository) protected stackRepository: IStackRepository,
@Inject(IStorageRepository) protected storageRepository: IStorageRepository, @Inject(IStorageRepository) protected storageRepository: IStorageRepository,
protected systemMetadataRepository: SystemMetadataRepository, @Inject(ISystemMetadataRepository) protected systemMetadataRepository: ISystemMetadataRepository,
@Inject(ITagRepository) protected tagRepository: ITagRepository, @Inject(ITagRepository) protected tagRepository: ITagRepository,
protected telemetryRepository: TelemetryRepository, protected telemetryRepository: TelemetryRepository,
protected trashRepository: TrashRepository, protected trashRepository: TrashRepository,
+1 -1
View File
@@ -1,6 +1,6 @@
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { CliService } from 'src/services/cli.service'; import { CliService } from 'src/services/cli.service';
import { ISystemMetadataRepository } from 'src/types';
import { userStub } from 'test/fixtures/user.stub'; import { userStub } from 'test/fixtures/user.stub';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
import { Mocked, describe, it } from 'vitest'; import { Mocked, describe, it } from 'vitest';
@@ -1,9 +1,10 @@
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { DuplicateService } from 'src/services/duplicate.service'; import { DuplicateService } from 'src/services/duplicate.service';
import { SearchService } from 'src/services/search.service'; import { SearchService } from 'src/services/search.service';
import { ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { ILoggingRepository } from 'src/types';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
+1 -12
View File
@@ -181,7 +181,6 @@ describe(LibraryService.name, () => {
name: JobName.LIBRARY_SYNC_FILE, name: JobName.LIBRARY_SYNC_FILE,
data: { data: {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
ownerId: libraryStub.externalLibrary1.owner.id,
assetPath: '/data/user1/photo.jpg', assetPath: '/data/user1/photo.jpg',
}, },
}, },
@@ -401,7 +400,6 @@ describe(LibraryService.name, () => {
it('should import a new asset', async () => { it('should import a new asset', async () => {
const mockLibraryJob: ILibraryFileJob = { const mockLibraryJob: ILibraryFileJob = {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
ownerId: mockUser.id,
assetPath: '/data/user1/photo.jpg', assetPath: '/data/user1/photo.jpg',
}; };
@@ -445,7 +443,6 @@ describe(LibraryService.name, () => {
it('should import a new video', async () => { it('should import a new video', async () => {
const mockLibraryJob: ILibraryFileJob = { const mockLibraryJob: ILibraryFileJob = {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
ownerId: mockUser.id,
assetPath: '/data/user1/video.mp4', assetPath: '/data/user1/video.mp4',
}; };
@@ -489,7 +486,6 @@ describe(LibraryService.name, () => {
it('should not import an asset to a soft deleted library', async () => { it('should not import an asset to a soft deleted library', async () => {
const mockLibraryJob: ILibraryFileJob = { const mockLibraryJob: ILibraryFileJob = {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
ownerId: mockUser.id,
assetPath: '/data/user1/photo.jpg', assetPath: '/data/user1/photo.jpg',
}; };
@@ -504,7 +500,6 @@ describe(LibraryService.name, () => {
it('should not refresh a file whose mtime matches existing asset', async () => { it('should not refresh a file whose mtime matches existing asset', async () => {
const mockLibraryJob: ILibraryFileJob = { const mockLibraryJob: ILibraryFileJob = {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
ownerId: mockUser.id,
assetPath: assetStub.hasFileExtension.originalPath, assetPath: assetStub.hasFileExtension.originalPath,
}; };
@@ -522,10 +517,9 @@ describe(LibraryService.name, () => {
expect(jobMock.queueAll).not.toHaveBeenCalled(); expect(jobMock.queueAll).not.toHaveBeenCalled();
}); });
it('should skip existing asset', async () => { it('should skip an existing asset', async () => {
const mockLibraryJob: ILibraryFileJob = { const mockLibraryJob: ILibraryFileJob = {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
ownerId: mockUser.id,
assetPath: '/data/user1/photo.jpg', assetPath: '/data/user1/photo.jpg',
}; };
@@ -537,7 +531,6 @@ describe(LibraryService.name, () => {
it('should not refresh an asset trashed by user', async () => { it('should not refresh an asset trashed by user', async () => {
const mockLibraryJob: ILibraryFileJob = { const mockLibraryJob: ILibraryFileJob = {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
ownerId: mockUser.id,
assetPath: assetStub.hasFileExtension.originalPath, assetPath: assetStub.hasFileExtension.originalPath,
}; };
@@ -554,7 +547,6 @@ describe(LibraryService.name, () => {
const mockLibraryJob: ILibraryFileJob = { const mockLibraryJob: ILibraryFileJob = {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
ownerId: userStub.admin.id,
assetPath: '/data/user1/photo.jpg', assetPath: '/data/user1/photo.jpg',
}; };
@@ -572,7 +564,6 @@ describe(LibraryService.name, () => {
const mockLibraryJob: ILibraryFileJob = { const mockLibraryJob: ILibraryFileJob = {
id: libraryStub.externalLibrary1.id, id: libraryStub.externalLibrary1.id,
ownerId: userStub.admin.id,
assetPath: '/data/user1/photo.jpg', assetPath: '/data/user1/photo.jpg',
}; };
@@ -923,7 +914,6 @@ describe(LibraryService.name, () => {
data: { data: {
id: libraryStub.externalLibraryWithImportPaths1.id, id: libraryStub.externalLibraryWithImportPaths1.id,
assetPath: '/foo/photo.jpg', assetPath: '/foo/photo.jpg',
ownerId: libraryStub.externalLibraryWithImportPaths1.owner.id,
}, },
}, },
]); ]);
@@ -948,7 +938,6 @@ describe(LibraryService.name, () => {
data: { data: {
id: libraryStub.externalLibraryWithImportPaths1.id, id: libraryStub.externalLibraryWithImportPaths1.id,
assetPath: '/foo/photo.jpg', assetPath: '/foo/photo.jpg',
ownerId: libraryStub.externalLibraryWithImportPaths1.owner.id,
}, },
}, },
]); ]);
+8 -3
View File
@@ -228,14 +228,13 @@ export class LibraryService extends BaseService {
return mapLibrary(library); return mapLibrary(library);
} }
private async syncFiles({ id, ownerId }: LibraryEntity, assetPaths: string[]) { private async syncFiles({ id }: LibraryEntity, assetPaths: string[]) {
await this.jobRepository.queueAll( await this.jobRepository.queueAll(
assetPaths.map((assetPath) => ({ assetPaths.map((assetPath) => ({
name: JobName.LIBRARY_SYNC_FILE, name: JobName.LIBRARY_SYNC_FILE,
data: { data: {
id, id,
assetPath, assetPath,
ownerId,
}, },
})), })),
); );
@@ -401,7 +400,7 @@ export class LibraryService extends BaseService {
const mtime = stat.mtime; const mtime = stat.mtime;
asset = await this.assetRepository.create({ asset = await this.assetRepository.create({
ownerId: job.ownerId, ownerId: library.ownerId,
libraryId: job.id, libraryId: job.id,
checksum: pathHash, checksum: pathHash,
originalPath: assetPath, originalPath: assetPath,
@@ -433,12 +432,18 @@ export class LibraryService extends BaseService {
async queueScan(id: string) { async queueScan(id: string) {
await this.findOrFail(id); await this.findOrFail(id);
// We purge any existing scan jobs for this library. This is because the scan settings
// might have changed and the user wants to start a new scan with these settings.
await this.jobRepository.removeJob(id, JobName.LIBRARY_QUEUE_SYNC_FILES);
await this.jobRepository.removeJob(id, JobName.LIBRARY_QUEUE_SYNC_ASSETS);
await this.jobRepository.queue({ await this.jobRepository.queue({
name: JobName.LIBRARY_QUEUE_SYNC_FILES, name: JobName.LIBRARY_QUEUE_SYNC_FILES,
data: { data: {
id, id,
}, },
}); });
await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, data: { id } }); await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, data: { id } });
} }
+2 -1
View File
@@ -18,8 +18,9 @@ import { IJobRepository, JobCounts, JobName, JobStatus } from 'src/interfaces/jo
import { IMoveRepository } from 'src/interfaces/move.interface'; import { IMoveRepository } from 'src/interfaces/move.interface';
import { IPersonRepository } from 'src/interfaces/person.interface'; import { IPersonRepository } from 'src/interfaces/person.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { MediaService } from 'src/services/media.service'; import { MediaService } from 'src/services/media.service';
import { ILoggingRepository, IMediaRepository, ISystemMetadataRepository, RawImageInfo } from 'src/types'; import { ILoggingRepository, IMediaRepository, RawImageInfo } from 'src/types';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { faceStub } from 'test/fixtures/face.stub'; import { faceStub } from 'test/fixtures/face.stub';
import { probeStub } from 'test/fixtures/media.stub'; import { probeStub } from 'test/fixtures/media.stub';
+2 -7
View File
@@ -12,17 +12,12 @@ import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
import { IPersonRepository } from 'src/interfaces/person.interface'; import { IPersonRepository } from 'src/interfaces/person.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { ITagRepository } from 'src/interfaces/tag.interface'; import { ITagRepository } from 'src/interfaces/tag.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { ImmichTags } from 'src/repositories/metadata.repository'; import { ImmichTags } from 'src/repositories/metadata.repository';
import { MetadataService } from 'src/services/metadata.service'; import { MetadataService } from 'src/services/metadata.service';
import { import { IConfigRepository, IMapRepository, IMediaRepository, IMetadataRepository } from 'src/types';
IConfigRepository,
IMapRepository,
IMediaRepository,
IMetadataRepository,
ISystemMetadataRepository,
} from 'src/types';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { fileStub } from 'test/fixtures/file.stub'; import { fileStub } from 'test/fixtures/file.stub';
import { probeStub } from 'test/fixtures/media.stub'; import { probeStub } from 'test/fixtures/media.stub';
@@ -8,10 +8,11 @@ import { IAlbumRepository } from 'src/interfaces/album.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface';
import { IEventRepository } from 'src/interfaces/event.interface'; import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, INotifyAlbumUpdateJob, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IJobRepository, INotifyAlbumUpdateJob, JobName, JobStatus } from 'src/interfaces/job.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { EmailTemplate } from 'src/repositories/notification.repository'; import { EmailTemplate } from 'src/repositories/notification.repository';
import { NotificationService } from 'src/services/notification.service'; import { NotificationService } from 'src/services/notification.service';
import { INotificationRepository, ISystemMetadataRepository } from 'src/types'; import { INotificationRepository } from 'src/types';
import { albumStub } from 'test/fixtures/album.stub'; import { albumStub } from 'test/fixtures/album.stub';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { userStub } from 'test/fixtures/user.stub'; import { userStub } from 'test/fixtures/user.stub';
+2 -1
View File
@@ -10,8 +10,9 @@ import { DetectedFaces, IMachineLearningRepository } from 'src/interfaces/machin
import { IPersonRepository } from 'src/interfaces/person.interface'; import { IPersonRepository } from 'src/interfaces/person.interface';
import { FaceSearchResult, ISearchRepository } from 'src/interfaces/search.interface'; import { FaceSearchResult, ISearchRepository } from 'src/interfaces/search.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { PersonService } from 'src/services/person.service'; import { PersonService } from 'src/services/person.service';
import { IMediaRepository, ISystemMetadataRepository } from 'src/types'; import { IMediaRepository } from 'src/types';
import { ImmichFileResponse } from 'src/utils/file'; import { ImmichFileResponse } from 'src/utils/file';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
+1 -1
View File
@@ -1,8 +1,8 @@
import { SystemMetadataKey } from 'src/enum'; import { SystemMetadataKey } from 'src/enum';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { ServerService } from 'src/services/server.service'; import { ServerService } from 'src/services/server.service';
import { ISystemMetadataRepository } from 'src/types';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
import { Mocked } from 'vitest'; import { Mocked } from 'vitest';
+5 -3
View File
@@ -1,6 +1,7 @@
import { UserEntity } from 'src/entities/user.entity';
import { JobStatus } from 'src/interfaces/job.interface'; import { JobStatus } from 'src/interfaces/job.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { SessionService } from 'src/services/session.service'; import { SessionService } from 'src/services/session.service';
import { ISessionRepository } from 'src/types';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { sessionStub } from 'test/fixtures/session.stub'; import { sessionStub } from 'test/fixtures/session.stub';
import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock';
@@ -37,6 +38,7 @@ describe('SessionService', () => {
deviceType: '', deviceType: '',
id: '123', id: '123',
token: '420', token: '420',
user: {} as UserEntity,
userId: '42', userId: '42',
}, },
]); ]);
@@ -48,7 +50,7 @@ describe('SessionService', () => {
describe('getAll', () => { describe('getAll', () => {
it('should get the devices', async () => { it('should get the devices', async () => {
sessionMock.getByUserId.mockResolvedValue([sessionStub.valid as any, sessionStub.inactive]); sessionMock.getByUserId.mockResolvedValue([sessionStub.valid, sessionStub.inactive]);
await expect(sut.getAll(authStub.user1)).resolves.toEqual([ await expect(sut.getAll(authStub.user1)).resolves.toEqual([
{ {
createdAt: '2021-01-01T00:00:00.000Z', createdAt: '2021-01-01T00:00:00.000Z',
@@ -74,7 +76,7 @@ describe('SessionService', () => {
describe('logoutDevices', () => { describe('logoutDevices', () => {
it('should logout all devices', async () => { it('should logout all devices', async () => {
sessionMock.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid] as any[]); sessionMock.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid]);
await sut.deleteAll(authStub.user1); await sut.deleteAll(authStub.user1);
@@ -29,11 +29,11 @@ describe(SharedLinkService.name, () => {
describe('getAll', () => { describe('getAll', () => {
it('should return all shared links for a user', async () => { it('should return all shared links for a user', async () => {
sharedLinkMock.getAll.mockResolvedValue([sharedLinkStub.expired, sharedLinkStub.valid]); sharedLinkMock.getAll.mockResolvedValue([sharedLinkStub.expired, sharedLinkStub.valid]);
await expect(sut.getAll(authStub.user1, {})).resolves.toEqual([ await expect(sut.getAll(authStub.user1)).resolves.toEqual([
sharedLinkResponseStub.expired, sharedLinkResponseStub.expired,
sharedLinkResponseStub.valid, sharedLinkResponseStub.valid,
]); ]);
expect(sharedLinkMock.getAll).toHaveBeenCalledWith({ userId: authStub.user1.user.id }); expect(sharedLinkMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
}); });
}); });
+2 -5
View File
@@ -9,7 +9,6 @@ import {
SharedLinkEditDto, SharedLinkEditDto,
SharedLinkPasswordDto, SharedLinkPasswordDto,
SharedLinkResponseDto, SharedLinkResponseDto,
SharedLinkSearchDto,
} from 'src/dtos/shared-link.dto'; } from 'src/dtos/shared-link.dto';
import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { Permission, SharedLinkType } from 'src/enum'; import { Permission, SharedLinkType } from 'src/enum';
@@ -18,10 +17,8 @@ import { getExternalDomain, OpenGraphTags } from 'src/utils/misc';
@Injectable() @Injectable()
export class SharedLinkService extends BaseService { export class SharedLinkService extends BaseService {
async getAll(auth: AuthDto, { albumId }: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> { async getAll(auth: AuthDto): Promise<SharedLinkResponseDto[]> {
return this.sharedLinkRepository return this.sharedLinkRepository.getAll(auth.user.id).then((links) => links.map((link) => mapSharedLink(link)));
.getAll({ userId: auth.user.id, albumId })
.then((links) => links.map((link) => mapSharedLink(link)));
} }
async getMine(auth: AuthDto, dto: SharedLinkPasswordDto): Promise<SharedLinkResponseDto> { async getMine(auth: AuthDto, dto: SharedLinkPasswordDto): Promise<SharedLinkResponseDto> {
@@ -5,8 +5,9 @@ import { IDatabaseRepository } from 'src/interfaces/database.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { SmartInfoService } from 'src/services/smart-info.service'; import { SmartInfoService } from 'src/services/smart-info.service';
import { IConfigRepository, ISystemMetadataRepository } from 'src/types'; import { IConfigRepository } from 'src/types';
import { getCLIPModelInfo } from 'src/utils/misc'; import { getCLIPModelInfo } from 'src/utils/misc';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub';
@@ -8,9 +8,9 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { JobStatus } from 'src/interfaces/job.interface'; import { JobStatus } from 'src/interfaces/job.interface';
import { IMoveRepository } from 'src/interfaces/move.interface'; import { IMoveRepository } from 'src/interfaces/move.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { StorageTemplateService } from 'src/services/storage-template.service'; import { StorageTemplateService } from 'src/services/storage-template.service';
import { ISystemMetadataRepository } from 'src/types';
import { albumStub } from 'test/fixtures/album.stub'; import { albumStub } from 'test/fixtures/album.stub';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { userStub } from 'test/fixtures/user.stub'; import { userStub } from 'test/fixtures/user.stub';
+2 -1
View File
@@ -1,7 +1,8 @@
import { SystemMetadataKey } from 'src/enum'; import { SystemMetadataKey } from 'src/enum';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { StorageService } from 'src/services/storage.service'; import { StorageService } from 'src/services/storage.service';
import { IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { IConfigRepository, ILoggingRepository } from 'src/types';
import { ImmichStartupError } from 'src/utils/misc'; import { ImmichStartupError } from 'src/utils/misc';
import { mockEnvData } from 'test/repositories/config.repository.mock'; import { mockEnvData } from 'test/repositories/config.repository.mock';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
@@ -14,8 +14,9 @@ import {
} from 'src/enum'; } from 'src/enum';
import { IEventRepository } from 'src/interfaces/event.interface'; import { IEventRepository } from 'src/interfaces/event.interface';
import { QueueName } from 'src/interfaces/job.interface'; import { QueueName } from 'src/interfaces/job.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { SystemConfigService } from 'src/services/system-config.service'; import { SystemConfigService } from 'src/services/system-config.service';
import { DeepPartial, IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { DeepPartial, IConfigRepository, ILoggingRepository } from 'src/types';
import { mockEnvData } from 'test/repositories/config.repository.mock'; import { mockEnvData } from 'test/repositories/config.repository.mock';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
import { Mocked } from 'vitest'; import { Mocked } from 'vitest';
@@ -1,6 +1,6 @@
import { SystemMetadataKey } from 'src/enum'; import { SystemMetadataKey } from 'src/enum';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { SystemMetadataService } from 'src/services/system-metadata.service'; import { SystemMetadataService } from 'src/services/system-metadata.service';
import { ISystemMetadataRepository } from 'src/types';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
import { Mocked } from 'vitest'; import { Mocked } from 'vitest';
+1 -1
View File
@@ -4,9 +4,9 @@ import { CacheControl, UserMetadataKey } from 'src/enum';
import { IAlbumRepository } from 'src/interfaces/album.interface'; import { IAlbumRepository } from 'src/interfaces/album.interface';
import { IJobRepository, JobName } from 'src/interfaces/job.interface'; import { IJobRepository, JobName } from 'src/interfaces/job.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { UserService } from 'src/services/user.service'; import { UserService } from 'src/services/user.service';
import { ISystemMetadataRepository } from 'src/types';
import { ImmichFileResponse } from 'src/utils/file'; import { ImmichFileResponse } from 'src/utils/file';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub';
+2 -7
View File
@@ -4,14 +4,9 @@ import { serverVersion } from 'src/constants';
import { ImmichEnvironment, SystemMetadataKey } from 'src/enum'; import { ImmichEnvironment, SystemMetadataKey } from 'src/enum';
import { IEventRepository } from 'src/interfaces/event.interface'; import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { VersionService } from 'src/services/version.service'; import { VersionService } from 'src/services/version.service';
import { import { IConfigRepository, ILoggingRepository, IServerInfoRepository, IVersionHistoryRepository } from 'src/types';
IConfigRepository,
ILoggingRepository,
IServerInfoRepository,
ISystemMetadataRepository,
IVersionHistoryRepository,
} from 'src/types';
import { mockEnvData } from 'test/repositories/config.repository.mock'; import { mockEnvData } from 'test/repositories/config.repository.mock';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
import { Mocked } from 'vitest'; import { Mocked } from 'vitest';
-8
View File
@@ -14,10 +14,7 @@ import { MemoryRepository } from 'src/repositories/memory.repository';
import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository';
import { NotificationRepository } from 'src/repositories/notification.repository'; import { NotificationRepository } from 'src/repositories/notification.repository';
import { OAuthRepository } from 'src/repositories/oauth.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository';
import { ProcessRepository } from 'src/repositories/process.repository';
import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository';
import { SessionRepository } from 'src/repositories/session.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { MetricGroupRepository, TelemetryRepository } from 'src/repositories/telemetry.repository'; import { MetricGroupRepository, TelemetryRepository } from 'src/repositories/telemetry.repository';
import { TrashRepository } from 'src/repositories/trash.repository'; import { TrashRepository } from 'src/repositories/trash.repository';
import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
@@ -61,10 +58,7 @@ export type IMetadataRepository = RepositoryInterface<MetadataRepository>;
export type IMetricGroupRepository = RepositoryInterface<MetricGroupRepository>; export type IMetricGroupRepository = RepositoryInterface<MetricGroupRepository>;
export type INotificationRepository = RepositoryInterface<NotificationRepository>; export type INotificationRepository = RepositoryInterface<NotificationRepository>;
export type IOAuthRepository = RepositoryInterface<OAuthRepository>; export type IOAuthRepository = RepositoryInterface<OAuthRepository>;
export type IProcessRepository = RepositoryInterface<ProcessRepository>;
export type ISessionRepository = RepositoryInterface<SessionRepository>;
export type IServerInfoRepository = RepositoryInterface<ServerInfoRepository>; export type IServerInfoRepository = RepositoryInterface<ServerInfoRepository>;
export type ISystemMetadataRepository = RepositoryInterface<SystemMetadataRepository>;
export type ITelemetryRepository = RepositoryInterface<TelemetryRepository>; export type ITelemetryRepository = RepositoryInterface<TelemetryRepository>;
export type ITrashRepository = RepositoryInterface<TrashRepository>; export type ITrashRepository = RepositoryInterface<TrashRepository>;
export type IViewRepository = RepositoryInterface<ViewRepository>; export type IViewRepository = RepositoryInterface<ViewRepository>;
@@ -83,8 +77,6 @@ export type MemoryItem =
| Awaited<ReturnType<IMemoryRepository['create']>> | Awaited<ReturnType<IMemoryRepository['create']>>
| Awaited<ReturnType<IMemoryRepository['search']>>[0]; | Awaited<ReturnType<IMemoryRepository['search']>>[0];
export type SessionItem = Awaited<ReturnType<ISessionRepository['getByUserId']>>[0];
export interface CropOptions { export interface CropOptions {
top: number; top: number;
left: number; left: number;
+2 -1
View File
@@ -7,7 +7,8 @@ import { SystemConfig, defaults } from 'src/config';
import { SystemConfigDto } from 'src/dtos/system-config.dto'; import { SystemConfigDto } from 'src/dtos/system-config.dto';
import { SystemMetadataKey } from 'src/enum'; import { SystemMetadataKey } from 'src/enum';
import { DatabaseLock } from 'src/interfaces/database.interface'; import { DatabaseLock } from 'src/interfaces/database.interface';
import { DeepPartial, IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { DeepPartial, IConfigRepository, ILoggingRepository } from 'src/types';
import { getKeysDeep, unsetDeep } from 'src/utils/misc'; import { getKeysDeep, unsetDeep } from 'src/utils/misc';
export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>; export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>;
@@ -1,4 +1,4 @@
import { IProcessRepository } from 'src/types'; import { IProcessRepository } from 'src/interfaces/process.interface';
import { Mocked, vitest } from 'vitest'; import { Mocked, vitest } from 'vitest';
export const newProcessRepositoryMock = (): Mocked<IProcessRepository> => { export const newProcessRepositoryMock = (): Mocked<IProcessRepository> => {
@@ -1,4 +1,4 @@
import { ISessionRepository } from 'src/types'; import { ISessionRepository } from 'src/interfaces/session.interface';
import { Mocked, vitest } from 'vitest'; import { Mocked, vitest } from 'vitest';
export const newSessionRepositoryMock = (): Mocked<ISessionRepository> => { export const newSessionRepositoryMock = (): Mocked<ISessionRepository> => {
@@ -1,4 +1,4 @@
import { ISystemMetadataRepository } from 'src/types'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { clearConfigCache } from 'src/utils/config'; import { clearConfigCache } from 'src/utils/config';
import { Mocked, vitest } from 'vitest'; import { Mocked, vitest } from 'vitest';
+3 -9
View File
@@ -15,10 +15,7 @@ import { MemoryRepository } from 'src/repositories/memory.repository';
import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository';
import { NotificationRepository } from 'src/repositories/notification.repository'; import { NotificationRepository } from 'src/repositories/notification.repository';
import { OAuthRepository } from 'src/repositories/oauth.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository';
import { ProcessRepository } from 'src/repositories/process.repository';
import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository';
import { SessionRepository } from 'src/repositories/session.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository';
import { TrashRepository } from 'src/repositories/trash.repository'; import { TrashRepository } from 'src/repositories/trash.repository';
import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
@@ -38,10 +35,7 @@ import {
IMetadataRepository, IMetadataRepository,
INotificationRepository, INotificationRepository,
IOAuthRepository, IOAuthRepository,
IProcessRepository,
IServerInfoRepository, IServerInfoRepository,
ISessionRepository,
ISystemMetadataRepository,
ITrashRepository, ITrashRepository,
IVersionHistoryRepository, IVersionHistoryRepository,
IViewRepository, IViewRepository,
@@ -169,14 +163,14 @@ export const newTestService = <T extends BaseService>(
oauthMock as IOAuthRepository as OAuthRepository, oauthMock as IOAuthRepository as OAuthRepository,
partnerMock, partnerMock,
personMock, personMock,
processMock as IProcessRepository as ProcessRepository, processMock,
searchMock, searchMock,
serverInfoMock as IServerInfoRepository as ServerInfoRepository, serverInfoMock as IServerInfoRepository as ServerInfoRepository,
sessionMock as ISessionRepository as SessionRepository, sessionMock,
sharedLinkMock, sharedLinkMock,
stackMock, stackMock,
storageMock, storageMock,
systemMock as ISystemMetadataRepository as SystemMetadataRepository, systemMock,
tagMock, tagMock,
telemetryMock as unknown as TelemetryRepository, telemetryMock as unknown as TelemetryRepository,
trashMock as ITrashRepository as TrashRepository, trashMock as ITrashRepository as TrashRepository,
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.126.1", "version": "1.125.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-web", "name": "immich-web",
"version": "1.126.1", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@formatjs/icu-messageformat-parser": "^2.9.8", "@formatjs/icu-messageformat-parser": "^2.9.8",
@@ -77,7 +77,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.1", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.126.1", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"scripts": { "scripts": {
"dev": "vite dev --host 0.0.0.0 --port 3000", "dev": "vite dev --host 0.0.0.0 --port 3000",
@@ -1,40 +0,0 @@
<script lang="ts">
import SharedLinkCopy from '$lib/components/sharedlinks-page/actions/shared-link-copy.svelte';
import { locale } from '$lib/stores/preferences.store';
import type { AlbumResponseDto, SharedLinkResponseDto } from '@immich/sdk';
import { Text } from '@immich/ui';
import { DateTime } from 'luxon';
import { t } from 'svelte-i18n';
type Props = {
album: AlbumResponseDto;
sharedLink: SharedLinkResponseDto;
};
const { album, sharedLink }: Props = $props();
</script>
<div class="flex justify-between items-center">
<div class="flex flex-col gap-1">
<Text size="small">{sharedLink.description || album.albumName}</Text>
<Text size="tiny" color="muted"
>{[
DateTime.fromISO(sharedLink.createdAt).toLocaleString(
{
month: 'long',
day: 'numeric',
year: 'numeric',
},
{ locale: $locale },
),
sharedLink.allowUpload && $t('upload'),
sharedLink.allowDownload && $t('download'),
sharedLink.showMetadata && $t('exif'),
sharedLink.password && $t('password'),
]
.filter(Boolean)
.join(' • ')}</Text
>
</div>
<SharedLinkCopy link={sharedLink} />
</div>
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import AlbumSharedLink from '$lib/components/album-page/album-shared-link.svelte';
import Dropdown from '$lib/components/elements/dropdown.svelte'; import Dropdown from '$lib/components/elements/dropdown.svelte';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
@@ -13,11 +12,11 @@
type SharedLinkResponseDto, type SharedLinkResponseDto,
type UserResponseDto, type UserResponseDto,
} from '@immich/sdk'; } from '@immich/sdk';
import { Button, Link, Stack, Text } from '@immich/ui'; import { mdiCheck, mdiEye, mdiLink, mdiPencil, mdiShareCircle } from '@mdi/js';
import { mdiCheck, mdiEye, mdiLink, mdiPencil } from '@mdi/js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { t } from 'svelte-i18n'; import Button from '../elements/buttons/button.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte'; import UserAvatar from '../shared-components/user-avatar.svelte';
import { t } from 'svelte-i18n';
interface Props { interface Props {
album: AlbumResponseDto; album: AlbumResponseDto;
@@ -39,7 +38,7 @@
let sharedLinks: SharedLinkResponseDto[] = $state([]); let sharedLinks: SharedLinkResponseDto[] = $state([]);
onMount(async () => { onMount(async () => {
sharedLinks = await getAllSharedLinks({ albumId: album.id }); await getSharedLinks();
const data = await searchUsers(); const data = await searchUsers();
// remove album owner // remove album owner
@@ -51,6 +50,11 @@
} }
}); });
const getSharedLinks = async () => {
const data = await getAllSharedLinks();
sharedLinks = data.filter((link) => link.album?.id === album.id);
};
const handleToggle = (user: UserResponseDto) => { const handleToggle = (user: UserResponseDto) => {
if (Object.keys(selectedUsers).includes(user.id)) { if (Object.keys(selectedUsers).includes(user.id)) {
delete selectedUsers[user.id]; delete selectedUsers[user.id];
@@ -68,10 +72,10 @@
}; };
</script> </script>
<FullScreenModal title={$t('share')} showLogo {onClose}> <FullScreenModal title={$t('invite_to_album')} showLogo {onClose}>
{#if Object.keys(selectedUsers).length > 0} {#if Object.keys(selectedUsers).length > 0}
<div class="mb-2 py-2 sticky"> <div class="mb-2 py-2 sticky">
<p class="text-xs font-medium">{$t('selected')}</p> <p class="text-xs font-medium">{$t('selected').toUpperCase()}</p>
<div class="my-2"> <div class="my-2">
{#each Object.values(selectedUsers) as { user }} {#each Object.values(selectedUsers) as { user }}
{#key user.id} {#key user.id}
@@ -113,7 +117,7 @@
<div class="immich-scrollbar max-h-[500px] overflow-y-auto"> <div class="immich-scrollbar max-h-[500px] overflow-y-auto">
{#if users.length > 0 && users.length !== Object.keys(selectedUsers).length} {#if users.length > 0 && users.length !== Object.keys(selectedUsers).length}
<Text>{$t('users')}</Text> <p class="text-xs font-medium">{$t('suggestions').toUpperCase()}</p>
<div class="my-2"> <div class="my-2">
{#each users as user} {#each users as user}
@@ -140,9 +144,9 @@
{#if users.length > 0} {#if users.length > 0}
<div class="py-3"> <div class="py-3">
<Button <Button
size="small" size="sm"
fullWidth fullwidth
shape="round" rounded="full"
disabled={Object.keys(selectedUsers).length === 0} disabled={Object.keys(selectedUsers).length === 0}
onclick={() => onclick={() =>
onSelect(Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })))} onSelect(Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })))}
@@ -151,22 +155,26 @@
</div> </div>
{/if} {/if}
<hr class="my-4" /> <hr />
<Stack gap={6}> <div id="shared-buttons" class="mt-4 flex place-content-center place-items-center justify-around">
{#if sharedLinks.length > 0} <button
<div class="flex justify-between items-center"> type="button"
<Text>{$t('shared_links')}</Text> class="flex flex-col place-content-center place-items-center gap-2 hover:cursor-pointer"
<Link href={AppRoute.SHARED_LINKS} class="text-sm">{$t('view_all')}</Link> onclick={onShare}
</div> >
<Icon path={mdiLink} size={24} />
<p class="text-sm">{$t('create_link')}</p>
</button>
<Stack gap={4}> {#if sharedLinks.length}
{#each sharedLinks as sharedLink} <a
<AlbumSharedLink {album} {sharedLink} /> href={AppRoute.SHARED_LINKS}
{/each} class="flex flex-col place-content-center place-items-center gap-2 hover:cursor-pointer"
</Stack> >
<Icon path={mdiShareCircle} size={24} />
<p class="text-sm">{$t('view_links')}</p>
</a>
{/if} {/if}
</div>
<Button leadingIcon={mdiLink} size="small" shape="round" fullWidth onclick={onShare}>{$t('create_link')}</Button>
</Stack>
</FullScreenModal> </FullScreenModal>
@@ -27,7 +27,7 @@
let sharedLink = $derived(sharedLinks.find(({ id }) => id === page.params.id)); let sharedLink = $derived(sharedLinks.find(({ id }) => id === page.params.id));
const refresh = async () => { const refresh = async () => {
sharedLinks = await getAllSharedLinks({}); sharedLinks = await getAllSharedLinks();
}; };
onMount(async () => { onMount(async () => {
@@ -97,7 +97,7 @@
</div> </div>
{/snippet} {/snippet}
<div class="w-full max-w-3xl m-auto"> <div>
{#if sharedLinks.length === 0} {#if sharedLinks.length === 0}
<div <div
class="flex place-content-center place-items-center rounded-lg bg-gray-100 dark:bg-immich-dark-gray dark:text-immich-gray p-12" class="flex place-content-center place-items-center rounded-lg bg-gray-100 dark:bg-immich-dark-gray dark:text-immich-gray p-12"