refactor: create album users (#9315)

This commit is contained in:
Jason Rasmussen 2024-05-07 16:38:09 -04:00 committed by GitHub
parent e9f99673b9
commit e79d1b1ec2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 90 additions and 50 deletions

View file

@ -1,6 +1,7 @@
import {
ActivityCreateDto,
AlbumResponseDto,
AlbumUserRole,
AssetFileUploadResponseDto,
LoginResponseDto,
ReactionType,
@ -33,7 +34,7 @@ describe('/activity', () => {
createAlbumDto: {
albumName: 'Album 1',
assetIds: [asset.id],
sharedWithUserIds: [nonOwner.userId],
albumUsers: [{ userId: nonOwner.userId, role: AlbumUserRole.Editor }],
},
},
{ headers: asBearerAuth(admin.accessToken) },

View file

@ -49,72 +49,50 @@ describe('/album', () => {
utils.createAsset(user1.accessToken),
]);
const albums = await Promise.all([
// user 1
/* 0 */
user1Albums = await Promise.all([
utils.createAlbum(user1.accessToken, {
albumName: user1SharedEditorUser,
sharedWithUserIds: [user2.userId],
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }],
assetIds: [user1Asset1.id],
}),
/* 1 */
utils.createAlbum(user1.accessToken, {
albumName: user1SharedLink,
assetIds: [user1Asset1.id],
}),
/* 2 */
utils.createAlbum(user1.accessToken, {
albumName: user1NotShared,
assetIds: [user1Asset1.id, user1Asset2.id],
}),
// user 2
/* 3 */
utils.createAlbum(user2.accessToken, {
albumName: user2SharedUser,
sharedWithUserIds: [user1.userId, user3.userId],
}),
/* 4 */
utils.createAlbum(user2.accessToken, { albumName: user2SharedLink }),
/* 5 */
utils.createAlbum(user2.accessToken, { albumName: user2NotShared }),
// user 3
/* 6 */
utils.createAlbum(user3.accessToken, {
albumName: 'Deleted',
sharedWithUserIds: [user1.userId],
}),
// user1 shared with an editor
/* 7 */
utils.createAlbum(user1.accessToken, {
albumName: user1SharedViewerUser,
sharedWithUserIds: [user2.userId],
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
assetIds: [user1Asset1.id],
}),
]);
// Make viewer
await utils.updateAlbumUser(user1.accessToken, {
id: albums[7].id,
userId: user2.userId,
updateAlbumUserDto: { role: AlbumUserRole.Viewer },
user2Albums = await Promise.all([
utils.createAlbum(user2.accessToken, {
albumName: user2SharedUser,
albumUsers: [
{ userId: user1.userId, role: AlbumUserRole.Editor },
{ userId: user3.userId, role: AlbumUserRole.Editor },
],
}),
utils.createAlbum(user2.accessToken, { albumName: user2SharedLink }),
utils.createAlbum(user2.accessToken, { albumName: user2NotShared }),
]);
await utils.createAlbum(user3.accessToken, {
albumName: 'Deleted',
albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }],
});
albums[0].albumUsers[0].role = AlbumUserRole.Editor;
albums[3].albumUsers[0].role = AlbumUserRole.Editor;
albums[6].albumUsers[0].role = AlbumUserRole.Editor;
await addAssetsToAlbum(
{ id: albums[3].id, bulkIdsDto: { ids: [user1Asset1.id] } },
{ id: user2Albums[0].id, bulkIdsDto: { ids: [user1Asset1.id] } },
{ headers: asBearerAuth(user1.accessToken) },
);
albums[3] = await getAlbumInfo({ id: albums[3].id }, { headers: asBearerAuth(user2.accessToken) });
user1Albums = [...albums.slice(0, 3), albums[7]];
user2Albums = albums.slice(3, 6);
user2Albums[0] = await getAlbumInfo({ id: user2Albums[0].id }, { headers: asBearerAuth(user2.accessToken) });
await Promise.all([
// add shared link to user1SharedLink album
@ -641,9 +619,11 @@ describe('/album', () => {
it('should allow the album owner to change the role of a shared user', async () => {
const album = await utils.createAlbum(user1.accessToken, {
albumName: 'testAlbum',
sharedWithUserIds: [user2.userId],
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
});
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
const { status } = await request(app)
.put(`/album/${album.id}/user/${user2.userId}`)
.set('Authorization', `Bearer ${user1.accessToken}`)
@ -663,9 +643,11 @@ describe('/album', () => {
it('should not allow a shared user to change the role of another shared user', async () => {
const album = await utils.createAlbum(user1.accessToken, {
albumName: 'testAlbum',
sharedWithUserIds: [user2.userId],
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
});
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
const { status, body } = await request(app)
.put(`/album/${album.id}/user/${user2.userId}`)
.set('Authorization', `Bearer ${user2.accessToken}`)

View file

@ -18,6 +18,7 @@ doc/AlbumApi.md
doc/AlbumCountResponseDto.md
doc/AlbumResponseDto.md
doc/AlbumUserAddDto.md
doc/AlbumUserCreateDto.md
doc/AlbumUserResponseDto.md
doc/AlbumUserRole.md
doc/AllJobStatusResponseDto.md
@ -257,6 +258,7 @@ lib/model/admin_onboarding_update_dto.dart
lib/model/album_count_response_dto.dart
lib/model/album_response_dto.dart
lib/model/album_user_add_dto.dart
lib/model/album_user_create_dto.dart
lib/model/album_user_response_dto.dart
lib/model/album_user_role.dart
lib/model/all_job_status_response_dto.dart
@ -444,6 +446,7 @@ test/album_api_test.dart
test/album_count_response_dto_test.dart
test/album_response_dto_test.dart
test/album_user_add_dto_test.dart
test/album_user_create_dto_test.dart
test/album_user_response_dto_test.dart
test/album_user_role_test.dart
test/all_job_status_response_dto_test.dart

BIN
mobile/openapi/README.md generated

Binary file not shown.

BIN
mobile/openapi/doc/AlbumUserCreateDto.md generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -6779,6 +6779,22 @@
],
"type": "object"
},
"AlbumUserCreateDto": {
"properties": {
"role": {
"$ref": "#/components/schemas/AlbumUserRole"
},
"userId": {
"format": "uuid",
"type": "string"
}
},
"required": [
"role",
"userId"
],
"type": "object"
},
"AlbumUserResponseDto": {
"properties": {
"role": {
@ -7599,6 +7615,13 @@
"albumName": {
"type": "string"
},
"albumUsers": {
"description": "This property was added in v1.104.0",
"items": {
"$ref": "#/components/schemas/AlbumUserCreateDto"
},
"type": "array"
},
"assetIds": {
"items": {
"format": "uuid",
@ -7610,6 +7633,8 @@
"type": "string"
},
"sharedWithUserIds": {
"deprecated": true,
"description": "This property was deprecated in v1.104.0",
"items": {
"format": "uuid",
"type": "string"

View file

@ -169,10 +169,17 @@ export type AlbumResponseDto = {
startDate?: string;
updatedAt: string;
};
export type AlbumUserCreateDto = {
role: AlbumUserRole;
userId: string;
};
export type CreateAlbumDto = {
albumName: string;
/** This property was added in v1.104.0 */
albumUsers?: AlbumUserCreateDto[];
assetIds?: string[];
description?: string;
/** This property was deprecated in v1.104.0 */
sharedWithUserIds?: string[];
};
export type AlbumCountResponseDto = {

View file

@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { ArrayNotEmpty, IsEnum, IsString } from 'class-validator';
import { Type } from 'class-transformer';
import { ArrayNotEmpty, IsArray, IsEnum, IsString, ValidateNested } from 'class-validator';
import _ from 'lodash';
import { PropertyLifecycle } from 'src/decorators';
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
@ -32,6 +33,14 @@ export class AddUsersDto {
albumUsers!: AlbumUserAddDto[];
}
class AlbumUserCreateDto {
@ValidateUUID()
userId!: string;
@IsEnum(AlbumUserRole)
role!: AlbumUserRole;
}
export class CreateAlbumDto {
@IsString()
@ApiProperty()
@ -41,7 +50,15 @@ export class CreateAlbumDto {
@Optional()
description?: string;
@Optional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => AlbumUserCreateDto)
@PropertyLifecycle({ addedAt: 'v1.104.0' })
albumUsers?: AlbumUserCreateDto[];
@ValidateUUID({ optional: true, each: true })
@PropertyLifecycle({ deprecatedAt: 'v1.104.0' })
sharedWithUserIds?: string[];
@ValidateUUID({ optional: true, each: true })

View file

@ -281,11 +281,11 @@ export class AlbumRepository implements IAlbumRepository {
.execute();
}
async create(album: Partial<AlbumEntity>): Promise<AlbumEntity> {
create(album: Partial<AlbumEntity>): Promise<AlbumEntity> {
return this.save(album);
}
async update(album: Partial<AlbumEntity>): Promise<AlbumEntity> {
update(album: Partial<AlbumEntity>): Promise<AlbumEntity> {
return this.save(album);
}

View file

@ -194,7 +194,7 @@ describe(AlbumService.name, () => {
ownerId: authStub.admin.user.id,
albumName: albumStub.empty.albumName,
description: albumStub.empty.description,
albumUsers: [{ user: { id: 'user-id' } }],
albumUsers: [{ userId: 'user-id', role: AlbumUserRole.EDITOR }],
assets: [{ id: '123' }],
albumThumbnailAssetId: '123',
});

View file

@ -114,7 +114,12 @@ export class AlbumService {
}
async create(auth: AuthDto, dto: CreateAlbumDto): Promise<AlbumResponseDto> {
const albumUsers = dto.albumUsers || [];
for (const userId of dto.sharedWithUserIds || []) {
albumUsers.push({ userId, role: AlbumUserRole.EDITOR });
}
for (const { userId } of albumUsers) {
const exists = await this.userRepository.get(userId, {});
if (!exists) {
throw new BadRequestException('User not found');
@ -128,7 +133,7 @@ export class AlbumService {
ownerId: auth.user.id,
albumName: dto.albumName,
description: dto.description,
albumUsers: dto.sharedWithUserIds?.map((userId) => ({ user: { id: userId } }) as AlbumUserEntity) ?? [],
albumUsers: albumUsers.map((albumUser) => albumUser as AlbumUserEntity) ?? [],
assets,
albumThumbnailAssetId: assets[0]?.id || null,
});