From 3d971f69dc46998308f1db252c5d09c55f37c0b9 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen <jason@rasm.me> Date: Thu, 17 Oct 2024 13:11:51 -0400 Subject: [PATCH] refactor(server): storage template options (#13553) --- server/src/constants.ts | 29 ---------- .../controllers/system-config.controller.ts | 8 ++- .../services/storage-template.service.spec.ts | 35 +++++++++++ .../src/services/storage-template.service.ts | 58 ++++++++++++------- .../services/system-config.service.spec.ts | 35 ----------- server/src/services/system-config.service.ts | 27 +-------- 6 files changed, 80 insertions(+), 112 deletions(-) diff --git a/server/src/constants.ts b/server/src/constants.ts index eef9ffab05..e99970723a 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -30,35 +30,6 @@ export const excludePaths = ['/.well-known/immich', '/custom.css', '/favicon.ico export const FACE_THUMBNAIL_SIZE = 250; -export const supportedYearTokens = ['y', 'yy']; -export const supportedMonthTokens = ['M', 'MM', 'MMM', 'MMMM']; -export const supportedWeekTokens = ['W', 'WW']; -export const supportedDayTokens = ['d', 'dd']; -export const supportedHourTokens = ['h', 'hh', 'H', 'HH']; -export const supportedMinuteTokens = ['m', 'mm']; -export const supportedSecondTokens = ['s', 'ss', 'SSS']; -export const supportedPresetTokens = [ - '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}/{{MM}}-{{dd}}/{{filename}}', - '{{y}}/{{MMMM}}-{{dd}}/{{filename}}', - '{{y}}/{{MM}}/{{filename}}', - '{{y}}/{{#if album}}{{album}}{{else}}Other/{{MM}}{{/if}}/{{filename}}', - '{{y}}/{{MMM}}/{{filename}}', - '{{y}}/{{MMMM}}/{{filename}}', - '{{y}}/{{MM}}/{{dd}}/{{filename}}', - '{{y}}/{{MMMM}}/{{dd}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}-{{MMM}}-{{dd}}/{{filename}}', - '{{y}}-{{MMMM}}-{{dd}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}/{{filename}}', - '{{y}}/{{y}}-{{WW}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}-{{dd}}/{{assetId}}', - '{{y}}/{{y}}-{{MM}}/{{assetId}}', - '{{y}}/{{y}}-{{WW}}/{{assetId}}', - '{{album}}/{{filename}}', -]; - type ModelInfo = { dimSize: number }; export const CLIP_MODEL_INFO: Record<string, ModelInfo> = { RN101__openai: { dimSize: 512 }, diff --git a/server/src/controllers/system-config.controller.ts b/server/src/controllers/system-config.controller.ts index f59c8ad66c..58e8bde87b 100644 --- a/server/src/controllers/system-config.controller.ts +++ b/server/src/controllers/system-config.controller.ts @@ -3,12 +3,16 @@ import { ApiTags } from '@nestjs/swagger'; import { SystemConfigDto, SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto'; import { Permission } from 'src/enum'; import { Authenticated } from 'src/middleware/auth.guard'; +import { StorageTemplateService } from 'src/services/storage-template.service'; import { SystemConfigService } from 'src/services/system-config.service'; @ApiTags('System Config') @Controller('system-config') export class SystemConfigController { - constructor(private service: SystemConfigService) {} + constructor( + private service: SystemConfigService, + private storageTemplateService: StorageTemplateService, + ) {} @Get() @Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true }) @@ -31,6 +35,6 @@ export class SystemConfigController { @Get('storage-template-options') @Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true }) getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { - return this.service.getStorageTemplateOptions(); + return this.storageTemplateService.getStorageTemplateOptions(); } } diff --git a/server/src/services/storage-template.service.spec.ts b/server/src/services/storage-template.service.spec.ts index 6e5af3baf9..fd063bd50d 100644 --- a/server/src/services/storage-template.service.spec.ts +++ b/server/src/services/storage-template.service.spec.ts @@ -70,6 +70,41 @@ describe(StorageTemplateService.name, () => { }); }); + describe('getStorageTemplateOptions', () => { + it('should send back the datetime variables', () => { + expect(sut.getStorageTemplateOptions()).toEqual({ + dayOptions: ['d', 'dd'], + hourOptions: ['h', 'hh', 'H', 'HH'], + minuteOptions: ['m', 'mm'], + monthOptions: ['M', 'MM', 'MMM', 'MMMM'], + presetOptions: [ + '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', + '{{y}}/{{MM}}-{{dd}}/{{filename}}', + '{{y}}/{{MMMM}}-{{dd}}/{{filename}}', + '{{y}}/{{MM}}/{{filename}}', + '{{y}}/{{#if album}}{{album}}{{else}}Other/{{MM}}{{/if}}/{{filename}}', + '{{y}}/{{MMM}}/{{filename}}', + '{{y}}/{{MMMM}}/{{filename}}', + '{{y}}/{{MM}}/{{dd}}/{{filename}}', + '{{y}}/{{MMMM}}/{{dd}}/{{filename}}', + '{{y}}/{{y}}-{{MM}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', + '{{y}}-{{MM}}-{{dd}}/{{filename}}', + '{{y}}-{{MMM}}-{{dd}}/{{filename}}', + '{{y}}-{{MMMM}}-{{dd}}/{{filename}}', + '{{y}}/{{y}}-{{MM}}/{{filename}}', + '{{y}}/{{y}}-{{WW}}/{{filename}}', + '{{y}}/{{y}}-{{MM}}-{{dd}}/{{assetId}}', + '{{y}}/{{y}}-{{MM}}/{{assetId}}', + '{{y}}/{{y}}-{{WW}}/{{assetId}}', + '{{album}}/{{filename}}', + ], + secondOptions: ['s', 'ss', 'SSS'], + weekOptions: ['W', 'WW'], + yearOptions: ['y', 'yy'], + }); + }); + }); + describe('handleMigrationSingle', () => { it('should skip when storage template is disabled', async () => { systemMock.get.mockResolvedValue({ storageTemplate: { enabled: false } }); diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts index e400981f54..d239435660 100644 --- a/server/src/services/storage-template.service.ts +++ b/server/src/services/storage-template.service.ts @@ -3,17 +3,9 @@ import handlebar from 'handlebars'; import { DateTime } from 'luxon'; import path from 'node:path'; import sanitize from 'sanitize-filename'; -import { - supportedDayTokens, - supportedHourTokens, - supportedMinuteTokens, - supportedMonthTokens, - supportedSecondTokens, - supportedWeekTokens, - supportedYearTokens, -} from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; import { OnEvent } from 'src/decorators'; +import { SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType, AssetType, StorageFolder } from 'src/enum'; import { DatabaseLock } from 'src/interfaces/database.interface'; @@ -23,6 +15,38 @@ import { BaseService } from 'src/services/base.service'; import { getLivePhotoMotionFilename } from 'src/utils/file'; import { usePagination } from 'src/utils/pagination'; +const storageTokens = { + secondOptions: ['s', 'ss', 'SSS'], + minuteOptions: ['m', 'mm'], + dayOptions: ['d', 'dd'], + weekOptions: ['W', 'WW'], + hourOptions: ['h', 'hh', 'H', 'HH'], + yearOptions: ['y', 'yy'], + monthOptions: ['M', 'MM', 'MMM', 'MMMM'], +}; + +const storagePresets = [ + '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', + '{{y}}/{{MM}}-{{dd}}/{{filename}}', + '{{y}}/{{MMMM}}-{{dd}}/{{filename}}', + '{{y}}/{{MM}}/{{filename}}', + '{{y}}/{{#if album}}{{album}}{{else}}Other/{{MM}}{{/if}}/{{filename}}', + '{{y}}/{{MMM}}/{{filename}}', + '{{y}}/{{MMMM}}/{{filename}}', + '{{y}}/{{MM}}/{{dd}}/{{filename}}', + '{{y}}/{{MMMM}}/{{dd}}/{{filename}}', + '{{y}}/{{y}}-{{MM}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', + '{{y}}-{{MM}}-{{dd}}/{{filename}}', + '{{y}}-{{MMM}}-{{dd}}/{{filename}}', + '{{y}}-{{MMMM}}-{{dd}}/{{filename}}', + '{{y}}/{{y}}-{{MM}}/{{filename}}', + '{{y}}/{{y}}-{{WW}}/{{filename}}', + '{{y}}/{{y}}-{{MM}}-{{dd}}/{{assetId}}', + '{{y}}/{{y}}-{{MM}}/{{assetId}}', + '{{y}}/{{y}}-{{WW}}/{{assetId}}', + '{{album}}/{{filename}}', +]; + export interface MoveAssetMetadata { storageLabel: string | null; filename: string; @@ -80,6 +104,10 @@ export class StorageTemplateService extends BaseService { } } + getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { + return { ...storageTokens, presetOptions: storagePresets }; + } + async handleMigrationSingle({ id }: IEntityJob): Promise<JobStatus> { const config = await this.getConfig({ withCache: true }); const storageTemplateEnabled = config.storageTemplate.enabled; @@ -277,17 +305,7 @@ export class StorageTemplateService extends BaseService { const zone = asset.exifInfo?.timeZone || systemTimeZone; const dt = DateTime.fromJSDate(asset.fileCreatedAt, { zone }); - const dateTokens = [ - ...supportedYearTokens, - ...supportedMonthTokens, - ...supportedWeekTokens, - ...supportedDayTokens, - ...supportedHourTokens, - ...supportedMinuteTokens, - ...supportedSecondTokens, - ]; - - for (const token of dateTokens) { + for (const token of Object.values(storageTokens).flat()) { substitutions[token] = dt.toFormat(token); } diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index 52a5b1dcd8..807d8299b8 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -341,41 +341,6 @@ describe(SystemConfigService.name, () => { } }); - describe('getStorageTemplateOptions', () => { - it('should send back the datetime variables', () => { - expect(sut.getStorageTemplateOptions()).toEqual({ - dayOptions: ['d', 'dd'], - hourOptions: ['h', 'hh', 'H', 'HH'], - minuteOptions: ['m', 'mm'], - monthOptions: ['M', 'MM', 'MMM', 'MMMM'], - presetOptions: [ - '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}/{{MM}}-{{dd}}/{{filename}}', - '{{y}}/{{MMMM}}-{{dd}}/{{filename}}', - '{{y}}/{{MM}}/{{filename}}', - '{{y}}/{{#if album}}{{album}}{{else}}Other/{{MM}}{{/if}}/{{filename}}', - '{{y}}/{{MMM}}/{{filename}}', - '{{y}}/{{MMMM}}/{{filename}}', - '{{y}}/{{MM}}/{{dd}}/{{filename}}', - '{{y}}/{{MMMM}}/{{dd}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}-{{MMM}}-{{dd}}/{{filename}}', - '{{y}}-{{MMMM}}-{{dd}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}/{{filename}}', - '{{y}}/{{y}}-{{WW}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}-{{dd}}/{{assetId}}', - '{{y}}/{{y}}-{{MM}}/{{assetId}}', - '{{y}}/{{y}}-{{WW}}/{{assetId}}', - '{{album}}/{{filename}}', - ], - secondOptions: ['s', 'ss', 'SSS'], - weekOptions: ['W', 'WW'], - yearOptions: ['y', 'yy'], - }); - }); - }); - describe('updateConfig', () => { it('should update the config and emit an event', async () => { systemMock.get.mockResolvedValue(partialConfig); diff --git a/server/src/services/system-config.service.ts b/server/src/services/system-config.service.ts index 96a1f0897b..8f19b22173 100644 --- a/server/src/services/system-config.service.ts +++ b/server/src/services/system-config.service.ts @@ -2,18 +2,8 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { instanceToPlain } from 'class-transformer'; import _ from 'lodash'; import { defaults } from 'src/config'; -import { - supportedDayTokens, - supportedHourTokens, - supportedMinuteTokens, - supportedMonthTokens, - supportedPresetTokens, - supportedSecondTokens, - supportedWeekTokens, - supportedYearTokens, -} from 'src/constants'; import { OnEvent } from 'src/decorators'; -import { SystemConfigDto, SystemConfigTemplateStorageOptionDto, mapConfig } from 'src/dtos/system-config.dto'; +import { SystemConfigDto, mapConfig } from 'src/dtos/system-config.dto'; import { ArgOf } from 'src/interfaces/event.interface'; import { BaseService } from 'src/services/base.service'; import { clearConfigCache } from 'src/utils/config'; @@ -77,21 +67,6 @@ export class SystemConfigService extends BaseService { return mapConfig(newConfig); } - getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { - const options = new SystemConfigTemplateStorageOptionDto(); - - options.dayOptions = supportedDayTokens; - options.weekOptions = supportedWeekTokens; - options.monthOptions = supportedMonthTokens; - options.yearOptions = supportedYearTokens; - options.hourOptions = supportedHourTokens; - options.secondOptions = supportedSecondTokens; - options.minuteOptions = supportedMinuteTokens; - options.presetOptions = supportedPresetTokens; - - return options; - } - async getCustomCss(): Promise<string> { const { theme } = await this.getConfig({ withCache: false }); return theme.customCss;