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

View file

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

View file

@ -18,6 +18,7 @@ doc/AlbumApi.md
doc/AlbumCountResponseDto.md doc/AlbumCountResponseDto.md
doc/AlbumResponseDto.md doc/AlbumResponseDto.md
doc/AlbumUserAddDto.md doc/AlbumUserAddDto.md
doc/AlbumUserCreateDto.md
doc/AlbumUserResponseDto.md doc/AlbumUserResponseDto.md
doc/AlbumUserRole.md doc/AlbumUserRole.md
doc/AllJobStatusResponseDto.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_count_response_dto.dart
lib/model/album_response_dto.dart lib/model/album_response_dto.dart
lib/model/album_user_add_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_response_dto.dart
lib/model/album_user_role.dart lib/model/album_user_role.dart
lib/model/all_job_status_response_dto.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_count_response_dto_test.dart
test/album_response_dto_test.dart test/album_response_dto_test.dart
test/album_user_add_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_response_dto_test.dart
test/album_user_role_test.dart test/album_user_role_test.dart
test/all_job_status_response_dto_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" "type": "object"
}, },
"AlbumUserCreateDto": {
"properties": {
"role": {
"$ref": "#/components/schemas/AlbumUserRole"
},
"userId": {
"format": "uuid",
"type": "string"
}
},
"required": [
"role",
"userId"
],
"type": "object"
},
"AlbumUserResponseDto": { "AlbumUserResponseDto": {
"properties": { "properties": {
"role": { "role": {
@ -7599,6 +7615,13 @@
"albumName": { "albumName": {
"type": "string" "type": "string"
}, },
"albumUsers": {
"description": "This property was added in v1.104.0",
"items": {
"$ref": "#/components/schemas/AlbumUserCreateDto"
},
"type": "array"
},
"assetIds": { "assetIds": {
"items": { "items": {
"format": "uuid", "format": "uuid",
@ -7610,6 +7633,8 @@
"type": "string" "type": "string"
}, },
"sharedWithUserIds": { "sharedWithUserIds": {
"deprecated": true,
"description": "This property was deprecated in v1.104.0",
"items": { "items": {
"format": "uuid", "format": "uuid",
"type": "string" "type": "string"

View file

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

View file

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

View file

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

View file

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

View file

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