From 06028a283bad4e751a33a9fee60ebb365d4891b9 Mon Sep 17 00:00:00 2001 From: mgabor <> Date: Fri, 12 Apr 2024 13:48:55 +0200 Subject: [PATCH] expose albumPermissions on the API, deprecate sharedUsers --- server/src/dtos/album.dto.ts | 21 ++++++++++++++---- server/src/entities/album.entity.ts | 7 ------ server/src/repositories/album.repository.ts | 24 ++++++++++----------- server/src/services/album.service.ts | 15 +++++++------ 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index 3f7af0f538..6205190505 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -83,6 +83,11 @@ export class AlbumCountResponseDto { notShared!: number; } +export class AlbumPermissionResponseDto { + user!: UserResponseDto; + readonly!: boolean; +} + export class AlbumResponseDto { id!: string; ownerId!: string; @@ -92,7 +97,9 @@ export class AlbumResponseDto { updatedAt!: Date; albumThumbnailAssetId!: string | null; shared!: boolean; + @ApiProperty({ deprecated: true, description: 'Deprecated in favor of albumPermissions' }) sharedUsers!: UserResponseDto[]; + albumPermissions!: AlbumPermissionResponseDto[]; hasSharedLink!: boolean; assets!: AssetResponseDto[]; owner!: UserResponseDto; @@ -109,10 +116,15 @@ export class AlbumResponseDto { export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDto): AlbumResponseDto => { const sharedUsers: UserResponseDto[] = []; - - if (entity.sharedUsers) { - for (const user of entity.sharedUsers) { - sharedUsers.push(mapUser(user)); + const albumPermissions: AlbumPermissionResponseDto[] = []; + + if (entity.albumPermissions) { + for (const permission of entity.albumPermissions) { + sharedUsers.push(mapUser(permission.users)); + albumPermissions.push({ + user: mapUser(permission.users), + readonly: permission.readonly, + }); } } @@ -138,6 +150,7 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDt ownerId: entity.ownerId, owner: mapUser(entity.owner), sharedUsers, + albumPermissions, shared: hasSharedUser || hasSharedLink, hasSharedLink, startDate, diff --git a/server/src/entities/album.entity.ts b/server/src/entities/album.entity.ts index 35abce574f..17a89c2a58 100644 --- a/server/src/entities/album.entity.ts +++ b/server/src/entities/album.entity.ts @@ -53,13 +53,6 @@ export class AlbumEntity { @Column({ comment: 'Asset ID to be used as thumbnail', nullable: true }) albumThumbnailAssetId!: string | null; - @ManyToMany(() => UserEntity) - @JoinTable({ - name: 'albums_shared_users_users', - synchronize: false, // Table is managed by AlbumPermissionEntity - }) - sharedUsers!: UserEntity[]; - @OneToMany(() => AlbumPermissionEntity, (permission) => permission.albums) albumPermissions!: AlbumPermissionEntity[]; diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts index bbaab2a12b..d7335c548e 100644 --- a/server/src/repositories/album.repository.ts +++ b/server/src/repositories/album.repository.ts @@ -8,7 +8,7 @@ import { AssetEntity } from 'src/entities/asset.entity'; import { AlbumAsset, AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface'; import { Instrumentation } from 'src/utils/instrumentation'; import { setUnion } from 'src/utils/set'; -import { DataSource, FindOptionsOrder, FindOptionsRelations, In, IsNull, Not, Repository } from 'typeorm'; +import { DataSource, Equal, FindOptionsOrder, FindOptionsRelations, In, IsNull, Not, Repository } from 'typeorm'; @Instrumentation() @Injectable() @@ -23,7 +23,7 @@ export class AlbumRepository implements IAlbumRepository { getById(id: string, options: AlbumInfoOptions): Promise { const relations: FindOptionsRelations = { owner: true, - sharedUsers: true, + albumPermissions: { users: true }, assets: false, sharedLinks: true, }; @@ -52,7 +52,7 @@ export class AlbumRepository implements IAlbumRepository { }, relations: { owner: true, - sharedUsers: true, + albumPermissions: { users: true }, }, }); } @@ -62,9 +62,9 @@ export class AlbumRepository implements IAlbumRepository { return this.repository.find({ where: [ { ownerId, assets: { id: assetId } }, - { sharedUsers: { id: ownerId }, assets: { id: assetId } }, + { albumPermissions: { users: Equal(ownerId) }, assets: { id: assetId } }, ], - relations: { owner: true, sharedUsers: true }, + relations: { owner: true, albumPermissions: { users: true } }, order: { createdAt: 'DESC' }, }); } @@ -129,7 +129,7 @@ export class AlbumRepository implements IAlbumRepository { @GenerateSql({ params: [DummyValue.UUID] }) getOwned(ownerId: string): Promise { return this.repository.find({ - relations: { sharedUsers: true, sharedLinks: true, owner: true }, + relations: { albumPermissions: { users: true }, sharedLinks: true, owner: true }, where: { ownerId }, order: { createdAt: 'DESC' }, }); @@ -141,11 +141,11 @@ export class AlbumRepository implements IAlbumRepository { @GenerateSql({ params: [DummyValue.UUID] }) getShared(ownerId: string): Promise { return this.repository.find({ - relations: { sharedUsers: true, sharedLinks: true, owner: true }, + relations: { albumPermissions: { users: true }, sharedLinks: true, owner: true }, where: [ - { sharedUsers: { id: ownerId } }, + { albumPermissions: { users: Equal(ownerId) } }, { sharedLinks: { userId: ownerId } }, - { ownerId, sharedUsers: { id: Not(IsNull()) } }, + { ownerId, albumPermissions: { users: Not(IsNull()) } }, ], order: { createdAt: 'DESC' }, }); @@ -157,8 +157,8 @@ export class AlbumRepository implements IAlbumRepository { @GenerateSql({ params: [DummyValue.UUID] }) getNotShared(ownerId: string): Promise { return this.repository.find({ - relations: { sharedUsers: true, sharedLinks: true, owner: true }, - where: { ownerId, sharedUsers: { id: IsNull() }, sharedLinks: { id: IsNull() } }, + relations: { albumPermissions: true, sharedLinks: true, owner: true }, + where: { ownerId, albumPermissions: { users: IsNull() }, sharedLinks: { id: IsNull() } }, order: { createdAt: 'DESC' }, }); } @@ -282,7 +282,7 @@ export class AlbumRepository implements IAlbumRepository { where: { id }, relations: { owner: true, - sharedUsers: true, + albumPermissions: { users: true }, sharedLinks: true, assets: true, }, diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index a6943193ed..c5f4215a73 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -14,9 +14,9 @@ import { } from 'src/dtos/album.dto'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; +import { AlbumPermissionEntity } from 'src/entities/album-permission.entity'; import { AlbumEntity } from 'src/entities/album.entity'; import { AssetEntity } from 'src/entities/asset.entity'; -import { UserEntity } from 'src/entities/user.entity'; import { IAccessRepository } from 'src/interfaces/access.interface'; import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; @@ -123,7 +123,8 @@ export class AlbumService { ownerId: auth.user.id, albumName: dto.albumName, description: dto.description, - sharedUsers: dto.sharedWithUserIds?.map((value) => ({ id: value }) as UserEntity) ?? [], + albumPermissions: + dto.sharedWithUserIds?.map((userId) => ({ users: { id: userId } }) as AlbumPermissionEntity) ?? [], assets: (dto.assetIds || []).map((id) => ({ id }) as AssetEntity), albumThumbnailAssetId: dto.assetIds?.[0] || null, }); @@ -216,7 +217,7 @@ export class AlbumService { throw new BadRequestException('Cannot be shared with owner'); } - const exists = album.sharedUsers.find((user) => user.id === userId); + const exists = album.albumPermissions.find(({ users: { id } }) => id === userId); if (exists) { throw new BadRequestException('User already added'); } @@ -226,14 +227,14 @@ export class AlbumService { throw new BadRequestException('User not found'); } - album.sharedUsers.push({ id: userId } as UserEntity); + album.albumPermissions.push({ users: { id: userId } } as AlbumPermissionEntity); } return this.albumRepository .update({ id: album.id, updatedAt: new Date(), - sharedUsers: album.sharedUsers, + albumPermissions: album.albumPermissions, }) .then(mapAlbumWithoutAssets); } @@ -249,7 +250,7 @@ export class AlbumService { throw new BadRequestException('Cannot remove album owner'); } - const exists = album.sharedUsers.find((user) => user.id === userId); + const exists = album.albumPermissions.find(({ users: { id } }) => id === userId); if (!exists) { throw new BadRequestException('Album not shared with user'); } @@ -262,7 +263,7 @@ export class AlbumService { await this.albumRepository.update({ id: album.id, updatedAt: new Date(), - sharedUsers: album.sharedUsers.filter((user) => user.id !== userId), + albumPermissions: album.albumPermissions.filter(({ users: { id } }) => id !== userId), }); }