From 1a633f3fca378c4aed3c6e9062c65bfcd0b9fb60 Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:22:44 +0200 Subject: [PATCH] chore(server): Use access core for person permissions (#4138) * use access core for all person methods * minor fixes, feedback * reorder assignments * remove unnecessary permission requirement * unify naming of tests * reorder variables --- server/src/domain/access/access.core.ts | 13 ++ server/src/domain/access/access.repository.ts | 4 + server/src/domain/person/person.repository.ts | 4 +- .../src/domain/person/person.service.spec.ts | 117 ++++++++++++++++-- server/src/domain/person/person.service.ts | 36 ++++-- .../infra/repositories/access.repository.ts | 14 ++- .../infra/repositories/person.repository.ts | 7 +- .../repositories/access.repository.mock.ts | 5 + 8 files changed, 171 insertions(+), 29 deletions(-) diff --git a/server/src/domain/access/access.core.ts b/server/src/domain/access/access.core.ts index 3e3d4a469d..b648f8c5e9 100644 --- a/server/src/domain/access/access.core.ts +++ b/server/src/domain/access/access.core.ts @@ -23,6 +23,10 @@ export enum Permission { LIBRARY_READ = 'library.read', LIBRARY_DOWNLOAD = 'library.download', + + PERSON_READ = 'person.read', + PERSON_WRITE = 'person.write', + PERSON_MERGE = 'person.merge', } export class AccessCore { @@ -167,6 +171,15 @@ export class AccessCore { case Permission.LIBRARY_DOWNLOAD: return authUser.id === id; + case Permission.PERSON_READ: + return this.repository.person.hasOwnerAccess(authUser.id, id); + + case Permission.PERSON_WRITE: + return this.repository.person.hasOwnerAccess(authUser.id, id); + + case Permission.PERSON_MERGE: + return this.repository.person.hasOwnerAccess(authUser.id, id); + default: return false; } diff --git a/server/src/domain/access/access.repository.ts b/server/src/domain/access/access.repository.ts index 3d99fecb07..0db8bf606d 100644 --- a/server/src/domain/access/access.repository.ts +++ b/server/src/domain/access/access.repository.ts @@ -17,4 +17,8 @@ export interface IAccessRepository { library: { hasPartnerAccess(userId: string, partnerId: string): Promise; }; + + person: { + hasOwnerAccess(userId: string, personId: string): Promise; + }; } diff --git a/server/src/domain/person/person.repository.ts b/server/src/domain/person/person.repository.ts index 30315eb69c..a13a4295c4 100644 --- a/server/src/domain/person/person.repository.ts +++ b/server/src/domain/person/person.repository.ts @@ -17,9 +17,9 @@ export interface IPersonRepository { getAllWithoutThumbnail(): Promise; getAllForUser(userId: string, options: PersonSearchOptions): Promise; getAllWithoutFaces(): Promise; - getById(userId: string, personId: string): Promise; + getById(personId: string): Promise; - getAssets(userId: string, personId: string): Promise; + getAssets(personId: string): Promise; prepareReassignFaces(data: UpdateFacesData): Promise; reassignFaces(data: UpdateFacesData): Promise; diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index c403932dd8..175b302dd1 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -1,8 +1,10 @@ import { BadRequestException, NotFoundException } from '@nestjs/common'; import { + IAccessRepositoryMock, assetStub, authStub, faceStub, + newAccessRepositoryMock, newJobRepositoryMock, newPersonRepositoryMock, newStorageRepositoryMock, @@ -26,18 +28,20 @@ const responseDto: PersonResponseDto = { }; describe(PersonService.name, () => { - let sut: PersonService; - let personMock: jest.Mocked; + let accessMock: IAccessRepositoryMock; let configMock: jest.Mocked; - let storageMock: jest.Mocked; let jobMock: jest.Mocked; + let personMock: jest.Mocked; + let storageMock: jest.Mocked; + let sut: PersonService; beforeEach(async () => { + accessMock = newAccessRepositoryMock(); personMock = newPersonRepositoryMock(); storageMock = newStorageRepositoryMock(); configMock = newSystemConfigRepositoryMock(); jobMock = newJobRepositoryMock(); - sut = new PersonService(personMock, configMock, storageMock, jobMock); + sut = new PersonService(accessMock, personMock, configMock, storageMock, jobMock); }); it('should be defined', () => { @@ -93,74 +97,124 @@ describe(PersonService.name, () => { }); describe('getById', () => { + it('should require person.read permission', async () => { + personMock.getById.mockResolvedValue(personStub.withName); + accessMock.person.hasOwnerAccess.mockResolvedValue(false); + await expect(sut.getById(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); + }); + it('should throw a bad request when person is not found', async () => { personMock.getById.mockResolvedValue(null); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.getById(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it('should get a person by id', async () => { personMock.getById.mockResolvedValue(personStub.withName); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.getById(authStub.admin, 'person-1')).resolves.toEqual(responseDto); - expect(personMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); + expect(personMock.getById).toHaveBeenCalledWith('person-1'); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); }); describe('getThumbnail', () => { + it('should require person.read permission', async () => { + personMock.getById.mockResolvedValue(personStub.noName); + accessMock.person.hasOwnerAccess.mockResolvedValue(false); + await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException); + expect(storageMock.createReadStream).not.toHaveBeenCalled(); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); + }); + it('should throw an error when personId is invalid', async () => { personMock.getById.mockResolvedValue(null); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(NotFoundException); expect(storageMock.createReadStream).not.toHaveBeenCalled(); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it('should throw an error when person has no thumbnail', async () => { personMock.getById.mockResolvedValue(personStub.noThumbnail); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(NotFoundException); expect(storageMock.createReadStream).not.toHaveBeenCalled(); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it('should serve the thumbnail', async () => { personMock.getById.mockResolvedValue(personStub.noName); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await sut.getThumbnail(authStub.admin, 'person-1'); expect(storageMock.createReadStream).toHaveBeenCalledWith('/path/to/thumbnail.jpg', 'image/jpeg'); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); }); describe('getAssets', () => { + it('should require person.read permission', async () => { + personMock.getAssets.mockResolvedValue([assetStub.image, assetStub.video]); + accessMock.person.hasOwnerAccess.mockResolvedValue(false); + await expect(sut.getAssets(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException); + expect(personMock.getAssets).not.toHaveBeenCalled(); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); + }); + it("should return a person's assets", async () => { personMock.getAssets.mockResolvedValue([assetStub.image, assetStub.video]); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await sut.getAssets(authStub.admin, 'person-1'); - expect(personMock.getAssets).toHaveBeenCalledWith('admin_id', 'person-1'); + expect(personMock.getAssets).toHaveBeenCalledWith('person-1'); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); }); describe('update', () => { - it('should throw an error when personId is invalid', async () => { - personMock.getById.mockResolvedValue(null); + it('should require person.write permission', async () => { + personMock.getById.mockResolvedValue(personStub.noName); + accessMock.person.hasOwnerAccess.mockResolvedValue(false); await expect(sut.update(authStub.admin, 'person-1', { name: 'Person 1' })).rejects.toBeInstanceOf( BadRequestException, ); expect(personMock.update).not.toHaveBeenCalled(); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); + }); + + it('should throw an error when personId is invalid', async () => { + personMock.getById.mockResolvedValue(null); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); + await expect(sut.update(authStub.admin, 'person-1', { name: 'Person 1' })).rejects.toBeInstanceOf( + BadRequestException, + ); + expect(personMock.update).not.toHaveBeenCalled(); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it("should update a person's name", async () => { personMock.getById.mockResolvedValue(personStub.noName); personMock.update.mockResolvedValue(personStub.withName); personMock.getAssets.mockResolvedValue([assetStub.image]); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.update(authStub.admin, 'person-1', { name: 'Person 1' })).resolves.toEqual(responseDto); - expect(personMock.getById).toHaveBeenCalledWith('admin_id', 'person-1'); + expect(personMock.getById).toHaveBeenCalledWith('person-1'); expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', name: 'Person 1' }); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_INDEX_ASSET, data: { ids: [assetStub.image.id] }, }); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); 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]); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.update(authStub.admin, 'person-1', { birthDate: new Date('1976-06-30') })).resolves.toEqual({ id: 'person-1', @@ -170,35 +224,39 @@ describe(PersonService.name, () => { isHidden: false, }); - expect(personMock.getById).toHaveBeenCalledWith('admin_id', 'person-1'); + 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.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it('should update a person visibility', async () => { personMock.getById.mockResolvedValue(personStub.hidden); personMock.update.mockResolvedValue(personStub.withName); personMock.getAssets.mockResolvedValue([assetStub.image]); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.update(authStub.admin, 'person-1', { isHidden: false })).resolves.toEqual(responseDto); - expect(personMock.getById).toHaveBeenCalledWith('admin_id', 'person-1'); + expect(personMock.getById).toHaveBeenCalledWith('person-1'); expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', isHidden: false }); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_INDEX_ASSET, data: { ids: [assetStub.image.id] }, }); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it("should update a person's thumbnailPath", async () => { personMock.getById.mockResolvedValue(personStub.withName); personMock.getFaceById.mockResolvedValue(faceStub.face1); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect( sut.update(authStub.admin, 'person-1', { featureFaceAssetId: faceStub.face1.assetId }), ).resolves.toEqual(responseDto); - expect(personMock.getById).toHaveBeenCalledWith('admin_id', 'person-1'); + expect(personMock.getById).toHaveBeenCalledWith('person-1'); expect(personMock.getFaceById).toHaveBeenCalledWith({ assetId: faceStub.face1.assetId, personId: 'person-1', @@ -218,25 +276,31 @@ describe(PersonService.name, () => { imageWidth: faceStub.face1.imageWidth, }, }); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it('should throw an error when the face feature assetId is invalid', async () => { personMock.getById.mockResolvedValue(personStub.withName); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.update(authStub.admin, 'person-1', { featureFaceAssetId: '-1' })).rejects.toThrow( BadRequestException, ); expect(personMock.update).not.toHaveBeenCalled(); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); }); describe('updateAll', () => { it('should throw an error when personId is invalid', async () => { personMock.getById.mockResolvedValue(null); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); + 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.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); }); @@ -255,11 +319,31 @@ describe(PersonService.name, () => { }); describe('mergePerson', () => { + it('should require person.write and person.merge permission', async () => { + personMock.getById.mockResolvedValueOnce(personStub.primaryPerson); + personMock.getById.mockResolvedValueOnce(personStub.mergePerson); + personMock.prepareReassignFaces.mockResolvedValue([]); + personMock.delete.mockResolvedValue(personStub.mergePerson); + accessMock.person.hasOwnerAccess.mockResolvedValue(false); + + await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).rejects.toBeInstanceOf( + BadRequestException, + ); + + expect(personMock.prepareReassignFaces).not.toHaveBeenCalled(); + + expect(personMock.reassignFaces).not.toHaveBeenCalled(); + + expect(personMock.delete).not.toHaveBeenCalled(); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); + }); + it('should merge two people', async () => { personMock.getById.mockResolvedValueOnce(personStub.primaryPerson); personMock.getById.mockResolvedValueOnce(personStub.mergePerson); personMock.prepareReassignFaces.mockResolvedValue([]); personMock.delete.mockResolvedValue(personStub.mergePerson); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([ { id: 'person-2', success: true }, @@ -276,12 +360,14 @@ describe(PersonService.name, () => { }); expect(personMock.delete).toHaveBeenCalledWith(personStub.mergePerson); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it('should delete conflicting faces before merging', async () => { personMock.getById.mockResolvedValue(personStub.primaryPerson); personMock.getById.mockResolvedValue(personStub.mergePerson); personMock.prepareReassignFaces.mockResolvedValue([assetStub.image.id]); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([ { id: 'person-2', success: true }, @@ -296,21 +382,25 @@ describe(PersonService.name, () => { name: JobName.SEARCH_REMOVE_FACE, data: { assetId: assetStub.image.id, personId: personStub.mergePerson.id }, }); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it('should throw an error when the primary person is not found', async () => { personMock.getById.mockResolvedValue(null); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).rejects.toBeInstanceOf( BadRequestException, ); expect(personMock.delete).not.toHaveBeenCalled(); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it('should handle invalid merge ids', async () => { personMock.getById.mockResolvedValueOnce(personStub.primaryPerson); personMock.getById.mockResolvedValueOnce(null); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([ { id: 'person-2', success: false, error: BulkIdErrorReason.NOT_FOUND }, @@ -319,6 +409,7 @@ describe(PersonService.name, () => { expect(personMock.prepareReassignFaces).not.toHaveBeenCalled(); expect(personMock.reassignFaces).not.toHaveBeenCalled(); expect(personMock.delete).not.toHaveBeenCalled(); + expect(accessMock.person.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); it('should handle an error reassigning faces', async () => { @@ -326,12 +417,14 @@ describe(PersonService.name, () => { personMock.getById.mockResolvedValue(personStub.mergePerson); personMock.prepareReassignFaces.mockResolvedValue([assetStub.image.id]); personMock.reassignFaces.mockRejectedValue(new Error('update failed')); + accessMock.person.hasOwnerAccess.mockResolvedValue(true); 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.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'person-1'); }); }); }); diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index 252666e22f..e5f17b8f21 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -1,4 +1,5 @@ import { BadRequestException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common'; +import { AccessCore, IAccessRepository, Permission } from '../access'; import { AssetResponseDto, BulkIdErrorReason, BulkIdResponseDto, mapAsset } from '../asset'; import { AuthUserDto } from '../auth'; import { mimeTypes } from '../domain.constant'; @@ -18,15 +19,18 @@ import { IPersonRepository, UpdateFacesData } from './person.repository'; @Injectable() export class PersonService { + private access: AccessCore; private configCore: SystemConfigCore; readonly logger = new Logger(PersonService.name); constructor( + @Inject(IAccessRepository) private accessRepository: IAccessRepository, @Inject(IPersonRepository) private repository: IPersonRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, ) { + this.access = new AccessCore(accessRepository); this.configCore = new SystemConfigCore(configRepository); } @@ -48,12 +52,14 @@ export class PersonService { }; } - getById(authUser: AuthUserDto, id: string): Promise { - return this.findOrFail(authUser, id).then(mapPerson); + async getById(authUser: AuthUserDto, id: string): Promise { + await this.access.requirePermission(authUser, Permission.PERSON_READ, id); + return this.findOrFail(id).then(mapPerson); } async getThumbnail(authUser: AuthUserDto, id: string): Promise { - const person = await this.repository.getById(authUser.id, id); + await this.access.requirePermission(authUser, Permission.PERSON_READ, id); + const person = await this.repository.getById(id); if (!person || !person.thumbnailPath) { throw new NotFoundException(); } @@ -62,17 +68,19 @@ export class PersonService { } async getAssets(authUser: AuthUserDto, id: string): Promise { - const assets = await this.repository.getAssets(authUser.id, id); + await this.access.requirePermission(authUser, Permission.PERSON_READ, id); + const assets = await this.repository.getAssets(id); return assets.map(mapAsset); } async update(authUser: AuthUserDto, id: string, dto: PersonUpdateDto): Promise { - let person = await this.findOrFail(authUser, id); + await this.access.requirePermission(authUser, Permission.PERSON_WRITE, id); + let person = await this.findOrFail(id); if (dto.name !== undefined || dto.birthDate !== undefined || dto.isHidden !== undefined) { person = await this.repository.update({ id, name: dto.name, birthDate: dto.birthDate, isHidden: dto.isHidden }); if (this.needsSearchIndexUpdate(dto)) { - const assets = await this.repository.getAssets(authUser.id, id); + const assets = await this.repository.getAssets(id); const ids = assets.map((asset) => asset.id); await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } }); } @@ -141,14 +149,22 @@ export class PersonService { async mergePerson(authUser: AuthUserDto, id: string, dto: MergePersonDto): Promise { const mergeIds = dto.ids; - const primaryPerson = await this.findOrFail(authUser, id); + await this.access.requirePermission(authUser, Permission.PERSON_WRITE, id); + const primaryPerson = await this.findOrFail(id); const primaryName = primaryPerson.name || primaryPerson.id; const results: BulkIdResponseDto[] = []; for (const mergeId of mergeIds) { + const hasPermission = await this.access.hasPermission(authUser, Permission.PERSON_MERGE, mergeId); + + if (!hasPermission) { + results.push({ id: mergeId, success: false, error: BulkIdErrorReason.NO_PERMISSION }); + continue; + } + try { - const mergePerson = await this.repository.getById(authUser.id, mergeId); + const mergePerson = await this.repository.getById(mergeId); if (!mergePerson) { results.push({ id: mergeId, success: false, error: BulkIdErrorReason.NOT_FOUND }); continue; @@ -188,8 +204,8 @@ export class PersonService { return dto.name !== undefined || dto.isHidden !== undefined; } - private async findOrFail(authUser: AuthUserDto, id: string) { - const person = await this.repository.getById(authUser.id, id); + private async findOrFail(id: string) { + const person = await this.repository.getById(id); if (!person) { throw new BadRequestException('Person not found'); } diff --git a/server/src/infra/repositories/access.repository.ts b/server/src/infra/repositories/access.repository.ts index fe518807e6..91706cb2c0 100644 --- a/server/src/infra/repositories/access.repository.ts +++ b/server/src/infra/repositories/access.repository.ts @@ -1,13 +1,14 @@ import { IAccessRepository } from '@app/domain'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { AlbumEntity, AssetEntity, PartnerEntity, SharedLinkEntity } from '../entities'; +import { AlbumEntity, AssetEntity, PartnerEntity, PersonEntity, SharedLinkEntity } from '../entities'; export class AccessRepository implements IAccessRepository { constructor( @InjectRepository(AssetEntity) private assetRepository: Repository, @InjectRepository(AlbumEntity) private albumRepository: Repository, @InjectRepository(PartnerEntity) private partnerRepository: Repository, + @InjectRepository(PersonEntity) private personRepository: Repository, @InjectRepository(SharedLinkEntity) private sharedLinkRepository: Repository, ) {} @@ -156,4 +157,15 @@ export class AccessRepository implements IAccessRepository { }); }, }; + + person = { + hasOwnerAccess: (userId: string, personId: string): Promise => { + return this.personRepository.exist({ + where: { + id: personId, + ownerId: userId, + }, + }); + }, + }; } diff --git a/server/src/infra/repositories/person.repository.ts b/server/src/infra/repositories/person.repository.ts index edfc1357f4..505729f427 100644 --- a/server/src/infra/repositories/person.repository.ts +++ b/server/src/infra/repositories/person.repository.ts @@ -86,14 +86,13 @@ export class PersonRepository implements IPersonRepository { .getMany(); } - getById(ownerId: string, personId: string): Promise { - return this.personRepository.findOne({ where: { id: personId, ownerId } }); + getById(personId: string): Promise { + return this.personRepository.findOne({ where: { id: personId } }); } - getAssets(ownerId: string, personId: string): Promise { + getAssets(personId: string): Promise { return this.assetRepository.find({ where: { - ownerId, faces: { personId, }, diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index ad2f68ca50..eaf8371cb1 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -4,6 +4,7 @@ export interface IAccessRepositoryMock { asset: jest.Mocked; album: jest.Mocked; library: jest.Mocked; + person: jest.Mocked; } export const newAccessRepositoryMock = (): IAccessRepositoryMock => { @@ -24,5 +25,9 @@ export const newAccessRepositoryMock = (): IAccessRepositoryMock => { library: { hasPartnerAccess: jest.fn(), }, + + person: { + hasOwnerAccess: jest.fn(), + }, }; };