refactor(server): update asset endpoint (#3973)

* refactor(server): update asset

* chore: open api
This commit is contained in:
Jason Rasmussen
2023-09-04 22:25:31 -04:00
committed by GitHub
parent 26bc889f8d
commit 454737ca79
27 changed files with 237 additions and 184 deletions

View File

@@ -1,4 +1,4 @@
import { AssetEntity, AssetType } from '@app/infra/entities';
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
import { Paginated, PaginationOptions } from '../domain.util';
export type AssetStats = Record<AssetType, number>;
@@ -86,4 +86,5 @@ export interface IAssetRepository {
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
getByTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
upsertExif(exif: Partial<ExifEntity>): Promise<void>;
}

View File

@@ -519,6 +519,30 @@ describe(AssetService.name, () => {
});
});
describe('update', () => {
it('should require asset write access for the id', async () => {
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
await expect(sut.update(authStub.admin, 'asset-1', { isArchived: false })).rejects.toBeInstanceOf(
BadRequestException,
);
expect(assetMock.save).not.toHaveBeenCalled();
});
it('should update the asset', async () => {
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
assetMock.save.mockResolvedValue(assetStub.image);
await sut.update(authStub.admin, 'asset-1', { isFavorite: true });
expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true });
});
it('should update the exif description', async () => {
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
assetMock.save.mockResolvedValue(assetStub.image);
await sut.update(authStub.admin, 'asset-1', { description: 'Test description' });
expect(assetMock.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', description: 'Test description' });
});
});
describe('updateAll', () => {
it('should require asset write access for all ids', async () => {
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);

View File

@@ -24,6 +24,7 @@ import {
MemoryLaneDto,
TimeBucketAssetDto,
TimeBucketDto,
UpdateAssetDto,
mapStats,
} from './dto';
import {
@@ -279,6 +280,19 @@ export class AssetService {
return mapStats(stats);
}
async update(authUser: AuthUserDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, id);
const { description, ...rest } = dto;
if (description !== undefined) {
await this.assetRepository.upsertExif({ assetId: id, description });
}
const asset = await this.assetRepository.save({ id, ...rest });
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids: [id] } });
return mapAsset(asset);
}
async updateAll(authUser: AuthUserDto, dto: AssetBulkUpdateDto) {
const { ids, ...options } = dto;
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids);

View File

@@ -1,4 +1,4 @@
import { IsBoolean } from 'class-validator';
import { IsBoolean, IsString } from 'class-validator';
import { Optional } from '../../domain.util';
import { BulkIdsDto } from '../response-dto';
@@ -11,3 +11,17 @@ export class AssetBulkUpdateDto extends BulkIdsDto {
@IsBoolean()
isArchived?: boolean;
}
export class UpdateAssetDto {
@Optional()
@IsBoolean()
isFavorite?: boolean;
@Optional()
@IsBoolean()
isArchived?: boolean;
@Optional()
@IsString()
description?: string;
}