mirror of
https://github.com/immich-app/immich.git
synced 2026-02-28 09:38:43 +03:00
refactor: asset service queries (#25535)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user