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
+8 -1
View File
@@ -5894,6 +5894,9 @@
"id": {
"type": "string"
},
"isActivityEnabled": {
"type": "boolean"
},
"lastModifiedAssetTimestamp": {
"format": "date-time",
"type": "string"
@@ -5935,7 +5938,8 @@
"sharedUsers",
"hasSharedLink",
"assets",
"owner"
"owner",
"isActivityEnabled"
],
"type": "object"
},
@@ -8910,6 +8914,9 @@
},
"description": {
"type": "string"
},
"isActivityEnabled": {
"type": "boolean"
}
},
"type": "object"
+1 -4
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:
+18 -2
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, {
@@ -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,
};
};
+1 -1
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] } });
@@ -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;
}
@@ -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>;
@@ -56,4 +56,7 @@ export class AlbumEntity {
@OneToMany(() => SharedLinkEntity, (link) => link.album)
sharedLinks!: SharedLinkEntity[];
@Column({ default: true })
isActivityEnabled!: boolean;
}
@@ -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"`);
}
}
@@ -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> => {
+1
View File
@@ -226,6 +226,7 @@ describe(`${AlbumController.name} (e2e)`, () => {
assets: [],
assetCount: 0,
owner: expect.objectContaining({ email: user1.userEmail }),
isActivityEnabled: true,
});
});
});
+10
View File
@@ -18,6 +18,7 @@ export const albumStub = {
deletedAt: null,
sharedLinks: [],
sharedUsers: [],
isActivityEnabled: true,
}),
sharedWithUser: Object.freeze<AlbumEntity>({
id: 'album-2',
@@ -33,6 +34,7 @@ export const albumStub = {
deletedAt: null,
sharedLinks: [],
sharedUsers: [userStub.user1],
isActivityEnabled: true,
}),
sharedWithMultiple: Object.freeze<AlbumEntity>({
id: 'album-3',
@@ -48,6 +50,7 @@ export const albumStub = {
deletedAt: null,
sharedLinks: [],
sharedUsers: [userStub.user1, userStub.user2],
isActivityEnabled: true,
}),
sharedWithAdmin: Object.freeze<AlbumEntity>({
id: 'album-3',
@@ -63,6 +66,7 @@ export const albumStub = {
deletedAt: null,
sharedLinks: [],
sharedUsers: [userStub.admin],
isActivityEnabled: true,
}),
oneAsset: Object.freeze<AlbumEntity>({
id: 'album-4',
@@ -78,6 +82,7 @@ export const albumStub = {
deletedAt: null,
sharedLinks: [],
sharedUsers: [],
isActivityEnabled: true,
}),
twoAssets: Object.freeze<AlbumEntity>({
id: 'album-4a',
@@ -93,6 +98,7 @@ export const albumStub = {
deletedAt: null,
sharedLinks: [],
sharedUsers: [],
isActivityEnabled: true,
}),
emptyWithInvalidThumbnail: Object.freeze<AlbumEntity>({
id: 'album-5',
@@ -108,6 +114,7 @@ export const albumStub = {
deletedAt: null,
sharedLinks: [],
sharedUsers: [],
isActivityEnabled: true,
}),
emptyWithValidThumbnail: Object.freeze<AlbumEntity>({
id: 'album-5',
@@ -123,6 +130,7 @@ export const albumStub = {
deletedAt: null,
sharedLinks: [],
sharedUsers: [],
isActivityEnabled: true,
}),
oneAssetInvalidThumbnail: Object.freeze<AlbumEntity>({
id: 'album-6',
@@ -138,6 +146,7 @@ export const albumStub = {
deletedAt: null,
sharedLinks: [],
sharedUsers: [],
isActivityEnabled: true,
}),
oneAssetValidThumbnail: Object.freeze<AlbumEntity>({
id: 'album-6',
@@ -153,5 +162,6 @@ export const albumStub = {
deletedAt: null,
sharedLinks: [],
sharedUsers: [],
isActivityEnabled: true,
}),
};
+2
View File
@@ -100,6 +100,7 @@ const albumResponse: AlbumResponseDto = {
hasSharedLink: false,
assets: [],
assetCount: 1,
isActivityEnabled: true,
};
export const sharedLinkStub = {
@@ -179,6 +180,7 @@ export const sharedLinkStub = {
albumThumbnailAssetId: null,
sharedUsers: [],
sharedLinks: [],
isActivityEnabled: true,
assets: [
{
id: 'id_1',
@@ -19,6 +19,7 @@ export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock =>
activity: {
hasOwnerAccess: jest.fn(),
hasAlbumOwnerAccess: jest.fn(),
hasCreateAccess: jest.fn(),
},
asset: {
hasOwnerAccess: jest.fn(),