You've already forked immich
							
							
				mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 00:18:28 +02:00 
			
		
		
		
	feat(server,web): system config for admin (#959)
* feat: add admin config module for user configured config, uses it for ffmpeg * feat: add api endpoint to retrieve admin config settings and values * feat: add settings panel to admin page on web (wip) * feat: add api endpoint to update the admin config * chore: re-generate openapi spec after rebase * refactor: move from admin config to system config naming * chore: move away from UseGuards to new @Authenticated decorator * style: dark mode styling for lists and fix conflicting colors * wip: 2 column design, no edit button * refactor: system config * chore: generate open api * chore: rm broken test * chore: cleanup types * refactor: config module names Co-authored-by: Zack Pollard <zackpollard@ymail.com> Co-authored-by: Zack Pollard <zack.pollard@moonpig.com>
This commit is contained in:
		| @@ -59,6 +59,10 @@ doc/ServerStatsResponseDto.md | ||||
| doc/ServerVersionReponseDto.md | ||||
| doc/SignUpDto.md | ||||
| doc/SmartInfoResponseDto.md | ||||
| doc/SystemConfigApi.md | ||||
| doc/SystemConfigKey.md | ||||
| doc/SystemConfigResponseDto.md | ||||
| doc/SystemConfigResponseItem.md | ||||
| doc/ThumbnailFormat.md | ||||
| doc/TimeGroupEnum.md | ||||
| doc/UpdateAlbumDto.md | ||||
| @@ -79,6 +83,7 @@ lib/api/device_info_api.dart | ||||
| lib/api/job_api.dart | ||||
| lib/api/o_auth_api.dart | ||||
| lib/api/server_info_api.dart | ||||
| lib/api/system_config_api.dart | ||||
| lib/api/user_api.dart | ||||
| lib/api_client.dart | ||||
| lib/api_exception.dart | ||||
| @@ -138,6 +143,9 @@ lib/model/server_stats_response_dto.dart | ||||
| lib/model/server_version_reponse_dto.dart | ||||
| lib/model/sign_up_dto.dart | ||||
| lib/model/smart_info_response_dto.dart | ||||
| lib/model/system_config_key.dart | ||||
| lib/model/system_config_response_dto.dart | ||||
| lib/model/system_config_response_item.dart | ||||
| lib/model/thumbnail_format.dart | ||||
| lib/model/time_group_enum.dart | ||||
| lib/model/update_album_dto.dart | ||||
|   | ||||
| @@ -109,6 +109,8 @@ Class | Method | HTTP request | Description | ||||
| *ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version |  | ||||
| *ServerInfoApi* | [**getStats**](doc//ServerInfoApi.md#getstats) | **GET** /server-info/stats |  | ||||
| *ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |  | ||||
| *SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config |  | ||||
| *SystemConfigApi* | [**updateConfig**](doc//SystemConfigApi.md#updateconfig) | **PUT** /system-config |  | ||||
| *UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /user/profile-image |  | ||||
| *UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user |  | ||||
| *UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /user/{userId} |  | ||||
| @@ -173,6 +175,9 @@ Class | Method | HTTP request | Description | ||||
|  - [ServerVersionReponseDto](doc//ServerVersionReponseDto.md) | ||||
|  - [SignUpDto](doc//SignUpDto.md) | ||||
|  - [SmartInfoResponseDto](doc//SmartInfoResponseDto.md) | ||||
|  - [SystemConfigKey](doc//SystemConfigKey.md) | ||||
|  - [SystemConfigResponseDto](doc//SystemConfigResponseDto.md) | ||||
|  - [SystemConfigResponseItem](doc//SystemConfigResponseItem.md) | ||||
|  - [ThumbnailFormat](doc//ThumbnailFormat.md) | ||||
|  - [TimeGroupEnum](doc//TimeGroupEnum.md) | ||||
|  - [UpdateAlbumDto](doc//UpdateAlbumDto.md) | ||||
|   | ||||
							
								
								
									
										15
									
								
								mobile/openapi/doc/AdminConfigResponseDto.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								mobile/openapi/doc/AdminConfigResponseDto.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| # openapi.model.AdminConfigResponseDto | ||||
|  | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
|  | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **config** | [**Object**](.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) | ||||
|  | ||||
|  | ||||
							
								
								
									
										105
									
								
								mobile/openapi/doc/ConfigApi.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								mobile/openapi/doc/ConfigApi.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| # openapi.api.ConfigApi | ||||
|  | ||||
| ## Load the API package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
|  | ||||
| All URIs are relative to */api* | ||||
|  | ||||
| Method | HTTP request | Description | ||||
| ------------- | ------------- | ------------- | ||||
| [**getSystemConfig**](ConfigApi.md#getsystemconfig) | **GET** /config/system |  | ||||
| [**updateSystemConfig**](ConfigApi.md#updatesystemconfig) | **PUT** /config/system |  | ||||
|  | ||||
|  | ||||
| # **getSystemConfig** | ||||
| > SystemConfigResponseDto getSystemConfig() | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 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 = ConfigApi(); | ||||
|  | ||||
| try { | ||||
|     final result = api_instance.getSystemConfig(); | ||||
|     print(result); | ||||
| } catch (e) { | ||||
|     print('Exception when calling ConfigApi->getSystemConfig: $e\n'); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Parameters | ||||
| This endpoint does not need any parameter. | ||||
|  | ||||
| ### Return type | ||||
|  | ||||
| [**SystemConfigResponseDto**](SystemConfigResponseDto.md) | ||||
|  | ||||
| ### Authorization | ||||
|  | ||||
| [bearer](../README.md#bearer) | ||||
|  | ||||
| ### HTTP request headers | ||||
|  | ||||
|  - **Content-Type**: Not defined | ||||
|  - **Accept**: application/json | ||||
|  | ||||
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||
|  | ||||
| # **updateSystemConfig** | ||||
| > SystemConfigResponseDto updateSystemConfig(body) | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 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 = ConfigApi(); | ||||
| final body = Object(); // Object |  | ||||
|  | ||||
| try { | ||||
|     final result = api_instance.updateSystemConfig(body); | ||||
|     print(result); | ||||
| } catch (e) { | ||||
|     print('Exception when calling ConfigApi->updateSystemConfig: $e\n'); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Parameters | ||||
|  | ||||
| Name | Type | Description  | Notes | ||||
| ------------- | ------------- | ------------- | ------------- | ||||
|  **body** | **Object**|  |  | ||||
|  | ||||
| ### Return type | ||||
|  | ||||
| [**SystemConfigResponseDto**](SystemConfigResponseDto.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) | ||||
|  | ||||
							
								
								
									
										105
									
								
								mobile/openapi/doc/SystemConfigApi.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								mobile/openapi/doc/SystemConfigApi.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| # openapi.api.SystemConfigApi | ||||
|  | ||||
| ## Load the API package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
|  | ||||
| All URIs are relative to */api* | ||||
|  | ||||
| Method | HTTP request | Description | ||||
| ------------- | ------------- | ------------- | ||||
| [**getConfig**](SystemConfigApi.md#getconfig) | **GET** /system-config |  | ||||
| [**updateConfig**](SystemConfigApi.md#updateconfig) | **PUT** /system-config |  | ||||
|  | ||||
|  | ||||
| # **getConfig** | ||||
| > SystemConfigResponseDto getConfig() | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 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 = SystemConfigApi(); | ||||
|  | ||||
| try { | ||||
|     final result = api_instance.getConfig(); | ||||
|     print(result); | ||||
| } catch (e) { | ||||
|     print('Exception when calling SystemConfigApi->getConfig: $e\n'); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Parameters | ||||
| This endpoint does not need any parameter. | ||||
|  | ||||
| ### Return type | ||||
|  | ||||
| [**SystemConfigResponseDto**](SystemConfigResponseDto.md) | ||||
|  | ||||
| ### Authorization | ||||
|  | ||||
| [bearer](../README.md#bearer) | ||||
|  | ||||
| ### HTTP request headers | ||||
|  | ||||
|  - **Content-Type**: Not defined | ||||
|  - **Accept**: application/json | ||||
|  | ||||
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||
|  | ||||
| # **updateConfig** | ||||
| > SystemConfigResponseDto updateConfig(body) | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 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 = SystemConfigApi(); | ||||
| final body = Object(); // Object |  | ||||
|  | ||||
| try { | ||||
|     final result = api_instance.updateConfig(body); | ||||
|     print(result); | ||||
| } catch (e) { | ||||
|     print('Exception when calling SystemConfigApi->updateConfig: $e\n'); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Parameters | ||||
|  | ||||
| Name | Type | Description  | Notes | ||||
| ------------- | ------------- | ------------- | ------------- | ||||
|  **body** | **Object**|  |  | ||||
|  | ||||
| ### Return type | ||||
|  | ||||
| [**SystemConfigResponseDto**](SystemConfigResponseDto.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) | ||||
|  | ||||
							
								
								
									
										16
									
								
								mobile/openapi/doc/SystemConfigEntity.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								mobile/openapi/doc/SystemConfigEntity.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| # openapi.model.SystemConfigEntity | ||||
|  | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
|  | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **key** | **String** |  |  | ||||
| **value** | [**Object**](.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/SystemConfigKey.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								mobile/openapi/doc/SystemConfigKey.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| # openapi.model.SystemConfigKey | ||||
|  | ||||
| ## 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) | ||||
|  | ||||
|  | ||||
							
								
								
									
										15
									
								
								mobile/openapi/doc/SystemConfigResponseDto.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								mobile/openapi/doc/SystemConfigResponseDto.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| # openapi.model.SystemConfigResponseDto | ||||
|  | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
|  | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **config** | [**List<SystemConfigResponseItem>**](SystemConfigResponseItem.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) | ||||
|  | ||||
|  | ||||
							
								
								
									
										18
									
								
								mobile/openapi/doc/SystemConfigResponseItem.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								mobile/openapi/doc/SystemConfigResponseItem.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # openapi.model.SystemConfigResponseItem | ||||
|  | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
|  | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **name** | **String** |  |  | ||||
| **key** | [**SystemConfigKey**](SystemConfigKey.md) |  |  | ||||
| **value** | **String** |  |  | ||||
| **defaultValue** | **String** |  |  | ||||
|  | ||||
| [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||
|  | ||||
|  | ||||
| @@ -34,6 +34,7 @@ part 'api/device_info_api.dart'; | ||||
| part 'api/job_api.dart'; | ||||
| part 'api/o_auth_api.dart'; | ||||
| part 'api/server_info_api.dart'; | ||||
| part 'api/system_config_api.dart'; | ||||
| part 'api/user_api.dart'; | ||||
|  | ||||
| part 'model/add_assets_dto.dart'; | ||||
| @@ -86,6 +87,9 @@ part 'model/server_stats_response_dto.dart'; | ||||
| part 'model/server_version_reponse_dto.dart'; | ||||
| part 'model/sign_up_dto.dart'; | ||||
| part 'model/smart_info_response_dto.dart'; | ||||
| part 'model/system_config_key.dart'; | ||||
| part 'model/system_config_response_dto.dart'; | ||||
| part 'model/system_config_response_item.dart'; | ||||
| part 'model/thumbnail_format.dart'; | ||||
| part 'model/time_group_enum.dart'; | ||||
| part 'model/update_album_dto.dart'; | ||||
|   | ||||
							
								
								
									
										106
									
								
								mobile/openapi/lib/api/config_api.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								mobile/openapi/lib/api/config_api.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| // | ||||
| // 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 ConfigApi { | ||||
|   ConfigApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; | ||||
|  | ||||
|   final ApiClient apiClient; | ||||
|  | ||||
|   /// Performs an HTTP 'GET /config/system' operation and returns the [Response]. | ||||
|   Future<Response> getSystemConfigWithHttpInfo() async { | ||||
|     // ignore: prefer_const_declarations | ||||
|     final path = r'/config/system'; | ||||
|  | ||||
|     // ignore: prefer_final_locals | ||||
|     Object? postBody; | ||||
|  | ||||
|     final queryParams = <QueryParam>[]; | ||||
|     final headerParams = <String, String>{}; | ||||
|     final formParams = <String, String>{}; | ||||
|  | ||||
|     const contentTypes = <String>[]; | ||||
|  | ||||
|  | ||||
|     return apiClient.invokeAPI( | ||||
|       path, | ||||
|       'GET', | ||||
|       queryParams, | ||||
|       postBody, | ||||
|       headerParams, | ||||
|       formParams, | ||||
|       contentTypes.isEmpty ? null : contentTypes.first, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<SystemConfigResponseDto?> getSystemConfig() async { | ||||
|     final response = await getSystemConfigWithHttpInfo(); | ||||
|     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), 'SystemConfigResponseDto',) as SystemConfigResponseDto; | ||||
|      | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   /// Performs an HTTP 'PUT /config/system' operation and returns the [Response]. | ||||
|   /// Parameters: | ||||
|   /// | ||||
|   /// * [Object] body (required): | ||||
|   Future<Response> updateSystemConfigWithHttpInfo(Object body,) async { | ||||
|     // ignore: prefer_const_declarations | ||||
|     final path = r'/config/system'; | ||||
|  | ||||
|     // ignore: prefer_final_locals | ||||
|     Object? postBody = body; | ||||
|  | ||||
|     final queryParams = <QueryParam>[]; | ||||
|     final headerParams = <String, String>{}; | ||||
|     final formParams = <String, String>{}; | ||||
|  | ||||
|     const contentTypes = <String>['application/json']; | ||||
|  | ||||
|  | ||||
|     return apiClient.invokeAPI( | ||||
|       path, | ||||
|       'PUT', | ||||
|       queryParams, | ||||
|       postBody, | ||||
|       headerParams, | ||||
|       formParams, | ||||
|       contentTypes.isEmpty ? null : contentTypes.first, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   /// Parameters: | ||||
|   /// | ||||
|   /// * [Object] body (required): | ||||
|   Future<SystemConfigResponseDto?> updateSystemConfig(Object body,) async { | ||||
|     final response = await updateSystemConfigWithHttpInfo(body,); | ||||
|     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), 'SystemConfigResponseDto',) as SystemConfigResponseDto; | ||||
|      | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										106
									
								
								mobile/openapi/lib/api/system_config_api.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								mobile/openapi/lib/api/system_config_api.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| // | ||||
| // 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 SystemConfigApi { | ||||
|   SystemConfigApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; | ||||
|  | ||||
|   final ApiClient apiClient; | ||||
|  | ||||
|   /// Performs an HTTP 'GET /system-config' operation and returns the [Response]. | ||||
|   Future<Response> getConfigWithHttpInfo() async { | ||||
|     // ignore: prefer_const_declarations | ||||
|     final path = r'/system-config'; | ||||
|  | ||||
|     // ignore: prefer_final_locals | ||||
|     Object? postBody; | ||||
|  | ||||
|     final queryParams = <QueryParam>[]; | ||||
|     final headerParams = <String, String>{}; | ||||
|     final formParams = <String, String>{}; | ||||
|  | ||||
|     const contentTypes = <String>[]; | ||||
|  | ||||
|  | ||||
|     return apiClient.invokeAPI( | ||||
|       path, | ||||
|       'GET', | ||||
|       queryParams, | ||||
|       postBody, | ||||
|       headerParams, | ||||
|       formParams, | ||||
|       contentTypes.isEmpty ? null : contentTypes.first, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<SystemConfigResponseDto?> getConfig() async { | ||||
|     final response = await getConfigWithHttpInfo(); | ||||
|     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), 'SystemConfigResponseDto',) as SystemConfigResponseDto; | ||||
|      | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   /// Performs an HTTP 'PUT /system-config' operation and returns the [Response]. | ||||
|   /// Parameters: | ||||
|   /// | ||||
|   /// * [Object] body (required): | ||||
|   Future<Response> updateConfigWithHttpInfo(Object body,) async { | ||||
|     // ignore: prefer_const_declarations | ||||
|     final path = r'/system-config'; | ||||
|  | ||||
|     // ignore: prefer_final_locals | ||||
|     Object? postBody = body; | ||||
|  | ||||
|     final queryParams = <QueryParam>[]; | ||||
|     final headerParams = <String, String>{}; | ||||
|     final formParams = <String, String>{}; | ||||
|  | ||||
|     const contentTypes = <String>['application/json']; | ||||
|  | ||||
|  | ||||
|     return apiClient.invokeAPI( | ||||
|       path, | ||||
|       'PUT', | ||||
|       queryParams, | ||||
|       postBody, | ||||
|       headerParams, | ||||
|       formParams, | ||||
|       contentTypes.isEmpty ? null : contentTypes.first, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   /// Parameters: | ||||
|   /// | ||||
|   /// * [Object] body (required): | ||||
|   Future<SystemConfigResponseDto?> updateConfig(Object body,) async { | ||||
|     final response = await updateConfigWithHttpInfo(body,); | ||||
|     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), 'SystemConfigResponseDto',) as SystemConfigResponseDto; | ||||
|      | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
| @@ -292,6 +292,12 @@ class ApiClient { | ||||
|           return SignUpDto.fromJson(value); | ||||
|         case 'SmartInfoResponseDto': | ||||
|           return SmartInfoResponseDto.fromJson(value); | ||||
|         case 'SystemConfigKey': | ||||
|           return SystemConfigKeyTypeTransformer().decode(value); | ||||
|         case 'SystemConfigResponseDto': | ||||
|           return SystemConfigResponseDto.fromJson(value); | ||||
|         case 'SystemConfigResponseItem': | ||||
|           return SystemConfigResponseItem.fromJson(value); | ||||
|         case 'ThumbnailFormat': | ||||
|           return ThumbnailFormatTypeTransformer().decode(value); | ||||
|         case 'TimeGroupEnum': | ||||
|   | ||||
| @@ -70,6 +70,9 @@ String parameterToString(dynamic value) { | ||||
|   if (value is JobId) { | ||||
|     return JobIdTypeTransformer().encode(value).toString(); | ||||
|   } | ||||
|   if (value is SystemConfigKey) { | ||||
|     return SystemConfigKeyTypeTransformer().encode(value).toString(); | ||||
|   } | ||||
|   if (value is ThumbnailFormat) { | ||||
|     return ThumbnailFormatTypeTransformer().encode(value).toString(); | ||||
|   } | ||||
|   | ||||
							
								
								
									
										111
									
								
								mobile/openapi/lib/model/admin_config_response_dto.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								mobile/openapi/lib/model/admin_config_response_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 AdminConfigResponseDto { | ||||
|   /// Returns a new [AdminConfigResponseDto] instance. | ||||
|   AdminConfigResponseDto({ | ||||
|     required this.config, | ||||
|   }); | ||||
|  | ||||
|   Object config; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is AdminConfigResponseDto && | ||||
|      other.config == config; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (config.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => 'AdminConfigResponseDto[config=$config]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|       _json[r'config'] = config; | ||||
|     return _json; | ||||
|   } | ||||
|  | ||||
|   /// Returns a new [AdminConfigResponseDto] instance and imports its values from | ||||
|   /// [value] if it's a [Map], null otherwise. | ||||
|   // ignore: prefer_constructors_over_static_methods | ||||
|   static AdminConfigResponseDto? 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 "AdminConfigResponseDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "AdminConfigResponseDto[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
|  | ||||
|       return AdminConfigResponseDto( | ||||
|         config: mapValueOfType<Object>(json, r'config')!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<AdminConfigResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <AdminConfigResponseDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = AdminConfigResponseDto.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
|  | ||||
|   static Map<String, AdminConfigResponseDto> mapFromJson(dynamic json) { | ||||
|     final map = <String, AdminConfigResponseDto>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = AdminConfigResponseDto.fromJson(entry.value); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
|  | ||||
|   // maps a json object with a list of AdminConfigResponseDto-objects as value to a dart map | ||||
|   static Map<String, List<AdminConfigResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<AdminConfigResponseDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = AdminConfigResponseDto.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>{ | ||||
|     'config', | ||||
|   }; | ||||
| } | ||||
|  | ||||
							
								
								
									
										202
									
								
								mobile/openapi/lib/model/system_config_entity.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								mobile/openapi/lib/model/system_config_entity.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| // | ||||
| // 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 SystemConfigEntity { | ||||
|   /// Returns a new [SystemConfigEntity] instance. | ||||
|   SystemConfigEntity({ | ||||
|     required this.key, | ||||
|     required this.value, | ||||
|   }); | ||||
|  | ||||
|   SystemConfigEntityKeyEnum key; | ||||
|  | ||||
|   Object value; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is SystemConfigEntity && | ||||
|      other.key == key && | ||||
|      other.value == value; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (key.hashCode) + | ||||
|     (value.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => 'SystemConfigEntity[key=$key, value=$value]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|       _json[r'key'] = key; | ||||
|       _json[r'value'] = value; | ||||
|     return _json; | ||||
|   } | ||||
|  | ||||
|   /// Returns a new [SystemConfigEntity] instance and imports its values from | ||||
|   /// [value] if it's a [Map], null otherwise. | ||||
|   // ignore: prefer_constructors_over_static_methods | ||||
|   static SystemConfigEntity? 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 "SystemConfigEntity[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "SystemConfigEntity[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
|  | ||||
|       return SystemConfigEntity( | ||||
|         key: SystemConfigEntityKeyEnum.fromJson(json[r'key'])!, | ||||
|         value: mapValueOfType<Object>(json, r'value')!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<SystemConfigEntity>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <SystemConfigEntity>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = SystemConfigEntity.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
|  | ||||
|   static Map<String, SystemConfigEntity> mapFromJson(dynamic json) { | ||||
|     final map = <String, SystemConfigEntity>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = SystemConfigEntity.fromJson(entry.value); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
|  | ||||
|   // maps a json object with a list of SystemConfigEntity-objects as value to a dart map | ||||
|   static Map<String, List<SystemConfigEntity>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<SystemConfigEntity>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = SystemConfigEntity.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>{ | ||||
|     'key', | ||||
|     'value', | ||||
|   }; | ||||
| } | ||||
|  | ||||
|  | ||||
| class SystemConfigEntityKeyEnum { | ||||
|   /// Instantiate a new enum with the provided [value]. | ||||
|   const SystemConfigEntityKeyEnum._(this.value); | ||||
|  | ||||
|   /// The underlying value of this enum member. | ||||
|   final String value; | ||||
|  | ||||
|   @override | ||||
|   String toString() => value; | ||||
|  | ||||
|   String toJson() => value; | ||||
|  | ||||
|   static const crf = SystemConfigEntityKeyEnum._(r'ffmpeg_crf'); | ||||
|   static const preset = SystemConfigEntityKeyEnum._(r'ffmpeg_preset'); | ||||
|   static const targetVideoCodec = SystemConfigEntityKeyEnum._(r'ffmpeg_target_video_codec'); | ||||
|   static const targetAudioCodec = SystemConfigEntityKeyEnum._(r'ffmpeg_target_audio_codec'); | ||||
|   static const targetScaling = SystemConfigEntityKeyEnum._(r'ffmpeg_target_scaling'); | ||||
|  | ||||
|   /// List of all possible values in this [enum][SystemConfigEntityKeyEnum]. | ||||
|   static const values = <SystemConfigEntityKeyEnum>[ | ||||
|     crf, | ||||
|     preset, | ||||
|     targetVideoCodec, | ||||
|     targetAudioCodec, | ||||
|     targetScaling, | ||||
|   ]; | ||||
|  | ||||
|   static SystemConfigEntityKeyEnum? fromJson(dynamic value) => SystemConfigEntityKeyEnumTypeTransformer().decode(value); | ||||
|  | ||||
|   static List<SystemConfigEntityKeyEnum>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <SystemConfigEntityKeyEnum>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = SystemConfigEntityKeyEnum.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// Transformation class that can [encode] an instance of [SystemConfigEntityKeyEnum] to String, | ||||
| /// and [decode] dynamic data back to [SystemConfigEntityKeyEnum]. | ||||
| class SystemConfigEntityKeyEnumTypeTransformer { | ||||
|   factory SystemConfigEntityKeyEnumTypeTransformer() => _instance ??= const SystemConfigEntityKeyEnumTypeTransformer._(); | ||||
|  | ||||
|   const SystemConfigEntityKeyEnumTypeTransformer._(); | ||||
|  | ||||
|   String encode(SystemConfigEntityKeyEnum data) => data.value; | ||||
|  | ||||
|   /// Decodes a [dynamic value][data] to a SystemConfigEntityKeyEnum. | ||||
|   /// | ||||
|   /// 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. | ||||
|   SystemConfigEntityKeyEnum? decode(dynamic data, {bool allowNull = true}) { | ||||
|     if (data != null) { | ||||
|       switch (data.toString()) { | ||||
|         case r'ffmpeg_crf': return SystemConfigEntityKeyEnum.crf; | ||||
|         case r'ffmpeg_preset': return SystemConfigEntityKeyEnum.preset; | ||||
|         case r'ffmpeg_target_video_codec': return SystemConfigEntityKeyEnum.targetVideoCodec; | ||||
|         case r'ffmpeg_target_audio_codec': return SystemConfigEntityKeyEnum.targetAudioCodec; | ||||
|         case r'ffmpeg_target_scaling': return SystemConfigEntityKeyEnum.targetScaling; | ||||
|         default: | ||||
|           if (!allowNull) { | ||||
|             throw ArgumentError('Unknown enum value to decode: $data'); | ||||
|           } | ||||
|       } | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   /// Singleton [SystemConfigEntityKeyEnumTypeTransformer] instance. | ||||
|   static SystemConfigEntityKeyEnumTypeTransformer? _instance; | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										94
									
								
								mobile/openapi/lib/model/system_config_key.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								mobile/openapi/lib/model/system_config_key.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| // | ||||
| // 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 SystemConfigKey { | ||||
|   /// Instantiate a new enum with the provided [value]. | ||||
|   const SystemConfigKey._(this.value); | ||||
|  | ||||
|   /// The underlying value of this enum member. | ||||
|   final String value; | ||||
|  | ||||
|   @override | ||||
|   String toString() => value; | ||||
|  | ||||
|   String toJson() => value; | ||||
|  | ||||
|   static const crf = SystemConfigKey._(r'ffmpeg_crf'); | ||||
|   static const preset = SystemConfigKey._(r'ffmpeg_preset'); | ||||
|   static const targetVideoCodec = SystemConfigKey._(r'ffmpeg_target_video_codec'); | ||||
|   static const targetAudioCodec = SystemConfigKey._(r'ffmpeg_target_audio_codec'); | ||||
|   static const targetScaling = SystemConfigKey._(r'ffmpeg_target_scaling'); | ||||
|  | ||||
|   /// List of all possible values in this [enum][SystemConfigKey]. | ||||
|   static const values = <SystemConfigKey>[ | ||||
|     crf, | ||||
|     preset, | ||||
|     targetVideoCodec, | ||||
|     targetAudioCodec, | ||||
|     targetScaling, | ||||
|   ]; | ||||
|  | ||||
|   static SystemConfigKey? fromJson(dynamic value) => SystemConfigKeyTypeTransformer().decode(value); | ||||
|  | ||||
|   static List<SystemConfigKey>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <SystemConfigKey>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = SystemConfigKey.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// Transformation class that can [encode] an instance of [SystemConfigKey] to String, | ||||
| /// and [decode] dynamic data back to [SystemConfigKey]. | ||||
| class SystemConfigKeyTypeTransformer { | ||||
|   factory SystemConfigKeyTypeTransformer() => _instance ??= const SystemConfigKeyTypeTransformer._(); | ||||
|  | ||||
|   const SystemConfigKeyTypeTransformer._(); | ||||
|  | ||||
|   String encode(SystemConfigKey data) => data.value; | ||||
|  | ||||
|   /// Decodes a [dynamic value][data] to a SystemConfigKey. | ||||
|   /// | ||||
|   /// 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. | ||||
|   SystemConfigKey? decode(dynamic data, {bool allowNull = true}) { | ||||
|     if (data != null) { | ||||
|       switch (data.toString()) { | ||||
|         case r'ffmpeg_crf': return SystemConfigKey.crf; | ||||
|         case r'ffmpeg_preset': return SystemConfigKey.preset; | ||||
|         case r'ffmpeg_target_video_codec': return SystemConfigKey.targetVideoCodec; | ||||
|         case r'ffmpeg_target_audio_codec': return SystemConfigKey.targetAudioCodec; | ||||
|         case r'ffmpeg_target_scaling': return SystemConfigKey.targetScaling; | ||||
|         default: | ||||
|           if (!allowNull) { | ||||
|             throw ArgumentError('Unknown enum value to decode: $data'); | ||||
|           } | ||||
|       } | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   /// Singleton [SystemConfigKeyTypeTransformer] instance. | ||||
|   static SystemConfigKeyTypeTransformer? _instance; | ||||
| } | ||||
|  | ||||
							
								
								
									
										111
									
								
								mobile/openapi/lib/model/system_config_response_dto.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								mobile/openapi/lib/model/system_config_response_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 SystemConfigResponseDto { | ||||
|   /// Returns a new [SystemConfigResponseDto] instance. | ||||
|   SystemConfigResponseDto({ | ||||
|     this.config = const [], | ||||
|   }); | ||||
|  | ||||
|   List<SystemConfigResponseItem> config; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is SystemConfigResponseDto && | ||||
|      other.config == config; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (config.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => 'SystemConfigResponseDto[config=$config]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|       _json[r'config'] = config; | ||||
|     return _json; | ||||
|   } | ||||
|  | ||||
|   /// Returns a new [SystemConfigResponseDto] instance and imports its values from | ||||
|   /// [value] if it's a [Map], null otherwise. | ||||
|   // ignore: prefer_constructors_over_static_methods | ||||
|   static SystemConfigResponseDto? 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 "SystemConfigResponseDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "SystemConfigResponseDto[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
|  | ||||
|       return SystemConfigResponseDto( | ||||
|         config: SystemConfigResponseItem.listFromJson(json[r'config'])!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<SystemConfigResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <SystemConfigResponseDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = SystemConfigResponseDto.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
|  | ||||
|   static Map<String, SystemConfigResponseDto> mapFromJson(dynamic json) { | ||||
|     final map = <String, SystemConfigResponseDto>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = SystemConfigResponseDto.fromJson(entry.value); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
|  | ||||
|   // maps a json object with a list of SystemConfigResponseDto-objects as value to a dart map | ||||
|   static Map<String, List<SystemConfigResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<SystemConfigResponseDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = SystemConfigResponseDto.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>{ | ||||
|     'config', | ||||
|   }; | ||||
| } | ||||
|  | ||||
							
								
								
									
										135
									
								
								mobile/openapi/lib/model/system_config_response_item.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								mobile/openapi/lib/model/system_config_response_item.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| // | ||||
| // 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 SystemConfigResponseItem { | ||||
|   /// Returns a new [SystemConfigResponseItem] instance. | ||||
|   SystemConfigResponseItem({ | ||||
|     required this.name, | ||||
|     required this.key, | ||||
|     required this.value, | ||||
|     required this.defaultValue, | ||||
|   }); | ||||
|  | ||||
|   String name; | ||||
|  | ||||
|   SystemConfigKey key; | ||||
|  | ||||
|   String value; | ||||
|  | ||||
|   String defaultValue; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is SystemConfigResponseItem && | ||||
|      other.name == name && | ||||
|      other.key == key && | ||||
|      other.value == value && | ||||
|      other.defaultValue == defaultValue; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (name.hashCode) + | ||||
|     (key.hashCode) + | ||||
|     (value.hashCode) + | ||||
|     (defaultValue.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => 'SystemConfigResponseItem[name=$name, key=$key, value=$value, defaultValue=$defaultValue]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|       _json[r'name'] = name; | ||||
|       _json[r'key'] = key; | ||||
|       _json[r'value'] = value; | ||||
|       _json[r'defaultValue'] = defaultValue; | ||||
|     return _json; | ||||
|   } | ||||
|  | ||||
|   /// Returns a new [SystemConfigResponseItem] instance and imports its values from | ||||
|   /// [value] if it's a [Map], null otherwise. | ||||
|   // ignore: prefer_constructors_over_static_methods | ||||
|   static SystemConfigResponseItem? 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 "SystemConfigResponseItem[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "SystemConfigResponseItem[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
|  | ||||
|       return SystemConfigResponseItem( | ||||
|         name: mapValueOfType<String>(json, r'name')!, | ||||
|         key: SystemConfigKey.fromJson(json[r'key'])!, | ||||
|         value: mapValueOfType<String>(json, r'value')!, | ||||
|         defaultValue: mapValueOfType<String>(json, r'defaultValue')!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<SystemConfigResponseItem>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <SystemConfigResponseItem>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = SystemConfigResponseItem.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
|  | ||||
|   static Map<String, SystemConfigResponseItem> mapFromJson(dynamic json) { | ||||
|     final map = <String, SystemConfigResponseItem>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = SystemConfigResponseItem.fromJson(entry.value); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
|  | ||||
|   // maps a json object with a list of SystemConfigResponseItem-objects as value to a dart map | ||||
|   static Map<String, List<SystemConfigResponseItem>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<SystemConfigResponseItem>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = SystemConfigResponseItem.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>{ | ||||
|     'name', | ||||
|     'key', | ||||
|     'value', | ||||
|     'defaultValue', | ||||
|   }; | ||||
| } | ||||
|  | ||||
							
								
								
									
										26
									
								
								mobile/openapi/test/admin_config_api_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								mobile/openapi/test/admin_config_api_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // | ||||
| // 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 AdminConfigApi | ||||
| void main() { | ||||
|   // final instance = AdminConfigApi(); | ||||
|  | ||||
|   group('tests for AdminConfigApi', () { | ||||
|     //Future<AdminConfigResponseDto> getAdminConfig() async | ||||
|     test('test getAdminConfig', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										27
									
								
								mobile/openapi/test/admin_config_response_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								mobile/openapi/test/admin_config_response_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -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 AdminConfigResponseDto | ||||
| void main() { | ||||
|   // final instance = AdminConfigResponseDto(); | ||||
|  | ||||
|   group('test AdminConfigResponseDto', () { | ||||
|     // Object config | ||||
|     test('to test the property `config`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|  | ||||
|   }); | ||||
|  | ||||
| } | ||||
							
								
								
									
										31
									
								
								mobile/openapi/test/config_api_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								mobile/openapi/test/config_api_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // | ||||
| // 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 ConfigApi | ||||
| void main() { | ||||
|   // final instance = ConfigApi(); | ||||
|  | ||||
|   group('tests for ConfigApi', () { | ||||
|     //Future<SystemConfigResponseDto> getSystemConfig() async | ||||
|     test('test getSystemConfig', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|     //Future<SystemConfigResponseDto> putSystemConfig(Object body) async | ||||
|     test('test putSystemConfig', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										31
									
								
								mobile/openapi/test/system_config_api_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								mobile/openapi/test/system_config_api_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // | ||||
| // 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 SystemConfigApi | ||||
| void main() { | ||||
|   // final instance = SystemConfigApi(); | ||||
|  | ||||
|   group('tests for SystemConfigApi', () { | ||||
|     //Future<SystemConfigResponseDto> getConfig() async | ||||
|     test('test getConfig', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|     //Future<SystemConfigResponseDto> updateConfig(Object body) async | ||||
|     test('test updateConfig', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										32
									
								
								mobile/openapi/test/system_config_entity_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								mobile/openapi/test/system_config_entity_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 SystemConfigEntity | ||||
| void main() { | ||||
|   // final instance = SystemConfigEntity(); | ||||
|  | ||||
|   group('test SystemConfigEntity', () { | ||||
|     // String key | ||||
|     test('to test the property `key`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|     // Object value | ||||
|     test('to test the property `value`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|  | ||||
|   }); | ||||
|  | ||||
| } | ||||
							
								
								
									
										21
									
								
								mobile/openapi/test/system_config_key_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								mobile/openapi/test/system_config_key_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 SystemConfigKey | ||||
| void main() { | ||||
|  | ||||
|   group('test SystemConfigKey', () { | ||||
|  | ||||
|   }); | ||||
|  | ||||
| } | ||||
							
								
								
									
										27
									
								
								mobile/openapi/test/system_config_response_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								mobile/openapi/test/system_config_response_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -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 SystemConfigResponseDto | ||||
| void main() { | ||||
|   // final instance = SystemConfigResponseDto(); | ||||
|  | ||||
|   group('test SystemConfigResponseDto', () { | ||||
|     // Object config | ||||
|     test('to test the property `config`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|  | ||||
|   }); | ||||
|  | ||||
| } | ||||
							
								
								
									
										37
									
								
								mobile/openapi/test/system_config_response_item_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								mobile/openapi/test/system_config_response_item_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| // | ||||
| // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||
| // | ||||
| // @dart=2.12 | ||||
|  | ||||
| // ignore_for_file: unused_element, unused_import | ||||
| // ignore_for_file: always_put_required_named_parameters_first | ||||
| // ignore_for_file: constant_identifier_names | ||||
| // ignore_for_file: lines_longer_than_80_chars | ||||
|  | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:test/test.dart'; | ||||
|  | ||||
| // tests for SystemConfigResponseItem | ||||
| void main() { | ||||
|   // final instance = SystemConfigResponseItem(); | ||||
|  | ||||
|   group('test SystemConfigResponseItem', () { | ||||
|     // String name | ||||
|     test('to test the property `name`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|     // String key | ||||
|     test('to test the property `key`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|     // Object value | ||||
|     test('to test the property `value`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|  | ||||
|   }); | ||||
|  | ||||
| } | ||||
| @@ -1 +1,6 @@ | ||||
| # Immich Server- NestJs | ||||
| ## How to run migration | ||||
|  | ||||
| 1. Attached to the container shell | ||||
| 2. Run `npm run typeorm -- migration:generate ./libs/database/src/<migration-name> -d libs/database/src/config/database.config.ts` | ||||
| 3. Check if the migration file makes sense | ||||
| 4. Move the migration file to folder `server/libs/database/src/migrations` in your code editor. | ||||
| @@ -0,0 +1,20 @@ | ||||
| import { SystemConfigKey, SystemConfigValue } from '@app/database/entities/system-config.entity'; | ||||
| import { ApiProperty } from '@nestjs/swagger'; | ||||
| import { IsEnum, IsNotEmpty, ValidateNested } from 'class-validator'; | ||||
|  | ||||
| export class UpdateSystemConfigDto { | ||||
|   @IsNotEmpty() | ||||
|   @ValidateNested({ each: true }) | ||||
|   config!: SystemConfigItem[]; | ||||
| } | ||||
|  | ||||
| export class SystemConfigItem { | ||||
|   @IsNotEmpty() | ||||
|   @IsEnum(SystemConfigKey) | ||||
|   @ApiProperty({ | ||||
|     enum: SystemConfigKey, | ||||
|     enumName: 'SystemConfigKey', | ||||
|   }) | ||||
|   key!: SystemConfigKey; | ||||
|   value!: SystemConfigValue; | ||||
| } | ||||
| @@ -0,0 +1,20 @@ | ||||
| import { SystemConfigKey, SystemConfigValue } from '@app/database/entities/system-config.entity'; | ||||
| import { ApiProperty } from '@nestjs/swagger'; | ||||
|  | ||||
| export class SystemConfigResponseDto { | ||||
|   config!: SystemConfigResponseItem[]; | ||||
| } | ||||
|  | ||||
| export class SystemConfigResponseItem { | ||||
|   @ApiProperty({ type: 'string' }) | ||||
|   name!: string; | ||||
|  | ||||
|   @ApiProperty({ enumName: 'SystemConfigKey', enum: SystemConfigKey }) | ||||
|   key!: SystemConfigKey; | ||||
|  | ||||
|   @ApiProperty({ type: 'string' }) | ||||
|   value!: SystemConfigValue; | ||||
|  | ||||
|   @ApiProperty({ type: 'string' }) | ||||
|   defaultValue!: SystemConfigValue; | ||||
| } | ||||
| @@ -0,0 +1,24 @@ | ||||
| import { Body, Controller, Get, Put, ValidationPipe } from '@nestjs/common'; | ||||
| import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; | ||||
| import { Authenticated } from '../../decorators/authenticated.decorator'; | ||||
| import { UpdateSystemConfigDto } from './dto/update-system-config'; | ||||
| import { SystemConfigResponseDto } from './response-dto/system-config-response.dto'; | ||||
| import { SystemConfigService } from './system-config.service'; | ||||
|  | ||||
| @ApiTags('System Config') | ||||
| @ApiBearerAuth() | ||||
| @Authenticated({ admin: true }) | ||||
| @Controller('system-config') | ||||
| export class SystemConfigController { | ||||
|   constructor(private readonly systemConfigService: SystemConfigService) {} | ||||
|  | ||||
|   @Get() | ||||
|   getConfig(): Promise<SystemConfigResponseDto> { | ||||
|     return this.systemConfigService.getConfig(); | ||||
|   } | ||||
|  | ||||
|   @Put() | ||||
|   async updateConfig(@Body(ValidationPipe) dto: UpdateSystemConfigDto): Promise<SystemConfigResponseDto> { | ||||
|     return this.systemConfigService.updateConfig(dto); | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,14 @@ | ||||
| import { SystemConfigEntity } from '@app/database/entities/system-config.entity'; | ||||
| import { Module } from '@nestjs/common'; | ||||
| import { TypeOrmModule } from '@nestjs/typeorm'; | ||||
| import { ImmichConfigModule } from 'libs/immich-config/src'; | ||||
| import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; | ||||
| import { SystemConfigController } from './system-config.controller'; | ||||
| import { SystemConfigService } from './system-config.service'; | ||||
|  | ||||
| @Module({ | ||||
|   imports: [ImmichJwtModule, ImmichConfigModule, TypeOrmModule.forFeature([SystemConfigEntity])], | ||||
|   controllers: [SystemConfigController], | ||||
|   providers: [SystemConfigService], | ||||
| }) | ||||
| export class SystemConfigModule {} | ||||
| @@ -0,0 +1,20 @@ | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { ImmichConfigService } from 'libs/immich-config/src'; | ||||
| import { UpdateSystemConfigDto } from './dto/update-system-config'; | ||||
| import { SystemConfigResponseDto } from './response-dto/system-config-response.dto'; | ||||
|  | ||||
| @Injectable() | ||||
| export class SystemConfigService { | ||||
|   constructor(private immichConfigService: ImmichConfigService) {} | ||||
|  | ||||
|   async getConfig(): Promise<SystemConfigResponseDto> { | ||||
|     const config = await this.immichConfigService.getSystemConfig(); | ||||
|     return { config }; | ||||
|   } | ||||
|  | ||||
|   async updateConfig(dto: UpdateSystemConfigDto): Promise<SystemConfigResponseDto> { | ||||
|     await this.immichConfigService.updateSystemConfig(dto.config); | ||||
|     const config = await this.immichConfigService.getSystemConfig(); | ||||
|     return { config }; | ||||
|   } | ||||
| } | ||||
| @@ -16,6 +16,7 @@ import { ScheduleModule } from '@nestjs/schedule'; | ||||
| import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module'; | ||||
| import { DatabaseModule } from '@app/database'; | ||||
| import { JobModule } from './api-v1/job/job.module'; | ||||
| import { SystemConfigModule } from './api-v1/system-config/system-config.module'; | ||||
| import { OAuthModule } from './api-v1/oauth/oauth.module'; | ||||
|  | ||||
| @Module({ | ||||
| @@ -60,6 +61,8 @@ import { OAuthModule } from './api-v1/oauth/oauth.module'; | ||||
|     ScheduleTasksModule, | ||||
|  | ||||
|     JobModule, | ||||
|  | ||||
|     SystemConfigModule, | ||||
|   ], | ||||
|   controllers: [AppController], | ||||
|   providers: [], | ||||
|   | ||||
| @@ -7,8 +7,9 @@ import { UserEntity } from '@app/database/entities/user.entity'; | ||||
| import { QueueNameEnum } from '@app/job/constants/queue-name.constant'; | ||||
| import { BullModule } from '@nestjs/bull'; | ||||
| import { Module } from '@nestjs/common'; | ||||
| import { ConfigModule, ConfigService } from '@nestjs/config'; | ||||
| import { ConfigModule } from '@nestjs/config'; | ||||
| import { TypeOrmModule } from '@nestjs/typeorm'; | ||||
| import { ImmichConfigModule } from 'libs/immich-config/src'; | ||||
| import { CommunicationModule } from '../../immich/src/api-v1/communication/communication.module'; | ||||
| import { MicroservicesService } from './microservices.service'; | ||||
| import { AssetUploadedProcessor } from './processors/asset-uploaded.processor'; | ||||
| @@ -22,6 +23,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | ||||
|   imports: [ | ||||
|     ConfigModule.forRoot(immichAppConfig), | ||||
|     DatabaseModule, | ||||
|     ImmichConfigModule, | ||||
|     TypeOrmModule.forFeature([UserEntity, ExifEntity, AssetEntity, SmartInfoEntity]), | ||||
|     BullModule.forRootAsync({ | ||||
|       useFactory: async () => ({ | ||||
| @@ -96,7 +98,6 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' | ||||
|     VideoTranscodeProcessor, | ||||
|     GenerateChecksumProcessor, | ||||
|     MachineLearningProcessor, | ||||
|     ConfigService, | ||||
|   ], | ||||
|   exports: [], | ||||
| }) | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import { InjectRepository } from '@nestjs/typeorm'; | ||||
| import { Job } from 'bull'; | ||||
| import ffmpeg from 'fluent-ffmpeg'; | ||||
| import { existsSync, mkdirSync } from 'fs'; | ||||
| import { ImmichConfigService } from 'libs/immich-config/src'; | ||||
| import { Repository } from 'typeorm'; | ||||
|  | ||||
| @Processor(QueueNameEnum.VIDEO_CONVERSION) | ||||
| @@ -16,6 +17,7 @@ export class VideoTranscodeProcessor { | ||||
|   constructor( | ||||
|     @InjectRepository(AssetEntity) | ||||
|     private assetRepository: Repository<AssetEntity>, | ||||
|     private immichConfigService: ImmichConfigService, | ||||
|   ) {} | ||||
|  | ||||
|   @Process({ name: mp4ConversionProcessorName, concurrency: 1 }) | ||||
| @@ -40,9 +42,17 @@ export class VideoTranscodeProcessor { | ||||
|   } | ||||
|  | ||||
|   async runFFMPEGPipeLine(asset: AssetEntity, savedEncodedPath: string): Promise<void> { | ||||
|     const config = await this.immichConfigService.getSystemConfigMap(); | ||||
|  | ||||
|     return new Promise((resolve, reject) => { | ||||
|       ffmpeg(asset.originalPath) | ||||
|         .outputOptions(['-crf 23', '-preset ultrafast', '-vcodec libx264', '-acodec mp3', '-vf scale=1280:-2']) | ||||
|         .outputOptions([ | ||||
|           `-crf ${config.ffmpeg_crf}`, | ||||
|           `-preset ${config.ffmpeg_preset}`, | ||||
|           `-vcodec ${config.ffmpeg_target_video_codec}`, | ||||
|           `-acodec ${config.ffmpeg_target_audio_codec}`, | ||||
|           `-vf scale=${config.ffmpeg_target_scaling}`, | ||||
|         ]) | ||||
|         .output(savedEncodedPath) | ||||
|         .on('start', () => { | ||||
|           Logger.log('Start Converting Video', 'mp4Conversion'); | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										27
									
								
								server/libs/database/src/entities/system-config.entity.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								server/libs/database/src/entities/system-config.entity.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import { Column, Entity, PrimaryColumn } from 'typeorm'; | ||||
|  | ||||
| @Entity('system_config') | ||||
| export class SystemConfigEntity { | ||||
|   @PrimaryColumn() | ||||
|   key!: SystemConfigKey; | ||||
|  | ||||
|   @Column({ type: 'varchar', nullable: true }) | ||||
|   value!: SystemConfigValue; | ||||
| } | ||||
|  | ||||
| export type SystemConfig = SystemConfigEntity[]; | ||||
|  | ||||
| export enum SystemConfigKey { | ||||
|   FFMPEG_CRF = 'ffmpeg_crf', | ||||
|   FFMPEG_PRESET = 'ffmpeg_preset', | ||||
|   FFMPEG_TARGET_VIDEO_CODEC = 'ffmpeg_target_video_codec', | ||||
|   FFMPEG_TARGET_AUDIO_CODEC = 'ffmpeg_target_audio_codec', | ||||
|   FFMPEG_TARGET_SCALING = 'ffmpeg_target_scaling', | ||||
| } | ||||
|  | ||||
| export type SystemConfigValue = string | null; | ||||
|  | ||||
| export interface SystemConfigItem { | ||||
|   key: SystemConfigKey; | ||||
|   value: SystemConfigValue; | ||||
| } | ||||
| @@ -0,0 +1,15 @@ | ||||
| import { MigrationInterface, QueryRunner } from 'typeorm'; | ||||
|  | ||||
| export class CreateSystemConfigTable1665540663419 implements MigrationInterface { | ||||
|   name = 'CreateSystemConfigTable1665540663419'; | ||||
|  | ||||
|   public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|     await queryRunner.query( | ||||
|       `CREATE TABLE "system_config" ("key" character varying NOT NULL, "value" character varying, CONSTRAINT "PK_aab69295b445016f56731f4d535" PRIMARY KEY ("key"))`, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|     await queryRunner.query(`DROP TABLE "system_config"`); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										11
									
								
								server/libs/immich-config/src/immich-config.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								server/libs/immich-config/src/immich-config.module.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| import { SystemConfigEntity } from '@app/database/entities/system-config.entity'; | ||||
| import { Module } from '@nestjs/common'; | ||||
| import { TypeOrmModule } from '@nestjs/typeorm'; | ||||
| import { ImmichConfigService } from './immich-config.service'; | ||||
|  | ||||
| @Module({ | ||||
|   imports: [TypeOrmModule.forFeature([SystemConfigEntity])], | ||||
|   providers: [ImmichConfigService], | ||||
|   exports: [ImmichConfigService], | ||||
| }) | ||||
| export class ImmichConfigModule {} | ||||
							
								
								
									
										97
									
								
								server/libs/immich-config/src/immich-config.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								server/libs/immich-config/src/immich-config.service.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| import { SystemConfigEntity, SystemConfigKey, SystemConfigValue } from '@app/database/entities/system-config.entity'; | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { InjectRepository } from '@nestjs/typeorm'; | ||||
| import { In, Repository } from 'typeorm'; | ||||
|  | ||||
| type SystemConfigMap = Record<SystemConfigKey, SystemConfigValue>; | ||||
|  | ||||
| const configDefaults: Record<SystemConfigKey, { name: string; value: SystemConfigValue }> = { | ||||
|   [SystemConfigKey.FFMPEG_CRF]: { | ||||
|     name: 'FFmpeg Constant Rate Factor (-crf)', | ||||
|     value: '23', | ||||
|   }, | ||||
|   [SystemConfigKey.FFMPEG_PRESET]: { | ||||
|     name: 'FFmpeg preset (-preset)', | ||||
|     value: 'ultrafast', | ||||
|   }, | ||||
|   [SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC]: { | ||||
|     name: 'FFmpeg target video codec (-vcodec)', | ||||
|     value: 'libx264', | ||||
|   }, | ||||
|   [SystemConfigKey.FFMPEG_TARGET_AUDIO_CODEC]: { | ||||
|     name: 'FFmpeg target audio codec (-acodec)', | ||||
|     value: 'mp3', | ||||
|   }, | ||||
|   [SystemConfigKey.FFMPEG_TARGET_SCALING]: { | ||||
|     name: 'FFmpeg target scaling (-vf scale=)', | ||||
|     value: '1280:-2', | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| @Injectable() | ||||
| export class ImmichConfigService { | ||||
|   constructor( | ||||
|     @InjectRepository(SystemConfigEntity) | ||||
|     private systemConfigRepository: Repository<SystemConfigEntity>, | ||||
|   ) {} | ||||
|  | ||||
|   public async getSystemConfig() { | ||||
|     const items = this._getDefaults(); | ||||
|  | ||||
|     // override default values | ||||
|     const overrides = await this.systemConfigRepository.find(); | ||||
|     for (const override of overrides) { | ||||
|       const item = items.find((_item) => _item.key === override.key); | ||||
|       if (item) { | ||||
|         item.value = override.value; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return items; | ||||
|   } | ||||
|  | ||||
|   public async getSystemConfigMap(): Promise<SystemConfigMap> { | ||||
|     const items = await this.getSystemConfig(); | ||||
|     const map: Partial<SystemConfigMap> = {}; | ||||
|  | ||||
|     for (const { key, value } of items) { | ||||
|       map[key] = value; | ||||
|     } | ||||
|  | ||||
|     return map as SystemConfigMap; | ||||
|   } | ||||
|  | ||||
|   public async updateSystemConfig(items: SystemConfigEntity[]): Promise<void> { | ||||
|     const deletes: SystemConfigEntity[] = []; | ||||
|     const updates: SystemConfigEntity[] = []; | ||||
|  | ||||
|     for (const item of items) { | ||||
|       if (item.value === null || item.value === this._getDefaultValue(item.key)) { | ||||
|         deletes.push(item); | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       updates.push(item); | ||||
|     } | ||||
|  | ||||
|     if (updates.length > 0) { | ||||
|       await this.systemConfigRepository.save(updates); | ||||
|     } | ||||
|  | ||||
|     if (deletes.length > 0) { | ||||
|       await this.systemConfigRepository.delete({ key: In(deletes.map((item) => item.key)) }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private _getDefaults() { | ||||
|     return Object.values(SystemConfigKey).map((key) => ({ | ||||
|       key, | ||||
|       defaultValue: configDefaults[key].value, | ||||
|       ...configDefaults[key], | ||||
|     })); | ||||
|   } | ||||
|  | ||||
|   private _getDefaultValue(key: SystemConfigKey) { | ||||
|     return this._getDefaults().find((item) => item.key === key)?.value || null; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										2
									
								
								server/libs/immich-config/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								server/libs/immich-config/src/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| export * from './immich-config.module'; | ||||
| export * from './immich-config.service'; | ||||
							
								
								
									
										9
									
								
								server/libs/immich-config/tsconfig.lib.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								server/libs/immich-config/tsconfig.lib.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| { | ||||
|   "extends": "../../tsconfig.json", | ||||
|   "compilerOptions": { | ||||
|     "declaration": true, | ||||
|     "outDir": "../../dist/libs/immich-config" | ||||
|   }, | ||||
|   "include": ["src/**/*"], | ||||
|   "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] | ||||
| } | ||||
| @@ -70,6 +70,15 @@ | ||||
|       "compilerOptions": { | ||||
|         "tsConfigPath": "libs/job/tsconfig.lib.json" | ||||
|       } | ||||
|     }, | ||||
|     "system-config": { | ||||
|       "type": "library", | ||||
|       "root": "libs/system-config", | ||||
|       "entryFile": "index", | ||||
|       "sourceRoot": "libs/system-config/src", | ||||
|       "compilerOptions": { | ||||
|         "tsConfigPath": "libs/system-config/tsconfig.lib.json" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| } | ||||
| @@ -13,6 +13,7 @@ | ||||
|     "build": "nest build immich && nest build microservices && nest build cli", | ||||
|     "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", | ||||
|     "start": "nest start", | ||||
|     "nest": "nest", | ||||
|     "start:dev": "nest start --watch", | ||||
|     "start:debug": "nest start --debug 0.0.0.0:9230 --watch", | ||||
|     "start:prod": "node dist/main", | ||||
| @@ -139,7 +140,8 @@ | ||||
|       "@app/database/config/(.*)": "<rootDir>/libs/database/src/config/$1", | ||||
|       "@app/database/config": "<rootDir>/libs/database/src/config", | ||||
|       "@app/common": "<rootDir>/libs/common/src", | ||||
|       "^@app/job(|/.*)$": "<rootDir>/libs/job/src/$1" | ||||
|       "^@app/job(|/.*)$": "<rootDir>/libs/job/src/$1", | ||||
|       "^@app/system-config(|/.*)$": "<rootDir>/libs/system-config/src/$1" | ||||
|     } | ||||
|   } | ||||
| } | ||||
| } | ||||
| @@ -16,29 +16,15 @@ | ||||
|     "esModuleInterop": true, | ||||
|     "baseUrl": "./", | ||||
|     "paths": { | ||||
|       "@app/common": [ | ||||
|         "libs/common/src" | ||||
|       ], | ||||
|       "@app/common/*": [ | ||||
|         "libs/common/src/*" | ||||
|       ], | ||||
|       "@app/database": [ | ||||
|         "libs/database/src" | ||||
|       ], | ||||
|       "@app/database/*": [ | ||||
|         "libs/database/src/*" | ||||
|       ], | ||||
|       "@app/job": [ | ||||
|         "libs/job/src" | ||||
|       ], | ||||
|       "@app/job/*": [ | ||||
|         "libs/job/src/*" | ||||
|       ] | ||||
|       "@app/common": ["libs/common/src"], | ||||
|       "@app/common/*": ["libs/common/src/*"], | ||||
|       "@app/database": ["libs/database/src"], | ||||
|       "@app/database/*": ["libs/database/src/*"], | ||||
|       "@app/job": ["libs/job/src"], | ||||
|       "@app/job/*": ["libs/job/src/*"], | ||||
|       "@app/system-config": ["libs/immich-config/src"], | ||||
|       "@app/system-config/*": ["libs/immich-config/src/*"] | ||||
|     } | ||||
|   }, | ||||
|   "exclude": [ | ||||
|     "dist", | ||||
|     "node_modules", | ||||
|     "upload" | ||||
|   ] | ||||
| } | ||||
|   "exclude": ["dist", "node_modules", "upload"] | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { | ||||
| 	JobApi, | ||||
| 	OAuthApi, | ||||
| 	ServerInfoApi, | ||||
| 	SystemConfigApi, | ||||
| 	UserApi | ||||
| } from './open-api'; | ||||
|  | ||||
| @@ -20,6 +21,7 @@ class ImmichApi { | ||||
| 	public deviceInfoApi: DeviceInfoApi; | ||||
| 	public serverInfoApi: ServerInfoApi; | ||||
| 	public jobApi: JobApi; | ||||
| 	public systemConfigApi: SystemConfigApi; | ||||
|  | ||||
| 	private config = new Configuration({ basePath: '/api' }); | ||||
|  | ||||
| @@ -32,6 +34,7 @@ class ImmichApi { | ||||
| 		this.deviceInfoApi = new DeviceInfoApi(this.config); | ||||
| 		this.serverInfoApi = new ServerInfoApi(this.config); | ||||
| 		this.jobApi = new JobApi(this.config); | ||||
| 		this.systemConfigApi = new SystemConfigApi(this.config); | ||||
| 	} | ||||
|  | ||||
| 	public setAccessToken(accessToken: string) { | ||||
|   | ||||
| @@ -1407,6 +1407,67 @@ export interface SmartInfoResponseDto { | ||||
|  * @enum {string} | ||||
|  */ | ||||
|  | ||||
| export const SystemConfigKey = { | ||||
|     Crf: 'ffmpeg_crf', | ||||
|     Preset: 'ffmpeg_preset', | ||||
|     TargetVideoCodec: 'ffmpeg_target_video_codec', | ||||
|     TargetAudioCodec: 'ffmpeg_target_audio_codec', | ||||
|     TargetScaling: 'ffmpeg_target_scaling' | ||||
| } as const; | ||||
|  | ||||
| export type SystemConfigKey = typeof SystemConfigKey[keyof typeof SystemConfigKey]; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface SystemConfigResponseDto | ||||
|  */ | ||||
| export interface SystemConfigResponseDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {Array<SystemConfigResponseItem>} | ||||
|      * @memberof SystemConfigResponseDto | ||||
|      */ | ||||
|     'config': Array<SystemConfigResponseItem>; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface SystemConfigResponseItem | ||||
|  */ | ||||
| export interface SystemConfigResponseItem { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
|      * @memberof SystemConfigResponseItem | ||||
|      */ | ||||
|     'name': string; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {SystemConfigKey} | ||||
|      * @memberof SystemConfigResponseItem | ||||
|      */ | ||||
|     'key': SystemConfigKey; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
|      * @memberof SystemConfigResponseItem | ||||
|      */ | ||||
|     'value': string; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
|      * @memberof SystemConfigResponseItem | ||||
|      */ | ||||
|     'defaultValue': string; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @enum {string} | ||||
|  */ | ||||
|  | ||||
| export const ThumbnailFormat = { | ||||
|     Jpeg: 'JPEG', | ||||
|     Webp: 'WEBP' | ||||
| @@ -4946,6 +5007,173 @@ export class ServerInfoApi extends BaseAPI { | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * SystemConfigApi - axios parameter creator | ||||
|  * @export | ||||
|  */ | ||||
| export const SystemConfigApiAxiosParamCreator = function (configuration?: Configuration) { | ||||
|     return { | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getConfig: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             const localVarPath = `/system-config`; | ||||
|             // 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) | ||||
|  | ||||
|  | ||||
|      | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
|  | ||||
|             return { | ||||
|                 url: toPathString(localVarUrlObj), | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {object} body  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         updateConfig: async (body: object, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             // verify required parameter 'body' is not null or undefined | ||||
|             assertParamExists('updateConfig', 'body', body) | ||||
|             const localVarPath = `/system-config`; | ||||
|             // 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: 'PUT', ...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(body, localVarRequestOptions, configuration) | ||||
|  | ||||
|             return { | ||||
|                 url: toPathString(localVarUrlObj), | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|     } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * SystemConfigApi - functional programming interface | ||||
|  * @export | ||||
|  */ | ||||
| export const SystemConfigApiFp = function(configuration?: Configuration) { | ||||
|     const localVarAxiosParamCreator = SystemConfigApiAxiosParamCreator(configuration) | ||||
|     return { | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async getConfig(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SystemConfigResponseDto>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.getConfig(options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {object} body  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async updateConfig(body: object, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SystemConfigResponseDto>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.updateConfig(body, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|     } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * SystemConfigApi - factory interface | ||||
|  * @export | ||||
|  */ | ||||
| export const SystemConfigApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { | ||||
|     const localVarFp = SystemConfigApiFp(configuration) | ||||
|     return { | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getConfig(options?: any): AxiosPromise<SystemConfigResponseDto> { | ||||
|             return localVarFp.getConfig(options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {object} body  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         updateConfig(body: object, options?: any): AxiosPromise<SystemConfigResponseDto> { | ||||
|             return localVarFp.updateConfig(body, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|     }; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * SystemConfigApi - object-oriented interface | ||||
|  * @export | ||||
|  * @class SystemConfigApi | ||||
|  * @extends {BaseAPI} | ||||
|  */ | ||||
| export class SystemConfigApi extends BaseAPI { | ||||
|     /** | ||||
|      *  | ||||
|      * @param {*} [options] Override http request option. | ||||
|      * @throws {RequiredError} | ||||
|      * @memberof SystemConfigApi | ||||
|      */ | ||||
|     public getConfig(options?: AxiosRequestConfig) { | ||||
|         return SystemConfigApiFp(this.configuration).getConfig(options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  | ||||
|      * @param {object} body  | ||||
|      * @param {*} [options] Override http request option. | ||||
|      * @throws {RequiredError} | ||||
|      * @memberof SystemConfigApi | ||||
|      */ | ||||
|     public updateConfig(body: object, options?: AxiosRequestConfig) { | ||||
|         return SystemConfigApiFp(this.configuration).updateConfig(body, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * UserApi - axios parameter creator | ||||
|  * @export | ||||
|   | ||||
| @@ -59,7 +59,7 @@ input:focus-visible { | ||||
|  | ||||
| @layer utilities { | ||||
| 	.immich-form-input { | ||||
| 		@apply bg-slate-100 p-2 rounded-md dark:text-immich-dark-bg focus:border-immich-primary text-sm; | ||||
| 		@apply bg-slate-100 p-2 rounded-md dark:text-immich-dark-bg focus:border-immich-primary text-sm dark:bg-gray-600 dark:text-immich-dark-fg; | ||||
| 	} | ||||
|  | ||||
| 	.immich-form-label { | ||||
|   | ||||
| @@ -0,0 +1,97 @@ | ||||
| <script lang="ts"> | ||||
| 	import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; | ||||
| 	import { | ||||
| 		notificationController, | ||||
| 		NotificationType | ||||
| 	} from '$lib/components/shared-components/notification/notification'; | ||||
| 	import { api, SystemConfigResponseItem } from '@api'; | ||||
| 	import { onMount } from 'svelte'; | ||||
|  | ||||
| 	let isSaving = false; | ||||
| 	let items: Array<SystemConfigResponseItem & { originalValue: string }> = []; | ||||
|  | ||||
| 	const refreshConfig = async () => { | ||||
| 		const { data: systemConfig } = await api.systemConfigApi.getConfig(); | ||||
| 		items = systemConfig.config.map((item) => ({ ...item, originalValue: item.value })); | ||||
| 	}; | ||||
|  | ||||
| 	onMount(() => refreshConfig()); | ||||
|  | ||||
| 	const handleSave = async () => { | ||||
| 		try { | ||||
| 			isSaving = true; | ||||
| 			const updates = items | ||||
| 				.filter((item) => item.value !== item.originalValue) | ||||
| 				.map(({ key, value }) => ({ key, value: value || null })); | ||||
| 			if (updates.length > 0) { | ||||
| 				await api.systemConfigApi.updateConfig({ config: updates }); | ||||
| 				refreshConfig(); | ||||
| 			} | ||||
|  | ||||
| 			notificationController.show({ | ||||
| 				message: `Saved settings`, | ||||
| 				type: NotificationType.Info | ||||
| 			}); | ||||
| 		} catch (e) { | ||||
| 			console.error('Error [updateSystemConfig]', e); | ||||
| 			notificationController.show({ | ||||
| 				message: `Unable to save changes.`, | ||||
| 				type: NotificationType.Error | ||||
| 			}); | ||||
| 		} finally { | ||||
| 			isSaving = false; | ||||
| 		} | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <section> | ||||
| 	<table class="text-left my-4 w-full"> | ||||
| 		<thead | ||||
| 			class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray" | ||||
| 		> | ||||
| 			<tr class="flex w-full place-items-center"> | ||||
| 				<th class="text-center w-1/2 font-medium text-sm">Setting</th> | ||||
| 				<th class="text-center w-1/2 font-medium text-sm">Value</th> | ||||
| 			</tr> | ||||
| 		</thead> | ||||
| 		<tbody class="rounded-md block border dark:border-immich-dark-gray"> | ||||
| 			{#each items as item, i} | ||||
| 				<tr | ||||
| 					class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${ | ||||
| 						i % 2 == 0 ? 'bg-slate-50 dark:bg-[#181818]' : 'bg-immich-bg dark:bg-immich-dark-bg' | ||||
| 					}`} | ||||
| 				> | ||||
| 					<td class="text-sm px-4 w-1/2 text-ellipsis"> | ||||
| 						{item.name} | ||||
| 					</td> | ||||
| 					<td class="text-sm px-4 w-1/2 text-ellipsis"> | ||||
| 						<input | ||||
| 							style="text-align: center" | ||||
| 							class="immich-form-input" | ||||
| 							id={item.key} | ||||
| 							disabled={isSaving} | ||||
| 							name={item.key} | ||||
| 							type="text" | ||||
| 							bind:value={item.value} | ||||
| 							placeholder={item.defaultValue + ''} | ||||
| 						/> | ||||
| 					</td> | ||||
| 				</tr> | ||||
| 			{/each} | ||||
| 		</tbody> | ||||
| 	</table> | ||||
|  | ||||
| 	<div class="flex justify-end"> | ||||
| 		<button | ||||
| 			on:click={handleSave} | ||||
| 			class="px-6 py-3 text-sm bg-immich-primary dark:bg-immich-dark-primary font-medium rounded-2xl hover:bg-immich-primary/50 transition-all hover:cursor-pointer disabled:cursor-not-allowed shadow-sm text-immich-bg dark:text-immich-dark-gray" | ||||
| 			disabled={isSaving} | ||||
| 		> | ||||
| 			{#if isSaving} | ||||
| 				<LoadingSpinner /> | ||||
| 			{:else} | ||||
| 				Save | ||||
| 			{/if} | ||||
| 		</button> | ||||
| 	</div> | ||||
| </section> | ||||
| @@ -12,8 +12,6 @@ export const load: PageServerLoad = async ({ parent }) => { | ||||
| 	} | ||||
|  | ||||
| 	const { data: allUsers } = await serverApi.userApi.getAllUsers(false); | ||||
| 	return { | ||||
| 		user: user, | ||||
| 		allUsers: allUsers | ||||
| 	}; | ||||
|  | ||||
| 	return { user, allUsers }; | ||||
| }; | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| 	import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection'; | ||||
| 	import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte'; | ||||
| 	import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte'; | ||||
| 	import Sync from 'svelte-material-icons/Sync.svelte'; | ||||
| 	import Cog from 'svelte-material-icons/Cog.svelte'; | ||||
| 	import Server from 'svelte-material-icons/Server.svelte'; | ||||
| 	import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte'; | ||||
| @@ -16,6 +17,7 @@ | ||||
| 	import type { PageData } from './$types'; | ||||
| 	import { api, ServerStatsResponseDto, UserResponseDto } from '@api'; | ||||
| 	import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte'; | ||||
| 	import SettingsPanel from '$lib/components/admin-page/settings/settings-panel.svelte'; | ||||
| 	import ServerStatsPanel from '$lib/components/admin-page/server-stats/server-stats-panel.svelte'; | ||||
| 	import RestoreDialoge from '$lib/components/admin-page/restore-dialoge.svelte'; | ||||
|  | ||||
| @@ -190,11 +192,18 @@ | ||||
| 		/> | ||||
| 		<SideBarButton | ||||
| 			title="Jobs" | ||||
| 			logo={Cog} | ||||
| 			logo={Sync} | ||||
| 			actionType={AdminSideBarSelection.JOBS} | ||||
| 			isSelected={selectedAction === AdminSideBarSelection.JOBS} | ||||
| 			on:selected={onButtonClicked} | ||||
| 		/> | ||||
| 		<SideBarButton | ||||
| 			title="Settings" | ||||
| 			logo={Cog} | ||||
| 			actionType={AdminSideBarSelection.SETTINGS} | ||||
| 			isSelected={selectedAction === AdminSideBarSelection.SETTINGS} | ||||
| 			on:selected={onButtonClicked} | ||||
| 		/> | ||||
| 		<SideBarButton | ||||
| 			title="Server Stats" | ||||
| 			logo={Server} | ||||
| @@ -228,6 +237,9 @@ | ||||
| 				{#if selectedAction === AdminSideBarSelection.JOBS} | ||||
| 					<JobsPanel /> | ||||
| 				{/if} | ||||
| 				{#if selectedAction === AdminSideBarSelection.SETTINGS} | ||||
| 					<SettingsPanel /> | ||||
| 				{/if} | ||||
| 				{#if selectedAction === AdminSideBarSelection.STATS && serverStat} | ||||
| 					<ServerStatsPanel stats={serverStat} allUsers={data.allUsers} /> | ||||
| 				{/if} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user