diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index d177c0d6a1..4888e1840f 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -12,6 +12,7 @@ doc/ActivityApi.md doc/ActivityCreateDto.md doc/ActivityResponseDto.md doc/ActivityStatisticsResponseDto.md +doc/AddUserDto.md doc/AddUsersDto.md doc/AdminOnboardingUpdateDto.md doc/AlbumApi.md @@ -248,6 +249,7 @@ lib/auth/oauth.dart lib/model/activity_create_dto.dart lib/model/activity_response_dto.dart lib/model/activity_statistics_response_dto.dart +lib/model/add_user_dto.dart lib/model/add_users_dto.dart lib/model/admin_onboarding_update_dto.dart lib/model/album_count_response_dto.dart @@ -430,6 +432,7 @@ test/activity_api_test.dart test/activity_create_dto_test.dart test/activity_response_dto_test.dart test/activity_statistics_response_dto_test.dart +test/add_user_dto_test.dart test/add_users_dto_test.dart test/admin_onboarding_update_dto_test.dart test/album_api_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 44eb3b8829..a88775f5d4 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -235,6 +235,7 @@ Class | Method | HTTP request | Description - [ActivityCreateDto](doc//ActivityCreateDto.md) - [ActivityResponseDto](doc//ActivityResponseDto.md) - [ActivityStatisticsResponseDto](doc//ActivityStatisticsResponseDto.md) + - [AddUserDto](doc//AddUserDto.md) - [AddUsersDto](doc//AddUsersDto.md) - [AdminOnboardingUpdateDto](doc//AdminOnboardingUpdateDto.md) - [AlbumCountResponseDto](doc//AlbumCountResponseDto.md) diff --git a/mobile/openapi/doc/AddUserDto.md b/mobile/openapi/doc/AddUserDto.md new file mode 100644 index 0000000000..9fbd085306 --- /dev/null +++ b/mobile/openapi/doc/AddUserDto.md @@ -0,0 +1,16 @@ +# openapi.model.AddUserDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**role** | [**AlbumUserRole**](AlbumUserRole.md) | | [optional] +**userId** | **String** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/AddUsersDto.md b/mobile/openapi/doc/AddUsersDto.md index 9f7770d604..070aebdf69 100644 --- a/mobile/openapi/doc/AddUsersDto.md +++ b/mobile/openapi/doc/AddUsersDto.md @@ -8,7 +8,8 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**sharedUserIds** | **List** | | [default to const []] +**albumUsers** | [**List**](AddUserDto.md) | | [default to const []] +**sharedUserIds** | **List** | Deprecated in favor of albumUsers | [optional] [default to const []] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index e7821e5e8f..b5873a9e6f 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -63,6 +63,7 @@ part 'model/api_key_update_dto.dart'; part 'model/activity_create_dto.dart'; part 'model/activity_response_dto.dart'; part 'model/activity_statistics_response_dto.dart'; +part 'model/add_user_dto.dart'; part 'model/add_users_dto.dart'; part 'model/admin_onboarding_update_dto.dart'; part 'model/album_count_response_dto.dart'; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 76e46ed433..a7bbc368b5 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -196,6 +196,8 @@ class ApiClient { return ActivityResponseDto.fromJson(value); case 'ActivityStatisticsResponseDto': return ActivityStatisticsResponseDto.fromJson(value); + case 'AddUserDto': + return AddUserDto.fromJson(value); case 'AddUsersDto': return AddUsersDto.fromJson(value); case 'AdminOnboardingUpdateDto': diff --git a/mobile/openapi/lib/model/add_user_dto.dart b/mobile/openapi/lib/model/add_user_dto.dart new file mode 100644 index 0000000000..54fe87d53e --- /dev/null +++ b/mobile/openapi/lib/model/add_user_dto.dart @@ -0,0 +1,115 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AddUserDto { + /// Returns a new [AddUserDto] instance. + AddUserDto({ + this.role, + required this.userId, + }); + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AlbumUserRole? role; + + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is AddUserDto && + other.role == role && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (role == null ? 0 : role!.hashCode) + + (userId.hashCode); + + @override + String toString() => 'AddUserDto[role=$role, userId=$userId]'; + + Map toJson() { + final json = {}; + if (this.role != null) { + json[r'role'] = this.role; + } else { + // json[r'role'] = null; + } + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [AddUserDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AddUserDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return AddUserDto( + role: AlbumUserRole.fromJson(json[r'role']), + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AddUserDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AddUserDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AddUserDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AddUserDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/add_users_dto.dart b/mobile/openapi/lib/model/add_users_dto.dart index 9ce47afc6a..d7b4b6276b 100644 --- a/mobile/openapi/lib/model/add_users_dto.dart +++ b/mobile/openapi/lib/model/add_users_dto.dart @@ -13,25 +13,32 @@ part of openapi.api; class AddUsersDto { /// Returns a new [AddUsersDto] instance. AddUsersDto({ + this.albumUsers = const [], this.sharedUserIds = const [], }); + List albumUsers; + + /// Deprecated in favor of albumUsers List sharedUserIds; @override bool operator ==(Object other) => identical(this, other) || other is AddUsersDto && + _deepEquality.equals(other.albumUsers, albumUsers) && _deepEquality.equals(other.sharedUserIds, sharedUserIds); @override int get hashCode => // ignore: unnecessary_parenthesis + (albumUsers.hashCode) + (sharedUserIds.hashCode); @override - String toString() => 'AddUsersDto[sharedUserIds=$sharedUserIds]'; + String toString() => 'AddUsersDto[albumUsers=$albumUsers, sharedUserIds=$sharedUserIds]'; Map toJson() { final json = {}; + json[r'albumUsers'] = this.albumUsers; json[r'sharedUserIds'] = this.sharedUserIds; return json; } @@ -44,6 +51,7 @@ class AddUsersDto { final json = value.cast(); return AddUsersDto( + albumUsers: AddUserDto.listFromJson(json[r'albumUsers']), sharedUserIds: json[r'sharedUserIds'] is Iterable ? (json[r'sharedUserIds'] as Iterable).cast().toList(growable: false) : const [], @@ -94,7 +102,7 @@ class AddUsersDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'sharedUserIds', + 'albumUsers', }; } diff --git a/mobile/openapi/test/add_user_dto_test.dart b/mobile/openapi/test/add_user_dto_test.dart new file mode 100644 index 0000000000..f6dcadbfd1 --- /dev/null +++ b/mobile/openapi/test/add_user_dto_test.dart @@ -0,0 +1,32 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for AddUserDto +void main() { + // final instance = AddUserDto(); + + group('test AddUserDto', () { + // AlbumUserRole role + test('to test the property `role`', () async { + // TODO + }); + + // String userId + test('to test the property `userId`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/add_users_dto_test.dart b/mobile/openapi/test/add_users_dto_test.dart index 3dadfd8b4d..604d7da415 100644 --- a/mobile/openapi/test/add_users_dto_test.dart +++ b/mobile/openapi/test/add_users_dto_test.dart @@ -16,6 +16,12 @@ void main() { // final instance = AddUsersDto(); group('test AddUsersDto', () { + // List albumUsers (default value: const []) + test('to test the property `albumUsers`', () async { + // TODO + }); + + // Deprecated in favor of albumUsers // List sharedUserIds (default value: const []) test('to test the property `sharedUserIds`', () async { // TODO diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index e29ee58be9..a2e0bfc0f5 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -7300,9 +7300,32 @@ ], "type": "object" }, + "AddUserDto": { + "properties": { + "role": { + "$ref": "#/components/schemas/AlbumUserRole" + }, + "userId": { + "format": "uuid", + "type": "string" + } + }, + "required": [ + "userId" + ], + "type": "object" + }, "AddUsersDto": { "properties": { + "albumUsers": { + "items": { + "$ref": "#/components/schemas/AddUserDto" + }, + "type": "array" + }, "sharedUserIds": { + "deprecated": true, + "description": "Deprecated in favor of albumUsers", "items": { "format": "uuid", "type": "string" @@ -7311,7 +7334,7 @@ } }, "required": [ - "sharedUserIds" + "albumUsers" ], "type": "object" }, diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index a037545f53..03b4018c1e 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -196,8 +196,14 @@ export type BulkIdResponseDto = { export type UpdateAlbumUserDto = { role: AlbumUserRole; }; +export type AddUserDto = { + role?: AlbumUserRole; + userId: string; +}; export type AddUsersDto = { - sharedUserIds: string[]; + albumUsers: AddUserDto[]; + /** Deprecated in favor of albumUsers */ + sharedUserIds?: string[]; }; export type ApiKeyResponseDto = { createdAt: string; diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index 0f1183e381..ce035fda00 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -13,10 +13,23 @@ export class AlbumInfoDto { withoutAssets?: boolean; } +export class AddUserDto { + @ValidateUUID() + userId!: string; + + @IsEnum(AlbumUserRole) + @ApiProperty({ enum: AlbumUserRole, enumName: 'AlbumUserRole', default: AlbumUserRole.EDITOR }) + role?: AlbumUserRole; +} + export class AddUsersDto { - @ValidateUUID({ each: true }) + @ValidateUUID({ each: true, optional: true }) @ArrayNotEmpty() - sharedUserIds!: string[]; + @ApiProperty({ deprecated: true, description: 'Deprecated in favor of albumUsers' }) + sharedUserIds?: string[]; + + @ArrayNotEmpty() + albumUsers!: AddUserDto[]; } export class CreateAlbumDto { diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index a3c3dc5e73..1cc049d851 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -14,7 +14,7 @@ import { } from 'src/dtos/album.dto'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { AlbumUserEntity } from 'src/entities/album-user.entity'; +import { AlbumUserEntity, AlbumUserRole } from 'src/entities/album-user.entity'; import { AlbumEntity } from 'src/entities/album.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { IAccessRepository } from 'src/interfaces/access.interface'; @@ -211,12 +211,20 @@ export class AlbumService { return results; } - async addUsers(auth: AuthDto, id: string, dto: AddUsersDto): Promise { + async addUsers(auth: AuthDto, id: string, { albumUsers, sharedUserIds }: AddUsersDto): Promise { + // Remove once deprecated sharedUserIds is removed + if (!albumUsers) { + if (!sharedUserIds) { + throw new BadRequestException('No users provided'); + } + albumUsers = sharedUserIds.map((userId) => ({ userId, role: AlbumUserRole.EDITOR })); + } + await this.access.requirePermission(auth, Permission.ALBUM_SHARE, id); const album = await this.findOrFail(id, { withAssets: false }); - for (const userId of dto.sharedUserIds) { + for (const { userId, role } of albumUsers) { if (album.ownerId === userId) { throw new BadRequestException('Cannot be shared with owner'); } @@ -231,7 +239,7 @@ export class AlbumService { throw new BadRequestException('User not found'); } - await this.albumUserRepository.create({ userId: userId, albumId: id }); + await this.albumUserRepository.create({ userId: userId, albumId: id, role }); } return this.findOrFail(id, { withAssets: true }).then(mapAlbumWithoutAssets);