From 84d824d6a7e16ee813eca186cff6bdd434190e96 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 20 May 2024 18:09:10 -0400 Subject: [PATCH] refactor: library type (#9525) --- docs/docs/administration/repair-page.md | 2 +- docs/docs/features/libraries.md | 4 - e2e/src/api/specs/asset.e2e-spec.ts | 21 -- e2e/src/api/specs/library.e2e-spec.ts | 158 +-------------- e2e/src/responses.ts | 5 - mobile/openapi/.openapi-generator/FILES | 3 - mobile/openapi/README.md | Bin 27101 -> 27063 bytes mobile/openapi/doc/AssetApi.md | Bin 39428 -> 39293 bytes mobile/openapi/doc/AssetResponseDto.md | Bin 2082 -> 2133 bytes mobile/openapi/doc/CreateLibraryDto.md | Bin 655 -> 604 bytes mobile/openapi/doc/LibraryApi.md | Bin 18820 -> 18673 bytes mobile/openapi/doc/LibraryResponseDto.md | Bin 833 -> 782 bytes mobile/openapi/doc/LibraryType.md | Bin 377 -> 0 bytes mobile/openapi/lib/api.dart | Bin 9697 -> 9665 bytes mobile/openapi/lib/api/asset_api.dart | Bin 36425 -> 36176 bytes mobile/openapi/lib/api/library_api.dart | Bin 13975 -> 13725 bytes mobile/openapi/lib/api_client.dart | Bin 26140 -> 26051 bytes mobile/openapi/lib/api_helper.dart | Bin 6344 -> 6244 bytes .../openapi/lib/model/asset_response_dto.dart | Bin 14269 -> 14403 bytes .../openapi/lib/model/create_library_dto.dart | Bin 4480 -> 4278 bytes .../lib/model/library_response_dto.dart | Bin 5561 -> 5359 bytes mobile/openapi/lib/model/library_type.dart | Bin 2587 -> 0 bytes mobile/openapi/test/asset_api_test.dart | Bin 3850 -> 3832 bytes .../openapi/test/asset_response_dto_test.dart | Bin 4182 -> 4230 bytes .../openapi/test/create_library_dto_test.dart | Bin 1051 -> 953 bytes mobile/openapi/test/library_api_test.dart | Bin 1726 -> 1706 bytes .../test/library_response_dto_test.dart | Bin 1567 -> 1469 bytes mobile/openapi/test/library_type_test.dart | Bin 419 -> 0 bytes open-api/immich-openapi-specs.json | 36 +--- open-api/typescript-sdk/src/fetch-client.ts | 18 +- server/src/controllers/library.controller.ts | 7 +- server/src/cores/access.core.ts | 2 +- server/src/cores/user.core.ts | 12 +- server/src/dtos/asset-response.dto.ts | 3 +- server/src/dtos/asset-v1.dto.ts | 5 +- server/src/dtos/library.dto.ts | 19 +- server/src/entities/asset.entity.ts | 17 +- server/src/entities/library.entity.ts | 8 - server/src/interfaces/access.interface.ts | 4 - server/src/interfaces/asset.interface.ts | 2 +- server/src/interfaces/library.interface.ts | 6 +- .../1715804005643-RemoveLibraryType.ts | 29 +++ server/src/queries/access.repository.sql | 14 -- server/src/queries/asset.repository.sql | 26 +-- server/src/queries/library.repository.sql | 46 ----- server/src/queries/user.repository.sql | 4 +- server/src/repositories/access.repository.ts | 25 --- server/src/repositories/asset.repository.ts | 14 +- server/src/repositories/library.repository.ts | 31 +-- server/src/repositories/user.repository.ts | 4 +- server/src/services/asset-v1.service.spec.ts | 4 - server/src/services/asset-v1.service.ts | 38 ++-- server/src/services/asset.service.ts | 5 +- server/src/services/download.service.spec.ts | 3 - server/src/services/library.service.spec.ts | 186 +++--------------- server/src/services/library.service.ts | 59 ++---- server/src/services/metadata.service.ts | 2 +- server/test/fixtures/asset.stub.ts | 40 ---- server/test/fixtures/error.stub.ts | 5 - server/test/fixtures/library.stub.ts | 22 +-- server/test/fixtures/shared-link.stub.ts | 3 - .../repositories/access.repository.mock.ts | 5 - .../repositories/library.repository.mock.ts | 2 - .../forms/library-scan-settings-form.svelte | 6 +- .../admin/library-management/+page.svelte | 30 +-- web/src/routes/admin/repair/+page.svelte | 2 +- 66 files changed, 155 insertions(+), 782 deletions(-) delete mode 100644 mobile/openapi/doc/LibraryType.md delete mode 100644 mobile/openapi/lib/model/library_type.dart delete mode 100644 mobile/openapi/test/library_type_test.dart create mode 100644 server/src/migrations/1715804005643-RemoveLibraryType.ts diff --git a/docs/docs/administration/repair-page.md b/docs/docs/administration/repair-page.md index e5022970a..f230c6d58 100644 --- a/docs/docs/administration/repair-page.md +++ b/docs/docs/administration/repair-page.md @@ -18,7 +18,7 @@ In any other situation, there are 3 different options that can appear: - MATCHES - These files are matched by their checksums. -- OFFLINE PATHS - These files are the result of manually deleting files in the upload library or a failed file move in the past (losing track of a file). +- OFFLINE PATHS - These files are the result of manually deleting files from immich or a failed file move in the past (losing track of a file). - UNTRACKED FILES - These files are not tracked by the application. They can be the result of failed moves, interrupted uploads, or left behind due to a bug. diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md index 40b4b56b0..782dfdac1 100644 --- a/docs/docs/features/libraries.md +++ b/docs/docs/features/libraries.md @@ -4,10 +4,6 @@ Immich supports the creation of libraries which is a top-level asset container. Currently, there are two types of libraries: traditional upload libraries that can sync with a mobile device, and external libraries, that keeps up to date with files on disk. Libraries are different from albums in that an asset can belong to multiple albums but only one library, and deleting a library deletes all assets contained within. As of August 2023, this is a new feature and libraries have a lot of potential for future development beyond what is documented here. This document attempts to describe the current state of libraries. -## The Upload Library - -Immich comes preconfigured with an upload library for each user. All assets uploaded to Immich are added to this library. This library can be renamed, but not deleted. The upload library is the only library that can be synced with a mobile device. No items in an upload library is allowed to have the same sha1 hash as another item in the same library in order to prevent duplicates. - ## External Libraries External libraries tracks assets stored outside of Immich, i.e. in the file system. When the external library is scanned, Immich will read the metadata from the file and create an asset in the library for each image or video file. These items will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc. diff --git a/e2e/src/api/specs/asset.e2e-spec.ts b/e2e/src/api/specs/asset.e2e-spec.ts index 050fa9b1e..50b84fd9b 100644 --- a/e2e/src/api/specs/asset.e2e-spec.ts +++ b/e2e/src/api/specs/asset.e2e-spec.ts @@ -2,10 +2,8 @@ import { AssetFileUploadResponseDto, AssetResponseDto, AssetTypeEnum, - LibraryResponseDto, LoginResponseDto, SharedLinkType, - getAllLibraries, getAssetInfo, updateAssets, } from '@immich/sdk'; @@ -819,25 +817,6 @@ describe('/asset', () => { expect(duplicate).toBe(true); }); - it("should not upload to another user's library", async () => { - const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) }); - const library = libraries.find((library) => library.ownerId === user1.userId) as LibraryResponseDto; - - const { body, status } = await request(app) - .post('/asset/upload') - .set('Authorization', `Bearer ${admin.accessToken}`) - .field('libraryId', library.id) - .field('deviceAssetId', 'example-image') - .field('deviceId', 'e2e') - .field('fileCreatedAt', new Date().toISOString()) - .field('fileModifiedAt', new Date().toISOString()) - .field('duration', '0:00:00.000000') - .attach('assetData', makeRandomImage(), 'example.png'); - - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('Not found or no asset.upload access')); - }); - it('should update the used quota', async () => { const { body, status } = await request(app) .post('/asset/upload') diff --git a/e2e/src/api/specs/library.e2e-spec.ts b/e2e/src/api/specs/library.e2e-spec.ts index 18becec77..f31a20e27 100644 --- a/e2e/src/api/specs/library.e2e-spec.ts +++ b/e2e/src/api/specs/library.e2e-spec.ts @@ -1,11 +1,4 @@ -import { - LibraryResponseDto, - LibraryType, - LoginResponseDto, - ScanLibraryDto, - getAllLibraries, - scanLibrary, -} from '@immich/sdk'; +import { LibraryResponseDto, LoginResponseDto, ScanLibraryDto, getAllLibraries, scanLibrary } from '@immich/sdk'; import { cpSync, existsSync } from 'node:fs'; import { Socket } from 'socket.io-client'; import { userDto, uuidDto } from 'src/fixtures'; @@ -29,7 +22,7 @@ describe('/library', () => { admin = await utils.adminSetup(); await utils.resetAdminConfig(admin.accessToken); user = await utils.userSetup(admin.accessToken, userDto.user1); - library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, type: LibraryType.External }); + library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId }); websocket = await utils.connectWebsocket(admin.accessToken); utils.createImageFile(`${testAssetDir}/temp/directoryA/assetA.png`); utils.createImageFile(`${testAssetDir}/temp/directoryB/assetB.png`); @@ -50,24 +43,6 @@ describe('/library', () => { expect(status).toBe(401); expect(body).toEqual(errorDto.unauthorized); }); - - it('should start with a default upload library', async () => { - const { status, body } = await request(app).get('/library').set('Authorization', `Bearer ${admin.accessToken}`); - expect(status).toBe(200); - expect(body).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - ownerId: admin.userId, - type: LibraryType.Upload, - name: 'Default Library', - refreshedAt: null, - assetCount: 0, - importPaths: [], - exclusionPatterns: [], - }), - ]), - ); - }); }); describe('POST /library', () => { @@ -81,7 +56,7 @@ describe('/library', () => { const { status, body } = await request(app) .post('/library') .set('Authorization', `Bearer ${user.accessToken}`) - .send({ ownerId: admin.userId, type: LibraryType.External }); + .send({ ownerId: admin.userId }); expect(status).toBe(403); expect(body).toEqual(errorDto.forbidden); @@ -91,13 +66,12 @@ describe('/library', () => { const { status, body } = await request(app) .post('/library') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ ownerId: admin.userId, type: LibraryType.External }); + .send({ ownerId: admin.userId }); expect(status).toBe(201); expect(body).toEqual( expect.objectContaining({ ownerId: admin.userId, - type: LibraryType.External, name: 'New External Library', refreshedAt: null, assetCount: 0, @@ -113,7 +87,6 @@ describe('/library', () => { .set('Authorization', `Bearer ${admin.accessToken}`) .send({ ownerId: admin.userId, - type: LibraryType.External, name: 'My Awesome Library', importPaths: ['/path/to/import'], exclusionPatterns: ['**/Raw/**'], @@ -134,7 +107,6 @@ describe('/library', () => { .set('Authorization', `Bearer ${admin.accessToken}`) .send({ ownerId: admin.userId, - type: LibraryType.External, name: 'My Awesome Library', importPaths: ['/path', '/path'], exclusionPatterns: ['**/Raw/**'], @@ -150,7 +122,6 @@ describe('/library', () => { .set('Authorization', `Bearer ${admin.accessToken}`) .send({ ownerId: admin.userId, - type: LibraryType.External, name: 'My Awesome Library', importPaths: ['/path/to/import'], exclusionPatterns: ['**/Raw/**', '**/Raw/**'], @@ -159,60 +130,6 @@ describe('/library', () => { expect(status).toBe(400); expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"])); }); - - it('should create an upload library with defaults', async () => { - const { status, body } = await request(app) - .post('/library') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ ownerId: admin.userId, type: LibraryType.Upload }); - - expect(status).toBe(201); - expect(body).toEqual( - expect.objectContaining({ - ownerId: admin.userId, - type: LibraryType.Upload, - name: 'New Upload Library', - refreshedAt: null, - assetCount: 0, - importPaths: [], - exclusionPatterns: [], - }), - ); - }); - - it('should create an upload library with options', async () => { - const { status, body } = await request(app) - .post('/library') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ ownerId: admin.userId, type: LibraryType.Upload, name: 'My Awesome Library' }); - - expect(status).toBe(201); - expect(body).toEqual( - expect.objectContaining({ - name: 'My Awesome Library', - }), - ); - }); - - it('should not allow upload libraries to have import paths', async () => { - const { status, body } = await request(app) - .post('/library') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ ownerId: admin.userId, type: LibraryType.Upload, importPaths: ['/path/to/import'] }); - - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('Upload libraries cannot have import paths')); - }); - - it('should not allow upload libraries to have exclusion patterns', async () => { - const { status, body } = await request(app) - .post('/library') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ ownerId: admin.userId, type: LibraryType.Upload, exclusionPatterns: ['**/Raw/**'] }); - - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('Upload libraries cannot have exclusion patterns')); - }); }); describe('PUT /library/:id', () => { @@ -332,10 +249,7 @@ describe('/library', () => { }); it('should get library by id', async () => { - const library = await utils.createLibrary(admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.External, - }); + const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId }); const { status, body } = await request(app) .get(`/library/${library.id}`) @@ -345,7 +259,6 @@ describe('/library', () => { expect(body).toEqual( expect.objectContaining({ ownerId: admin.userId, - type: LibraryType.External, name: 'New External Library', refreshedAt: null, assetCount: 0, @@ -373,24 +286,9 @@ describe('/library', () => { expect(body).toEqual(errorDto.unauthorized); }); - it('should not scan an upload library', async () => { - const library = await utils.createLibrary(admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.Upload, - }); - - const { status, body } = await request(app) - .post(`/library/${library.id}/scan`) - .set('Authorization', `Bearer ${admin.accessToken}`); - - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('Can only refresh external libraries')); - }); - it('should scan external library', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp/directoryA`], }); @@ -406,7 +304,6 @@ describe('/library', () => { it('should scan external library with exclusion pattern', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp`], exclusionPatterns: ['**/directoryA'], }); @@ -423,7 +320,6 @@ describe('/library', () => { it('should scan multiple import paths', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp/directoryA`, `${testAssetDirInternal}/temp/directoryB`], }); @@ -440,7 +336,6 @@ describe('/library', () => { it('should pick up new files', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp`], }); @@ -466,7 +361,6 @@ describe('/library', () => { utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.png`); const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp`], }); @@ -493,7 +387,6 @@ describe('/library', () => { it('should scan new files', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp`], }); @@ -521,7 +414,6 @@ describe('/library', () => { it('should reimport modified files', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp`], }); @@ -549,7 +441,6 @@ describe('/library', () => { it('should not reimport unmodified files', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp`], }); @@ -579,7 +470,6 @@ describe('/library', () => { it('should reimport all files', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp`], }); @@ -617,7 +507,6 @@ describe('/library', () => { it('should remove offline files', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp`], }); @@ -658,7 +547,6 @@ describe('/library', () => { it('should not remove online files', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp`], }); @@ -737,37 +625,8 @@ describe('/library', () => { expect(body).toEqual(errorDto.unauthorized); }); - it('should not delete the last upload library', async () => { - const libraries = await getAllLibraries( - { $type: LibraryType.Upload }, - { headers: asBearerAuth(admin.accessToken) }, - ); - - const adminLibraries = libraries.filter((library) => library.ownerId === admin.userId); - expect(adminLibraries.length).toBeGreaterThanOrEqual(1); - const lastLibrary = adminLibraries.pop() as LibraryResponseDto; - - // delete all but the last upload library - for (const library of adminLibraries) { - const { status } = await request(app) - .delete(`/library/${library.id}`) - .set('Authorization', `Bearer ${admin.accessToken}`); - expect(status).toBe(204); - } - - const { status, body } = await request(app) - .delete(`/library/${lastLibrary.id}`) - .set('Authorization', `Bearer ${admin.accessToken}`); - - expect(body).toEqual(errorDto.noDeleteUploadLibrary); - expect(status).toBe(400); - }); - it('should delete an external library', async () => { - const library = await utils.createLibrary(admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.External, - }); + const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId }); const { status, body } = await request(app) .delete(`/library/${library.id}`) @@ -776,7 +635,7 @@ describe('/library', () => { expect(status).toBe(204); expect(body).toEqual({}); - const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) }); + const libraries = await getAllLibraries({ headers: asBearerAuth(admin.accessToken) }); expect(libraries).not.toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -789,7 +648,6 @@ describe('/library', () => { it('should delete an external library with assets', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, - type: LibraryType.External, importPaths: [`${testAssetDirInternal}/temp`], }); @@ -803,7 +661,7 @@ describe('/library', () => { expect(status).toBe(204); expect(body).toEqual({}); - const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) }); + const libraries = await getAllLibraries({ headers: asBearerAuth(admin.accessToken) }); expect(libraries).not.toEqual( expect.arrayContaining([ expect.objectContaining({ diff --git a/e2e/src/responses.ts b/e2e/src/responses.ts index 37892be0c..afe3334a7 100644 --- a/e2e/src/responses.ts +++ b/e2e/src/responses.ts @@ -51,11 +51,6 @@ export const errorDto = { statusCode: 400, message: 'The server already has an admin', }, - noDeleteUploadLibrary: { - error: 'Bad Request', - statusCode: 400, - message: 'Cannot delete the last upload library', - }, }; export const signupResponseDto = { diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 69172bd97..74f3ac621 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -92,7 +92,6 @@ doc/JobStatusDto.md doc/LibraryApi.md doc/LibraryResponseDto.md doc/LibraryStatsResponseDto.md -doc/LibraryType.md doc/LogLevel.md doc/LoginCredentialDto.md doc/LoginResponseDto.md @@ -331,7 +330,6 @@ lib/model/job_settings_dto.dart lib/model/job_status_dto.dart lib/model/library_response_dto.dart lib/model/library_stats_response_dto.dart -lib/model/library_type.dart lib/model/log_level.dart lib/model/login_credential_dto.dart lib/model/login_response_dto.dart @@ -531,7 +529,6 @@ test/job_status_dto_test.dart test/library_api_test.dart test/library_response_dto_test.dart test/library_stats_response_dto_test.dart -test/library_type_test.dart test/log_level_test.dart test/login_credential_dto_test.dart test/login_response_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 048d5b00a0d5df4718be1eaeff826a2190a6dc73..090182fc397e78b7d9c2e60d25f0c24e98ba16a8 100644 GIT binary patch delta 14 Wcmcb6nQ{AN#tk`6t5MU|c@lmAba-7MO(n~e=3FnLzK?dEfnOc`McHvgY&!ORMl Oocvy0X|vIEZzcd-fgS7s diff --git a/mobile/openapi/doc/AssetResponseDto.md b/mobile/openapi/doc/AssetResponseDto.md index 41a628bd540183fc36d9ee0cb9d9f4d8035c9137..3ff4db3402df786adc3e198455a2848206426607 100644 GIT binary patch delta 33 pcmZ1^a8+Q#b>_(_%&G!rdIkzL3eoumC7Jnoi8-;GZ!&wb0074v3!4A{ delta 16 YcmcaAut;FTb>_(n7}+)pu(+}S060bkYybcN diff --git a/mobile/openapi/doc/CreateLibraryDto.md b/mobile/openapi/doc/CreateLibraryDto.md index f7d5c0ecfe9da2b3607c9c698a0a04677d7d31de..662c9b0d4bf01eafdf30dfe7dee67f18bd33e08a 100644 GIT binary patch delta 11 ScmeBYy~DC$JLBY6j7tF>4g~-J delta 56 zcmcb^($BhKJEM-4R!L<+s+N{QjY71RmQQ961Rg>u#EGkrJX>%ZDZe(z8X>N2N oWN&42AZ~ATAZ}%4WFTQ~c_46Ma$#*{bY*fblU_db*5b^*3 diff --git a/mobile/openapi/doc/LibraryType.md b/mobile/openapi/doc/LibraryType.md deleted file mode 100644 index 0bd5a3a14bee57631feea18a69c40ca92c35eb81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 377 zcma)1v2Fq}4Bh<|mTov05_XuVU_b(ds%{AB(u;wpB#v@sKn2DuRVj zPYShH+N8Wp;;4hQ@r1uvOmdr1*CrRmamZ-Rk{Rlu^6&vE^$G)Qw;%aW6vkBvg(ug^ zZa2_+^f{l-R&(ubWw||j3Sx?{+H+EU!fuFv*VE17&D)$;QLvF+kbe?4!zc4009*lW CfplB| diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 110c4f757e7275c2b9b2e79d1447a6f647ae04cc..8cd0f4365ca15e6d138cc40441ab0b375bb6274d 100644 GIT binary patch delta 12 TcmaFpeb9S@u+U~>p>@IlBwqx_ delta 20 ccmX@;{m^@Zu+Zeag6xx-L?t#039T0f08_sP(EtDd diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index dba33fc181ba995d43a4b0de7cb99cc8ce4a8c72..f3c8389ab423121e0ab2a18eca764374092ba7ca 100644 GIT binary patch delta 58 zcmX>(hv~vBrVXA|lld$7C$H^r-n_R;h;j3y>Q(HUSzF3kK*F2XcEm7FepKx_*}+qM Na!uFI&E4Jmm;m%@8RY-~ delta 183 zcmcaGi|OPXrVXA|lN)R~ICC?P)%d6NJ1%pe9GV{{y z72rxXudZIh&I(pPSy5VKa$S=Ol77P`aRhI(OLG?srUjetw#Q&Gc=GCM@5v6H;*)1~ Q{uF|`#|p{bzOF+|0D_%Kh5!Hn diff --git a/mobile/openapi/lib/api/library_api.dart b/mobile/openapi/lib/api/library_api.dart index 7a980535fa6ede51d7fa63332701a342db81121f..48f46e6e1b6c0b3e2fae983d82d934461b46a524 100644 GIT binary patch delta 53 zcmV-50LuTDZJledfDDtg3@wwb6Hb%h0y~p{4TzIJAy$*O4waLD4>FTb1tpX14;-`c LEZzaL^bw;ZSyUD> delta 238 zcmbQ6Jw10r1J7iC33)-E%%q~kqRNoUg49@r5+FI*kx6v&L>?KjY6T=Yd$632Lha-} zWxvS}n5-wq3wH!(rYUHE^e8CWD&&>s5P2OlP!S0ipRFqg$Ir*Z!@#YIApA7&A&<_y+ diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index a43ed6ecbf47ab9f31b1553723cfb80fcf10cf00..f945dbb115e4ec487033f2d9b05771a51b171952 100644 GIT binary patch delta 12 TcmX?M_{3ntF0RdT+^cv2CTay0 delta 33 mcmaE2aKdoIE-u#0q@u*4$qqas>^@*-iZb)k?KiV=yYomY*xD-OmFDCq*ee() zSSjRWCKV+XReGihVaJ_G^Is*uDWpQMn;Eel5slXDmnv!53j3X|C( lD+Fm~ASjc8A{Db!B7+J6A+y#j%nAo-Vsc?}c}bI)H>-G797X^D diff --git a/mobile/openapi/lib/model/create_library_dto.dart b/mobile/openapi/lib/model/create_library_dto.dart index 7d1ce0eea6a7c66e724127ab5c262e4362bf519e..65ceec8e8a4f4afbff8e2e0f3dbec5c7bc50c6e8 100644 GIT binary patch delta 47 zcmV+~0MP${Beo&1^Z~OB0>}Zg=>s1DlOhGolLQ8ov!n)50kakeHUqPO3+@F5eLD($ F3JRIS4`ToT delta 221 zcmdm{*r2@O3!{jFLQ!gAX=YJsib6?7X0cvLWkKrZ4~%CR1$;7-iV}+|Lx2JbVA1W& zDvVMJYHC~xK#*UOky->+tzc^lmY;l^|fsE1$ z3R%VZdC^7cAR+Zw1)y%Y#ck|1%vuUyEmjIh4$w<0%Fp!zYSTc`yLkuK4^|-s1&C2P MK&xx5xoWw%0Qs^?D*ylh diff --git a/mobile/openapi/lib/model/library_response_dto.dart b/mobile/openapi/lib/model/library_response_dto.dart index c2a25913c5b0e42b5c4b397b496213040c200a6a..e27b48910439c6a1d22bfb40a2992a4aa3d249d5 100644 GIT binary patch delta 44 zcmV+{0Mq}uEAJ_=g94MA0?D&^1I_`nVFg(Mlh6kHv)2bz0<%pDV+6D04z&i8r4lds{mA-ky)$<;#qIr%2~*w1!7t$AX%W7R+OLX xRh*xvkp(28i_{@nH5E6@@a<;)pMIY&=U=Al`3&wK7V`njRbvHTvaUkB(xmwBd24O`HkIM^LKL@-*g>=Ull#a~64_jk~JB7flYa=F1#=u2D1HR4${dcu9l#wp z6h=Q`R4Htpm@N4=LOmaD*IE?!-9gyMi`=2ZV0(@FrAoPxl~8-|w-g-71~)m+cifQ8 zOeiMdt23>nH}g?Habqw56sa$r4iEi17=H*xy=Z&=r`78G63gpEQb+#yd9j+#V0@N> zxeXV$m;u0v#{AoQ{$)8`T+wN+NUP3!A*^o#lM)JTt=LH(0<|DiNPk0R-flWA@ufZS z>I-Jk=WiXc4;%CX5JDaJ=OzR?ZG9BAnU2~7GNW@}nzTY_VlhIusE8do0>|1gWpNyG zZu)mo$`hUPey6vsb?Z(d1Br>>pS$@R+cAJ2JFO+`jXpA}#uzdtEgwAJc_cRdH{6!e zU1W?&QO+WRK3M~VkFW9Dw?OAe*rj&!yl_=x4h?Dp{eUWTN_8jEm;1(LK;r*T?}55~ct_MZMYrJ_|qC38#TOff+bmkJ7-KWD18nG{#-h zq)*hNOENjbjPGd15~oe|DzpfCaU17kGQMP%Gt)>kVazTl=)VMORtSaZcYqF*qNfmg z2-MY1@XTZ0A|q~^iulHFHzd5JqOfji#9-K-@CgrCah!d9Tp~ucgjQ!tq*p97QR;P* z*gvHm>^Ov8q+rV=;nrqoq@vNL{-eI~&?Bn>K|GD%4}IiFlmi-(GuM93UiV$w{+|ef zZ$jCl!c`cSBTwhXXcLk=;JB#MQqT%G3kc(^0Jm0J!I82-i_^~!fC~&48kQWZ)LBGf z-Y{7lE8Idj)Q0D{6w;<5B#-ObpoHPb+5$sD_h4%FCH?8~UG3jKs_zPW8kTiv1wuanbOdiG z`LHb;AFmJ{5hlflOct>G6`LfvLmKN6%-J9#vT7oumbIIw{UdWGK6xRO_r#h10_tv1 A0{{R3 diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index de84e535462748e6583887c50c2a044131bbdd89..a1ecd2cc3019ece5e430674216645c900e528d32 100644 GIT binary patch delta 11 ScmeB@`ysobh;MQWUmE}&0R))< delta 28 jcmew%+a+SO?5fOWdIp=%bF{Mq09IiKb^rhX delta 16 XcmZouyr!_>I>%&xe)i3LoE7WCYLnM9a{&Nf_YWBW diff --git a/mobile/openapi/test/library_api_test.dart b/mobile/openapi/test/library_api_test.dart index d34286f992d5b656fa164e0ca21a9e2796e7cd41..f23d1189794d1bc4721f710e72c6972cd592ead4 100644 GIT binary patch delta 16 YcmdnTyNY+if2PUxOyZlhn5QrU067N*)&Kwi delta 37 qcmZ3*yN`FneQgNQDsPFL8?LtkeK|CNs$H2=w(%#e3wOFaujPa0Dct^WB>pF diff --git a/mobile/openapi/test/library_type_test.dart b/mobile/openapi/test/library_type_test.dart deleted file mode 100644 index 3101173c59216b01cb05c6ba94dfac2e54716f8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419 zcmZvYQA@)x6oudOE3Qx7pswoE*bp4Flfhg;>cgJGb-n9?Z4z%%rO5ufiBb^U!#$S+ z-^odmWg^R9^-`7BkHxNdUR6a7>&>=EAunN9R`68joAv%GV3B;Nk^TMM>~=1qRgE@G z@@QBdRZ9!#b?>N_)KW*HirN!xqL^xi8DagAM2iyXEmcHwY#m*WSc zok#7ZszaeVD&n8GyI9sLO-{B(Hv#h})N+0!4<}?N#P1y;v3PjHh8D(>Mys+gv-s*{ z`492rd=_N{Vrlda!k55Sy)yDp4VJ`*Q8jd^^kfQOLI5;udOJxX{TED97>V(`/library${QS.query(QS.explode({ - "type": $type - }))}`, { + }>("/library", { ...opts })); } @@ -2913,10 +2907,6 @@ export enum JobCommand { Empty = "empty", ClearFailed = "clear-failed" } -export enum LibraryType { - Upload = "UPLOAD", - External = "EXTERNAL" -} export enum Type2 { OnThisDay = "on_this_day" } diff --git a/server/src/controllers/library.controller.ts b/server/src/controllers/library.controller.ts index 74a4f73d2..adbae8af0 100644 --- a/server/src/controllers/library.controller.ts +++ b/server/src/controllers/library.controller.ts @@ -1,11 +1,10 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { CreateLibraryDto, LibraryResponseDto, LibraryStatsResponseDto, ScanLibraryDto, - SearchLibraryDto, UpdateLibraryDto, ValidateLibraryDto, ValidateLibraryResponseDto, @@ -21,8 +20,8 @@ export class LibraryController { @Get() @Authenticated({ admin: true }) - getAllLibraries(@Query() dto: SearchLibraryDto): Promise { - return this.service.getAll(dto); + getAllLibraries(): Promise { + return this.service.getAll(); } @Post() diff --git a/server/src/cores/access.core.ts b/server/src/cores/access.core.ts index 6f8930d05..ae666562c 100644 --- a/server/src/cores/access.core.ts +++ b/server/src/cores/access.core.ts @@ -274,7 +274,7 @@ export class AccessCore { } case Permission.ASSET_UPLOAD: { - return await this.repository.library.checkOwnerAccess(auth.user.id, ids); + return ids.has(auth.user.id) ? new Set([auth.user.id]) : new Set(); } case Permission.ARCHIVE_READ: { diff --git a/server/src/cores/user.core.ts b/server/src/cores/user.core.ts index db2a9c780..4628c8383 100644 --- a/server/src/cores/user.core.ts +++ b/server/src/cores/user.core.ts @@ -2,7 +2,6 @@ import { BadRequestException, ForbiddenException } from '@nestjs/common'; import sanitize from 'sanitize-filename'; import { SALT_ROUNDS } from 'src/constants'; import { UserResponseDto } from 'src/dtos/user.dto'; -import { LibraryType } from 'src/entities/library.entity'; import { UserEntity } from 'src/entities/user.entity'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; @@ -93,16 +92,7 @@ export class UserCore { if (payload.storageLabel) { payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', '')); } - const userEntity = await this.userRepository.create(payload); - await this.libraryRepository.create({ - owner: { id: userEntity.id } as UserEntity, - name: 'Default Library', - assets: [], - type: LibraryType.UPLOAD, - importPaths: [], - exclusionPatterns: [], - }); - return userEntity; + return this.userRepository.create(payload); } } diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index 879d358e8..2cf12061d 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -26,7 +26,8 @@ export class AssetResponseDto extends SanitizedAssetResponseDto { deviceId!: string; ownerId!: string; owner?: UserResponseDto; - libraryId!: string; + @PropertyLifecycle({ deprecatedAt: 'v1.106.0' }) + libraryId?: string | null; originalPath!: string; originalFileName!: string; fileCreatedAt!: Date; diff --git a/server/src/dtos/asset-v1.dto.ts b/server/src/dtos/asset-v1.dto.ts index 131d28ca4..83c34ab0f 100644 --- a/server/src/dtos/asset-v1.dto.ts +++ b/server/src/dtos/asset-v1.dto.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { ArrayNotEmpty, IsArray, IsEnum, IsInt, IsNotEmpty, IsString, IsUUID, ValidateNested } from 'class-validator'; import { UploadFieldName } from 'src/dtos/asset.dto'; -import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; +import { Optional, ValidateBoolean, ValidateDate } from 'src/validation'; export class AssetBulkUploadCheckItem { @IsString() @@ -64,9 +64,6 @@ export class CheckExistingAssetsDto { } export class CreateAssetDto { - @ValidateUUID({ optional: true }) - libraryId?: string; - @IsNotEmpty() @IsString() deviceAssetId!: string; diff --git a/server/src/dtos/library.dto.ts b/server/src/dtos/library.dto.ts index 045aaecf5..b9578a2c3 100644 --- a/server/src/dtos/library.dto.ts +++ b/server/src/dtos/library.dto.ts @@ -1,13 +1,9 @@ import { ApiProperty } from '@nestjs/swagger'; -import { ArrayMaxSize, ArrayUnique, IsEnum, IsNotEmpty, IsString } from 'class-validator'; -import { LibraryEntity, LibraryType } from 'src/entities/library.entity'; +import { ArrayMaxSize, ArrayUnique, IsNotEmpty, IsString } from 'class-validator'; +import { LibraryEntity } from 'src/entities/library.entity'; import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; export class CreateLibraryDto { - @IsEnum(LibraryType) - @ApiProperty({ enumName: 'LibraryType', enum: LibraryType }) - type!: LibraryType; - @ValidateUUID() ownerId!: string; @@ -97,21 +93,11 @@ export class ScanLibraryDto { refreshAllFiles?: boolean; } -export class SearchLibraryDto { - @IsEnum(LibraryType) - @ApiProperty({ enumName: 'LibraryType', enum: LibraryType }) - @Optional() - type?: LibraryType; -} - export class LibraryResponseDto { id!: string; ownerId!: string; name!: string; - @ApiProperty({ enumName: 'LibraryType', enum: LibraryType }) - type!: LibraryType; - @ApiProperty({ type: 'integer' }) assetCount!: number; @@ -146,7 +132,6 @@ export function mapLibrary(entity: LibraryEntity): LibraryResponseDto { return { id: entity.id, ownerId: entity.ownerId, - type: entity.type, name: entity.name, createdAt: entity.createdAt, updatedAt: entity.updatedAt, diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts index 7169ee907..2189d5389 100644 --- a/server/src/entities/asset.entity.ts +++ b/server/src/entities/asset.entity.ts @@ -25,12 +25,17 @@ import { UpdateDateColumn, } from 'typeorm'; -export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_library_checksum'; +export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_checksum'; @Entity('assets') // Checksums must be unique per user and library -@Index(ASSET_CHECKSUM_CONSTRAINT, ['owner', 'library', 'checksum'], { +@Index(ASSET_CHECKSUM_CONSTRAINT, ['owner', 'checksum'], { unique: true, + where: '"libraryId" IS NULL', +}) +@Index('UQ_assets_owner_library_checksum' + '', ['owner', 'library', 'checksum'], { + unique: true, + where: '"libraryId" IS NOT NULL', }) @Index('IDX_day_of_month', { synchronize: false }) @Index('IDX_month', { synchronize: false }) @@ -51,11 +56,11 @@ export class AssetEntity { @Column() ownerId!: string; - @ManyToOne(() => LibraryEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) - library!: LibraryEntity; + @ManyToOne(() => LibraryEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + library?: LibraryEntity | null; - @Column() - libraryId!: string; + @Column({ nullable: true }) + libraryId?: string | null; @Column() deviceId!: string; diff --git a/server/src/entities/library.entity.ts b/server/src/entities/library.entity.ts index 56e62dd06..a6053e421 100644 --- a/server/src/entities/library.entity.ts +++ b/server/src/entities/library.entity.ts @@ -30,9 +30,6 @@ export class LibraryEntity { @Column() ownerId!: string; - @Column() - type!: LibraryType; - @Column('text', { array: true }) importPaths!: string[]; @@ -51,8 +48,3 @@ export class LibraryEntity { @Column({ type: 'timestamptz', nullable: true }) refreshedAt!: Date | null; } - -export enum LibraryType { - UPLOAD = 'UPLOAD', - EXTERNAL = 'EXTERNAL', -} diff --git a/server/src/interfaces/access.interface.ts b/server/src/interfaces/access.interface.ts index e07b877b6..6b408c263 100644 --- a/server/src/interfaces/access.interface.ts +++ b/server/src/interfaces/access.interface.ts @@ -26,10 +26,6 @@ export interface IAccessRepository { checkSharedLinkAccess(sharedLinkId: string, albumIds: Set): Promise>; }; - library: { - checkOwnerAccess(userId: string, libraryIds: Set): Promise>; - }; - timeline: { checkPartnerAccess(userId: string, partnerIds: Set): Promise>; }; diff --git a/server/src/interfaces/asset.interface.ts b/server/src/interfaces/asset.interface.ts index 4b9ff031e..0a8437818 100644 --- a/server/src/interfaces/asset.interface.ts +++ b/server/src/interfaces/asset.interface.ts @@ -164,7 +164,7 @@ export interface IAssetRepository { ): Promise; getByIdsWithAllRelations(ids: string[]): Promise; getByDayOfYear(ownerIds: string[], monthDay: MonthDay): Promise; - getByChecksum(libraryId: string, checksum: Buffer): Promise; + getByChecksum(libraryId: string | null, checksum: Buffer): Promise; getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise; getByAlbumId(pagination: PaginationOptions, albumId: string): Paginated; getByUserId(pagination: PaginationOptions, userId: string, options?: AssetSearchOptions): Paginated; diff --git a/server/src/interfaces/library.interface.ts b/server/src/interfaces/library.interface.ts index dbc7fab81..30b8b39a0 100644 --- a/server/src/interfaces/library.interface.ts +++ b/server/src/interfaces/library.interface.ts @@ -1,18 +1,16 @@ import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; -import { LibraryEntity, LibraryType } from 'src/entities/library.entity'; +import { LibraryEntity } from 'src/entities/library.entity'; export const ILibraryRepository = 'ILibraryRepository'; export interface ILibraryRepository { getCountForUser(ownerId: string): Promise; - getAll(withDeleted?: boolean, type?: LibraryType): Promise; + getAll(withDeleted?: boolean): Promise; getAllDeleted(): Promise; get(id: string, withDeleted?: boolean): Promise; create(library: Partial): Promise; delete(id: string): Promise; softDelete(id: string): Promise; - getDefaultUploadLibrary(ownerId: string): Promise; - getUploadLibraryCount(ownerId: string): Promise; update(library: Partial): Promise; getStatistics(id: string): Promise; getAssetIds(id: string, withDeleted?: boolean): Promise; diff --git a/server/src/migrations/1715804005643-RemoveLibraryType.ts b/server/src/migrations/1715804005643-RemoveLibraryType.ts new file mode 100644 index 000000000..d42ba4ec7 --- /dev/null +++ b/server/src/migrations/1715804005643-RemoveLibraryType.ts @@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveLibraryType1715804005643 implements MigrationInterface { + name = 'RemoveLibraryType1715804005643'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c"`); + await queryRunner.query(`DROP INDEX "public"."UQ_assets_owner_library_checksum"`); + await queryRunner.query(`DROP INDEX "public"."IDX_originalPath_libraryId"`); + await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "libraryId" DROP NOT NULL`); + await queryRunner.query(` + UPDATE "assets" + SET "libraryId" = NULL + FROM "libraries" + WHERE "assets"."libraryId" = "libraries"."id" + AND "libraries"."type" = 'UPLOAD' +`); + await queryRunner.query(`DELETE FROM "libraries" WHERE "type" = 'UPLOAD'`); + await queryRunner.query(`ALTER TABLE "libraries" DROP COLUMN "type"`); + await queryRunner.query(`CREATE INDEX "IDX_originalPath_libraryId" ON "assets" ("originalPath", "libraryId")`); + await queryRunner.query(`CREATE UNIQUE INDEX "UQ_assets_owner_checksum" ON "assets" ("ownerId", "checksum") WHERE "libraryId" IS NULL`); + await queryRunner.query(`CREATE UNIQUE INDEX "UQ_assets_owner_library_checksum" ON "assets" ("ownerId", "libraryId", "checksum") WHERE "libraryId" IS NOT NULL`); + await queryRunner.query(`ALTER TABLE "assets" ADD CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c" FOREIGN KEY ("libraryId") REFERENCES "libraries"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + } + + public async down(): Promise { + // not implemented + } +} diff --git a/server/src/queries/access.repository.sql b/server/src/queries/access.repository.sql index 52cf28c77..b08638707 100644 --- a/server/src/queries/access.repository.sql +++ b/server/src/queries/access.repository.sql @@ -191,20 +191,6 @@ WHERE AND ("SessionEntity"."id" IN ($2)) ) --- AccessRepository.library.checkOwnerAccess -SELECT - "LibraryEntity"."id" AS "LibraryEntity_id" -FROM - "libraries" "LibraryEntity" -WHERE - ( - ( - ("LibraryEntity"."id" IN ($1)) - AND ("LibraryEntity"."ownerId" = $2) - ) - ) - AND ("LibraryEntity"."deletedAt" IS NULL) - -- AccessRepository.memory.checkOwnerAccess SELECT "MemoryEntity"."id" AS "MemoryEntity_id" diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index 3c6c83ff1..6829c1445 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -483,26 +483,16 @@ LIMIT 1 -- AssetRepository.getUploadAssetIdByChecksum -SELECT DISTINCT - "distinctAlias"."AssetEntity_id" AS "ids_AssetEntity_id" +SELECT + "AssetEntity"."id" AS "AssetEntity_id" FROM + "assets" "AssetEntity" +WHERE ( - SELECT - "AssetEntity"."id" AS "AssetEntity_id" - FROM - "assets" "AssetEntity" - LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId" - WHERE - ( - ("AssetEntity"."ownerId" = $1) - AND ("AssetEntity"."checksum" = $2) - AND ( - (("AssetEntity__AssetEntity_library"."type" = $3)) - ) - ) - ) "distinctAlias" -ORDER BY - "AssetEntity_id" ASC + ("AssetEntity"."ownerId" = $1) + AND ("AssetEntity"."checksum" = $2) + AND ("AssetEntity"."libraryId" IS NULL) + ) LIMIT 1 diff --git a/server/src/queries/library.repository.sql b/server/src/queries/library.repository.sql index 3e655d650..700a71048 100644 --- a/server/src/queries/library.repository.sql +++ b/server/src/queries/library.repository.sql @@ -9,7 +9,6 @@ FROM "LibraryEntity"."id" AS "LibraryEntity_id", "LibraryEntity"."name" AS "LibraryEntity_name", "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."type" AS "LibraryEntity_type", "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", @@ -77,53 +76,11 @@ WHERE ((("LibraryEntity"."ownerId" = $1))) AND ("LibraryEntity"."deletedAt" IS NULL) --- LibraryRepository.getDefaultUploadLibrary -SELECT - "LibraryEntity"."id" AS "LibraryEntity_id", - "LibraryEntity"."name" AS "LibraryEntity_name", - "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."type" AS "LibraryEntity_type", - "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", - "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", - "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", - "LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt", - "LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt", - "LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt" -FROM - "libraries" "LibraryEntity" -WHERE - ( - ( - ("LibraryEntity"."ownerId" = $1) - AND ("LibraryEntity"."type" = $2) - ) - ) - AND ("LibraryEntity"."deletedAt" IS NULL) -ORDER BY - "LibraryEntity"."createdAt" ASC -LIMIT - 1 - --- LibraryRepository.getUploadLibraryCount -SELECT - COUNT(1) AS "cnt" -FROM - "libraries" "LibraryEntity" -WHERE - ( - ( - ("LibraryEntity"."ownerId" = $1) - AND ("LibraryEntity"."type" = $2) - ) - ) - AND ("LibraryEntity"."deletedAt" IS NULL) - -- LibraryRepository.getAllByUserId SELECT "LibraryEntity"."id" AS "LibraryEntity_id", "LibraryEntity"."name" AS "LibraryEntity_name", "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."type" AS "LibraryEntity_type", "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", @@ -163,7 +120,6 @@ SELECT "LibraryEntity"."id" AS "LibraryEntity_id", "LibraryEntity"."name" AS "LibraryEntity_name", "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."type" AS "LibraryEntity_type", "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", @@ -202,7 +158,6 @@ SELECT "LibraryEntity"."id" AS "LibraryEntity_id", "LibraryEntity"."name" AS "LibraryEntity_name", "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."type" AS "LibraryEntity_type", "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", @@ -238,7 +193,6 @@ SELECT "libraries"."id" AS "libraries_id", "libraries"."name" AS "libraries_name", "libraries"."ownerId" AS "libraries_ownerId", - "libraries"."type" AS "libraries_type", "libraries"."importPaths" AS "libraries_importPaths", "libraries"."exclusionPatterns" AS "libraries_exclusionPatterns", "libraries"."createdAt" AS "libraries_createdAt", diff --git a/server/src/queries/user.repository.sql b/server/src/queries/user.repository.sql index 30464da78..d27b83fdd 100644 --- a/server/src/queries/user.repository.sql +++ b/server/src/queries/user.repository.sql @@ -167,12 +167,10 @@ SET COALESCE(SUM(exif."fileSizeInByte"), 0) FROM "assets" "assets" - LEFT JOIN "libraries" "library" ON "library"."id" = "assets"."libraryId" - AND ("library"."deletedAt" IS NULL) LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id" WHERE "assets"."ownerId" = users.id - AND "library"."type" = 'UPLOAD' + AND "assets"."libraryId" IS NULL ), "updatedAt" = CURRENT_TIMESTAMP WHERE diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts index 992f8f143..17746b155 100644 --- a/server/src/repositories/access.repository.ts +++ b/server/src/repositories/access.repository.ts @@ -20,7 +20,6 @@ type IActivityAccess = IAccessRepository['activity']; type IAlbumAccess = IAccessRepository['album']; type IAssetAccess = IAccessRepository['asset']; type IAuthDeviceAccess = IAccessRepository['authDevice']; -type ILibraryAccess = IAccessRepository['library']; type ITimelineAccess = IAccessRepository['timeline']; type IMemoryAccess = IAccessRepository['memory']; type IPersonAccess = IAccessRepository['person']; @@ -313,28 +312,6 @@ class AuthDeviceAccess implements IAuthDeviceAccess { } } -class LibraryAccess implements ILibraryAccess { - constructor(private libraryRepository: Repository) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, libraryIds: Set): Promise> { - if (libraryIds.size === 0) { - return new Set(); - } - - return this.libraryRepository - .find({ - select: { id: true }, - where: { - id: In([...libraryIds]), - ownerId: userId, - }, - }) - .then((libraries) => new Set(libraries.map((library) => library.id))); - } -} - class TimelineAccess implements ITimelineAccess { constructor(private partnerRepository: Repository) {} @@ -447,7 +424,6 @@ export class AccessRepository implements IAccessRepository { album: IAlbumAccess; asset: IAssetAccess; authDevice: IAuthDeviceAccess; - library: ILibraryAccess; memory: IMemoryAccess; person: IPersonAccess; partner: IPartnerAccess; @@ -469,7 +445,6 @@ export class AccessRepository implements IAccessRepository { this.album = new AlbumAccess(albumRepository, sharedLinkRepository); this.asset = new AssetAccess(albumRepository, assetRepository, partnerRepository, sharedLinkRepository); this.authDevice = new AuthDeviceAccess(sessionRepository); - this.library = new LibraryAccess(libraryRepository); this.memory = new MemoryAccess(memoryRepository); this.person = new PersonAccess(assetFaceRepository, personRepository); this.partner = new PartnerAccess(partnerRepository); diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index b4869b9fb..9a68b4e70 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -5,7 +5,6 @@ import { AlbumEntity, AssetOrder } from 'src/entities/album.entity'; import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; -import { LibraryType } from 'src/entities/library.entity'; import { PartnerEntity } from 'src/entities/partner.entity'; import { SmartInfoEntity } from 'src/entities/smart-info.entity'; import { @@ -292,8 +291,13 @@ export class AssetRepository implements IAssetRepository { } @GenerateSql({ params: [DummyValue.UUID, DummyValue.BUFFER] }) - getByChecksum(libraryId: string, checksum: Buffer): Promise { - return this.repository.findOne({ where: { libraryId, checksum } }); + getByChecksum(libraryId: string | null, checksum: Buffer): Promise { + return this.repository.findOne({ + where: { + libraryId: libraryId || IsNull(), + checksum, + }, + }); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.BUFFER] }) @@ -303,9 +307,7 @@ export class AssetRepository implements IAssetRepository { where: { ownerId, checksum, - library: { - type: LibraryType.UPLOAD, - }, + library: IsNull(), }, withDeleted: true, }); diff --git a/server/src/repositories/library.repository.ts b/server/src/repositories/library.repository.ts index 25eb01035..ca7078072 100644 --- a/server/src/repositories/library.repository.ts +++ b/server/src/repositories/library.repository.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DummyValue, GenerateSql } from 'src/decorators'; import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; -import { LibraryEntity, LibraryType } from 'src/entities/library.entity'; +import { LibraryEntity } from 'src/entities/library.entity'; import { ILibraryRepository } from 'src/interfaces/library.interface'; import { Instrumentation } from 'src/utils/instrumentation'; import { EntityNotFoundError, IsNull, Not } from 'typeorm'; @@ -40,34 +40,10 @@ export class LibraryRepository implements ILibraryRepository { } @GenerateSql({ params: [DummyValue.UUID] }) - getDefaultUploadLibrary(ownerId: string): Promise { - return this.repository.findOne({ - where: { - ownerId: ownerId, - type: LibraryType.UPLOAD, - }, - order: { - createdAt: 'ASC', - }, - }); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getUploadLibraryCount(ownerId: string): Promise { - return this.repository.count({ - where: { - ownerId: ownerId, - type: LibraryType.UPLOAD, - }, - }); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getAllByUserId(ownerId: string, type?: LibraryType): Promise { + getAllByUserId(ownerId: string): Promise { return this.repository.find({ where: { ownerId, - type, }, relations: { owner: true, @@ -79,9 +55,8 @@ export class LibraryRepository implements ILibraryRepository { } @GenerateSql({ params: [] }) - getAll(withDeleted = false, type?: LibraryType): Promise { + getAll(withDeleted = false): Promise { return this.repository.find({ - where: { type }, relations: { owner: true, }, diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index 4f62732e7..6829bb024 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DummyValue, GenerateSql } from 'src/decorators'; import { AssetEntity } from 'src/entities/asset.entity'; -import { LibraryType } from 'src/entities/library.entity'; import { UserEntity } from 'src/entities/user.entity'; import { IUserRepository, @@ -123,10 +122,9 @@ export class UserRepository implements IUserRepository { const subQuery = this.assetRepository .createQueryBuilder('assets') .select('COALESCE(SUM(exif."fileSizeInByte"), 0)') - .leftJoin('assets.library', 'library') .leftJoin('assets.exifInfo', 'exif') .where('assets.ownerId = users.id') - .andWhere(`library.type = '${LibraryType.UPLOAD}'`) + .andWhere(`assets.libraryId IS NULL`) .withDeleted(); const query = this.userRepository diff --git a/server/src/services/asset-v1.service.spec.ts b/server/src/services/asset-v1.service.spec.ts index 40211ee40..89485d266 100644 --- a/server/src/services/asset-v1.service.spec.ts +++ b/server/src/services/asset-v1.service.spec.ts @@ -32,7 +32,6 @@ const _getCreateAssetDto = (): CreateAssetDto => { createAssetDto.isFavorite = false; createAssetDto.isArchived = false; createAssetDto.duration = '0:00:00.000000'; - createAssetDto.libraryId = 'libraryId'; return createAssetDto; }; @@ -121,7 +120,6 @@ describe('AssetService', () => { const dto = _getCreateAssetDto(); assetMock.create.mockResolvedValue(assetEntity); - accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([dto.libraryId!])); await expect(sut.uploadFile(authStub.user1, dto, file)).resolves.toEqual({ duplicate: false, id: 'id_1' }); @@ -149,7 +147,6 @@ describe('AssetService', () => { assetMock.create.mockRejectedValue(error); assetRepositoryMockV1.getAssetsByChecksums.mockResolvedValue([_getAsset_1()]); - accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([dto.libraryId!])); await expect(sut.uploadFile(authStub.user1, dto, file)).resolves.toEqual({ duplicate: true, id: 'id_1' }); @@ -167,7 +164,6 @@ describe('AssetService', () => { assetMock.create.mockResolvedValueOnce(assetStub.livePhotoMotionAsset); assetMock.create.mockResolvedValueOnce(assetStub.livePhotoStillAsset); - accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([dto.libraryId!])); await expect( sut.uploadFile(authStub.user1, dto, fileStub.livePhotoStill, fileStub.livePhotoMotion), diff --git a/server/src/services/asset-v1.service.ts b/server/src/services/asset-v1.service.ts index bd6f54006..6868ca2da 100644 --- a/server/src/services/asset-v1.service.ts +++ b/server/src/services/asset-v1.service.ts @@ -25,7 +25,6 @@ import { } from 'src/dtos/asset-v1.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/entities/asset.entity'; -import { LibraryType } from 'src/entities/library.entity'; import { IAccessRepository } from 'src/interfaces/access.interface'; import { IAssetRepositoryV1 } from 'src/interfaces/asset-v1.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; @@ -76,15 +75,20 @@ export class AssetServiceV1 { let livePhotoAsset: AssetEntity | null = null; try { - const libraryId = await this.getLibraryId(auth, dto.libraryId); - await this.access.requirePermission(auth, Permission.ASSET_UPLOAD, libraryId); + await this.access.requirePermission( + auth, + Permission.ASSET_UPLOAD, + // do not need an id here, but the interface requires it + auth.user.id, + ); + this.requireQuota(auth, file.size); if (livePhotoFile) { - const livePhotoDto = { ...dto, assetType: AssetType.VIDEO, isVisible: false, libraryId }; + const livePhotoDto = { ...dto, assetType: AssetType.VIDEO, isVisible: false }; livePhotoAsset = await this.create(auth, livePhotoDto, livePhotoFile); } - const asset = await this.create(auth, { ...dto, libraryId }, file, livePhotoAsset?.id, sidecarFile?.originalPath); + const asset = await this.create(auth, dto, file, livePhotoAsset?.id, sidecarFile?.originalPath); await this.userRepository.updateUsage(auth.user.id, (livePhotoFile?.size || 0) + file.size); @@ -245,36 +249,16 @@ export class AssetServiceV1 { return asset.previewPath; } - private async getLibraryId(auth: AuthDto, libraryId?: string) { - if (libraryId) { - return libraryId; - } - - let library = await this.libraryRepository.getDefaultUploadLibrary(auth.user.id); - if (!library) { - library = await this.libraryRepository.create({ - ownerId: auth.user.id, - name: 'Default Library', - assets: [], - type: LibraryType.UPLOAD, - importPaths: [], - exclusionPatterns: [], - }); - } - - return library.id; - } - private async create( auth: AuthDto, - dto: CreateAssetDto & { libraryId: string }, + dto: CreateAssetDto, file: UploadFile, livePhotoAssetId?: string, sidecarPath?: string, ): Promise { const asset = await this.assetRepository.create({ ownerId: auth.user.id, - libraryId: dto.libraryId, + libraryId: null, checksum: file.checksum, originalPath: file.originalPath, diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index d266b1ed2..c37d8d8b6 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -27,7 +27,6 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { MapMarkerDto, MapMarkerResponseDto, MemoryLaneDto } from 'src/dtos/search.dto'; import { UpdateStackParentDto } from 'src/dtos/stack.dto'; import { AssetEntity } from 'src/entities/asset.entity'; -import { LibraryType } from 'src/entities/library.entity'; import { IAccessRepository } from 'src/interfaces/access.interface'; import { IAlbumRepository } from 'src/interfaces/album.interface'; import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface'; @@ -424,7 +423,7 @@ export class AssetService { } await this.assetRepository.remove(asset); - if (asset.library.type === LibraryType.UPLOAD) { + if (!asset.libraryId) { await this.userRepository.updateUsage(asset.ownerId, -(asset.exifInfo?.fileSizeInByte || 0)); } this.eventRepository.clientSend(ClientEvent.ASSET_DELETE, asset.ownerId, id); @@ -436,7 +435,7 @@ export class AssetService { const files = [asset.thumbnailPath, asset.previewPath, asset.encodedVideoPath]; // skip originals if the user deleted the whole library - if (!asset.library.deletedAt) { + if (!asset.library?.deletedAt) { files.push(asset.sidecarPath, asset.originalPath); } diff --git a/server/src/services/download.service.spec.ts b/server/src/services/download.service.spec.ts index 331ddcaaa..48231a11a 100644 --- a/server/src/services/download.service.spec.ts +++ b/server/src/services/download.service.spec.ts @@ -180,7 +180,6 @@ describe(DownloadService.name, () => { }); it('should return a list of archives (userId)', async () => { - accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([authStub.admin.user.id])); assetMock.getByUserId.mockResolvedValue({ items: [assetStub.image, assetStub.video], hasNextPage: false, @@ -196,8 +195,6 @@ describe(DownloadService.name, () => { }); it('should split archives by size', async () => { - accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([authStub.admin.user.id])); - assetMock.getByUserId.mockResolvedValue({ items: [ { ...assetStub.image, id: 'asset-1' }, diff --git a/server/src/services/library.service.spec.ts b/server/src/services/library.service.spec.ts index 9f2cb073c..b63df692a 100644 --- a/server/src/services/library.service.spec.ts +++ b/server/src/services/library.service.spec.ts @@ -4,7 +4,6 @@ import { SystemConfig } from 'src/config'; import { SystemConfigCore } from 'src/cores/system-config.core'; import { mapLibrary } from 'src/dtos/library.dto'; import { AssetType } from 'src/entities/asset.entity'; -import { LibraryType } from 'src/entities/library.entity'; import { UserEntity } from 'src/entities/user.entity'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; @@ -213,18 +212,6 @@ describe(LibraryService.name, () => { ]); }); - it('should not scan upload libraries', async () => { - const mockLibraryJob: ILibraryRefreshJob = { - id: libraryStub.externalLibrary1.id, - refreshModifiedFiles: false, - refreshAllFiles: false, - }; - - libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1); - - await expect(sut.handleQueueAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.FAILED); - }); - it('should ignore import paths that do not exist', async () => { storageMock.stat.mockImplementation((path): Promise => { if (path === libraryStub.externalLibraryWithImportPaths1.importPaths[0]) { @@ -707,7 +694,6 @@ describe(LibraryService.name, () => { describe('delete', () => { it('should delete a library', async () => { assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - libraryMock.getUploadLibraryCount.mockResolvedValue(2); libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); await sut.delete(libraryStub.externalLibrary1.id); @@ -720,21 +706,8 @@ describe(LibraryService.name, () => { expect(libraryMock.softDelete).toHaveBeenCalledWith(libraryStub.externalLibrary1.id); }); - it('should throw error if the last upload library is deleted', async () => { - assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - libraryMock.getUploadLibraryCount.mockResolvedValue(1); - libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1); - - await expect(sut.delete(libraryStub.uploadLibrary1.id)).rejects.toBeInstanceOf(BadRequestException); - - expect(jobMock.queue).not.toHaveBeenCalled(); - expect(jobMock.queueAll).not.toHaveBeenCalled(); - expect(libraryMock.softDelete).not.toHaveBeenCalled(); - }); - it('should allow an external library to be deleted', async () => { assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - libraryMock.getUploadLibraryCount.mockResolvedValue(1); libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); await sut.delete(libraryStub.externalLibrary1.id); @@ -749,7 +722,6 @@ describe(LibraryService.name, () => { it('should unwatch an external library when deleted', async () => { assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - libraryMock.getUploadLibraryCount.mockResolvedValue(1); libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); @@ -767,37 +739,37 @@ describe(LibraryService.name, () => { describe('get', () => { it('should return a library', async () => { - libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1); - await expect(sut.get(libraryStub.uploadLibrary1.id)).resolves.toEqual( + libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); + await expect(sut.get(libraryStub.externalLibrary1.id)).resolves.toEqual( expect.objectContaining({ - id: libraryStub.uploadLibrary1.id, - name: libraryStub.uploadLibrary1.name, - ownerId: libraryStub.uploadLibrary1.ownerId, + id: libraryStub.externalLibrary1.id, + name: libraryStub.externalLibrary1.name, + ownerId: libraryStub.externalLibrary1.ownerId, }), ); - expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.uploadLibrary1.id); + expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.externalLibrary1.id); }); it('should throw an error when a library is not found', async () => { libraryMock.get.mockResolvedValue(null); - await expect(sut.get(libraryStub.uploadLibrary1.id)).rejects.toBeInstanceOf(BadRequestException); - expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.uploadLibrary1.id); + await expect(sut.get(libraryStub.externalLibrary1.id)).rejects.toBeInstanceOf(BadRequestException); + expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.externalLibrary1.id); }); }); describe('getStatistics', () => { it('should return library statistics', async () => { - libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1); + libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); libraryMock.getStatistics.mockResolvedValue({ photos: 10, videos: 0, total: 10, usage: 1337 }); - await expect(sut.getStatistics(libraryStub.uploadLibrary1.id)).resolves.toEqual({ + await expect(sut.getStatistics(libraryStub.externalLibrary1.id)).resolves.toEqual({ photos: 10, videos: 0, total: 10, usage: 1337, }); - expect(libraryMock.getStatistics).toHaveBeenCalledWith(libraryStub.uploadLibrary1.id); + expect(libraryMock.getStatistics).toHaveBeenCalledWith(libraryStub.externalLibrary1.id); }); }); @@ -805,10 +777,9 @@ describe(LibraryService.name, () => { describe('external library', () => { it('should create with default settings', async () => { libraryMock.create.mockResolvedValue(libraryStub.externalLibrary1); - await expect(sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.EXTERNAL })).resolves.toEqual( + await expect(sut.create({ ownerId: authStub.admin.user.id })).resolves.toEqual( expect.objectContaining({ id: libraryStub.externalLibrary1.id, - type: LibraryType.EXTERNAL, name: libraryStub.externalLibrary1.name, ownerId: libraryStub.externalLibrary1.ownerId, assetCount: 0, @@ -823,7 +794,6 @@ describe(LibraryService.name, () => { expect(libraryMock.create).toHaveBeenCalledWith( expect.objectContaining({ name: expect.any(String), - type: LibraryType.EXTERNAL, importPaths: [], exclusionPatterns: [], }), @@ -832,12 +802,9 @@ describe(LibraryService.name, () => { it('should create with name', async () => { libraryMock.create.mockResolvedValue(libraryStub.externalLibrary1); - await expect( - sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.EXTERNAL, name: 'My Awesome Library' }), - ).resolves.toEqual( + await expect(sut.create({ ownerId: authStub.admin.user.id, name: 'My Awesome Library' })).resolves.toEqual( expect.objectContaining({ id: libraryStub.externalLibrary1.id, - type: LibraryType.EXTERNAL, name: libraryStub.externalLibrary1.name, ownerId: libraryStub.externalLibrary1.ownerId, assetCount: 0, @@ -852,7 +819,6 @@ describe(LibraryService.name, () => { expect(libraryMock.create).toHaveBeenCalledWith( expect.objectContaining({ name: 'My Awesome Library', - type: LibraryType.EXTERNAL, importPaths: [], exclusionPatterns: [], }), @@ -864,13 +830,11 @@ describe(LibraryService.name, () => { await expect( sut.create({ ownerId: authStub.admin.user.id, - type: LibraryType.EXTERNAL, importPaths: ['/data/images', '/data/videos'], }), ).resolves.toEqual( expect.objectContaining({ id: libraryStub.externalLibrary1.id, - type: LibraryType.EXTERNAL, name: libraryStub.externalLibrary1.name, ownerId: libraryStub.externalLibrary1.ownerId, assetCount: 0, @@ -885,7 +849,6 @@ describe(LibraryService.name, () => { expect(libraryMock.create).toHaveBeenCalledWith( expect.objectContaining({ name: expect.any(String), - type: LibraryType.EXTERNAL, importPaths: ['/data/images', '/data/videos'], exclusionPatterns: [], }), @@ -901,7 +864,6 @@ describe(LibraryService.name, () => { await sut.init(); await sut.create({ ownerId: authStub.admin.user.id, - type: LibraryType.EXTERNAL, importPaths: libraryStub.externalLibraryWithImportPaths1.importPaths, }); }); @@ -911,13 +873,11 @@ describe(LibraryService.name, () => { await expect( sut.create({ ownerId: authStub.admin.user.id, - type: LibraryType.EXTERNAL, exclusionPatterns: ['*.tmp', '*.bak'], }), ).resolves.toEqual( expect.objectContaining({ id: libraryStub.externalLibrary1.id, - type: LibraryType.EXTERNAL, name: libraryStub.externalLibrary1.name, ownerId: libraryStub.externalLibrary1.ownerId, assetCount: 0, @@ -932,105 +892,22 @@ describe(LibraryService.name, () => { expect(libraryMock.create).toHaveBeenCalledWith( expect.objectContaining({ name: expect.any(String), - type: LibraryType.EXTERNAL, importPaths: [], exclusionPatterns: ['*.tmp', '*.bak'], }), ); }); }); - - describe('upload library', () => { - it('should create with default settings', async () => { - libraryMock.create.mockResolvedValue(libraryStub.uploadLibrary1); - await expect(sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.UPLOAD })).resolves.toEqual( - expect.objectContaining({ - id: libraryStub.uploadLibrary1.id, - type: LibraryType.UPLOAD, - name: libraryStub.uploadLibrary1.name, - ownerId: libraryStub.uploadLibrary1.ownerId, - assetCount: 0, - importPaths: [], - exclusionPatterns: [], - createdAt: libraryStub.uploadLibrary1.createdAt, - updatedAt: libraryStub.uploadLibrary1.updatedAt, - refreshedAt: null, - }), - ); - - expect(libraryMock.create).toHaveBeenCalledWith( - expect.objectContaining({ - name: 'New Upload Library', - type: LibraryType.UPLOAD, - importPaths: [], - exclusionPatterns: [], - }), - ); - }); - - it('should create with name', async () => { - libraryMock.create.mockResolvedValue(libraryStub.uploadLibrary1); - await expect( - sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.UPLOAD, name: 'My Awesome Library' }), - ).resolves.toEqual( - expect.objectContaining({ - id: libraryStub.uploadLibrary1.id, - type: LibraryType.UPLOAD, - name: libraryStub.uploadLibrary1.name, - ownerId: libraryStub.uploadLibrary1.ownerId, - assetCount: 0, - importPaths: [], - exclusionPatterns: [], - createdAt: libraryStub.uploadLibrary1.createdAt, - updatedAt: libraryStub.uploadLibrary1.updatedAt, - refreshedAt: null, - }), - ); - - expect(libraryMock.create).toHaveBeenCalledWith( - expect.objectContaining({ - name: 'My Awesome Library', - type: LibraryType.UPLOAD, - importPaths: [], - exclusionPatterns: [], - }), - ); - }); - - it('should not create with import paths', async () => { - await expect( - sut.create({ - ownerId: authStub.admin.user.id, - type: LibraryType.UPLOAD, - importPaths: ['/data/images', '/data/videos'], - }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(libraryMock.create).not.toHaveBeenCalled(); - }); - - it('should not create with exclusion patterns', async () => { - await expect( - sut.create({ - ownerId: authStub.admin.user.id, - type: LibraryType.UPLOAD, - exclusionPatterns: ['*.tmp', '*.bak'], - }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(libraryMock.create).not.toHaveBeenCalled(); - }); - }); }); describe('handleQueueCleanup', () => { it('should queue cleanup jobs', async () => { - libraryMock.getAllDeleted.mockResolvedValue([libraryStub.uploadLibrary1, libraryStub.externalLibrary1]); + libraryMock.getAllDeleted.mockResolvedValue([libraryStub.externalLibrary1, libraryStub.externalLibrary2]); await expect(sut.handleQueueCleanup()).resolves.toBe(JobStatus.SUCCESS); expect(jobMock.queueAll).toHaveBeenCalledWith([ - { name: JobName.LIBRARY_DELETE, data: { id: libraryStub.uploadLibrary1.id } }, { name: JobName.LIBRARY_DELETE, data: { id: libraryStub.externalLibrary1.id } }, + { name: JobName.LIBRARY_DELETE, data: { id: libraryStub.externalLibrary2.id } }, ]); }); }); @@ -1044,9 +921,9 @@ describe(LibraryService.name, () => { }); it('should update library', async () => { - libraryMock.update.mockResolvedValue(libraryStub.uploadLibrary1); - libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1); - await expect(sut.update('library-id', {})).resolves.toEqual(mapLibrary(libraryStub.uploadLibrary1)); + libraryMock.update.mockResolvedValue(libraryStub.externalLibrary1); + libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); + await expect(sut.update('library-id', {})).resolves.toEqual(mapLibrary(libraryStub.externalLibrary1)); expect(libraryMock.update).toHaveBeenCalledWith(expect.objectContaining({ id: 'library-id' })); }); }); @@ -1109,15 +986,6 @@ describe(LibraryService.name, () => { expect(storageMock.watch).not.toHaveBeenCalled(); }); - it('should throw error when watching upload library', async () => { - libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1); - libraryMock.getAll.mockResolvedValue([libraryStub.uploadLibrary1]); - - await expect(sut.watchAll()).rejects.toThrow('Can only watch external libraries'); - - expect(storageMock.watch).not.toHaveBeenCalled(); - }); - it('should handle a new file event', async () => { libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); @@ -1253,25 +1121,25 @@ describe(LibraryService.name, () => { libraryMock.getAssetIds.mockResolvedValue([]); libraryMock.delete.mockImplementation(async () => {}); - await expect(sut.handleDeleteLibrary({ id: libraryStub.uploadLibrary1.id })).resolves.toBe(JobStatus.FAILED); + await expect(sut.handleDeleteLibrary({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.FAILED); }); it('should delete an empty library', async () => { - libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1); + libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); libraryMock.getAssetIds.mockResolvedValue([]); libraryMock.delete.mockImplementation(async () => {}); - await expect(sut.handleDeleteLibrary({ id: libraryStub.uploadLibrary1.id })).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleDeleteLibrary({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SUCCESS); }); it('should delete a library with assets', async () => { - libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1); + libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); libraryMock.getAssetIds.mockResolvedValue([assetStub.image1.id]); libraryMock.delete.mockImplementation(async () => {}); assetMock.getById.mockResolvedValue(assetStub.image1); - await expect(sut.handleDeleteLibrary({ id: libraryStub.uploadLibrary1.id })).resolves.toBe(JobStatus.SUCCESS); + await expect(sut.handleDeleteLibrary({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SUCCESS); }); }); @@ -1295,14 +1163,6 @@ describe(LibraryService.name, () => { ]); }); - it('should not queue a library scan of upload library', async () => { - libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1); - - await expect(sut.queueScan(libraryStub.uploadLibrary1.id, {})).rejects.toBeInstanceOf(BadRequestException); - - expect(jobMock.queue).not.toBeCalled(); - }); - it('should queue a library scan of all modified assets', async () => { libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index fb6991d01..3a87c418b 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -12,7 +12,6 @@ import { LibraryResponseDto, LibraryStatsResponseDto, ScanLibraryDto, - SearchLibraryDto, UpdateLibraryDto, ValidateLibraryDto, ValidateLibraryImportPathResponseDto, @@ -20,7 +19,7 @@ import { mapLibrary, } from 'src/dtos/library.dto'; import { AssetType } from 'src/entities/asset.entity'; -import { LibraryEntity, LibraryType } from 'src/entities/library.entity'; +import { LibraryEntity } from 'src/entities/library.entity'; import { IAssetRepository, WithProperty } from 'src/interfaces/asset.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface'; @@ -118,10 +117,7 @@ export class LibraryService { } const library = await this.findOrFail(id); - - if (library.type !== LibraryType.EXTERNAL) { - throw new BadRequestException('Can only watch external libraries'); - } else if (library.importPaths.length === 0) { + if (library.importPaths.length === 0) { return false; } @@ -212,8 +208,7 @@ export class LibraryService { return false; } - const libraries = await this.repository.getAll(false, LibraryType.EXTERNAL); - + const libraries = await this.repository.getAll(false); for (const library of libraries) { await this.watch(library.id); } @@ -229,8 +224,8 @@ export class LibraryService { return mapLibrary(library); } - async getAll(dto: SearchLibraryDto): Promise { - const libraries = await this.repository.getAll(false, dto.type); + async getAll(): Promise { + const libraries = await this.repository.getAll(false); return libraries.map((library) => mapLibrary(library)); } @@ -244,37 +239,12 @@ export class LibraryService { } async create(dto: CreateLibraryDto): Promise { - switch (dto.type) { - case LibraryType.EXTERNAL: { - if (!dto.name) { - dto.name = 'New External Library'; - } - break; - } - case LibraryType.UPLOAD: { - if (!dto.name) { - dto.name = 'New Upload Library'; - } - if (dto.importPaths && dto.importPaths.length > 0) { - throw new BadRequestException('Upload libraries cannot have import paths'); - } - if (dto.exclusionPatterns && dto.exclusionPatterns.length > 0) { - throw new BadRequestException('Upload libraries cannot have exclusion patterns'); - } - break; - } - } - const library = await this.repository.create({ ownerId: dto.ownerId, - name: dto.name, - type: dto.type, + name: dto.name ?? 'New External Library', importPaths: dto.importPaths ?? [], exclusionPatterns: dto.exclusionPatterns ?? [], }); - - this.logger.log(`Creating ${dto.type} library for ${dto.ownerId}}`); - return mapLibrary(library); } @@ -362,11 +332,7 @@ export class LibraryService { } async delete(id: string) { - const library = await this.findOrFail(id); - const uploadCount = await this.repository.getUploadLibraryCount(library.ownerId); - if (library.type === LibraryType.UPLOAD && uploadCount <= 1) { - throw new BadRequestException('Cannot delete the last upload library'); - } + await this.findOrFail(id); if (this.watchLibraries) { await this.unwatch(id); @@ -529,10 +495,7 @@ export class LibraryService { } async queueScan(id: string, dto: ScanLibraryDto) { - const library = await this.findOrFail(id); - if (library.type !== LibraryType.EXTERNAL) { - throw new BadRequestException('Can only refresh external libraries'); - } + await this.findOrFail(id); await this.jobRepository.queue({ name: JobName.LIBRARY_SCAN, @@ -556,7 +519,7 @@ export class LibraryService { await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_CLEANUP, data: {} }); // Queue all library refresh - const libraries = await this.repository.getAll(true, LibraryType.EXTERNAL); + const libraries = await this.repository.getAll(true); await this.jobRepository.queueAll( libraries.map((library) => ({ name: JobName.LIBRARY_SCAN, @@ -587,8 +550,8 @@ export class LibraryService { async handleQueueAssetRefresh(job: ILibraryRefreshJob): Promise { const library = await this.repository.get(job.id); - if (!library || library.type !== LibraryType.EXTERNAL) { - this.logger.warn('Can only refresh external libraries'); + if (!library) { + this.logger.warn('Library not found'); return JobStatus.FAILED; } diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index cca6d9eb1..7d7e2f91a 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -409,7 +409,7 @@ export class MetadataService { } const checksum = this.cryptoRepository.hashSha1(video); - let motionAsset = await this.assetRepository.getByChecksum(asset.libraryId, checksum); + let motionAsset = await this.assetRepository.getByChecksum(asset.libraryId ?? null, checksum); if (motionAsset) { this.logger.debug( `Asset ${asset.id}'s motion photo video with checksum ${checksum.toString( diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts index 35a1790a3..e71094f45 100644 --- a/server/test/fixtures/asset.stub.ts +++ b/server/test/fixtures/asset.stub.ts @@ -48,8 +48,6 @@ export const assetStub = { deletedAt: null, isOffline: false, isExternal: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, duplicateId: null, }), @@ -84,8 +82,6 @@ export const assetStub = { sidecarPath: null, isOffline: false, isExternal: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, exifInfo: { fileSizeInByte: 123_000, } as ExifEntity, @@ -114,8 +110,6 @@ export const assetStub = { isFavorite: true, isArchived: false, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, duration: null, isVisible: true, isExternal: false, @@ -156,8 +150,6 @@ export const assetStub = { livePhotoVideo: null, livePhotoVideoId: null, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, tags: [], sharedLinks: [], originalFileName: 'asset-id.jpg', @@ -203,8 +195,6 @@ export const assetStub = { livePhotoVideo: null, livePhotoVideoId: null, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, tags: [], sharedLinks: [], originalFileName: 'asset-id.jpg', @@ -285,8 +275,6 @@ export const assetStub = { livePhotoVideo: null, livePhotoVideoId: null, isOffline: true, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, tags: [], sharedLinks: [], originalFileName: 'asset-id.jpg', @@ -364,8 +352,6 @@ export const assetStub = { isVisible: true, livePhotoVideo: null, livePhotoVideoId: null, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, isExternal: false, isOffline: false, tags: [], @@ -401,8 +387,6 @@ export const assetStub = { isArchived: false, isExternal: false, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, duration: null, isVisible: true, livePhotoVideo: null, @@ -442,8 +426,6 @@ export const assetStub = { isArchived: false, isExternal: false, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, duration: null, isVisible: true, livePhotoVideo: null, @@ -467,8 +449,6 @@ export const assetStub = { isVisible: false, fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'), fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'), - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, exifInfo: { fileSizeInByte: 100_000, timeZone: `America/New_York`, @@ -483,8 +463,6 @@ export const assetStub = { isVisible: false, fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'), fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'), - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, previewPath: '/uploads/user-id/thumbs/path.ext', thumbnailPath: '/uploads/user-id/webp/path.ext', exifInfo: { @@ -502,8 +480,6 @@ export const assetStub = { isVisible: true, fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'), fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'), - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, exifInfo: { fileSizeInByte: 25_000, timeZone: `America/New_York`, @@ -533,8 +509,6 @@ export const assetStub = { isArchived: false, isExternal: false, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, duration: null, isVisible: true, livePhotoVideo: null, @@ -576,8 +550,6 @@ export const assetStub = { isArchived: false, isExternal: false, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, duration: null, isVisible: true, livePhotoVideo: null, @@ -612,8 +584,6 @@ export const assetStub = { isArchived: false, isExternal: false, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, duration: null, isVisible: true, livePhotoVideo: null, @@ -649,8 +619,6 @@ export const assetStub = { isArchived: false, isExternal: false, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, duration: null, isVisible: true, livePhotoVideo: null, @@ -687,8 +655,6 @@ export const assetStub = { isArchived: false, isExternal: false, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, duration: null, isVisible: true, livePhotoVideo: null, @@ -807,8 +773,6 @@ export const assetStub = { livePhotoVideo: null, livePhotoVideoId: null, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, tags: [], sharedLinks: [], originalFileName: 'asset-id.jpg', @@ -848,8 +812,6 @@ export const assetStub = { livePhotoVideo: null, livePhotoVideoId: null, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, tags: [], sharedLinks: [], originalFileName: 'asset-id.jpg', @@ -891,8 +853,6 @@ export const assetStub = { livePhotoVideo: null, livePhotoVideoId: null, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, tags: [], sharedLinks: [], originalFileName: 'asset-id.jpg', diff --git a/server/test/fixtures/error.stub.ts b/server/test/fixtures/error.stub.ts index cea514e26..7e4399d7e 100644 --- a/server/test/fixtures/error.stub.ts +++ b/server/test/fixtures/error.stub.ts @@ -49,9 +49,4 @@ export const errorStub = { statusCode: 400, message: 'The server already has an admin', }, - noDeleteUploadLibrary: { - error: 'Bad Request', - statusCode: 400, - message: 'Cannot delete the last upload library', - }, }; diff --git a/server/test/fixtures/library.stub.ts b/server/test/fixtures/library.stub.ts index bb95439d1..1a83ffe5d 100644 --- a/server/test/fixtures/library.stub.ts +++ b/server/test/fixtures/library.stub.ts @@ -1,30 +1,16 @@ import { join } from 'node:path'; import { APP_MEDIA_LOCATION } from 'src/constants'; import { THUMBNAIL_DIR } from 'src/cores/storage.core'; -import { LibraryEntity, LibraryType } from 'src/entities/library.entity'; +import { LibraryEntity } from 'src/entities/library.entity'; import { userStub } from 'test/fixtures/user.stub'; export const libraryStub = { - uploadLibrary1: Object.freeze({ - id: 'library-id', - name: 'test_library', - assets: [], - owner: userStub.user1, - ownerId: 'user-id', - type: LibraryType.UPLOAD, - importPaths: [], - createdAt: new Date('2022-01-01'), - updatedAt: new Date('2022-01-01'), - refreshedAt: null, - exclusionPatterns: [], - }), externalLibrary1: Object.freeze({ id: 'library-id', name: 'test_library', assets: [], owner: userStub.admin, ownerId: 'admin_id', - type: LibraryType.EXTERNAL, importPaths: [], createdAt: new Date('2023-01-01'), updatedAt: new Date('2023-01-01'), @@ -37,7 +23,6 @@ export const libraryStub = { assets: [], owner: userStub.admin, ownerId: 'admin_id', - type: LibraryType.EXTERNAL, importPaths: [], createdAt: new Date('2021-01-01'), updatedAt: new Date('2022-01-01'), @@ -50,7 +35,6 @@ export const libraryStub = { assets: [], owner: userStub.admin, ownerId: 'admin_id', - type: LibraryType.EXTERNAL, importPaths: ['/foo', '/bar'], createdAt: new Date('2023-01-01'), updatedAt: new Date('2023-01-01'), @@ -63,7 +47,6 @@ export const libraryStub = { assets: [], owner: userStub.admin, ownerId: 'admin_id', - type: LibraryType.EXTERNAL, importPaths: ['/xyz', '/asdf'], createdAt: new Date('2023-01-01'), updatedAt: new Date('2023-01-01'), @@ -76,7 +59,6 @@ export const libraryStub = { assets: [], owner: userStub.admin, ownerId: 'user-id', - type: LibraryType.EXTERNAL, importPaths: [], createdAt: new Date('2023-01-01'), updatedAt: new Date('2023-01-01'), @@ -89,7 +71,6 @@ export const libraryStub = { assets: [], owner: userStub.admin, ownerId: 'user-id', - type: LibraryType.EXTERNAL, importPaths: ['/xyz', '/asdf'], createdAt: new Date('2023-01-01'), updatedAt: new Date('2023-01-01'), @@ -102,7 +83,6 @@ export const libraryStub = { assets: [], owner: userStub.admin, ownerId: 'user-id', - type: LibraryType.EXTERNAL, importPaths: [join(THUMBNAIL_DIR, 'library'), '/xyz', join(APP_MEDIA_LOCATION, 'library')], createdAt: new Date('2023-01-01'), updatedAt: new Date('2023-01-01'), diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts index d83bd4909..ebadc63fa 100644 --- a/server/test/fixtures/shared-link.stub.ts +++ b/server/test/fixtures/shared-link.stub.ts @@ -9,7 +9,6 @@ import { SharedLinkEntity, SharedLinkType } from 'src/entities/shared-link.entit import { UserEntity } from 'src/entities/user.entity'; import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; -import { libraryStub } from 'test/fixtures/library.stub'; import { userStub } from 'test/fixtures/user.stub'; const today = new Date(); @@ -210,8 +209,6 @@ export const sharedLinkStub = { isArchived: false, isExternal: false, isOffline: false, - libraryId: 'library-id', - library: libraryStub.uploadLibrary1, smartInfo: { assetId: 'id_1', tags: [], diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index 21d298599..8d69e35c0 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -7,7 +7,6 @@ export interface IAccessRepositoryMock { asset: Mocked; album: Mocked; authDevice: Mocked; - library: Mocked; timeline: Mocked; memory: Mocked; person: Mocked; @@ -43,10 +42,6 @@ export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock => checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), }, - library: { - checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), - }, - timeline: { checkPartnerAccess: vitest.fn().mockResolvedValue(new Set()), }, diff --git a/server/test/repositories/library.repository.mock.ts b/server/test/repositories/library.repository.mock.ts index 428061986..6f6100d93 100644 --- a/server/test/repositories/library.repository.mock.ts +++ b/server/test/repositories/library.repository.mock.ts @@ -10,8 +10,6 @@ export const newLibraryRepositoryMock = (): Mocked => { softDelete: vitest.fn(), update: vitest.fn(), getStatistics: vitest.fn(), - getDefaultUploadLibrary: vitest.fn(), - getUploadLibraryCount: vitest.fn(), getAssetIds: vitest.fn(), getAllDeleted: vitest.fn(), getAll: vitest.fn(), diff --git a/web/src/lib/components/forms/library-scan-settings-form.svelte b/web/src/lib/components/forms/library-scan-settings-form.svelte index 3896d7df7..244d99bf3 100644 --- a/web/src/lib/components/forms/library-scan-settings-form.svelte +++ b/web/src/lib/components/forms/library-scan-settings-form.svelte @@ -1,5 +1,5 @@