diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 53c4d6e1e5..e9e5782a00 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -577,6 +577,7 @@ Class | Method | HTTP request | Description - [UpdateAlbumUserDto](doc//UpdateAlbumUserDto.md) - [UpdateAssetDto](doc//UpdateAssetDto.md) - [UpdateLibraryDto](doc//UpdateLibraryDto.md) + - [UploadOkDto](doc//UploadOkDto.md) - [UsageByUserDto](doc//UsageByUserDto.md) - [UserAdminCreateDto](doc//UserAdminCreateDto.md) - [UserAdminDeleteDto](doc//UserAdminDeleteDto.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 8bce0075f2..c488f05ff9 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -344,6 +344,7 @@ part 'model/update_album_dto.dart'; part 'model/update_album_user_dto.dart'; part 'model/update_asset_dto.dart'; part 'model/update_library_dto.dart'; +part 'model/upload_ok_dto.dart'; part 'model/usage_by_user_dto.dart'; part 'model/user_admin_create_dto.dart'; part 'model/user_admin_delete_dto.dart'; diff --git a/mobile/openapi/lib/api/upload_api.dart b/mobile/openapi/lib/api/upload_api.dart index 10dfe43beb..0c9b698025 100644 --- a/mobile/openapi/lib/api/upload_api.dart +++ b/mobile/openapi/lib/api/upload_api.dart @@ -259,7 +259,7 @@ class UploadApi { /// * [String] key: /// /// * [String] slug: - Future resumeUpload(String contentLength, String id, String uploadComplete, String uploadDraftInteropVersion, String uploadOffset, { String? key, String? slug, }) async { + Future resumeUpload(String contentLength, String id, String uploadComplete, String uploadDraftInteropVersion, String uploadOffset, { String? key, String? slug, }) async { final response = await resumeUploadWithHttpInfo(contentLength, id, uploadComplete, uploadDraftInteropVersion, uploadOffset, key: key, slug: slug, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -268,7 +268,7 @@ class UploadApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'Object',) as Object; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UploadOkDto',) as UploadOkDto; } return null; @@ -358,7 +358,7 @@ class UploadApi { /// * [String] key: /// /// * [String] slug: - Future startUpload(String contentLength, String reprDigest, String uploadComplete, String uploadDraftInteropVersion, String xImmichAssetData, { String? key, String? slug, }) async { + Future startUpload(String contentLength, String reprDigest, String uploadComplete, String uploadDraftInteropVersion, String xImmichAssetData, { String? key, String? slug, }) async { final response = await startUploadWithHttpInfo(contentLength, reprDigest, uploadComplete, uploadDraftInteropVersion, xImmichAssetData, key: key, slug: slug, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -367,7 +367,7 @@ class UploadApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'Object',) as Object; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UploadOkDto',) as UploadOkDto; } return null; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 06d27593c9..5ad8bc664c 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -740,6 +740,8 @@ class ApiClient { return UpdateAssetDto.fromJson(value); case 'UpdateLibraryDto': return UpdateLibraryDto.fromJson(value); + case 'UploadOkDto': + return UploadOkDto.fromJson(value); case 'UsageByUserDto': return UsageByUserDto.fromJson(value); case 'UserAdminCreateDto': diff --git a/mobile/openapi/lib/model/upload_ok_dto.dart b/mobile/openapi/lib/model/upload_ok_dto.dart new file mode 100644 index 0000000000..1ccae05b74 --- /dev/null +++ b/mobile/openapi/lib/model/upload_ok_dto.dart @@ -0,0 +1,99 @@ +// +// 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 UploadOkDto { + /// Returns a new [UploadOkDto] instance. + UploadOkDto({ + required this.id, + }); + + String id; + + @override + bool operator ==(Object other) => identical(this, other) || other is UploadOkDto && + other.id == id; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (id.hashCode); + + @override + String toString() => 'UploadOkDto[id=$id]'; + + Map toJson() { + final json = {}; + json[r'id'] = this.id; + return json; + } + + /// Returns a new [UploadOkDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UploadOkDto? fromJson(dynamic value) { + upgradeDto(value, "UploadOkDto"); + if (value is Map) { + final json = value.cast(); + + return UploadOkDto( + id: mapValueOfType(json, r'id')!, + ); + } + 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 = UploadOkDto.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 = UploadOkDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UploadOkDto-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] = UploadOkDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + }; +} + diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index c3630292da..4847f8dd16 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -17685,7 +17685,14 @@ "type": "object" }, "UploadOkDto": { - "properties": {}, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], "type": "object" }, "UsageByUserDto": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 1e65e0ee49..8f75f1c981 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -1595,7 +1595,9 @@ export type TimeBucketsResponseDto = { export type TrashResponseDto = { count: number; }; -export type UploadOkDto = {}; +export type UploadOkDto = { + id: string; +}; export type UserUpdateMeDto = { avatarColor?: (UserAvatarColor) | null; email?: string; diff --git a/server/src/dtos/asset-upload.ts b/server/src/dtos/asset-upload.ts index fe59984d9c..fafe51afc5 100644 --- a/server/src/dtos/asset-upload.ts +++ b/server/src/dtos/asset-upload.ts @@ -1,6 +1,7 @@ import { BadRequestException } from '@nestjs/common'; +import { ApiProperty } from '@nestjs/swagger'; import { Expose, plainToInstance, Transform, Type } from 'class-transformer'; -import { Equals, IsEnum, IsInt, IsNotEmpty, IsString, Min, ValidateIf, ValidateNested } from 'class-validator'; +import { Equals, IsEmpty, IsEnum, IsInt, IsNotEmpty, IsString, Min, ValidateIf, ValidateNested } from 'class-validator'; import { ImmichHeader } from 'src/enum'; import { Optional, ValidateBoolean, ValidateDate } from 'src/validation'; import { parseDictionary } from 'structured-headers'; @@ -39,13 +40,14 @@ export enum StructuredBoolean { } export enum UploadHeader { - UploadOffset = 'upload-offset', ContentLength = 'content-length', - UploadLength = 'upload-length', - UploadComplete = 'upload-complete', - UploadIncomplete = 'upload-incomplete', + ContentType = 'content-type', InteropVersion = 'upload-draft-interop-version', ReprDigest = 'repr-digest', + UploadComplete = 'upload-complete', + UploadIncomplete = 'upload-incomplete', + UploadLength = 'upload-length', + UploadOffset = 'upload-offset', } class BaseRufhHeadersDto { @@ -126,10 +128,14 @@ export class StartUploadDto extends BaseUploadHeadersDto { @IsInt() @Type(() => Number) uploadLength!: number; + + @Expose({ name: UploadHeader.UploadOffset }) + @IsEmpty() + uploadOffset: string | undefined; } export class ResumeUploadDto extends BaseUploadHeadersDto { - @Expose({ name: 'content-type' }) + @Expose({ name: UploadHeader.ContentType }) @ValidateIf((o) => o.version && o.version >= 6) @Equals('application/partial-upload') contentType!: string; @@ -148,8 +154,21 @@ export class ResumeUploadDto extends BaseUploadHeadersDto { uploadOffset!: number; } -export class GetUploadStatusDto extends BaseRufhHeadersDto {} +export class GetUploadStatusDto extends BaseRufhHeadersDto { + @Expose({ name: UploadHeader.UploadComplete }) + @IsEmpty() + uploadComplete: string | undefined; + + @Expose({ name: UploadHeader.UploadIncomplete }) + @IsEmpty() + uploadIncomplete: string | undefined; + + @Expose({ name: UploadHeader.UploadOffset }) + @IsEmpty() + uploadOffset: string | undefined; +} export class UploadOkDto { + @ApiProperty() id!: string; }