mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
feat(web,server): run jobs for specific assets (#3712)
* feat(web,server): manually queue asset job * chore: open api * chore: tests
This commit is contained in:
@@ -1367,6 +1367,41 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/asset/jobs": {
|
||||
"post": {
|
||||
"operationId": "runAssetJobs",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetJobsDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Asset"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/asset/map-marker": {
|
||||
"get": {
|
||||
"operationId": "getMapMarkers",
|
||||
@@ -5042,6 +5077,33 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetJobName": {
|
||||
"enum": [
|
||||
"regenerate-thumbnail",
|
||||
"refresh-metadata",
|
||||
"transcode-video"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AssetJobsDto": {
|
||||
"properties": {
|
||||
"assetIds": {
|
||||
"items": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"$ref": "#/components/schemas/AssetJobName"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"assetIds",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetResponseDto": {
|
||||
"properties": {
|
||||
"checksum": {
|
||||
|
||||
@@ -7,15 +7,17 @@ import {
|
||||
newAccessRepositoryMock,
|
||||
newAssetRepositoryMock,
|
||||
newCryptoRepositoryMock,
|
||||
newJobRepositoryMock,
|
||||
newStorageRepositoryMock,
|
||||
} from '@test';
|
||||
import { when } from 'jest-when';
|
||||
import { Readable } from 'stream';
|
||||
import { ICryptoRepository } from '../crypto';
|
||||
import { IJobRepository, JobName } from '../index';
|
||||
import { IStorageRepository } from '../storage';
|
||||
import { AssetStats, IAssetRepository } from './asset.repository';
|
||||
import { AssetService, UploadFieldName } from './asset.service';
|
||||
import { AssetStatsResponseDto, DownloadResponseDto } from './dto';
|
||||
import { AssetJobName, AssetStatsResponseDto, DownloadResponseDto } from './dto';
|
||||
import { mapAsset } from './response-dto';
|
||||
|
||||
const downloadResponse: DownloadResponseDto = {
|
||||
@@ -145,6 +147,7 @@ describe(AssetService.name, () => {
|
||||
let accessMock: IAccessRepositoryMock;
|
||||
let assetMock: jest.Mocked<IAssetRepository>;
|
||||
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
||||
let jobMock: jest.Mocked<IJobRepository>;
|
||||
let storageMock: jest.Mocked<IStorageRepository>;
|
||||
|
||||
it('should work', () => {
|
||||
@@ -155,8 +158,9 @@ describe(AssetService.name, () => {
|
||||
accessMock = newAccessRepositoryMock();
|
||||
assetMock = newAssetRepositoryMock();
|
||||
cryptoMock = newCryptoRepositoryMock();
|
||||
jobMock = newJobRepositoryMock();
|
||||
storageMock = newStorageRepositoryMock();
|
||||
sut = new AssetService(accessMock, assetMock, cryptoMock, storageMock);
|
||||
sut = new AssetService(accessMock, assetMock, cryptoMock, jobMock, storageMock);
|
||||
});
|
||||
|
||||
describe('canUpload', () => {
|
||||
@@ -532,4 +536,24 @@ describe(AssetService.name, () => {
|
||||
expect(assetMock.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('run', () => {
|
||||
it('should run the refresh metadata job', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_METADATA }),
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.METADATA_EXTRACTION, data: { id: 'asset-1' } });
|
||||
});
|
||||
|
||||
it('should run the refresh thumbnails job', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL }),
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1' } });
|
||||
});
|
||||
|
||||
it('should run the transcode video', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.TRANSCODE_VIDEO }),
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.VIDEO_CONVERSION, data: { id: 'asset-1' } });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,11 +8,14 @@ import { AuthUserDto } from '../auth';
|
||||
import { ICryptoRepository } from '../crypto';
|
||||
import { mimeTypes } from '../domain.constant';
|
||||
import { HumanReadableSize, usePagination } from '../domain.util';
|
||||
import { IJobRepository, JobName } from '../job';
|
||||
import { ImmichReadStream, IStorageRepository, StorageCore, StorageFolder } from '../storage';
|
||||
import { IAssetRepository } from './asset.repository';
|
||||
import {
|
||||
AssetBulkUpdateDto,
|
||||
AssetIdsDto,
|
||||
AssetJobName,
|
||||
AssetJobsDto,
|
||||
DownloadArchiveInfo,
|
||||
DownloadInfoDto,
|
||||
DownloadResponseDto,
|
||||
@@ -54,6 +57,7 @@ export class AssetService {
|
||||
@Inject(IAccessRepository) accessRepository: IAccessRepository,
|
||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||
) {
|
||||
this.access = new AccessCore(accessRepository);
|
||||
@@ -275,4 +279,24 @@ export class AssetService {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids);
|
||||
await this.assetRepository.updateAll(ids, options);
|
||||
}
|
||||
|
||||
async run(authUser: AuthUserDto, dto: AssetJobsDto) {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, dto.assetIds);
|
||||
|
||||
for (const id of dto.assetIds) {
|
||||
switch (dto.name) {
|
||||
case AssetJobName.REFRESH_METADATA:
|
||||
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id } });
|
||||
break;
|
||||
|
||||
case AssetJobName.REGENERATE_THUMBNAIL:
|
||||
await this.jobRepository.queue({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id } });
|
||||
break;
|
||||
|
||||
case AssetJobName.TRANSCODE_VIDEO:
|
||||
await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { id } });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum } from 'class-validator';
|
||||
import { ValidateUUID } from '../../domain.util';
|
||||
|
||||
export class AssetIdsDto {
|
||||
@ValidateUUID({ each: true })
|
||||
assetIds!: string[];
|
||||
}
|
||||
|
||||
export enum AssetJobName {
|
||||
REGENERATE_THUMBNAIL = 'regenerate-thumbnail',
|
||||
REFRESH_METADATA = 'refresh-metadata',
|
||||
TRANSCODE_VIDEO = 'transcode-video',
|
||||
}
|
||||
|
||||
export class AssetJobsDto extends AssetIdsDto {
|
||||
@ApiProperty({ enumName: 'AssetJobName', enum: AssetJobName })
|
||||
@IsEnum(AssetJobName)
|
||||
name!: AssetJobName;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AssetBulkUpdateDto,
|
||||
AssetIdsDto,
|
||||
AssetJobsDto,
|
||||
AssetResponseDto,
|
||||
AssetService,
|
||||
AssetStatsDto,
|
||||
@@ -78,6 +79,12 @@ export class AssetController {
|
||||
return this.service.getByTimeBucket(authUser, dto);
|
||||
}
|
||||
|
||||
@Post('jobs')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
runAssetJobs(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetJobsDto): Promise<void> {
|
||||
return this.service.run(authUser, dto);
|
||||
}
|
||||
|
||||
@Put()
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
updateAssets(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetBulkUpdateDto): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user