mirror of
https://github.com/immich-app/immich.git
synced 2025-12-24 01:11:32 +03:00
feat(web,server): search people (#5703)
* feat: search peoples * fix: responsive design * use existing count * generate sql file * fix: tests * remove visible people * fix: merge, hide... * use component * fix: linter * chore: regenerate api * fix: change name when searching for a face * save search * remove duplicate * use enums for query parameters * fix: increase to 20 for the local search * use constants * simplify * fix: number of people more visible * fix: merge * fix: search * fix: loading spinner position * pr feedback
This commit is contained in:
@@ -128,9 +128,6 @@ export class PeopleResponseDto {
|
||||
@ApiProperty({ type: 'integer' })
|
||||
total!: number;
|
||||
|
||||
@ApiProperty({ type: 'integer' })
|
||||
visible!: number;
|
||||
|
||||
people!: PersonResponseDto[];
|
||||
}
|
||||
|
||||
|
||||
@@ -116,9 +116,9 @@ describe(PersonService.name, () => {
|
||||
describe('getAll', () => {
|
||||
it('should get all people with thumbnails', async () => {
|
||||
personMock.getAllForUser.mockResolvedValue([personStub.withName, personStub.noThumbnail]);
|
||||
personMock.getNumberOfPeople.mockResolvedValue(1);
|
||||
await expect(sut.getAll(authStub.admin, { withHidden: undefined })).resolves.toEqual({
|
||||
total: 1,
|
||||
visible: 1,
|
||||
people: [responseDto],
|
||||
});
|
||||
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.user.id, {
|
||||
@@ -128,9 +128,9 @@ describe(PersonService.name, () => {
|
||||
});
|
||||
it('should get all visible people with thumbnails', async () => {
|
||||
personMock.getAllForUser.mockResolvedValue([personStub.withName, personStub.hidden]);
|
||||
personMock.getNumberOfPeople.mockResolvedValue(2);
|
||||
await expect(sut.getAll(authStub.admin, { withHidden: false })).resolves.toEqual({
|
||||
total: 2,
|
||||
visible: 1,
|
||||
people: [responseDto],
|
||||
});
|
||||
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.user.id, {
|
||||
@@ -140,9 +140,9 @@ describe(PersonService.name, () => {
|
||||
});
|
||||
it('should get all hidden and visible people with thumbnails', async () => {
|
||||
personMock.getAllForUser.mockResolvedValue([personStub.withName, personStub.hidden]);
|
||||
personMock.getNumberOfPeople.mockResolvedValue(2);
|
||||
await expect(sut.getAll(authStub.admin, { withHidden: true })).resolves.toEqual({
|
||||
total: 2,
|
||||
visible: 1,
|
||||
people: [
|
||||
responseDto,
|
||||
{
|
||||
|
||||
@@ -82,6 +82,7 @@ export class PersonService {
|
||||
minimumFaceCount: machineLearning.facialRecognition.minFaces,
|
||||
withHidden: dto.withHidden || false,
|
||||
});
|
||||
const total = await this.repository.getNumberOfPeople(auth.user.id);
|
||||
const persons: PersonResponseDto[] = people
|
||||
// with thumbnails
|
||||
.filter((person) => !!person.thumbnailPath)
|
||||
@@ -89,8 +90,7 @@ export class PersonService {
|
||||
|
||||
return {
|
||||
people: persons.filter((person) => dto.withHidden || !person.isHidden),
|
||||
total: persons.length,
|
||||
visible: persons.filter((person: PersonResponseDto) => !person.isHidden).length,
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ export interface IPersonRepository {
|
||||
getRandomFace(personId: string): Promise<AssetFaceEntity | null>;
|
||||
getStatistics(personId: string): Promise<PersonStatistics>;
|
||||
reassignFace(assetFaceId: string, newPersonId: string): Promise<number>;
|
||||
getNumberOfPeople(userId: string): Promise<number>;
|
||||
reassignFaces(data: UpdateFacesData): Promise<number>;
|
||||
update(entity: Partial<PersonEntity>): Promise<PersonEntity>;
|
||||
}
|
||||
|
||||
@@ -211,6 +211,18 @@ export class PersonRepository implements IPersonRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getNumberOfPeople(userId: string): Promise<number> {
|
||||
return this.personRepository
|
||||
.createQueryBuilder('person')
|
||||
.leftJoin('person.faces', 'face')
|
||||
.where('person.ownerId = :userId', { userId })
|
||||
.having('COUNT(face.assetId) != 0')
|
||||
.groupBy('person.id')
|
||||
.withDeleted()
|
||||
.getCount();
|
||||
}
|
||||
|
||||
create(entity: Partial<PersonEntity>): Promise<PersonEntity> {
|
||||
return this.personRepository.save(entity);
|
||||
}
|
||||
|
||||
@@ -338,6 +338,17 @@ ORDER BY
|
||||
LIMIT
|
||||
1000
|
||||
|
||||
-- PersonRepository.getNumberOfPeople
|
||||
SELECT
|
||||
COUNT(DISTINCT ("person"."id")) AS "cnt"
|
||||
FROM
|
||||
"person" "person"
|
||||
LEFT JOIN "asset_faces" "face" ON "face"."personId" = "person"."id"
|
||||
WHERE
|
||||
"person"."ownerId" = $1
|
||||
HAVING
|
||||
COUNT("face"."assetId") != 0
|
||||
|
||||
-- PersonRepository.getFacesByIds
|
||||
SELECT
|
||||
"AssetFaceEntity"."id" AS "AssetFaceEntity_id",
|
||||
|
||||
Reference in New Issue
Block a user