mirror of
https://github.com/immich-app/immich.git
synced 2025-04-17 21:46:25 +02:00
Add asset repository and refactor asset service (#540)
* build endpoint to get asset count by month * Added asset repository * Added create asset * get asset by device ID * Added test for existing methods * Refactor additional endpoint * Refactor database api to get curated locations and curated objects * Refactor get search properties * Fixed cookies parsing for websocket * Added API to get asset count by time group * Remove unused code
This commit is contained in:
parent
6b7c97c02a
commit
f980a2f27a
33 changed files with 1321 additions and 138 deletions
mobile/openapi
.openapi-generator
README.mddoc
AssetApi.mdAssetCountByTimeGroupDto.mdAssetCountByTimeGroupResponseDto.mdGetAssetCountByTimeGroupDto.mdTimeGroupEnum.md
lib
test
server
apps/immich/src/api-v1
asset
asset-repository.tsasset.controller.tsasset.module.tsasset.service.spec.tsasset.service.ts
dto
response-dto
communication
web/src
|
@ -8,6 +8,8 @@ doc/AdminSignupResponseDto.md
|
||||||
doc/AlbumApi.md
|
doc/AlbumApi.md
|
||||||
doc/AlbumResponseDto.md
|
doc/AlbumResponseDto.md
|
||||||
doc/AssetApi.md
|
doc/AssetApi.md
|
||||||
|
doc/AssetCountByTimeGroupDto.md
|
||||||
|
doc/AssetCountByTimeGroupResponseDto.md
|
||||||
doc/AssetFileUploadResponseDto.md
|
doc/AssetFileUploadResponseDto.md
|
||||||
doc/AssetResponseDto.md
|
doc/AssetResponseDto.md
|
||||||
doc/AssetTypeEnum.md
|
doc/AssetTypeEnum.md
|
||||||
|
@ -27,6 +29,7 @@ doc/DeviceInfoApi.md
|
||||||
doc/DeviceInfoResponseDto.md
|
doc/DeviceInfoResponseDto.md
|
||||||
doc/DeviceTypeEnum.md
|
doc/DeviceTypeEnum.md
|
||||||
doc/ExifResponseDto.md
|
doc/ExifResponseDto.md
|
||||||
|
doc/GetAssetCountByTimeGroupDto.md
|
||||||
doc/LoginCredentialDto.md
|
doc/LoginCredentialDto.md
|
||||||
doc/LoginResponseDto.md
|
doc/LoginResponseDto.md
|
||||||
doc/LogoutResponseDto.md
|
doc/LogoutResponseDto.md
|
||||||
|
@ -39,6 +42,7 @@ doc/ServerVersionReponseDto.md
|
||||||
doc/SignUpDto.md
|
doc/SignUpDto.md
|
||||||
doc/SmartInfoResponseDto.md
|
doc/SmartInfoResponseDto.md
|
||||||
doc/ThumbnailFormat.md
|
doc/ThumbnailFormat.md
|
||||||
|
doc/TimeGroupEnum.md
|
||||||
doc/UpdateAlbumDto.md
|
doc/UpdateAlbumDto.md
|
||||||
doc/UpdateDeviceInfoDto.md
|
doc/UpdateDeviceInfoDto.md
|
||||||
doc/UpdateUserDto.md
|
doc/UpdateUserDto.md
|
||||||
|
@ -66,6 +70,8 @@ lib/model/add_assets_dto.dart
|
||||||
lib/model/add_users_dto.dart
|
lib/model/add_users_dto.dart
|
||||||
lib/model/admin_signup_response_dto.dart
|
lib/model/admin_signup_response_dto.dart
|
||||||
lib/model/album_response_dto.dart
|
lib/model/album_response_dto.dart
|
||||||
|
lib/model/asset_count_by_time_group_dto.dart
|
||||||
|
lib/model/asset_count_by_time_group_response_dto.dart
|
||||||
lib/model/asset_file_upload_response_dto.dart
|
lib/model/asset_file_upload_response_dto.dart
|
||||||
lib/model/asset_response_dto.dart
|
lib/model/asset_response_dto.dart
|
||||||
lib/model/asset_type_enum.dart
|
lib/model/asset_type_enum.dart
|
||||||
|
@ -83,6 +89,7 @@ lib/model/delete_asset_status.dart
|
||||||
lib/model/device_info_response_dto.dart
|
lib/model/device_info_response_dto.dart
|
||||||
lib/model/device_type_enum.dart
|
lib/model/device_type_enum.dart
|
||||||
lib/model/exif_response_dto.dart
|
lib/model/exif_response_dto.dart
|
||||||
|
lib/model/get_asset_count_by_time_group_dto.dart
|
||||||
lib/model/login_credential_dto.dart
|
lib/model/login_credential_dto.dart
|
||||||
lib/model/login_response_dto.dart
|
lib/model/login_response_dto.dart
|
||||||
lib/model/logout_response_dto.dart
|
lib/model/logout_response_dto.dart
|
||||||
|
@ -94,6 +101,7 @@ lib/model/server_version_reponse_dto.dart
|
||||||
lib/model/sign_up_dto.dart
|
lib/model/sign_up_dto.dart
|
||||||
lib/model/smart_info_response_dto.dart
|
lib/model/smart_info_response_dto.dart
|
||||||
lib/model/thumbnail_format.dart
|
lib/model/thumbnail_format.dart
|
||||||
|
lib/model/time_group_enum.dart
|
||||||
lib/model/update_album_dto.dart
|
lib/model/update_album_dto.dart
|
||||||
lib/model/update_device_info_dto.dart
|
lib/model/update_device_info_dto.dart
|
||||||
lib/model/update_user_dto.dart
|
lib/model/update_user_dto.dart
|
||||||
|
|
|
@ -79,10 +79,11 @@ Class | Method | HTTP request | Description
|
||||||
*AssetApi* | [**downloadFile**](doc//AssetApi.md#downloadfile) | **GET** /asset/download |
|
*AssetApi* | [**downloadFile**](doc//AssetApi.md#downloadfile) | **GET** /asset/download |
|
||||||
*AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset |
|
*AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset |
|
||||||
*AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |
|
*AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |
|
||||||
*AssetApi* | [**getAssetSearchTerms**](doc//AssetApi.md#getassetsearchterms) | **GET** /asset/searchTerm |
|
*AssetApi* | [**getAssetCountByTimeGroup**](doc//AssetApi.md#getassetcountbytimegroup) | **GET** /asset/count-by-date |
|
||||||
|
*AssetApi* | [**getAssetSearchTerms**](doc//AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms |
|
||||||
*AssetApi* | [**getAssetThumbnail**](doc//AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |
|
*AssetApi* | [**getAssetThumbnail**](doc//AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |
|
||||||
*AssetApi* | [**getCuratedLocations**](doc//AssetApi.md#getcuratedlocations) | **GET** /asset/allLocation |
|
*AssetApi* | [**getCuratedLocations**](doc//AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations |
|
||||||
*AssetApi* | [**getCuratedObjects**](doc//AssetApi.md#getcuratedobjects) | **GET** /asset/allObjects |
|
*AssetApi* | [**getCuratedObjects**](doc//AssetApi.md#getcuratedobjects) | **GET** /asset/curated-objects |
|
||||||
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||||
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
|
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
|
||||||
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file |
|
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file |
|
||||||
|
@ -112,6 +113,8 @@ Class | Method | HTTP request | Description
|
||||||
- [AddUsersDto](doc//AddUsersDto.md)
|
- [AddUsersDto](doc//AddUsersDto.md)
|
||||||
- [AdminSignupResponseDto](doc//AdminSignupResponseDto.md)
|
- [AdminSignupResponseDto](doc//AdminSignupResponseDto.md)
|
||||||
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
||||||
|
- [AssetCountByTimeGroupDto](doc//AssetCountByTimeGroupDto.md)
|
||||||
|
- [AssetCountByTimeGroupResponseDto](doc//AssetCountByTimeGroupResponseDto.md)
|
||||||
- [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md)
|
- [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md)
|
||||||
- [AssetResponseDto](doc//AssetResponseDto.md)
|
- [AssetResponseDto](doc//AssetResponseDto.md)
|
||||||
- [AssetTypeEnum](doc//AssetTypeEnum.md)
|
- [AssetTypeEnum](doc//AssetTypeEnum.md)
|
||||||
|
@ -129,6 +132,7 @@ Class | Method | HTTP request | Description
|
||||||
- [DeviceInfoResponseDto](doc//DeviceInfoResponseDto.md)
|
- [DeviceInfoResponseDto](doc//DeviceInfoResponseDto.md)
|
||||||
- [DeviceTypeEnum](doc//DeviceTypeEnum.md)
|
- [DeviceTypeEnum](doc//DeviceTypeEnum.md)
|
||||||
- [ExifResponseDto](doc//ExifResponseDto.md)
|
- [ExifResponseDto](doc//ExifResponseDto.md)
|
||||||
|
- [GetAssetCountByTimeGroupDto](doc//GetAssetCountByTimeGroupDto.md)
|
||||||
- [LoginCredentialDto](doc//LoginCredentialDto.md)
|
- [LoginCredentialDto](doc//LoginCredentialDto.md)
|
||||||
- [LoginResponseDto](doc//LoginResponseDto.md)
|
- [LoginResponseDto](doc//LoginResponseDto.md)
|
||||||
- [LogoutResponseDto](doc//LogoutResponseDto.md)
|
- [LogoutResponseDto](doc//LogoutResponseDto.md)
|
||||||
|
@ -140,6 +144,7 @@ Class | Method | HTTP request | Description
|
||||||
- [SignUpDto](doc//SignUpDto.md)
|
- [SignUpDto](doc//SignUpDto.md)
|
||||||
- [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
|
- [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
|
||||||
- [ThumbnailFormat](doc//ThumbnailFormat.md)
|
- [ThumbnailFormat](doc//ThumbnailFormat.md)
|
||||||
|
- [TimeGroupEnum](doc//TimeGroupEnum.md)
|
||||||
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
|
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
|
||||||
- [UpdateDeviceInfoDto](doc//UpdateDeviceInfoDto.md)
|
- [UpdateDeviceInfoDto](doc//UpdateDeviceInfoDto.md)
|
||||||
- [UpdateUserDto](doc//UpdateUserDto.md)
|
- [UpdateUserDto](doc//UpdateUserDto.md)
|
||||||
|
|
|
@ -14,10 +14,11 @@ Method | HTTP request | Description
|
||||||
[**downloadFile**](AssetApi.md#downloadfile) | **GET** /asset/download |
|
[**downloadFile**](AssetApi.md#downloadfile) | **GET** /asset/download |
|
||||||
[**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset |
|
[**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset |
|
||||||
[**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |
|
[**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |
|
||||||
[**getAssetSearchTerms**](AssetApi.md#getassetsearchterms) | **GET** /asset/searchTerm |
|
[**getAssetCountByTimeGroup**](AssetApi.md#getassetcountbytimegroup) | **GET** /asset/count-by-date |
|
||||||
|
[**getAssetSearchTerms**](AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms |
|
||||||
[**getAssetThumbnail**](AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |
|
[**getAssetThumbnail**](AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |
|
||||||
[**getCuratedLocations**](AssetApi.md#getcuratedlocations) | **GET** /asset/allLocation |
|
[**getCuratedLocations**](AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations |
|
||||||
[**getCuratedObjects**](AssetApi.md#getcuratedobjects) | **GET** /asset/allObjects |
|
[**getCuratedObjects**](AssetApi.md#getcuratedobjects) | **GET** /asset/curated-objects |
|
||||||
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||||
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
|
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
|
||||||
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file |
|
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file |
|
||||||
|
@ -267,6 +268,53 @@ 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)
|
[[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)
|
||||||
|
|
||||||
|
# **getAssetCountByTimeGroup**
|
||||||
|
> AssetCountByTimeGroupResponseDto getAssetCountByTimeGroup(getAssetCountByTimeGroupDto)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
// TODO Configure HTTP Bearer authorization: bearer
|
||||||
|
// Case 1. Use String Token
|
||||||
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
|
||||||
|
// Case 2. Use Function which generate token.
|
||||||
|
// String yourTokenGeneratorFunction() { ... }
|
||||||
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||||
|
|
||||||
|
final api_instance = AssetApi();
|
||||||
|
final getAssetCountByTimeGroupDto = GetAssetCountByTimeGroupDto(); // GetAssetCountByTimeGroupDto |
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = api_instance.getAssetCountByTimeGroup(getAssetCountByTimeGroupDto);
|
||||||
|
print(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling AssetApi->getAssetCountByTimeGroup: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------- | ------------- | ------------- | -------------
|
||||||
|
**getAssetCountByTimeGroupDto** | [**GetAssetCountByTimeGroupDto**](GetAssetCountByTimeGroupDto.md)| |
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
[**AssetCountByTimeGroupResponseDto**](AssetCountByTimeGroupResponseDto.md)
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
[bearer](../README.md#bearer)
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
# **getAssetSearchTerms**
|
# **getAssetSearchTerms**
|
||||||
> List<String> getAssetSearchTerms()
|
> List<String> getAssetSearchTerms()
|
||||||
|
|
||||||
|
|
16
mobile/openapi/doc/AssetCountByTimeGroupDto.md
Normal file
16
mobile/openapi/doc/AssetCountByTimeGroupDto.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# openapi.model.AssetCountByTimeGroupDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**timeGroup** | **String** | |
|
||||||
|
**count** | **int** | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
16
mobile/openapi/doc/AssetCountByTimeGroupResponseDto.md
Normal file
16
mobile/openapi/doc/AssetCountByTimeGroupResponseDto.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# openapi.model.AssetCountByTimeGroupResponseDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**totalAssets** | **int** | |
|
||||||
|
**groups** | [**List<AssetCountByTimeGroupDto>**](AssetCountByTimeGroupDto.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)
|
||||||
|
|
||||||
|
|
15
mobile/openapi/doc/GetAssetCountByTimeGroupDto.md
Normal file
15
mobile/openapi/doc/GetAssetCountByTimeGroupDto.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# openapi.model.GetAssetCountByTimeGroupDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**timeGroup** | [**TimeGroupEnum**](TimeGroupEnum.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)
|
||||||
|
|
||||||
|
|
14
mobile/openapi/doc/TimeGroupEnum.md
Normal file
14
mobile/openapi/doc/TimeGroupEnum.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# openapi.model.TimeGroupEnum
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ part 'model/add_assets_dto.dart';
|
||||||
part 'model/add_users_dto.dart';
|
part 'model/add_users_dto.dart';
|
||||||
part 'model/admin_signup_response_dto.dart';
|
part 'model/admin_signup_response_dto.dart';
|
||||||
part 'model/album_response_dto.dart';
|
part 'model/album_response_dto.dart';
|
||||||
|
part 'model/asset_count_by_time_group_dto.dart';
|
||||||
|
part 'model/asset_count_by_time_group_response_dto.dart';
|
||||||
part 'model/asset_file_upload_response_dto.dart';
|
part 'model/asset_file_upload_response_dto.dart';
|
||||||
part 'model/asset_response_dto.dart';
|
part 'model/asset_response_dto.dart';
|
||||||
part 'model/asset_type_enum.dart';
|
part 'model/asset_type_enum.dart';
|
||||||
|
@ -55,6 +57,7 @@ part 'model/delete_asset_status.dart';
|
||||||
part 'model/device_info_response_dto.dart';
|
part 'model/device_info_response_dto.dart';
|
||||||
part 'model/device_type_enum.dart';
|
part 'model/device_type_enum.dart';
|
||||||
part 'model/exif_response_dto.dart';
|
part 'model/exif_response_dto.dart';
|
||||||
|
part 'model/get_asset_count_by_time_group_dto.dart';
|
||||||
part 'model/login_credential_dto.dart';
|
part 'model/login_credential_dto.dart';
|
||||||
part 'model/login_response_dto.dart';
|
part 'model/login_response_dto.dart';
|
||||||
part 'model/logout_response_dto.dart';
|
part 'model/logout_response_dto.dart';
|
||||||
|
@ -66,6 +69,7 @@ part 'model/server_version_reponse_dto.dart';
|
||||||
part 'model/sign_up_dto.dart';
|
part 'model/sign_up_dto.dart';
|
||||||
part 'model/smart_info_response_dto.dart';
|
part 'model/smart_info_response_dto.dart';
|
||||||
part 'model/thumbnail_format.dart';
|
part 'model/thumbnail_format.dart';
|
||||||
|
part 'model/time_group_enum.dart';
|
||||||
part 'model/update_album_dto.dart';
|
part 'model/update_album_dto.dart';
|
||||||
part 'model/update_device_info_dto.dart';
|
part 'model/update_device_info_dto.dart';
|
||||||
part 'model/update_user_dto.dart';
|
part 'model/update_user_dto.dart';
|
||||||
|
|
|
@ -298,10 +298,57 @@ class AssetApi {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs an HTTP 'GET /asset/searchTerm' operation and returns the [Response].
|
/// Performs an HTTP 'GET /asset/count-by-date' operation and returns the [Response].
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [GetAssetCountByTimeGroupDto] getAssetCountByTimeGroupDto (required):
|
||||||
|
Future<Response> getAssetCountByTimeGroupWithHttpInfo(GetAssetCountByTimeGroupDto getAssetCountByTimeGroupDto,) async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final path = r'/asset/count-by-date';
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody = getAssetCountByTimeGroupDto;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>['application/json'];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'GET',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [GetAssetCountByTimeGroupDto] getAssetCountByTimeGroupDto (required):
|
||||||
|
Future<AssetCountByTimeGroupResponseDto?> getAssetCountByTimeGroup(GetAssetCountByTimeGroupDto getAssetCountByTimeGroupDto,) async {
|
||||||
|
final response = await getAssetCountByTimeGroupWithHttpInfo(getAssetCountByTimeGroupDto,);
|
||||||
|
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), 'AssetCountByTimeGroupResponseDto',) as AssetCountByTimeGroupResponseDto;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an HTTP 'GET /asset/search-terms' operation and returns the [Response].
|
||||||
Future<Response> getAssetSearchTermsWithHttpInfo() async {
|
Future<Response> getAssetSearchTermsWithHttpInfo() async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/asset/searchTerm';
|
final path = r'/asset/search-terms';
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody;
|
Object? postBody;
|
||||||
|
@ -398,10 +445,10 @@ class AssetApi {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs an HTTP 'GET /asset/allLocation' operation and returns the [Response].
|
/// Performs an HTTP 'GET /asset/curated-locations' operation and returns the [Response].
|
||||||
Future<Response> getCuratedLocationsWithHttpInfo() async {
|
Future<Response> getCuratedLocationsWithHttpInfo() async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/asset/allLocation';
|
final path = r'/asset/curated-locations';
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody;
|
Object? postBody;
|
||||||
|
@ -442,10 +489,10 @@ class AssetApi {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs an HTTP 'GET /asset/allObjects' operation and returns the [Response].
|
/// Performs an HTTP 'GET /asset/curated-objects' operation and returns the [Response].
|
||||||
Future<Response> getCuratedObjectsWithHttpInfo() async {
|
Future<Response> getCuratedObjectsWithHttpInfo() async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/asset/allObjects';
|
final path = r'/asset/curated-objects';
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody;
|
Object? postBody;
|
||||||
|
|
|
@ -200,6 +200,10 @@ class ApiClient {
|
||||||
return AdminSignupResponseDto.fromJson(value);
|
return AdminSignupResponseDto.fromJson(value);
|
||||||
case 'AlbumResponseDto':
|
case 'AlbumResponseDto':
|
||||||
return AlbumResponseDto.fromJson(value);
|
return AlbumResponseDto.fromJson(value);
|
||||||
|
case 'AssetCountByTimeGroupDto':
|
||||||
|
return AssetCountByTimeGroupDto.fromJson(value);
|
||||||
|
case 'AssetCountByTimeGroupResponseDto':
|
||||||
|
return AssetCountByTimeGroupResponseDto.fromJson(value);
|
||||||
case 'AssetFileUploadResponseDto':
|
case 'AssetFileUploadResponseDto':
|
||||||
return AssetFileUploadResponseDto.fromJson(value);
|
return AssetFileUploadResponseDto.fromJson(value);
|
||||||
case 'AssetResponseDto':
|
case 'AssetResponseDto':
|
||||||
|
@ -234,6 +238,8 @@ class ApiClient {
|
||||||
return DeviceTypeEnumTypeTransformer().decode(value);
|
return DeviceTypeEnumTypeTransformer().decode(value);
|
||||||
case 'ExifResponseDto':
|
case 'ExifResponseDto':
|
||||||
return ExifResponseDto.fromJson(value);
|
return ExifResponseDto.fromJson(value);
|
||||||
|
case 'GetAssetCountByTimeGroupDto':
|
||||||
|
return GetAssetCountByTimeGroupDto.fromJson(value);
|
||||||
case 'LoginCredentialDto':
|
case 'LoginCredentialDto':
|
||||||
return LoginCredentialDto.fromJson(value);
|
return LoginCredentialDto.fromJson(value);
|
||||||
case 'LoginResponseDto':
|
case 'LoginResponseDto':
|
||||||
|
@ -256,6 +262,8 @@ class ApiClient {
|
||||||
return SmartInfoResponseDto.fromJson(value);
|
return SmartInfoResponseDto.fromJson(value);
|
||||||
case 'ThumbnailFormat':
|
case 'ThumbnailFormat':
|
||||||
return ThumbnailFormatTypeTransformer().decode(value);
|
return ThumbnailFormatTypeTransformer().decode(value);
|
||||||
|
case 'TimeGroupEnum':
|
||||||
|
return TimeGroupEnumTypeTransformer().decode(value);
|
||||||
case 'UpdateAlbumDto':
|
case 'UpdateAlbumDto':
|
||||||
return UpdateAlbumDto.fromJson(value);
|
return UpdateAlbumDto.fromJson(value);
|
||||||
case 'UpdateDeviceInfoDto':
|
case 'UpdateDeviceInfoDto':
|
||||||
|
|
|
@ -67,6 +67,9 @@ String parameterToString(dynamic value) {
|
||||||
if (value is ThumbnailFormat) {
|
if (value is ThumbnailFormat) {
|
||||||
return ThumbnailFormatTypeTransformer().encode(value).toString();
|
return ThumbnailFormatTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
if (value is TimeGroupEnum) {
|
||||||
|
return TimeGroupEnumTypeTransformer().encode(value).toString();
|
||||||
|
}
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
119
mobile/openapi/lib/model/asset_count_by_time_group_dto.dart
Normal file
119
mobile/openapi/lib/model/asset_count_by_time_group_dto.dart
Normal file
|
@ -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 AssetCountByTimeGroupDto {
|
||||||
|
/// Returns a new [AssetCountByTimeGroupDto] instance.
|
||||||
|
AssetCountByTimeGroupDto({
|
||||||
|
required this.timeGroup,
|
||||||
|
required this.count,
|
||||||
|
});
|
||||||
|
|
||||||
|
String timeGroup;
|
||||||
|
|
||||||
|
int count;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetCountByTimeGroupDto &&
|
||||||
|
other.timeGroup == timeGroup &&
|
||||||
|
other.count == count;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(timeGroup.hashCode) +
|
||||||
|
(count.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetCountByTimeGroupDto[timeGroup=$timeGroup, count=$count]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'timeGroup'] = timeGroup;
|
||||||
|
_json[r'count'] = count;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetCountByTimeGroupDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetCountByTimeGroupDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// 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 "AssetCountByTimeGroupDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "AssetCountByTimeGroupDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return AssetCountByTimeGroupDto(
|
||||||
|
timeGroup: mapValueOfType<String>(json, r'timeGroup')!,
|
||||||
|
count: mapValueOfType<int>(json, r'count')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetCountByTimeGroupDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetCountByTimeGroupDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetCountByTimeGroupDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetCountByTimeGroupDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetCountByTimeGroupDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeGroupDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetCountByTimeGroupDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetCountByTimeGroupDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetCountByTimeGroupDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeGroupDto.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 = <String>{
|
||||||
|
'timeGroup',
|
||||||
|
'count',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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 AssetCountByTimeGroupResponseDto {
|
||||||
|
/// Returns a new [AssetCountByTimeGroupResponseDto] instance.
|
||||||
|
AssetCountByTimeGroupResponseDto({
|
||||||
|
required this.totalAssets,
|
||||||
|
this.groups = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
int totalAssets;
|
||||||
|
|
||||||
|
List<AssetCountByTimeGroupDto> groups;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetCountByTimeGroupResponseDto &&
|
||||||
|
other.totalAssets == totalAssets &&
|
||||||
|
other.groups == groups;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(totalAssets.hashCode) +
|
||||||
|
(groups.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetCountByTimeGroupResponseDto[totalAssets=$totalAssets, groups=$groups]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'totalAssets'] = totalAssets;
|
||||||
|
_json[r'groups'] = groups;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetCountByTimeGroupResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetCountByTimeGroupResponseDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// 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 "AssetCountByTimeGroupResponseDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "AssetCountByTimeGroupResponseDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return AssetCountByTimeGroupResponseDto(
|
||||||
|
totalAssets: mapValueOfType<int>(json, r'totalAssets')!,
|
||||||
|
groups: AssetCountByTimeGroupDto.listFromJson(json[r'groups'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetCountByTimeGroupResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetCountByTimeGroupResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetCountByTimeGroupResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetCountByTimeGroupResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetCountByTimeGroupResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeGroupResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetCountByTimeGroupResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetCountByTimeGroupResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetCountByTimeGroupResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeGroupResponseDto.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 = <String>{
|
||||||
|
'totalAssets',
|
||||||
|
'groups',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
111
mobile/openapi/lib/model/get_asset_count_by_time_group_dto.dart
Normal file
111
mobile/openapi/lib/model/get_asset_count_by_time_group_dto.dart
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
//
|
||||||
|
// 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 GetAssetCountByTimeGroupDto {
|
||||||
|
/// Returns a new [GetAssetCountByTimeGroupDto] instance.
|
||||||
|
GetAssetCountByTimeGroupDto({
|
||||||
|
required this.timeGroup,
|
||||||
|
});
|
||||||
|
|
||||||
|
TimeGroupEnum timeGroup;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is GetAssetCountByTimeGroupDto &&
|
||||||
|
other.timeGroup == timeGroup;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(timeGroup.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'GetAssetCountByTimeGroupDto[timeGroup=$timeGroup]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'timeGroup'] = timeGroup;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [GetAssetCountByTimeGroupDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static GetAssetCountByTimeGroupDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// 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 "GetAssetCountByTimeGroupDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "GetAssetCountByTimeGroupDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return GetAssetCountByTimeGroupDto(
|
||||||
|
timeGroup: TimeGroupEnum.fromJson(json[r'timeGroup'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<GetAssetCountByTimeGroupDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <GetAssetCountByTimeGroupDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = GetAssetCountByTimeGroupDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, GetAssetCountByTimeGroupDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, GetAssetCountByTimeGroupDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = GetAssetCountByTimeGroupDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of GetAssetCountByTimeGroupDto-objects as value to a dart map
|
||||||
|
static Map<String, List<GetAssetCountByTimeGroupDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<GetAssetCountByTimeGroupDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = GetAssetCountByTimeGroupDto.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 = <String>{
|
||||||
|
'timeGroup',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
85
mobile/openapi/lib/model/time_group_enum.dart
Normal file
85
mobile/openapi/lib/model/time_group_enum.dart
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
//
|
||||||
|
// 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 TimeGroupEnum {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const TimeGroupEnum._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const day = TimeGroupEnum._(r'day');
|
||||||
|
static const month = TimeGroupEnum._(r'month');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][TimeGroupEnum].
|
||||||
|
static const values = <TimeGroupEnum>[
|
||||||
|
day,
|
||||||
|
month,
|
||||||
|
];
|
||||||
|
|
||||||
|
static TimeGroupEnum? fromJson(dynamic value) => TimeGroupEnumTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<TimeGroupEnum>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <TimeGroupEnum>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = TimeGroupEnum.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [TimeGroupEnum] to String,
|
||||||
|
/// and [decode] dynamic data back to [TimeGroupEnum].
|
||||||
|
class TimeGroupEnumTypeTransformer {
|
||||||
|
factory TimeGroupEnumTypeTransformer() => _instance ??= const TimeGroupEnumTypeTransformer._();
|
||||||
|
|
||||||
|
const TimeGroupEnumTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(TimeGroupEnum data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a TimeGroupEnum.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
TimeGroupEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data.toString()) {
|
||||||
|
case r'day': return TimeGroupEnum.day;
|
||||||
|
case r'month': return TimeGroupEnum.month;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [TimeGroupEnumTypeTransformer] instance.
|
||||||
|
static TimeGroupEnumTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
32
mobile/openapi/test/asset_count_by_time_group_dto_test.dart
Normal file
32
mobile/openapi/test/asset_count_by_time_group_dto_test.dart
Normal file
|
@ -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 AssetCountByTimeGroupDto
|
||||||
|
void main() {
|
||||||
|
// final instance = AssetCountByTimeGroupDto();
|
||||||
|
|
||||||
|
group('test AssetCountByTimeGroupDto', () {
|
||||||
|
// String timeGroup
|
||||||
|
test('to test the property `timeGroup`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// int count
|
||||||
|
test('to test the property `count`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -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 AssetCountByTimeGroupResponseDto
|
||||||
|
void main() {
|
||||||
|
// final instance = AssetCountByTimeGroupResponseDto();
|
||||||
|
|
||||||
|
group('test AssetCountByTimeGroupResponseDto', () {
|
||||||
|
// int totalAssets
|
||||||
|
test('to test the property `totalAssets`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// List<AssetCountByTimeGroupDto> groups (default value: const [])
|
||||||
|
test('to test the property `groups`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// 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 GetAssetCountByTimeGroupDto
|
||||||
|
void main() {
|
||||||
|
// final instance = GetAssetCountByTimeGroupDto();
|
||||||
|
|
||||||
|
group('test GetAssetCountByTimeGroupDto', () {
|
||||||
|
// String timeGroup (default value: 'month')
|
||||||
|
test('to test the property `timeGroup`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
21
mobile/openapi/test/time_group_enum_test.dart
Normal file
21
mobile/openapi/test/time_group_enum_test.dart
Normal file
|
@ -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 TimeGroupEnum
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
group('test TimeGroupEnum', () {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
187
server/apps/immich/src/api-v1/asset/asset-repository.ts
Normal file
187
server/apps/immich/src/api-v1/asset/asset-repository.ts
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
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 { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm/repository/Repository';
|
||||||
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
|
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
||||||
|
import { AssetCountByTimeGroupDto } from './response-dto/asset-count-by-time-group-response.dto';
|
||||||
|
import { TimeGroupEnum } from './dto/get-asset-count-by-time-group.dto';
|
||||||
|
|
||||||
|
export interface IAssetRepository {
|
||||||
|
create(createAssetDto: CreateAssetDto, ownerId: string, originalPath: string, mimeType: string): Promise<AssetEntity>;
|
||||||
|
getAllByUserId(userId: string): Promise<AssetEntity[]>;
|
||||||
|
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
|
||||||
|
getById(assetId: string): Promise<AssetEntity>;
|
||||||
|
getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]>;
|
||||||
|
getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]>;
|
||||||
|
getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]>;
|
||||||
|
getAssetCountByTimeGroup(userId: string, timeGroup: TimeGroupEnum): Promise<AssetCountByTimeGroupDto[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ASSET_REPOSITORY = 'ASSET_REPOSITORY';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AssetRepository implements IAssetRepository {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(AssetEntity)
|
||||||
|
private assetRepository: Repository<AssetEntity>,
|
||||||
|
) {}
|
||||||
|
async getAssetCountByTimeGroup(userId: string, timeGroup: TimeGroupEnum) {
|
||||||
|
let result: AssetCountByTimeGroupDto[] = [];
|
||||||
|
|
||||||
|
if (timeGroup === TimeGroupEnum.Month) {
|
||||||
|
result = await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.select(`COUNT(asset.id)::int`, 'count')
|
||||||
|
.addSelect(`to_char(date_trunc('month', "createdAt"::timestamptz), 'YYYY_MM')`, 'timeGroup')
|
||||||
|
.where('"userId" = :userId', { userId: userId })
|
||||||
|
.groupBy(`date_trunc('month', "createdAt"::timestamptz)`)
|
||||||
|
.orderBy(`date_trunc('month', "createdAt"::timestamptz)`, 'DESC')
|
||||||
|
.getRawMany();
|
||||||
|
} else if (timeGroup === TimeGroupEnum.Day) {
|
||||||
|
result = await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.select(`COUNT(asset.id)::int`, 'count')
|
||||||
|
.addSelect(`to_char(date_trunc('day', "createdAt"::timestamptz), 'YYYY_MM_DD')`, 'timeGroup')
|
||||||
|
.where('"userId" = :userId', { userId: userId })
|
||||||
|
.groupBy(`date_trunc('day', "createdAt"::timestamptz)`)
|
||||||
|
.orderBy(`date_trunc('day', "createdAt"::timestamptz)`, 'DESC')
|
||||||
|
.getRawMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]> {
|
||||||
|
return await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.where('asset.userId = :userId', { userId: userId })
|
||||||
|
.leftJoin('asset.exifInfo', 'ei')
|
||||||
|
.leftJoin('asset.smartInfo', 'si')
|
||||||
|
.select('si.tags', 'tags')
|
||||||
|
.addSelect('si.objects', 'objects')
|
||||||
|
.addSelect('asset.type', 'assetType')
|
||||||
|
.addSelect('ei.orientation', 'orientation')
|
||||||
|
.addSelect('ei."lensModel"', 'lensModel')
|
||||||
|
.addSelect('ei.make', 'make')
|
||||||
|
.addSelect('ei.model', 'model')
|
||||||
|
.addSelect('ei.city', 'city')
|
||||||
|
.addSelect('ei.state', 'state')
|
||||||
|
.addSelect('ei.country', 'country')
|
||||||
|
.distinctOn(['si.tags'])
|
||||||
|
.getRawMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]> {
|
||||||
|
return await this.assetRepository.query(
|
||||||
|
`
|
||||||
|
SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId"
|
||||||
|
FROM assets a
|
||||||
|
LEFT JOIN smart_info si ON a.id = si."assetId"
|
||||||
|
WHERE a."userId" = $1
|
||||||
|
AND si.objects IS NOT NULL
|
||||||
|
`,
|
||||||
|
[userId],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]> {
|
||||||
|
return await this.assetRepository.query(
|
||||||
|
`
|
||||||
|
SELECT DISTINCT ON (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId"
|
||||||
|
FROM assets a
|
||||||
|
LEFT JOIN exif e ON a.id = e."assetId"
|
||||||
|
WHERE a."userId" = $1
|
||||||
|
AND e.city IS NOT NULL
|
||||||
|
AND a.type = 'IMAGE';
|
||||||
|
`,
|
||||||
|
[userId],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single asset information by its ID
|
||||||
|
* - include exif info
|
||||||
|
* @param assetId
|
||||||
|
*/
|
||||||
|
async getById(assetId: string): Promise<AssetEntity> {
|
||||||
|
return await this.assetRepository.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
id: assetId,
|
||||||
|
},
|
||||||
|
relations: ['exifInfo'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all assets belong to the user on the database
|
||||||
|
* @param userId
|
||||||
|
*/
|
||||||
|
async getAllByUserId(userId: string): Promise<AssetEntity[]> {
|
||||||
|
const query = this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.where('asset.userId = :userId', { userId: userId })
|
||||||
|
.andWhere('asset.resizePath is not NULL')
|
||||||
|
.leftJoinAndSelect('asset.exifInfo', 'exifInfo')
|
||||||
|
.orderBy('asset.createdAt', 'DESC');
|
||||||
|
|
||||||
|
return await query.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new asset information in database
|
||||||
|
* @param createAssetDto
|
||||||
|
* @param ownerId
|
||||||
|
* @param originalPath
|
||||||
|
* @param mimeType
|
||||||
|
* @returns Promise<AssetEntity>
|
||||||
|
*/
|
||||||
|
async create(
|
||||||
|
createAssetDto: CreateAssetDto,
|
||||||
|
ownerId: string,
|
||||||
|
originalPath: string,
|
||||||
|
mimeType: string,
|
||||||
|
): Promise<AssetEntity> {
|
||||||
|
const asset = new AssetEntity();
|
||||||
|
asset.deviceAssetId = createAssetDto.deviceAssetId;
|
||||||
|
asset.userId = ownerId;
|
||||||
|
asset.deviceId = createAssetDto.deviceId;
|
||||||
|
asset.type = createAssetDto.assetType || AssetType.OTHER;
|
||||||
|
asset.originalPath = originalPath;
|
||||||
|
asset.createdAt = createAssetDto.createdAt;
|
||||||
|
asset.modifiedAt = createAssetDto.modifiedAt;
|
||||||
|
asset.isFavorite = createAssetDto.isFavorite;
|
||||||
|
asset.mimeType = mimeType;
|
||||||
|
asset.duration = createAssetDto.duration || null;
|
||||||
|
|
||||||
|
const createdAsset = await this.assetRepository.save(asset);
|
||||||
|
|
||||||
|
if (!createdAsset) {
|
||||||
|
throw new BadRequestException('Asset not created');
|
||||||
|
}
|
||||||
|
return createdAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get assets by device's Id on the database
|
||||||
|
* @param userId
|
||||||
|
* @param deviceId
|
||||||
|
*
|
||||||
|
* @returns Promise<string[]> - Array of assetIds belong to the device
|
||||||
|
*/
|
||||||
|
async getAllByDeviceId(userId: string, deviceId: string): Promise<string[]> {
|
||||||
|
const rows = await this.assetRepository.find({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
deviceId: deviceId,
|
||||||
|
},
|
||||||
|
select: ['deviceAssetId'],
|
||||||
|
});
|
||||||
|
const res: string[] = [];
|
||||||
|
rows.forEach((v) => res.push(v.deviceAssetId));
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ import {
|
||||||
Controller,
|
Controller,
|
||||||
Post,
|
Post,
|
||||||
UseInterceptors,
|
UseInterceptors,
|
||||||
UploadedFiles,
|
|
||||||
Body,
|
Body,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
Get,
|
Get,
|
||||||
|
@ -44,6 +43,8 @@ import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
|
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
|
||||||
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
||||||
import { GetAssetThumbnailDto } from './dto/get-asset-thumbnail.dto';
|
import { GetAssetThumbnailDto } from './dto/get-asset-thumbnail.dto';
|
||||||
|
import { AssetCountByTimeGroupResponseDto } from './response-dto/asset-count-by-time-group-response.dto';
|
||||||
|
import { GetAssetCountByTimeGroupDto } from './dto/get-asset-count-by-time-group.dto';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
|
@ -117,17 +118,17 @@ export class AssetController {
|
||||||
return this.assetService.getAssetThumbnail(assetId, query);
|
return this.assetService.getAssetThumbnail(assetId, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/allObjects')
|
@Get('/curated-objects')
|
||||||
async getCuratedObjects(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
async getCuratedObjects(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
||||||
return this.assetService.getCuratedObject(authUser);
|
return this.assetService.getCuratedObject(authUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/allLocation')
|
@Get('/curated-locations')
|
||||||
async getCuratedLocations(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedLocationsResponseDto[]> {
|
async getCuratedLocations(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedLocationsResponseDto[]> {
|
||||||
return this.assetService.getCuratedLocation(authUser);
|
return this.assetService.getCuratedLocation(authUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/searchTerm')
|
@Get('/search-terms')
|
||||||
async getAssetSearchTerms(@GetAuthUser() authUser: AuthUserDto): Promise<string[]> {
|
async getAssetSearchTerms(@GetAuthUser() authUser: AuthUserDto): Promise<string[]> {
|
||||||
return this.assetService.getAssetSearchTerm(authUser);
|
return this.assetService.getAssetSearchTerm(authUser);
|
||||||
}
|
}
|
||||||
|
@ -140,6 +141,14 @@ export class AssetController {
|
||||||
return this.assetService.searchAsset(authUser, searchAssetDto);
|
return this.assetService.searchAsset(authUser, searchAssetDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('/count-by-date')
|
||||||
|
async getAssetCountByTimeGroup(
|
||||||
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
|
@Body(ValidationPipe) getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto,
|
||||||
|
): Promise<AssetCountByTimeGroupResponseDto> {
|
||||||
|
return this.assetService.getAssetCountByTimeGroup(authUser, getAssetCountByTimeGroupDto);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all AssetEntity belong to the user
|
* Get all AssetEntity belong to the user
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { BackgroundTaskModule } from '../../modules/background-task/background-t
|
||||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
||||||
import { CommunicationModule } from '../communication/communication.module';
|
import { CommunicationModule } from '../communication/communication.module';
|
||||||
import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
||||||
|
import { AssetRepository, ASSET_REPOSITORY } from './asset-repository';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -24,7 +25,14 @@ import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
controllers: [AssetController],
|
controllers: [AssetController],
|
||||||
providers: [AssetService, BackgroundTaskService],
|
providers: [
|
||||||
|
AssetService,
|
||||||
|
BackgroundTaskService,
|
||||||
|
{
|
||||||
|
provide: ASSET_REPOSITORY,
|
||||||
|
useClass: AssetRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
exports: [AssetService],
|
exports: [AssetService],
|
||||||
})
|
})
|
||||||
export class AssetModule {}
|
export class AssetModule {}
|
||||||
|
|
92
server/apps/immich/src/api-v1/asset/asset.service.spec.ts
Normal file
92
server/apps/immich/src/api-v1/asset/asset.service.spec.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import { AssetRepository, IAssetRepository } from './asset-repository';
|
||||||
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
|
import { AssetService } from './asset.service';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
||||||
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
|
|
||||||
|
describe('AssetService', () => {
|
||||||
|
let sui: AssetService;
|
||||||
|
let a: Repository<AssetEntity>; // TO BE DELETED AFTER FINISHED REFACTORING
|
||||||
|
let assetRepositoryMock: jest.Mocked<IAssetRepository>;
|
||||||
|
|
||||||
|
const authUser: AuthUserDto = Object.freeze({
|
||||||
|
id: '3ea54709-e168-42b7-90b0-a0dfe8a7ecbd',
|
||||||
|
email: 'auth@test.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
const _getCreateAssetDto = (): CreateAssetDto => {
|
||||||
|
const createAssetDto = new CreateAssetDto();
|
||||||
|
createAssetDto.deviceAssetId = 'deviceAssetId';
|
||||||
|
createAssetDto.deviceId = 'deviceId';
|
||||||
|
createAssetDto.assetType = AssetType.OTHER;
|
||||||
|
createAssetDto.createdAt = '2022-06-19T23:41:36.910Z';
|
||||||
|
createAssetDto.modifiedAt = '2022-06-19T23:41:36.910Z';
|
||||||
|
createAssetDto.isFavorite = false;
|
||||||
|
createAssetDto.duration = '0:00:00.000000';
|
||||||
|
|
||||||
|
return createAssetDto;
|
||||||
|
};
|
||||||
|
const _getAsset = () => {
|
||||||
|
const assetEntity = new AssetEntity();
|
||||||
|
|
||||||
|
assetEntity.id = 'e8edabfd-7d8a-45d0-9d61-7c7ca60f2c67';
|
||||||
|
assetEntity.userId = '3ea54709-e168-42b7-90b0-a0dfe8a7ecbd';
|
||||||
|
assetEntity.deviceAssetId = '4967046344801';
|
||||||
|
assetEntity.deviceId = '116766fd-2ef2-52dc-a3ef-149988997291';
|
||||||
|
assetEntity.type = AssetType.VIDEO;
|
||||||
|
assetEntity.originalPath =
|
||||||
|
'upload/3ea54709-e168-42b7-90b0-a0dfe8a7ecbd/original/116766fd-2ef2-52dc-a3ef-149988997291/51c97f95-244f-462d-bdf0-e1dc19913516.jpg';
|
||||||
|
assetEntity.resizePath = '';
|
||||||
|
assetEntity.createdAt = '2022-06-19T23:41:36.910Z';
|
||||||
|
assetEntity.modifiedAt = '2022-06-19T23:41:36.910Z';
|
||||||
|
assetEntity.isFavorite = false;
|
||||||
|
assetEntity.mimeType = 'image/jpeg';
|
||||||
|
assetEntity.webpPath = '';
|
||||||
|
assetEntity.encodedVideoPath = '';
|
||||||
|
assetEntity.duration = '0:00:00.000000';
|
||||||
|
|
||||||
|
return assetEntity;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
assetRepositoryMock = {
|
||||||
|
create: jest.fn(),
|
||||||
|
getAllByUserId: jest.fn(),
|
||||||
|
getAllByDeviceId: jest.fn(),
|
||||||
|
getAssetCountByTimeGroup: jest.fn(),
|
||||||
|
getById: jest.fn(),
|
||||||
|
getDetectedObjectsByUserId: jest.fn(),
|
||||||
|
getLocationsByUserId: jest.fn(),
|
||||||
|
getSearchPropertiesByUserId: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
sui = new AssetService(assetRepositoryMock, a);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create an asset', async () => {
|
||||||
|
const assetEntity = _getAsset();
|
||||||
|
|
||||||
|
assetRepositoryMock.create.mockImplementation(() => Promise.resolve<AssetEntity>(assetEntity));
|
||||||
|
|
||||||
|
const originalPath =
|
||||||
|
'upload/3ea54709-e168-42b7-90b0-a0dfe8a7ecbd/original/116766fd-2ef2-52dc-a3ef-149988997291/51c97f95-244f-462d-bdf0-e1dc19913516.jpg';
|
||||||
|
const mimeType = 'image/jpeg';
|
||||||
|
const createAssetDto = _getCreateAssetDto();
|
||||||
|
const result = await sui.createUserAsset(authUser, createAssetDto, originalPath, mimeType);
|
||||||
|
|
||||||
|
expect(result.userId).toEqual(authUser.id);
|
||||||
|
expect(result.resizePath).toEqual('');
|
||||||
|
expect(result.webpPath).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get assets by device id', async () => {
|
||||||
|
assetRepositoryMock.getAllByDeviceId.mockImplementation(() => Promise.resolve<string[]>(['4967046344801']));
|
||||||
|
|
||||||
|
const deviceId = '116766fd-2ef2-52dc-a3ef-149988997291';
|
||||||
|
const result = await sui.getUserAssetsByDeviceId(authUser, deviceId);
|
||||||
|
|
||||||
|
expect(result.length).toEqual(1);
|
||||||
|
expect(result[0]).toEqual('4967046344801');
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,7 @@
|
||||||
|
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
Logger,
|
Logger,
|
||||||
|
@ -7,7 +9,7 @@ import {
|
||||||
StreamableFile,
|
StreamableFile,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { IsNull, Not, Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
||||||
import { constants, createReadStream, ReadStream, stat } from 'fs';
|
import { constants, createReadStream, ReadStream, stat } from 'fs';
|
||||||
|
@ -25,83 +27,49 @@ import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
||||||
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
||||||
import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto';
|
import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto';
|
||||||
|
import { ASSET_REPOSITORY, IAssetRepository } from './asset-repository';
|
||||||
|
import { SearchPropertiesDto } from './dto/search-properties.dto';
|
||||||
|
import {
|
||||||
|
AssetCountByTimeGroupResponseDto,
|
||||||
|
mapAssetCountByTimeGroupResponse,
|
||||||
|
} from './response-dto/asset-count-by-time-group-response.dto';
|
||||||
|
import { GetAssetCountByTimeGroupDto } from './dto/get-asset-count-by-time-group.dto';
|
||||||
|
|
||||||
const fileInfo = promisify(stat);
|
const fileInfo = promisify(stat);
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AssetService {
|
export class AssetService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(ASSET_REPOSITORY)
|
||||||
|
private _assetRepository: IAssetRepository,
|
||||||
|
|
||||||
@InjectRepository(AssetEntity)
|
@InjectRepository(AssetEntity)
|
||||||
private assetRepository: Repository<AssetEntity>,
|
private assetRepository: Repository<AssetEntity>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async updateThumbnailInfo(asset: AssetEntity, thumbnailPath: string): Promise<AssetEntity> {
|
|
||||||
const updatedAsset = await this.assetRepository
|
|
||||||
.createQueryBuilder('assets')
|
|
||||||
.update<AssetEntity>(AssetEntity, { ...asset, resizePath: thumbnailPath })
|
|
||||||
.where('assets.id = :id', { id: asset.id })
|
|
||||||
.returning('*')
|
|
||||||
.updateEntity(true)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
return updatedAsset.raw[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createUserAsset(
|
public async createUserAsset(
|
||||||
authUser: AuthUserDto,
|
authUser: AuthUserDto,
|
||||||
assetInfo: CreateAssetDto,
|
createAssetDto: CreateAssetDto,
|
||||||
path: string,
|
originalPath: string,
|
||||||
mimeType: string,
|
mimeType: string,
|
||||||
): Promise<AssetEntity | undefined> {
|
): Promise<AssetEntity> {
|
||||||
const asset = new AssetEntity();
|
const assetEntity = await this._assetRepository.create(createAssetDto, authUser.id, originalPath, mimeType);
|
||||||
asset.deviceAssetId = assetInfo.deviceAssetId;
|
|
||||||
asset.userId = authUser.id;
|
|
||||||
asset.deviceId = assetInfo.deviceId;
|
|
||||||
asset.type = assetInfo.assetType || AssetType.OTHER;
|
|
||||||
asset.originalPath = path;
|
|
||||||
asset.createdAt = assetInfo.createdAt;
|
|
||||||
asset.modifiedAt = assetInfo.modifiedAt;
|
|
||||||
asset.isFavorite = assetInfo.isFavorite;
|
|
||||||
asset.mimeType = mimeType;
|
|
||||||
asset.duration = assetInfo.duration || null;
|
|
||||||
|
|
||||||
const createdAsset = await this.assetRepository.save(asset);
|
return assetEntity;
|
||||||
if (!createdAsset) {
|
|
||||||
throw new Error('Asset not created');
|
|
||||||
}
|
|
||||||
return createdAsset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) {
|
public async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) {
|
||||||
const rows = await this.assetRepository.find({
|
return this._assetRepository.getAllByDeviceId(authUser.id, deviceId);
|
||||||
where: {
|
|
||||||
userId: authUser.id,
|
|
||||||
deviceId: deviceId,
|
|
||||||
},
|
|
||||||
select: ['deviceAssetId'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const res: string[] = [];
|
|
||||||
rows.forEach((v) => res.push(v.deviceAssetId));
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllAssets(authUser: AuthUserDto): Promise<AssetResponseDto[]> {
|
public async getAllAssets(authUser: AuthUserDto): Promise<AssetResponseDto[]> {
|
||||||
const assets = await this.assetRepository.find({
|
const assets = await this._assetRepository.getAllByUserId(authUser.id);
|
||||||
where: {
|
|
||||||
userId: authUser.id,
|
|
||||||
resizePath: Not(IsNull()),
|
|
||||||
},
|
|
||||||
relations: ['exifInfo'],
|
|
||||||
order: {
|
|
||||||
createdAt: 'DESC',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return assets.map((asset) => mapAsset(asset));
|
return assets.map((asset) => mapAsset(asset));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findAssetOfDevice(deviceId: string, assetId: string): Promise<AssetResponseDto> {
|
// TODO - Refactor this to get asset by its own id
|
||||||
|
private async findAssetOfDevice(deviceId: string, assetId: string): Promise<AssetResponseDto> {
|
||||||
const rows = await this.assetRepository.query(
|
const rows = await this.assetRepository.query(
|
||||||
'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."deviceId" = $2',
|
'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."deviceId" = $2',
|
||||||
[assetId, deviceId],
|
[assetId, deviceId],
|
||||||
|
@ -117,16 +85,7 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetById(authUser: AuthUserDto, assetId: string): Promise<AssetResponseDto> {
|
public async getAssetById(authUser: AuthUserDto, assetId: string): Promise<AssetResponseDto> {
|
||||||
const asset = await this.assetRepository.findOne({
|
const asset = await this._assetRepository.getById(assetId);
|
||||||
where: {
|
|
||||||
id: assetId,
|
|
||||||
},
|
|
||||||
relations: ['exifInfo'],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!asset) {
|
|
||||||
throw new NotFoundException('Asset not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapAsset(asset);
|
return mapAsset(asset);
|
||||||
}
|
}
|
||||||
|
@ -394,45 +353,35 @@ export class AssetService {
|
||||||
|
|
||||||
async getAssetSearchTerm(authUser: AuthUserDto): Promise<string[]> {
|
async getAssetSearchTerm(authUser: AuthUserDto): Promise<string[]> {
|
||||||
const possibleSearchTerm = new Set<string>();
|
const possibleSearchTerm = new Set<string>();
|
||||||
// TODO: should use query builder
|
|
||||||
const rows = await this.assetRepository.query(
|
|
||||||
`
|
|
||||||
SELECT DISTINCT si.tags, si.objects, e.orientation, e."lensModel", e.make, e.model , a.type, e.city, e.state, e.country
|
|
||||||
FROM assets a
|
|
||||||
LEFT JOIN exif e ON a.id = e."assetId"
|
|
||||||
LEFT JOIN smart_info si ON a.id = si."assetId"
|
|
||||||
WHERE a."userId" = $1;
|
|
||||||
`,
|
|
||||||
[authUser.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
rows.forEach((row: { [x: string]: any }) => {
|
const rows = await this._assetRepository.getSearchPropertiesByUserId(authUser.id);
|
||||||
|
rows.forEach((row: SearchPropertiesDto) => {
|
||||||
// tags
|
// tags
|
||||||
row['tags']?.map((tag: string) => possibleSearchTerm.add(tag?.toLowerCase()));
|
row.tags?.map((tag: string) => possibleSearchTerm.add(tag?.toLowerCase()));
|
||||||
|
|
||||||
// objects
|
// objects
|
||||||
row['objects']?.map((object: string) => possibleSearchTerm.add(object?.toLowerCase()));
|
row.objects?.map((object: string) => possibleSearchTerm.add(object?.toLowerCase()));
|
||||||
|
|
||||||
// asset's tyoe
|
// asset's tyoe
|
||||||
possibleSearchTerm.add(row['type']?.toLowerCase());
|
possibleSearchTerm.add(row.assetType?.toLowerCase() || '');
|
||||||
|
|
||||||
// image orientation
|
// image orientation
|
||||||
possibleSearchTerm.add(row['orientation']?.toLowerCase());
|
possibleSearchTerm.add(row.orientation?.toLowerCase() || '');
|
||||||
|
|
||||||
// Lens model
|
// Lens model
|
||||||
possibleSearchTerm.add(row['lensModel']?.toLowerCase());
|
possibleSearchTerm.add(row.lensModel?.toLowerCase() || '');
|
||||||
|
|
||||||
// Make and model
|
// Make and model
|
||||||
possibleSearchTerm.add(row['make']?.toLowerCase());
|
possibleSearchTerm.add(row.make?.toLowerCase() || '');
|
||||||
possibleSearchTerm.add(row['model']?.toLowerCase());
|
possibleSearchTerm.add(row.model?.toLowerCase() || '');
|
||||||
|
|
||||||
// Location
|
// Location
|
||||||
possibleSearchTerm.add(row['city']?.toLowerCase());
|
possibleSearchTerm.add(row.city?.toLowerCase() || '');
|
||||||
possibleSearchTerm.add(row['state']?.toLowerCase());
|
possibleSearchTerm.add(row.state?.toLowerCase() || '');
|
||||||
possibleSearchTerm.add(row['country']?.toLowerCase());
|
possibleSearchTerm.add(row.country?.toLowerCase() || '');
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(possibleSearchTerm).filter((x) => x != null);
|
return Array.from(possibleSearchTerm).filter((x) => x != null && x != '');
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchAsset(authUser: AuthUserDto, searchAssetDto: SearchAssetDto): Promise<AssetResponseDto[]> {
|
async searchAsset(authUser: AuthUserDto, searchAssetDto: SearchAssetDto): Promise<AssetResponseDto[]> {
|
||||||
|
@ -459,33 +408,12 @@ export class AssetService {
|
||||||
return searchResults.map((asset) => mapAsset(asset));
|
return searchResults.map((asset) => mapAsset(asset));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCuratedLocation(authUser: AuthUserDto) {
|
async getCuratedLocation(authUser: AuthUserDto): Promise<CuratedLocationsResponseDto[]> {
|
||||||
return await this.assetRepository.query(
|
return this._assetRepository.getLocationsByUserId(authUser.id);
|
||||||
`
|
|
||||||
SELECT DISTINCT ON (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId"
|
|
||||||
FROM assets a
|
|
||||||
LEFT JOIN exif e ON a.id = e."assetId"
|
|
||||||
WHERE a."userId" = $1
|
|
||||||
AND e.city IS NOT NULL
|
|
||||||
AND a.type = 'IMAGE';
|
|
||||||
`,
|
|
||||||
[authUser.id],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCuratedObject(authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
async getCuratedObject(authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
||||||
const curatedObjects: CuratedObjectsResponseDto[] = await this.assetRepository.query(
|
return this._assetRepository.getDetectedObjectsByUserId(authUser.id);
|
||||||
`
|
|
||||||
SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId"
|
|
||||||
FROM assets a
|
|
||||||
LEFT JOIN smart_info si ON a.id = si."assetId"
|
|
||||||
WHERE a."userId" = $1
|
|
||||||
AND si.objects IS NOT NULL
|
|
||||||
`,
|
|
||||||
[authUser.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
return curatedObjects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkDuplicatedAsset(
|
async checkDuplicatedAsset(
|
||||||
|
@ -504,4 +432,16 @@ export class AssetService {
|
||||||
|
|
||||||
return new CheckDuplicateAssetResponseDto(isDuplicated, res?.id);
|
return new CheckDuplicateAssetResponseDto(isDuplicated, res?.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAssetCountByTimeGroup(
|
||||||
|
authUser: AuthUserDto,
|
||||||
|
getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto,
|
||||||
|
): Promise<AssetCountByTimeGroupResponseDto> {
|
||||||
|
const result = await this._assetRepository.getAssetCountByTimeGroup(
|
||||||
|
authUser.id,
|
||||||
|
getAssetCountByTimeGroupDto.timeGroup,
|
||||||
|
);
|
||||||
|
|
||||||
|
return mapAssetCountByTimeGroupResponse(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export enum TimeGroupEnum {
|
||||||
|
Day = 'day',
|
||||||
|
Month = 'month',
|
||||||
|
}
|
||||||
|
export class GetAssetCountByTimeGroupDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
enum: TimeGroupEnum,
|
||||||
|
enumName: 'TimeGroupEnum',
|
||||||
|
})
|
||||||
|
timeGroup!: TimeGroupEnum;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export class SearchPropertiesDto {
|
||||||
|
tags?: string[];
|
||||||
|
objects?: string[];
|
||||||
|
assetType?: string;
|
||||||
|
orientation?: string;
|
||||||
|
lensModel?: string;
|
||||||
|
make?: string;
|
||||||
|
model?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
country?: string;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class AssetCountByTimeGroupDto {
|
||||||
|
@ApiProperty({ type: 'string' })
|
||||||
|
timeGroup!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
count!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AssetCountByTimeGroupResponseDto {
|
||||||
|
groups!: AssetCountByTimeGroupDto[];
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
totalAssets!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapAssetCountByTimeGroupResponse(result: AssetCountByTimeGroupDto[]): AssetCountByTimeGroupResponseDto {
|
||||||
|
return {
|
||||||
|
groups: result,
|
||||||
|
totalAssets: result.map((group) => group.count).reduce((a, b) => a + b, 0),
|
||||||
|
};
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import { Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { UserEntity } from '@app/database/entities/user.entity';
|
import { UserEntity } from '@app/database/entities/user.entity';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
import cookieParser from 'cookie';
|
||||||
@WebSocketGateway({ cors: true })
|
@WebSocketGateway({ cors: true })
|
||||||
export class CommunicationGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
export class CommunicationGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -26,8 +26,24 @@ export class CommunicationGateway implements OnGatewayConnection, OnGatewayDisco
|
||||||
async handleConnection(client: Socket) {
|
async handleConnection(client: Socket) {
|
||||||
try {
|
try {
|
||||||
Logger.log(`New websocket connection: ${client.id}`, 'WebsocketConnectionEvent');
|
Logger.log(`New websocket connection: ${client.id}`, 'WebsocketConnectionEvent');
|
||||||
|
let accessToken = '';
|
||||||
|
|
||||||
const accessToken = client.handshake.headers.authorization?.split(' ')[1];
|
if (client.handshake.headers.cookie != undefined) {
|
||||||
|
const cookies = cookieParser.parse(client.handshake.headers.cookie);
|
||||||
|
if (cookies.immich_access_token) {
|
||||||
|
accessToken = cookies.immich_access_token;
|
||||||
|
} else {
|
||||||
|
client.emit('error', 'unauthorized');
|
||||||
|
client.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (client.handshake.headers.authorization != undefined) {
|
||||||
|
accessToken = client.handshake.headers.authorization.split(' ')[1];
|
||||||
|
} else {
|
||||||
|
client.emit('error', 'unauthorized');
|
||||||
|
client.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const res: JwtValidationResult = accessToken
|
const res: JwtValidationResult = accessToken
|
||||||
? await this.immichJwtService.validateToken(accessToken)
|
? await this.immichJwtService.validateToken(accessToken)
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -145,6 +145,44 @@ export interface AlbumResponseDto {
|
||||||
*/
|
*/
|
||||||
'assets': Array<AssetResponseDto>;
|
'assets': Array<AssetResponseDto>;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface AssetCountByTimeGroupDto
|
||||||
|
*/
|
||||||
|
export interface AssetCountByTimeGroupDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof AssetCountByTimeGroupDto
|
||||||
|
*/
|
||||||
|
'timeGroup': string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AssetCountByTimeGroupDto
|
||||||
|
*/
|
||||||
|
'count': number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface AssetCountByTimeGroupResponseDto
|
||||||
|
*/
|
||||||
|
export interface AssetCountByTimeGroupResponseDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AssetCountByTimeGroupResponseDto
|
||||||
|
*/
|
||||||
|
'totalAssets': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {Array<AssetCountByTimeGroupDto>}
|
||||||
|
* @memberof AssetCountByTimeGroupResponseDto
|
||||||
|
*/
|
||||||
|
'groups': Array<AssetCountByTimeGroupDto>;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -720,6 +758,19 @@ export interface ExifResponseDto {
|
||||||
*/
|
*/
|
||||||
'country'?: string | null;
|
'country'?: string | null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface GetAssetCountByTimeGroupDto
|
||||||
|
*/
|
||||||
|
export interface GetAssetCountByTimeGroupDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {TimeGroupEnum}
|
||||||
|
* @memberof GetAssetCountByTimeGroupDto
|
||||||
|
*/
|
||||||
|
'timeGroup': TimeGroupEnum;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -996,6 +1047,20 @@ export const ThumbnailFormat = {
|
||||||
export type ThumbnailFormat = typeof ThumbnailFormat[keyof typeof ThumbnailFormat];
|
export type ThumbnailFormat = typeof ThumbnailFormat[keyof typeof ThumbnailFormat];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const TimeGroupEnum = {
|
||||||
|
Day: 'day',
|
||||||
|
Month: 'month'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type TimeGroupEnum = typeof TimeGroupEnum[keyof typeof TimeGroupEnum];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -2072,13 +2137,52 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getAssetCountByTimeGroup: async (getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'getAssetCountByTimeGroupDto' is not null or undefined
|
||||||
|
assertParamExists('getAssetCountByTimeGroup', 'getAssetCountByTimeGroupDto', getAssetCountByTimeGroupDto)
|
||||||
|
const localVarPath = `/asset/count-by-date`;
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// authentication bearer required
|
||||||
|
// http bearer authentication required
|
||||||
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
localVarRequestOptions.data = serializeDataIfNeeded(getAssetCountByTimeGroupDto, localVarRequestOptions, configuration)
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getAssetSearchTerms: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
getAssetSearchTerms: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
const localVarPath = `/asset/searchTerm`;
|
const localVarPath = `/asset/search-terms`;
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
let baseOptions;
|
let baseOptions;
|
||||||
|
@ -2153,7 +2257,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getCuratedLocations: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
getCuratedLocations: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
const localVarPath = `/asset/allLocation`;
|
const localVarPath = `/asset/curated-locations`;
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
let baseOptions;
|
let baseOptions;
|
||||||
|
@ -2186,7 +2290,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getCuratedObjects: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
getCuratedObjects: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
const localVarPath = `/asset/allObjects`;
|
const localVarPath = `/asset/curated-objects`;
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
let baseOptions;
|
let baseOptions;
|
||||||
|
@ -2456,6 +2560,16 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetById(assetId, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetById(assetId, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async getAssetCountByTimeGroup(getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AssetCountByTimeGroupResponseDto>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByTimeGroup(getAssetCountByTimeGroupDto, options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
@ -2598,6 +2712,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
||||||
getAssetById(assetId: string, options?: any): AxiosPromise<AssetResponseDto> {
|
getAssetById(assetId: string, options?: any): AxiosPromise<AssetResponseDto> {
|
||||||
return localVarFp.getAssetById(assetId, options).then((request) => request(axios, basePath));
|
return localVarFp.getAssetById(assetId, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getAssetCountByTimeGroup(getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options?: any): AxiosPromise<AssetCountByTimeGroupResponseDto> {
|
||||||
|
return localVarFp.getAssetCountByTimeGroup(getAssetCountByTimeGroupDto, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
@ -2742,6 +2865,17 @@ export class AssetApi extends BaseAPI {
|
||||||
return AssetApiFp(this.configuration).getAssetById(assetId, options).then((request) => request(this.axios, this.basePath));
|
return AssetApiFp(this.configuration).getAssetById(assetId, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof AssetApi
|
||||||
|
*/
|
||||||
|
public getAssetCountByTimeGroup(getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options?: AxiosRequestConfig) {
|
||||||
|
return AssetApiFp(this.configuration).getAssetCountByTimeGroup(getAssetCountByTimeGroupDto, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Socket, io } from 'socket.io-client';
|
import { Socket, io } from 'socket.io-client';
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
let websocket: Socket;
|
let websocket: Socket;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { serverApi } from '@api';
|
import { serverApi, TimeGroupEnum } from '@api';
|
||||||
import * as cookieParser from 'cookie';
|
import * as cookieParser from 'cookie';
|
||||||
|
|
||||||
import type { LayoutServerLoad } from './$types';
|
import type { LayoutServerLoad } from './$types';
|
||||||
|
@ -21,6 +21,9 @@ export const load: LayoutServerLoad = async ({ request }) => {
|
||||||
user: userInfo
|
user: userInfo
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('[ERROR] layout.server.ts [LayoutServerLoad]: ', e);
|
console.error('[ERROR] layout.server.ts [LayoutServerLoad]: ', e);
|
||||||
|
return {
|
||||||
|
user: undefined
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,11 +20,13 @@
|
||||||
import Close from 'svelte-material-icons/Close.svelte';
|
import Close from 'svelte-material-icons/Close.svelte';
|
||||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import {
|
import {
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType
|
NotificationType
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
|
import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
@ -193,6 +195,18 @@
|
||||||
console.error('Error deleteSelectedAssetHandler', e);
|
console.error('Error deleteSelectedAssetHandler', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
openWebsocketConnection();
|
||||||
|
|
||||||
|
const { data: assets } = await api.assetApi.getAllAssets();
|
||||||
|
|
||||||
|
setAssetInfo(assets);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
closeWebsocketConnection();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
Loading…
Add table
Reference in a new issue