mirror of
https://github.com/immich-app/immich.git
synced 2025-12-20 17:25:35 +03:00
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:
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
14
server/src/infra/migrations/1699268680508-DisableActivity.ts
Normal file
14
server/src/infra/migrations/1699268680508-DisableActivity.ts
Normal 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"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> => {
|
||||
|
||||
Reference in New Issue
Block a user