mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:49:11 -04:00 
			
		
		
		
	feat(server): Storage template support album condition (#12000)
feat(server): Storage template support album condition ([Request](https://github.com/immich-app/immich/discussions/11999))
This commit is contained in:
		
							parent
							
								
									9894b9513b
								
							
						
					
					
						commit
						b051b29eca
					
				| @ -15,6 +15,7 @@ import { IStorageRepository } from 'src/interfaces/storage.interface'; | ||||
| import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; | ||||
| import { IUserRepository } from 'src/interfaces/user.interface'; | ||||
| import { StorageTemplateService } from 'src/services/storage-template.service'; | ||||
| import { albumStub } from 'test/fixtures/album.stub'; | ||||
| import { assetStub } from 'test/fixtures/asset.stub'; | ||||
| import { userStub } from 'test/fixtures/user.stub'; | ||||
| import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'; | ||||
| @ -83,7 +84,7 @@ describe(StorageTemplateService.name, () => { | ||||
|           newConfig: { | ||||
|             storageTemplate: { | ||||
|               template: | ||||
|                 '{{y}}{{M}}{{W}}{{d}}{{h}}{{m}}{{s}}{{filename}}{{ext}}{{filetype}}{{filetypefull}}{{assetId}}{{album}}', | ||||
|                 '{{y}}{{M}}{{W}}{{d}}{{h}}{{m}}{{s}}{{filename}}{{ext}}{{filetype}}{{filetypefull}}{{assetId}}{{#if album}}{{album}}{{else}}other{{/if}}', | ||||
|             }, | ||||
|           } as SystemConfig, | ||||
|           oldConfig: {} as SystemConfig, | ||||
| @ -163,6 +164,47 @@ describe(StorageTemplateService.name, () => { | ||||
|         originalPath: newMotionPicturePath, | ||||
|       }); | ||||
|     }); | ||||
|     it('Should use handlebar if condition for album', async () => { | ||||
|       const asset = assetStub.image; | ||||
|       const user = userStub.user1; | ||||
|       const album = albumStub.oneAsset; | ||||
|       const config = structuredClone(defaults); | ||||
|       config.storageTemplate.template = '{{y}}/{{#if album}}{{album}}{{else}}other/{{MM}}{{/if}}/{{filename}}'; | ||||
|       SystemConfigCore.create(systemMock, loggerMock).config$.next(config); | ||||
| 
 | ||||
|       userMock.get.mockResolvedValue(user); | ||||
|       assetMock.getByIds.mockResolvedValueOnce([asset]); | ||||
|       albumMock.getByAssetId.mockResolvedValueOnce([album]); | ||||
| 
 | ||||
|       expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.SUCCESS); | ||||
| 
 | ||||
|       expect(moveMock.create).toHaveBeenCalledWith({ | ||||
|         entityId: asset.id, | ||||
|         newPath: `upload/library/${user.id}/${asset.fileCreatedAt.getFullYear()}/${album.albumName}/${asset.originalFileName}`, | ||||
|         oldPath: asset.originalPath, | ||||
|         pathType: AssetPathType.ORIGINAL, | ||||
|       }); | ||||
|     }); | ||||
|     it('Should use handlebar else condition for album', async () => { | ||||
|       const asset = assetStub.image; | ||||
|       const user = userStub.user1; | ||||
|       const config = structuredClone(defaults); | ||||
|       config.storageTemplate.template = '{{y}}/{{#if album}}{{album}}{{else}}other//{{MM}}{{/if}}/{{filename}}'; | ||||
|       SystemConfigCore.create(systemMock, loggerMock).config$.next(config); | ||||
| 
 | ||||
|       userMock.get.mockResolvedValue(user); | ||||
|       assetMock.getByIds.mockResolvedValueOnce([asset]); | ||||
| 
 | ||||
|       expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.SUCCESS); | ||||
| 
 | ||||
|       const month = (asset.fileCreatedAt.getMonth() + 1).toString().padStart(2, '0'); | ||||
|       expect(moveMock.create).toHaveBeenCalledWith({ | ||||
|         entityId: asset.id, | ||||
|         newPath: `upload/library/${user.id}/${asset.fileCreatedAt.getFullYear()}/other/${month}/${asset.originalFileName}`, | ||||
|         oldPath: asset.originalPath, | ||||
|         pathType: AssetPathType.ORIGINAL, | ||||
|       }); | ||||
|     }); | ||||
|     it('should migrate previously failed move from original path when it still exists', async () => { | ||||
|       userMock.get.mockResolvedValue(userStub.user1); | ||||
|       const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`; | ||||
|  | ||||
| @ -308,7 +308,7 @@ export class StorageTemplateService { | ||||
|       filetypefull: asset.type == AssetType.IMAGE ? 'IMAGE' : 'VIDEO', | ||||
|       assetId: asset.id, | ||||
|       //just throw into the root if it doesn't belong to an album
 | ||||
|       album: (albumName && sanitize(albumName.replaceAll(/\.+/g, ''))) || '.', | ||||
|       album: (albumName && sanitize(albumName.replaceAll(/\.+/g, ''))) || '', | ||||
|     }; | ||||
| 
 | ||||
|     const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; | ||||
| @ -329,6 +329,6 @@ export class StorageTemplateService { | ||||
|       substitutions[token] = dt.toFormat(token); | ||||
|     } | ||||
| 
 | ||||
|     return template(substitutions); | ||||
|     return template(substitutions).replaceAll(/\/{2,}/gm, '/'); | ||||
|   } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user