From 5de8ea162d8f04fb356147294202c2bd37f7dc2e Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 5 Dec 2022 11:56:44 -0600 Subject: [PATCH] feat(server) Tagging system (#1046) --- .gitattributes | 13 + Makefile | 3 + NOTES.md | 9 - mobile/openapi/.openapi-generator/FILES | 20 + mobile/openapi/README.md | 16 +- mobile/openapi/doc/AssetApi.md | 10 +- mobile/openapi/doc/AssetEntity.md | 34 + mobile/openapi/doc/AssetResponseDto.md | 1 + mobile/openapi/doc/CreateTagDto.md | 16 + mobile/openapi/doc/ExifEntity.md | 39 + mobile/openapi/doc/SmartInfoEntity.md | 19 + mobile/openapi/doc/TagApi.md | 221 + mobile/openapi/doc/TagEntity.md | 21 + mobile/openapi/doc/TagResponseDto.md | 17 + mobile/openapi/doc/TagTypeEnum.md | 14 + mobile/openapi/doc/UpdateAssetDto.md | 3 +- mobile/openapi/doc/UpdateTagDto.md | 16 + mobile/openapi/doc/UserEntity.md | 27 + mobile/openapi/lib/api.dart | 10 + mobile/openapi/lib/api/asset_api.dart | 8 +- mobile/openapi/lib/api/tag_api.dart | 257 ++ mobile/openapi/lib/api_client.dart | 18 + mobile/openapi/lib/api_helper.dart | 3 + .../openapi/lib/model/album_response_dto.dart | 94 +- mobile/openapi/lib/model/asset_entity.dart | 384 ++ .../openapi/lib/model/asset_response_dto.dart | 14 +- mobile/openapi/lib/model/create_tag_dto.dart | 119 + mobile/openapi/lib/model/exif_entity.dart | 414 ++ .../openapi/lib/model/smart_info_entity.dart | 164 + mobile/openapi/lib/model/tag_entity.dart | 236 + .../openapi/lib/model/tag_response_dto.dart | 127 + mobile/openapi/lib/model/tag_type_enum.dart | 88 + .../openapi/lib/model/update_asset_dto.dart | 30 +- mobile/openapi/lib/model/update_tag_dto.dart | 137 + mobile/openapi/lib/model/user_entity.dart | 234 + mobile/openapi/test/asset_entity_test.dart | 122 + mobile/openapi/test/create_tag_dto_test.dart | 32 + mobile/openapi/test/exif_entity_test.dart | 150 + .../openapi/test/smart_info_entity_test.dart | 47 + mobile/openapi/test/tag_api_test.dart | 46 + mobile/openapi/test/tag_entity_test.dart | 52 + .../openapi/test/tag_response_dto_test.dart | 37 + mobile/openapi/test/tag_type_enum_test.dart | 21 + mobile/openapi/test/update_tag_dto_test.dart | 32 + mobile/openapi/test/user_entity_test.dart | 82 + .../immich/src/api-v1/album/album.module.ts | 29 +- .../src/api-v1/asset/asset-repository.ts | 26 +- .../src/api-v1/asset/asset.controller.ts | 8 +- .../immich/src/api-v1/asset/asset.module.ts | 36 +- .../immich/src/api-v1/asset/asset.service.ts | 4 +- .../src/api-v1/asset/dto/update-asset.dto.ts | 22 +- .../asset/response-dto/asset-response.dto.ts | 3 + .../apps/immich/src/api-v1/job/job.module.ts | 20 +- .../src/api-v1/tag/dto/create-tag.dto.ts | 14 + .../src/api-v1/tag/dto/update-tag.dto.ts | 11 + .../tag/response-dto/tag-response.dto.ts | 20 + .../immich/src/api-v1/tag/tag.controller.ts | 44 + .../apps/immich/src/api-v1/tag/tag.module.ts | 18 + .../immich/src/api-v1/tag/tag.repository.ts | 61 + .../immich/src/api-v1/tag/tag.service.spec.ts | 91 + .../apps/immich/src/api-v1/tag/tag.service.ts | 48 + .../src/api-v1/user/user.service.spec.ts | 3 + server/apps/immich/src/app.module.ts | 3 + server/apps/immich/src/main.ts | 2 +- .../immich-jwt/immich-jwt.service.spec.ts | 1 + server/immich-openapi-specs.json | 3976 ++++++++++++++++- .../database/src/entities/asset.entity.ts | 8 +- .../libs/database/src/entities/tag.entity.ts | 45 + .../libs/database/src/entities/user.entity.ts | 6 +- .../1670257571385-CreateTagsTable.ts | 26 + web/src/api/open-api/api.ts | 943 +++- .../asset-viewer/asset-viewer.svelte | 2 +- .../shared-components/status-box.svelte | 4 +- .../shared-components/upload-panel.svelte | 4 +- 74 files changed, 8768 insertions(+), 167 deletions(-) create mode 100644 .gitattributes delete mode 100644 NOTES.md create mode 100644 mobile/openapi/doc/AssetEntity.md create mode 100644 mobile/openapi/doc/CreateTagDto.md create mode 100644 mobile/openapi/doc/ExifEntity.md create mode 100644 mobile/openapi/doc/SmartInfoEntity.md create mode 100644 mobile/openapi/doc/TagApi.md create mode 100644 mobile/openapi/doc/TagEntity.md create mode 100644 mobile/openapi/doc/TagResponseDto.md create mode 100644 mobile/openapi/doc/TagTypeEnum.md create mode 100644 mobile/openapi/doc/UpdateTagDto.md create mode 100644 mobile/openapi/doc/UserEntity.md create mode 100644 mobile/openapi/lib/api/tag_api.dart create mode 100644 mobile/openapi/lib/model/asset_entity.dart create mode 100644 mobile/openapi/lib/model/create_tag_dto.dart create mode 100644 mobile/openapi/lib/model/exif_entity.dart create mode 100644 mobile/openapi/lib/model/smart_info_entity.dart create mode 100644 mobile/openapi/lib/model/tag_entity.dart create mode 100644 mobile/openapi/lib/model/tag_response_dto.dart create mode 100644 mobile/openapi/lib/model/tag_type_enum.dart create mode 100644 mobile/openapi/lib/model/update_tag_dto.dart create mode 100644 mobile/openapi/lib/model/user_entity.dart create mode 100644 mobile/openapi/test/asset_entity_test.dart create mode 100644 mobile/openapi/test/create_tag_dto_test.dart create mode 100644 mobile/openapi/test/exif_entity_test.dart create mode 100644 mobile/openapi/test/smart_info_entity_test.dart create mode 100644 mobile/openapi/test/tag_api_test.dart create mode 100644 mobile/openapi/test/tag_entity_test.dart create mode 100644 mobile/openapi/test/tag_response_dto_test.dart create mode 100644 mobile/openapi/test/tag_type_enum_test.dart create mode 100644 mobile/openapi/test/update_tag_dto_test.dart create mode 100644 mobile/openapi/test/user_entity_test.dart create mode 100644 server/apps/immich/src/api-v1/tag/dto/create-tag.dto.ts create mode 100644 server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts create mode 100644 server/apps/immich/src/api-v1/tag/response-dto/tag-response.dto.ts create mode 100644 server/apps/immich/src/api-v1/tag/tag.controller.ts create mode 100644 server/apps/immich/src/api-v1/tag/tag.module.ts create mode 100644 server/apps/immich/src/api-v1/tag/tag.repository.ts create mode 100644 server/apps/immich/src/api-v1/tag/tag.service.spec.ts create mode 100644 server/apps/immich/src/api-v1/tag/tag.service.ts create mode 100644 server/libs/database/src/entities/tag.entity.ts create mode 100644 server/libs/database/src/migrations/1670257571385-CreateTagsTable.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..b45b801c9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +mobile/openapi/**/*.md -diff -merge +mobile/openapi/**/*.md linguist-generated=true +mobile/openapi/**/*.dart -diff -merge +mobile/openapi/**/*.dart linguist-generated=true + +web/src/api/open-api/**/*.md -diff -merge +web/src/api/open-api/**/*.md linguist-generated=true + +web/src/api/open-api/**/*.ts -diff -merge +web/src/api/open-api/**/*.ts linguist-generated=true + +mobile/openapi/.openapi-generator/FILES -diff -merge +mobile/openapi/.openapi-generator/FILES linguist-generated=true diff --git a/Makefile b/Makefile index 460f01eb3..34c9619c5 100644 --- a/Makefile +++ b/Makefile @@ -27,3 +27,6 @@ prod-scale: api: cd ./server && npm run api:generate + +attach-server: + docker exec -it docker_immich-server_1 sh \ No newline at end of file diff --git a/NOTES.md b/NOTES.md deleted file mode 100644 index a21726327..000000000 --- a/NOTES.md +++ /dev/null @@ -1,9 +0,0 @@ -# TODO - -Server scenario with web - -[ ] 1 user exist without admin right -> make admin on first check - -[ ] 2 users exist without admin right -> ask user to choose which account will be the admin - -[ X ] No users exist -> prompt signup form for Admin \ No newline at end of file diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 3409c3eb1..92f15f288 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -14,6 +14,7 @@ doc/AssetApi.md doc/AssetCountByTimeBucket.md doc/AssetCountByTimeBucketResponseDto.md doc/AssetCountByUserIdResponseDto.md +doc/AssetEntity.md doc/AssetFileUploadResponseDto.md doc/AssetResponseDto.md doc/AssetTypeEnum.md @@ -25,6 +26,7 @@ doc/CheckExistingAssetsResponseDto.md doc/CreateAlbumDto.md doc/CreateDeviceInfoDto.md doc/CreateProfileImageResponseDto.md +doc/CreateTagDto.md doc/CreateUserDto.md doc/CuratedLocationsResponseDto.md doc/CuratedObjectsResponseDto.md @@ -34,6 +36,7 @@ doc/DeleteAssetStatus.md doc/DeviceInfoApi.md doc/DeviceInfoResponseDto.md doc/DeviceTypeEnum.md +doc/ExifEntity.md doc/ExifResponseDto.md doc/GetAssetByTimeBucketDto.md doc/GetAssetCountByTimeBucketDto.md @@ -58,20 +61,27 @@ doc/ServerPingResponse.md doc/ServerStatsResponseDto.md doc/ServerVersionReponseDto.md doc/SignUpDto.md +doc/SmartInfoEntity.md doc/SmartInfoResponseDto.md doc/SystemConfigApi.md doc/SystemConfigKey.md doc/SystemConfigResponseDto.md doc/SystemConfigResponseItem.md +doc/TagApi.md +doc/TagEntity.md +doc/TagResponseDto.md +doc/TagTypeEnum.md doc/ThumbnailFormat.md doc/TimeGroupEnum.md doc/UpdateAlbumDto.md doc/UpdateAssetDto.md doc/UpdateDeviceInfoDto.md +doc/UpdateTagDto.md doc/UpdateUserDto.md doc/UsageByUserDto.md doc/UserApi.md doc/UserCountResponseDto.md +doc/UserEntity.md doc/UserResponseDto.md doc/ValidateAccessTokenResponseDto.md git_push.sh @@ -84,6 +94,7 @@ lib/api/job_api.dart lib/api/o_auth_api.dart lib/api/server_info_api.dart lib/api/system_config_api.dart +lib/api/tag_api.dart lib/api/user_api.dart lib/api_client.dart lib/api_exception.dart @@ -103,6 +114,7 @@ lib/model/all_job_status_response_dto.dart lib/model/asset_count_by_time_bucket.dart lib/model/asset_count_by_time_bucket_response_dto.dart lib/model/asset_count_by_user_id_response_dto.dart +lib/model/asset_entity.dart lib/model/asset_file_upload_response_dto.dart lib/model/asset_response_dto.dart lib/model/asset_type_enum.dart @@ -113,6 +125,7 @@ lib/model/check_existing_assets_response_dto.dart lib/model/create_album_dto.dart lib/model/create_device_info_dto.dart lib/model/create_profile_image_response_dto.dart +lib/model/create_tag_dto.dart lib/model/create_user_dto.dart lib/model/curated_locations_response_dto.dart lib/model/curated_objects_response_dto.dart @@ -121,6 +134,7 @@ lib/model/delete_asset_response_dto.dart lib/model/delete_asset_status.dart lib/model/device_info_response_dto.dart lib/model/device_type_enum.dart +lib/model/exif_entity.dart lib/model/exif_response_dto.dart lib/model/get_asset_by_time_bucket_dto.dart lib/model/get_asset_count_by_time_bucket_dto.dart @@ -142,18 +156,24 @@ lib/model/server_ping_response.dart lib/model/server_stats_response_dto.dart lib/model/server_version_reponse_dto.dart lib/model/sign_up_dto.dart +lib/model/smart_info_entity.dart lib/model/smart_info_response_dto.dart lib/model/system_config_key.dart lib/model/system_config_response_dto.dart lib/model/system_config_response_item.dart +lib/model/tag_entity.dart +lib/model/tag_response_dto.dart +lib/model/tag_type_enum.dart lib/model/thumbnail_format.dart lib/model/time_group_enum.dart lib/model/update_album_dto.dart lib/model/update_asset_dto.dart lib/model/update_device_info_dto.dart +lib/model/update_tag_dto.dart lib/model/update_user_dto.dart lib/model/usage_by_user_dto.dart lib/model/user_count_response_dto.dart +lib/model/user_entity.dart lib/model/user_response_dto.dart lib/model/validate_access_token_response_dto.dart pubspec.yaml diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index a414e0986..2974e839e 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -93,7 +93,7 @@ Class | Method | HTTP request | Description *AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} | *AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search | *AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{assetId} | -*AssetApi* | [**updateAssetById**](doc//AssetApi.md#updateassetbyid) | **PUT** /asset/assetById/{assetId} | +*AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{assetId} | *AssetApi* | [**uploadFile**](doc//AssetApi.md#uploadfile) | **POST** /asset/upload | *AuthenticationApi* | [**adminSignUp**](doc//AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up | *AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login | @@ -112,6 +112,11 @@ Class | Method | HTTP request | Description *ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping | *SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config | *SystemConfigApi* | [**updateConfig**](doc//SystemConfigApi.md#updateconfig) | **PUT** /system-config | +*TagApi* | [**create**](doc//TagApi.md#create) | **POST** /tag | +*TagApi* | [**delete**](doc//TagApi.md#delete) | **DELETE** /tag/{id} | +*TagApi* | [**findAll**](doc//TagApi.md#findall) | **GET** /tag | +*TagApi* | [**findOne**](doc//TagApi.md#findone) | **GET** /tag/{id} | +*TagApi* | [**update**](doc//TagApi.md#update) | **PATCH** /tag/{id} | *UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /user/profile-image | *UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user | *UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /user/{userId} | @@ -136,6 +141,7 @@ Class | Method | HTTP request | Description - [AssetCountByTimeBucket](doc//AssetCountByTimeBucket.md) - [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md) - [AssetCountByUserIdResponseDto](doc//AssetCountByUserIdResponseDto.md) + - [AssetEntity](doc//AssetEntity.md) - [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md) - [AssetResponseDto](doc//AssetResponseDto.md) - [AssetTypeEnum](doc//AssetTypeEnum.md) @@ -146,6 +152,7 @@ Class | Method | HTTP request | Description - [CreateAlbumDto](doc//CreateAlbumDto.md) - [CreateDeviceInfoDto](doc//CreateDeviceInfoDto.md) - [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md) + - [CreateTagDto](doc//CreateTagDto.md) - [CreateUserDto](doc//CreateUserDto.md) - [CuratedLocationsResponseDto](doc//CuratedLocationsResponseDto.md) - [CuratedObjectsResponseDto](doc//CuratedObjectsResponseDto.md) @@ -154,6 +161,7 @@ Class | Method | HTTP request | Description - [DeleteAssetStatus](doc//DeleteAssetStatus.md) - [DeviceInfoResponseDto](doc//DeviceInfoResponseDto.md) - [DeviceTypeEnum](doc//DeviceTypeEnum.md) + - [ExifEntity](doc//ExifEntity.md) - [ExifResponseDto](doc//ExifResponseDto.md) - [GetAssetByTimeBucketDto](doc//GetAssetByTimeBucketDto.md) - [GetAssetCountByTimeBucketDto](doc//GetAssetCountByTimeBucketDto.md) @@ -175,18 +183,24 @@ Class | Method | HTTP request | Description - [ServerStatsResponseDto](doc//ServerStatsResponseDto.md) - [ServerVersionReponseDto](doc//ServerVersionReponseDto.md) - [SignUpDto](doc//SignUpDto.md) + - [SmartInfoEntity](doc//SmartInfoEntity.md) - [SmartInfoResponseDto](doc//SmartInfoResponseDto.md) - [SystemConfigKey](doc//SystemConfigKey.md) - [SystemConfigResponseDto](doc//SystemConfigResponseDto.md) - [SystemConfigResponseItem](doc//SystemConfigResponseItem.md) + - [TagEntity](doc//TagEntity.md) + - [TagResponseDto](doc//TagResponseDto.md) + - [TagTypeEnum](doc//TagTypeEnum.md) - [ThumbnailFormat](doc//ThumbnailFormat.md) - [TimeGroupEnum](doc//TimeGroupEnum.md) - [UpdateAlbumDto](doc//UpdateAlbumDto.md) - [UpdateAssetDto](doc//UpdateAssetDto.md) - [UpdateDeviceInfoDto](doc//UpdateDeviceInfoDto.md) + - [UpdateTagDto](doc//UpdateTagDto.md) - [UpdateUserDto](doc//UpdateUserDto.md) - [UsageByUserDto](doc//UsageByUserDto.md) - [UserCountResponseDto](doc//UserCountResponseDto.md) + - [UserEntity](doc//UserEntity.md) - [UserResponseDto](doc//UserResponseDto.md) - [ValidateAccessTokenResponseDto](doc//ValidateAccessTokenResponseDto.md) diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 9140b2d5e..d6139bd58 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -26,7 +26,7 @@ Method | HTTP request | Description [**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} | [**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search | [**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{assetId} | -[**updateAssetById**](AssetApi.md#updateassetbyid) | **PUT** /asset/assetById/{assetId} | +[**updateAsset**](AssetApi.md#updateasset) | **PUT** /asset/{assetId} | [**uploadFile**](AssetApi.md#uploadfile) | **POST** /asset/upload | @@ -833,8 +833,8 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) -# **updateAssetById** -> AssetResponseDto updateAssetById(assetId, updateAssetDto) +# **updateAsset** +> AssetResponseDto updateAsset(assetId, updateAssetDto) @@ -855,10 +855,10 @@ final assetId = assetId_example; // String | final updateAssetDto = UpdateAssetDto(); // UpdateAssetDto | try { - final result = api_instance.updateAssetById(assetId, updateAssetDto); + final result = api_instance.updateAsset(assetId, updateAssetDto); print(result); } catch (e) { - print('Exception when calling AssetApi->updateAssetById: $e\n'); + print('Exception when calling AssetApi->updateAsset: $e\n'); } ``` diff --git a/mobile/openapi/doc/AssetEntity.md b/mobile/openapi/doc/AssetEntity.md new file mode 100644 index 000000000..8c83a94b0 --- /dev/null +++ b/mobile/openapi/doc/AssetEntity.md @@ -0,0 +1,34 @@ +# openapi.model.AssetEntity + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **String** | | +**deviceAssetId** | **String** | | +**userId** | **String** | | +**deviceId** | **String** | | +**type** | **String** | | +**originalPath** | **String** | | +**resizePath** | **String** | | +**webpPath** | **String** | | +**encodedVideoPath** | **String** | | +**createdAt** | **String** | | +**modifiedAt** | **String** | | +**isFavorite** | **bool** | | +**mimeType** | **String** | | +**checksum** | [**Object**](.md) | | [optional] +**duration** | **String** | | +**isVisible** | **bool** | | +**livePhotoVideoId** | **String** | | +**exifInfo** | [**ExifEntity**](ExifEntity.md) | | [optional] +**smartInfo** | [**SmartInfoEntity**](SmartInfoEntity.md) | | [optional] +**tags** | [**List**](TagEntity.md) | | [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/doc/AssetResponseDto.md b/mobile/openapi/doc/AssetResponseDto.md index 3e80e12c3..aa7d5494c 100644 --- a/mobile/openapi/doc/AssetResponseDto.md +++ b/mobile/openapi/doc/AssetResponseDto.md @@ -25,6 +25,7 @@ Name | Type | Description | Notes **exifInfo** | [**ExifResponseDto**](ExifResponseDto.md) | | [optional] **smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) | | [optional] **livePhotoVideoId** | **String** | | [optional] +**tags** | [**List**](TagResponseDto.md) | | [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/doc/CreateTagDto.md b/mobile/openapi/doc/CreateTagDto.md new file mode 100644 index 000000000..64222db49 --- /dev/null +++ b/mobile/openapi/doc/CreateTagDto.md @@ -0,0 +1,16 @@ +# openapi.model.CreateTagDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**type** | [**TagTypeEnum**](TagTypeEnum.md) | | +**name** | **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/ExifEntity.md b/mobile/openapi/doc/ExifEntity.md new file mode 100644 index 000000000..4de3fc96e --- /dev/null +++ b/mobile/openapi/doc/ExifEntity.md @@ -0,0 +1,39 @@ +# openapi.model.ExifEntity + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **String** | | +**assetId** | **String** | | +**description** | **String** | General info | +**exifImageWidth** | **num** | | +**exifImageHeight** | **num** | | +**fileSizeInByte** | **num** | | +**orientation** | **String** | | +**dateTimeOriginal** | [**DateTime**](DateTime.md) | | +**modifyDate** | [**DateTime**](DateTime.md) | | +**latitude** | **num** | | +**longitude** | **num** | | +**city** | **String** | | +**state** | **String** | | +**country** | **String** | | +**make** | **String** | Image info | +**model** | **String** | | +**imageName** | **String** | | +**lensModel** | **String** | | +**fNumber** | **num** | | +**focalLength** | **num** | | +**iso** | **num** | | +**exposureTime** | **num** | | +**fps** | **num** | Video info | [optional] +**asset** | [**AssetEntity**](AssetEntity.md) | | [optional] +**exifTextSearchableColumn** | **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/SmartInfoEntity.md b/mobile/openapi/doc/SmartInfoEntity.md new file mode 100644 index 000000000..6b22c3c00 --- /dev/null +++ b/mobile/openapi/doc/SmartInfoEntity.md @@ -0,0 +1,19 @@ +# openapi.model.SmartInfoEntity + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **String** | | +**assetId** | **String** | | +**tags** | **List** | | [default to const []] +**objects** | **List** | | [default to const []] +**asset** | [**AssetEntity**](AssetEntity.md) | | [optional] + +[[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/TagApi.md b/mobile/openapi/doc/TagApi.md new file mode 100644 index 000000000..5137a1dba --- /dev/null +++ b/mobile/openapi/doc/TagApi.md @@ -0,0 +1,221 @@ +# openapi.api.TagApi + +## Load the API package +```dart +import 'package:openapi/api.dart'; +``` + +All URIs are relative to */api* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**create**](TagApi.md#create) | **POST** /tag | +[**delete**](TagApi.md#delete) | **DELETE** /tag/{id} | +[**findAll**](TagApi.md#findall) | **GET** /tag | +[**findOne**](TagApi.md#findone) | **GET** /tag/{id} | +[**update**](TagApi.md#update) | **PATCH** /tag/{id} | + + +# **create** +> TagEntity create(createTagDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; + +final api_instance = TagApi(); +final createTagDto = CreateTagDto(); // CreateTagDto | + +try { + final result = api_instance.create(createTagDto); + print(result); +} catch (e) { + print('Exception when calling TagApi->create: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **createTagDto** | [**CreateTagDto**](CreateTagDto.md)| | + +### Return type + +[**TagEntity**](TagEntity.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **delete** +> TagEntity delete(id) + + + +### Example +```dart +import 'package:openapi/api.dart'; + +final api_instance = TagApi(); +final id = id_example; // String | + +try { + final result = api_instance.delete(id); + print(result); +} catch (e) { + print('Exception when calling TagApi->delete: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **id** | **String**| | + +### Return type + +[**TagEntity**](TagEntity.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **findAll** +> List findAll() + + + +### Example +```dart +import 'package:openapi/api.dart'; + +final api_instance = TagApi(); + +try { + final result = api_instance.findAll(); + print(result); +} catch (e) { + print('Exception when calling TagApi->findAll: $e\n'); +} +``` + +### Parameters +This endpoint does not need any parameter. + +### Return type + +[**List**](TagEntity.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **findOne** +> TagEntity findOne(id) + + + +### Example +```dart +import 'package:openapi/api.dart'; + +final api_instance = TagApi(); +final id = id_example; // String | + +try { + final result = api_instance.findOne(id); + print(result); +} catch (e) { + print('Exception when calling TagApi->findOne: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **id** | **String**| | + +### Return type + +[**TagEntity**](TagEntity.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **update** +> Object update(id, updateTagDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; + +final api_instance = TagApi(); +final id = id_example; // String | +final updateTagDto = UpdateTagDto(); // UpdateTagDto | + +try { + final result = api_instance.update(id, updateTagDto); + print(result); +} catch (e) { + print('Exception when calling TagApi->update: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **id** | **String**| | + **updateTagDto** | [**UpdateTagDto**](UpdateTagDto.md)| | + +### Return type + +[**Object**](Object.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/mobile/openapi/doc/TagEntity.md b/mobile/openapi/doc/TagEntity.md new file mode 100644 index 000000000..3b5bd36c2 --- /dev/null +++ b/mobile/openapi/doc/TagEntity.md @@ -0,0 +1,21 @@ +# openapi.model.TagEntity + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **String** | | +**type** | **String** | | +**name** | **String** | | +**userId** | **String** | | +**renameTagId** | **String** | | +**assets** | [**List**](AssetEntity.md) | | [default to const []] +**user** | [**UserEntity**](UserEntity.md) | | + +[[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/TagResponseDto.md b/mobile/openapi/doc/TagResponseDto.md new file mode 100644 index 000000000..6e2685477 --- /dev/null +++ b/mobile/openapi/doc/TagResponseDto.md @@ -0,0 +1,17 @@ +# openapi.model.TagResponseDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **String** | | +**type** | [**TagTypeEnum**](TagTypeEnum.md) | | +**name** | **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/TagTypeEnum.md b/mobile/openapi/doc/TagTypeEnum.md new file mode 100644 index 000000000..cede14090 --- /dev/null +++ b/mobile/openapi/doc/TagTypeEnum.md @@ -0,0 +1,14 @@ +# openapi.model.TagTypeEnum + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[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/UpdateAssetDto.md b/mobile/openapi/doc/UpdateAssetDto.md index dc4f1ff4b..f02b6d5ed 100644 --- a/mobile/openapi/doc/UpdateAssetDto.md +++ b/mobile/openapi/doc/UpdateAssetDto.md @@ -8,7 +8,8 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**isFavorite** | **bool** | | +**tagIds** | **List** | | [optional] [default to const []] +**isFavorite** | **bool** | | [optional] [[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/UpdateTagDto.md b/mobile/openapi/doc/UpdateTagDto.md new file mode 100644 index 000000000..ad4e551be --- /dev/null +++ b/mobile/openapi/doc/UpdateTagDto.md @@ -0,0 +1,16 @@ +# openapi.model.UpdateTagDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **String** | | [optional] +**renameTagId** | **String** | | [optional] + +[[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/UserEntity.md b/mobile/openapi/doc/UserEntity.md new file mode 100644 index 000000000..d342eb488 --- /dev/null +++ b/mobile/openapi/doc/UserEntity.md @@ -0,0 +1,27 @@ +# openapi.model.UserEntity + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **String** | | +**firstName** | **String** | | +**lastName** | **String** | | +**isAdmin** | **bool** | | +**email** | **String** | | +**password** | **String** | | [optional] +**salt** | **String** | | [optional] +**oauthId** | **String** | | +**profileImagePath** | **String** | | +**shouldChangePassword** | **bool** | | +**createdAt** | **String** | | +**deletedAt** | [**DateTime**](DateTime.md) | | [optional] +**tags** | [**List**](TagEntity.md) | | [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 72d4c4fd0..930cf69df 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -35,6 +35,7 @@ part 'api/job_api.dart'; part 'api/o_auth_api.dart'; part 'api/server_info_api.dart'; part 'api/system_config_api.dart'; +part 'api/tag_api.dart'; part 'api/user_api.dart'; part 'model/add_assets_dto.dart'; @@ -47,6 +48,7 @@ part 'model/all_job_status_response_dto.dart'; part 'model/asset_count_by_time_bucket.dart'; part 'model/asset_count_by_time_bucket_response_dto.dart'; part 'model/asset_count_by_user_id_response_dto.dart'; +part 'model/asset_entity.dart'; part 'model/asset_file_upload_response_dto.dart'; part 'model/asset_response_dto.dart'; part 'model/asset_type_enum.dart'; @@ -57,6 +59,7 @@ part 'model/check_existing_assets_response_dto.dart'; part 'model/create_album_dto.dart'; part 'model/create_device_info_dto.dart'; part 'model/create_profile_image_response_dto.dart'; +part 'model/create_tag_dto.dart'; part 'model/create_user_dto.dart'; part 'model/curated_locations_response_dto.dart'; part 'model/curated_objects_response_dto.dart'; @@ -65,6 +68,7 @@ part 'model/delete_asset_response_dto.dart'; part 'model/delete_asset_status.dart'; part 'model/device_info_response_dto.dart'; part 'model/device_type_enum.dart'; +part 'model/exif_entity.dart'; part 'model/exif_response_dto.dart'; part 'model/get_asset_by_time_bucket_dto.dart'; part 'model/get_asset_count_by_time_bucket_dto.dart'; @@ -86,18 +90,24 @@ part 'model/server_ping_response.dart'; part 'model/server_stats_response_dto.dart'; part 'model/server_version_reponse_dto.dart'; part 'model/sign_up_dto.dart'; +part 'model/smart_info_entity.dart'; part 'model/smart_info_response_dto.dart'; part 'model/system_config_key.dart'; part 'model/system_config_response_dto.dart'; part 'model/system_config_response_item.dart'; +part 'model/tag_entity.dart'; +part 'model/tag_response_dto.dart'; +part 'model/tag_type_enum.dart'; part 'model/thumbnail_format.dart'; part 'model/time_group_enum.dart'; part 'model/update_album_dto.dart'; part 'model/update_asset_dto.dart'; part 'model/update_device_info_dto.dart'; +part 'model/update_tag_dto.dart'; part 'model/update_user_dto.dart'; part 'model/usage_by_user_dto.dart'; part 'model/user_count_response_dto.dart'; +part 'model/user_entity.dart'; part 'model/user_response_dto.dart'; part 'model/validate_access_token_response_dto.dart'; diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index 73811beb6..c48f8b124 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -924,9 +924,9 @@ class AssetApi { /// * [String] assetId (required): /// /// * [UpdateAssetDto] updateAssetDto (required): - Future updateAssetByIdWithHttpInfo(String assetId, UpdateAssetDto updateAssetDto,) async { + Future updateAssetWithHttpInfo(String assetId, UpdateAssetDto updateAssetDto,) async { // ignore: prefer_const_declarations - final path = r'/asset/assetById/{assetId}' + final path = r'/asset/{assetId}' .replaceAll('{assetId}', assetId); // ignore: prefer_final_locals @@ -959,8 +959,8 @@ class AssetApi { /// * [String] assetId (required): /// /// * [UpdateAssetDto] updateAssetDto (required): - Future updateAssetById(String assetId, UpdateAssetDto updateAssetDto,) async { - final response = await updateAssetByIdWithHttpInfo(assetId, updateAssetDto,); + Future updateAsset(String assetId, UpdateAssetDto updateAssetDto,) async { + final response = await updateAssetWithHttpInfo(assetId, updateAssetDto,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/tag_api.dart b/mobile/openapi/lib/api/tag_api.dart new file mode 100644 index 000000000..662b6c772 --- /dev/null +++ b/mobile/openapi/lib/api/tag_api.dart @@ -0,0 +1,257 @@ +// +// 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 TagApi { + TagApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Performs an HTTP 'POST /tag' operation and returns the [Response]. + /// Parameters: + /// + /// * [CreateTagDto] createTagDto (required): + Future createWithHttpInfo(CreateTagDto createTagDto,) async { + // ignore: prefer_const_declarations + final path = r'/tag'; + + // ignore: prefer_final_locals + Object? postBody = createTagDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [CreateTagDto] createTagDto (required): + Future create(CreateTagDto createTagDto,) async { + final response = await createWithHttpInfo(createTagDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // 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), 'TagEntity',) as TagEntity; + + } + return null; + } + + /// Performs an HTTP 'DELETE /tag/{id}' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + Future deleteWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final path = r'/tag/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + Future delete(String id,) async { + final response = await deleteWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // 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), 'TagEntity',) as TagEntity; + + } + return null; + } + + /// Performs an HTTP 'GET /tag' operation and returns the [Response]. + Future findAllWithHttpInfo() async { + // ignore: prefer_const_declarations + final path = r'/tag'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + Future?> findAll() async { + final response = await findAllWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // 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) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(); + + } + return null; + } + + /// Performs an HTTP 'GET /tag/{id}' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + Future findOneWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final path = r'/tag/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + Future findOne(String id,) async { + final response = await findOneWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // 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), 'TagEntity',) as TagEntity; + + } + return null; + } + + /// Performs an HTTP 'PATCH /tag/{id}' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdateTagDto] updateTagDto (required): + Future updateWithHttpInfo(String id, UpdateTagDto updateTagDto,) async { + // ignore: prefer_const_declarations + final path = r'/tag/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = updateTagDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'PATCH', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdateTagDto] updateTagDto (required): + Future update(String id, UpdateTagDto updateTagDto,) async { + final response = await updateWithHttpInfo(id, updateTagDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // 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 null; + } +} diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index e1e6fc552..cf61c2a85 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -212,6 +212,8 @@ class ApiClient { return AssetCountByTimeBucketResponseDto.fromJson(value); case 'AssetCountByUserIdResponseDto': return AssetCountByUserIdResponseDto.fromJson(value); + case 'AssetEntity': + return AssetEntity.fromJson(value); case 'AssetFileUploadResponseDto': return AssetFileUploadResponseDto.fromJson(value); case 'AssetResponseDto': @@ -232,6 +234,8 @@ class ApiClient { return CreateDeviceInfoDto.fromJson(value); case 'CreateProfileImageResponseDto': return CreateProfileImageResponseDto.fromJson(value); + case 'CreateTagDto': + return CreateTagDto.fromJson(value); case 'CreateUserDto': return CreateUserDto.fromJson(value); case 'CuratedLocationsResponseDto': @@ -248,6 +252,8 @@ class ApiClient { return DeviceInfoResponseDto.fromJson(value); case 'DeviceTypeEnum': return DeviceTypeEnumTypeTransformer().decode(value); + case 'ExifEntity': + return ExifEntity.fromJson(value); case 'ExifResponseDto': return ExifResponseDto.fromJson(value); case 'GetAssetByTimeBucketDto': @@ -290,6 +296,8 @@ class ApiClient { return ServerVersionReponseDto.fromJson(value); case 'SignUpDto': return SignUpDto.fromJson(value); + case 'SmartInfoEntity': + return SmartInfoEntity.fromJson(value); case 'SmartInfoResponseDto': return SmartInfoResponseDto.fromJson(value); case 'SystemConfigKey': @@ -298,6 +306,12 @@ class ApiClient { return SystemConfigResponseDto.fromJson(value); case 'SystemConfigResponseItem': return SystemConfigResponseItem.fromJson(value); + case 'TagEntity': + return TagEntity.fromJson(value); + case 'TagResponseDto': + return TagResponseDto.fromJson(value); + case 'TagTypeEnum': + return TagTypeEnumTypeTransformer().decode(value); case 'ThumbnailFormat': return ThumbnailFormatTypeTransformer().decode(value); case 'TimeGroupEnum': @@ -308,12 +322,16 @@ class ApiClient { return UpdateAssetDto.fromJson(value); case 'UpdateDeviceInfoDto': return UpdateDeviceInfoDto.fromJson(value); + case 'UpdateTagDto': + return UpdateTagDto.fromJson(value); case 'UpdateUserDto': return UpdateUserDto.fromJson(value); case 'UsageByUserDto': return UsageByUserDto.fromJson(value); case 'UserCountResponseDto': return UserCountResponseDto.fromJson(value); + case 'UserEntity': + return UserEntity.fromJson(value); case 'UserResponseDto': return UserResponseDto.fromJson(value); case 'ValidateAccessTokenResponseDto': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 94e878289..828ff84fc 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -73,6 +73,9 @@ String parameterToString(dynamic value) { if (value is SystemConfigKey) { return SystemConfigKeyTypeTransformer().encode(value).toString(); } + if (value is TagTypeEnum) { + return TagTypeEnumTypeTransformer().encode(value).toString(); + } if (value is ThumbnailFormat) { return ThumbnailFormatTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart index f9901a358..ac170523d 100644 --- a/mobile/openapi/lib/model/album_response_dto.dart +++ b/mobile/openapi/lib/model/album_response_dto.dart @@ -43,51 +43,48 @@ class AlbumResponseDto { List assets; @override - bool operator ==(Object other) => - identical(this, other) || - other is AlbumResponseDto && - other.assetCount == assetCount && - other.id == id && - other.ownerId == ownerId && - other.albumName == albumName && - other.createdAt == createdAt && - other.albumThumbnailAssetId == albumThumbnailAssetId && - other.shared == shared && - other.sharedUsers == sharedUsers && - other.assets == assets; + bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto && + other.assetCount == assetCount && + other.id == id && + other.ownerId == ownerId && + other.albumName == albumName && + other.createdAt == createdAt && + other.albumThumbnailAssetId == albumThumbnailAssetId && + other.shared == shared && + other.sharedUsers == sharedUsers && + other.assets == assets; @override int get hashCode => - // ignore: unnecessary_parenthesis - (assetCount.hashCode) + - (id.hashCode) + - (ownerId.hashCode) + - (albumName.hashCode) + - (createdAt.hashCode) + - (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) + - (shared.hashCode) + - (sharedUsers.hashCode) + - (assets.hashCode); + // ignore: unnecessary_parenthesis + (assetCount.hashCode) + + (id.hashCode) + + (ownerId.hashCode) + + (albumName.hashCode) + + (createdAt.hashCode) + + (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) + + (shared.hashCode) + + (sharedUsers.hashCode) + + (assets.hashCode); @override - String toString() => - 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]'; + String toString() => 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]'; Map toJson() { final _json = {}; - _json[r'assetCount'] = assetCount; - _json[r'id'] = id; - _json[r'ownerId'] = ownerId; - _json[r'albumName'] = albumName; - _json[r'createdAt'] = createdAt; + _json[r'assetCount'] = assetCount; + _json[r'id'] = id; + _json[r'ownerId'] = ownerId; + _json[r'albumName'] = albumName; + _json[r'createdAt'] = createdAt; if (albumThumbnailAssetId != null) { _json[r'albumThumbnailAssetId'] = albumThumbnailAssetId; } else { _json[r'albumThumbnailAssetId'] = null; } - _json[r'shared'] = shared; - _json[r'sharedUsers'] = sharedUsers; - _json[r'assets'] = assets; + _json[r'shared'] = shared; + _json[r'sharedUsers'] = sharedUsers; + _json[r'assets'] = assets; return _json; } @@ -101,13 +98,13 @@ class AlbumResponseDto { // Ensure that the map contains the required keys. // Note 1: the values aren't checked for validity beyond being non-null. // Note 2: this code is stripped in release mode! - // assert(() { - // requiredKeys.forEach((key) { - // assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.'); - // assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.'); - // }); - // return true; - // }()); + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.'); + }); + return true; + }()); return AlbumResponseDto( assetCount: mapValueOfType(json, r'assetCount')!, @@ -115,8 +112,7 @@ class AlbumResponseDto { ownerId: mapValueOfType(json, r'ownerId')!, albumName: mapValueOfType(json, r'albumName')!, createdAt: mapValueOfType(json, r'createdAt')!, - albumThumbnailAssetId: - mapValueOfType(json, r'albumThumbnailAssetId'), + albumThumbnailAssetId: mapValueOfType(json, r'albumThumbnailAssetId'), shared: mapValueOfType(json, r'shared')!, sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!, assets: AssetResponseDto.listFromJson(json[r'assets'])!, @@ -125,10 +121,7 @@ class AlbumResponseDto { return null; } - static List? listFromJson( - dynamic json, { - bool growable = false, - }) { + static List? listFromJson(dynamic json, {bool growable = false,}) { final result = []; if (json is List && json.isNotEmpty) { for (final row in json) { @@ -156,18 +149,12 @@ class AlbumResponseDto { } // maps a json object with a list of AlbumResponseDto-objects as value to a dart map - static Map> mapListFromJson( - dynamic json, { - bool growable = false, - }) { + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { final map = >{}; if (json is Map && json.isNotEmpty) { json = json.cast(); // ignore: parameter_assignments for (final entry in json.entries) { - final value = AlbumResponseDto.listFromJson( - entry.value, - growable: growable, - ); + final value = AlbumResponseDto.listFromJson(entry.value, growable: growable,); if (value != null) { map[entry.key] = value; } @@ -189,3 +176,4 @@ class AlbumResponseDto { 'assets', }; } + diff --git a/mobile/openapi/lib/model/asset_entity.dart b/mobile/openapi/lib/model/asset_entity.dart new file mode 100644 index 000000000..b6ca35a1d --- /dev/null +++ b/mobile/openapi/lib/model/asset_entity.dart @@ -0,0 +1,384 @@ +// +// 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 AssetEntity { + /// Returns a new [AssetEntity] instance. + AssetEntity({ + required this.id, + required this.deviceAssetId, + required this.userId, + required this.deviceId, + required this.type, + required this.originalPath, + required this.resizePath, + required this.webpPath, + required this.encodedVideoPath, + required this.createdAt, + required this.modifiedAt, + required this.isFavorite, + required this.mimeType, + this.checksum, + required this.duration, + required this.isVisible, + required this.livePhotoVideoId, + this.exifInfo, + this.smartInfo, + this.tags = const [], + }); + + String id; + + String deviceAssetId; + + String userId; + + String deviceId; + + AssetEntityTypeEnum type; + + String originalPath; + + String? resizePath; + + String? webpPath; + + String encodedVideoPath; + + String createdAt; + + String modifiedAt; + + bool isFavorite; + + String? mimeType; + + Object? checksum; + + String? duration; + + bool isVisible; + + String? livePhotoVideoId; + + /// + /// 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. + /// + ExifEntity? exifInfo; + + /// + /// 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. + /// + SmartInfoEntity? smartInfo; + + List tags; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetEntity && + other.id == id && + other.deviceAssetId == deviceAssetId && + other.userId == userId && + other.deviceId == deviceId && + other.type == type && + other.originalPath == originalPath && + other.resizePath == resizePath && + other.webpPath == webpPath && + other.encodedVideoPath == encodedVideoPath && + other.createdAt == createdAt && + other.modifiedAt == modifiedAt && + other.isFavorite == isFavorite && + other.mimeType == mimeType && + other.checksum == checksum && + other.duration == duration && + other.isVisible == isVisible && + other.livePhotoVideoId == livePhotoVideoId && + other.exifInfo == exifInfo && + other.smartInfo == smartInfo && + other.tags == tags; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (id.hashCode) + + (deviceAssetId.hashCode) + + (userId.hashCode) + + (deviceId.hashCode) + + (type.hashCode) + + (originalPath.hashCode) + + (resizePath == null ? 0 : resizePath!.hashCode) + + (webpPath == null ? 0 : webpPath!.hashCode) + + (encodedVideoPath.hashCode) + + (createdAt.hashCode) + + (modifiedAt.hashCode) + + (isFavorite.hashCode) + + (mimeType == null ? 0 : mimeType!.hashCode) + + (checksum == null ? 0 : checksum!.hashCode) + + (duration == null ? 0 : duration!.hashCode) + + (isVisible.hashCode) + + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + + (exifInfo == null ? 0 : exifInfo!.hashCode) + + (smartInfo == null ? 0 : smartInfo!.hashCode) + + (tags.hashCode); + + @override + String toString() => 'AssetEntity[id=$id, deviceAssetId=$deviceAssetId, userId=$userId, deviceId=$deviceId, type=$type, originalPath=$originalPath, resizePath=$resizePath, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, checksum=$checksum, duration=$duration, isVisible=$isVisible, livePhotoVideoId=$livePhotoVideoId, exifInfo=$exifInfo, smartInfo=$smartInfo, tags=$tags]'; + + Map toJson() { + final _json = {}; + _json[r'id'] = id; + _json[r'deviceAssetId'] = deviceAssetId; + _json[r'userId'] = userId; + _json[r'deviceId'] = deviceId; + _json[r'type'] = type; + _json[r'originalPath'] = originalPath; + if (resizePath != null) { + _json[r'resizePath'] = resizePath; + } else { + _json[r'resizePath'] = null; + } + if (webpPath != null) { + _json[r'webpPath'] = webpPath; + } else { + _json[r'webpPath'] = null; + } + _json[r'encodedVideoPath'] = encodedVideoPath; + _json[r'createdAt'] = createdAt; + _json[r'modifiedAt'] = modifiedAt; + _json[r'isFavorite'] = isFavorite; + if (mimeType != null) { + _json[r'mimeType'] = mimeType; + } else { + _json[r'mimeType'] = null; + } + if (checksum != null) { + _json[r'checksum'] = checksum; + } else { + _json[r'checksum'] = null; + } + if (duration != null) { + _json[r'duration'] = duration; + } else { + _json[r'duration'] = null; + } + _json[r'isVisible'] = isVisible; + if (livePhotoVideoId != null) { + _json[r'livePhotoVideoId'] = livePhotoVideoId; + } else { + _json[r'livePhotoVideoId'] = null; + } + if (exifInfo != null) { + _json[r'exifInfo'] = exifInfo; + } else { + _json[r'exifInfo'] = null; + } + if (smartInfo != null) { + _json[r'smartInfo'] = smartInfo; + } else { + _json[r'smartInfo'] = null; + } + _json[r'tags'] = tags; + return _json; + } + + /// Returns a new [AssetEntity] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetEntity? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "AssetEntity[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "AssetEntity[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return AssetEntity( + id: mapValueOfType(json, r'id')!, + deviceAssetId: mapValueOfType(json, r'deviceAssetId')!, + userId: mapValueOfType(json, r'userId')!, + deviceId: mapValueOfType(json, r'deviceId')!, + type: AssetEntityTypeEnum.fromJson(json[r'type'])!, + originalPath: mapValueOfType(json, r'originalPath')!, + resizePath: mapValueOfType(json, r'resizePath'), + webpPath: mapValueOfType(json, r'webpPath'), + encodedVideoPath: mapValueOfType(json, r'encodedVideoPath')!, + createdAt: mapValueOfType(json, r'createdAt')!, + modifiedAt: mapValueOfType(json, r'modifiedAt')!, + isFavorite: mapValueOfType(json, r'isFavorite')!, + mimeType: mapValueOfType(json, r'mimeType'), + checksum: mapValueOfType(json, r'checksum'), + duration: mapValueOfType(json, r'duration'), + isVisible: mapValueOfType(json, r'isVisible')!, + livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), + exifInfo: ExifEntity.fromJson(json[r'exifInfo']), + smartInfo: SmartInfoEntity.fromJson(json[r'smartInfo']), + tags: TagEntity.listFromJson(json[r'tags'])!, + ); + } + 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 = AssetEntity.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 = AssetEntity.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetEntity-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetEntity.listFromJson(entry.value, growable: growable,); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + 'deviceAssetId', + 'userId', + 'deviceId', + 'type', + 'originalPath', + 'resizePath', + 'webpPath', + 'encodedVideoPath', + 'createdAt', + 'modifiedAt', + 'isFavorite', + 'mimeType', + 'duration', + 'isVisible', + 'livePhotoVideoId', + 'tags', + }; +} + + +class AssetEntityTypeEnum { + /// Instantiate a new enum with the provided [value]. + const AssetEntityTypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const IMAGE = AssetEntityTypeEnum._(r'IMAGE'); + static const VIDEO = AssetEntityTypeEnum._(r'VIDEO'); + static const AUDIO = AssetEntityTypeEnum._(r'AUDIO'); + static const OTHER = AssetEntityTypeEnum._(r'OTHER'); + + /// List of all possible values in this [enum][AssetEntityTypeEnum]. + static const values = [ + IMAGE, + VIDEO, + AUDIO, + OTHER, + ]; + + static AssetEntityTypeEnum? fromJson(dynamic value) => AssetEntityTypeEnumTypeTransformer().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 = AssetEntityTypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetEntityTypeEnum] to String, +/// and [decode] dynamic data back to [AssetEntityTypeEnum]. +class AssetEntityTypeEnumTypeTransformer { + factory AssetEntityTypeEnumTypeTransformer() => _instance ??= const AssetEntityTypeEnumTypeTransformer._(); + + const AssetEntityTypeEnumTypeTransformer._(); + + String encode(AssetEntityTypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetEntityTypeEnum. + /// + /// 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. + AssetEntityTypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data.toString()) { + case r'IMAGE': return AssetEntityTypeEnum.IMAGE; + case r'VIDEO': return AssetEntityTypeEnum.VIDEO; + case r'AUDIO': return AssetEntityTypeEnum.AUDIO; + case r'OTHER': return AssetEntityTypeEnum.OTHER; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetEntityTypeEnumTypeTransformer] instance. + static AssetEntityTypeEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index 44a41036a..ae15193a3 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -30,6 +30,7 @@ class AssetResponseDto { this.exifInfo, this.smartInfo, this.livePhotoVideoId, + this.tags = const [], }); AssetTypeEnum type; @@ -78,6 +79,8 @@ class AssetResponseDto { String? livePhotoVideoId; + List tags; + @override bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto && other.type == type && @@ -96,7 +99,8 @@ class AssetResponseDto { other.encodedVideoPath == encodedVideoPath && other.exifInfo == exifInfo && other.smartInfo == smartInfo && - other.livePhotoVideoId == livePhotoVideoId; + other.livePhotoVideoId == livePhotoVideoId && + other.tags == tags; @override int get hashCode => @@ -117,10 +121,11 @@ class AssetResponseDto { (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) + (exifInfo == null ? 0 : exifInfo!.hashCode) + (smartInfo == null ? 0 : smartInfo!.hashCode) + - (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode); + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + + (tags.hashCode); @override - String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId]'; + String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]'; Map toJson() { final _json = {}; @@ -169,6 +174,7 @@ class AssetResponseDto { } else { _json[r'livePhotoVideoId'] = null; } + _json[r'tags'] = tags; return _json; } @@ -208,6 +214,7 @@ class AssetResponseDto { exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']), smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']), livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), + tags: TagResponseDto.listFromJson(json[r'tags'])!, ); } return null; @@ -270,6 +277,7 @@ class AssetResponseDto { 'mimeType', 'duration', 'webpPath', + 'tags', }; } diff --git a/mobile/openapi/lib/model/create_tag_dto.dart b/mobile/openapi/lib/model/create_tag_dto.dart new file mode 100644 index 000000000..0d9a79255 --- /dev/null +++ b/mobile/openapi/lib/model/create_tag_dto.dart @@ -0,0 +1,119 @@ +// +// 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 CreateTagDto { + /// Returns a new [CreateTagDto] instance. + CreateTagDto({ + required this.type, + required this.name, + }); + + TagTypeEnum type; + + String name; + + @override + bool operator ==(Object other) => identical(this, other) || other is CreateTagDto && + other.type == type && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (type.hashCode) + + (name.hashCode); + + @override + String toString() => 'CreateTagDto[type=$type, name=$name]'; + + Map toJson() { + final _json = {}; + _json[r'type'] = type; + _json[r'name'] = name; + return _json; + } + + /// Returns a new [CreateTagDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static CreateTagDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "CreateTagDto[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "CreateTagDto[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return CreateTagDto( + type: TagTypeEnum.fromJson(json[r'type'])!, + name: mapValueOfType(json, r'name')!, + ); + } + 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 = CreateTagDto.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 = CreateTagDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of CreateTagDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = CreateTagDto.listFromJson(entry.value, growable: growable,); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'type', + 'name', + }; +} + diff --git a/mobile/openapi/lib/model/exif_entity.dart b/mobile/openapi/lib/model/exif_entity.dart new file mode 100644 index 000000000..208127120 --- /dev/null +++ b/mobile/openapi/lib/model/exif_entity.dart @@ -0,0 +1,414 @@ +// +// 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 ExifEntity { + /// Returns a new [ExifEntity] instance. + ExifEntity({ + required this.id, + required this.assetId, + required this.description, + required this.exifImageWidth, + required this.exifImageHeight, + required this.fileSizeInByte, + required this.orientation, + required this.dateTimeOriginal, + required this.modifyDate, + required this.latitude, + required this.longitude, + required this.city, + required this.state, + required this.country, + required this.make, + required this.model, + required this.imageName, + required this.lensModel, + required this.fNumber, + required this.focalLength, + required this.iso, + required this.exposureTime, + this.fps, + this.asset, + required this.exifTextSearchableColumn, + }); + + String id; + + String assetId; + + /// General info + String description; + + num? exifImageWidth; + + num? exifImageHeight; + + num? fileSizeInByte; + + String? orientation; + + DateTime? dateTimeOriginal; + + DateTime? modifyDate; + + num? latitude; + + num? longitude; + + String? city; + + String? state; + + String? country; + + /// Image info + String? make; + + String? model; + + String? imageName; + + String? lensModel; + + num? fNumber; + + num? focalLength; + + num? iso; + + num? exposureTime; + + /// Video info + num? fps; + + /// + /// 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. + /// + AssetEntity? asset; + + String exifTextSearchableColumn; + + @override + bool operator ==(Object other) => identical(this, other) || other is ExifEntity && + other.id == id && + other.assetId == assetId && + other.description == description && + other.exifImageWidth == exifImageWidth && + other.exifImageHeight == exifImageHeight && + other.fileSizeInByte == fileSizeInByte && + other.orientation == orientation && + other.dateTimeOriginal == dateTimeOriginal && + other.modifyDate == modifyDate && + other.latitude == latitude && + other.longitude == longitude && + other.city == city && + other.state == state && + other.country == country && + other.make == make && + other.model == model && + other.imageName == imageName && + other.lensModel == lensModel && + other.fNumber == fNumber && + other.focalLength == focalLength && + other.iso == iso && + other.exposureTime == exposureTime && + other.fps == fps && + other.asset == asset && + other.exifTextSearchableColumn == exifTextSearchableColumn; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (id.hashCode) + + (assetId.hashCode) + + (description.hashCode) + + (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + + (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + + (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + + (orientation == null ? 0 : orientation!.hashCode) + + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + + (modifyDate == null ? 0 : modifyDate!.hashCode) + + (latitude == null ? 0 : latitude!.hashCode) + + (longitude == null ? 0 : longitude!.hashCode) + + (city == null ? 0 : city!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (imageName == null ? 0 : imageName!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (fNumber == null ? 0 : fNumber!.hashCode) + + (focalLength == null ? 0 : focalLength!.hashCode) + + (iso == null ? 0 : iso!.hashCode) + + (exposureTime == null ? 0 : exposureTime!.hashCode) + + (fps == null ? 0 : fps!.hashCode) + + (asset == null ? 0 : asset!.hashCode) + + (exifTextSearchableColumn.hashCode); + + @override + String toString() => 'ExifEntity[id=$id, assetId=$assetId, description=$description, exifImageWidth=$exifImageWidth, exifImageHeight=$exifImageHeight, fileSizeInByte=$fileSizeInByte, orientation=$orientation, dateTimeOriginal=$dateTimeOriginal, modifyDate=$modifyDate, latitude=$latitude, longitude=$longitude, city=$city, state=$state, country=$country, make=$make, model=$model, imageName=$imageName, lensModel=$lensModel, fNumber=$fNumber, focalLength=$focalLength, iso=$iso, exposureTime=$exposureTime, fps=$fps, asset=$asset, exifTextSearchableColumn=$exifTextSearchableColumn]'; + + Map toJson() { + final _json = {}; + _json[r'id'] = id; + _json[r'assetId'] = assetId; + _json[r'description'] = description; + if (exifImageWidth != null) { + _json[r'exifImageWidth'] = exifImageWidth; + } else { + _json[r'exifImageWidth'] = null; + } + if (exifImageHeight != null) { + _json[r'exifImageHeight'] = exifImageHeight; + } else { + _json[r'exifImageHeight'] = null; + } + if (fileSizeInByte != null) { + _json[r'fileSizeInByte'] = fileSizeInByte; + } else { + _json[r'fileSizeInByte'] = null; + } + if (orientation != null) { + _json[r'orientation'] = orientation; + } else { + _json[r'orientation'] = null; + } + if (dateTimeOriginal != null) { + _json[r'dateTimeOriginal'] = dateTimeOriginal!.toUtc().toIso8601String(); + } else { + _json[r'dateTimeOriginal'] = null; + } + if (modifyDate != null) { + _json[r'modifyDate'] = modifyDate!.toUtc().toIso8601String(); + } else { + _json[r'modifyDate'] = null; + } + if (latitude != null) { + _json[r'latitude'] = latitude; + } else { + _json[r'latitude'] = null; + } + if (longitude != null) { + _json[r'longitude'] = longitude; + } else { + _json[r'longitude'] = null; + } + if (city != null) { + _json[r'city'] = city; + } else { + _json[r'city'] = null; + } + if (state != null) { + _json[r'state'] = state; + } else { + _json[r'state'] = null; + } + if (country != null) { + _json[r'country'] = country; + } else { + _json[r'country'] = null; + } + if (make != null) { + _json[r'make'] = make; + } else { + _json[r'make'] = null; + } + if (model != null) { + _json[r'model'] = model; + } else { + _json[r'model'] = null; + } + if (imageName != null) { + _json[r'imageName'] = imageName; + } else { + _json[r'imageName'] = null; + } + if (lensModel != null) { + _json[r'lensModel'] = lensModel; + } else { + _json[r'lensModel'] = null; + } + if (fNumber != null) { + _json[r'fNumber'] = fNumber; + } else { + _json[r'fNumber'] = null; + } + if (focalLength != null) { + _json[r'focalLength'] = focalLength; + } else { + _json[r'focalLength'] = null; + } + if (iso != null) { + _json[r'iso'] = iso; + } else { + _json[r'iso'] = null; + } + if (exposureTime != null) { + _json[r'exposureTime'] = exposureTime; + } else { + _json[r'exposureTime'] = null; + } + if (fps != null) { + _json[r'fps'] = fps; + } else { + _json[r'fps'] = null; + } + if (asset != null) { + _json[r'asset'] = asset; + } else { + _json[r'asset'] = null; + } + _json[r'exifTextSearchableColumn'] = exifTextSearchableColumn; + return _json; + } + + /// Returns a new [ExifEntity] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ExifEntity? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "ExifEntity[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "ExifEntity[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return ExifEntity( + id: mapValueOfType(json, r'id')!, + assetId: mapValueOfType(json, r'assetId')!, + description: mapValueOfType(json, r'description')!, + exifImageWidth: json[r'exifImageWidth'] == null + ? null + : num.parse(json[r'exifImageWidth'].toString()), + exifImageHeight: json[r'exifImageHeight'] == null + ? null + : num.parse(json[r'exifImageHeight'].toString()), + fileSizeInByte: json[r'fileSizeInByte'] == null + ? null + : num.parse(json[r'fileSizeInByte'].toString()), + orientation: mapValueOfType(json, r'orientation'), + dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', ''), + modifyDate: mapDateTime(json, r'modifyDate', ''), + latitude: json[r'latitude'] == null + ? null + : num.parse(json[r'latitude'].toString()), + longitude: json[r'longitude'] == null + ? null + : num.parse(json[r'longitude'].toString()), + city: mapValueOfType(json, r'city'), + state: mapValueOfType(json, r'state'), + country: mapValueOfType(json, r'country'), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + imageName: mapValueOfType(json, r'imageName'), + lensModel: mapValueOfType(json, r'lensModel'), + fNumber: json[r'fNumber'] == null + ? null + : num.parse(json[r'fNumber'].toString()), + focalLength: json[r'focalLength'] == null + ? null + : num.parse(json[r'focalLength'].toString()), + iso: json[r'iso'] == null + ? null + : num.parse(json[r'iso'].toString()), + exposureTime: json[r'exposureTime'] == null + ? null + : num.parse(json[r'exposureTime'].toString()), + fps: json[r'fps'] == null + ? null + : num.parse(json[r'fps'].toString()), + asset: AssetEntity.fromJson(json[r'asset']), + exifTextSearchableColumn: mapValueOfType(json, r'exifTextSearchableColumn')!, + ); + } + 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 = ExifEntity.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 = ExifEntity.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ExifEntity-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ExifEntity.listFromJson(entry.value, growable: growable,); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + 'assetId', + 'description', + 'exifImageWidth', + 'exifImageHeight', + 'fileSizeInByte', + 'orientation', + 'dateTimeOriginal', + 'modifyDate', + 'latitude', + 'longitude', + 'city', + 'state', + 'country', + 'make', + 'model', + 'imageName', + 'lensModel', + 'fNumber', + 'focalLength', + 'iso', + 'exposureTime', + 'exifTextSearchableColumn', + }; +} + diff --git a/mobile/openapi/lib/model/smart_info_entity.dart b/mobile/openapi/lib/model/smart_info_entity.dart new file mode 100644 index 000000000..cf3e33595 --- /dev/null +++ b/mobile/openapi/lib/model/smart_info_entity.dart @@ -0,0 +1,164 @@ +// +// 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 SmartInfoEntity { + /// Returns a new [SmartInfoEntity] instance. + SmartInfoEntity({ + required this.id, + required this.assetId, + this.tags = const [], + this.objects = const [], + this.asset, + }); + + String id; + + String assetId; + + List? tags; + + List? objects; + + /// + /// 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. + /// + AssetEntity? asset; + + @override + bool operator ==(Object other) => identical(this, other) || other is SmartInfoEntity && + other.id == id && + other.assetId == assetId && + other.tags == tags && + other.objects == objects && + other.asset == asset; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (id.hashCode) + + (assetId.hashCode) + + (tags == null ? 0 : tags!.hashCode) + + (objects == null ? 0 : objects!.hashCode) + + (asset == null ? 0 : asset!.hashCode); + + @override + String toString() => 'SmartInfoEntity[id=$id, assetId=$assetId, tags=$tags, objects=$objects, asset=$asset]'; + + Map toJson() { + final _json = {}; + _json[r'id'] = id; + _json[r'assetId'] = assetId; + if (tags != null) { + _json[r'tags'] = tags; + } else { + _json[r'tags'] = null; + } + if (objects != null) { + _json[r'objects'] = objects; + } else { + _json[r'objects'] = null; + } + if (asset != null) { + _json[r'asset'] = asset; + } else { + _json[r'asset'] = null; + } + return _json; + } + + /// Returns a new [SmartInfoEntity] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SmartInfoEntity? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "SmartInfoEntity[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "SmartInfoEntity[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return SmartInfoEntity( + id: mapValueOfType(json, r'id')!, + assetId: mapValueOfType(json, r'assetId')!, + tags: json[r'tags'] is List + ? (json[r'tags'] as List).cast() + : const [], + objects: json[r'objects'] is List + ? (json[r'objects'] as List).cast() + : const [], + asset: AssetEntity.fromJson(json[r'asset']), + ); + } + 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 = SmartInfoEntity.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 = SmartInfoEntity.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SmartInfoEntity-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SmartInfoEntity.listFromJson(entry.value, growable: growable,); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + 'assetId', + 'tags', + 'objects', + }; +} + diff --git a/mobile/openapi/lib/model/tag_entity.dart b/mobile/openapi/lib/model/tag_entity.dart new file mode 100644 index 000000000..d973c3f88 --- /dev/null +++ b/mobile/openapi/lib/model/tag_entity.dart @@ -0,0 +1,236 @@ +// +// 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 TagEntity { + /// Returns a new [TagEntity] instance. + TagEntity({ + required this.id, + required this.type, + required this.name, + required this.userId, + required this.renameTagId, + this.assets = const [], + required this.user, + }); + + String id; + + TagEntityTypeEnum type; + + String name; + + String userId; + + String renameTagId; + + List assets; + + UserEntity user; + + @override + bool operator ==(Object other) => identical(this, other) || other is TagEntity && + other.id == id && + other.type == type && + other.name == name && + other.userId == userId && + other.renameTagId == renameTagId && + other.assets == assets && + other.user == user; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (id.hashCode) + + (type.hashCode) + + (name.hashCode) + + (userId.hashCode) + + (renameTagId.hashCode) + + (assets.hashCode) + + (user.hashCode); + + @override + String toString() => 'TagEntity[id=$id, type=$type, name=$name, userId=$userId, renameTagId=$renameTagId, assets=$assets, user=$user]'; + + Map toJson() { + final _json = {}; + _json[r'id'] = id; + _json[r'type'] = type; + _json[r'name'] = name; + _json[r'userId'] = userId; + _json[r'renameTagId'] = renameTagId; + _json[r'assets'] = assets; + _json[r'user'] = user; + return _json; + } + + /// Returns a new [TagEntity] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TagEntity? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "TagEntity[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "TagEntity[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return TagEntity( + id: mapValueOfType(json, r'id')!, + type: TagEntityTypeEnum.fromJson(json[r'type'])!, + name: mapValueOfType(json, r'name')!, + userId: mapValueOfType(json, r'userId')!, + renameTagId: mapValueOfType(json, r'renameTagId')!, + assets: AssetEntity.listFromJson(json[r'assets'])!, + user: UserEntity.fromJson(json[r'user'])!, + ); + } + 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 = TagEntity.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 = TagEntity.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TagEntity-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TagEntity.listFromJson(entry.value, growable: growable,); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + 'type', + 'name', + 'userId', + 'renameTagId', + 'assets', + 'user', + }; +} + + +class TagEntityTypeEnum { + /// Instantiate a new enum with the provided [value]. + const TagEntityTypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const OBJECT = TagEntityTypeEnum._(r'OBJECT'); + static const FACE = TagEntityTypeEnum._(r'FACE'); + static const CUSTOM = TagEntityTypeEnum._(r'CUSTOM'); + + /// List of all possible values in this [enum][TagEntityTypeEnum]. + static const values = [ + OBJECT, + FACE, + CUSTOM, + ]; + + static TagEntityTypeEnum? fromJson(dynamic value) => TagEntityTypeEnumTypeTransformer().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 = TagEntityTypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [TagEntityTypeEnum] to String, +/// and [decode] dynamic data back to [TagEntityTypeEnum]. +class TagEntityTypeEnumTypeTransformer { + factory TagEntityTypeEnumTypeTransformer() => _instance ??= const TagEntityTypeEnumTypeTransformer._(); + + const TagEntityTypeEnumTypeTransformer._(); + + String encode(TagEntityTypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a TagEntityTypeEnum. + /// + /// 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. + TagEntityTypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data.toString()) { + case r'OBJECT': return TagEntityTypeEnum.OBJECT; + case r'FACE': return TagEntityTypeEnum.FACE; + case r'CUSTOM': return TagEntityTypeEnum.CUSTOM; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [TagEntityTypeEnumTypeTransformer] instance. + static TagEntityTypeEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/tag_response_dto.dart b/mobile/openapi/lib/model/tag_response_dto.dart new file mode 100644 index 000000000..d4ed671a2 --- /dev/null +++ b/mobile/openapi/lib/model/tag_response_dto.dart @@ -0,0 +1,127 @@ +// +// 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 TagResponseDto { + /// Returns a new [TagResponseDto] instance. + TagResponseDto({ + required this.id, + required this.type, + required this.name, + }); + + String id; + + TagTypeEnum type; + + String name; + + @override + bool operator ==(Object other) => identical(this, other) || other is TagResponseDto && + other.id == id && + other.type == type && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (id.hashCode) + + (type.hashCode) + + (name.hashCode); + + @override + String toString() => 'TagResponseDto[id=$id, type=$type, name=$name]'; + + Map toJson() { + final _json = {}; + _json[r'id'] = id; + _json[r'type'] = type; + _json[r'name'] = name; + return _json; + } + + /// Returns a new [TagResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TagResponseDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "TagResponseDto[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "TagResponseDto[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return TagResponseDto( + id: mapValueOfType(json, r'id')!, + type: TagTypeEnum.fromJson(json[r'type'])!, + name: mapValueOfType(json, r'name')!, + ); + } + 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 = TagResponseDto.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 = TagResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TagResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TagResponseDto.listFromJson(entry.value, growable: growable,); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + 'type', + 'name', + }; +} + diff --git a/mobile/openapi/lib/model/tag_type_enum.dart b/mobile/openapi/lib/model/tag_type_enum.dart new file mode 100644 index 000000000..66c44d130 --- /dev/null +++ b/mobile/openapi/lib/model/tag_type_enum.dart @@ -0,0 +1,88 @@ +// +// 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 TagTypeEnum { + /// Instantiate a new enum with the provided [value]. + const TagTypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const OBJECT = TagTypeEnum._(r'OBJECT'); + static const FACE = TagTypeEnum._(r'FACE'); + static const CUSTOM = TagTypeEnum._(r'CUSTOM'); + + /// List of all possible values in this [enum][TagTypeEnum]. + static const values = [ + OBJECT, + FACE, + CUSTOM, + ]; + + static TagTypeEnum? fromJson(dynamic value) => TagTypeEnumTypeTransformer().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 = TagTypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [TagTypeEnum] to String, +/// and [decode] dynamic data back to [TagTypeEnum]. +class TagTypeEnumTypeTransformer { + factory TagTypeEnumTypeTransformer() => _instance ??= const TagTypeEnumTypeTransformer._(); + + const TagTypeEnumTypeTransformer._(); + + String encode(TagTypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a TagTypeEnum. + /// + /// 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. + TagTypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data.toString()) { + case r'OBJECT': return TagTypeEnum.OBJECT; + case r'FACE': return TagTypeEnum.FACE; + case r'CUSTOM': return TagTypeEnum.CUSTOM; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [TagTypeEnumTypeTransformer] instance. + static TagTypeEnumTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/update_asset_dto.dart b/mobile/openapi/lib/model/update_asset_dto.dart index 84db0f2a1..cb7fd1c88 100644 --- a/mobile/openapi/lib/model/update_asset_dto.dart +++ b/mobile/openapi/lib/model/update_asset_dto.dart @@ -13,26 +13,42 @@ part of openapi.api; class UpdateAssetDto { /// Returns a new [UpdateAssetDto] instance. UpdateAssetDto({ - required this.isFavorite, + this.tagIds = const [], + this.isFavorite, }); - bool isFavorite; + List tagIds; + + /// + /// 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. + /// + bool? isFavorite; @override bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto && + other.tagIds == tagIds && other.isFavorite == isFavorite; @override int get hashCode => // ignore: unnecessary_parenthesis - (isFavorite.hashCode); + (tagIds.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode); @override - String toString() => 'UpdateAssetDto[isFavorite=$isFavorite]'; + String toString() => 'UpdateAssetDto[tagIds=$tagIds, isFavorite=$isFavorite]'; Map toJson() { final _json = {}; + _json[r'tagIds'] = tagIds; + if (isFavorite != null) { _json[r'isFavorite'] = isFavorite; + } else { + _json[r'isFavorite'] = null; + } return _json; } @@ -55,7 +71,10 @@ class UpdateAssetDto { }()); return UpdateAssetDto( - isFavorite: mapValueOfType(json, r'isFavorite')!, + tagIds: json[r'tagIds'] is List + ? (json[r'tagIds'] as List).cast() + : const [], + isFavorite: mapValueOfType(json, r'isFavorite'), ); } return null; @@ -105,7 +124,6 @@ class UpdateAssetDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'isFavorite', }; } diff --git a/mobile/openapi/lib/model/update_tag_dto.dart b/mobile/openapi/lib/model/update_tag_dto.dart new file mode 100644 index 000000000..a651405ec --- /dev/null +++ b/mobile/openapi/lib/model/update_tag_dto.dart @@ -0,0 +1,137 @@ +// +// 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 UpdateTagDto { + /// Returns a new [UpdateTagDto] instance. + UpdateTagDto({ + this.name, + this.renameTagId, + }); + + /// + /// 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. + /// + String? name; + + /// + /// 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. + /// + String? renameTagId; + + @override + bool operator ==(Object other) => identical(this, other) || other is UpdateTagDto && + other.name == name && + other.renameTagId == renameTagId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (name == null ? 0 : name!.hashCode) + + (renameTagId == null ? 0 : renameTagId!.hashCode); + + @override + String toString() => 'UpdateTagDto[name=$name, renameTagId=$renameTagId]'; + + Map toJson() { + final _json = {}; + if (name != null) { + _json[r'name'] = name; + } else { + _json[r'name'] = null; + } + if (renameTagId != null) { + _json[r'renameTagId'] = renameTagId; + } else { + _json[r'renameTagId'] = null; + } + return _json; + } + + /// Returns a new [UpdateTagDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UpdateTagDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "UpdateTagDto[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "UpdateTagDto[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return UpdateTagDto( + name: mapValueOfType(json, r'name'), + renameTagId: mapValueOfType(json, r'renameTagId'), + ); + } + 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 = UpdateTagDto.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 = UpdateTagDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UpdateTagDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UpdateTagDto.listFromJson(entry.value, growable: growable,); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/user_entity.dart b/mobile/openapi/lib/model/user_entity.dart new file mode 100644 index 000000000..7862d296e --- /dev/null +++ b/mobile/openapi/lib/model/user_entity.dart @@ -0,0 +1,234 @@ +// +// 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 UserEntity { + /// Returns a new [UserEntity] instance. + UserEntity({ + required this.id, + required this.firstName, + required this.lastName, + required this.isAdmin, + required this.email, + this.password, + this.salt, + required this.oauthId, + required this.profileImagePath, + required this.shouldChangePassword, + required this.createdAt, + this.deletedAt, + this.tags = const [], + }); + + String id; + + String firstName; + + String lastName; + + bool isAdmin; + + String email; + + /// + /// 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. + /// + String? password; + + /// + /// 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. + /// + String? salt; + + String oauthId; + + String profileImagePath; + + bool shouldChangePassword; + + String createdAt; + + /// + /// 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. + /// + DateTime? deletedAt; + + List tags; + + @override + bool operator ==(Object other) => identical(this, other) || other is UserEntity && + other.id == id && + other.firstName == firstName && + other.lastName == lastName && + other.isAdmin == isAdmin && + other.email == email && + other.password == password && + other.salt == salt && + other.oauthId == oauthId && + other.profileImagePath == profileImagePath && + other.shouldChangePassword == shouldChangePassword && + other.createdAt == createdAt && + other.deletedAt == deletedAt && + other.tags == tags; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (id.hashCode) + + (firstName.hashCode) + + (lastName.hashCode) + + (isAdmin.hashCode) + + (email.hashCode) + + (password == null ? 0 : password!.hashCode) + + (salt == null ? 0 : salt!.hashCode) + + (oauthId.hashCode) + + (profileImagePath.hashCode) + + (shouldChangePassword.hashCode) + + (createdAt.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (tags.hashCode); + + @override + String toString() => 'UserEntity[id=$id, firstName=$firstName, lastName=$lastName, isAdmin=$isAdmin, email=$email, password=$password, salt=$salt, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, createdAt=$createdAt, deletedAt=$deletedAt, tags=$tags]'; + + Map toJson() { + final _json = {}; + _json[r'id'] = id; + _json[r'firstName'] = firstName; + _json[r'lastName'] = lastName; + _json[r'isAdmin'] = isAdmin; + _json[r'email'] = email; + if (password != null) { + _json[r'password'] = password; + } else { + _json[r'password'] = null; + } + if (salt != null) { + _json[r'salt'] = salt; + } else { + _json[r'salt'] = null; + } + _json[r'oauthId'] = oauthId; + _json[r'profileImagePath'] = profileImagePath; + _json[r'shouldChangePassword'] = shouldChangePassword; + _json[r'createdAt'] = createdAt; + if (deletedAt != null) { + _json[r'deletedAt'] = deletedAt!.toUtc().toIso8601String(); + } else { + _json[r'deletedAt'] = null; + } + _json[r'tags'] = tags; + return _json; + } + + /// Returns a new [UserEntity] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UserEntity? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "UserEntity[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "UserEntity[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return UserEntity( + id: mapValueOfType(json, r'id')!, + firstName: mapValueOfType(json, r'firstName')!, + lastName: mapValueOfType(json, r'lastName')!, + isAdmin: mapValueOfType(json, r'isAdmin')!, + email: mapValueOfType(json, r'email')!, + password: mapValueOfType(json, r'password'), + salt: mapValueOfType(json, r'salt'), + oauthId: mapValueOfType(json, r'oauthId')!, + profileImagePath: mapValueOfType(json, r'profileImagePath')!, + shouldChangePassword: mapValueOfType(json, r'shouldChangePassword')!, + createdAt: mapValueOfType(json, r'createdAt')!, + deletedAt: mapDateTime(json, r'deletedAt', ''), + tags: TagEntity.listFromJson(json[r'tags'])!, + ); + } + 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 = UserEntity.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 = UserEntity.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UserEntity-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UserEntity.listFromJson(entry.value, growable: growable,); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + 'firstName', + 'lastName', + 'isAdmin', + 'email', + 'oauthId', + 'profileImagePath', + 'shouldChangePassword', + 'createdAt', + 'tags', + }; +} + diff --git a/mobile/openapi/test/asset_entity_test.dart b/mobile/openapi/test/asset_entity_test.dart new file mode 100644 index 000000000..d34d06612 --- /dev/null +++ b/mobile/openapi/test/asset_entity_test.dart @@ -0,0 +1,122 @@ +// +// 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 AssetEntity +void main() { + // final instance = AssetEntity(); + + group('test AssetEntity', () { + // String id + test('to test the property `id`', () async { + // TODO + }); + + // String deviceAssetId + test('to test the property `deviceAssetId`', () async { + // TODO + }); + + // String userId + test('to test the property `userId`', () async { + // TODO + }); + + // String deviceId + test('to test the property `deviceId`', () async { + // TODO + }); + + // String type + test('to test the property `type`', () async { + // TODO + }); + + // String originalPath + test('to test the property `originalPath`', () async { + // TODO + }); + + // String resizePath + test('to test the property `resizePath`', () async { + // TODO + }); + + // String webpPath + test('to test the property `webpPath`', () async { + // TODO + }); + + // String encodedVideoPath + test('to test the property `encodedVideoPath`', () async { + // TODO + }); + + // String createdAt + test('to test the property `createdAt`', () async { + // TODO + }); + + // String modifiedAt + test('to test the property `modifiedAt`', () async { + // TODO + }); + + // bool isFavorite + test('to test the property `isFavorite`', () async { + // TODO + }); + + // String mimeType + test('to test the property `mimeType`', () async { + // TODO + }); + + // Object checksum + test('to test the property `checksum`', () async { + // TODO + }); + + // String duration + test('to test the property `duration`', () async { + // TODO + }); + + // bool isVisible + test('to test the property `isVisible`', () async { + // TODO + }); + + // String livePhotoVideoId + test('to test the property `livePhotoVideoId`', () async { + // TODO + }); + + // ExifEntity exifInfo + test('to test the property `exifInfo`', () async { + // TODO + }); + + // SmartInfoEntity smartInfo + test('to test the property `smartInfo`', () async { + // TODO + }); + + // List tags (default value: const []) + test('to test the property `tags`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/create_tag_dto_test.dart b/mobile/openapi/test/create_tag_dto_test.dart new file mode 100644 index 000000000..8df20a4ef --- /dev/null +++ b/mobile/openapi/test/create_tag_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 CreateTagDto +void main() { + // final instance = CreateTagDto(); + + group('test CreateTagDto', () { + // TagTypeEnum type + test('to test the property `type`', () async { + // TODO + }); + + // String name + test('to test the property `name`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/exif_entity_test.dart b/mobile/openapi/test/exif_entity_test.dart new file mode 100644 index 000000000..2cc95f966 --- /dev/null +++ b/mobile/openapi/test/exif_entity_test.dart @@ -0,0 +1,150 @@ +// +// 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 ExifEntity +void main() { + // final instance = ExifEntity(); + + group('test ExifEntity', () { + // String id + test('to test the property `id`', () async { + // TODO + }); + + // String assetId + test('to test the property `assetId`', () async { + // TODO + }); + + // General info + // String description + test('to test the property `description`', () async { + // TODO + }); + + // num exifImageWidth + test('to test the property `exifImageWidth`', () async { + // TODO + }); + + // num exifImageHeight + test('to test the property `exifImageHeight`', () async { + // TODO + }); + + // num fileSizeInByte + test('to test the property `fileSizeInByte`', () async { + // TODO + }); + + // String orientation + test('to test the property `orientation`', () async { + // TODO + }); + + // DateTime dateTimeOriginal + test('to test the property `dateTimeOriginal`', () async { + // TODO + }); + + // DateTime modifyDate + test('to test the property `modifyDate`', () async { + // TODO + }); + + // num latitude + test('to test the property `latitude`', () async { + // TODO + }); + + // num longitude + test('to test the property `longitude`', () async { + // TODO + }); + + // String city + test('to test the property `city`', () async { + // TODO + }); + + // String state + test('to test the property `state`', () async { + // TODO + }); + + // String country + test('to test the property `country`', () async { + // TODO + }); + + // Image info + // String make + test('to test the property `make`', () async { + // TODO + }); + + // String model + test('to test the property `model`', () async { + // TODO + }); + + // String imageName + test('to test the property `imageName`', () async { + // TODO + }); + + // String lensModel + test('to test the property `lensModel`', () async { + // TODO + }); + + // num fNumber + test('to test the property `fNumber`', () async { + // TODO + }); + + // num focalLength + test('to test the property `focalLength`', () async { + // TODO + }); + + // num iso + test('to test the property `iso`', () async { + // TODO + }); + + // num exposureTime + test('to test the property `exposureTime`', () async { + // TODO + }); + + // Video info + // num fps + test('to test the property `fps`', () async { + // TODO + }); + + // AssetEntity asset + test('to test the property `asset`', () async { + // TODO + }); + + // String exifTextSearchableColumn + test('to test the property `exifTextSearchableColumn`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/smart_info_entity_test.dart b/mobile/openapi/test/smart_info_entity_test.dart new file mode 100644 index 000000000..ca4ce46c5 --- /dev/null +++ b/mobile/openapi/test/smart_info_entity_test.dart @@ -0,0 +1,47 @@ +// +// 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 SmartInfoEntity +void main() { + // final instance = SmartInfoEntity(); + + group('test SmartInfoEntity', () { + // String id + test('to test the property `id`', () async { + // TODO + }); + + // String assetId + test('to test the property `assetId`', () async { + // TODO + }); + + // List tags (default value: const []) + test('to test the property `tags`', () async { + // TODO + }); + + // List objects (default value: const []) + test('to test the property `objects`', () async { + // TODO + }); + + // AssetEntity asset + test('to test the property `asset`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/tag_api_test.dart b/mobile/openapi/test/tag_api_test.dart new file mode 100644 index 000000000..6554281d5 --- /dev/null +++ b/mobile/openapi/test/tag_api_test.dart @@ -0,0 +1,46 @@ +// +// 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 TagApi +void main() { + // final instance = TagApi(); + + group('tests for TagApi', () { + //Future create(CreateTagDto createTagDto) async + test('test create', () async { + // TODO + }); + + //Future> findAll() async + test('test findAll', () async { + // TODO + }); + + //Future findOne(String id) async + test('test findOne', () async { + // TODO + }); + + //Future remove(String id) async + test('test remove', () async { + // TODO + }); + + //Future update(String id, UpdateTagDto updateTagDto) async + test('test update', () async { + // TODO + }); + + }); +} diff --git a/mobile/openapi/test/tag_entity_test.dart b/mobile/openapi/test/tag_entity_test.dart new file mode 100644 index 000000000..f19564b38 --- /dev/null +++ b/mobile/openapi/test/tag_entity_test.dart @@ -0,0 +1,52 @@ +// +// 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 TagEntity +void main() { + // final instance = TagEntity(); + + group('test TagEntity', () { + // String id + test('to test the property `id`', () async { + // TODO + }); + + // String type + test('to test the property `type`', () async { + // TODO + }); + + // String name + test('to test the property `name`', () async { + // TODO + }); + + // String renameTagId + test('to test the property `renameTagId`', () async { + // TODO + }); + + // List assets (default value: const []) + test('to test the property `assets`', () async { + // TODO + }); + + // UserEntity user + test('to test the property `user`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/tag_response_dto_test.dart b/mobile/openapi/test/tag_response_dto_test.dart new file mode 100644 index 000000000..44ed368fb --- /dev/null +++ b/mobile/openapi/test/tag_response_dto_test.dart @@ -0,0 +1,37 @@ +// +// 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 TagResponseDto +void main() { + // final instance = TagResponseDto(); + + group('test TagResponseDto', () { + // String id + test('to test the property `id`', () async { + // TODO + }); + + // TagTypeEnum type + test('to test the property `type`', () async { + // TODO + }); + + // String name + test('to test the property `name`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/tag_type_enum_test.dart b/mobile/openapi/test/tag_type_enum_test.dart new file mode 100644 index 000000000..07a038946 --- /dev/null +++ b/mobile/openapi/test/tag_type_enum_test.dart @@ -0,0 +1,21 @@ +// +// 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 TagTypeEnum +void main() { + + group('test TagTypeEnum', () { + + }); + +} diff --git a/mobile/openapi/test/update_tag_dto_test.dart b/mobile/openapi/test/update_tag_dto_test.dart new file mode 100644 index 000000000..4f4e9498b --- /dev/null +++ b/mobile/openapi/test/update_tag_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 UpdateTagDto +void main() { + // final instance = UpdateTagDto(); + + group('test UpdateTagDto', () { + // String name + test('to test the property `name`', () async { + // TODO + }); + + // String renameTagId + test('to test the property `renameTagId`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/user_entity_test.dart b/mobile/openapi/test/user_entity_test.dart new file mode 100644 index 000000000..de2efd6e0 --- /dev/null +++ b/mobile/openapi/test/user_entity_test.dart @@ -0,0 +1,82 @@ +// +// 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 UserEntity +void main() { + // final instance = UserEntity(); + + group('test UserEntity', () { + // String id + test('to test the property `id`', () async { + // TODO + }); + + // String firstName + test('to test the property `firstName`', () async { + // TODO + }); + + // String lastName + test('to test the property `lastName`', () async { + // TODO + }); + + // bool isAdmin + test('to test the property `isAdmin`', () async { + // TODO + }); + + // String email + test('to test the property `email`', () async { + // TODO + }); + + // String password + test('to test the property `password`', () async { + // TODO + }); + + // String salt + test('to test the property `salt`', () async { + // TODO + }); + + // String profileImagePath + test('to test the property `profileImagePath`', () async { + // TODO + }); + + // bool shouldChangePassword + test('to test the property `shouldChangePassword`', () async { + // TODO + }); + + // String createdAt + test('to test the property `createdAt`', () async { + // TODO + }); + + // DateTime deletedAt + test('to test the property `deletedAt`', () async { + // TODO + }); + + // List tags (default value: const []) + test('to test the property `tags`', () async { + // TODO + }); + + + }); + +} diff --git a/server/apps/immich/src/api-v1/album/album.module.ts b/server/apps/immich/src/api-v1/album/album.module.ts index ee589dca2..4df3835ff 100644 --- a/server/apps/immich/src/api-v1/album/album.module.ts +++ b/server/apps/immich/src/api-v1/album/album.module.ts @@ -1,32 +1,29 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { AlbumService } from './album.service'; import { AlbumController } from './album.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { AssetEntity } from '@app/database/entities/asset.entity'; -import { UserEntity } from '@app/database/entities/user.entity'; import { AlbumEntity } from '../../../../../libs/database/src/entities/album.entity'; import { AssetAlbumEntity } from '@app/database/entities/asset-album.entity'; import { UserAlbumEntity } from '@app/database/entities/user-album.entity'; import { AlbumRepository, ALBUM_REPOSITORY } from './album-repository'; -import { AssetRepository, ASSET_REPOSITORY } from '../asset/asset-repository'; import { DownloadModule } from '../../modules/download/download.module'; +import { AssetModule } from '../asset/asset.module'; +import { UserModule } from '../user/user.module'; + +const ALBUM_REPOSITORY_PROVIDER = { + provide: ALBUM_REPOSITORY, + useClass: AlbumRepository, +}; @Module({ imports: [ - TypeOrmModule.forFeature([AssetEntity, UserEntity, AlbumEntity, AssetAlbumEntity, UserAlbumEntity]), + TypeOrmModule.forFeature([AlbumEntity, AssetAlbumEntity, UserAlbumEntity]), DownloadModule, + UserModule, + forwardRef(() => AssetModule), ], controllers: [AlbumController], - providers: [ - AlbumService, - { - provide: ALBUM_REPOSITORY, - useClass: AlbumRepository, - }, - { - provide: ASSET_REPOSITORY, - useClass: AssetRepository, - }, - ], + providers: [AlbumService, ALBUM_REPOSITORY_PROVIDER], + exports: [ALBUM_REPOSITORY_PROVIDER], }) export class AlbumModule {} diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts index 2aa5fc641..bd8d1ab6c 100644 --- a/server/apps/immich/src/api-v1/asset/asset-repository.ts +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -1,7 +1,7 @@ import { SearchPropertiesDto } from './dto/search-properties.dto'; import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; import { AssetEntity, AssetType } from '@app/database/entities/asset.entity'; -import { BadRequestException, Injectable } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm/repository/Repository'; import { CreateAssetDto } from './dto/create-asset.dto'; @@ -14,6 +14,7 @@ import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; import { In } from 'typeorm/find-options/operator/In'; import { UpdateAssetDto } from './dto/update-asset.dto'; +import { ITagRepository, TAG_REPOSITORY } from '../tag/tag.repository'; export interface IAssetRepository { create( @@ -25,7 +26,7 @@ export interface IAssetRepository { checksum?: Buffer, livePhotoAssetEntity?: AssetEntity, ): Promise; - update(asset: AssetEntity, dto: UpdateAssetDto): Promise; + update(userId: string, asset: AssetEntity, dto: UpdateAssetDto): Promise; getAllByUserId(userId: string, skip?: number): Promise; getAllByDeviceId(userId: string, deviceId: string): Promise; getById(assetId: string): Promise; @@ -53,6 +54,8 @@ export class AssetRepository implements IAssetRepository { constructor( @InjectRepository(AssetEntity) private assetRepository: Repository, + + @Inject(TAG_REPOSITORY) private _tagRepository: ITagRepository, ) {} async getAssetWithNoSmartInfo(): Promise { @@ -222,7 +225,7 @@ export class AssetRepository implements IAssetRepository { where: { id: assetId, }, - relations: ['exifInfo'], + relations: ['exifInfo', 'tags'], }); } @@ -237,9 +240,9 @@ export class AssetRepository implements IAssetRepository { .andWhere('asset.resizePath is not NULL') .andWhere('asset.isVisible = true') .leftJoinAndSelect('asset.exifInfo', 'exifInfo') + .leftJoinAndSelect('asset.tags', 'tags') .skip(skip || 0) .orderBy('asset.createdAt', 'DESC'); - return await query.getMany(); } @@ -286,9 +289,14 @@ export class AssetRepository implements IAssetRepository { /** * Update asset */ - async update(asset: AssetEntity, dto: UpdateAssetDto): Promise { + async update(userId: string, asset: AssetEntity, dto: UpdateAssetDto): Promise { asset.isFavorite = dto.isFavorite ?? asset.isFavorite; + if (dto.tagIds) { + const tags = await this._tagRepository.getByIds(userId, dto.tagIds); + asset.tags = tags; + } + return await this.assetRepository.save(asset); } @@ -347,10 +355,10 @@ export class AssetRepository implements IAssetRepository { async countByIdAndUser(assetId: string, userId: string): Promise { return await this.assetRepository.count({ - where: { - id: assetId, - userId - } + where: { + id: assetId, + userId, + }, }); } } diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index 83befba8b..7148376bf 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -216,14 +216,14 @@ export class AssetController { /** * Update an asset */ - @Put('/assetById/:assetId') - async updateAssetById( + @Put('/:assetId') + async updateAsset( @GetAuthUser() authUser: AuthUserDto, @Param('assetId') assetId: string, - @Body() dto: UpdateAssetDto, + @Body(ValidationPipe) dto: UpdateAssetDto, ): Promise { await this.assetService.checkAssetsAccess(authUser, [assetId], true); - return await this.assetService.updateAssetById(assetId, dto); + return await this.assetService.updateAsset(authUser, assetId, dto); } @Delete('/') diff --git a/server/apps/immich/src/api-v1/asset/asset.module.ts b/server/apps/immich/src/api-v1/asset/asset.module.ts index d6d3b9819..c8501426e 100644 --- a/server/apps/immich/src/api-v1/asset/asset.module.ts +++ b/server/apps/immich/src/api-v1/asset/asset.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { AssetService } from './asset.service'; import { AssetController } from './asset.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; @@ -10,18 +10,25 @@ import { CommunicationModule } from '../communication/communication.module'; import { QueueNameEnum } from '@app/job/constants/queue-name.constant'; import { AssetRepository, ASSET_REPOSITORY } from './asset-repository'; import { DownloadModule } from '../../modules/download/download.module'; -import { ALBUM_REPOSITORY, AlbumRepository } from '../album/album-repository'; -import { AlbumEntity } from '@app/database/entities/album.entity'; -import { UserAlbumEntity } from '@app/database/entities/user-album.entity'; -import { UserEntity } from '@app/database/entities/user.entity'; -import { AssetAlbumEntity } from '@app/database/entities/asset-album.entity'; +import { TagModule } from '../tag/tag.module'; +import { AlbumModule } from '../album/album.module'; +import { UserModule } from '../user/user.module'; + +const ASSET_REPOSITORY_PROVIDER = { + provide: ASSET_REPOSITORY, + useClass: AssetRepository, +}; @Module({ imports: [ + TypeOrmModule.forFeature([AssetEntity]), CommunicationModule, BackgroundTaskModule, DownloadModule, - TypeOrmModule.forFeature([AssetEntity, AlbumEntity, UserAlbumEntity, UserEntity, AssetAlbumEntity]), + UserModule, + AlbumModule, + TagModule, + forwardRef(() => AlbumModule), BullModule.registerQueue({ name: QueueNameEnum.ASSET_UPLOADED, defaultJobOptions: { @@ -40,18 +47,7 @@ import { AssetAlbumEntity } from '@app/database/entities/asset-album.entity'; }), ], controllers: [AssetController], - providers: [ - AssetService, - BackgroundTaskService, - { - provide: ASSET_REPOSITORY, - useClass: AssetRepository, - }, - { - provide: ALBUM_REPOSITORY, - useClass: AlbumRepository, - }, - ], - exports: [AssetService], + providers: [AssetService, BackgroundTaskService, ASSET_REPOSITORY_PROVIDER], + exports: [ASSET_REPOSITORY_PROVIDER], }) export class AssetModule {} diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index 6c9d72a0d..577e08525 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -231,13 +231,13 @@ export class AssetService { return mapAsset(asset); } - public async updateAssetById(assetId: string, dto: UpdateAssetDto): Promise { + public async updateAsset(authUser: AuthUserDto, assetId: string, dto: UpdateAssetDto): Promise { const asset = await this._assetRepository.getById(assetId); if (!asset) { throw new BadRequestException('Asset not found'); } - const updatedAsset = await this._assetRepository.update(asset, dto); + const updatedAsset = await this._assetRepository.update(authUser.id, asset, dto); return mapAsset(updatedAsset); } diff --git a/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts b/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts index 38122e326..9c18c50a7 100644 --- a/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts +++ b/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts @@ -1,6 +1,24 @@ -import { IsBoolean } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class UpdateAssetDto { + @IsOptional() @IsBoolean() - isFavorite!: boolean; + isFavorite?: boolean; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + @IsNotEmpty({ each: true }) + @ApiProperty({ + isArray: true, + type: String, + title: 'Array of tag IDs to add to the asset', + example: [ + 'bf973405-3f2a-48d2-a687-2ed4167164be', + 'dd41870b-5d00-46d2-924e-1d8489a0aa0f', + 'fad77c3f-deef-4e7e-9608-14c1aa4e559a', + ], + }) + tagIds?: string[]; } diff --git a/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts b/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts index 09d61aae5..840d47c27 100644 --- a/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts +++ b/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts @@ -1,5 +1,6 @@ import { AssetEntity, AssetType } from '@app/database/entities/asset.entity'; import { ApiProperty } from '@nestjs/swagger'; +import { mapTag, TagResponseDto } from '../../tag/response-dto/tag-response.dto'; import { ExifResponseDto, mapExif } from './exif-response.dto'; import { SmartInfoResponseDto, mapSmartInfo } from './smart-info-response.dto'; @@ -23,6 +24,7 @@ export class AssetResponseDto { exifInfo?: ExifResponseDto; smartInfo?: SmartInfoResponseDto; livePhotoVideoId?: string | null; + tags!: TagResponseDto[]; } export function mapAsset(entity: AssetEntity): AssetResponseDto { @@ -44,5 +46,6 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto { exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined, smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined, livePhotoVideoId: entity.livePhotoVideoId, + tags: entity.tags?.map(mapTag), }; } diff --git a/server/apps/immich/src/api-v1/job/job.module.ts b/server/apps/immich/src/api-v1/job/job.module.ts index 2cb5beb7b..09ac3885a 100644 --- a/server/apps/immich/src/api-v1/job/job.module.ts +++ b/server/apps/immich/src/api-v1/job/job.module.ts @@ -5,18 +5,21 @@ import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; import { JwtModule } from '@nestjs/jwt'; import { jwtConfig } from '../../config/jwt.config'; -import { UserEntity } from '@app/database/entities/user.entity'; import { TypeOrmModule } from '@nestjs/typeorm'; import { BullModule } from '@nestjs/bull'; import { QueueNameEnum } from '@app/job'; -import { AssetEntity } from '@app/database/entities/asset.entity'; import { ExifEntity } from '@app/database/entities/exif.entity'; -import { AssetRepository, ASSET_REPOSITORY } from '../asset/asset-repository'; +import { TagModule } from '../tag/tag.module'; +import { AssetModule } from '../asset/asset.module'; +import { UserModule } from '../user/user.module'; @Module({ imports: [ - TypeOrmModule.forFeature([UserEntity, AssetEntity, ExifEntity]), + TypeOrmModule.forFeature([ExifEntity]), ImmichJwtModule, + TagModule, + AssetModule, + UserModule, JwtModule.register(jwtConfig), BullModule.registerQueue( { @@ -70,13 +73,6 @@ import { AssetRepository, ASSET_REPOSITORY } from '../asset/asset-repository'; ), ], controllers: [JobController], - providers: [ - JobService, - ImmichJwtService, - { - provide: ASSET_REPOSITORY, - useClass: AssetRepository, - }, - ], + providers: [JobService, ImmichJwtService], }) export class JobModule {} diff --git a/server/apps/immich/src/api-v1/tag/dto/create-tag.dto.ts b/server/apps/immich/src/api-v1/tag/dto/create-tag.dto.ts new file mode 100644 index 000000000..3c3859291 --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/dto/create-tag.dto.ts @@ -0,0 +1,14 @@ +import { TagType } from '@app/database/entities/tag.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; + +export class CreateTagDto { + @IsString() + @IsNotEmpty() + name!: string; + + @IsEnum(TagType) + @IsNotEmpty() + @ApiProperty({ enumName: 'TagTypeEnum', enum: TagType }) + type!: TagType; +} diff --git a/server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts b/server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts new file mode 100644 index 000000000..64632f1f6 --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts @@ -0,0 +1,11 @@ +import { IsOptional, IsString } from 'class-validator'; + +export class UpdateTagDto { + @IsString() + @IsOptional() + name?: string; + + @IsString() + @IsOptional() + renameTagId?: string; +} diff --git a/server/apps/immich/src/api-v1/tag/response-dto/tag-response.dto.ts b/server/apps/immich/src/api-v1/tag/response-dto/tag-response.dto.ts new file mode 100644 index 000000000..973b47db6 --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/response-dto/tag-response.dto.ts @@ -0,0 +1,20 @@ +import { TagEntity, TagType } from '@app/database/entities/tag.entity'; +import { ApiProperty } from '@nestjs/swagger'; + +export class TagResponseDto { + @ApiProperty() + id!: string; + + @ApiProperty({ enumName: 'TagTypeEnum', enum: TagType }) + type!: string; + + name!: string; +} + +export function mapTag(entity: TagEntity): TagResponseDto { + return { + id: entity.id, + type: entity.type, + name: entity.name, + }; +} diff --git a/server/apps/immich/src/api-v1/tag/tag.controller.ts b/server/apps/immich/src/api-v1/tag/tag.controller.ts new file mode 100644 index 000000000..fb2d79f82 --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/tag.controller.ts @@ -0,0 +1,44 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete, ValidationPipe } from '@nestjs/common'; +import { TagService } from './tag.service'; +import { CreateTagDto } from './dto/create-tag.dto'; +import { UpdateTagDto } from './dto/update-tag.dto'; +import { Authenticated } from '../../decorators/authenticated.decorator'; +import { ApiTags } from '@nestjs/swagger'; +import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; +import { TagEntity } from '@app/database/entities/tag.entity'; + +@Authenticated() +@ApiTags('Tag') +@Controller('tag') +export class TagController { + constructor(private readonly tagService: TagService) {} + + @Post() + create(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) createTagDto: CreateTagDto): Promise { + return this.tagService.create(authUser, createTagDto); + } + + @Get() + findAll(@GetAuthUser() authUser: AuthUserDto) { + return this.tagService.findAll(authUser); + } + + @Get(':id') + findOne(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string) { + return this.tagService.findOne(authUser, id); + } + + @Patch(':id') + update( + @GetAuthUser() authUser: AuthUserDto, + @Param('id') id: string, + @Body(ValidationPipe) updateTagDto: UpdateTagDto, + ) { + return this.tagService.update(authUser, id, updateTagDto); + } + + @Delete(':id') + delete(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string) { + return this.tagService.remove(authUser, id); + } +} diff --git a/server/apps/immich/src/api-v1/tag/tag.module.ts b/server/apps/immich/src/api-v1/tag/tag.module.ts new file mode 100644 index 000000000..5a9db11e7 --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/tag.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { TagService } from './tag.service'; +import { TagController } from './tag.controller'; +import { TagEntity } from '@app/database/entities/tag.entity'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TagRepository, TAG_REPOSITORY } from './tag.repository'; + +const TAG_REPOSITORY_PROVIDER = { + provide: TAG_REPOSITORY, + useClass: TagRepository, +}; +@Module({ + imports: [TypeOrmModule.forFeature([TagEntity])], + controllers: [TagController], + providers: [TagService, TAG_REPOSITORY_PROVIDER], + exports: [TAG_REPOSITORY_PROVIDER], +}) +export class TagModule {} diff --git a/server/apps/immich/src/api-v1/tag/tag.repository.ts b/server/apps/immich/src/api-v1/tag/tag.repository.ts new file mode 100644 index 000000000..a1a383aec --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/tag.repository.ts @@ -0,0 +1,61 @@ +import { TagEntity, TagType } from '@app/database/entities/tag.entity'; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; +import { UpdateTagDto } from './dto/update-tag.dto'; + +export interface ITagRepository { + create(userId: string, tagType: TagType, tagName: string): Promise; + getByIds(userId: string, tagIds: string[]): Promise; + getById(tagId: string, userId: string): Promise; + getByUserId(userId: string): Promise; + update(tag: TagEntity, updateTagDto: UpdateTagDto): Promise; + remove(tag: TagEntity): Promise; +} + +export const TAG_REPOSITORY = 'TAG_REPOSITORY'; + +@Injectable() +export class TagRepository implements ITagRepository { + constructor( + @InjectRepository(TagEntity) + private tagRepository: Repository, + ) {} + + async create(userId: string, tagType: TagType, tagName: string): Promise { + const tag = new TagEntity(); + tag.name = tagName; + tag.type = tagType; + tag.userId = userId; + + return this.tagRepository.save(tag); + } + + async getById(tagId: string, userId: string): Promise { + return await this.tagRepository.findOne({ where: { id: tagId, userId }, relations: ['user'] }); + } + + async getByIds(userId: string, tagIds: string[]): Promise { + return await this.tagRepository.find({ + where: { id: In(tagIds), userId }, + relations: { + user: true, + }, + }); + } + + async getByUserId(userId: string): Promise { + return await this.tagRepository.find({ where: { userId } }); + } + + async update(tag: TagEntity, updateTagDto: UpdateTagDto): Promise { + tag.name = updateTagDto.name ?? tag.name; + tag.renameTagId = updateTagDto.renameTagId ?? tag.renameTagId; + + return this.tagRepository.save(tag); + } + + async remove(tag: TagEntity): Promise { + return await this.tagRepository.remove(tag); + } +} diff --git a/server/apps/immich/src/api-v1/tag/tag.service.spec.ts b/server/apps/immich/src/api-v1/tag/tag.service.spec.ts new file mode 100644 index 000000000..3ab1d3034 --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/tag.service.spec.ts @@ -0,0 +1,91 @@ +import { TagEntity, TagType } from '@app/database/entities/tag.entity'; +import { UserEntity } from '@app/database/entities/user.entity'; +import { AuthUserDto } from '../../decorators/auth-user.decorator'; +import { ITagRepository } from './tag.repository'; +import { TagService } from './tag.service'; + +describe('TagService', () => { + let sut: TagService; + let tagRepositoryMock: jest.Mocked; + + const user1AuthUser: AuthUserDto = Object.freeze({ + id: '1111', + email: 'testuser@email.com', + }); + + const user1: UserEntity = Object.freeze({ + id: '1111', + firstName: 'Alex', + lastName: 'Tran', + isAdmin: true, + email: 'testuser@email.com', + profileImagePath: '', + shouldChangePassword: true, + createdAt: '2022-12-02T19:29:23.603Z', + deletedAt: undefined, + tags: [], + oauthId: 'oauth-id-1', + }); + + // const user2: UserEntity = Object.freeze({ + // id: '2222', + // firstName: 'Alex', + // lastName: 'Tran', + // isAdmin: true, + // email: 'testuser2@email.com', + // profileImagePath: '', + // shouldChangePassword: true, + // createdAt: '2022-12-02T19:29:23.603Z', + // deletedAt: undefined, + // tags: [], + // oauthId: 'oauth-id-2', + // }); + + const user1Tag1: TagEntity = Object.freeze({ + name: 'user 1 tag 1', + type: TagType.CUSTOM, + userId: user1.id, + user: user1, + renameTagId: '', + id: 'user1-tag-1-id', + assets: [], + }); + + // const user1Tag2: TagEntity = Object.freeze({ + // name: 'user 1 tag 2', + // type: TagType.CUSTOM, + // userId: user1.id, + // user: user1, + // renameTagId: '', + // id: 'user1-tag-2-id', + // assets: [], + // }); + + beforeAll(() => { + tagRepositoryMock = { + create: jest.fn(), + getByIds: jest.fn(), + getById: jest.fn(), + getByUserId: jest.fn(), + remove: jest.fn(), + update: jest.fn(), + }; + + sut = new TagService(tagRepositoryMock); + }); + + it('creates tag', async () => { + const createTagDto = { + name: 'user 1 tag 1', + type: TagType.CUSTOM, + }; + + tagRepositoryMock.create.mockResolvedValue(user1Tag1); + + const result = await sut.create(user1AuthUser, createTagDto); + + expect(result.userId).toEqual(user1AuthUser.id); + expect(result.name).toEqual(createTagDto.name); + expect(result.type).toEqual(createTagDto.type); + }); +}); diff --git a/server/apps/immich/src/api-v1/tag/tag.service.ts b/server/apps/immich/src/api-v1/tag/tag.service.ts new file mode 100644 index 000000000..e14e97880 --- /dev/null +++ b/server/apps/immich/src/api-v1/tag/tag.service.ts @@ -0,0 +1,48 @@ +import { TagEntity } from '@app/database/entities/tag.entity'; +import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; +import { AuthUserDto } from '../../decorators/auth-user.decorator'; +import { CreateTagDto } from './dto/create-tag.dto'; +import { UpdateTagDto } from './dto/update-tag.dto'; +import { ITagRepository, TAG_REPOSITORY } from './tag.repository'; + +@Injectable() +export class TagService { + readonly logger = new Logger(TagService.name); + + constructor(@Inject(TAG_REPOSITORY) private _tagRepository: ITagRepository) {} + + async create(authUser: AuthUserDto, createTagDto: CreateTagDto) { + try { + return await this._tagRepository.create(authUser.id, createTagDto.type, createTagDto.name); + } catch (e: any) { + this.logger.error(e, e.stack); + throw new BadRequestException(`Failed to create tag: ${e.detail}`); + } + } + + async findAll(authUser: AuthUserDto) { + return await this._tagRepository.getByUserId(authUser.id); + } + + async findOne(authUser: AuthUserDto, id: string): Promise { + const tag = await this._tagRepository.getById(id, authUser.id); + + if (!tag) { + throw new BadRequestException('Tag not found'); + } + + return tag; + } + + async update(authUser: AuthUserDto, id: string, updateTagDto: UpdateTagDto) { + const tag = await this.findOne(authUser, id); + + return this._tagRepository.update(tag, updateTagDto); + } + + async remove(authUser: AuthUserDto, id: string) { + const tag = await this.findOne(authUser, id); + + return this._tagRepository.remove(tag); + } +} diff --git a/server/apps/immich/src/api-v1/user/user.service.spec.ts b/server/apps/immich/src/api-v1/user/user.service.spec.ts index 49d82938a..126d92560 100644 --- a/server/apps/immich/src/api-v1/user/user.service.spec.ts +++ b/server/apps/immich/src/api-v1/user/user.service.spec.ts @@ -31,6 +31,7 @@ describe('UserService', () => { shouldChangePassword: false, profileImagePath: '', createdAt: '2021-01-01', + tags: [], }); const immichUser: UserEntity = Object.freeze({ @@ -45,6 +46,7 @@ describe('UserService', () => { shouldChangePassword: false, profileImagePath: '', createdAt: '2021-01-01', + tags: [], }); const updatedImmichUser: UserEntity = Object.freeze({ @@ -59,6 +61,7 @@ describe('UserService', () => { shouldChangePassword: true, profileImagePath: '', createdAt: '2021-01-01', + tags: [], }); beforeAll(() => { diff --git a/server/apps/immich/src/app.module.ts b/server/apps/immich/src/app.module.ts index 487847bbc..25ed0ab35 100644 --- a/server/apps/immich/src/app.module.ts +++ b/server/apps/immich/src/app.module.ts @@ -18,6 +18,7 @@ import { DatabaseModule } from '@app/database'; import { JobModule } from './api-v1/job/job.module'; import { SystemConfigModule } from './api-v1/system-config/system-config.module'; import { OAuthModule } from './api-v1/oauth/oauth.module'; +import { TagModule } from './api-v1/tag/tag.module'; @Module({ imports: [ @@ -63,6 +64,8 @@ import { OAuthModule } from './api-v1/oauth/oauth.module'; JobModule, SystemConfigModule, + + TagModule, ], controllers: [AppController], providers: [], diff --git a/server/apps/immich/src/main.ts b/server/apps/immich/src/main.ts index cd1cd22d2..3e5e3fb2a 100644 --- a/server/apps/immich/src/main.ts +++ b/server/apps/immich/src/main.ts @@ -55,7 +55,7 @@ async function bootstrap() { if (process.env.NODE_ENV == 'development') { // Generate API Documentation only in development mode const outputPath = path.resolve(process.cwd(), 'immich-openapi-specs.json'); - writeFileSync(outputPath, JSON.stringify(apiDocument), { encoding: 'utf8' }); + writeFileSync(outputPath, JSON.stringify(apiDocument, null, 2), { encoding: 'utf8' }); Logger.log( `Running Immich Server in DEVELOPMENT environment - version ${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch}`, 'ImmichServer', diff --git a/server/apps/immich/src/modules/immich-jwt/immich-jwt.service.spec.ts b/server/apps/immich/src/modules/immich-jwt/immich-jwt.service.spec.ts index 9936f9de3..7edf31399 100644 --- a/server/apps/immich/src/modules/immich-jwt/immich-jwt.service.spec.ts +++ b/server/apps/immich/src/modules/immich-jwt/immich-jwt.service.spec.ts @@ -56,6 +56,7 @@ describe('ImmichJwtService', () => { profileImagePath: '', shouldChangePassword: false, createdAt: 'today', + tags: [], }; const dto: LoginResponseDto = { diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index bee972c82..7ff2daa56 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1 +1,3975 @@ -{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/info/{userId}":{"get":{"operationId":"getUserById","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/{userId}":{"delete":{"operationId":"deleteUser","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/{userId}/restore":{"post":{"operationId":"restoreUser","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"description":"A new avatar for the user","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"description":"Asset Upload Information","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AssetFileUploadDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetFileUploadResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download/{assetId}":{"get":{"operationId":"downloadFile","parameters":[{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}},{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download-library":{"get":{"operationId":"downloadLibrary","parameters":[{"name":"skip","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file/{assetId}":{"get":{"operationId":"serveFile","parameters":[{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}},{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","schema":{"$ref":"#/components/schemas/ThumbnailFormat"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-objects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-locations":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search-terms":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-time-bucket":{"post":{"operationId":"getAssetCountByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetCountByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByTimeBucketResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-user-id":{"get":{"operationId":"getAssetCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByUserIdResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[{"name":"if-none-match","in":"header","description":"ETag of data already cached on the client","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/time-bucket":{"post":{"operationId":"getAssetByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"put":{"operationId":"updateAssetById","summary":"","description":"Update an asset","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/exist":{"post":{"operationId":"checkExistingAssets","summary":"","description":"Checks if multiple assets exist on the server and returns all existing - used by background backup","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckExistingAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckExistingAssetsResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/auth/logout":{"post":{"operationId":"logout","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutResponseDto"}}}}},"tags":["Authentication"]}},"/oauth/config":{"post":{"operationId":"generateConfig","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthConfigDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthConfigResponseDto"}}}}},"tags":["OAuth"]}},"/oauth/callback":{"post":{"operationId":"callback","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthCallbackDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["OAuth"]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/server-info/stats":{"get":{"operationId":"getStats","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerStatsResponseDto"}}}}},"tags":["Server Info"]}},"/album/count-by-user-id":{"get":{"operationId":"getAlbumCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumCountResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"assetId","required":false,"in":"query","description":"Only returns albums that contain the asset\nIgnores the shared parameter\nundefined: get all albums","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/download":{"get":{"operationId":"downloadArchive","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"skip","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/jobs":{"get":{"operationId":"getAllJobsStatus","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllJobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}},"/jobs/{jobId}":{"get":{"operationId":"getJobStatus","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]},"put":{"operationId":"sendJobCommand","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCommandDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"number"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}},"/system-config":{"get":{"operationId":"getConfig","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemConfigResponseDto"}}}}},"tags":["System Config"],"security":[{"bearer":[]}]},"put":{"operationId":"updateConfig","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSystemConfigDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemConfigResponseDto"}}}}},"tags":["System Config"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"Bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"},"deletedAt":{"format":"date-time","type":"string"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"integer"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"AssetFileUploadDto":{"type":"object","properties":{"assetData":{"type":"string","format":"binary"}},"required":["assetData"]},"AssetFileUploadResponseDto":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"ThumbnailFormat":{"type":"string","enum":["JPEG","WEBP"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"AssetTypeEnum":{"type":"string","enum":["IMAGE","VIDEO","AUDIO","OTHER"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"integer","nullable":true,"default":null,"format":"int64"},"fileSizeInByte":{"type":"integer","nullable":true,"default":null,"format":"int64"},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}}},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/AssetTypeEnum"},"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"},"livePhotoVideoId":{"type":"string","nullable":true}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath"]},"TimeGroupEnum":{"type":"string","enum":["day","month"]},"GetAssetCountByTimeBucketDto":{"type":"object","properties":{"timeGroup":{"$ref":"#/components/schemas/TimeGroupEnum"}},"required":["timeGroup"]},"AssetCountByTimeBucket":{"type":"object","properties":{"timeBucket":{"type":"string"},"count":{"type":"integer"}},"required":["timeBucket","count"]},"AssetCountByTimeBucketResponseDto":{"type":"object","properties":{"totalCount":{"type":"integer"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/AssetCountByTimeBucket"}}},"required":["totalCount","buckets"]},"AssetCountByUserIdResponseDto":{"type":"object","properties":{"audio":{"type":"integer","default":0},"photos":{"type":"integer","default":0},"videos":{"type":"integer","default":0},"other":{"type":"integer","default":0},"total":{"type":"integer","default":0}},"required":["audio","photos","videos","other","total"]},"GetAssetByTimeBucketDto":{"type":"object","properties":{"timeBucket":{"title":"Array of date time buckets","example":["2015-06-01T00:00:00.000Z","2016-02-01T00:00:00.000Z","2016-03-01T00:00:00.000Z"],"type":"array","items":{"type":"string"}}},"required":["timeBucket"]},"UpdateAssetDto":{"type":"object","properties":{"isFavorite":{"type":"boolean"}},"required":["isFavorite"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"DeleteAssetStatus":{"type":"string","enum":["SUCCESS","FAILED"]},"DeleteAssetResponseDto":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/DeleteAssetStatus"},"id":{"type":"string"}},"required":["status","id"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"},"id":{"type":"string"}},"required":["isExist"]},"CheckExistingAssetsDto":{"type":"object","properties":{"deviceAssetIds":{"type":"array","items":{"type":"string"}},"deviceId":{"type":"string"}},"required":["deviceAssetIds","deviceId"]},"CheckExistingAssetsResponseDto":{"type":"object","properties":{"existingIds":{"type":"array","items":{"type":"string"}}},"required":["existingIds"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{"authStatus":{"type":"boolean"}},"required":["authStatus"]},"LogoutResponseDto":{"type":"object","properties":{"successful":{"type":"boolean","readOnly":true},"redirectUri":{"type":"string","readOnly":true}},"required":["successful","redirectUri"]},"OAuthConfigDto":{"type":"object","properties":{"redirectUri":{"type":"string"}},"required":["redirectUri"]},"OAuthConfigResponseDto":{"type":"object","properties":{"enabled":{"type":"boolean","readOnly":true},"url":{"type":"string","readOnly":true},"buttonText":{"type":"string","readOnly":true}},"required":["enabled"]},"OAuthCallbackDto":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]},"DeviceTypeEnum":{"type":"string","enum":["IOS","ANDROID","WEB"]},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"integer"},"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"userId":{"type":"string"},"deviceId":{"type":"string"},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","deviceType","userId","deviceId","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"ServerInfoResponseDto":{"type":"object","properties":{"diskSizeRaw":{"type":"integer","format":"int64"},"diskUseRaw":{"type":"integer","format":"int64"},"diskAvailableRaw":{"type":"integer","format":"int64"},"diskUsagePercentage":{"type":"number","format":"float"},"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"}},"required":["diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage","diskSize","diskUse","diskAvailable"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"integer"},"minor":{"type":"integer"},"patch":{"type":"integer"},"build":{"type":"integer"}},"required":["major","minor","patch","build"]},"UsageByUserDto":{"type":"object","properties":{"userId":{"type":"string"},"videos":{"type":"integer"},"photos":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"}},"required":["userId","videos","photos","usageRaw","usage"]},"ServerStatsResponseDto":{"type":"object","properties":{"photos":{"type":"integer"},"videos":{"type":"integer"},"objects":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"},"usageByUser":{"title":"Array of usage for each user","example":[{"photos":1,"videos":1,"diskUsageRaw":1}],"type":"array","items":{"$ref":"#/components/schemas/UsageByUserDto"}}},"required":["photos","videos","objects","usageRaw","usage","usageByUser"]},"AlbumCountResponseDto":{"type":"object","properties":{"owned":{"type":"integer"},"shared":{"type":"integer"},"sharing":{"type":"integer"}},"required":["owned","shared","sharing"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"assetCount":{"type":"integer"},"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["assetCount","id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"AddAssetsResponseDto":{"type":"object","properties":{"successfullyAdded":{"type":"integer"},"alreadyInAlbum":{"type":"array","items":{"type":"string"}},"album":{"$ref":"#/components/schemas/AlbumResponseDto"}},"required":["successfullyAdded","alreadyInAlbum"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"albumThumbnailAssetId":{"type":"string"}}},"JobCounts":{"type":"object","properties":{"active":{"type":"integer"},"completed":{"type":"integer"},"failed":{"type":"integer"},"delayed":{"type":"integer"},"waiting":{"type":"integer"}},"required":["active","completed","failed","delayed","waiting"]},"AllJobStatusResponseDto":{"type":"object","properties":{"thumbnailGenerationQueueCount":{"$ref":"#/components/schemas/JobCounts"},"metadataExtractionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"videoConversionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"machineLearningQueueCount":{"$ref":"#/components/schemas/JobCounts"},"isThumbnailGenerationActive":{"type":"boolean"},"isMetadataExtractionActive":{"type":"boolean"},"isVideoConversionActive":{"type":"boolean"},"isMachineLearningActive":{"type":"boolean"}},"required":["thumbnailGenerationQueueCount","metadataExtractionQueueCount","videoConversionQueueCount","machineLearningQueueCount","isThumbnailGenerationActive","isMetadataExtractionActive","isVideoConversionActive","isMachineLearningActive"]},"JobId":{"type":"string","enum":["thumbnail-generation","metadata-extraction","video-conversion","machine-learning"]},"JobStatusResponseDto":{"type":"object","properties":{"isActive":{"type":"boolean"},"queueCount":{"type":"object"}},"required":["isActive","queueCount"]},"JobCommand":{"type":"string","enum":["start","stop"]},"JobCommandDto":{"type":"object","properties":{"command":{"$ref":"#/components/schemas/JobCommand"}},"required":["command"]},"SystemConfigKey":{"type":"string","enum":["ffmpeg_crf","ffmpeg_preset","ffmpeg_target_video_codec","ffmpeg_target_audio_codec","ffmpeg_target_scaling"]},"SystemConfigResponseItem":{"type":"object","properties":{"name":{"type":"string"},"key":{"$ref":"#/components/schemas/SystemConfigKey"},"value":{"type":"string"},"defaultValue":{"type":"string"}},"required":["name","key","value","defaultValue"]},"SystemConfigResponseDto":{"type":"object","properties":{"config":{"type":"array","items":{"$ref":"#/components/schemas/SystemConfigResponseItem"}}},"required":["config"]},"UpdateSystemConfigDto":{"type":"object","properties":{}}}}} \ No newline at end of file +{ + "openapi": "3.0.0", + "paths": { + "/user": { + "get": { + "operationId": "getAllUsers", + "parameters": [ + { + "name": "isAll", + "required": true, + "in": "query", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + } + }, + "tags": [ + "User" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "post": { + "operationId": "createUser", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUserDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "put": { + "operationId": "updateUser", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateUserDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/user/info/{userId}": { + "get": { + "operationId": "getUserById", + "parameters": [ + { + "name": "userId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ] + } + }, + "/user/me": { + "get": { + "operationId": "getMyUserInfo", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/user/count": { + "get": { + "operationId": "getUserCount", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserCountResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ] + } + }, + "/user/{userId}": { + "delete": { + "operationId": "deleteUser", + "parameters": [ + { + "name": "userId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/user/{userId}/restore": { + "post": { + "operationId": "restoreUser", + "parameters": [ + { + "name": "userId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/user/profile-image": { + "post": { + "operationId": "createProfileImage", + "parameters": [], + "requestBody": { + "required": true, + "description": "A new avatar for the user", + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateProfileImageDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateProfileImageResponseDto" + } + } + } + } + }, + "tags": [ + "User" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/user/profile-image/{userId}": { + "get": { + "operationId": "getProfileImage", + "parameters": [ + { + "name": "userId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "User" + ] + } + }, + "/asset/upload": { + "post": { + "operationId": "uploadFile", + "parameters": [], + "requestBody": { + "required": true, + "description": "Asset Upload Information", + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/AssetFileUploadDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetFileUploadResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/download/{assetId}": { + "get": { + "operationId": "downloadFile", + "parameters": [ + { + "name": "isThumb", + "required": false, + "in": "query", + "schema": { + "title": "Is serve thumbnail (resize) file", + "type": "boolean" + } + }, + { + "name": "isWeb", + "required": false, + "in": "query", + "schema": { + "title": "Is request made from web", + "type": "boolean" + } + }, + { + "name": "assetId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/download-library": { + "get": { + "operationId": "downloadLibrary", + "parameters": [ + { + "name": "skip", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/file/{assetId}": { + "get": { + "operationId": "serveFile", + "parameters": [ + { + "name": "isThumb", + "required": false, + "in": "query", + "schema": { + "title": "Is serve thumbnail (resize) file", + "type": "boolean" + } + }, + { + "name": "isWeb", + "required": false, + "in": "query", + "schema": { + "title": "Is request made from web", + "type": "boolean" + } + }, + { + "name": "assetId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/thumbnail/{assetId}": { + "get": { + "operationId": "getAssetThumbnail", + "parameters": [ + { + "name": "assetId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/ThumbnailFormat" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/curated-objects": { + "get": { + "operationId": "getCuratedObjects", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CuratedObjectsResponseDto" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/curated-locations": { + "get": { + "operationId": "getCuratedLocations", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CuratedLocationsResponseDto" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/search-terms": { + "get": { + "operationId": "getAssetSearchTerms", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/search": { + "post": { + "operationId": "searchAsset", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchAssetDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetResponseDto" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/count-by-time-bucket": { + "post": { + "operationId": "getAssetCountByTimeBucket", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAssetCountByTimeBucketDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetCountByTimeBucketResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/count-by-user-id": { + "get": { + "operationId": "getAssetCountByUserId", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetCountByUserIdResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset": { + "get": { + "operationId": "getAllAssets", + "summary": "", + "description": "Get all AssetEntity belong to the user", + "parameters": [ + { + "name": "if-none-match", + "in": "header", + "description": "ETag of data already cached on the client", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetResponseDto" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "delete": { + "operationId": "deleteAsset", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteAssetDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeleteAssetResponseDto" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/time-bucket": { + "post": { + "operationId": "getAssetByTimeBucket", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAssetByTimeBucketDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetResponseDto" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/{deviceId}": { + "get": { + "operationId": "getUserAssetsByDeviceId", + "summary": "", + "description": "Get all asset of a device that are in the database, ID only.", + "parameters": [ + { + "name": "deviceId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/assetById/{assetId}": { + "get": { + "operationId": "getAssetById", + "summary": "", + "description": "Get a single asset's information", + "parameters": [ + { + "name": "assetId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/{assetId}": { + "put": { + "operationId": "updateAsset", + "summary": "", + "description": "Update an asset", + "parameters": [ + { + "name": "assetId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAssetDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/check": { + "post": { + "operationId": "checkDuplicateAsset", + "summary": "", + "description": "Check duplicated asset before uploading - for Web upload used", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckDuplicateAssetDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckDuplicateAssetResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/exist": { + "post": { + "operationId": "checkExistingAssets", + "summary": "", + "description": "Checks if multiple assets exist on the server and returns all existing - used by background backup", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckExistingAssetsDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CheckExistingAssetsResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/album/count-by-user-id": { + "get": { + "operationId": "getAlbumCountByUserId", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumCountResponseDto" + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/album": { + "post": { + "operationId": "createAlbum", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAlbumDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumResponseDto" + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "get": { + "operationId": "getAllAlbums", + "parameters": [ + { + "name": "shared", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "assetId", + "required": false, + "in": "query", + "description": "Only returns albums that contain the asset\nIgnores the shared parameter\nundefined: get all albums", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AlbumResponseDto" + } + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/album/{albumId}/users": { + "put": { + "operationId": "addUsersToAlbum", + "parameters": [ + { + "name": "albumId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddUsersDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumResponseDto" + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/album/{albumId}/assets": { + "put": { + "operationId": "addAssetsToAlbum", + "parameters": [ + { + "name": "albumId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddAssetsDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddAssetsResponseDto" + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "delete": { + "operationId": "removeAssetFromAlbum", + "parameters": [ + { + "name": "albumId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RemoveAssetsDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumResponseDto" + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/album/{albumId}": { + "get": { + "operationId": "getAlbumInfo", + "parameters": [ + { + "name": "albumId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumResponseDto" + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "delete": { + "operationId": "deleteAlbum", + "parameters": [ + { + "name": "albumId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "patch": { + "operationId": "updateAlbumInfo", + "parameters": [ + { + "name": "albumId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAlbumDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumResponseDto" + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/album/{albumId}/user/{userId}": { + "delete": { + "operationId": "removeUserFromAlbum", + "parameters": [ + { + "name": "albumId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/album/{albumId}/download": { + "get": { + "operationId": "downloadArchive", + "parameters": [ + { + "name": "albumId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Album" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/tag": { + "post": { + "operationId": "create", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTagDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagEntity" + } + } + } + } + }, + "tags": [ + "Tag" + ] + }, + "get": { + "operationId": "findAll", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagEntity" + } + } + } + } + } + }, + "tags": [ + "Tag" + ] + } + }, + "/tag/{id}": { + "get": { + "operationId": "findOne", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagEntity" + } + } + } + } + }, + "tags": [ + "Tag" + ] + }, + "patch": { + "operationId": "update", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTagDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Tag" + ] + }, + "delete": { + "operationId": "delete", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagEntity" + } + } + } + } + }, + "tags": [ + "Tag" + ] + } + }, + "/auth/login": { + "post": { + "operationId": "login", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginCredentialDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginResponseDto" + } + } + } + } + }, + "tags": [ + "Authentication" + ] + } + }, + "/auth/admin-sign-up": { + "post": { + "operationId": "adminSignUp", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SignUpDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminSignupResponseDto" + } + } + } + }, + "400": { + "description": "The server already has an admin" + } + }, + "tags": [ + "Authentication" + ] + } + }, + "/auth/validateToken": { + "post": { + "operationId": "validateAccessToken", + "parameters": [], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateAccessTokenResponseDto" + } + } + } + } + }, + "tags": [ + "Authentication" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/auth/logout": { + "post": { + "operationId": "logout", + "parameters": [], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LogoutResponseDto" + } + } + } + } + }, + "tags": [ + "Authentication" + ] + } + }, + "/oauth/config": { + "post": { + "operationId": "generateConfig", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthConfigDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthConfigResponseDto" + } + } + } + } + }, + "tags": [ + "OAuth" + ] + } + }, + "/oauth/callback": { + "post": { + "operationId": "callback", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthCallbackDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginResponseDto" + } + } + } + } + }, + "tags": [ + "OAuth" + ] + } + }, + "/device-info": { + "post": { + "operationId": "createDeviceInfo", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDeviceInfoDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceInfoResponseDto" + } + } + } + } + }, + "tags": [ + "Device Info" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "patch": { + "operationId": "updateDeviceInfo", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDeviceInfoDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceInfoResponseDto" + } + } + } + } + }, + "tags": [ + "Device Info" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/server-info": { + "get": { + "operationId": "getServerInfo", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerInfoResponseDto" + } + } + } + } + }, + "tags": [ + "Server Info" + ] + } + }, + "/server-info/ping": { + "get": { + "operationId": "pingServer", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerPingResponse" + } + } + } + } + }, + "tags": [ + "Server Info" + ] + } + }, + "/server-info/version": { + "get": { + "operationId": "getServerVersion", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerVersionReponseDto" + } + } + } + } + }, + "tags": [ + "Server Info" + ] + } + }, + "/server-info/stats": { + "get": { + "operationId": "getStats", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerStatsResponseDto" + } + } + } + } + }, + "tags": [ + "Server Info" + ] + } + }, + "/jobs": { + "get": { + "operationId": "getAllJobsStatus", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AllJobStatusResponseDto" + } + } + } + } + }, + "tags": [ + "Job" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/jobs/{jobId}": { + "get": { + "operationId": "getJobStatus", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "$ref": "#/components/schemas/JobId" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobStatusResponseDto" + } + } + } + } + }, + "tags": [ + "Job" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "put": { + "operationId": "sendJobCommand", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "$ref": "#/components/schemas/JobId" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobCommandDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "number" + } + } + } + } + }, + "tags": [ + "Job" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/system-config": { + "get": { + "operationId": "getConfig", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemConfigResponseDto" + } + } + } + } + }, + "tags": [ + "System Config" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "put": { + "operationId": "updateConfig", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSystemConfigDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemConfigResponseDto" + } + } + } + } + }, + "tags": [ + "System Config" + ], + "security": [ + { + "bearer": [] + } + ] + } + } + }, + "info": { + "title": "Immich", + "description": "Immich API", + "version": "1.17.0", + "contact": {} + }, + "tags": [], + "servers": [ + { + "url": "/api" + } + ], + "components": { + "securitySchemes": { + "bearer": { + "scheme": "Bearer", + "bearerFormat": "JWT", + "type": "http", + "name": "JWT", + "description": "Enter JWT token", + "in": "header" + } + }, + "schemas": { + "UserResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "profileImagePath": { + "type": "string" + }, + "shouldChangePassword": { + "type": "boolean" + }, + "isAdmin": { + "type": "boolean" + }, + "deletedAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "lastName", + "createdAt", + "profileImagePath", + "shouldChangePassword", + "isAdmin" + ] + }, + "CreateUserDto": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "testuser@email.com" + }, + "password": { + "type": "string", + "example": "password" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "Doe" + } + }, + "required": [ + "email", + "password", + "firstName", + "lastName" + ] + }, + "UserCountResponseDto": { + "type": "object", + "properties": { + "userCount": { + "type": "integer" + } + }, + "required": [ + "userCount" + ] + }, + "UpdateUserDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "password": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "isAdmin": { + "type": "boolean" + }, + "shouldChangePassword": { + "type": "boolean" + }, + "profileImagePath": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "CreateProfileImageDto": { + "type": "object", + "properties": { + "file": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "file" + ] + }, + "CreateProfileImageResponseDto": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "profileImagePath": { + "type": "string" + } + }, + "required": [ + "userId", + "profileImagePath" + ] + }, + "AssetFileUploadDto": { + "type": "object", + "properties": { + "assetData": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "assetData" + ] + }, + "AssetFileUploadResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "ThumbnailFormat": { + "type": "string", + "enum": [ + "JPEG", + "WEBP" + ] + }, + "CuratedObjectsResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string" + }, + "resizePath": { + "type": "string" + }, + "deviceAssetId": { + "type": "string" + }, + "deviceId": { + "type": "string" + } + }, + "required": [ + "id", + "object", + "resizePath", + "deviceAssetId", + "deviceId" + ] + }, + "CuratedLocationsResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "city": { + "type": "string" + }, + "resizePath": { + "type": "string" + }, + "deviceAssetId": { + "type": "string" + }, + "deviceId": { + "type": "string" + } + }, + "required": [ + "id", + "city", + "resizePath", + "deviceAssetId", + "deviceId" + ] + }, + "SearchAssetDto": { + "type": "object", + "properties": { + "searchTerm": { + "type": "string" + } + }, + "required": [ + "searchTerm" + ] + }, + "AssetTypeEnum": { + "type": "string", + "enum": [ + "IMAGE", + "VIDEO", + "AUDIO", + "OTHER" + ] + }, + "ExifResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "nullable": true, + "default": null, + "format": "int64" + }, + "fileSizeInByte": { + "type": "integer", + "nullable": true, + "default": null, + "format": "int64" + }, + "make": { + "type": "string", + "nullable": true, + "default": null + }, + "model": { + "type": "string", + "nullable": true, + "default": null + }, + "imageName": { + "type": "string", + "nullable": true, + "default": null + }, + "exifImageWidth": { + "type": "number", + "nullable": true, + "default": null + }, + "exifImageHeight": { + "type": "number", + "nullable": true, + "default": null + }, + "orientation": { + "type": "string", + "nullable": true, + "default": null + }, + "dateTimeOriginal": { + "format": "date-time", + "type": "string", + "nullable": true, + "default": null + }, + "modifyDate": { + "format": "date-time", + "type": "string", + "nullable": true, + "default": null + }, + "lensModel": { + "type": "string", + "nullable": true, + "default": null + }, + "fNumber": { + "type": "number", + "nullable": true, + "default": null + }, + "focalLength": { + "type": "number", + "nullable": true, + "default": null + }, + "iso": { + "type": "number", + "nullable": true, + "default": null + }, + "exposureTime": { + "type": "number", + "nullable": true, + "default": null + }, + "latitude": { + "type": "number", + "nullable": true, + "default": null + }, + "longitude": { + "type": "number", + "nullable": true, + "default": null + }, + "city": { + "type": "string", + "nullable": true, + "default": null + }, + "state": { + "type": "string", + "nullable": true, + "default": null + }, + "country": { + "type": "string", + "nullable": true, + "default": null + } + } + }, + "SmartInfoResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "tags": { + "nullable": true, + "type": "array", + "items": { + "type": "string" + } + }, + "objects": { + "nullable": true, + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "TagTypeEnum": { + "type": "string", + "enum": [ + "OBJECT", + "FACE", + "CUSTOM" + ] + }, + "TagResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/TagTypeEnum" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "type", + "name" + ] + }, + "AssetResponseDto": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/AssetTypeEnum" + }, + "id": { + "type": "string" + }, + "deviceAssetId": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "deviceId": { + "type": "string" + }, + "originalPath": { + "type": "string" + }, + "resizePath": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string" + }, + "modifiedAt": { + "type": "string" + }, + "isFavorite": { + "type": "boolean" + }, + "mimeType": { + "type": "string", + "nullable": true + }, + "duration": { + "type": "string" + }, + "webpPath": { + "type": "string", + "nullable": true + }, + "encodedVideoPath": { + "type": "string", + "nullable": true + }, + "exifInfo": { + "$ref": "#/components/schemas/ExifResponseDto" + }, + "smartInfo": { + "$ref": "#/components/schemas/SmartInfoResponseDto" + }, + "livePhotoVideoId": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagResponseDto" + } + } + }, + "required": [ + "type", + "id", + "deviceAssetId", + "ownerId", + "deviceId", + "originalPath", + "resizePath", + "createdAt", + "modifiedAt", + "isFavorite", + "mimeType", + "duration", + "webpPath", + "tags" + ] + }, + "TimeGroupEnum": { + "type": "string", + "enum": [ + "day", + "month" + ] + }, + "GetAssetCountByTimeBucketDto": { + "type": "object", + "properties": { + "timeGroup": { + "$ref": "#/components/schemas/TimeGroupEnum" + } + }, + "required": [ + "timeGroup" + ] + }, + "AssetCountByTimeBucket": { + "type": "object", + "properties": { + "timeBucket": { + "type": "string" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "timeBucket", + "count" + ] + }, + "AssetCountByTimeBucketResponseDto": { + "type": "object", + "properties": { + "totalCount": { + "type": "integer" + }, + "buckets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetCountByTimeBucket" + } + } + }, + "required": [ + "totalCount", + "buckets" + ] + }, + "AssetCountByUserIdResponseDto": { + "type": "object", + "properties": { + "audio": { + "type": "integer", + "default": 0 + }, + "photos": { + "type": "integer", + "default": 0 + }, + "videos": { + "type": "integer", + "default": 0 + }, + "other": { + "type": "integer", + "default": 0 + }, + "total": { + "type": "integer", + "default": 0 + } + }, + "required": [ + "audio", + "photos", + "videos", + "other", + "total" + ] + }, + "GetAssetByTimeBucketDto": { + "type": "object", + "properties": { + "timeBucket": { + "title": "Array of date time buckets", + "example": [ + "2015-06-01T00:00:00.000Z", + "2016-02-01T00:00:00.000Z", + "2016-03-01T00:00:00.000Z" + ], + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "timeBucket" + ] + }, + "UpdateAssetDto": { + "type": "object", + "properties": { + "tagIds": { + "title": "Array of tag IDs to add to the asset", + "example": [ + "bf973405-3f2a-48d2-a687-2ed4167164be", + "dd41870b-5d00-46d2-924e-1d8489a0aa0f", + "fad77c3f-deef-4e7e-9608-14c1aa4e559a" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "isFavorite": { + "type": "boolean" + } + } + }, + "DeleteAssetDto": { + "type": "object", + "properties": { + "ids": { + "title": "Array of asset IDs to delete", + "example": [ + "bf973405-3f2a-48d2-a687-2ed4167164be", + "dd41870b-5d00-46d2-924e-1d8489a0aa0f", + "fad77c3f-deef-4e7e-9608-14c1aa4e559a" + ], + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "ids" + ] + }, + "DeleteAssetStatus": { + "type": "string", + "enum": [ + "SUCCESS", + "FAILED" + ] + }, + "DeleteAssetResponseDto": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/DeleteAssetStatus" + }, + "id": { + "type": "string" + } + }, + "required": [ + "status", + "id" + ] + }, + "CheckDuplicateAssetDto": { + "type": "object", + "properties": { + "deviceAssetId": { + "type": "string" + }, + "deviceId": { + "type": "string" + } + }, + "required": [ + "deviceAssetId", + "deviceId" + ] + }, + "CheckDuplicateAssetResponseDto": { + "type": "object", + "properties": { + "isExist": { + "type": "boolean" + }, + "id": { + "type": "string" + } + }, + "required": [ + "isExist" + ] + }, + "CheckExistingAssetsDto": { + "type": "object", + "properties": { + "deviceAssetIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "deviceId": { + "type": "string" + } + }, + "required": [ + "deviceAssetIds", + "deviceId" + ] + }, + "CheckExistingAssetsResponseDto": { + "type": "object", + "properties": { + "existingIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "existingIds" + ] + }, + "AlbumCountResponseDto": { + "type": "object", + "properties": { + "owned": { + "type": "integer" + }, + "shared": { + "type": "integer" + }, + "sharing": { + "type": "integer" + } + }, + "required": [ + "owned", + "shared", + "sharing" + ] + }, + "CreateAlbumDto": { + "type": "object", + "properties": { + "albumName": { + "type": "string" + }, + "sharedWithUserIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "assetIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "albumName" + ] + }, + "AlbumResponseDto": { + "type": "object", + "properties": { + "assetCount": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "albumName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "albumThumbnailAssetId": { + "type": "string", + "nullable": true + }, + "shared": { + "type": "boolean" + }, + "sharedUsers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserResponseDto" + } + }, + "assets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetResponseDto" + } + } + }, + "required": [ + "assetCount", + "id", + "ownerId", + "albumName", + "createdAt", + "albumThumbnailAssetId", + "shared", + "sharedUsers", + "assets" + ] + }, + "AddUsersDto": { + "type": "object", + "properties": { + "sharedUserIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "sharedUserIds" + ] + }, + "AddAssetsDto": { + "type": "object", + "properties": { + "assetIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "assetIds" + ] + }, + "AddAssetsResponseDto": { + "type": "object", + "properties": { + "successfullyAdded": { + "type": "integer" + }, + "alreadyInAlbum": { + "type": "array", + "items": { + "type": "string" + } + }, + "album": { + "$ref": "#/components/schemas/AlbumResponseDto" + } + }, + "required": [ + "successfullyAdded", + "alreadyInAlbum" + ] + }, + "RemoveAssetsDto": { + "type": "object", + "properties": { + "assetIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "assetIds" + ] + }, + "UpdateAlbumDto": { + "type": "object", + "properties": { + "albumName": { + "type": "string" + }, + "albumThumbnailAssetId": { + "type": "string" + } + } + }, + "CreateTagDto": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/TagTypeEnum" + }, + "name": { + "type": "string" + } + }, + "required": [ + "type", + "name" + ] + }, + "ExifEntity": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "assetId": { + "type": "string" + }, + "description": { + "type": "string", + "description": "General info" + }, + "exifImageWidth": { + "type": "number", + "nullable": true + }, + "exifImageHeight": { + "type": "number", + "nullable": true + }, + "fileSizeInByte": { + "type": "number", + "nullable": true + }, + "orientation": { + "type": "string", + "nullable": true + }, + "dateTimeOriginal": { + "format": "date-time", + "type": "string", + "nullable": true + }, + "modifyDate": { + "format": "date-time", + "type": "string", + "nullable": true + }, + "latitude": { + "type": "number", + "nullable": true + }, + "longitude": { + "type": "number", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "state": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "make": { + "type": "string", + "nullable": true, + "description": "Image info" + }, + "model": { + "type": "string", + "nullable": true + }, + "imageName": { + "type": "string", + "nullable": true + }, + "lensModel": { + "type": "string", + "nullable": true + }, + "fNumber": { + "type": "number", + "nullable": true + }, + "focalLength": { + "type": "number", + "nullable": true + }, + "iso": { + "type": "number", + "nullable": true + }, + "exposureTime": { + "type": "number", + "nullable": true + }, + "fps": { + "type": "number", + "nullable": true, + "description": "Video info" + }, + "asset": { + "$ref": "#/components/schemas/AssetEntity" + }, + "exifTextSearchableColumn": { + "type": "string" + } + }, + "required": [ + "id", + "assetId", + "description", + "exifImageWidth", + "exifImageHeight", + "fileSizeInByte", + "orientation", + "dateTimeOriginal", + "modifyDate", + "latitude", + "longitude", + "city", + "state", + "country", + "make", + "model", + "imageName", + "lensModel", + "fNumber", + "focalLength", + "iso", + "exposureTime", + "exifTextSearchableColumn" + ] + }, + "SmartInfoEntity": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "assetId": { + "type": "string" + }, + "tags": { + "nullable": true, + "type": "array", + "items": { + "type": "string" + } + }, + "objects": { + "nullable": true, + "type": "array", + "items": { + "type": "string" + } + }, + "asset": { + "$ref": "#/components/schemas/AssetEntity" + } + }, + "required": [ + "id", + "assetId", + "tags", + "objects" + ] + }, + "UserEntity": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "isAdmin": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "salt": { + "type": "string" + }, + "oauthId": { + "type": "string" + }, + "profileImagePath": { + "type": "string" + }, + "shouldChangePassword": { + "type": "boolean" + }, + "createdAt": { + "type": "string" + }, + "deletedAt": { + "format": "date-time", + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagEntity" + } + } + }, + "required": [ + "id", + "firstName", + "lastName", + "isAdmin", + "email", + "oauthId", + "profileImagePath", + "shouldChangePassword", + "createdAt", + "tags" + ] + }, + "TagEntity": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "OBJECT", + "FACE", + "CUSTOM" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "renameTagId": { + "type": "string" + }, + "assets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetEntity" + } + }, + "user": { + "$ref": "#/components/schemas/UserEntity" + } + }, + "required": [ + "id", + "type", + "name", + "userId", + "renameTagId", + "assets", + "user" + ] + }, + "AssetEntity": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "deviceAssetId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "deviceId": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "IMAGE", + "VIDEO", + "AUDIO", + "OTHER" + ] + }, + "originalPath": { + "type": "string" + }, + "resizePath": { + "type": "string", + "nullable": true + }, + "webpPath": { + "type": "string", + "nullable": true + }, + "encodedVideoPath": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "modifiedAt": { + "type": "string" + }, + "isFavorite": { + "type": "boolean" + }, + "mimeType": { + "type": "string", + "nullable": true + }, + "checksum": { + "type": "object", + "nullable": true + }, + "duration": { + "type": "string", + "nullable": true + }, + "isVisible": { + "type": "boolean" + }, + "livePhotoVideoId": { + "type": "string", + "nullable": true + }, + "exifInfo": { + "$ref": "#/components/schemas/ExifEntity" + }, + "smartInfo": { + "$ref": "#/components/schemas/SmartInfoEntity" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagEntity" + } + } + }, + "required": [ + "id", + "deviceAssetId", + "userId", + "deviceId", + "type", + "originalPath", + "resizePath", + "webpPath", + "encodedVideoPath", + "createdAt", + "modifiedAt", + "isFavorite", + "mimeType", + "duration", + "isVisible", + "livePhotoVideoId", + "tags" + ] + }, + "UpdateTagDto": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "renameTagId": { + "type": "string" + } + } + }, + "LoginCredentialDto": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "testuser@email.com" + }, + "password": { + "type": "string", + "example": "password" + } + }, + "required": [ + "email", + "password" + ] + }, + "LoginResponseDto": { + "type": "object", + "properties": { + "accessToken": { + "type": "string", + "readOnly": true + }, + "userId": { + "type": "string", + "readOnly": true + }, + "userEmail": { + "type": "string", + "readOnly": true + }, + "firstName": { + "type": "string", + "readOnly": true + }, + "lastName": { + "type": "string", + "readOnly": true + }, + "profileImagePath": { + "type": "string", + "readOnly": true + }, + "isAdmin": { + "type": "boolean", + "readOnly": true + }, + "shouldChangePassword": { + "type": "boolean", + "readOnly": true + } + }, + "required": [ + "accessToken", + "userId", + "userEmail", + "firstName", + "lastName", + "profileImagePath", + "isAdmin", + "shouldChangePassword" + ] + }, + "SignUpDto": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "testuser@email.com" + }, + "password": { + "type": "string", + "example": "password" + }, + "firstName": { + "type": "string", + "example": "Admin" + }, + "lastName": { + "type": "string", + "example": "Doe" + } + }, + "required": [ + "email", + "password", + "firstName", + "lastName" + ] + }, + "AdminSignupResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "lastName", + "createdAt" + ] + }, + "ValidateAccessTokenResponseDto": { + "type": "object", + "properties": { + "authStatus": { + "type": "boolean" + } + }, + "required": [ + "authStatus" + ] + }, + "LogoutResponseDto": { + "type": "object", + "properties": { + "successful": { + "type": "boolean", + "readOnly": true + }, + "redirectUri": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "successful", + "redirectUri" + ] + }, + "OAuthConfigDto": { + "type": "object", + "properties": { + "redirectUri": { + "type": "string" + } + }, + "required": [ + "redirectUri" + ] + }, + "OAuthConfigResponseDto": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "readOnly": true + }, + "url": { + "type": "string", + "readOnly": true + }, + "buttonText": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "enabled" + ] + }, + "OAuthCallbackDto": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + }, + "required": [ + "url" + ] + }, + "DeviceTypeEnum": { + "type": "string", + "enum": [ + "IOS", + "ANDROID", + "WEB" + ] + }, + "CreateDeviceInfoDto": { + "type": "object", + "properties": { + "deviceType": { + "$ref": "#/components/schemas/DeviceTypeEnum" + }, + "deviceId": { + "type": "string" + }, + "isAutoBackup": { + "type": "boolean" + } + }, + "required": [ + "deviceType", + "deviceId" + ] + }, + "DeviceInfoResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "deviceType": { + "$ref": "#/components/schemas/DeviceTypeEnum" + }, + "userId": { + "type": "string" + }, + "deviceId": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "isAutoBackup": { + "type": "boolean" + } + }, + "required": [ + "id", + "deviceType", + "userId", + "deviceId", + "createdAt", + "isAutoBackup" + ] + }, + "UpdateDeviceInfoDto": { + "type": "object", + "properties": { + "deviceType": { + "$ref": "#/components/schemas/DeviceTypeEnum" + }, + "deviceId": { + "type": "string" + }, + "isAutoBackup": { + "type": "boolean" + } + }, + "required": [ + "deviceType", + "deviceId" + ] + }, + "ServerInfoResponseDto": { + "type": "object", + "properties": { + "diskSizeRaw": { + "type": "integer", + "format": "int64" + }, + "diskUseRaw": { + "type": "integer", + "format": "int64" + }, + "diskAvailableRaw": { + "type": "integer", + "format": "int64" + }, + "diskUsagePercentage": { + "type": "number", + "format": "float" + }, + "diskSize": { + "type": "string" + }, + "diskUse": { + "type": "string" + }, + "diskAvailable": { + "type": "string" + } + }, + "required": [ + "diskSizeRaw", + "diskUseRaw", + "diskAvailableRaw", + "diskUsagePercentage", + "diskSize", + "diskUse", + "diskAvailable" + ] + }, + "ServerPingResponse": { + "type": "object", + "properties": { + "res": { + "type": "string", + "readOnly": true, + "example": "pong" + } + }, + "required": [ + "res" + ] + }, + "ServerVersionReponseDto": { + "type": "object", + "properties": { + "major": { + "type": "integer" + }, + "minor": { + "type": "integer" + }, + "patch": { + "type": "integer" + }, + "build": { + "type": "integer" + } + }, + "required": [ + "major", + "minor", + "patch", + "build" + ] + }, + "UsageByUserDto": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "videos": { + "type": "integer" + }, + "photos": { + "type": "integer" + }, + "usageRaw": { + "type": "integer", + "format": "int64" + }, + "usage": { + "type": "string" + } + }, + "required": [ + "userId", + "videos", + "photos", + "usageRaw", + "usage" + ] + }, + "ServerStatsResponseDto": { + "type": "object", + "properties": { + "photos": { + "type": "integer" + }, + "videos": { + "type": "integer" + }, + "objects": { + "type": "integer" + }, + "usageRaw": { + "type": "integer", + "format": "int64" + }, + "usage": { + "type": "string" + }, + "usageByUser": { + "title": "Array of usage for each user", + "example": [ + { + "photos": 1, + "videos": 1, + "diskUsageRaw": 1 + } + ], + "type": "array", + "items": { + "$ref": "#/components/schemas/UsageByUserDto" + } + } + }, + "required": [ + "photos", + "videos", + "objects", + "usageRaw", + "usage", + "usageByUser" + ] + }, + "JobCounts": { + "type": "object", + "properties": { + "active": { + "type": "integer" + }, + "completed": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "delayed": { + "type": "integer" + }, + "waiting": { + "type": "integer" + } + }, + "required": [ + "active", + "completed", + "failed", + "delayed", + "waiting" + ] + }, + "AllJobStatusResponseDto": { + "type": "object", + "properties": { + "thumbnailGenerationQueueCount": { + "$ref": "#/components/schemas/JobCounts" + }, + "metadataExtractionQueueCount": { + "$ref": "#/components/schemas/JobCounts" + }, + "videoConversionQueueCount": { + "$ref": "#/components/schemas/JobCounts" + }, + "machineLearningQueueCount": { + "$ref": "#/components/schemas/JobCounts" + }, + "isThumbnailGenerationActive": { + "type": "boolean" + }, + "isMetadataExtractionActive": { + "type": "boolean" + }, + "isVideoConversionActive": { + "type": "boolean" + }, + "isMachineLearningActive": { + "type": "boolean" + } + }, + "required": [ + "thumbnailGenerationQueueCount", + "metadataExtractionQueueCount", + "videoConversionQueueCount", + "machineLearningQueueCount", + "isThumbnailGenerationActive", + "isMetadataExtractionActive", + "isVideoConversionActive", + "isMachineLearningActive" + ] + }, + "JobId": { + "type": "string", + "enum": [ + "thumbnail-generation", + "metadata-extraction", + "video-conversion", + "machine-learning" + ] + }, + "JobStatusResponseDto": { + "type": "object", + "properties": { + "isActive": { + "type": "boolean" + }, + "queueCount": { + "type": "object" + } + }, + "required": [ + "isActive", + "queueCount" + ] + }, + "JobCommand": { + "type": "string", + "enum": [ + "start", + "stop" + ] + }, + "JobCommandDto": { + "type": "object", + "properties": { + "command": { + "$ref": "#/components/schemas/JobCommand" + } + }, + "required": [ + "command" + ] + }, + "SystemConfigKey": { + "type": "string", + "enum": [ + "ffmpeg_crf", + "ffmpeg_preset", + "ffmpeg_target_video_codec", + "ffmpeg_target_audio_codec", + "ffmpeg_target_scaling" + ] + }, + "SystemConfigResponseItem": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "key": { + "$ref": "#/components/schemas/SystemConfigKey" + }, + "value": { + "type": "string" + }, + "defaultValue": { + "type": "string" + } + }, + "required": [ + "name", + "key", + "value", + "defaultValue" + ] + }, + "SystemConfigResponseDto": { + "type": "object", + "properties": { + "config": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SystemConfigResponseItem" + } + } + }, + "required": [ + "config" + ] + }, + "UpdateSystemConfigDto": { + "type": "object", + "properties": {} + } + } + } +} \ No newline at end of file diff --git a/server/libs/database/src/entities/asset.entity.ts b/server/libs/database/src/entities/asset.entity.ts index 02c265e67..6ff49747e 100644 --- a/server/libs/database/src/entities/asset.entity.ts +++ b/server/libs/database/src/entities/asset.entity.ts @@ -1,6 +1,7 @@ -import { Column, Entity, Index, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; +import { Column, Entity, Index, JoinTable, ManyToMany, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; import { ExifEntity } from './exif.entity'; import { SmartInfoEntity } from './smart-info.entity'; +import { TagEntity } from './tag.entity'; @Entity('assets') @Unique('UQ_userid_checksum', ['userId', 'checksum']) @@ -62,6 +63,11 @@ export class AssetEntity { @OneToOne(() => SmartInfoEntity, (smartInfoEntity) => smartInfoEntity.asset) smartInfo?: SmartInfoEntity; + + // https://github.com/typeorm/typeorm/blob/master/docs/many-to-many-relations.md + @ManyToMany(() => TagEntity, (tag) => tag.assets, { cascade: true }) + @JoinTable({ name: 'tag_asset' }) + tags!: TagEntity[]; } export enum AssetType { diff --git a/server/libs/database/src/entities/tag.entity.ts b/server/libs/database/src/entities/tag.entity.ts new file mode 100644 index 000000000..b3564337a --- /dev/null +++ b/server/libs/database/src/entities/tag.entity.ts @@ -0,0 +1,45 @@ +import { Column, Entity, ManyToMany, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; +import { AssetEntity } from './asset.entity'; +import { UserEntity } from './user.entity'; + +@Entity('tags') +@Unique('UQ_tag_name_userId', ['name', 'userId']) +export class TagEntity { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @Column() + type!: TagType; + + @Column() + name!: string; + + @Column() + userId!: string; + + @Column({ type: 'uuid', comment: 'The new renamed tagId', nullable: true }) + renameTagId!: string; + + @ManyToMany(() => AssetEntity, (asset) => asset.tags) + assets!: AssetEntity[]; + + @ManyToOne(() => UserEntity, (user) => user.tags) + user!: UserEntity; +} + +export enum TagType { + /** + * Tag that is detected by the ML model for object detection will use this type + */ + OBJECT = 'OBJECT', + + /** + * Face that is detected by the ML model for facial detection (TBD/NOT YET IMPLEMENTED) will use this type + */ + FACE = 'FACE', + + /** + * Tag that is created by the user will use this type + */ + CUSTOM = 'CUSTOM', +} diff --git a/server/libs/database/src/entities/user.entity.ts b/server/libs/database/src/entities/user.entity.ts index c114101c6..13bfc3d2e 100644 --- a/server/libs/database/src/entities/user.entity.ts +++ b/server/libs/database/src/entities/user.entity.ts @@ -1,4 +1,5 @@ -import { Column, CreateDateColumn, DeleteDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { Column, CreateDateColumn, DeleteDateColumn, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; +import { TagEntity } from './tag.entity'; @Entity('users') export class UserEntity { @@ -37,4 +38,7 @@ export class UserEntity { @DeleteDateColumn() deletedAt?: Date; + + @OneToMany(() => TagEntity, (tag) => tag.user) + tags!: TagEntity[]; } diff --git a/server/libs/database/src/migrations/1670257571385-CreateTagsTable.ts b/server/libs/database/src/migrations/1670257571385-CreateTagsTable.ts new file mode 100644 index 000000000..0585aecc8 --- /dev/null +++ b/server/libs/database/src/migrations/1670257571385-CreateTagsTable.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateTagsTable1670257571385 implements MigrationInterface { + name = 'CreateTagsTable1670257571385' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "tags" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "type" character varying NOT NULL, "name" character varying NOT NULL, "userId" uuid NOT NULL, "renameTagId" uuid, CONSTRAINT "UQ_tag_name_userId" UNIQUE ("name", "userId"), CONSTRAINT "PK_e7dc17249a1148a1970748eda99" PRIMARY KEY ("id")); COMMENT ON COLUMN "tags"."renameTagId" IS 'The new renamed tagId'`); + await queryRunner.query(`CREATE TABLE "tag_asset" ("assetsId" uuid NOT NULL, "tagsId" uuid NOT NULL, CONSTRAINT "PK_ef5346fe522b5fb3bc96454747e" PRIMARY KEY ("assetsId", "tagsId"))`); + await queryRunner.query(`CREATE INDEX "IDX_f8e8a9e893cb5c54907f1b798e" ON "tag_asset" ("assetsId") `); + await queryRunner.query(`CREATE INDEX "IDX_e99f31ea4cdf3a2c35c7287eb4" ON "tag_asset" ("tagsId") `); + await queryRunner.query(`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9" FOREIGN KEY ("assetsId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42" FOREIGN KEY ("tagsId") REFERENCES "tags"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42"`); + await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9"`); + await queryRunner.query(`ALTER TABLE "tags" DROP CONSTRAINT "FK_92e67dc508c705dd66c94615576"`); + await queryRunner.query(`DROP INDEX "public"."IDX_e99f31ea4cdf3a2c35c7287eb4"`); + await queryRunner.query(`DROP INDEX "public"."IDX_f8e8a9e893cb5c54907f1b798e"`); + await queryRunner.query(`DROP TABLE "tag_asset"`); + await queryRunner.query(`DROP TABLE "tags"`); + } + +} diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 233800bdf..c9874b681 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -325,6 +325,143 @@ export interface AssetCountByUserIdResponseDto { */ 'total': number; } +/** + * + * @export + * @interface AssetEntity + */ +export interface AssetEntity { + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'id': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'deviceAssetId': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'userId': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'deviceId': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'type': AssetEntityTypeEnum; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'originalPath': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'resizePath': string | null; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'webpPath': string | null; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'encodedVideoPath': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'createdAt': string; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'modifiedAt': string; + /** + * + * @type {boolean} + * @memberof AssetEntity + */ + 'isFavorite': boolean; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'mimeType': string | null; + /** + * + * @type {object} + * @memberof AssetEntity + */ + 'checksum'?: object | null; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'duration': string | null; + /** + * + * @type {boolean} + * @memberof AssetEntity + */ + 'isVisible': boolean; + /** + * + * @type {string} + * @memberof AssetEntity + */ + 'livePhotoVideoId': string | null; + /** + * + * @type {ExifEntity} + * @memberof AssetEntity + */ + 'exifInfo'?: ExifEntity; + /** + * + * @type {SmartInfoEntity} + * @memberof AssetEntity + */ + 'smartInfo'?: SmartInfoEntity; + /** + * + * @type {Array} + * @memberof AssetEntity + */ + 'tags': Array; +} + +export const AssetEntityTypeEnum = { + Image: 'IMAGE', + Video: 'VIDEO', + Audio: 'AUDIO', + Other: 'OTHER' +} as const; + +export type AssetEntityTypeEnum = typeof AssetEntityTypeEnum[keyof typeof AssetEntityTypeEnum]; + /** * * @export @@ -446,6 +583,12 @@ export interface AssetResponseDto { * @memberof AssetResponseDto */ 'livePhotoVideoId'?: string | null; + /** + * + * @type {Array} + * @memberof AssetResponseDto + */ + 'tags': Array; } /** * @@ -602,6 +745,25 @@ export interface CreateProfileImageResponseDto { */ 'profileImagePath': string; } +/** + * + * @export + * @interface CreateTagDto + */ +export interface CreateTagDto { + /** + * + * @type {TagTypeEnum} + * @memberof CreateTagDto + */ + 'type': TagTypeEnum; + /** + * + * @type {string} + * @memberof CreateTagDto + */ + 'name': string; +} /** * * @export @@ -811,6 +973,163 @@ export const DeviceTypeEnum = { export type DeviceTypeEnum = typeof DeviceTypeEnum[keyof typeof DeviceTypeEnum]; +/** + * + * @export + * @interface ExifEntity + */ +export interface ExifEntity { + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'id': string; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'assetId': string; + /** + * General info + * @type {string} + * @memberof ExifEntity + */ + 'description': string; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'exifImageWidth': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'exifImageHeight': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'fileSizeInByte': number | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'orientation': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'dateTimeOriginal': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'modifyDate': string | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'latitude': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'longitude': number | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'city': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'state': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'country': string | null; + /** + * Image info + * @type {string} + * @memberof ExifEntity + */ + 'make': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'model': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'imageName': string | null; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'lensModel': string | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'fNumber': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'focalLength': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'iso': number | null; + /** + * + * @type {number} + * @memberof ExifEntity + */ + 'exposureTime': number | null; + /** + * Video info + * @type {number} + * @memberof ExifEntity + */ + 'fps'?: number | null; + /** + * + * @type {AssetEntity} + * @memberof ExifEntity + */ + 'asset'?: AssetEntity; + /** + * + * @type {string} + * @memberof ExifEntity + */ + 'exifTextSearchableColumn': string; +} /** * * @export @@ -1400,6 +1719,43 @@ export interface SignUpDto { */ 'lastName': string; } +/** + * + * @export + * @interface SmartInfoEntity + */ +export interface SmartInfoEntity { + /** + * + * @type {string} + * @memberof SmartInfoEntity + */ + 'id': string; + /** + * + * @type {string} + * @memberof SmartInfoEntity + */ + 'assetId': string; + /** + * + * @type {Array} + * @memberof SmartInfoEntity + */ + 'tags': Array | null; + /** + * + * @type {Array} + * @memberof SmartInfoEntity + */ + 'objects': Array | null; + /** + * + * @type {AssetEntity} + * @memberof SmartInfoEntity + */ + 'asset'?: AssetEntity; +} /** * * @export @@ -1486,6 +1842,104 @@ export interface SystemConfigResponseItem { */ 'defaultValue': string; } +/** + * + * @export + * @interface TagEntity + */ +export interface TagEntity { + /** + * + * @type {string} + * @memberof TagEntity + */ + 'id': string; + /** + * + * @type {string} + * @memberof TagEntity + */ + 'type': TagEntityTypeEnum; + /** + * + * @type {string} + * @memberof TagEntity + */ + 'name': string; + /** + * + * @type {string} + * @memberof TagEntity + */ + 'userId': string; + /** + * + * @type {string} + * @memberof TagEntity + */ + 'renameTagId': string; + /** + * + * @type {Array} + * @memberof TagEntity + */ + 'assets': Array; + /** + * + * @type {UserEntity} + * @memberof TagEntity + */ + 'user': UserEntity; +} + +export const TagEntityTypeEnum = { + Object: 'OBJECT', + Face: 'FACE', + Custom: 'CUSTOM' +} as const; + +export type TagEntityTypeEnum = typeof TagEntityTypeEnum[keyof typeof TagEntityTypeEnum]; + +/** + * + * @export + * @interface TagResponseDto + */ +export interface TagResponseDto { + /** + * + * @type {string} + * @memberof TagResponseDto + */ + 'id': string; + /** + * + * @type {TagTypeEnum} + * @memberof TagResponseDto + */ + 'type': TagTypeEnum; + /** + * + * @type {string} + * @memberof TagResponseDto + */ + 'name': string; +} +/** + * + * @export + * @enum {string} + */ + +export const TagTypeEnum = { + Object: 'OBJECT', + Face: 'FACE', + Custom: 'CUSTOM' +} as const; + +export type TagTypeEnum = typeof TagTypeEnum[keyof typeof TagTypeEnum]; + + /** * * @export @@ -1539,12 +1993,18 @@ export interface UpdateAlbumDto { * @interface UpdateAssetDto */ export interface UpdateAssetDto { + /** + * + * @type {Array} + * @memberof UpdateAssetDto + */ + 'tagIds'?: Array; /** * * @type {boolean} * @memberof UpdateAssetDto */ - 'isFavorite': boolean; + 'isFavorite'?: boolean; } /** * @@ -1571,6 +2031,25 @@ export interface UpdateDeviceInfoDto { */ 'isAutoBackup'?: boolean; } +/** + * + * @export + * @interface UpdateTagDto + */ +export interface UpdateTagDto { + /** + * + * @type {string} + * @memberof UpdateTagDto + */ + 'name'?: string; + /** + * + * @type {string} + * @memberof UpdateTagDto + */ + 'renameTagId'?: string; +} /** * * @export @@ -1670,6 +2149,91 @@ export interface UserCountResponseDto { */ 'userCount': number; } +/** + * + * @export + * @interface UserEntity + */ +export interface UserEntity { + /** + * + * @type {string} + * @memberof UserEntity + */ + 'id': string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'firstName': string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'lastName': string; + /** + * + * @type {boolean} + * @memberof UserEntity + */ + 'isAdmin': boolean; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'email': string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'password'?: string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'salt'?: string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'oauthId': string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'profileImagePath': string; + /** + * + * @type {boolean} + * @memberof UserEntity + */ + 'shouldChangePassword': boolean; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'createdAt': string; + /** + * + * @type {string} + * @memberof UserEntity + */ + 'deletedAt'?: string; + /** + * + * @type {Array} + * @memberof UserEntity + */ + 'tags': Array; +} /** * * @export @@ -3246,12 +3810,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {*} [options] Override http request option. * @throws {RequiredError} */ - updateAssetById: async (assetId: string, updateAssetDto: UpdateAssetDto, options: AxiosRequestConfig = {}): Promise => { + updateAsset: async (assetId: string, updateAssetDto: UpdateAssetDto, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'assetId' is not null or undefined - assertParamExists('updateAssetById', 'assetId', assetId) + assertParamExists('updateAsset', 'assetId', assetId) // verify required parameter 'updateAssetDto' is not null or undefined - assertParamExists('updateAssetById', 'updateAssetDto', updateAssetDto) - const localVarPath = `/asset/assetById/{assetId}` + assertParamExists('updateAsset', 'updateAssetDto', updateAssetDto) + const localVarPath = `/asset/{assetId}` .replace(`{${"assetId"}}`, encodeURIComponent(String(assetId))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -3520,8 +4084,8 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async updateAssetById(assetId: string, updateAssetDto: UpdateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.updateAssetById(assetId, updateAssetDto, options); + async updateAsset(assetId: string, updateAssetDto: UpdateAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateAsset(assetId, updateAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -3711,8 +4275,8 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @param {*} [options] Override http request option. * @throws {RequiredError} */ - updateAssetById(assetId: string, updateAssetDto: UpdateAssetDto, options?: any): AxiosPromise { - return localVarFp.updateAssetById(assetId, updateAssetDto, options).then((request) => request(axios, basePath)); + updateAsset(assetId: string, updateAssetDto: UpdateAssetDto, options?: any): AxiosPromise { + return localVarFp.updateAsset(assetId, updateAssetDto, options).then((request) => request(axios, basePath)); }, /** * @@ -3935,8 +4499,8 @@ export class AssetApi extends BaseAPI { * @throws {RequiredError} * @memberof AssetApi */ - public updateAssetById(assetId: string, updateAssetDto: UpdateAssetDto, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).updateAssetById(assetId, updateAssetDto, options).then((request) => request(this.axios, this.basePath)); + public updateAsset(assetId: string, updateAssetDto: UpdateAssetDto, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).updateAsset(assetId, updateAssetDto, options).then((request) => request(this.axios, this.basePath)); } /** @@ -5250,6 +5814,363 @@ export class SystemConfigApi extends BaseAPI { } +/** + * TagApi - axios parameter creator + * @export + */ +export const TagApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + _delete: async (id: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('_delete', 'id', id) + const localVarPath = `/tag/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {CreateTagDto} createTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + create: async (createTagDto: CreateTagDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'createTagDto' is not null or undefined + assertParamExists('create', 'createTagDto', createTagDto) + const localVarPath = `/tag`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(createTagDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + findAll: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/tag`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + findOne: async (id: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('findOne', 'id', id) + const localVarPath = `/tag/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id + * @param {UpdateTagDto} updateTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + update: async (id: string, updateTagDto: UpdateTagDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('update', 'id', id) + // verify required parameter 'updateTagDto' is not null or undefined + assertParamExists('update', 'updateTagDto', updateTagDto) + const localVarPath = `/tag/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(updateTagDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * TagApi - functional programming interface + * @export + */ +export const TagApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = TagApiAxiosParamCreator(configuration) + return { + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async _delete(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator._delete(id, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {CreateTagDto} createTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async create(createTagDto: CreateTagDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.create(createTagDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async findAll(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.findAll(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async findOne(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.findOne(id, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {string} id + * @param {UpdateTagDto} updateTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async update(id: string, updateTagDto: UpdateTagDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.update(id, updateTagDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * TagApi - factory interface + * @export + */ +export const TagApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = TagApiFp(configuration) + return { + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + _delete(id: string, options?: any): AxiosPromise { + return localVarFp._delete(id, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {CreateTagDto} createTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + create(createTagDto: CreateTagDto, options?: any): AxiosPromise { + return localVarFp.create(createTagDto, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + findAll(options?: any): AxiosPromise> { + return localVarFp.findAll(options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + findOne(id: string, options?: any): AxiosPromise { + return localVarFp.findOne(id, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {string} id + * @param {UpdateTagDto} updateTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + update(id: string, updateTagDto: UpdateTagDto, options?: any): AxiosPromise { + return localVarFp.update(id, updateTagDto, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * TagApi - object-oriented interface + * @export + * @class TagApi + * @extends {BaseAPI} + */ +export class TagApi extends BaseAPI { + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public _delete(id: string, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration)._delete(id, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {CreateTagDto} createTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public create(createTagDto: CreateTagDto, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).create(createTagDto, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public findAll(options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).findAll(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public findOne(id: string, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).findOne(id, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {string} id + * @param {UpdateTagDto} updateTagDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public update(id: string, updateTagDto: UpdateTagDto, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).update(id, updateTagDto, options).then((request) => request(this.axios, this.basePath)); + } +} + + /** * UserApi - axios parameter creator * @export diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index cd46776e9..17f58a64f 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -183,7 +183,7 @@ }; const toggleFavorite = async () => { - const { data } = await api.assetApi.updateAssetById(asset.id, { + const { data } = await api.assetApi.updateAsset(asset.id, { isFavorite: !asset.isFavorite }); diff --git a/web/src/lib/components/shared-components/status-box.svelte b/web/src/lib/components/shared-components/status-box.svelte index b8d5c5feb..a7271b469 100644 --- a/web/src/lib/components/shared-components/status-box.svelte +++ b/web/src/lib/components/shared-components/status-box.svelte @@ -62,7 +62,9 @@ style={`width: ${getStorageUsagePercentage()}%`} /> -

{asByteUnitString(serverInfo?.diskUseRaw)} of {asByteUnitString(serverInfo?.diskSizeRaw)} used

+

+ {asByteUnitString(serverInfo?.diskUseRaw)} of {asByteUnitString(serverInfo?.diskSizeRaw)} used +

{:else}
diff --git a/web/src/lib/components/shared-components/upload-panel.svelte b/web/src/lib/components/shared-components/upload-panel.svelte index e393f61dd..c8eb90246 100644 --- a/web/src/lib/components/shared-components/upload-panel.svelte +++ b/web/src/lib/components/shared-components/upload-panel.svelte @@ -115,9 +115,7 @@