1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-09 05:16:47 +01:00
immich/server/src/domain/person/person.service.spec.ts

878 lines
36 KiB
TypeScript
Raw Normal View History

import { AssetFaceEntity, Colorspace, SystemConfigKey } from '@app/infra/entities';
2023-05-17 19:07:17 +02:00
import { BadRequestException, NotFoundException } from '@nestjs/common';
import {
IAccessRepositoryMock,
assetStub,
2023-05-17 19:07:17 +02:00
authStub,
faceStub,
newAccessRepositoryMock,
newAssetRepositoryMock,
2023-05-17 19:07:17 +02:00
newJobRepositoryMock,
newMachineLearningRepositoryMock,
newMediaRepositoryMock,
newMoveRepositoryMock,
2023-05-17 19:07:17 +02:00
newPersonRepositoryMock,
2023-12-08 17:15:46 +01:00
newSmartInfoRepositoryMock,
2023-05-17 19:07:17 +02:00
newStorageRepositoryMock,
newSystemConfigRepositoryMock,
2023-05-17 19:07:17 +02:00
personStub,
} from '@test';
2023-10-09 16:25:03 +02:00
import { BulkIdErrorReason } from '../asset';
import { ImmichFileResponse } from '../domain.util';
2023-10-09 16:25:03 +02:00
import { JobName } from '../job';
import {
IAssetRepository,
IJobRepository,
IMachineLearningRepository,
IMediaRepository,
IMoveRepository,
2023-10-09 16:25:03 +02:00
IPersonRepository,
2023-12-08 17:15:46 +01:00
ISmartInfoRepository,
2023-10-09 16:25:03 +02:00
IStorageRepository,
ISystemConfigRepository,
WithoutProperty,
} from '../repositories';
2023-12-08 17:15:46 +01:00
import { PersonResponseDto, mapFaces, mapPerson } from './person.dto';
2023-05-17 19:07:17 +02:00
import { PersonService } from './person.service';
const responseDto: PersonResponseDto = {
id: 'person-1',
name: 'Person 1',
birthDate: null,
thumbnailPath: '/path/to/thumbnail.jpg',
isHidden: false,
2023-05-17 19:07:17 +02:00
};
const statistics = { assets: 3 };
const croppedFace = Buffer.from('Cropped Face');
const detectFaceMock = {
assetId: 'asset-1',
personId: 'person-1',
boundingBox: {
x1: 100,
y1: 100,
x2: 200,
y2: 200,
},
imageHeight: 500,
imageWidth: 400,
embedding: [1, 2, 3, 4],
score: 0.2,
};
2023-05-17 19:07:17 +02:00
describe(PersonService.name, () => {
let accessMock: IAccessRepositoryMock;
let assetMock: jest.Mocked<IAssetRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>;
2023-05-17 19:07:17 +02:00
let jobMock: jest.Mocked<IJobRepository>;
let machineLearningMock: jest.Mocked<IMachineLearningRepository>;
let mediaMock: jest.Mocked<IMediaRepository>;
let moveMock: jest.Mocked<IMoveRepository>;
let personMock: jest.Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>;
2023-12-08 17:15:46 +01:00
let smartInfoMock: jest.Mocked<ISmartInfoRepository>;
let sut: PersonService;
2023-05-17 19:07:17 +02:00
beforeEach(async () => {
accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock();
configMock = newSystemConfigRepositoryMock();
2023-05-17 19:07:17 +02:00
jobMock = newJobRepositoryMock();
machineLearningMock = newMachineLearningRepositoryMock();
moveMock = newMoveRepositoryMock();
mediaMock = newMediaRepositoryMock();
personMock = newPersonRepositoryMock();
storageMock = newStorageRepositoryMock();
2023-12-08 17:15:46 +01:00
smartInfoMock = newSmartInfoRepositoryMock();
sut = new PersonService(
accessMock,
assetMock,
machineLearningMock,
moveMock,
mediaMock,
personMock,
configMock,
storageMock,
jobMock,
2023-12-08 17:15:46 +01:00
smartInfoMock,
);
mediaMock.crop.mockResolvedValue(croppedFace);
2023-05-17 19:07:17 +02:00
});
it('should be defined', () => {
expect(sut).toBeDefined();
});
describe('getAll', () => {
it('should get all people with thumbnails', async () => {
personMock.getAllForUser.mockResolvedValue([personStub.withName, personStub.noThumbnail]);
await expect(sut.getAll(authStub.admin, { withHidden: undefined })).resolves.toEqual({
total: 1,
visible: 1,
people: [responseDto],
});
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.user.id, {
minimumFaceCount: 1,
withHidden: false,
});
});
it('should get all visible people with thumbnails', async () => {
personMock.getAllForUser.mockResolvedValue([personStub.withName, personStub.hidden]);
await expect(sut.getAll(authStub.admin, { withHidden: false })).resolves.toEqual({
total: 2,
visible: 1,
people: [responseDto],
});
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.user.id, {
minimumFaceCount: 1,
withHidden: false,
});
});
it('should get all hidden and visible people with thumbnails', async () => {
personMock.getAllForUser.mockResolvedValue([personStub.withName, personStub.hidden]);
await expect(sut.getAll(authStub.admin, { withHidden: true })).resolves.toEqual({
total: 2,
visible: 1,
people: [
responseDto,
{
id: 'person-1',
name: '',
birthDate: null,
thumbnailPath: '/path/to/thumbnail.jpg',
isHidden: true,
},
],
});
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.user.id, {
minimumFaceCount: 1,
withHidden: true,
});
2023-05-17 19:07:17 +02:00
});
});
describe('getById', () => {
it('should require person.read permission', async () => {
personMock.getById.mockResolvedValue(personStub.withName);
await expect(sut.getById(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
2023-05-17 19:07:17 +02:00
it('should throw a bad request when person is not found', async () => {
personMock.getById.mockResolvedValue(null);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
2023-05-17 19:07:17 +02:00
await expect(sut.getById(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
2023-05-17 19:07:17 +02:00
});
it('should get a person by id', async () => {
personMock.getById.mockResolvedValue(personStub.withName);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
2023-05-17 19:07:17 +02:00
await expect(sut.getById(authStub.admin, 'person-1')).resolves.toEqual(responseDto);
expect(personMock.getById).toHaveBeenCalledWith('person-1');
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
2023-05-17 19:07:17 +02:00
});
});
describe('getThumbnail', () => {
it('should require person.read permission', async () => {
personMock.getById.mockResolvedValue(personStub.noName);
await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
expect(storageMock.createReadStream).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
2023-05-17 19:07:17 +02:00
it('should throw an error when personId is invalid', async () => {
personMock.getById.mockResolvedValue(null);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
2023-05-17 19:07:17 +02:00
await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(NotFoundException);
expect(storageMock.createReadStream).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
2023-05-17 19:07:17 +02:00
});
it('should throw an error when person has no thumbnail', async () => {
personMock.getById.mockResolvedValue(personStub.noThumbnail);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
2023-05-17 19:07:17 +02:00
await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(NotFoundException);
expect(storageMock.createReadStream).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
2023-05-17 19:07:17 +02:00
});
it('should serve the thumbnail', async () => {
personMock.getById.mockResolvedValue(personStub.noName);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
await expect(sut.getThumbnail(authStub.admin, 'person-1')).resolves.toEqual(
new ImmichFileResponse({
path: '/path/to/thumbnail.jpg',
contentType: 'image/jpeg',
cacheControl: true,
}),
);
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
2023-05-17 19:07:17 +02:00
});
});
describe('getAssets', () => {
it('should require person.read permission', async () => {
personMock.getAssets.mockResolvedValue([assetStub.image, assetStub.video]);
await expect(sut.getAssets(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
expect(personMock.getAssets).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
2023-05-17 19:07:17 +02:00
it("should return a person's assets", async () => {
personMock.getAssets.mockResolvedValue([assetStub.image, assetStub.video]);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
2023-05-17 19:07:17 +02:00
await sut.getAssets(authStub.admin, 'person-1');
expect(personMock.getAssets).toHaveBeenCalledWith('person-1');
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
2023-05-17 19:07:17 +02:00
});
});
describe('update', () => {
it('should require person.write permission', async () => {
personMock.getById.mockResolvedValue(personStub.noName);
await expect(sut.update(authStub.admin, 'person-1', { name: 'Person 1' })).rejects.toBeInstanceOf(
BadRequestException,
);
expect(personMock.update).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
2023-05-17 19:07:17 +02:00
it('should throw an error when personId is invalid', async () => {
personMock.getById.mockResolvedValue(null);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
2023-05-17 19:07:17 +02:00
await expect(sut.update(authStub.admin, 'person-1', { name: 'Person 1' })).rejects.toBeInstanceOf(
BadRequestException,
);
expect(personMock.update).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
2023-05-17 19:07:17 +02:00
});
it("should update a person's name", async () => {
personMock.getById.mockResolvedValue(personStub.noName);
personMock.update.mockResolvedValue(personStub.withName);
personMock.getAssets.mockResolvedValue([assetStub.image]);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
2023-05-17 19:07:17 +02:00
await expect(sut.update(authStub.admin, 'person-1', { name: 'Person 1' })).resolves.toEqual(responseDto);
expect(personMock.getById).toHaveBeenCalledWith('person-1');
2023-05-17 19:07:17 +02:00
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', name: 'Person 1' });
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
2023-05-17 19:07:17 +02:00
});
it("should update a person's date of birth", async () => {
personMock.getById.mockResolvedValue(personStub.noBirthDate);
personMock.update.mockResolvedValue(personStub.withBirthDate);
personMock.getAssets.mockResolvedValue([assetStub.image]);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
await expect(sut.update(authStub.admin, 'person-1', { birthDate: new Date('1976-06-30') })).resolves.toEqual({
id: 'person-1',
name: 'Person 1',
birthDate: new Date('1976-06-30'),
thumbnailPath: '/path/to/thumbnail.jpg',
isHidden: false,
});
expect(personMock.getById).toHaveBeenCalledWith('person-1');
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: new Date('1976-06-30') });
expect(jobMock.queue).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
it('should update a person visibility', async () => {
personMock.getById.mockResolvedValue(personStub.hidden);
personMock.update.mockResolvedValue(personStub.withName);
personMock.getAssets.mockResolvedValue([assetStub.image]);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
await expect(sut.update(authStub.admin, 'person-1', { isHidden: false })).resolves.toEqual(responseDto);
expect(personMock.getById).toHaveBeenCalledWith('person-1');
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', isHidden: false });
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
it("should update a person's thumbnailPath", async () => {
personMock.getById.mockResolvedValue(personStub.withName);
personMock.update.mockResolvedValue(personStub.withName);
personMock.getFacesByIds.mockResolvedValue([faceStub.face1]);
chore(server): Check asset permissions in bulk (#5329) Modify Access repository, to evaluate `asset` permissions in bulk. Queries have been validated to match what they currently generate for single ids. Queries: * `asset` album access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "albums" "AlbumEntity" LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id" LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId" AND "AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId"="AlbumEntity"."id" LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."id"="AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId" AND "AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL WHERE ( ("AlbumEntity"."ownerId" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2) OR ("AlbumEntity__AlbumEntity_sharedUsers"."id" = $3 AND "AlbumEntity__AlbumEntity_assets"."id" = $4) OR ("AlbumEntity"."ownerId" = $5 AND "AlbumEntity__AlbumEntity_assets"."livePhotoVideoId" = $6) OR ("AlbumEntity__AlbumEntity_sharedUsers"."id" = $7 AND "AlbumEntity__AlbumEntity_assets"."livePhotoVideoId" = $8) ) AND "AlbumEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "asset"."id" AS "assetId", "asset"."livePhotoVideoId" AS "livePhotoVideoId" FROM "albums" "album" INNER JOIN "albums_assets_assets" "album_asset" ON "album_asset"."albumsId"="album"."id" INNER JOIN "assets" "asset" ON "asset"."id"="album_asset"."assetsId" AND "asset"."deletedAt" IS NULL LEFT JOIN "albums_shared_users_users" "album_sharedUsers" ON "album_sharedUsers"."albumsId"="album"."id" LEFT JOIN "users" "sharedUsers" ON "sharedUsers"."id"="album_sharedUsers"."usersId" AND "sharedUsers"."deletedAt" IS NULL WHERE ( "album"."ownerId" = $1 OR "sharedUsers"."id" = $2 ) AND ( "asset"."id" IN ($3, $4) OR "asset"."livePhotoVideoId" IN ($5, $6) ) AND "album"."deletedAt" IS NULL ``` * `asset` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "assets" "AssetEntity" WHERE "AssetEntity"."id" = $1 AND "AssetEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "AssetEntity"."id" AS "AssetEntity_id" FROM "assets" "AssetEntity" WHERE "AssetEntity"."id" IN ($1, $2) AND "AssetEntity"."ownerId" = $3 ``` * `asset` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__PartnerEntity_sharedWith" ON "PartnerEntity__PartnerEntity_sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__PartnerEntity_sharedWith"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__PartnerEntity_sharedBy" ON "PartnerEntity__PartnerEntity_sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__PartnerEntity_sharedBy"."deletedAt" IS NULL LEFT JOIN "assets" "0aabe9f4a62b794e2c24a074297e534f51a4ac6c" ON "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"."ownerId"="PartnerEntity__PartnerEntity_sharedBy"."id" AND "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity__PartnerEntity_sharedWith"."id" = $1 AND "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"."id" = $2 ) LIMIT 1 -- After SELECT "asset"."id" AS "assetId" FROM "partners" "partner" INNER JOIN "users" "sharedBy" ON "sharedBy"."id"="partner"."sharedById" AND "sharedBy"."deletedAt" IS NULL INNER JOIN "assets" "asset" ON "asset"."ownerId"="sharedBy"."id" AND "asset"."deletedAt" IS NULL WHERE "partner"."sharedWithId" = $1 AND "asset"."id" IN ($2, $3) ``` * `asset` shared link access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "shared_links" "SharedLinkEntity" LEFT JOIN "albums" "SharedLinkEntity__SharedLinkEntity_album" ON "SharedLinkEntity__SharedLinkEntity_album"."id"="SharedLinkEntity"."albumId" AND "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" IS NULL LEFT JOIN "albums_assets_assets" "760f12c00d97bdcec1ce224d1e3bf449859942b6" ON "760f12c00d97bdcec1ce224d1e3bf449859942b6"."albumsId"="SharedLinkEntity__SharedLinkEntity_album"."id" LEFT JOIN "assets" "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6" ON "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id"="760f12c00d97bdcec1ce224d1e3bf449859942b6"."assetsId" AND "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deletedAt" IS NULL LEFT JOIN "shared_link__asset" "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity" ON "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."sharedLinksId"="SharedLinkEntity"."id" LEFT JOIN "assets" "SharedLinkEntity__SharedLinkEntity_assets" ON "SharedLinkEntity__SharedLinkEntity_assets"."id"="SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."assetsId" AND "SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" IS NULL WHERE ( ("SharedLinkEntity"."id" = $1 AND "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id" = $2) OR ("SharedLinkEntity"."id" = $3 AND "SharedLinkEntity__SharedLinkEntity_assets"."id" = $4) OR ("SharedLinkEntity"."id" = $5 AND "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."livePhotoVideoId" = $6) OR ("SharedLinkEntity"."id" = $7 AND "SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" = $8) ) ) LIMIT 1 -- After SELECT "assets"."id" AS "assetId", "assets"."livePhotoVideoId" AS "assetLivePhotoVideoId", "albumAssets"."id" AS "albumAssetId", "albumAssets"."livePhotoVideoId" AS "albumAssetLivePhotoVideoId" FROM "shared_links" "sharedLink" LEFT JOIN "albums" "album" ON "album"."id"="sharedLink"."albumId" AND "album"."deletedAt" IS NULL LEFT JOIN "shared_link__asset" "assets_sharedLink" ON "assets_sharedLink"."sharedLinksId"="sharedLink"."id" LEFT JOIN "assets" "assets" ON "assets"."id"="assets_sharedLink"."assetsId" AND "assets"."deletedAt" IS NULL LEFT JOIN "albums_assets_assets" "album_albumAssets" ON "album_albumAssets"."albumsId"="album"."id" LEFT JOIN "assets" "albumAssets" ON "albumAssets"."id"="album_albumAssets"."assetsId" AND "albumAssets"."deletedAt" IS NULL WHERE "sharedLink"."id" = $1 AND ( "assets"."id" IN ($2, $3) OR "albumAssets"."id" IN ($4, $5) OR "assets"."livePhotoVideoId" IN ($6, $7) OR "albumAssets"."livePhotoVideoId" IN ($8, $9) ) ```
2023-12-02 03:56:41 +01:00
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
await expect(
sut.update(authStub.admin, 'person-1', { featureFaceAssetId: faceStub.face1.assetId }),
).resolves.toEqual(responseDto);
expect(personMock.getById).toHaveBeenCalledWith('person-1');
feat(web): re-assign person faces (2) (#4949) * feat: unassign person faces * multiple improvements * chore: regenerate api * feat: improve face interactions in photos * fix: tests * fix: tests * optimize * fix: wrong assignment on complex-multiple re-assignments * fix: thumbnails with large photos * fix: complex reassign * fix: don't send people with faces * fix: person thumbnail generation * chore: regenerate api * add tess * feat: face box even when zoomed * fix: change feature photo * feat: make the blue icon hoverable * chore: regenerate api * feat: use websocket * fix: loading spinner when clicking on the done button * fix: use the svelte way * fix: tests * simplify * fix: unused vars * fix: remove unused code * fix: add migration * chore: regenerate api * ci: add unit tests * chore: regenerate api * feat: if a new person is created for a face and the server takes more than 15 seconds to generate the person thumbnail, don't wait for it * reorganize * chore: regenerate api * feat: global edit * pr feedback * pr feedback * simplify * revert test * fix: face generation * fix: tests * fix: face generation * fix merge * feat: search names in unmerge face selector modal * fix: merge face selector * simplify feature photo generation * fix: change endpoint * pr feedback * chore: fix merge * chore: fix merge * fix: tests * fix: edit & hide buttons * fix: tests * feat: show if person is hidden * feat: rename face to person * feat: split in new panel * copy-paste-error * pr feedback * fix: feature photo * do not leak faces * fix: unmerge modal * fix: merge modal event * feat(server): remove duplicates * fix: title for image thumbnails * fix: disable side panel when there's no face until next PR --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-05 16:43:15 +01:00
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', faceAssetId: faceStub.face1.id });
expect(personMock.getFacesByIds).toHaveBeenCalledWith([
{
assetId: faceStub.face1.assetId,
personId: 'person-1',
},
]);
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: 'person-1' } });
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
it('should throw an error when the face feature assetId is invalid', async () => {
personMock.getById.mockResolvedValue(personStub.withName);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
await expect(sut.update(authStub.admin, 'person-1', { featureFaceAssetId: '-1' })).rejects.toThrow(
BadRequestException,
);
expect(personMock.update).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
2023-05-17 19:07:17 +02:00
});
describe('updateAll', () => {
it('should throw an error when personId is invalid', async () => {
personMock.getById.mockResolvedValue(null);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
await expect(
sut.updatePeople(authStub.admin, { people: [{ id: 'person-1', name: 'Person 1' }] }),
).resolves.toEqual([{ error: BulkIdErrorReason.UNKNOWN, id: 'person-1', success: false }]);
expect(personMock.update).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
});
feat(web): re-assign person faces (2) (#4949) * feat: unassign person faces * multiple improvements * chore: regenerate api * feat: improve face interactions in photos * fix: tests * fix: tests * optimize * fix: wrong assignment on complex-multiple re-assignments * fix: thumbnails with large photos * fix: complex reassign * fix: don't send people with faces * fix: person thumbnail generation * chore: regenerate api * add tess * feat: face box even when zoomed * fix: change feature photo * feat: make the blue icon hoverable * chore: regenerate api * feat: use websocket * fix: loading spinner when clicking on the done button * fix: use the svelte way * fix: tests * simplify * fix: unused vars * fix: remove unused code * fix: add migration * chore: regenerate api * ci: add unit tests * chore: regenerate api * feat: if a new person is created for a face and the server takes more than 15 seconds to generate the person thumbnail, don't wait for it * reorganize * chore: regenerate api * feat: global edit * pr feedback * pr feedback * simplify * revert test * fix: face generation * fix: tests * fix: face generation * fix merge * feat: search names in unmerge face selector modal * fix: merge face selector * simplify feature photo generation * fix: change endpoint * pr feedback * chore: fix merge * chore: fix merge * fix: tests * fix: edit & hide buttons * fix: tests * feat: show if person is hidden * feat: rename face to person * feat: split in new panel * copy-paste-error * pr feedback * fix: feature photo * do not leak faces * fix: unmerge modal * fix: merge modal event * feat(server): remove duplicates * fix: title for image thumbnails * fix: disable side panel when there's no face until next PR --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-05 16:43:15 +01:00
describe('reassignFaces', () => {
it('should throw an error if user has no access to the person', async () => {
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set());
await expect(
sut.reassignFaces(authStub.admin, personStub.noName.id, {
data: [{ personId: 'asset-face-1', assetId: '' }],
}),
).rejects.toBeInstanceOf(BadRequestException);
expect(jobMock.queue).not.toHaveBeenCalledWith();
});
it('should reassign a face', async () => {
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.withName.id]));
personMock.getById.mockResolvedValue(personStub.noName);
accessMock.person.hasFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id]));
personMock.getFacesByIds.mockResolvedValue([faceStub.face1]);
personMock.reassignFace.mockResolvedValue(1);
personMock.getRandomFace.mockResolvedValue(faceStub.primaryFace1);
await expect(
sut.reassignFaces(authStub.admin, personStub.noName.id, {
data: [{ personId: personStub.withName.id, assetId: assetStub.image.id }],
}),
).resolves.toEqual([personStub.noName]);
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.GENERATE_PERSON_THUMBNAIL,
data: { id: personStub.newThumbnail.id },
});
});
});
describe('handlePersonMigration', () => {
it('should not move person files', async () => {
personMock.getById.mockResolvedValue(null);
await expect(sut.handlePersonMigration(personStub.noName)).resolves.toStrictEqual(false);
});
});
describe('getFacesById', () => {
it('should get the bounding boxes for an asset', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([faceStub.face1.assetId]));
personMock.getFaces.mockResolvedValue([faceStub.primaryFace1]);
await expect(sut.getFacesById(authStub.admin, { id: faceStub.face1.assetId })).resolves.toStrictEqual([
mapFaces(faceStub.primaryFace1, authStub.admin),
]);
});
it('should reject if the user has not access to the asset', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set());
personMock.getFaces.mockResolvedValue([faceStub.primaryFace1]);
await expect(sut.getFacesById(authStub.admin, { id: faceStub.primaryFace1.assetId })).rejects.toBeInstanceOf(
BadRequestException,
);
});
});
describe('createNewFeaturePhoto', () => {
it('should change person feature photo', async () => {
personMock.getRandomFace.mockResolvedValue(faceStub.primaryFace1);
await sut.createNewFeaturePhoto([personStub.newThumbnail.id]);
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.GENERATE_PERSON_THUMBNAIL,
data: { id: personStub.newThumbnail.id },
});
});
});
describe('reassignFacesById', () => {
it('should create a new person', async () => {
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id]));
accessMock.person.hasFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id]));
personMock.getFaceById.mockResolvedValue(faceStub.face1);
personMock.reassignFace.mockResolvedValue(1);
personMock.getById.mockResolvedValue(personStub.noName);
personMock.getRandomFace.mockResolvedValue(null);
await expect(
sut.reassignFacesById(authStub.admin, personStub.noName.id, {
id: faceStub.face1.id,
}),
).resolves.toEqual({
birthDate: personStub.noName.birthDate,
isHidden: personStub.noName.isHidden,
id: personStub.noName.id,
name: personStub.noName.name,
thumbnailPath: personStub.noName.thumbnailPath,
});
expect(jobMock.queue).not.toHaveBeenCalledWith();
});
it('should fail if user has not the correct permissions on the asset', async () => {
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id]));
accessMock.person.hasFaceOwnerAccess.mockResolvedValue(new Set());
personMock.getFaceById.mockResolvedValue(faceStub.face1);
personMock.reassignFace.mockResolvedValue(1);
personMock.getById.mockResolvedValue(personStub.noName);
personMock.getRandomFace.mockResolvedValue(null);
await expect(
sut.reassignFacesById(authStub.admin, personStub.noName.id, {
id: faceStub.face1.id,
}),
).rejects.toBeInstanceOf(BadRequestException);
expect(jobMock.queue).not.toHaveBeenCalledWith();
});
});
describe('createPerson', () => {
it('should create a new person', async () => {
personMock.create.mockResolvedValue(personStub.primaryPerson);
personMock.getFaceById.mockResolvedValue(faceStub.face1);
accessMock.person.hasFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id]));
await expect(sut.createPerson(authStub.admin)).resolves.toBe(personStub.primaryPerson);
});
});
describe('handlePersonDelete', () => {
it('should stop if a person has not be found', async () => {
personMock.getById.mockResolvedValue(null);
await expect(sut.handlePersonDelete({ id: 'person-1' })).resolves.toBe(false);
expect(personMock.update).not.toHaveBeenCalled();
expect(storageMock.unlink).not.toHaveBeenCalled();
});
it('should delete a person', async () => {
personMock.getById.mockResolvedValue(personStub.primaryPerson);
await expect(sut.handlePersonDelete({ id: 'person-1' })).resolves.toBe(true);
expect(personMock.delete).toHaveBeenCalledWith(personStub.primaryPerson);
expect(storageMock.unlink).toHaveBeenCalledWith(personStub.primaryPerson.thumbnailPath);
});
});
2023-12-08 17:15:46 +01:00
describe('handlePersonDelete', () => {
it('should delete person', async () => {
personMock.getById.mockResolvedValue(personStub.withName);
await sut.handlePersonDelete({ id: personStub.withName.id });
expect(personMock.delete).toHaveBeenCalledWith(personStub.withName);
expect(storageMock.unlink).toHaveBeenCalledWith(personStub.withName.thumbnailPath);
});
});
2023-05-17 19:07:17 +02:00
describe('handlePersonCleanup', () => {
it('should delete people without faces', async () => {
personMock.getAllWithoutFaces.mockResolvedValue([personStub.noName]);
await sut.handlePersonCleanup();
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.PERSON_DELETE, data: { id: personStub.noName.id } });
2023-05-17 19:07:17 +02:00
});
});
describe('handleQueueRecognizeFaces', () => {
it('should return if machine learning is disabled', async () => {
configMock.load.mockResolvedValue([{ key: SystemConfigKey.MACHINE_LEARNING_ENABLED, value: false }]);
await expect(sut.handleQueueRecognizeFaces({})).resolves.toBe(true);
expect(jobMock.queue).not.toHaveBeenCalled();
expect(configMock.load).toHaveBeenCalled();
});
it('should queue missing assets', async () => {
assetMock.getWithout.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
});
await sut.handleQueueRecognizeFaces({});
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.FACES);
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.RECOGNIZE_FACES,
data: { id: assetStub.image.id },
});
});
it('should queue all assets', async () => {
assetMock.getAll.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
});
personMock.getAll.mockResolvedValue([personStub.withName]);
2023-12-08 17:15:46 +01:00
personMock.deleteAll.mockResolvedValue(5);
await sut.handleQueueRecognizeFaces({ force: true });
expect(assetMock.getAll).toHaveBeenCalled();
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.RECOGNIZE_FACES,
data: { id: assetStub.image.id },
});
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.PERSON_DELETE,
data: { id: personStub.withName.id },
});
});
});
describe('handleRecognizeFaces', () => {
it('should return if machine learning is disabled', async () => {
configMock.load.mockResolvedValue([{ key: SystemConfigKey.MACHINE_LEARNING_ENABLED, value: false }]);
await expect(sut.handleRecognizeFaces({ id: 'foo' })).resolves.toBe(true);
expect(assetMock.getByIds).not.toHaveBeenCalled();
expect(configMock.load).toHaveBeenCalled();
});
it('should skip when no resize path', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]);
await sut.handleRecognizeFaces({ id: assetStub.noResizePath.id });
expect(machineLearningMock.detectFaces).not.toHaveBeenCalled();
});
it('should skip it the asset has already been processed', async () => {
assetMock.getByIds.mockResolvedValue([
{
...assetStub.noResizePath,
faces: [
{
id: 'asset-face-1',
assetId: assetStub.noResizePath.id,
personId: faceStub.face1.personId,
} as AssetFaceEntity,
],
},
]);
await sut.handleRecognizeFaces({ id: assetStub.noResizePath.id });
expect(machineLearningMock.detectFaces).not.toHaveBeenCalled();
});
it('should handle no results', async () => {
const start = Date.now();
machineLearningMock.detectFaces.mockResolvedValue([]);
assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleRecognizeFaces({ id: assetStub.image.id });
expect(machineLearningMock.detectFaces).toHaveBeenCalledWith(
'http://immich-machine-learning:3003',
{
imagePath: assetStub.image.resizePath,
},
{
enabled: true,
maxDistance: 0.6,
minScore: 0.7,
minFaces: 1,
modelName: 'buffalo_l',
},
);
expect(personMock.createFace).not.toHaveBeenCalled();
expect(jobMock.queue).not.toHaveBeenCalled();
expect(assetMock.upsertJobStatus).toHaveBeenCalledWith({
assetId: assetStub.image.id,
facesRecognizedAt: expect.any(Date),
});
expect(assetMock.upsertJobStatus.mock.calls[0][0].facesRecognizedAt?.getTime()).toBeGreaterThan(start);
});
it('should match existing people', async () => {
machineLearningMock.detectFaces.mockResolvedValue([detectFaceMock]);
2023-12-08 17:15:46 +01:00
smartInfoMock.searchFaces.mockResolvedValue([faceStub.face1]);
assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleRecognizeFaces({ id: assetStub.image.id });
expect(personMock.createFace).toHaveBeenCalledWith({
personId: 'person-1',
assetId: 'asset-id',
embedding: [1, 2, 3, 4],
boundingBoxX1: 100,
boundingBoxY1: 100,
boundingBoxX2: 200,
boundingBoxY2: 200,
imageHeight: 500,
imageWidth: 400,
});
});
it('should create a new person', async () => {
machineLearningMock.detectFaces.mockResolvedValue([detectFaceMock]);
2023-12-08 17:15:46 +01:00
smartInfoMock.searchFaces.mockResolvedValue([]);
personMock.create.mockResolvedValue(personStub.noName);
assetMock.getByIds.mockResolvedValue([assetStub.image]);
feat(web): re-assign person faces (2) (#4949) * feat: unassign person faces * multiple improvements * chore: regenerate api * feat: improve face interactions in photos * fix: tests * fix: tests * optimize * fix: wrong assignment on complex-multiple re-assignments * fix: thumbnails with large photos * fix: complex reassign * fix: don't send people with faces * fix: person thumbnail generation * chore: regenerate api * add tess * feat: face box even when zoomed * fix: change feature photo * feat: make the blue icon hoverable * chore: regenerate api * feat: use websocket * fix: loading spinner when clicking on the done button * fix: use the svelte way * fix: tests * simplify * fix: unused vars * fix: remove unused code * fix: add migration * chore: regenerate api * ci: add unit tests * chore: regenerate api * feat: if a new person is created for a face and the server takes more than 15 seconds to generate the person thumbnail, don't wait for it * reorganize * chore: regenerate api * feat: global edit * pr feedback * pr feedback * simplify * revert test * fix: face generation * fix: tests * fix: face generation * fix merge * feat: search names in unmerge face selector modal * fix: merge face selector * simplify feature photo generation * fix: change endpoint * pr feedback * chore: fix merge * chore: fix merge * fix: tests * fix: edit & hide buttons * fix: tests * feat: show if person is hidden * feat: rename face to person * feat: split in new panel * copy-paste-error * pr feedback * fix: feature photo * do not leak faces * fix: unmerge modal * fix: merge modal event * feat(server): remove duplicates * fix: title for image thumbnails * fix: disable side panel when there's no face until next PR --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-05 16:43:15 +01:00
personMock.createFace.mockResolvedValue(faceStub.primaryFace1);
await sut.handleRecognizeFaces({ id: assetStub.image.id });
expect(personMock.create).toHaveBeenCalledWith({ ownerId: assetStub.image.ownerId });
expect(personMock.createFace).toHaveBeenCalledWith({
personId: 'person-1',
assetId: 'asset-id',
embedding: [1, 2, 3, 4],
boundingBoxX1: 100,
boundingBoxY1: 100,
boundingBoxX2: 200,
boundingBoxY2: 200,
imageHeight: 500,
imageWidth: 400,
});
});
});
describe('handleGeneratePersonThumbnail', () => {
it('should return if machine learning is disabled', async () => {
configMock.load.mockResolvedValue([{ key: SystemConfigKey.MACHINE_LEARNING_ENABLED, value: false }]);
await expect(sut.handleGeneratePersonThumbnail({ id: 'person-1' })).resolves.toBe(true);
expect(assetMock.getByIds).not.toHaveBeenCalled();
expect(configMock.load).toHaveBeenCalled();
});
it('should skip a person not found', async () => {
personMock.getById.mockResolvedValue(null);
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
expect(mediaMock.crop).not.toHaveBeenCalled();
});
it('should skip a person without a face asset id', async () => {
personMock.getById.mockResolvedValue(personStub.noThumbnail);
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
expect(mediaMock.crop).not.toHaveBeenCalled();
});
feat(web): re-assign person faces (2) (#4949) * feat: unassign person faces * multiple improvements * chore: regenerate api * feat: improve face interactions in photos * fix: tests * fix: tests * optimize * fix: wrong assignment on complex-multiple re-assignments * fix: thumbnails with large photos * fix: complex reassign * fix: don't send people with faces * fix: person thumbnail generation * chore: regenerate api * add tess * feat: face box even when zoomed * fix: change feature photo * feat: make the blue icon hoverable * chore: regenerate api * feat: use websocket * fix: loading spinner when clicking on the done button * fix: use the svelte way * fix: tests * simplify * fix: unused vars * fix: remove unused code * fix: add migration * chore: regenerate api * ci: add unit tests * chore: regenerate api * feat: if a new person is created for a face and the server takes more than 15 seconds to generate the person thumbnail, don't wait for it * reorganize * chore: regenerate api * feat: global edit * pr feedback * pr feedback * simplify * revert test * fix: face generation * fix: tests * fix: face generation * fix merge * feat: search names in unmerge face selector modal * fix: merge face selector * simplify feature photo generation * fix: change endpoint * pr feedback * chore: fix merge * chore: fix merge * fix: tests * fix: edit & hide buttons * fix: tests * feat: show if person is hidden * feat: rename face to person * feat: split in new panel * copy-paste-error * pr feedback * fix: feature photo * do not leak faces * fix: unmerge modal * fix: merge modal event * feat(server): remove duplicates * fix: title for image thumbnails * fix: disable side panel when there's no face until next PR --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-05 16:43:15 +01:00
it('should skip a person with a face asset id not found', async () => {
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.id });
personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.face1);
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
expect(mediaMock.crop).not.toHaveBeenCalled();
});
it('should skip a person with a face asset id without a thumbnail', async () => {
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.assetId });
feat(web): re-assign person faces (2) (#4949) * feat: unassign person faces * multiple improvements * chore: regenerate api * feat: improve face interactions in photos * fix: tests * fix: tests * optimize * fix: wrong assignment on complex-multiple re-assignments * fix: thumbnails with large photos * fix: complex reassign * fix: don't send people with faces * fix: person thumbnail generation * chore: regenerate api * add tess * feat: face box even when zoomed * fix: change feature photo * feat: make the blue icon hoverable * chore: regenerate api * feat: use websocket * fix: loading spinner when clicking on the done button * fix: use the svelte way * fix: tests * simplify * fix: unused vars * fix: remove unused code * fix: add migration * chore: regenerate api * ci: add unit tests * chore: regenerate api * feat: if a new person is created for a face and the server takes more than 15 seconds to generate the person thumbnail, don't wait for it * reorganize * chore: regenerate api * feat: global edit * pr feedback * pr feedback * simplify * revert test * fix: face generation * fix: tests * fix: face generation * fix merge * feat: search names in unmerge face selector modal * fix: merge face selector * simplify feature photo generation * fix: change endpoint * pr feedback * chore: fix merge * chore: fix merge * fix: tests * fix: edit & hide buttons * fix: tests * feat: show if person is hidden * feat: rename face to person * feat: split in new panel * copy-paste-error * pr feedback * fix: feature photo * do not leak faces * fix: unmerge modal * fix: merge modal event * feat(server): remove duplicates * fix: title for image thumbnails * fix: disable side panel when there's no face until next PR --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-05 16:43:15 +01:00
personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.face1);
assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]);
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
expect(mediaMock.crop).not.toHaveBeenCalled();
});
it('should generate a thumbnail', async () => {
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.assetId });
feat(web): re-assign person faces (2) (#4949) * feat: unassign person faces * multiple improvements * chore: regenerate api * feat: improve face interactions in photos * fix: tests * fix: tests * optimize * fix: wrong assignment on complex-multiple re-assignments * fix: thumbnails with large photos * fix: complex reassign * fix: don't send people with faces * fix: person thumbnail generation * chore: regenerate api * add tess * feat: face box even when zoomed * fix: change feature photo * feat: make the blue icon hoverable * chore: regenerate api * feat: use websocket * fix: loading spinner when clicking on the done button * fix: use the svelte way * fix: tests * simplify * fix: unused vars * fix: remove unused code * fix: add migration * chore: regenerate api * ci: add unit tests * chore: regenerate api * feat: if a new person is created for a face and the server takes more than 15 seconds to generate the person thumbnail, don't wait for it * reorganize * chore: regenerate api * feat: global edit * pr feedback * pr feedback * simplify * revert test * fix: face generation * fix: tests * fix: face generation * fix merge * feat: search names in unmerge face selector modal * fix: merge face selector * simplify feature photo generation * fix: change endpoint * pr feedback * chore: fix merge * chore: fix merge * fix: tests * fix: edit & hide buttons * fix: tests * feat: show if person is hidden * feat: rename face to person * feat: split in new panel * copy-paste-error * pr feedback * fix: feature photo * do not leak faces * fix: unmerge modal * fix: merge modal event * feat(server): remove duplicates * fix: title for image thumbnails * fix: disable side panel when there's no face until next PR --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-05 16:43:15 +01:00
personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.middle);
assetMock.getByIds.mockResolvedValue([assetStub.primaryImage]);
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
expect(assetMock.getByIds).toHaveBeenCalledWith([faceStub.middle.assetId]);
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/admin_id/pe/rs');
expect(mediaMock.crop).toHaveBeenCalledWith('/uploads/admin-id/thumbs/path.jpg', {
left: 95,
top: 95,
width: 110,
height: 110,
});
expect(mediaMock.resize).toHaveBeenCalledWith(croppedFace, 'upload/thumbs/admin_id/pe/rs/person-1.jpeg', {
format: 'jpeg',
size: 250,
quality: 80,
colorspace: Colorspace.P3,
});
expect(personMock.update).toHaveBeenCalledWith({
id: 'person-1',
thumbnailPath: 'upload/thumbs/admin_id/pe/rs/person-1.jpeg',
});
});
it('should generate a thumbnail without going negative', async () => {
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.start.assetId });
feat(web): re-assign person faces (2) (#4949) * feat: unassign person faces * multiple improvements * chore: regenerate api * feat: improve face interactions in photos * fix: tests * fix: tests * optimize * fix: wrong assignment on complex-multiple re-assignments * fix: thumbnails with large photos * fix: complex reassign * fix: don't send people with faces * fix: person thumbnail generation * chore: regenerate api * add tess * feat: face box even when zoomed * fix: change feature photo * feat: make the blue icon hoverable * chore: regenerate api * feat: use websocket * fix: loading spinner when clicking on the done button * fix: use the svelte way * fix: tests * simplify * fix: unused vars * fix: remove unused code * fix: add migration * chore: regenerate api * ci: add unit tests * chore: regenerate api * feat: if a new person is created for a face and the server takes more than 15 seconds to generate the person thumbnail, don't wait for it * reorganize * chore: regenerate api * feat: global edit * pr feedback * pr feedback * simplify * revert test * fix: face generation * fix: tests * fix: face generation * fix merge * feat: search names in unmerge face selector modal * fix: merge face selector * simplify feature photo generation * fix: change endpoint * pr feedback * chore: fix merge * chore: fix merge * fix: tests * fix: edit & hide buttons * fix: tests * feat: show if person is hidden * feat: rename face to person * feat: split in new panel * copy-paste-error * pr feedback * fix: feature photo * do not leak faces * fix: unmerge modal * fix: merge modal event * feat(server): remove duplicates * fix: title for image thumbnails * fix: disable side panel when there's no face until next PR --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-05 16:43:15 +01:00
personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.start);
assetMock.getByIds.mockResolvedValue([assetStub.image]);
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
expect(mediaMock.crop).toHaveBeenCalledWith('/uploads/user-id/thumbs/path.jpg', {
left: 0,
top: 0,
width: 510,
height: 510,
});
expect(mediaMock.resize).toHaveBeenCalledWith(croppedFace, 'upload/thumbs/admin_id/pe/rs/person-1.jpeg', {
format: 'jpeg',
size: 250,
quality: 80,
colorspace: Colorspace.P3,
});
});
it('should generate a thumbnail without overflowing', async () => {
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.end.assetId });
feat(web): re-assign person faces (2) (#4949) * feat: unassign person faces * multiple improvements * chore: regenerate api * feat: improve face interactions in photos * fix: tests * fix: tests * optimize * fix: wrong assignment on complex-multiple re-assignments * fix: thumbnails with large photos * fix: complex reassign * fix: don't send people with faces * fix: person thumbnail generation * chore: regenerate api * add tess * feat: face box even when zoomed * fix: change feature photo * feat: make the blue icon hoverable * chore: regenerate api * feat: use websocket * fix: loading spinner when clicking on the done button * fix: use the svelte way * fix: tests * simplify * fix: unused vars * fix: remove unused code * fix: add migration * chore: regenerate api * ci: add unit tests * chore: regenerate api * feat: if a new person is created for a face and the server takes more than 15 seconds to generate the person thumbnail, don't wait for it * reorganize * chore: regenerate api * feat: global edit * pr feedback * pr feedback * simplify * revert test * fix: face generation * fix: tests * fix: face generation * fix merge * feat: search names in unmerge face selector modal * fix: merge face selector * simplify feature photo generation * fix: change endpoint * pr feedback * chore: fix merge * chore: fix merge * fix: tests * fix: edit & hide buttons * fix: tests * feat: show if person is hidden * feat: rename face to person * feat: split in new panel * copy-paste-error * pr feedback * fix: feature photo * do not leak faces * fix: unmerge modal * fix: merge modal event * feat(server): remove duplicates * fix: title for image thumbnails * fix: disable side panel when there's no face until next PR --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-05 16:43:15 +01:00
personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.end);
assetMock.getByIds.mockResolvedValue([assetStub.primaryImage]);
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
expect(mediaMock.crop).toHaveBeenCalledWith('/uploads/admin-id/thumbs/path.jpg', {
left: 297,
top: 297,
width: 202,
height: 202,
});
expect(mediaMock.resize).toHaveBeenCalledWith(croppedFace, 'upload/thumbs/admin_id/pe/rs/person-1.jpeg', {
format: 'jpeg',
size: 250,
quality: 80,
colorspace: Colorspace.P3,
});
});
});
describe('mergePerson', () => {
it('should require person.write and person.merge permission', async () => {
personMock.getById.mockResolvedValueOnce(personStub.primaryPerson);
personMock.getById.mockResolvedValueOnce(personStub.mergePerson);
personMock.delete.mockResolvedValue(personStub.mergePerson);
await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).rejects.toBeInstanceOf(
BadRequestException,
);
expect(personMock.reassignFaces).not.toHaveBeenCalled();
expect(personMock.delete).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
it('should merge two people', async () => {
personMock.getById.mockResolvedValueOnce(personStub.primaryPerson);
personMock.getById.mockResolvedValueOnce(personStub.mergePerson);
personMock.delete.mockResolvedValue(personStub.mergePerson);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1']));
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2']));
await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([
{ id: 'person-2', success: true },
]);
expect(personMock.reassignFaces).toHaveBeenCalledWith({
newPersonId: personStub.primaryPerson.id,
oldPersonId: personStub.mergePerson.id,
});
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.PERSON_DELETE,
data: { id: personStub.mergePerson.id },
});
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
it('should throw an error when the primary person is not found', async () => {
personMock.getById.mockResolvedValue(null);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).rejects.toBeInstanceOf(
BadRequestException,
);
expect(personMock.delete).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
it('should handle invalid merge ids', async () => {
personMock.getById.mockResolvedValueOnce(personStub.primaryPerson);
personMock.getById.mockResolvedValueOnce(null);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1']));
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2']));
await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([
{ id: 'person-2', success: false, error: BulkIdErrorReason.NOT_FOUND },
]);
expect(personMock.reassignFaces).not.toHaveBeenCalled();
expect(personMock.delete).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
it('should handle an error reassigning faces', async () => {
personMock.getById.mockResolvedValue(personStub.primaryPerson);
personMock.getById.mockResolvedValue(personStub.mergePerson);
personMock.reassignFaces.mockRejectedValue(new Error('update failed'));
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1']));
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2']));
await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([
{ id: 'person-2', success: false, error: BulkIdErrorReason.UNKNOWN },
]);
expect(personMock.delete).not.toHaveBeenCalled();
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
});
describe('getStatistics', () => {
it('should get correct number of person', async () => {
personMock.getById.mockResolvedValue(personStub.primaryPerson);
personMock.getStatistics.mockResolvedValue(statistics);
chore(server): Check more permissions in bulk (#5315) Modify Access repository, to evaluate `authDevice`, `library`, `partner`, `person`, and `timeline` permissions in bulk. Queries have been validated to match what they currently generate for single ids. As an extra performance improvement, we now use a custom QueryBuilder for the Partners queries, to avoid the eager relationships that add unneeded `LEFT JOIN` clauses. We only filter based on the ids present in the `partners` table, so those joins can be avoided. Queries: * `library` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" = $1 AND "LibraryEntity"."ownerId" = $2 AND "LibraryEntity"."deletedAt" IS NULL ) LIMIT 1 -- After SELECT "LibraryEntity"."id" AS "LibraryEntity_id" FROM "libraries" "LibraryEntity" WHERE "LibraryEntity"."id" IN ($1, $2) AND "LibraryEntity"."ownerId" = $3 AND "LibraryEntity"."deletedAt" IS NULL ``` * `library` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `authDevice` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" = $2 ) LIMIT 1 -- After SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id" FROM "user_token" "UserTokenEntity" WHERE "UserTokenEntity"."userId" = $1 AND "UserTokenEntity"."id" IN ($2, $3) ``` * `timeline` partner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ``` * `person` owner access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "person" "PersonEntity" WHERE "PersonEntity"."id" = $1 AND "PersonEntity"."ownerId" = $2 ) LIMIT 1 -- After SELECT "PersonEntity"."id" AS "PersonEntity_id" FROM "person" "PersonEntity" WHERE "PersonEntity"."id" IN ($1, $2) AND "PersonEntity"."ownerId" = $3 ``` * `partner` update access: ```sql -- Before SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS ( SELECT 1 FROM "partners" "PartnerEntity" LEFT JOIN "users" "PartnerEntity__sharedBy" ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById" AND "PartnerEntity__sharedBy"."deletedAt" IS NULL LEFT JOIN "users" "PartnerEntity__sharedWith" ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId" AND "PartnerEntity__sharedWith"."deletedAt" IS NULL WHERE "PartnerEntity"."sharedWithId" = $1 AND "PartnerEntity"."sharedById" = $2 ) LIMIT 1 -- After SELECT "partner"."sharedById" AS "partner_sharedById", "partner"."sharedWithId" AS "partner_sharedWithId" FROM "partners" "partner" WHERE "partner"."sharedById" IN ($1, $2) AND "partner"."sharedWithId" = $3 ```
2023-11-26 13:50:41 +01:00
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
await expect(sut.getStatistics(authStub.admin, 'person-1')).resolves.toEqual({ assets: 3 });
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
it('should require person.read permission', async () => {
personMock.getById.mockResolvedValue(personStub.primaryPerson);
await expect(sut.getStatistics(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
});
2023-12-08 17:15:46 +01:00
describe('mapFace', () => {
it('should map a face', () => {
expect(mapFaces(faceStub.face1, { user: personStub.withName.owner })).toEqual({
2023-12-08 17:15:46 +01:00
boundingBoxX1: 0,
boundingBoxX2: 1,
boundingBoxY1: 0,
boundingBoxY2: 1,
id: 'assetFaceId',
imageHeight: 1024,
imageWidth: 1024,
person: mapPerson(personStub.withName),
});
});
it('should not map person if person is null', () => {
expect(mapFaces({ ...faceStub.face1, person: null }, authStub.user1).person).toBeNull();
});
it('should not map person if person does not match auth user id', () => {
expect(mapFaces(faceStub.face1, authStub.user1).person).toBeNull();
});
});
2023-05-17 19:07:17 +02:00
});