From 78ed4c0b65cbe3f56eed061557ce9fd15f851de2 Mon Sep 17 00:00:00 2001 From: tn801534 Date: Wed, 20 May 2026 20:37:31 +0800 Subject: [PATCH] feat(server): add isVideo/isImage storage template vars, UI support, and unit tests --- .../services/storage-template.service.spec.ts | 48 +++++++++++++++++++ .../src/services/storage-template.service.ts | 3 ++ .../StorageTemplateSettings.svelte | 10 ++-- .../SupportedVariablesPanel.svelte | 2 + 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/server/src/services/storage-template.service.spec.ts b/server/src/services/storage-template.service.spec.ts index 8f11a1dfa2..3e04b58dc3 100644 --- a/server/src/services/storage-template.service.spec.ts +++ b/server/src/services/storage-template.service.spec.ts @@ -86,6 +86,7 @@ describe(StorageTemplateService.name, () => { '{{y}}/{{y}}-{{WW}}/{{assetId}}', '{{album}}/{{filename}}', '{{make}}/{{model}}/{{lensModel}}/{{filename}}', + '{{#if isVideo}}videos{{/if}}/{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', ], secondOptions: ['s', 'ss', 'SSS'], weekOptions: ['W', 'WW'], @@ -260,6 +261,53 @@ describe(StorageTemplateService.name, () => { }); }); + it('should use handlebar if condition for isVideo (video asset)', async () => { + const user = UserFactory.create(); + const asset = AssetFactory.from({ type: AssetType.Video }).owner(user).exif().build(); + const config = structuredClone(defaults); + config.storageTemplate.template = '{{#if isVideo}}videos{{/if}}/{{y}}/{{filename}}'; + + sut.onConfigInit({ newConfig: config }); + + mocks.user.get.mockResolvedValue(user); + mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(asset)); + + expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success); + + expect(mocks.move.create).toHaveBeenCalledWith({ + entityId: asset.id, + newPath: expect.stringContaining( + `/data/library/${user.id}/videos/${asset.fileCreatedAt.getFullYear()}/${asset.originalFileName}`, + ), + oldPath: asset.originalPath, + pathType: AssetPathType.Original, + }); + }); + + it('should use handlebar if condition for isVideo (image asset)', async () => { + const user = UserFactory.create(); + const asset = AssetFactory.from({ type: AssetType.Image }).owner(user).exif().build(); + const config = structuredClone(defaults); + config.storageTemplate.template = '{{#if isVideo}}videos{{/if}}/{{y}}/{{filename}}'; + + sut.onConfigInit({ newConfig: config }); + + mocks.user.get.mockResolvedValue(user); + mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(getForStorageTemplate(asset)); + + expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success); + + expect(mocks.move.create).toHaveBeenCalledWith({ + entityId: asset.id, + newPath: expect.not.stringContaining('videos/') && + expect.stringContaining( + `/data/library/${user.id}/${asset.fileCreatedAt.getFullYear()}/${asset.originalFileName}`, + ), + oldPath: asset.originalPath, + pathType: AssetPathType.Original, + }); + }); + it('should handle album startDate', async () => { const user = UserFactory.create(); const asset = AssetFactory.from().owner(user).exif().build(); diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts index acdcc868b2..7f407e0b72 100644 --- a/server/src/services/storage-template.service.ts +++ b/server/src/services/storage-template.service.ts @@ -54,6 +54,7 @@ const storagePresets = [ '{{y}}/{{y}}-{{WW}}/{{assetId}}', '{{album}}/{{filename}}', '{{make}}/{{model}}/{{lensModel}}/{{filename}}', + '{{#if isVideo}}videos{{/if}}/{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', ]; export interface MoveAssetMetadata { @@ -404,6 +405,8 @@ export class StorageTemplateService extends BaseService { ext: extension, filetype: asset.type == AssetType.Image ? 'IMG' : 'VID', filetypefull: asset.type == AssetType.Image ? 'IMAGE' : 'VIDEO', + isVideo: asset.type == AssetType.Video ? 'true' : '', + isImage: asset.type == AssetType.Image ? 'true' : '', assetId: asset.id, assetIdShort: asset.id.slice(-12), //just throw into the root if it doesn't belong to an album diff --git a/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte b/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte index 9d99e86a42..6549b15e29 100644 --- a/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte +++ b/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte @@ -53,10 +53,12 @@ }); const substitutions: Record = { - filename: 'IMAGE_56437', - ext: 'jpg', - filetype: 'IMG', - filetypefull: 'IMAGE', + filename: 'VIDEO_12345', + ext: 'mp4', + filetype: 'VID', + filetypefull: 'VIDEO', + isVideo: 'true', + isImage: '', assetId: 'a8312960-e277-447d-b4ea-56717ccba856', assetIdShort: '56717ccba856', album: $t('album_name'), diff --git a/web/src/lib/components/admin-settings/SupportedVariablesPanel.svelte b/web/src/lib/components/admin-settings/SupportedVariablesPanel.svelte index d074d3e696..d5fe4f79a7 100644 --- a/web/src/lib/components/admin-settings/SupportedVariablesPanel.svelte +++ b/web/src/lib/components/admin-settings/SupportedVariablesPanel.svelte @@ -21,6 +21,8 @@