feat(server, web): Album's options (#4870)

* feat: disable activity

* fix: disable reactions

* fix: tests

* fix: tests

* fix: tests

* pr feedback

* pr feedback

* chore: styling & wording

* refactor component

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
martin
2023-11-07 05:37:21 +01:00
committed by GitHub
parent ace0a5911c
commit 9d01885b58
29 changed files with 293 additions and 24 deletions

View File

@@ -138,10 +138,7 @@ export class AccessCore {
switch (permission) {
// uses album id
case Permission.ACTIVITY_CREATE:
return (
(await this.repository.album.hasOwnerAccess(authUser.id, id)) ||
(await this.repository.album.hasSharedAlbumAccess(authUser.id, id))
);
return await this.repository.activity.hasCreateAccess(authUser.id, id);
// uses activity id
case Permission.ACTIVITY_DELETE:

View File

@@ -94,7 +94,7 @@ describe(ActivityService.name, () => {
});
it('should create a comment', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true);
accessMock.activity.hasCreateAccess.mockResolvedValue(true);
activityMock.create.mockResolvedValue(activityStub.oneComment);
await sut.create(authStub.admin, {
@@ -113,8 +113,23 @@ describe(ActivityService.name, () => {
});
});
it('should create a like', async () => {
it('should fail because activity is disabled for the album', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true);
accessMock.activity.hasCreateAccess.mockResolvedValue(false);
activityMock.create.mockResolvedValue(activityStub.oneComment);
await expect(
sut.create(authStub.admin, {
albumId: 'album-id',
assetId: 'asset-id',
type: ReactionType.COMMENT,
comment: 'comment',
}),
).rejects.toBeInstanceOf(BadRequestException);
});
it('should create a like', async () => {
accessMock.activity.hasCreateAccess.mockResolvedValue(true);
activityMock.create.mockResolvedValue(activityStub.liked);
activityMock.search.mockResolvedValue([]);
@@ -134,6 +149,7 @@ describe(ActivityService.name, () => {
it('should skip if like exists', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true);
accessMock.activity.hasCreateAccess.mockResolvedValue(true);
activityMock.search.mockResolvedValue([activityStub.liked]);
await sut.create(authStub.admin, {

View File

@@ -21,6 +21,7 @@ export class AlbumResponseDto {
lastModifiedAssetTimestamp?: Date;
startDate?: Date;
endDate?: Date;
isActivityEnabled!: boolean;
}
export const mapAlbum = (entity: AlbumEntity, withAssets: boolean): AlbumResponseDto => {
@@ -61,6 +62,7 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean): AlbumRespons
endDate,
assets: (withAssets ? assets : []).map((asset) => mapAsset(asset)),
assetCount: entity.assets?.length || 0,
isActivityEnabled: entity.isActivityEnabled,
};
};

View File

@@ -125,12 +125,12 @@ export class AlbumService {
throw new BadRequestException('Invalid album thumbnail');
}
}
const updatedAlbum = await this.albumRepository.update({
id: album.id,
albumName: dto.albumName,
description: dto.description,
albumThumbnailAssetId: dto.albumThumbnailAssetId,
isActivityEnabled: dto.isActivityEnabled,
});
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [updatedAlbum.id] } });

View File

@@ -1,4 +1,4 @@
import { IsString } from 'class-validator';
import { IsBoolean, IsString } from 'class-validator';
import { Optional, ValidateUUID } from '../../domain.util';
export class UpdateAlbumDto {
@@ -12,4 +12,8 @@ export class UpdateAlbumDto {
@ValidateUUID({ optional: true })
albumThumbnailAssetId?: string;
@Optional()
@IsBoolean()
isActivityEnabled?: boolean;
}

View File

@@ -2,8 +2,9 @@ export const IAccessRepository = 'IAccessRepository';
export interface IAccessRepository {
activity: {
hasOwnerAccess(userId: string, albumId: string): Promise<boolean>;
hasAlbumOwnerAccess(userId: string, albumId: string): Promise<boolean>;
hasOwnerAccess(userId: string, activityId: string): Promise<boolean>;
hasAlbumOwnerAccess(userId: string, activityId: string): Promise<boolean>;
hasCreateAccess(userId: string, albumId: string): Promise<boolean>;
};
asset: {
hasOwnerAccess(userId: string, assetId: string): Promise<boolean>;

View File

@@ -56,4 +56,7 @@ export class AlbumEntity {
@OneToMany(() => SharedLinkEntity, (link) => link.album)
sharedLinks!: SharedLinkEntity[];
@Column({ default: true })
isActivityEnabled!: boolean;
}

View File

@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class DisableActivity1699268680508 implements MigrationInterface {
name = 'DisableActivity1699268680508'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "albums" ADD "isActivityEnabled" boolean NOT NULL DEFAULT true`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "albums" DROP COLUMN "isActivityEnabled"`);
}
}

View File

@@ -43,6 +43,24 @@ export class AccessRepository implements IAccessRepository {
},
});
},
hasCreateAccess: (userId: string, albumId: string): Promise<boolean> => {
return this.albumRepository.exist({
where: [
{
id: albumId,
isActivityEnabled: true,
sharedUsers: {
id: userId,
},
},
{
id: albumId,
isActivityEnabled: true,
ownerId: userId,
},
],
});
},
};
library = {
hasOwnerAccess: (userId: string, libraryId: string): Promise<boolean> => {