mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:29:32 -05: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