From c9a6820de7896aae3248fb0d219c35b1767ddb70 Mon Sep 17 00:00:00 2001 From: Matthias Rupp Date: Mon, 27 Feb 2023 21:15:25 +0100 Subject: [PATCH 1/3] chore(mobile): Favorite provider unit test (#1874) * Favorite provider tests * Remove unused mock * Add setUp function to avoid duplicate code --- .../favorite/providers/favorite_provider.dart | 18 +- mobile/pubspec.lock | 8 + mobile/pubspec.yaml | 1 + mobile/test/favorite_provider_test.dart | 104 +++++++ mobile/test/favorite_provider_test.mocks.dart | 259 ++++++++++++++++++ 5 files changed, 383 insertions(+), 7 deletions(-) create mode 100644 mobile/test/favorite_provider_test.dart create mode 100644 mobile/test/favorite_provider_test.mocks.dart diff --git a/mobile/lib/modules/favorite/providers/favorite_provider.dart b/mobile/lib/modules/favorite/providers/favorite_provider.dart index e702eeec62..af63a7de51 100644 --- a/mobile/lib/modules/favorite/providers/favorite_provider.dart +++ b/mobile/lib/modules/favorite/providers/favorite_provider.dart @@ -3,14 +3,15 @@ import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; class FavoriteSelectionNotifier extends StateNotifier> { - FavoriteSelectionNotifier(this.ref) : super({}) { - state = ref.watch(assetProvider).allAssets + FavoriteSelectionNotifier(this.assetsState, this.assetNotifier) : super({}) { + state = assetsState.allAssets .where((asset) => asset.isFavorite) .map((asset) => asset.id) .toSet(); } - final Ref ref; + final AssetsState assetsState; + final AssetNotifier assetNotifier; void _setFavoriteForAssetId(String id, bool favorite) { if (!favorite) { @@ -29,7 +30,7 @@ class FavoriteSelectionNotifier extends StateNotifier> { _setFavoriteForAssetId(asset.id, !_isFavorite(asset.id)); - await ref.watch(assetProvider.notifier).toggleFavorite( + await assetNotifier.toggleFavorite( asset, state.contains(asset.id), ); @@ -37,8 +38,8 @@ class FavoriteSelectionNotifier extends StateNotifier> { Future addToFavorites(Iterable assets) { state = state.union(assets.map((a) => a.id).toSet()); - final futures = assets.map((a) => - ref.watch(assetProvider.notifier).toggleFavorite( + final futures = assets.map((a) => + assetNotifier.toggleFavorite( a, true, ), @@ -50,7 +51,10 @@ class FavoriteSelectionNotifier extends StateNotifier> { final favoriteProvider = StateNotifierProvider>((ref) { - return FavoriteSelectionNotifier(ref); + return FavoriteSelectionNotifier( + ref.watch(assetProvider), + ref.watch(assetProvider.notifier), + ); }); final favoriteAssetProvider = StateProvider((ref) { diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index f2ae9ae46c..9f895fadd9 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -740,6 +740,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: "2a8a17b82b1bde04d514e75d90d634a0ac23f6cb4991f6098009dd56836aeafe" + url: "https://pub.dev" + source: hosted + version: "5.3.2" nested: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 624a00aeb1..7d28923d46 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -64,6 +64,7 @@ dev_dependencies: flutter_launcher_icons: "^0.9.2" flutter_native_splash: ^2.2.16 isar_generator: *isar_version + mockito: ^5.3.2 integration_test: sdk: flutter diff --git a/mobile/test/favorite_provider_test.dart b/mobile/test/favorite_provider_test.dart new file mode 100644 index 0000000000..38a24a70d1 --- /dev/null +++ b/mobile/test/favorite_provider_test.dart @@ -0,0 +1,104 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart'; +import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/providers/asset.provider.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), +]) +import 'favorite_provider_test.mocks.dart'; + +Asset _getTestAsset(String id, bool favorite) { + return Asset( + remoteId: id, + deviceAssetId: '', + deviceId: '', + ownerId: '', + fileCreatedAt: DateTime.now(), + fileModifiedAt: DateTime.now(), + durationInSeconds: 0, + fileName: '', + isFavorite: favorite, + ); +} + +void main() { + group("Test favoriteProvider", () { + + late MockAssetsState assetsState; + late MockAssetNotifier assetNotifier; + late ProviderContainer container; + late StateNotifierProvider> testFavoritesProvider; + + setUp(() { + assetsState = MockAssetsState(); + assetNotifier = MockAssetNotifier(); + container = ProviderContainer(); + + testFavoritesProvider = + StateNotifierProvider>((ref) { + return FavoriteSelectionNotifier( + assetsState, + assetNotifier, + ); + }); + },); + + test("Empty favorites provider", () { + when(assetsState.allAssets).thenReturn([]); + expect({}, container.read(testFavoritesProvider)); + }); + + test("Non-empty favorites provider", () { + when(assetsState.allAssets).thenReturn([ + _getTestAsset("001", false), + _getTestAsset("002", true), + _getTestAsset("003", false), + _getTestAsset("004", false), + _getTestAsset("005", true), + ]); + + expect({"002", "005"}, container.read(testFavoritesProvider)); + }); + + test("Toggle favorite", () { + when(assetNotifier.toggleFavorite(null, false)) + .thenAnswer((_) async => false); + + final testAsset1 = _getTestAsset("001", false); + final testAsset2 = _getTestAsset("002", true); + + when(assetsState.allAssets).thenReturn([testAsset1, testAsset2]); + + expect({"002"}, container.read(testFavoritesProvider)); + + container.read(testFavoritesProvider.notifier).toggleFavorite(testAsset2); + expect({}, container.read(testFavoritesProvider)); + + container.read(testFavoritesProvider.notifier).toggleFavorite(testAsset1); + expect({"001"}, container.read(testFavoritesProvider)); + }); + + test("Add favorites", () { + when(assetNotifier.toggleFavorite(null, false)) + .thenAnswer((_) async => false); + + when(assetsState.allAssets).thenReturn([]); + + expect({}, container.read(testFavoritesProvider)); + + container.read(testFavoritesProvider.notifier).addToFavorites( + [ + _getTestAsset("001", false), + _getTestAsset("002", false), + ], + ); + + expect({"001", "002"}, container.read(testFavoritesProvider)); + }); + }); +} diff --git a/mobile/test/favorite_provider_test.mocks.dart b/mobile/test/favorite_provider_test.mocks.dart new file mode 100644 index 0000000000..d79b009d4a --- /dev/null +++ b/mobile/test/favorite_provider_test.mocks.dart @@ -0,0 +1,259 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in immich_mobile/test/favorite_provider_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; + +import 'package:hooks_riverpod/hooks_riverpod.dart' as _i7; +import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart' + as _i6; +import 'package:immich_mobile/shared/models/asset.dart' as _i4; +import 'package:immich_mobile/shared/providers/asset.provider.dart' as _i2; +import 'package:logging/logging.dart' as _i3; +import 'package:mockito/mockito.dart' as _i1; +import 'package:state_notifier/state_notifier.dart' as _i8; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeAssetsState_0 extends _i1.SmartFake implements _i2.AssetsState { + _FakeAssetsState_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLogger_1 extends _i1.SmartFake implements _i3.Logger { + _FakeLogger_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [AssetsState]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAssetsState extends _i1.Mock implements _i2.AssetsState { + @override + List<_i4.Asset> get allAssets => (super.noSuchMethod( + Invocation.getter(#allAssets), + returnValue: <_i4.Asset>[], + returnValueForMissingStub: <_i4.Asset>[], + ) as List<_i4.Asset>); + @override + _i5.Future<_i2.AssetsState> withRenderDataStructure( + _i6.AssetGridLayoutParameters? layout) => + (super.noSuchMethod( + Invocation.method( + #withRenderDataStructure, + [layout], + ), + returnValue: _i5.Future<_i2.AssetsState>.value(_FakeAssetsState_0( + this, + Invocation.method( + #withRenderDataStructure, + [layout], + ), + )), + returnValueForMissingStub: + _i5.Future<_i2.AssetsState>.value(_FakeAssetsState_0( + this, + Invocation.method( + #withRenderDataStructure, + [layout], + ), + )), + ) as _i5.Future<_i2.AssetsState>); + @override + _i2.AssetsState withAdditionalAssets(List<_i4.Asset>? toAdd) => + (super.noSuchMethod( + Invocation.method( + #withAdditionalAssets, + [toAdd], + ), + returnValue: _FakeAssetsState_0( + this, + Invocation.method( + #withAdditionalAssets, + [toAdd], + ), + ), + returnValueForMissingStub: _FakeAssetsState_0( + this, + Invocation.method( + #withAdditionalAssets, + [toAdd], + ), + ), + ) as _i2.AssetsState); +} + +/// A class which mocks [AssetNotifier]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAssetNotifier extends _i1.Mock implements _i2.AssetNotifier { + @override + _i3.Logger get log => (super.noSuchMethod( + Invocation.getter(#log), + returnValue: _FakeLogger_1( + this, + Invocation.getter(#log), + ), + returnValueForMissingStub: _FakeLogger_1( + this, + Invocation.getter(#log), + ), + ) as _i3.Logger); + @override + set onError(_i7.ErrorListener? _onError) => super.noSuchMethod( + Invocation.setter( + #onError, + _onError, + ), + returnValueForMissingStub: null, + ); + @override + bool get mounted => (super.noSuchMethod( + Invocation.getter(#mounted), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + _i5.Stream<_i2.AssetsState> get stream => (super.noSuchMethod( + Invocation.getter(#stream), + returnValue: _i5.Stream<_i2.AssetsState>.empty(), + returnValueForMissingStub: _i5.Stream<_i2.AssetsState>.empty(), + ) as _i5.Stream<_i2.AssetsState>); + @override + _i2.AssetsState get state => (super.noSuchMethod( + Invocation.getter(#state), + returnValue: _FakeAssetsState_0( + this, + Invocation.getter(#state), + ), + returnValueForMissingStub: _FakeAssetsState_0( + this, + Invocation.getter(#state), + ), + ) as _i2.AssetsState); + @override + set state(_i2.AssetsState? value) => super.noSuchMethod( + Invocation.setter( + #state, + value, + ), + returnValueForMissingStub: null, + ); + @override + _i2.AssetsState get debugState => (super.noSuchMethod( + Invocation.getter(#debugState), + returnValue: _FakeAssetsState_0( + this, + Invocation.getter(#debugState), + ), + returnValueForMissingStub: _FakeAssetsState_0( + this, + Invocation.getter(#debugState), + ), + ) as _i2.AssetsState); + @override + bool get hasListeners => (super.noSuchMethod( + Invocation.getter(#hasListeners), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + _i5.Future rebuildAssetGridDataStructure() => (super.noSuchMethod( + Invocation.method( + #rebuildAssetGridDataStructure, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + void onNewAssetUploaded(_i4.Asset? newAsset) => super.noSuchMethod( + Invocation.method( + #onNewAssetUploaded, + [newAsset], + ), + returnValueForMissingStub: null, + ); + @override + dynamic deleteAssets(Set<_i4.Asset>? deleteAssets) => super.noSuchMethod( + Invocation.method( + #deleteAssets, + [deleteAssets], + ), + returnValueForMissingStub: null, + ); + @override + _i5.Future toggleFavorite( + _i4.Asset? asset, + bool? status, + ) => + (super.noSuchMethod( + Invocation.method( + #toggleFavorite, + [ + asset, + status, + ], + ), + returnValue: _i5.Future.value(false), + returnValueForMissingStub: _i5.Future.value(false), + ) as _i5.Future); + @override + bool updateShouldNotify( + _i2.AssetsState? old, + _i2.AssetsState? current, + ) => + (super.noSuchMethod( + Invocation.method( + #updateShouldNotify, + [ + old, + current, + ], + ), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + _i7.RemoveListener addListener( + _i8.Listener<_i2.AssetsState>? listener, { + bool? fireImmediately = true, + }) => + (super.noSuchMethod( + Invocation.method( + #addListener, + [listener], + {#fireImmediately: fireImmediately}, + ), + returnValue: () {}, + returnValueForMissingStub: () {}, + ) as _i7.RemoveListener); + @override + void dispose() => super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValueForMissingStub: null, + ); +} From 243c98a02e0c700fcbc9ceda5db978ccc326f6d8 Mon Sep 17 00:00:00 2001 From: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Date: Tue, 28 Feb 2023 00:13:39 +0100 Subject: [PATCH 2/3] feat(web): re-add version announcement (#1887) * feat(web): re-add version announcement * show notification for every update --- .../shared-components/announcement-box.svelte | 64 --------------- .../version-announcement-box.svelte | 82 +++++++++++++++++++ web/src/lib/utils/check-app-version.ts | 50 ----------- web/src/lib/utils/get-github-version.ts | 18 ++++ web/src/routes/+layout.server.ts | 6 +- web/src/routes/+layout.svelte | 8 ++ 6 files changed, 112 insertions(+), 116 deletions(-) delete mode 100644 web/src/lib/components/shared-components/announcement-box.svelte create mode 100644 web/src/lib/components/shared-components/version-announcement-box.svelte delete mode 100644 web/src/lib/utils/check-app-version.ts create mode 100644 web/src/lib/utils/get-github-version.ts diff --git a/web/src/lib/components/shared-components/announcement-box.svelte b/web/src/lib/components/shared-components/announcement-box.svelte deleted file mode 100644 index e6f378909a..0000000000 --- a/web/src/lib/components/shared-components/announcement-box.svelte +++ /dev/null @@ -1,64 +0,0 @@ - - -
- console.log('Click outside')}> -
-

🎉 NEW VERSION AVAILABLE 🎉

-
- -
-
- Hi friend, there is a new release of IMMICH, please take your time to visit the - release note - and ensure your docker-compose, and .env setup is up-to-date to prevent - any misconfigurations, especially if you use WatchTower or any mechanism that handles updating - your application automatically. -
- - {#if remoteVersion == 'v1.11.0_17-dev'} -
- This specific version v1.11.0_17-dev includes changes in - the docker-compose setup that added additional containters. Please make sure to update the - docker-compose file, pull new images and check your setup for the latest features and bug - fixes. -
- {/if} -
- -
Your friend, Alex
-
- Local Version {localVersion} -
- Remote Version {remoteVersion} -
- -
- -
-
-
-
diff --git a/web/src/lib/components/shared-components/version-announcement-box.svelte b/web/src/lib/components/shared-components/version-announcement-box.svelte new file mode 100644 index 0000000000..fde48ba3e0 --- /dev/null +++ b/web/src/lib/components/shared-components/version-announcement-box.svelte @@ -0,0 +1,82 @@ + + +{#if showModal} + (showModal = false)}> +
+

🎉 NEW VERSION AVAILABLE 🎉

+ +
+ Hi friend, there is a new release of + IMMICH, please take your time to visit the + release notes + and ensure your docker-compose, and .env setup is up-to-date to prevent + any misconfigurations, especially if you use WatchTower or any mechanism that handles updating + your application automatically. +
+ +
Your friend, Alex
+ +
+ Server Version: {serverVersionName} +
+ Latest Version: {githubVersion} +
+ +
+ +
+
+
+{/if} diff --git a/web/src/lib/utils/check-app-version.ts b/web/src/lib/utils/check-app-version.ts deleted file mode 100644 index cacf56dfd3..0000000000 --- a/web/src/lib/utils/check-app-version.ts +++ /dev/null @@ -1,50 +0,0 @@ -type CheckAppVersionReponse = { - shouldShowAnnouncement: boolean; - localVersion?: string; - remoteVersion?: string; -}; - -type GithubRelease = { - tag_name: string; -}; - -export const checkAppVersion = async (): Promise => { - const res = await fetch('https://api.github.com/repos/immich-app/immich/releases/latest', { - headers: { - Accept: 'application/vnd.github.v3+json' - } - }); - - if (res.status == 200) { - const latestRelease = (await res.json()) as GithubRelease; - const appVersion = localStorage.getItem('appVersion'); - - if (!appVersion) { - return { - shouldShowAnnouncement: false, - remoteVersion: latestRelease.tag_name, - localVersion: 'empty' - }; - } - - if (appVersion != latestRelease.tag_name) { - return { - shouldShowAnnouncement: true, - remoteVersion: latestRelease.tag_name, - localVersion: appVersion - }; - } - - return { - shouldShowAnnouncement: false, - remoteVersion: latestRelease.tag_name, - localVersion: appVersion - }; - } else { - return { - shouldShowAnnouncement: false, - remoteVersion: '0', - localVersion: '0' - }; - } -}; diff --git a/web/src/lib/utils/get-github-version.ts b/web/src/lib/utils/get-github-version.ts new file mode 100644 index 0000000000..e3ae98190e --- /dev/null +++ b/web/src/lib/utils/get-github-version.ts @@ -0,0 +1,18 @@ +import axios from 'axios'; + +type GithubRelease = { + tag_name: string; +}; + +export const getGithubVersion = async (): Promise => { + const { data } = await axios.get( + 'https://api.github.com/repos/immich-app/immich/releases/latest', + { + headers: { + Accept: 'application/vnd.github.v3+json' + } + } + ); + + return data.tag_name; +}; diff --git a/web/src/routes/+layout.server.ts b/web/src/routes/+layout.server.ts index 08b7f2518c..bf504b7efa 100644 --- a/web/src/routes/+layout.server.ts +++ b/web/src/routes/+layout.server.ts @@ -1,5 +1,7 @@ import type { LayoutServerLoad } from './$types'; -export const load = (async ({ locals: { user } }) => { - return { user }; +export const load = (async ({ locals: { api, user } }) => { + const { data: serverVersion } = await api.serverInfoApi.getServerVersion(); + + return { serverVersion, user }; }) satisfies LayoutServerLoad; diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 748d5078cf..7d3bf9ccf9 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -7,7 +7,9 @@ import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte'; import UploadPanel from '$lib/components/shared-components/upload-panel.svelte'; import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte'; + import VersionAnnouncementBox from '$lib/components/shared-components/version-announcement-box.svelte'; import faviconUrl from '$lib/assets/favicon.png'; + import type { LayoutData } from './$types'; let showNavigationLoadingBar = false; @@ -18,6 +20,8 @@ afterNavigate(() => { showNavigationLoadingBar = false; }); + + export let data: LayoutData; @@ -50,3 +54,7 @@ + +{#if data.user?.isAdmin} + +{/if} From 25cff6a74886b36a0fe2679f199e1ad1814b5139 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 27 Feb 2023 18:28:45 -0600 Subject: [PATCH 3/3] fix(server) long album load time on Album and Sharing page (#1890) * chore: update package-lock.json version * rfix(server) long album load time * remove all eagerness * generate index * remove console.log * remove deadcode * fix: shared link album owner --- .../immich/src/api-v1/album/album-repository.ts | 5 +---- .../apps/immich/src/api-v1/album/album.service.ts | 2 +- server/libs/infra/src/db/entities/album.entity.ts | 6 +++--- server/libs/infra/src/db/entities/asset.entity.ts | 6 +++--- .../infra/src/db/entities/shared-link.entity.ts | 1 + ...7535643119-AddIndexForAlbumInSharedLinkTable.ts | 14 ++++++++++++++ .../src/db/repository/shared-link.repository.ts | 5 ++++- web/src/routes/(user)/albums/+page.svelte | 4 ---- 8 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 server/libs/infra/src/db/migrations/1677535643119-AddIndexForAlbumInSharedLinkTable.ts diff --git a/server/apps/immich/src/api-v1/album/album-repository.ts b/server/apps/immich/src/api-v1/album/album-repository.ts index f71d3dfa29..e0cf3d6281 100644 --- a/server/apps/immich/src/api-v1/album/album-repository.ts +++ b/server/apps/immich/src/api-v1/album/album-repository.ts @@ -79,6 +79,7 @@ export class AlbumRepository implements IAlbumRepository { const queryProperties: FindManyOptions = { relations: { sharedUsers: true, assets: true, sharedLinks: true, owner: true }, + select: { assets: { id: true } }, order: { assets: { fileCreatedAt: 'ASC' }, createdAt: 'ASC' }, }; @@ -112,10 +113,6 @@ export class AlbumRepository implements IAlbumRepository { }); } - const albums = await albumsQuery; - - albums.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf()); - return albumsQuery; } diff --git a/server/apps/immich/src/api-v1/album/album.service.ts b/server/apps/immich/src/api-v1/album/album.service.ts index 58d44b45f7..525cf50658 100644 --- a/server/apps/immich/src/api-v1/album/album.service.ts +++ b/server/apps/immich/src/api-v1/album/album.service.ts @@ -66,11 +66,11 @@ export class AlbumService { */ async getAllAlbums(authUser: AuthUserDto, getAlbumsDto: GetAlbumsDto): Promise { let albums: AlbumEntity[]; - if (typeof getAlbumsDto.assetId === 'string') { albums = await this.albumRepository.getListByAssetId(authUser.id, getAlbumsDto.assetId); } else { albums = await this.albumRepository.getList(authUser.id, getAlbumsDto); + if (getAlbumsDto.shared) { const publicSharingAlbums = await this.albumRepository.getPublicSharingList(authUser.id); albums = [...albums, ...publicSharingAlbums]; diff --git a/server/libs/infra/src/db/entities/album.entity.ts b/server/libs/infra/src/db/entities/album.entity.ts index 1bc481f135..565fdf217c 100644 --- a/server/libs/infra/src/db/entities/album.entity.ts +++ b/server/libs/infra/src/db/entities/album.entity.ts @@ -18,7 +18,7 @@ export class AlbumEntity { @PrimaryGeneratedColumn('uuid') id!: string; - @ManyToOne(() => UserEntity, { eager: true, onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) + @ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) owner!: UserEntity; @Column() @@ -36,11 +36,11 @@ export class AlbumEntity { @Column({ comment: 'Asset ID to be used as thumbnail', type: 'varchar', nullable: true }) albumThumbnailAssetId!: string | null; - @ManyToMany(() => UserEntity, { eager: true }) + @ManyToMany(() => UserEntity) @JoinTable() sharedUsers!: UserEntity[]; - @ManyToMany(() => AssetEntity, { eager: true }) + @ManyToMany(() => AssetEntity) @JoinTable() assets!: AssetEntity[]; diff --git a/server/libs/infra/src/db/entities/asset.entity.ts b/server/libs/infra/src/db/entities/asset.entity.ts index de8ee22959..6c7b76f15f 100644 --- a/server/libs/infra/src/db/entities/asset.entity.ts +++ b/server/libs/infra/src/db/entities/asset.entity.ts @@ -27,7 +27,7 @@ export class AssetEntity { @Column() deviceAssetId!: string; - @ManyToOne(() => UserEntity, { eager: true, onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) + @ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) owner!: UserEntity; @Column() @@ -92,11 +92,11 @@ export class AssetEntity { @OneToOne(() => SmartInfoEntity, (smartInfoEntity) => smartInfoEntity.asset) smartInfo?: SmartInfoEntity; - @ManyToMany(() => TagEntity, (tag) => tag.assets, { cascade: true, eager: true }) + @ManyToMany(() => TagEntity, (tag) => tag.assets, { cascade: true }) @JoinTable({ name: 'tag_asset' }) tags!: TagEntity[]; - @ManyToMany(() => SharedLinkEntity, (link) => link.assets, { cascade: true, eager: true }) + @ManyToMany(() => SharedLinkEntity, (link) => link.assets, { cascade: true }) @JoinTable({ name: 'shared_link__asset' }) sharedLinks!: SharedLinkEntity[]; } diff --git a/server/libs/infra/src/db/entities/shared-link.entity.ts b/server/libs/infra/src/db/entities/shared-link.entity.ts index 5825d8b080..1995d70f08 100644 --- a/server/libs/infra/src/db/entities/shared-link.entity.ts +++ b/server/libs/infra/src/db/entities/shared-link.entity.ts @@ -52,6 +52,7 @@ export class SharedLinkEntity { @ManyToMany(() => AssetEntity, (asset) => asset.sharedLinks) assets!: AssetEntity[]; + @Index('IDX_sharedlink_albumId') @ManyToOne(() => AlbumEntity, (album) => album.sharedLinks) album?: AlbumEntity; } diff --git a/server/libs/infra/src/db/migrations/1677535643119-AddIndexForAlbumInSharedLinkTable.ts b/server/libs/infra/src/db/migrations/1677535643119-AddIndexForAlbumInSharedLinkTable.ts new file mode 100644 index 0000000000..f3fb4a6c63 --- /dev/null +++ b/server/libs/infra/src/db/migrations/1677535643119-AddIndexForAlbumInSharedLinkTable.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddIndexForAlbumInSharedLinkTable1677535643119 implements MigrationInterface { + name = 'AddIndexForAlbumInSharedLinkTable1677535643119' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE INDEX "IDX_sharedlink_albumId" ON "shared_links" ("albumId") `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "public"."IDX_sharedlink_albumId"`); + } + +} diff --git a/server/libs/infra/src/db/repository/shared-link.repository.ts b/server/libs/infra/src/db/repository/shared-link.repository.ts index baacc07a50..d999c28f79 100644 --- a/server/libs/infra/src/db/repository/shared-link.repository.ts +++ b/server/libs/infra/src/db/repository/shared-link.repository.ts @@ -26,6 +26,7 @@ export class SharedLinkRepository implements ISharedLinkRepository { assets: { exifInfo: true, }, + owner: true, }, }, order: { @@ -49,7 +50,9 @@ export class SharedLinkRepository implements ISharedLinkRepository { }, relations: { assets: true, - album: true, + album: { + owner: true, + }, }, order: { createdAt: 'DESC', diff --git a/web/src/routes/(user)/albums/+page.svelte b/web/src/routes/(user)/albums/+page.svelte index 0b01b04fce..95848d2014 100644 --- a/web/src/routes/(user)/albums/+page.svelte +++ b/web/src/routes/(user)/albums/+page.svelte @@ -1,7 +1,6 @@