Skip to content

Commit

Permalink
add web placeholder, server endpoint, migration, various fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
mertalev committed Apr 2, 2024
1 parent a27c72a commit 7d9d9f9
Show file tree
Hide file tree
Showing 24 changed files with 303 additions and 38 deletions.
1 change: 1 addition & 0 deletions mobile/openapi/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 52 additions & 0 deletions mobile/openapi/doc/AssetApi.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions mobile/openapi/lib/api/asset_api.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions mobile/openapi/test/asset_api_test.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions open-api/immich-openapi-specs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,41 @@
]
}
},
"/asset/duplicates": {
"get": {
"operationId": "getAssetDuplicates",
"parameters": [],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/AssetResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Asset"
]
}
},
"/asset/exist": {
"post": {
"description": "Checks if multiple assets exist on the server and returns all existing - used by background backup",
Expand Down
8 changes: 8 additions & 0 deletions open-api/typescript-sdk/src/fetch-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,14 @@ export function getAllUserAssetsByDeviceId({ deviceId }: {
...opts
}));
}
export function getAssetDuplicates(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetResponseDto[];
}>("/asset/duplicates", {
...opts
}));
}
/**
* Checks if multiple assets exist on the server and returns all existing - used by background backup
*/
Expand Down
5 changes: 5 additions & 0 deletions server/src/controllers/asset.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ export class AssetController {
return this.service.getStatistics(auth, dto);
}

@Get('duplicates')
getAssetDuplicates(@Auth() auth: AuthDto): Promise<AssetResponseDto[]> {
return this.service.getDuplicates(auth);
}

@Post('jobs')
@HttpCode(HttpStatus.NO_CONTENT)
runAssetJobs(@Auth() auth: AuthDto, @Body() dto: AssetJobsDto): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion server/src/cores/system-config.core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const defaults = Object.freeze<SystemConfig>({
clip: {
enabled: true,
modelName: 'ViT-B-32__openai',
duplicateThreshold: 0.01,
duplicateThreshold: 0.03,
},
facialRecognition: {
enabled: true,
Expand Down
2 changes: 1 addition & 1 deletion server/src/entities/asset-duplicate.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Entity, Index, JoinColumn, OneToMany, PrimaryColumn } from 'typeorm';
@Entity('asset_duplicates')
@Index('asset_duplicates_assetId_uindex', ['assetId'], { unique: true })
export class AssetDuplicateEntity {
@OneToMany(() => AssetEntity, (asset) => asset.duplicates)
@OneToMany(() => AssetEntity, (asset) => asset.duplicates, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
@JoinColumn({ name: 'assetId', referencedColumnName: 'id' })
assets!: AssetEntity;

Expand Down
4 changes: 2 additions & 2 deletions server/src/entities/asset.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AlbumEntity } from 'src/entities/album.entity';
import { AssetDuplicateEntity } from 'src/entities/asset-duplicate.entity';
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
import { AssetStackEntity } from 'src/entities/asset-stack.entity';
Expand All @@ -24,7 +25,6 @@ import {
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { AssetDuplicateEntity } from './asset-duplicate.entity';

export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_library_checksum';

Expand Down Expand Up @@ -173,7 +173,7 @@ export class AssetEntity {
@Column({ nullable: true })
duplicateId?: string | null;

@ManyToOne(() => AssetDuplicateEntity, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' })
@ManyToOne(() => AssetDuplicateEntity, { nullable: true })
@JoinColumn({ name: 'duplicateId' })
duplicates?: AssetDuplicateEntity | null;
}
Expand Down
4 changes: 3 additions & 1 deletion server/src/interfaces/asset.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export interface AssetBuilderOptions {
isArchived?: boolean;
isFavorite?: boolean;
isTrashed?: boolean;
isDuplicate?: boolean;
albumId?: string;
personId?: string;
userIds?: string[];
Expand Down Expand Up @@ -172,8 +173,9 @@ export interface IAssetRepository {
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
upsertExif(exif: Partial<ExifEntity>): Promise<void>;
upsertJobStatus(jobStatus: Partial<AssetJobStatusEntity>): Promise<void>;
upsertJobStatus(jobStatus: Partial<AssetJobStatusEntity> | Partial<AssetJobStatusEntity>[]): Promise<void>;
getAssetIdByCity(userId: string, options: AssetExploreFieldOptions): Promise<SearchExploreItem<string>>;
getAssetIdByTag(userId: string, options: AssetExploreFieldOptions): Promise<SearchExploreItem<string>>;
getDuplicates(options: AssetBuilderOptions): Promise<AssetEntity[]>;
searchMetadata(query: string, userIds: string[], options: MetadataSearchOptions): Promise<AssetEntity[]>;
}
1 change: 1 addition & 0 deletions server/src/interfaces/search.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export interface FaceEmbeddingSearch extends SearchEmbeddingOptions {

export interface AssetDuplicateSearch {
assetId: string;
embedding: Embedding;
userIds: string[];
maxDistance?: number;
}
Expand Down
36 changes: 36 additions & 0 deletions server/src/migrations/1711989989911-CreateAssetDuplicateTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateAssetDuplicateTable1711989989911 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE asset_duplicates (
id uuid,
"assetId" uuid REFERENCES assets ON UPDATE CASCADE ON DELETE CASCADE,
PRIMARY KEY (id, "assetId")
);
`);

await queryRunner.query(`ALTER TABLE assets ADD COLUMN "duplicateId" uuid`);

await queryRunner.query(`ALTER TABLE asset_job_status ADD COLUMN "duplicatesDetectedAt" timestamptz`);

await queryRunner.query(`
ALTER TABLE assets
ADD CONSTRAINT asset_duplicates_id
FOREIGN KEY ("duplicateId", id)
REFERENCES asset_duplicates DEFERRABLE INITIALLY DEFERRED
`);

await queryRunner.query(`
CREATE UNIQUE INDEX "asset_duplicates_assetId_uindex"
ON asset_duplicates ("assetId")
`);

await queryRunner.query(`CREATE INDEX "IDX_assets_duplicateId" ON assets ("duplicateId")`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE assets DROP COLUMN "duplicateId"`);
await queryRunner.query(`DROP TABLE asset_duplicates`);
}
}
2 changes: 1 addition & 1 deletion server/src/repositories/asset-duplicate.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class AssetDuplicateRepository implements IAssetDuplicateRepository {
await manager.update(AssetDuplicateEntity, { id: In(oldDuplicateIds) }, { id });
}
await manager.update(AssetEntity, { id: In(assetIds) }, { duplicateId: id });
await manager.update(AssetEntity, { duplicateId: In(oldDuplicateIds) }, { duplicateId: id }); // TODO: cascade should handle this, but it doesn't seem to
await manager.update(AssetEntity, { duplicateId: In(oldDuplicateIds) }, { duplicateId: id });
});
}

Expand Down

0 comments on commit 7d9d9f9

Please sign in to comment.