1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-28 22:51:59 +00: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:
Mark 2024-08-27 03:48:39 +02:00 committed by GitHub
parent 9894b9513b
commit b051b29eca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 45 additions and 3 deletions

View file

@ -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`;

View file

@ -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, '/');
}
}