diff --git a/server/.eslintrc.js b/server/.eslintrc.js index f1e6564d8..3673add3c 100644 --- a/server/.eslintrc.js +++ b/server/.eslintrc.js @@ -25,6 +25,12 @@ module.exports = { 'unicorn/prefer-top-level-await': 'off', 'unicorn/prefer-event-target': 'off', 'unicorn/no-thenable': 'off', + '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'error', + // Note: you must disable the base rule as it can report incorrect errors + 'require-await': 'off', + '@typescript-eslint/require-await': 'error', curly: 2, 'prettier/prettier': 0, }, diff --git a/server/e2e/jobs/specs/library-watcher.e2e-spec.ts b/server/e2e/jobs/specs/library-watcher.e2e-spec.ts index 93f716353..d22748e1c 100644 --- a/server/e2e/jobs/specs/library-watcher.e2e-spec.ts +++ b/server/e2e/jobs/specs/library-watcher.e2e-spec.ts @@ -208,7 +208,7 @@ describe(`Library watcher (e2e)`, () => { await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir3`, { recursive: true }); }); - it('should use an updated import paths', async () => { + it('should use an updated import path', async () => { await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir4`, { recursive: true }); await api.libraryApi.setImportPaths(server, admin.accessToken, library.id, [ diff --git a/server/src/domain/activity/activity.spec.ts b/server/src/domain/activity/activity.spec.ts index 79c466b92..10a4c0725 100644 --- a/server/src/domain/activity/activity.spec.ts +++ b/server/src/domain/activity/activity.spec.ts @@ -11,7 +11,7 @@ describe(ActivityService.name, () => { let accessMock: IAccessRepositoryMock; let activityMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { accessMock = newAccessRepositoryMock(); activityMock = newActivityRepositoryMock(); diff --git a/server/src/domain/album/album.service.spec.ts b/server/src/domain/album/album.service.spec.ts index 10b6dde5e..fa0852d8c 100644 --- a/server/src/domain/album/album.service.spec.ts +++ b/server/src/domain/album/album.service.spec.ts @@ -23,7 +23,7 @@ describe(AlbumService.name, () => { let jobMock: jest.Mocked; let userMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { accessMock = newAccessRepositoryMock(); albumMock = newAlbumRepositoryMock(); assetMock = newAssetRepositoryMock(); diff --git a/server/src/domain/api-key/api-key.service.spec.ts b/server/src/domain/api-key/api-key.service.spec.ts index f6d650c41..f3b291084 100644 --- a/server/src/domain/api-key/api-key.service.spec.ts +++ b/server/src/domain/api-key/api-key.service.spec.ts @@ -8,7 +8,7 @@ describe(APIKeyService.name, () => { let keyMock: jest.Mocked; let cryptoMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { cryptoMock = newCryptoRepositoryMock(); keyMock = newKeyRepositoryMock(); sut = new APIKeyService(cryptoMock, keyMock); diff --git a/server/src/domain/asset/asset.service.spec.ts b/server/src/domain/asset/asset.service.spec.ts index 67721dc85..0b8dea717 100644 --- a/server/src/domain/asset/asset.service.spec.ts +++ b/server/src/domain/asset/asset.service.spec.ts @@ -169,7 +169,7 @@ describe(AssetService.name, () => { expect(sut).toBeDefined(); }); - beforeEach(async () => { + beforeEach(() => { accessMock = newAccessRepositoryMock(); assetMock = newAssetRepositoryMock(); communicationMock = newCommunicationRepositoryMock(); diff --git a/server/src/domain/audit/audit.service.spec.ts b/server/src/domain/audit/audit.service.spec.ts index d2f8bb6bc..861e0edc1 100644 --- a/server/src/domain/audit/audit.service.spec.ts +++ b/server/src/domain/audit/audit.service.spec.ts @@ -31,7 +31,7 @@ describe(AuditService.name, () => { let storageMock: jest.Mocked; let userMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { accessMock = newAccessRepositoryMock(); assetMock = newAssetRepositoryMock(); cryptoMock = newCryptoRepositoryMock(); diff --git a/server/src/domain/auth/auth.service.spec.ts b/server/src/domain/auth/auth.service.spec.ts index 359b28a00..214b6748e 100644 --- a/server/src/domain/auth/auth.service.spec.ts +++ b/server/src/domain/auth/auth.service.spec.ts @@ -74,7 +74,7 @@ describe('AuthService', () => { let callbackMock: jest.Mock; let userinfoMock: jest.Mock; - beforeEach(async () => { + beforeEach(() => { callbackMock = jest.fn().mockReturnValue({ access_token: 'access-token' }); userinfoMock = jest.fn().mockResolvedValue({ sub, email }); diff --git a/server/src/domain/database/database.service.spec.ts b/server/src/domain/database/database.service.spec.ts index 703805b06..14464c0cd 100644 --- a/server/src/domain/database/database.service.spec.ts +++ b/server/src/domain/database/database.service.spec.ts @@ -13,7 +13,7 @@ describe(DatabaseService.name, () => { let sut: DatabaseService; let databaseMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { databaseMock = newDatabaseRepositoryMock(); sut = new DatabaseService(databaseMock); @@ -31,7 +31,7 @@ describe(DatabaseService.name, () => { let errorLog: jest.SpyInstance; let warnLog: jest.SpyInstance; - beforeEach(async () => { + beforeEach(() => { fatalLog = jest.spyOn(ImmichLogger.prototype, 'fatal'); errorLog = jest.spyOn(ImmichLogger.prototype, 'error'); warnLog = jest.spyOn(ImmichLogger.prototype, 'warn'); diff --git a/server/src/domain/database/database.service.ts b/server/src/domain/database/database.service.ts index d697d032b..946c6dac8 100644 --- a/server/src/domain/database/database.service.ts +++ b/server/src/domain/database/database.service.ts @@ -1,6 +1,5 @@ import { ImmichLogger } from '@app/infra/logger'; import { Inject, Injectable } from '@nestjs/common'; -import { QueryFailedError } from 'typeorm'; import { Version, VersionType } from '../domain.constant'; import { DatabaseExtension, @@ -61,7 +60,9 @@ export class DatabaseService { } private async createVectorExtension() { - await this.databaseRepository.createExtension(this.vectorExt).catch(async (error: QueryFailedError) => { + try { + await this.databaseRepository.createExtension(this.vectorExt); + } catch (error) { const otherExt = this.vectorExt === DatabaseExtension.VECTORS ? DatabaseExtension.VECTOR : DatabaseExtension.VECTORS; this.logger.fatal(` @@ -78,7 +79,7 @@ export class DatabaseService { In this case, you may set either extension now, but you will not be able to switch to the other extension following a successful startup. `); throw error; - }); + } } private async updateVectorExtension() { diff --git a/server/src/domain/domain.util.ts b/server/src/domain/domain.util.ts index 5fdfd7ec5..1dadf03ae 100644 --- a/server/src/domain/domain.util.ts +++ b/server/src/domain/domain.util.ts @@ -1,3 +1,4 @@ +import { ImmichLogger } from '@app/infra/logger'; import { applyDecorators } from '@nestjs/common'; import { ApiProperty } from '@nestjs/swagger'; import { Transform, Type } from 'class-transformer'; @@ -157,7 +158,7 @@ export type Paginated = Promise>; export async function* usePagination( pageSize: number, - getNextPage: (pagination: PaginationOptions) => Paginated, + getNextPage: (pagination: PaginationOptions) => PaginationResult | Paginated, ) { let hasNextPage = true; @@ -252,3 +253,7 @@ export const setIsSuperset = (set: Set, subset: Set): boolean => { export const setIsEqual = (setA: Set, setB: Set): boolean => { return setA.size === setB.size && setIsSuperset(setA, setB); }; + +export const handlePromiseError = (promise: Promise, logger: ImmichLogger): void => { + promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack)); +}; diff --git a/server/src/domain/download/download.service.spec.ts b/server/src/domain/download/download.service.spec.ts index bc0e32b6b..fb9ae9567 100644 --- a/server/src/domain/download/download.service.spec.ts +++ b/server/src/domain/download/download.service.spec.ts @@ -34,7 +34,7 @@ describe(DownloadService.name, () => { expect(sut).toBeDefined(); }); - beforeEach(async () => { + beforeEach(() => { accessMock = newAccessRepositoryMock(); assetMock = newAssetRepositoryMock(); storageMock = newStorageRepositoryMock(); diff --git a/server/src/domain/download/download.service.ts b/server/src/domain/download/download.service.ts index 0ead29849..afd57b7d1 100644 --- a/server/src/domain/download/download.service.ts +++ b/server/src/domain/download/download.service.ts @@ -114,9 +114,7 @@ export class DownloadService { const assetIds = dto.assetIds; await this.access.requirePermission(auth, Permission.ASSET_DOWNLOAD, assetIds); const assets = await this.assetRepository.getByIds(assetIds); - return (async function* () { - yield assets; - })(); + return usePagination(PAGINATION_SIZE, () => ({ hasNextPage: false, items: assets })); } if (dto.albumId) { diff --git a/server/src/domain/job/job.service.spec.ts b/server/src/domain/job/job.service.spec.ts index 5a4d26b3c..9fe38a2ff 100644 --- a/server/src/domain/job/job.service.spec.ts +++ b/server/src/domain/job/job.service.spec.ts @@ -37,7 +37,7 @@ describe(JobService.name, () => { let jobMock: jest.Mocked; let personMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { assetMock = newAssetRepositoryMock(); configMock = newSystemConfigRepositoryMock(); communicationMock = newCommunicationRepositoryMock(); diff --git a/server/src/domain/library/library.service.spec.ts b/server/src/domain/library/library.service.spec.ts index ba1dd8374..ec5d25a3b 100644 --- a/server/src/domain/library/library.service.spec.ts +++ b/server/src/domain/library/library.service.spec.ts @@ -17,6 +17,7 @@ import { systemConfigStub, userStub, } from '@test'; +import { when } from 'jest-when'; import { Stats } from 'node:fs'; import { ILibraryFileJob, ILibraryRefreshJob, JobName } from '../job'; import { @@ -55,7 +56,7 @@ describe(LibraryService.name, () => { storageMock = newStorageRepositoryMock(); // Always validate owner access for library. - accessMock.library.checkOwnerAccess.mockImplementation(async (_, libraryIds) => libraryIds); + accessMock.library.checkOwnerAccess.mockImplementation((_, libraryIds) => Promise.resolve(libraryIds)); sut = new LibraryService( accessMock, @@ -106,19 +107,13 @@ describe(LibraryService.name, () => { configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled); libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); - libraryMock.get.mockImplementation(async (id) => { - switch (id) { - case libraryStub.externalLibraryWithImportPaths1.id: { - return libraryStub.externalLibraryWithImportPaths1; - } - case libraryStub.externalLibraryWithImportPaths2.id: { - return libraryStub.externalLibraryWithImportPaths2; - } - default: { - return null; - } - } - }); + when(libraryMock.get) + .calledWith(libraryStub.externalLibraryWithImportPaths1.id) + .mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); + + when(libraryMock.get) + .calledWith(libraryStub.externalLibraryWithImportPaths2.id) + .mockResolvedValue(libraryStub.externalLibraryWithImportPaths2); await sut.init(); @@ -1278,19 +1273,13 @@ describe(LibraryService.name, () => { configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled); libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); - libraryMock.get.mockImplementation(async (id) => { - switch (id) { - case libraryStub.externalLibraryWithImportPaths1.id: { - return libraryStub.externalLibraryWithImportPaths1; - } - case libraryStub.externalLibraryWithImportPaths2.id: { - return libraryStub.externalLibraryWithImportPaths2; - } - default: { - return null; - } - } - }); + when(libraryMock.get) + .calledWith(libraryStub.externalLibraryWithImportPaths1.id) + .mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); + + when(libraryMock.get) + .calledWith(libraryStub.externalLibraryWithImportPaths2.id) + .mockResolvedValue(libraryStub.externalLibraryWithImportPaths2); const mockClose = jest.fn(); storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose })); @@ -1304,9 +1293,8 @@ describe(LibraryService.name, () => { describe('handleDeleteLibrary', () => { it('should not delete a nonexistent library', async () => { - libraryMock.get.mockImplementation(async () => { - return null; - }); + libraryMock.get.mockResolvedValue(null); + libraryMock.getAssetIds.mockResolvedValue([]); libraryMock.delete.mockImplementation(async () => {}); diff --git a/server/src/domain/library/library.service.ts b/server/src/domain/library/library.service.ts index 4d8912685..33edf74bf 100644 --- a/server/src/domain/library/library.service.ts +++ b/server/src/domain/library/library.service.ts @@ -9,7 +9,7 @@ import picomatch from 'picomatch'; import { AccessCore, Permission } from '../access'; import { AuthDto } from '../auth'; import { mimeTypes } from '../domain.constant'; -import { usePagination, validateCronExpression } from '../domain.util'; +import { handlePromiseError, usePagination, validateCronExpression } from '../domain.util'; import { IBaseJob, IEntityJob, ILibraryFileJob, ILibraryRefreshJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job'; import { @@ -43,7 +43,7 @@ export class LibraryService extends EventEmitter { private access: AccessCore; private configCore: SystemConfigCore; private watchLibraries = false; - private watchers: Record void> = {}; + private watchers: Record Promise> = {}; constructor( @Inject(IAccessRepository) accessRepository: IAccessRepository, @@ -73,7 +73,11 @@ export class LibraryService extends EventEmitter { this.jobRepository.addCronJob( 'libraryScan', scan.cronExpression, - () => this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SCAN_ALL, data: { force: false } }), + () => + handlePromiseError( + this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SCAN_ALL, data: { force: false } }), + this.logger, + ), scan.enabled, ); @@ -81,12 +85,12 @@ export class LibraryService extends EventEmitter { await this.watchAll(); } - this.configCore.config$.subscribe(async ({ library }) => { + this.configCore.config$.subscribe(({ library }) => { this.jobRepository.updateCronJob('libraryScan', library.scan.cronExpression, library.scan.enabled); if (library.watch.enabled !== this.watchLibraries) { this.watchLibraries = library.watch.enabled; - await (this.watchLibraries ? this.watchAll() : this.unwatchAll()); + handlePromiseError(this.watchLibraries ? this.watchAll() : this.unwatchAll(), this.logger); } }); } @@ -124,28 +128,37 @@ export class LibraryService extends EventEmitter { }, { onReady: () => _resolve(), - onAdd: async (path) => { - this.logger.debug(`File add event received for ${path} in library ${library.id}}`); - if (matcher(path)) { - await this.scanAssets(library.id, [path], library.ownerId, false); - } - this.emit('add', path); + onAdd: (path) => { + const handler = async () => { + this.logger.debug(`File add event received for ${path} in library ${library.id}}`); + if (matcher(path)) { + await this.scanAssets(library.id, [path], library.ownerId, false); + } + this.emit('add', path); + }; + return handlePromiseError(handler(), this.logger); }, - onChange: async (path) => { - this.logger.debug(`Detected file change for ${path} in library ${library.id}`); - if (matcher(path)) { - // Note: if the changed file was not previously imported, it will be imported now. - await this.scanAssets(library.id, [path], library.ownerId, false); - } - this.emit('change', path); + onChange: (path) => { + const handler = async () => { + this.logger.debug(`Detected file change for ${path} in library ${library.id}`); + if (matcher(path)) { + // Note: if the changed file was not previously imported, it will be imported now. + await this.scanAssets(library.id, [path], library.ownerId, false); + } + this.emit('change', path); + }; + return handlePromiseError(handler(), this.logger); }, - onUnlink: async (path) => { - this.logger.debug(`Detected deleted file at ${path} in library ${library.id}`); - const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(library.id, path); - if (asset && matcher(path)) { - await this.assetRepository.save({ id: asset.id, isOffline: true }); - } - this.emit('unlink', path); + onUnlink: (path) => { + const handler = async () => { + this.logger.debug(`Detected deleted file at ${path} in library ${library.id}`); + const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(library.id, path); + if (asset && matcher(path)) { + await this.assetRepository.save({ id: asset.id, isOffline: true }); + } + this.emit('unlink', path); + }; + return handlePromiseError(handler(), this.logger); }, onError: (error) => { // TODO: should we log, or throw an exception? diff --git a/server/src/domain/media/media.service.spec.ts b/server/src/domain/media/media.service.spec.ts index 094401637..244978d09 100644 --- a/server/src/domain/media/media.service.spec.ts +++ b/server/src/domain/media/media.service.spec.ts @@ -48,7 +48,7 @@ describe(MediaService.name, () => { let storageMock: jest.Mocked; let cryptoMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { assetMock = newAssetRepositoryMock(); configMock = newSystemConfigRepositoryMock(); jobMock = newJobRepositoryMock(); diff --git a/server/src/domain/metadata/metadata.service.spec.ts b/server/src/domain/metadata/metadata.service.spec.ts index 6eafc176b..3da9ba371 100644 --- a/server/src/domain/metadata/metadata.service.spec.ts +++ b/server/src/domain/metadata/metadata.service.spec.ts @@ -56,7 +56,7 @@ describe(MetadataService.name, () => { let databaseMock: jest.Mocked; let sut: MetadataService; - beforeEach(async () => { + beforeEach(() => { albumMock = newAlbumRepositoryMock(); assetMock = newAssetRepositoryMock(); configMock = newSystemConfigRepositoryMock(); diff --git a/server/src/domain/metadata/metadata.service.ts b/server/src/domain/metadata/metadata.service.ts index 562568adf..7d2248511 100644 --- a/server/src/domain/metadata/metadata.service.ts +++ b/server/src/domain/metadata/metadata.service.ts @@ -7,7 +7,7 @@ import _ from 'lodash'; import { Duration } from 'luxon'; import { constants } from 'node:fs/promises'; import { Subscription } from 'rxjs'; -import { usePagination } from '../domain.util'; +import { handlePromiseError, usePagination } from '../domain.util'; import { IBaseJob, IEntityJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job'; import { ClientEvent, @@ -124,7 +124,7 @@ export class MetadataService { async init() { if (!this.subscription) { - this.subscription = this.configCore.config$.subscribe(() => this.init()); + this.subscription = this.configCore.config$.subscribe(() => handlePromiseError(this.init(), this.logger)); } const { reverseGeocoding } = await this.configCore.getConfig(); diff --git a/server/src/domain/partner/partner.service.spec.ts b/server/src/domain/partner/partner.service.spec.ts index 2bc5f3ca9..6e9c10c5c 100644 --- a/server/src/domain/partner/partner.service.spec.ts +++ b/server/src/domain/partner/partner.service.spec.ts @@ -49,7 +49,7 @@ describe(PartnerService.name, () => { let partnerMock: jest.Mocked; let accessMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { partnerMock = newPartnerRepositoryMock(); sut = new PartnerService(partnerMock, accessMock); }); diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index ffda9034b..0419fc056 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -80,7 +80,7 @@ describe(PersonService.name, () => { let cryptoMock: jest.Mocked; let sut: PersonService; - beforeEach(async () => { + beforeEach(() => { accessMock = newAccessRepositoryMock(); assetMock = newAssetRepositoryMock(); configMock = newSystemConfigRepositoryMock(); diff --git a/server/src/domain/repositories/communication.repository.ts b/server/src/domain/repositories/communication.repository.ts index daf0aef0a..4a3bc552c 100644 --- a/server/src/domain/repositories/communication.repository.ts +++ b/server/src/domain/repositories/communication.repository.ts @@ -34,7 +34,7 @@ export interface ClientEventMap { [ClientEvent.NEW_RELEASE]: ReleaseNotification; } -export type OnConnectCallback = (userId: string) => Promise; +export type OnConnectCallback = (userId: string) => void | Promise; export type OnServerEventCallback = () => Promise; export interface ICommunicationRepository { diff --git a/server/src/domain/repositories/storage.repository.ts b/server/src/domain/repositories/storage.repository.ts index c88095b17..d263713af 100644 --- a/server/src/domain/repositories/storage.repository.ts +++ b/server/src/domain/repositories/storage.repository.ts @@ -47,6 +47,6 @@ export interface IStorageRepository { crawl(crawlOptions: CrawlOptionsDto): Promise; copyFile(source: string, target: string): Promise; rename(source: string, target: string): Promise; - watch(paths: string[], options: WatchOptions, events: Partial): () => void; + watch(paths: string[], options: WatchOptions, events: Partial): () => Promise; utimes(filepath: string, atime: Date, mtime: Date): Promise; } diff --git a/server/src/domain/search/search.service.ts b/server/src/domain/search/search.service.ts index 5b5639998..8dce8434c 100644 --- a/server/src/domain/search/search.service.ts +++ b/server/src/domain/search/search.service.ts @@ -181,7 +181,7 @@ export class SearchService { return userIds; } - private async mapResponse(assets: AssetEntity[], nextPage: string | null): Promise { + private mapResponse(assets: AssetEntity[], nextPage: string | null): SearchResponseDto { return { albums: { total: 0, count: 0, items: [], facets: [] }, assets: { diff --git a/server/src/domain/server-info/server-info.service.ts b/server/src/domain/server-info/server-info.service.ts index 822235730..ba295aefa 100644 --- a/server/src/domain/server-info/server-info.service.ts +++ b/server/src/domain/server-info/server-info.service.ts @@ -170,7 +170,7 @@ export class ServerInfoService { return true; } - private async handleConnect(userId: string) { + private handleConnect(userId: string) { this.communicationRepository.send(ClientEvent.SERVER_VERSION, userId, serverVersion); this.newReleaseNotification(userId); } diff --git a/server/src/domain/shared-link/shared-link.service.spec.ts b/server/src/domain/shared-link/shared-link.service.spec.ts index 6d95d2831..f0d0715a3 100644 --- a/server/src/domain/shared-link/shared-link.service.spec.ts +++ b/server/src/domain/shared-link/shared-link.service.spec.ts @@ -22,7 +22,7 @@ describe(SharedLinkService.name, () => { let cryptoMock: jest.Mocked; let shareMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { accessMock = newAccessRepositoryMock(); cryptoMock = newCryptoRepositoryMock(); shareMock = newSharedLinkRepositoryMock(); diff --git a/server/src/domain/smart-info/smart-info.service.spec.ts b/server/src/domain/smart-info/smart-info.service.spec.ts index 9835ea1a5..712c2b6a7 100644 --- a/server/src/domain/smart-info/smart-info.service.spec.ts +++ b/server/src/domain/smart-info/smart-info.service.spec.ts @@ -35,7 +35,7 @@ describe(SmartInfoService.name, () => { let machineMock: jest.Mocked; let databaseMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { assetMock = newAssetRepositoryMock(); configMock = newSystemConfigRepositoryMock(); searchMock = newSearchRepositoryMock(); diff --git a/server/src/domain/storage-template/storage-template.service.spec.ts b/server/src/domain/storage-template/storage-template.service.spec.ts index 67d2bd222..1db312d78 100644 --- a/server/src/domain/storage-template/storage-template.service.spec.ts +++ b/server/src/domain/storage-template/storage-template.service.spec.ts @@ -45,7 +45,7 @@ describe(StorageTemplateService.name, () => { expect(sut).toBeDefined(); }); - beforeEach(async () => { + beforeEach(() => { configMock = newSystemConfigRepositoryMock(); assetMock = newAssetRepositoryMock(); albumMock = newAlbumRepositoryMock(); diff --git a/server/src/domain/storage/storage.service.spec.ts b/server/src/domain/storage/storage.service.spec.ts index 0c5531e5f..785891086 100644 --- a/server/src/domain/storage/storage.service.spec.ts +++ b/server/src/domain/storage/storage.service.spec.ts @@ -6,7 +6,7 @@ describe(StorageService.name, () => { let sut: StorageService; let storageMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { storageMock = newStorageRepositoryMock(); sut = new StorageService(storageMock); }); diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index a3d29b1ee..35e306a70 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -148,7 +148,7 @@ describe(SystemConfigService.name, () => { let communicationMock: jest.Mocked; let smartInfoMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { delete process.env.IMMICH_CONFIG_FILE; configMock = newSystemConfigRepositoryMock(); communicationMock = newCommunicationRepositoryMock(); diff --git a/server/src/domain/system-config/system-config.service.ts b/server/src/domain/system-config/system-config.service.ts index 39a3ea1df..54d113cf6 100644 --- a/server/src/domain/system-config/system-config.service.ts +++ b/server/src/domain/system-config/system-config.service.ts @@ -118,7 +118,7 @@ export class SystemConfigService { await this.core.refreshConfig(); } - private async setLogLevel({ logging }: SystemConfig) { + private setLogLevel({ logging }: SystemConfig) { const envLevel = this.getEnvLogLevel(); const configLevel = logging.enabled ? logging.level : false; const level = envLevel ?? configLevel; @@ -130,7 +130,7 @@ export class SystemConfigService { return process.env.LOG_LEVEL as LogLevel; } - private async validateConfig(newConfig: SystemConfig, oldConfig: SystemConfig) { + private validateConfig(newConfig: SystemConfig, oldConfig: SystemConfig) { if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && this.getEnvLogLevel()) { throw new Error('Logging cannot be changed while the environment variable LOG_LEVEL is set.'); } diff --git a/server/src/domain/trash/trash.service.spec.ts b/server/src/domain/trash/trash.service.spec.ts index 1b200a1bd..81f4186e8 100644 --- a/server/src/domain/trash/trash.service.spec.ts +++ b/server/src/domain/trash/trash.service.spec.ts @@ -23,7 +23,7 @@ describe(TrashService.name, () => { expect(sut).toBeDefined(); }); - beforeEach(async () => { + beforeEach(() => { accessMock = newAccessRepositoryMock(); assetMock = newAssetRepositoryMock(); communicationMock = newCommunicationRepositoryMock(); diff --git a/server/src/domain/user/user.service.spec.ts b/server/src/domain/user/user.service.spec.ts index 13ae149b4..a1e8b28c1 100644 --- a/server/src/domain/user/user.service.spec.ts +++ b/server/src/domain/user/user.service.spec.ts @@ -49,7 +49,7 @@ describe(UserService.name, () => { let libraryMock: jest.Mocked; let storageMock: jest.Mocked; - beforeEach(async () => { + beforeEach(() => { albumMock = newAlbumRepositoryMock(); assetMock = newAssetRepositoryMock(); cryptoRepositoryMock = newCryptoRepositoryMock(); diff --git a/server/src/immich/interceptors/error.interceptor.ts b/server/src/immich/interceptors/error.interceptor.ts index 1dc52258e..5fabdbe55 100644 --- a/server/src/immich/interceptors/error.interceptor.ts +++ b/server/src/immich/interceptors/error.interceptor.ts @@ -15,7 +15,7 @@ import { routeToErrorMessage } from '../app.utils'; export class ErrorInterceptor implements NestInterceptor { private logger = new ImmichLogger(ErrorInterceptor.name); - async intercept(context: ExecutionContext, next: CallHandler): Promise> { + intercept(context: ExecutionContext, next: CallHandler): Observable { return next.handle().pipe( catchError((error) => throwError(() => { diff --git a/server/src/immich/interceptors/file-upload.interceptor.ts b/server/src/immich/interceptors/file-upload.interceptor.ts index 52cc447e8..a698dc8a6 100644 --- a/server/src/immich/interceptors/file-upload.interceptor.ts +++ b/server/src/immich/interceptors/file-upload.interceptor.ts @@ -40,9 +40,9 @@ interface Callback { (error: null, result: T): void; } -const callbackify = async (target: (...arguments_: any[]) => T, callback: Callback) => { +const callbackify = (target: (...arguments_: any[]) => T, callback: Callback) => { try { - return callback(null, await target()); + return callback(null, target()); } catch (error: Error | any) { return callback(error); } diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index 4ed885e58..4bcfc963f 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -544,7 +544,7 @@ export class AssetRepository implements IAssetRepository { } async getStatistics(ownerId: string, options: AssetStatsOptions): Promise { - let builder = await this.repository + let builder = this.repository .createQueryBuilder('asset') .select(`COUNT(asset.id)`, 'count') .addSelect(`asset.type`, 'type') diff --git a/server/src/infra/repositories/library.repository.ts b/server/src/infra/repositories/library.repository.ts index 804fdc481..89db3d175 100644 --- a/server/src/infra/repositories/library.repository.ts +++ b/server/src/infra/repositories/library.repository.ts @@ -166,7 +166,7 @@ export class LibraryRepository implements ILibraryRepository { @GenerateSql({ params: [DummyValue.UUID] }) async getAssetIds(libraryId: string, withDeleted = false): Promise { - let query = await this.repository + let query = this.repository .createQueryBuilder('library') .innerJoinAndSelect('library.assets', 'assets') .where('library.id = :id', { id: libraryId }) diff --git a/server/src/infra/repositories/media.repository.ts b/server/src/infra/repositories/media.repository.ts index 1f9395ff2..d5e4cd36f 100644 --- a/server/src/infra/repositories/media.repository.ts +++ b/server/src/infra/repositories/media.repository.ts @@ -1,4 +1,11 @@ -import { CropOptions, IMediaRepository, ResizeOptions, TranscodeOptions, VideoInfo } from '@app/domain'; +import { + CropOptions, + IMediaRepository, + ResizeOptions, + TranscodeOptions, + VideoInfo, + handlePromiseError, +} from '@app/domain'; import { Colorspace } from '@app/infra/entities'; import { ImmichLogger } from '@app/infra/logger'; import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'; @@ -99,8 +106,8 @@ export class MediaRepository implements IMediaRepository { .addOptions('-pass', '2') .addOptions('-passlogfile', output) .on('error', reject) - .on('end', () => fs.unlink(`${output}-0.log`)) - .on('end', () => fs.rm(`${output}-0.log.mbtree`, { force: true })) + .on('end', () => handlePromiseError(fs.unlink(`${output}-0.log`), this.logger)) + .on('end', () => handlePromiseError(fs.rm(`${output}-0.log.mbtree`, { force: true }), this.logger)) .on('end', resolve) .run(); }) diff --git a/server/src/test-utils/utils.ts b/server/src/test-utils/utils.ts index e5566f95d..7b4faf99b 100644 --- a/server/src/test-utils/utils.ts +++ b/server/src/test-utils/utils.ts @@ -75,22 +75,22 @@ class JobMock implements IJobRepository { async resume() {} async empty() {} async setConcurrency() {} - async getQueueStatus() { - return null as any; + getQueueStatus() { + return Promise.resolve(null) as any; } - async getJobCounts() { - return null as any; + getJobCounts() { + return Promise.resolve(null) as any; } async pause() {} - async clear() { - return []; + clear() { + return Promise.resolve([]); } async waitForQueueCompletion() {} } class MediaMockRepository extends MediaRepository { - async generateThumbhash() { - return Buffer.from('mock-thumbhash'); + generateThumbhash() { + return Promise.resolve(Buffer.from('mock-thumbhash')); } } diff --git a/server/test/repositories/storage.repository.mock.ts b/server/test/repositories/storage.repository.mock.ts index 1ee57b78d..1ef51fabc 100644 --- a/server/test/repositories/storage.repository.mock.ts +++ b/server/test/repositories/storage.repository.mock.ts @@ -3,7 +3,7 @@ import { WatchOptions } from 'chokidar'; interface MockWatcherOptions { items?: Array<{ event: 'change' | 'add' | 'unlink' | 'error'; value: string }>; - close?: () => void; + close?: () => Promise; } export const makeMockWatcher = @@ -29,7 +29,12 @@ export const makeMockWatcher = } } } - return () => close?.(); + + if (close) { + return () => close(); + } + + return () => Promise.resolve(); }; export const newStorageRepositoryMock = (reset = true): jest.Mocked => {