mirror of
https://github.com/immich-app/immich.git
synced 2025-01-01 08:31: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:
parent
9894b9513b
commit
b051b29eca
2 changed files with 45 additions and 3 deletions
|
@ -15,6 +15,7 @@ import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
import { StorageTemplateService } from 'src/services/storage-template.service';
|
import { StorageTemplateService } from 'src/services/storage-template.service';
|
||||||
|
import { albumStub } from 'test/fixtures/album.stub';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { userStub } from 'test/fixtures/user.stub';
|
import { userStub } from 'test/fixtures/user.stub';
|
||||||
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
||||||
|
@ -83,7 +84,7 @@ describe(StorageTemplateService.name, () => {
|
||||||
newConfig: {
|
newConfig: {
|
||||||
storageTemplate: {
|
storageTemplate: {
|
||||||
template:
|
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,
|
} as SystemConfig,
|
||||||
oldConfig: {} as SystemConfig,
|
oldConfig: {} as SystemConfig,
|
||||||
|
@ -163,6 +164,47 @@ describe(StorageTemplateService.name, () => {
|
||||||
originalPath: newMotionPicturePath,
|
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 () => {
|
it('should migrate previously failed move from original path when it still exists', async () => {
|
||||||
userMock.get.mockResolvedValue(userStub.user1);
|
userMock.get.mockResolvedValue(userStub.user1);
|
||||||
const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`;
|
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',
|
filetypefull: asset.type == AssetType.IMAGE ? 'IMAGE' : 'VIDEO',
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
//just throw into the root if it doesn't belong to an album
|
//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;
|
const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
@ -329,6 +329,6 @@ export class StorageTemplateService {
|
||||||
substitutions[token] = dt.toFormat(token);
|
substitutions[token] = dt.toFormat(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
return template(substitutions);
|
return template(substitutions).replaceAll(/\/{2,}/gm, '/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue