mirror of
https://github.com/immich-app/immich.git
synced 2025-12-05 20:40:29 -08:00
feat: less asset-metadata validation (#24342)
This commit is contained in:
@@ -85,19 +85,6 @@ describe(AssetMediaController.name, () => {
|
|||||||
expect(body).toEqual(factory.responses.badRequest(['metadata must be valid JSON']));
|
expect(body).toEqual(factory.responses.badRequest(['metadata must be valid JSON']));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate iCloudId is a string', async () => {
|
|
||||||
const { status, body } = await request(ctx.getHttpServer())
|
|
||||||
.post('/assets')
|
|
||||||
.attach('assetData', assetData, filename)
|
|
||||||
.field({
|
|
||||||
...makeUploadDto(),
|
|
||||||
metadata: JSON.stringify([{ key: AssetMetadataKey.MobileApp, value: { iCloudId: 123 } }]),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(factory.responses.badRequest(['metadata.0.value.iCloudId must be a string']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require `deviceAssetId`', async () => {
|
it('should require `deviceAssetId`', async () => {
|
||||||
const { status, body } = await request(ctx.getHttpServer())
|
const { status, body } = await request(ctx.getHttpServer())
|
||||||
.post('/assets')
|
.post('/assets')
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AssetMetadataKey, AssetType, AssetVisibility } from 'src/enum';
|
import { AssetMetadataKey, AssetType, AssetVisibility } from 'src/enum';
|
||||||
import { AssetStats } from 'src/repositories/asset.repository';
|
import { AssetStats } from 'src/repositories/asset.repository';
|
||||||
import { AssetMetadata, AssetMetadataItem } from 'src/types';
|
|
||||||
import { IsNotSiblingOf, Optional, ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation';
|
import { IsNotSiblingOf, Optional, ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation';
|
||||||
|
|
||||||
export class DeviceIdDto {
|
export class DeviceIdDto {
|
||||||
@@ -154,23 +153,12 @@ export class AssetMetadataUpsertDto {
|
|||||||
items!: AssetMetadataUpsertItemDto[];
|
items!: AssetMetadataUpsertItemDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AssetMetadataUpsertItemDto implements AssetMetadataItem {
|
export class AssetMetadataUpsertItemDto {
|
||||||
@ValidateEnum({ enum: AssetMetadataKey, name: 'AssetMetadataKey' })
|
@ValidateEnum({ enum: AssetMetadataKey, name: 'AssetMetadataKey' })
|
||||||
key!: AssetMetadataKey;
|
key!: AssetMetadataKey;
|
||||||
|
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@ValidateNested()
|
value!: object;
|
||||||
@Type((options) => {
|
|
||||||
switch (options?.object.key) {
|
|
||||||
case AssetMetadataKey.MobileApp: {
|
|
||||||
return AssetMetadataMobileAppDto;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return Object;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
value!: AssetMetadata[AssetMetadataKey];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AssetMetadataMobileAppDto {
|
export class AssetMetadataMobileAppDto {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
|||||||
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
||||||
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { AssetMetadataItem } from 'src/types';
|
|
||||||
import {
|
import {
|
||||||
anyUuid,
|
anyUuid,
|
||||||
asUuid,
|
asUuid,
|
||||||
@@ -224,7 +223,7 @@ export class AssetRepository {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
upsertMetadata(id: string, items: AssetMetadataItem[]) {
|
upsertMetadata(id: string, items: Array<{ key: AssetMetadataKey; value: object }>) {
|
||||||
return this.db
|
return this.db
|
||||||
.insertInto('asset_metadata')
|
.insertInto('asset_metadata')
|
||||||
.values(items.map((item) => ({ assetId: id, ...item })))
|
.values(items.map((item) => ({ assetId: id, ...item })))
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'src/sql-tools';
|
} from 'src/sql-tools';
|
||||||
import { AssetMetadata, AssetMetadataItem } from 'src/types';
|
|
||||||
|
|
||||||
@UpdatedAtTrigger('asset_metadata_updated_at')
|
@UpdatedAtTrigger('asset_metadata_updated_at')
|
||||||
@Table('asset_metadata')
|
@Table('asset_metadata')
|
||||||
@@ -22,7 +21,7 @@ import { AssetMetadata, AssetMetadataItem } from 'src/types';
|
|||||||
referencingOldTableAs: 'old',
|
referencingOldTableAs: 'old',
|
||||||
when: 'pg_trigger_depth() = 0',
|
when: 'pg_trigger_depth() = 0',
|
||||||
})
|
})
|
||||||
export class AssetMetadataTable<T extends keyof AssetMetadata = AssetMetadataKey> implements AssetMetadataItem<T> {
|
export class AssetMetadataTable {
|
||||||
@ForeignKeyColumn(() => AssetTable, {
|
@ForeignKeyColumn(() => AssetTable, {
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
@@ -33,10 +32,10 @@ export class AssetMetadataTable<T extends keyof AssetMetadata = AssetMetadataKey
|
|||||||
assetId!: string;
|
assetId!: string;
|
||||||
|
|
||||||
@PrimaryColumn({ type: 'character varying' })
|
@PrimaryColumn({ type: 'character varying' })
|
||||||
key!: T;
|
key!: AssetMetadataKey;
|
||||||
|
|
||||||
@Column({ type: 'jsonb' })
|
@Column({ type: 'jsonb' })
|
||||||
value!: AssetMetadata[T];
|
value!: object;
|
||||||
|
|
||||||
@UpdateIdColumn({ index: true })
|
@UpdateIdColumn({ index: true })
|
||||||
updateId!: Generated<string>;
|
updateId!: Generated<string>;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Asset } from 'src/database';
|
|||||||
import { UploadFieldName } from 'src/dtos/asset-media.dto';
|
import { UploadFieldName } from 'src/dtos/asset-media.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import {
|
import {
|
||||||
AssetMetadataKey,
|
|
||||||
AssetOrder,
|
AssetOrder,
|
||||||
AssetType,
|
AssetType,
|
||||||
DatabaseSslMode,
|
DatabaseSslMode,
|
||||||
@@ -563,12 +562,3 @@ export interface UserMetadata extends Record<UserMetadataKey, Record<string, any
|
|||||||
[UserMetadataKey.License]: { licenseKey: string; activationKey: string; activatedAt: string };
|
[UserMetadataKey.License]: { licenseKey: string; activationKey: string; activatedAt: string };
|
||||||
[UserMetadataKey.Onboarding]: { isOnboarded: boolean };
|
[UserMetadataKey.Onboarding]: { isOnboarded: boolean };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AssetMetadataItem<T extends keyof AssetMetadata = AssetMetadataKey> = {
|
|
||||||
key: T;
|
|
||||||
value: AssetMetadata[T];
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface AssetMetadata extends Record<AssetMetadataKey, Record<string, any>> {
|
|
||||||
[AssetMetadataKey.MobileApp]: { iCloudId: string };
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user