From 334a709cc644d97c8ef3a5c7344912bda4b4594e Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 8 Jul 2024 16:41:39 -0400 Subject: [PATCH] refactor(server): partner search dto (#10902) * refactor(server): partner search dto * fix: missed reference * mobile fix --------- Co-authored-by: Alex Tran --- mobile/lib/services/partner.service.dart | 14 +-- mobile/lib/services/user.service.dart | 4 +- mobile/openapi/README.md | 1 + mobile/openapi/lib/api.dart | 1 + mobile/openapi/lib/api/partners_api.dart | 8 +- mobile/openapi/lib/api_client.dart | 2 + mobile/openapi/lib/api_helper.dart | 3 + .../openapi/lib/model/partner_direction.dart | 85 +++++++++++++++++++ open-api/immich-openapi-specs.json | 13 +-- open-api/typescript-sdk/src/fetch-client.ts | 6 +- server/src/controllers/partner.controller.ts | 6 +- server/src/dtos/partner.dto.ts | 10 ++- server/src/services/partner.service.spec.ts | 6 +- server/src/services/partner.service.ts | 4 +- .../partner-selection-modal.svelte | 4 +- .../partner-settings.svelte | 5 +- web/src/routes/(user)/sharing/+page.ts | 4 +- 17 files changed, 136 insertions(+), 40 deletions(-) create mode 100644 mobile/openapi/lib/model/partner_direction.dart diff --git a/mobile/lib/services/partner.service.dart b/mobile/lib/services/partner.service.dart index 84f37ce7e1..8cd2fe424f 100644 --- a/mobile/lib/services/partner.service.dart +++ b/mobile/lib/services/partner.service.dart @@ -14,17 +14,6 @@ final partnerServiceProvider = Provider( ), ); -enum PartnerDirection { - sharedWith("shared-with"), - sharedBy("shared-by"); - - const PartnerDirection( - this._value, - ); - - final String _value; -} - class PartnerService { final ApiService _apiService; final Isar _db; @@ -34,8 +23,7 @@ class PartnerService { Future?> getPartners(PartnerDirection direction) async { try { - final userDtos = - await _apiService.partnersApi.getPartners(direction._value); + final userDtos = await _apiService.partnersApi.getPartners(direction); if (userDtos != null) { return userDtos.map((u) => User.fromPartnerDto(u)).toList(); } diff --git a/mobile/lib/services/user.service.dart b/mobile/lib/services/user.service.dart index 0d8d47b104..9631141c41 100644 --- a/mobile/lib/services/user.service.dart +++ b/mobile/lib/services/user.service.dart @@ -73,9 +73,9 @@ class UserService { Future?> getUsersFromServer() async { final List? users = await _getAllUsers(); final List? sharedBy = - await _partnerService.getPartners(PartnerDirection.sharedBy); + await _partnerService.getPartners(PartnerDirection.by); final List? sharedWith = - await _partnerService.getPartners(PartnerDirection.sharedWith); + await _partnerService.getPartners(PartnerDirection.with_); if (users == null || sharedBy == null || sharedWith == null) { _log.warning("Failed to refresh users"); diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index c3ef247ddc..674409d81d 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -353,6 +353,7 @@ Class | Method | HTTP request | Description - [OAuthCallbackDto](doc//OAuthCallbackDto.md) - [OAuthConfigDto](doc//OAuthConfigDto.md) - [OnThisDayDto](doc//OnThisDayDto.md) + - [PartnerDirection](doc//PartnerDirection.md) - [PartnerResponseDto](doc//PartnerResponseDto.md) - [PathEntityType](doc//PathEntityType.md) - [PathType](doc//PathType.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index cda2bac4c5..e6a3907a24 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -166,6 +166,7 @@ part 'model/o_auth_authorize_response_dto.dart'; part 'model/o_auth_callback_dto.dart'; part 'model/o_auth_config_dto.dart'; part 'model/on_this_day_dto.dart'; +part 'model/partner_direction.dart'; part 'model/partner_response_dto.dart'; part 'model/path_entity_type.dart'; part 'model/path_type.dart'; diff --git a/mobile/openapi/lib/api/partners_api.dart b/mobile/openapi/lib/api/partners_api.dart index 3794e79079..ac0d03054a 100644 --- a/mobile/openapi/lib/api/partners_api.dart +++ b/mobile/openapi/lib/api/partners_api.dart @@ -67,8 +67,8 @@ class PartnersApi { /// Performs an HTTP 'GET /partners' operation and returns the [Response]. /// Parameters: /// - /// * [String] direction (required): - Future getPartnersWithHttpInfo(String direction,) async { + /// * [PartnerDirection] direction (required): + Future getPartnersWithHttpInfo(PartnerDirection direction,) async { // ignore: prefer_const_declarations final path = r'/partners'; @@ -97,8 +97,8 @@ class PartnersApi { /// Parameters: /// - /// * [String] direction (required): - Future?> getPartners(String direction,) async { + /// * [PartnerDirection] direction (required): + Future?> getPartners(PartnerDirection direction,) async { final response = await getPartnersWithHttpInfo(direction,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 32490a6820..2645a32813 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -390,6 +390,8 @@ class ApiClient { return OAuthConfigDto.fromJson(value); case 'OnThisDayDto': return OnThisDayDto.fromJson(value); + case 'PartnerDirection': + return PartnerDirectionTypeTransformer().decode(value); case 'PartnerResponseDto': return PartnerResponseDto.fromJson(value); case 'PathEntityType': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 762b728869..db8af0bfc2 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -103,6 +103,9 @@ String parameterToString(dynamic value) { if (value is MemoryType) { return MemoryTypeTypeTransformer().encode(value).toString(); } + if (value is PartnerDirection) { + return PartnerDirectionTypeTransformer().encode(value).toString(); + } if (value is PathEntityType) { return PathEntityTypeTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/partner_direction.dart b/mobile/openapi/lib/model/partner_direction.dart new file mode 100644 index 0000000000..c43c0df75d --- /dev/null +++ b/mobile/openapi/lib/model/partner_direction.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// 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 PartnerDirection { + /// Instantiate a new enum with the provided [value]. + const PartnerDirection._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const by = PartnerDirection._(r'shared-by'); + static const with_ = PartnerDirection._(r'shared-with'); + + /// List of all possible values in this [enum][PartnerDirection]. + static const values = [ + by, + with_, + ]; + + static PartnerDirection? fromJson(dynamic value) => PartnerDirectionTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PartnerDirection.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [PartnerDirection] to String, +/// and [decode] dynamic data back to [PartnerDirection]. +class PartnerDirectionTypeTransformer { + factory PartnerDirectionTypeTransformer() => _instance ??= const PartnerDirectionTypeTransformer._(); + + const PartnerDirectionTypeTransformer._(); + + String encode(PartnerDirection data) => data.value; + + /// Decodes a [dynamic value][data] to a PartnerDirection. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + PartnerDirection? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'shared-by': return PartnerDirection.by; + case r'shared-with': return PartnerDirection.with_; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [PartnerDirectionTypeTransformer] instance. + static PartnerDirectionTypeTransformer? _instance; +} + diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index b25e78da77..12472daf5b 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -3660,11 +3660,7 @@ "required": true, "in": "query", "schema": { - "enum": [ - "shared-by", - "shared-with" - ], - "type": "string" + "$ref": "#/components/schemas/PartnerDirection" } } ], @@ -9473,6 +9469,13 @@ ], "type": "object" }, + "PartnerDirection": { + "enum": [ + "shared-by", + "shared-with" + ], + "type": "string" + }, "PartnerResponseDto": { "properties": { "avatarColor": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index ac3f776991..0130c80946 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -2128,7 +2128,7 @@ export function unlinkOAuthAccount(opts?: Oazapfts.RequestOpts) { })); } export function getPartners({ direction }: { - direction: "shared-by" | "shared-with"; + direction: PartnerDirection; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3131,6 +3131,10 @@ export enum Type2 { export enum MemoryType { OnThisDay = "on_this_day" } +export enum PartnerDirection { + SharedBy = "shared-by", + SharedWith = "shared-with" +} export enum PathEntityType { Asset = "asset", Person = "person", diff --git a/server/src/controllers/partner.controller.ts b/server/src/controllers/partner.controller.ts index 927078bf0b..208d571464 100644 --- a/server/src/controllers/partner.controller.ts +++ b/server/src/controllers/partner.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common'; import { ApiQuery, ApiTags } from '@nestjs/swagger'; import { AuthDto } from 'src/dtos/auth.dto'; -import { PartnerResponseDto, UpdatePartnerDto } from 'src/dtos/partner.dto'; +import { PartnerResponseDto, PartnerSearchDto, UpdatePartnerDto } from 'src/dtos/partner.dto'; import { PartnerDirection } from 'src/interfaces/partner.interface'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { PartnerService } from 'src/services/partner.service'; @@ -16,8 +16,8 @@ export class PartnerController { @ApiQuery({ name: 'direction', type: 'string', enum: PartnerDirection, required: true }) @Authenticated() // TODO: remove 'direction' and convert to full query dto - getPartners(@Auth() auth: AuthDto, @Query('direction') direction: PartnerDirection): Promise { - return this.service.getAll(auth, direction); + getPartners(@Auth() auth: AuthDto, @Query() dto: PartnerSearchDto): Promise { + return this.service.search(auth, dto); } @Post(':id') diff --git a/server/src/dtos/partner.dto.ts b/server/src/dtos/partner.dto.ts index 187f8f341b..38573998d6 100644 --- a/server/src/dtos/partner.dto.ts +++ b/server/src/dtos/partner.dto.ts @@ -1,11 +1,19 @@ -import { IsNotEmpty } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNotEmpty } from 'class-validator'; import { UserResponseDto } from 'src/dtos/user.dto'; +import { PartnerDirection } from 'src/interfaces/partner.interface'; export class UpdatePartnerDto { @IsNotEmpty() inTimeline!: boolean; } +export class PartnerSearchDto { + @IsEnum(PartnerDirection) + @ApiProperty({ enum: PartnerDirection, enumName: 'PartnerDirection' }) + direction!: PartnerDirection; +} + export class PartnerResponseDto extends UserResponseDto { inTimeline?: boolean; } diff --git a/server/src/services/partner.service.spec.ts b/server/src/services/partner.service.spec.ts index 043b8ae71a..b2b3401251 100644 --- a/server/src/services/partner.service.spec.ts +++ b/server/src/services/partner.service.spec.ts @@ -21,16 +21,16 @@ describe(PartnerService.name, () => { expect(sut).toBeDefined(); }); - describe('getAll', () => { + describe('search', () => { it("should return a list of partners with whom I've shared my library", async () => { partnerMock.getAll.mockResolvedValue([partnerStub.adminToUser1, partnerStub.user1ToAdmin1]); - await expect(sut.getAll(authStub.user1, PartnerDirection.SharedBy)).resolves.toBeDefined(); + await expect(sut.search(authStub.user1, { direction: PartnerDirection.SharedBy })).resolves.toBeDefined(); expect(partnerMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id); }); it('should return a list of partners who have shared their libraries with me', async () => { partnerMock.getAll.mockResolvedValue([partnerStub.adminToUser1, partnerStub.user1ToAdmin1]); - await expect(sut.getAll(authStub.user1, PartnerDirection.SharedWith)).resolves.toBeDefined(); + await expect(sut.search(authStub.user1, { direction: PartnerDirection.SharedWith })).resolves.toBeDefined(); expect(partnerMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id); }); }); diff --git a/server/src/services/partner.service.ts b/server/src/services/partner.service.ts index e1d4e9738b..d26149dceb 100644 --- a/server/src/services/partner.service.ts +++ b/server/src/services/partner.service.ts @@ -1,7 +1,7 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { AccessCore, Permission } from 'src/cores/access.core'; import { AuthDto } from 'src/dtos/auth.dto'; -import { PartnerResponseDto, UpdatePartnerDto } from 'src/dtos/partner.dto'; +import { PartnerResponseDto, PartnerSearchDto, UpdatePartnerDto } from 'src/dtos/partner.dto'; import { mapUser } from 'src/dtos/user.dto'; import { PartnerEntity } from 'src/entities/partner.entity'; import { IAccessRepository } from 'src/interfaces/access.interface'; @@ -38,7 +38,7 @@ export class PartnerService { await this.repository.remove(partner); } - async getAll(auth: AuthDto, direction: PartnerDirection): Promise { + async search(auth: AuthDto, { direction }: PartnerSearchDto): Promise { const partners = await this.repository.getAll(auth.user.id); const key = direction === PartnerDirection.SharedBy ? 'sharedById' : 'sharedWithId'; return partners diff --git a/web/src/lib/components/user-settings-page/partner-selection-modal.svelte b/web/src/lib/components/user-settings-page/partner-selection-modal.svelte index 88cda3cc0c..3cff1cd1de 100644 --- a/web/src/lib/components/user-settings-page/partner-selection-modal.svelte +++ b/web/src/lib/components/user-settings-page/partner-selection-modal.svelte @@ -1,5 +1,5 @@