feat: unassign faces
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Body, Controller, Get, Param, Put, Query } from '@nestjs/common';
|
||||
import { Body, Controller, Delete, Get, Param, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetFaceResponseDto, FaceDto, PersonResponseDto } from 'src/dtos/person.dto';
|
||||
@@ -26,4 +26,10 @@ export class FaceController {
|
||||
): Promise<PersonResponseDto> {
|
||||
return this.service.reassignFacesById(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated()
|
||||
unassignFace(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetFaceResponseDto> {
|
||||
return this.service.unassignFace(auth, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Body, Controller, Get, Next, Param, Post, Put, Query, Res } from '@nestjs/common';
|
||||
import { Body, Controller, Delete, Get, Next, Param, Post, Put, Query, Res } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { NextFunction, Response } from 'express';
|
||||
import { BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto';
|
||||
@@ -83,6 +83,11 @@ export class PersonController {
|
||||
return this.service.getAssets(auth, id);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
unassignFaces(@Auth() auth: AuthDto, @Body() dto: AssetFaceUpdateDto): Promise<BulkIdResponseDto[]> {
|
||||
return this.service.unassignFaces(auth, dto);
|
||||
}
|
||||
|
||||
@Put(':id/reassign')
|
||||
@Authenticated()
|
||||
reassignFaces(
|
||||
|
||||
@@ -2,7 +2,12 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
import { PropertyLifecycle } from 'src/decorators';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { ExifResponseDto, mapExif } from 'src/dtos/exif.dto';
|
||||
import { PersonWithFacesResponseDto, mapFacesWithoutPerson, mapPerson } from 'src/dtos/person.dto';
|
||||
import {
|
||||
PeopleWithFacesResponseDto,
|
||||
PersonWithFacesResponseDto,
|
||||
mapFacesWithoutPerson,
|
||||
mapPerson,
|
||||
} from 'src/dtos/person.dto';
|
||||
import { TagResponseDto, mapTag } from 'src/dtos/tag.dto';
|
||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||
@@ -43,7 +48,7 @@ export class AssetResponseDto extends SanitizedAssetResponseDto {
|
||||
exifInfo?: ExifResponseDto;
|
||||
smartInfo?: SmartInfoResponseDto;
|
||||
tags?: TagResponseDto[];
|
||||
people?: PersonWithFacesResponseDto[];
|
||||
people?: PeopleWithFacesResponseDto;
|
||||
/**base64 encoded sha1 hash */
|
||||
checksum!: string;
|
||||
stackParentId?: string | null;
|
||||
@@ -58,7 +63,7 @@ export type AssetMapOptions = {
|
||||
auth?: AuthDto;
|
||||
};
|
||||
|
||||
const peopleWithFaces = (faces: AssetFaceEntity[]): PersonWithFacesResponseDto[] => {
|
||||
const peopleWithFaces = (faces: AssetFaceEntity[]): PeopleWithFacesResponseDto => {
|
||||
const result: PersonWithFacesResponseDto[] = [];
|
||||
if (faces) {
|
||||
for (const face of faces) {
|
||||
@@ -73,7 +78,7 @@ const peopleWithFaces = (faces: AssetFaceEntity[]): PersonWithFacesResponseDto[]
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return { faces: result, numberOfFaces: faces.length };
|
||||
};
|
||||
|
||||
export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): AssetResponseDto {
|
||||
@@ -117,7 +122,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
||||
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
||||
livePhotoVideoId: entity.livePhotoVideoId,
|
||||
tags: entity.tags?.map(mapTag),
|
||||
people: peopleWithFaces(entity.faces),
|
||||
people: entity.faces ? peopleWithFaces(entity.faces) : undefined,
|
||||
checksum: entity.checksum.toString('base64'),
|
||||
stackParentId: withStack ? entity.stack?.primaryAssetId : undefined,
|
||||
stack: withStack
|
||||
|
||||
@@ -77,6 +77,12 @@ export class PersonWithFacesResponseDto extends PersonResponseDto {
|
||||
faces!: AssetFaceWithoutPersonResponseDto[];
|
||||
}
|
||||
|
||||
export class PeopleWithFacesResponseDto {
|
||||
faces!: PersonWithFacesResponseDto[];
|
||||
@ApiProperty({ type: 'integer' })
|
||||
numberOfFaces!: number;
|
||||
}
|
||||
|
||||
export class AssetFaceWithoutPersonResponseDto {
|
||||
@ValidateUUID()
|
||||
id!: string;
|
||||
|
||||
@@ -37,6 +37,9 @@ export class AssetFaceEntity {
|
||||
@Column({ default: 0, type: 'int' })
|
||||
boundingBoxY2!: number;
|
||||
|
||||
@Column({ default: false })
|
||||
isEdited!: boolean;
|
||||
|
||||
@ManyToOne(() => AssetEntity, (asset) => asset.faces, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
asset!: AssetEntity;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface AssetFaceId {
|
||||
export interface UpdateFacesData {
|
||||
oldPersonId?: string;
|
||||
faceIds?: string[];
|
||||
newPersonId: string;
|
||||
newPersonId: string | null;
|
||||
}
|
||||
|
||||
export interface PersonStatistics {
|
||||
@@ -60,7 +60,7 @@ export interface IPersonRepository {
|
||||
getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]>;
|
||||
getRandomFace(personId: string): Promise<AssetFaceEntity | null>;
|
||||
getStatistics(personId: string): Promise<PersonStatistics>;
|
||||
reassignFace(assetFaceId: string, newPersonId: string): Promise<number>;
|
||||
reassignFace(assetFaceId: string, newPersonId: string | null): Promise<number>;
|
||||
getNumberOfPeople(userId: string): Promise<PeopleStatistics>;
|
||||
reassignFaces(data: UpdateFacesData): Promise<number>;
|
||||
update(entity: Partial<PersonEntity>): Promise<PersonEntity>;
|
||||
|
||||
13
server/src/migrations/1715357609038-AddEditedAssetFace.ts
Normal file
13
server/src/migrations/1715357609038-AddEditedAssetFace.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddEditedAssetFace1715357609038 implements MigrationInterface {
|
||||
name = 'AddEditedAssetFace1715357609038';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "asset_faces" ADD "isEdited" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "asset_faces" DROP COLUMN "isEdited"`);
|
||||
}
|
||||
}
|
||||
@@ -192,6 +192,7 @@ SELECT
|
||||
"AssetEntity__AssetEntity_faces"."boundingBoxY1" AS "AssetEntity__AssetEntity_faces_boundingBoxY1",
|
||||
"AssetEntity__AssetEntity_faces"."boundingBoxX2" AS "AssetEntity__AssetEntity_faces_boundingBoxX2",
|
||||
"AssetEntity__AssetEntity_faces"."boundingBoxY2" AS "AssetEntity__AssetEntity_faces_boundingBoxY2",
|
||||
"AssetEntity__AssetEntity_faces"."isEdited" AS "AssetEntity__AssetEntity_faces_isEdited",
|
||||
"8258e303a73a72cf6abb13d73fb592dde0d68280"."id" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_id",
|
||||
"8258e303a73a72cf6abb13d73fb592dde0d68280"."createdAt" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_createdAt",
|
||||
"8258e303a73a72cf6abb13d73fb592dde0d68280"."updatedAt" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_updatedAt",
|
||||
|
||||
@@ -71,6 +71,7 @@ SELECT
|
||||
"AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
|
||||
"AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
|
||||
"AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
|
||||
"AssetFaceEntity"."isEdited" AS "AssetFaceEntity_isEdited",
|
||||
"AssetFaceEntity__AssetFaceEntity_person"."id" AS "AssetFaceEntity__AssetFaceEntity_person_id",
|
||||
"AssetFaceEntity__AssetFaceEntity_person"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_person_createdAt",
|
||||
"AssetFaceEntity__AssetFaceEntity_person"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_person_updatedAt",
|
||||
@@ -103,6 +104,7 @@ FROM
|
||||
"AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
|
||||
"AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
|
||||
"AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
|
||||
"AssetFaceEntity"."isEdited" AS "AssetFaceEntity_isEdited",
|
||||
"AssetFaceEntity__AssetFaceEntity_person"."id" AS "AssetFaceEntity__AssetFaceEntity_person_id",
|
||||
"AssetFaceEntity__AssetFaceEntity_person"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_person_createdAt",
|
||||
"AssetFaceEntity__AssetFaceEntity_person"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_person_updatedAt",
|
||||
@@ -138,6 +140,7 @@ FROM
|
||||
"AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
|
||||
"AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
|
||||
"AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
|
||||
"AssetFaceEntity"."isEdited" AS "AssetFaceEntity_isEdited",
|
||||
"AssetFaceEntity__AssetFaceEntity_person"."id" AS "AssetFaceEntity__AssetFaceEntity_person_id",
|
||||
"AssetFaceEntity__AssetFaceEntity_person"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_person_createdAt",
|
||||
"AssetFaceEntity__AssetFaceEntity_person"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_person_updatedAt",
|
||||
@@ -193,9 +196,10 @@ LIMIT
|
||||
-- PersonRepository.reassignFace
|
||||
UPDATE "asset_faces"
|
||||
SET
|
||||
"personId" = $1
|
||||
"personId" = $1,
|
||||
"isEdited" = $2
|
||||
WHERE
|
||||
"id" = $2
|
||||
"id" = $3
|
||||
|
||||
-- PersonRepository.getByName
|
||||
SELECT
|
||||
@@ -281,6 +285,7 @@ FROM
|
||||
"AssetEntity__AssetEntity_faces"."boundingBoxY1" AS "AssetEntity__AssetEntity_faces_boundingBoxY1",
|
||||
"AssetEntity__AssetEntity_faces"."boundingBoxX2" AS "AssetEntity__AssetEntity_faces_boundingBoxX2",
|
||||
"AssetEntity__AssetEntity_faces"."boundingBoxY2" AS "AssetEntity__AssetEntity_faces_boundingBoxY2",
|
||||
"AssetEntity__AssetEntity_faces"."isEdited" AS "AssetEntity__AssetEntity_faces_isEdited",
|
||||
"8258e303a73a72cf6abb13d73fb592dde0d68280"."id" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_id",
|
||||
"8258e303a73a72cf6abb13d73fb592dde0d68280"."createdAt" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_createdAt",
|
||||
"8258e303a73a72cf6abb13d73fb592dde0d68280"."updatedAt" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_updatedAt",
|
||||
@@ -373,6 +378,7 @@ SELECT
|
||||
"AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
|
||||
"AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
|
||||
"AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
|
||||
"AssetFaceEntity"."isEdited" AS "AssetFaceEntity_isEdited",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."id" AS "AssetFaceEntity__AssetFaceEntity_asset_id",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."deviceAssetId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceAssetId",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_asset_ownerId",
|
||||
@@ -424,7 +430,8 @@ SELECT
|
||||
"AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1",
|
||||
"AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
|
||||
"AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
|
||||
"AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2"
|
||||
"AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
|
||||
"AssetFaceEntity"."isEdited" AS "AssetFaceEntity_isEdited"
|
||||
FROM
|
||||
"asset_faces" "AssetFaceEntity"
|
||||
WHERE
|
||||
|
||||
@@ -207,6 +207,7 @@ WITH
|
||||
"faces"."boundingBoxY1" AS "boundingBoxY1",
|
||||
"faces"."boundingBoxX2" AS "boundingBoxX2",
|
||||
"faces"."boundingBoxY2" AS "boundingBoxY2",
|
||||
"faces"."isEdited" AS "isEdited",
|
||||
"faces"."embedding" <= > $1 AS "distance"
|
||||
FROM
|
||||
"asset_faces" "faces"
|
||||
|
||||
@@ -148,7 +148,7 @@ export class PersonRepository implements IPersonRepository {
|
||||
const result = await this.assetFaceRepository
|
||||
.createQueryBuilder()
|
||||
.update()
|
||||
.set({ personId: newPersonId })
|
||||
.set({ personId: newPersonId, isEdited: true })
|
||||
.where({ id: assetFaceId })
|
||||
.execute();
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ export class AssetService {
|
||||
}
|
||||
|
||||
if (data.ownerId !== auth.user.id || auth.sharedLink) {
|
||||
data.people = [];
|
||||
delete data.people;
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
@@ -437,6 +437,36 @@ describe(PersonService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('unassignFace', () => {
|
||||
it('should unassign a face', async () => {
|
||||
personMock.getFaceById.mockResolvedValueOnce(faceStub.face1);
|
||||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id]));
|
||||
accessMock.person.checkFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id]));
|
||||
personMock.reassignFace.mockResolvedValue(1);
|
||||
personMock.getRandomFace.mockResolvedValue(null);
|
||||
personMock.getFaceById.mockResolvedValueOnce(faceStub.unassignedFace);
|
||||
|
||||
await expect(sut.unassignFace(authStub.admin, faceStub.face1.id)).resolves.toStrictEqual(
|
||||
mapFaces(faceStub.unassignedFace, authStub.admin),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unassignFaces', () => {
|
||||
it('should unassign a face', async () => {
|
||||
personMock.getFacesByIds.mockResolvedValueOnce([faceStub.face1]);
|
||||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id]));
|
||||
accessMock.person.checkFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id]));
|
||||
personMock.reassignFace.mockResolvedValue(1);
|
||||
personMock.getRandomFace.mockResolvedValue(null);
|
||||
personMock.getFaceById.mockResolvedValueOnce(faceStub.unassignedFace);
|
||||
|
||||
await expect(
|
||||
sut.unassignFaces(authStub.admin, { data: [{ assetId: faceStub.face1.id, personId: 'person-1' }] }),
|
||||
).resolves.toStrictEqual([{ id: 'assetFaceId1', success: true }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handlePersonCleanup', () => {
|
||||
it('should delete people without faces', async () => {
|
||||
personMock.getAllWithoutFaces.mockResolvedValue([personStub.noName]);
|
||||
@@ -550,7 +580,10 @@ describe(PersonService.name, () => {
|
||||
|
||||
await sut.handleQueueRecognizeFaces({});
|
||||
|
||||
expect(personMock.getAllFaces).toHaveBeenCalledWith({ skip: 0, take: 1000 }, { where: { personId: IsNull() } });
|
||||
expect(personMock.getAllFaces).toHaveBeenCalledWith(
|
||||
{ skip: 0, take: 1000 },
|
||||
{ where: { personId: IsNull(), isEdited: false } },
|
||||
);
|
||||
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
||||
{
|
||||
name: JobName.FACIAL_RECOGNITION,
|
||||
|
||||
@@ -101,6 +101,22 @@ export class PersonService {
|
||||
};
|
||||
}
|
||||
|
||||
async unassignFace(auth: AuthDto, id: string): Promise<AssetFaceResponseDto> {
|
||||
let face = await this.repository.getFaceById(id);
|
||||
await this.access.requirePermission(auth, Permission.PERSON_CREATE, face.id);
|
||||
|
||||
if (face.personId) {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, face.personId);
|
||||
}
|
||||
|
||||
await this.repository.reassignFace(face.id, null);
|
||||
if (face.person && face.person.faceAssetId === face.id) {
|
||||
await this.createNewFeaturePhoto([face.person.id]);
|
||||
}
|
||||
face = await this.repository.getFaceById(id);
|
||||
return mapFaces(face, auth);
|
||||
}
|
||||
|
||||
async reassignFaces(auth: AuthDto, personId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, personId);
|
||||
const person = await this.findOrFail(personId);
|
||||
@@ -130,6 +146,34 @@ export class PersonService {
|
||||
return result;
|
||||
}
|
||||
|
||||
async unassignFaces(auth: AuthDto, dto: AssetFaceUpdateDto): Promise<BulkIdResponseDto[]> {
|
||||
const changeFeaturePhoto: string[] = [];
|
||||
const results: BulkIdResponseDto[] = [];
|
||||
|
||||
for (const data of dto.data) {
|
||||
const faces = await this.repository.getFacesByIds([{ personId: data.personId, assetId: data.assetId }]);
|
||||
|
||||
for (const face of faces) {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_CREATE, face.id);
|
||||
if (face.personId) {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, face.personId);
|
||||
}
|
||||
|
||||
await this.repository.reassignFace(face.id, null);
|
||||
if (face.person && face.person.faceAssetId === face.id) {
|
||||
changeFeaturePhoto.push(face.person.id);
|
||||
}
|
||||
results.push({ id: face.id, success: true });
|
||||
}
|
||||
}
|
||||
if (changeFeaturePhoto.length > 0) {
|
||||
// Remove duplicates
|
||||
await this.createNewFeaturePhoto([...changeFeaturePhoto]);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async reassignFacesById(auth: AuthDto, personId: string, dto: FaceDto): Promise<PersonResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, personId);
|
||||
|
||||
@@ -386,7 +430,7 @@ export class PersonService {
|
||||
}
|
||||
|
||||
const facePagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
|
||||
this.repository.getAllFaces(pagination, { where: force ? undefined : { personId: IsNull() } }),
|
||||
this.repository.getAllFaces(pagination, { where: force ? undefined : { personId: IsNull(), isEdited: false } }),
|
||||
);
|
||||
|
||||
for await (const page of facePagination) {
|
||||
|
||||
24
server/test/fixtures/face.stub.ts
vendored
24
server/test/fixtures/face.stub.ts
vendored
@@ -18,6 +18,7 @@ export const faceStub = {
|
||||
boundingBoxY2: 1,
|
||||
imageHeight: 1024,
|
||||
imageWidth: 1024,
|
||||
isEdited: false,
|
||||
}),
|
||||
primaryFace1: Object.freeze<NonNullableProperty<AssetFaceEntity>>({
|
||||
id: 'assetFaceId2',
|
||||
@@ -32,6 +33,7 @@ export const faceStub = {
|
||||
boundingBoxY2: 1,
|
||||
imageHeight: 1024,
|
||||
imageWidth: 1024,
|
||||
isEdited: false,
|
||||
}),
|
||||
mergeFace1: Object.freeze<NonNullableProperty<AssetFaceEntity>>({
|
||||
id: 'assetFaceId3',
|
||||
@@ -46,6 +48,7 @@ export const faceStub = {
|
||||
boundingBoxY2: 1,
|
||||
imageHeight: 1024,
|
||||
imageWidth: 1024,
|
||||
isEdited: false,
|
||||
}),
|
||||
mergeFace2: Object.freeze<NonNullableProperty<AssetFaceEntity>>({
|
||||
id: 'assetFaceId4',
|
||||
@@ -60,6 +63,7 @@ export const faceStub = {
|
||||
boundingBoxY2: 1,
|
||||
imageHeight: 1024,
|
||||
imageWidth: 1024,
|
||||
isEdited: false,
|
||||
}),
|
||||
start: Object.freeze<NonNullableProperty<AssetFaceEntity>>({
|
||||
id: 'assetFaceId5',
|
||||
@@ -74,6 +78,7 @@ export const faceStub = {
|
||||
boundingBoxY2: 505,
|
||||
imageHeight: 1000,
|
||||
imageWidth: 1000,
|
||||
isEdited: false,
|
||||
}),
|
||||
middle: Object.freeze<NonNullableProperty<AssetFaceEntity>>({
|
||||
id: 'assetFaceId6',
|
||||
@@ -88,6 +93,7 @@ export const faceStub = {
|
||||
boundingBoxY2: 200,
|
||||
imageHeight: 500,
|
||||
imageWidth: 400,
|
||||
isEdited: false,
|
||||
}),
|
||||
end: Object.freeze<NonNullableProperty<AssetFaceEntity>>({
|
||||
id: 'assetFaceId7',
|
||||
@@ -102,6 +108,7 @@ export const faceStub = {
|
||||
boundingBoxY2: 495,
|
||||
imageHeight: 500,
|
||||
imageWidth: 500,
|
||||
isEdited: false,
|
||||
}),
|
||||
noPerson1: Object.freeze<AssetFaceEntity>({
|
||||
id: 'assetFaceId8',
|
||||
@@ -116,6 +123,7 @@ export const faceStub = {
|
||||
boundingBoxY2: 1,
|
||||
imageHeight: 1024,
|
||||
imageWidth: 1024,
|
||||
isEdited: false,
|
||||
}),
|
||||
noPerson2: Object.freeze<AssetFaceEntity>({
|
||||
id: 'assetFaceId9',
|
||||
@@ -130,5 +138,21 @@ export const faceStub = {
|
||||
boundingBoxY2: 1,
|
||||
imageHeight: 1024,
|
||||
imageWidth: 1024,
|
||||
isEdited: false,
|
||||
}),
|
||||
unassignedFace: Object.freeze<AssetFaceEntity>({
|
||||
id: 'assetFaceId',
|
||||
assetId: assetStub.image.id,
|
||||
asset: assetStub.image,
|
||||
personId: null,
|
||||
person: null,
|
||||
embedding: [1, 2, 3, 4],
|
||||
boundingBoxX1: 0,
|
||||
boundingBoxY1: 0,
|
||||
boundingBoxX2: 1,
|
||||
boundingBoxY2: 1,
|
||||
imageHeight: 1024,
|
||||
imageWidth: 1024,
|
||||
isEdited: false,
|
||||
}),
|
||||
};
|
||||
|
||||
2
server/test/fixtures/shared-link.stub.ts
vendored
2
server/test/fixtures/shared-link.stub.ts
vendored
@@ -73,7 +73,7 @@ const assetResponse: AssetResponseDto = {
|
||||
exifInfo: assetInfo,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
people: [],
|
||||
people: undefined,
|
||||
checksum: 'ZmlsZSBoYXNo',
|
||||
isTrashed: false,
|
||||
libraryId: 'library-id',
|
||||
|
||||
Reference in New Issue
Block a user