mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-12-08 20:29:05 +00:00
fix: suggest people (#4566)
* fix: suggest people * feat: remove hidden people * add hidden people when merging faces * pr feedback * fix: don't use reactive statement * fixed section height * improve merging * fix: migration * fix migration * feat: add asset count * fix: test * rename endpoint * add server test * improve responsive design * fix: remove videos from live photos in the asset count * pr feedback * fix: rename asset count endpoint * fix: return firstname and lastname * fix: reset people only on error * fix: search * fix: responsive design & div flickering * fix: cleanup * chore: open api --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
@@ -73,6 +73,11 @@ export class PersonResponseDto {
|
||||
isHidden!: boolean;
|
||||
}
|
||||
|
||||
export class PersonStatisticsResponseDto {
|
||||
@ApiProperty({ type: 'integer' })
|
||||
assets!: number;
|
||||
}
|
||||
|
||||
export class PeopleResponseDto {
|
||||
@ApiProperty({ type: 'integer' })
|
||||
total!: number;
|
||||
|
||||
@@ -42,6 +42,8 @@ const responseDto: PersonResponseDto = {
|
||||
isHidden: false,
|
||||
};
|
||||
|
||||
const statistics = { assets: 3 };
|
||||
|
||||
const croppedFace = Buffer.from('Cropped Face');
|
||||
|
||||
const detectFaceMock = {
|
||||
@@ -731,4 +733,21 @@ describe(PersonService.name, () => {
|
||||
expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStatistics', () => {
|
||||
it('should get correct number of person', async () => {
|
||||
personMock.getById.mockResolvedValue(personStub.primaryPerson);
|
||||
personMock.getStatistics.mockResolvedValue(statistics);
|
||||
accessMock.person.hasOwnerAccess.mockResolvedValue(true);
|
||||
await expect(sut.getStatistics(authStub.admin, 'person-1')).resolves.toEqual({ assets: 3 });
|
||||
expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1');
|
||||
});
|
||||
|
||||
it('should require person.read permission', async () => {
|
||||
personMock.getById.mockResolvedValue(personStub.primaryPerson);
|
||||
accessMock.person.hasOwnerAccess.mockResolvedValue(false);
|
||||
await expect(sut.getStatistics(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
PeopleUpdateDto,
|
||||
PersonResponseDto,
|
||||
PersonSearchDto,
|
||||
PersonStatisticsResponseDto,
|
||||
PersonUpdateDto,
|
||||
mapPerson,
|
||||
} from './person.dto';
|
||||
@@ -84,6 +85,11 @@ export class PersonService {
|
||||
return this.findOrFail(id).then(mapPerson);
|
||||
}
|
||||
|
||||
async getStatistics(authUser: AuthUserDto, id: string): Promise<PersonStatisticsResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_READ, id);
|
||||
return this.repository.getStatistics(id);
|
||||
}
|
||||
|
||||
async getThumbnail(authUser: AuthUserDto, id: string): Promise<ImmichReadStream> {
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_READ, id);
|
||||
const person = await this.repository.getById(id);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AssetEntity, AssetFaceEntity, PersonEntity } from '@app/infra/entities';
|
||||
|
||||
export const IPersonRepository = 'IPersonRepository';
|
||||
|
||||
export interface PersonSearchOptions {
|
||||
@@ -6,6 +7,10 @@ export interface PersonSearchOptions {
|
||||
withHidden: boolean;
|
||||
}
|
||||
|
||||
export interface PersonNameSearchOptions {
|
||||
withHidden?: boolean;
|
||||
}
|
||||
|
||||
export interface AssetFaceId {
|
||||
assetId: string;
|
||||
personId: string;
|
||||
@@ -16,13 +21,17 @@ export interface UpdateFacesData {
|
||||
newPersonId: string;
|
||||
}
|
||||
|
||||
export interface PersonStatistics {
|
||||
assets: number;
|
||||
}
|
||||
|
||||
export interface IPersonRepository {
|
||||
getAll(): Promise<PersonEntity[]>;
|
||||
getAllWithoutThumbnail(): Promise<PersonEntity[]>;
|
||||
getAllForUser(userId: string, options: PersonSearchOptions): Promise<PersonEntity[]>;
|
||||
getAllWithoutFaces(): Promise<PersonEntity[]>;
|
||||
getById(personId: string): Promise<PersonEntity | null>;
|
||||
getByName(userId: string, personName: string): Promise<PersonEntity[]>;
|
||||
getByName(userId: string, personName: string, options: PersonNameSearchOptions): Promise<PersonEntity[]>;
|
||||
|
||||
getAssets(personId: string): Promise<AssetEntity[]>;
|
||||
prepareReassignFaces(data: UpdateFacesData): Promise<string[]>;
|
||||
@@ -33,6 +42,8 @@ export interface IPersonRepository {
|
||||
delete(entity: PersonEntity): Promise<PersonEntity | null>;
|
||||
deleteAll(): Promise<number>;
|
||||
|
||||
getStatistics(personId: string): Promise<PersonStatistics>;
|
||||
|
||||
getAllFaces(): Promise<AssetFaceEntity[]>;
|
||||
getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]>;
|
||||
getRandomFace(personId: string): Promise<AssetFaceEntity | null>;
|
||||
|
||||
@@ -90,4 +90,9 @@ export class SearchPeopleDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name!: string;
|
||||
|
||||
@IsBoolean()
|
||||
@Transform(toBoolean)
|
||||
@Optional()
|
||||
withHidden?: boolean;
|
||||
}
|
||||
|
||||
@@ -159,8 +159,8 @@ export class SearchService {
|
||||
};
|
||||
}
|
||||
|
||||
async searchPerson(authUser: AuthUserDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
|
||||
return await this.personRepository.getByName(authUser.id, dto.name);
|
||||
searchPerson(authUser: AuthUserDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
|
||||
return this.personRepository.getByName(authUser.id, dto.name, { withHidden: dto.withHidden });
|
||||
}
|
||||
|
||||
async handleIndexAlbums() {
|
||||
|
||||
Reference in New Issue
Block a user