mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-05 11:05:53 +00:00
refactor: create album users (#9315)
This commit is contained in:
parent
e9f99673b9
commit
e79d1b1ec2
18 changed files with 90 additions and 50 deletions
|
|
@ -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) },
|
||||
|
|
|
|||
|
|
@ -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}`)
|
||||
|
|
|
|||
3
mobile/openapi/.openapi-generator/FILES
generated
3
mobile/openapi/.openapi-generator/FILES
generated
|
|
@ -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
BIN
mobile/openapi/README.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/AlbumUserCreateDto.md
generated
Normal file
BIN
mobile/openapi/doc/AlbumUserCreateDto.md
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/doc/CreateAlbumDto.md
generated
BIN
mobile/openapi/doc/CreateAlbumDto.md
generated
Binary file not shown.
BIN
mobile/openapi/lib/api.dart
generated
BIN
mobile/openapi/lib/api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api_client.dart
generated
BIN
mobile/openapi/lib/api_client.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/album_user_create_dto.dart
generated
Normal file
BIN
mobile/openapi/lib/model/album_user_create_dto.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/lib/model/create_album_dto.dart
generated
BIN
mobile/openapi/lib/model/create_album_dto.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/album_user_create_dto_test.dart
generated
Normal file
BIN
mobile/openapi/test/album_user_create_dto_test.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/test/create_album_dto_test.dart
generated
BIN
mobile/openapi/test/create_album_dto_test.dart
generated
Binary file not shown.
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue