refactor: asset service queries (#25535)

This commit is contained in:
Daniel Dietzler
2026-02-19 14:54:28 +01:00
committed by GitHub
parent d0ed76dc37
commit fd0338f89c
7 changed files with 185 additions and 17 deletions

View File

@@ -636,3 +636,44 @@ from
where
"asset"."id" = $1
and "asset"."type" = $2
-- AssetRepository.getForOcr
select
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"asset_edit"."action",
"asset_edit"."parameters"
from
"asset_edit"
where
"asset_edit"."assetId" = "asset"."id"
) as agg
) as "edits",
"asset_exif"."exifImageWidth",
"asset_exif"."exifImageHeight",
"asset_exif"."orientation"
from
"asset"
inner join "asset_exif" on "asset_exif"."assetId" = "asset"."id"
where
"asset"."id" = $1
-- AssetRepository.getForEdit
select
"asset"."type",
"asset"."livePhotoVideoId",
"asset"."originalPath",
"asset"."originalFileName",
"asset_exif"."exifImageWidth",
"asset_exif"."exifImageHeight",
"asset_exif"."orientation",
"asset_exif"."projectionType"
from
"asset"
inner join "asset_exif" on "asset_exif"."assetId" = "asset"."id"
where
"asset"."id" = $1

View File

@@ -1059,4 +1059,31 @@ export class AssetRepository {
.where('asset.type', '=', AssetType.Video)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID] })
async getForOcr(id: string) {
return this.db
.selectFrom('asset')
.where('asset.id', '=', id)
.select(withEdits)
.innerJoin('asset_exif', (join) => join.onRef('asset_exif.assetId', '=', 'asset.id'))
.select(['asset_exif.exifImageWidth', 'asset_exif.exifImageHeight', 'asset_exif.orientation'])
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID] })
async getForEdit(id: string) {
return this.db
.selectFrom('asset')
.select(['asset.type', 'asset.livePhotoVideoId', 'asset.originalPath', 'asset.originalFileName'])
.where('asset.id', '=', id)
.innerJoin('asset_exif', (join) => join.onRef('asset_exif.assetId', '=', 'asset.id'))
.select([
'asset_exif.exifImageWidth',
'asset_exif.exifImageHeight',
'asset_exif.orientation',
'asset_exif.projectionType',
])
.executeTakeFirst();
}
}

View File

@@ -660,7 +660,7 @@ describe(AssetService.name, () => {
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
mocks.ocr.getByAssetId.mockResolvedValue([ocr1, ocr2]);
mocks.asset.getById.mockResolvedValue(asset);
mocks.asset.getForOcr.mockResolvedValue({ edits: [], ...asset.exifInfo });
await expect(sut.getOcr(authStub.admin, asset.id)).resolves.toEqual([ocr1, ocr2]);
@@ -676,7 +676,7 @@ describe(AssetService.name, () => {
const asset = AssetFactory.from().exif().build();
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
mocks.ocr.getByAssetId.mockResolvedValue([]);
mocks.asset.getById.mockResolvedValue(asset);
mocks.asset.getForOcr.mockResolvedValue({ edits: [], ...asset.exifInfo });
await expect(sut.getOcr(authStub.admin, asset.id)).resolves.toEqual([]);
expect(mocks.ocr.getByAssetId).toHaveBeenCalledWith(asset.id);

View File

@@ -404,15 +404,19 @@ export class AssetService extends BaseService {
async getOcr(auth: AuthDto, id: string): Promise<AssetOcrResponseDto[]> {
await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] });
const ocr = await this.ocrRepository.getByAssetId(id);
const asset = await this.assetRepository.getById(id, { exifInfo: true, edits: true });
const asset = await this.assetRepository.getForOcr(id);
if (!asset || !asset.exifInfo || !asset.edits) {
if (!asset) {
throw new BadRequestException('Asset not found');
}
const dimensions = getDimensions(asset.exifInfo);
const dimensions = getDimensions({
exifImageHeight: asset.exifImageHeight,
exifImageWidth: asset.exifImageWidth,
orientation: asset.orientation,
});
return ocr.map((item) => transformOcrBoundingBox(item, asset.edits!, dimensions));
return ocr.map((item) => transformOcrBoundingBox(item, asset.edits, dimensions));
}
async upsertBulkMetadata(auth: AuthDto, dto: AssetMetadataBulkUpsertDto): Promise<AssetMetadataBulkResponseDto[]> {
@@ -551,7 +555,7 @@ export class AssetService extends BaseService {
async editAsset(auth: AuthDto, id: string, dto: AssetEditActionListDto): Promise<AssetEditsDto> {
await this.requireAccess({ auth, permission: Permission.AssetEditCreate, ids: [id] });
const asset = await this.assetRepository.getById(id, { exifInfo: true });
const asset = await this.assetRepository.getForEdit(id);
if (!asset) {
throw new BadRequestException('Asset not found');
}
@@ -584,7 +588,7 @@ export class AssetService extends BaseService {
const crop = cropIndex === -1 ? null : (dto.edits[cropIndex] as AssetEditActionCrop);
if (crop) {
// check that crop parameters will not go out of bounds
const { width: assetWidth, height: assetHeight } = getDimensions(asset.exifInfo!);
const { width: assetWidth, height: assetHeight } = getDimensions(asset);
if (!assetWidth || !assetHeight) {
throw new BadRequestException('Asset dimensions are not available for editing');

View File

@@ -1,10 +1,9 @@
import { BadRequestException } from '@nestjs/common';
import { StorageCore } from 'src/cores/storage.core';
import { AssetFile, Exif } from 'src/database';
import { AssetFile } from 'src/database';
import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto';
import { UploadFieldName } from 'src/dtos/asset-media.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { ExifResponseDto } from 'src/dtos/exif.dto';
import { AssetFileType, AssetType, AssetVisibility, Permission } from 'src/enum';
import { AuthRequest } from 'src/middleware/auth.guard';
import { AccessRepository } from 'src/repositories/access.repository';
@@ -210,20 +209,26 @@ const isFlipped = (orientation?: string | null) => {
return value && [5, 6, 7, 8, -90, 90].includes(value);
};
export const getDimensions = (exifInfo: ExifResponseDto | Exif) => {
const { exifImageWidth: width, exifImageHeight: height } = exifInfo;
export const getDimensions = ({
exifImageHeight: height,
exifImageWidth: width,
orientation,
}: {
exifImageHeight: number | null;
exifImageWidth: number | null;
orientation: string | null;
}) => {
if (!width || !height) {
return { width: 0, height: 0 };
}
if (isFlipped(exifInfo.orientation)) {
if (isFlipped(orientation)) {
return { width: height, height: width };
}
return { width, height };
};
export const isPanorama = (asset: { exifInfo?: Exif | null; originalFileName: string }) => {
return asset.exifInfo?.projectionType === 'EQUIRECTANGULAR' || asset.originalFileName.toLowerCase().endsWith('.insp');
export const isPanorama = (asset: { projectionType: string | null; originalFileName: string }) => {
return asset.projectionType === 'EQUIRECTANGULAR' || asset.originalFileName.toLowerCase().endsWith('.insp');
};