mirror of
https://github.com/immich-app/immich.git
synced 2024-11-15 09:59:00 -07:00
feat(web,server): api keys (#1244)
* feat(server): api keys * chore: open-api * feat(web): api keys * fix: remove keys when deleting a user
This commit is contained in:
parent
9edbff0ec0
commit
9e6d6b2532
15
mobile/openapi/.openapi-generator/FILES
generated
15
mobile/openapi/.openapi-generator/FILES
generated
@ -3,6 +3,11 @@
|
||||
.travis.yml
|
||||
README.md
|
||||
analysis_options.yaml
|
||||
doc/APIKeyApi.md
|
||||
doc/APIKeyCreateDto.md
|
||||
doc/APIKeyCreateResponseDto.md
|
||||
doc/APIKeyResponseDto.md
|
||||
doc/APIKeyUpdateDto.md
|
||||
doc/AddAssetsDto.md
|
||||
doc/AddAssetsResponseDto.md
|
||||
doc/AddUsersDto.md
|
||||
@ -85,6 +90,7 @@ doc/ValidateAccessTokenResponseDto.md
|
||||
git_push.sh
|
||||
lib/api.dart
|
||||
lib/api/album_api.dart
|
||||
lib/api/api_key_api.dart
|
||||
lib/api/asset_api.dart
|
||||
lib/api/authentication_api.dart
|
||||
lib/api/device_info_api.dart
|
||||
@ -109,6 +115,10 @@ lib/model/admin_signup_response_dto.dart
|
||||
lib/model/album_count_response_dto.dart
|
||||
lib/model/album_response_dto.dart
|
||||
lib/model/all_job_status_response_dto.dart
|
||||
lib/model/api_key_create_dto.dart
|
||||
lib/model/api_key_create_response_dto.dart
|
||||
lib/model/api_key_response_dto.dart
|
||||
lib/model/api_key_update_dto.dart
|
||||
lib/model/asset_count_by_time_bucket.dart
|
||||
lib/model/asset_count_by_time_bucket_response_dto.dart
|
||||
lib/model/asset_count_by_user_id_response_dto.dart
|
||||
@ -180,6 +190,11 @@ test/album_api_test.dart
|
||||
test/album_count_response_dto_test.dart
|
||||
test/album_response_dto_test.dart
|
||||
test/all_job_status_response_dto_test.dart
|
||||
test/api_key_api_test.dart
|
||||
test/api_key_create_dto_test.dart
|
||||
test/api_key_create_response_dto_test.dart
|
||||
test/api_key_response_dto_test.dart
|
||||
test/api_key_update_dto_test.dart
|
||||
test/asset_api_test.dart
|
||||
test/asset_count_by_time_bucket_response_dto_test.dart
|
||||
test/asset_count_by_time_bucket_test.dart
|
||||
|
24
mobile/openapi/README.md
generated
24
mobile/openapi/README.md
generated
@ -39,22 +39,15 @@ Please follow the [installation procedure](#installation--usage) and then run th
|
||||
```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 = AlbumApi();
|
||||
final albumId = albumId_example; // String |
|
||||
final addAssetsDto = AddAssetsDto(); // AddAssetsDto |
|
||||
final api_instance = APIKeyApi();
|
||||
final aPIKeyCreateDto = APIKeyCreateDto(); // APIKeyCreateDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.addAssetsToAlbum(albumId, addAssetsDto);
|
||||
final result = api_instance.createKey(aPIKeyCreateDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AlbumApi->addAssetsToAlbum: $e\n');
|
||||
print('Exception when calling APIKeyApi->createKey: $e\n');
|
||||
}
|
||||
|
||||
```
|
||||
@ -65,6 +58,11 @@ All URIs are relative to */api*
|
||||
|
||||
Class | Method | HTTP request | Description
|
||||
------------ | ------------- | ------------- | -------------
|
||||
*APIKeyApi* | [**createKey**](doc//APIKeyApi.md#createkey) | **POST** /api-key |
|
||||
*APIKeyApi* | [**deleteKey**](doc//APIKeyApi.md#deletekey) | **DELETE** /api-key/{id} |
|
||||
*APIKeyApi* | [**getKey**](doc//APIKeyApi.md#getkey) | **GET** /api-key/{id} |
|
||||
*APIKeyApi* | [**getKeys**](doc//APIKeyApi.md#getkeys) | **GET** /api-key |
|
||||
*APIKeyApi* | [**updateKey**](doc//APIKeyApi.md#updatekey) | **PUT** /api-key/{id} |
|
||||
*AlbumApi* | [**addAssetsToAlbum**](doc//AlbumApi.md#addassetstoalbum) | **PUT** /album/{albumId}/assets |
|
||||
*AlbumApi* | [**addUsersToAlbum**](doc//AlbumApi.md#adduserstoalbum) | **PUT** /album/{albumId}/users |
|
||||
*AlbumApi* | [**createAlbum**](doc//AlbumApi.md#createalbum) | **POST** /album |
|
||||
@ -138,6 +136,10 @@ Class | Method | HTTP request | Description
|
||||
|
||||
## Documentation For Models
|
||||
|
||||
- [APIKeyCreateDto](doc//APIKeyCreateDto.md)
|
||||
- [APIKeyCreateResponseDto](doc//APIKeyCreateResponseDto.md)
|
||||
- [APIKeyResponseDto](doc//APIKeyResponseDto.md)
|
||||
- [APIKeyUpdateDto](doc//APIKeyUpdateDto.md)
|
||||
- [AddAssetsDto](doc//AddAssetsDto.md)
|
||||
- [AddAssetsResponseDto](doc//AddAssetsResponseDto.md)
|
||||
- [AddUsersDto](doc//AddUsersDto.md)
|
||||
|
220
mobile/openapi/doc/APIKeyApi.md
generated
Normal file
220
mobile/openapi/doc/APIKeyApi.md
generated
Normal file
@ -0,0 +1,220 @@
|
||||
# openapi.api.APIKeyApi
|
||||
|
||||
## Load the API package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
All URIs are relative to */api*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**createKey**](APIKeyApi.md#createkey) | **POST** /api-key |
|
||||
[**deleteKey**](APIKeyApi.md#deletekey) | **DELETE** /api-key/{id} |
|
||||
[**getKey**](APIKeyApi.md#getkey) | **GET** /api-key/{id} |
|
||||
[**getKeys**](APIKeyApi.md#getkeys) | **GET** /api-key |
|
||||
[**updateKey**](APIKeyApi.md#updatekey) | **PUT** /api-key/{id} |
|
||||
|
||||
|
||||
# **createKey**
|
||||
> APIKeyCreateResponseDto createKey(aPIKeyCreateDto)
|
||||
|
||||
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
final api_instance = APIKeyApi();
|
||||
final aPIKeyCreateDto = APIKeyCreateDto(); // APIKeyCreateDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.createKey(aPIKeyCreateDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling APIKeyApi->createKey: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**aPIKeyCreateDto** | [**APIKeyCreateDto**](APIKeyCreateDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**APIKeyCreateResponseDto**](APIKeyCreateResponseDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **deleteKey**
|
||||
> deleteKey(id)
|
||||
|
||||
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
final api_instance = APIKeyApi();
|
||||
final id = 8.14; // num |
|
||||
|
||||
try {
|
||||
api_instance.deleteKey(id);
|
||||
} catch (e) {
|
||||
print('Exception when calling APIKeyApi->deleteKey: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**id** | **num**| |
|
||||
|
||||
### Return type
|
||||
|
||||
void (empty response body)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: Not defined
|
||||
|
||||
[[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)
|
||||
|
||||
# **getKey**
|
||||
> APIKeyResponseDto getKey(id)
|
||||
|
||||
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
final api_instance = APIKeyApi();
|
||||
final id = 8.14; // num |
|
||||
|
||||
try {
|
||||
final result = api_instance.getKey(id);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling APIKeyApi->getKey: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**id** | **num**| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**APIKeyResponseDto**](APIKeyResponseDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **getKeys**
|
||||
> List<APIKeyResponseDto> getKeys()
|
||||
|
||||
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
final api_instance = APIKeyApi();
|
||||
|
||||
try {
|
||||
final result = api_instance.getKeys();
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling APIKeyApi->getKeys: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
This endpoint does not need any parameter.
|
||||
|
||||
### Return type
|
||||
|
||||
[**List<APIKeyResponseDto>**](APIKeyResponseDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **updateKey**
|
||||
> APIKeyResponseDto updateKey(id, aPIKeyUpdateDto)
|
||||
|
||||
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
final api_instance = APIKeyApi();
|
||||
final id = 8.14; // num |
|
||||
final aPIKeyUpdateDto = APIKeyUpdateDto(); // APIKeyUpdateDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.updateKey(id, aPIKeyUpdateDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling APIKeyApi->updateKey: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**id** | **num**| |
|
||||
**aPIKeyUpdateDto** | [**APIKeyUpdateDto**](APIKeyUpdateDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**APIKeyResponseDto**](APIKeyResponseDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
15
mobile/openapi/doc/APIKeyCreateDto.md
generated
Normal file
15
mobile/openapi/doc/APIKeyCreateDto.md
generated
Normal file
@ -0,0 +1,15 @@
|
||||
# openapi.model.APIKeyCreateDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**name** | **String** | | [optional]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
16
mobile/openapi/doc/APIKeyCreateResponseDto.md
generated
Normal file
16
mobile/openapi/doc/APIKeyCreateResponseDto.md
generated
Normal file
@ -0,0 +1,16 @@
|
||||
# openapi.model.APIKeyCreateResponseDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**secret** | **String** | |
|
||||
**apiKey** | [**APIKeyResponseDto**](APIKeyResponseDto.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)
|
||||
|
||||
|
18
mobile/openapi/doc/APIKeyResponseDto.md
generated
Normal file
18
mobile/openapi/doc/APIKeyResponseDto.md
generated
Normal file
@ -0,0 +1,18 @@
|
||||
# openapi.model.APIKeyResponseDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**id** | **num** | |
|
||||
**name** | **String** | |
|
||||
**createdAt** | **String** | |
|
||||
**updatedAt** | **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)
|
||||
|
||||
|
15
mobile/openapi/doc/APIKeyUpdateDto.md
generated
Normal file
15
mobile/openapi/doc/APIKeyUpdateDto.md
generated
Normal file
@ -0,0 +1,15 @@
|
||||
# openapi.model.APIKeyUpdateDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**name** | **String** | |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
5
mobile/openapi/lib/api.dart
generated
5
mobile/openapi/lib/api.dart
generated
@ -27,6 +27,7 @@ part 'auth/oauth.dart';
|
||||
part 'auth/http_basic_auth.dart';
|
||||
part 'auth/http_bearer_auth.dart';
|
||||
|
||||
part 'api/api_key_api.dart';
|
||||
part 'api/album_api.dart';
|
||||
part 'api/asset_api.dart';
|
||||
part 'api/authentication_api.dart';
|
||||
@ -38,6 +39,10 @@ part 'api/system_config_api.dart';
|
||||
part 'api/tag_api.dart';
|
||||
part 'api/user_api.dart';
|
||||
|
||||
part 'model/api_key_create_dto.dart';
|
||||
part 'model/api_key_create_response_dto.dart';
|
||||
part 'model/api_key_response_dto.dart';
|
||||
part 'model/api_key_update_dto.dart';
|
||||
part 'model/add_assets_dto.dart';
|
||||
part 'model/add_assets_response_dto.dart';
|
||||
part 'model/add_users_dto.dart';
|
||||
|
249
mobile/openapi/lib/api/api_key_api.dart
generated
Normal file
249
mobile/openapi/lib/api/api_key_api.dart
generated
Normal file
@ -0,0 +1,249 @@
|
||||
//
|
||||
// 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 APIKeyApi {
|
||||
APIKeyApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// Performs an HTTP 'POST /api-key' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [APIKeyCreateDto] aPIKeyCreateDto (required):
|
||||
Future<Response> createKeyWithHttpInfo(APIKeyCreateDto aPIKeyCreateDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/api-key';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = aPIKeyCreateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [APIKeyCreateDto] aPIKeyCreateDto (required):
|
||||
Future<APIKeyCreateResponseDto?> createKey(APIKeyCreateDto aPIKeyCreateDto,) async {
|
||||
final response = await createKeyWithHttpInfo(aPIKeyCreateDto,);
|
||||
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), 'APIKeyCreateResponseDto',) as APIKeyCreateResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'DELETE /api-key/{id}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [num] id (required):
|
||||
Future<Response> deleteKeyWithHttpInfo(num id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/api-key/{id}'
|
||||
.replaceAll('{id}', id.toString());
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'DELETE',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [num] id (required):
|
||||
Future<void> deleteKey(num id,) async {
|
||||
final response = await deleteKeyWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /api-key/{id}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [num] id (required):
|
||||
Future<Response> getKeyWithHttpInfo(num id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/api-key/{id}'
|
||||
.replaceAll('{id}', id.toString());
|
||||
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [num] id (required):
|
||||
Future<APIKeyResponseDto?> getKey(num id,) async {
|
||||
final response = await getKeyWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'APIKeyResponseDto',) as APIKeyResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /api-key' operation and returns the [Response].
|
||||
Future<Response> getKeysWithHttpInfo() async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/api-key';
|
||||
|
||||
// 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<List<APIKeyResponseDto>?> getKeys() async {
|
||||
final response = await getKeysWithHttpInfo();
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<APIKeyResponseDto>') as List)
|
||||
.cast<APIKeyResponseDto>()
|
||||
.toList();
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'PUT /api-key/{id}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [num] id (required):
|
||||
///
|
||||
/// * [APIKeyUpdateDto] aPIKeyUpdateDto (required):
|
||||
Future<Response> updateKeyWithHttpInfo(num id, APIKeyUpdateDto aPIKeyUpdateDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/api-key/{id}'
|
||||
.replaceAll('{id}', id.toString());
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = aPIKeyUpdateDto;
|
||||
|
||||
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:
|
||||
///
|
||||
/// * [num] id (required):
|
||||
///
|
||||
/// * [APIKeyUpdateDto] aPIKeyUpdateDto (required):
|
||||
Future<APIKeyResponseDto?> updateKey(num id, APIKeyUpdateDto aPIKeyUpdateDto,) async {
|
||||
final response = await updateKeyWithHttpInfo(id, aPIKeyUpdateDto,);
|
||||
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), 'APIKeyResponseDto',) as APIKeyResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
8
mobile/openapi/lib/api_client.dart
generated
8
mobile/openapi/lib/api_client.dart
generated
@ -192,6 +192,14 @@ class ApiClient {
|
||||
return valueString == 'true' || valueString == '1';
|
||||
case 'DateTime':
|
||||
return value is DateTime ? value : DateTime.tryParse(value);
|
||||
case 'APIKeyCreateDto':
|
||||
return APIKeyCreateDto.fromJson(value);
|
||||
case 'APIKeyCreateResponseDto':
|
||||
return APIKeyCreateResponseDto.fromJson(value);
|
||||
case 'APIKeyResponseDto':
|
||||
return APIKeyResponseDto.fromJson(value);
|
||||
case 'APIKeyUpdateDto':
|
||||
return APIKeyUpdateDto.fromJson(value);
|
||||
case 'AddAssetsDto':
|
||||
return AddAssetsDto.fromJson(value);
|
||||
case 'AddAssetsResponseDto':
|
||||
|
120
mobile/openapi/lib/model/api_key_create_dto.dart
generated
Normal file
120
mobile/openapi/lib/model/api_key_create_dto.dart
generated
Normal file
@ -0,0 +1,120 @@
|
||||
//
|
||||
// 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 APIKeyCreateDto {
|
||||
/// Returns a new [APIKeyCreateDto] instance.
|
||||
APIKeyCreateDto({
|
||||
this.name,
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
String? name;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is APIKeyCreateDto &&
|
||||
other.name == name;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(name == null ? 0 : name!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'APIKeyCreateDto[name=$name]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final _json = <String, dynamic>{};
|
||||
if (name != null) {
|
||||
_json[r'name'] = name;
|
||||
} else {
|
||||
_json[r'name'] = null;
|
||||
}
|
||||
return _json;
|
||||
}
|
||||
|
||||
/// Returns a new [APIKeyCreateDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static APIKeyCreateDto? 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 "APIKeyCreateDto[$key]" is missing from JSON.');
|
||||
assert(json[key] != null, 'Required key "APIKeyCreateDto[$key]" has a null value in JSON.');
|
||||
});
|
||||
return true;
|
||||
}());
|
||||
|
||||
return APIKeyCreateDto(
|
||||
name: mapValueOfType<String>(json, r'name'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<APIKeyCreateDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <APIKeyCreateDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = APIKeyCreateDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, APIKeyCreateDto> mapFromJson(dynamic json) {
|
||||
final map = <String, APIKeyCreateDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = APIKeyCreateDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of APIKeyCreateDto-objects as value to a dart map
|
||||
static Map<String, List<APIKeyCreateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<APIKeyCreateDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = APIKeyCreateDto.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>{
|
||||
};
|
||||
}
|
||||
|
119
mobile/openapi/lib/model/api_key_create_response_dto.dart
generated
Normal file
119
mobile/openapi/lib/model/api_key_create_response_dto.dart
generated
Normal file
@ -0,0 +1,119 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class APIKeyCreateResponseDto {
|
||||
/// Returns a new [APIKeyCreateResponseDto] instance.
|
||||
APIKeyCreateResponseDto({
|
||||
required this.secret,
|
||||
required this.apiKey,
|
||||
});
|
||||
|
||||
String secret;
|
||||
|
||||
APIKeyResponseDto apiKey;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is APIKeyCreateResponseDto &&
|
||||
other.secret == secret &&
|
||||
other.apiKey == apiKey;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(secret.hashCode) +
|
||||
(apiKey.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'APIKeyCreateResponseDto[secret=$secret, apiKey=$apiKey]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final _json = <String, dynamic>{};
|
||||
_json[r'secret'] = secret;
|
||||
_json[r'apiKey'] = apiKey;
|
||||
return _json;
|
||||
}
|
||||
|
||||
/// Returns a new [APIKeyCreateResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static APIKeyCreateResponseDto? 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 "APIKeyCreateResponseDto[$key]" is missing from JSON.');
|
||||
assert(json[key] != null, 'Required key "APIKeyCreateResponseDto[$key]" has a null value in JSON.');
|
||||
});
|
||||
return true;
|
||||
}());
|
||||
|
||||
return APIKeyCreateResponseDto(
|
||||
secret: mapValueOfType<String>(json, r'secret')!,
|
||||
apiKey: APIKeyResponseDto.fromJson(json[r'apiKey'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<APIKeyCreateResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <APIKeyCreateResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = APIKeyCreateResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, APIKeyCreateResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, APIKeyCreateResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = APIKeyCreateResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of APIKeyCreateResponseDto-objects as value to a dart map
|
||||
static Map<String, List<APIKeyCreateResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<APIKeyCreateResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = APIKeyCreateResponseDto.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>{
|
||||
'secret',
|
||||
'apiKey',
|
||||
};
|
||||
}
|
||||
|
137
mobile/openapi/lib/model/api_key_response_dto.dart
generated
Normal file
137
mobile/openapi/lib/model/api_key_response_dto.dart
generated
Normal file
@ -0,0 +1,137 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class APIKeyResponseDto {
|
||||
/// Returns a new [APIKeyResponseDto] instance.
|
||||
APIKeyResponseDto({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
num id;
|
||||
|
||||
String name;
|
||||
|
||||
String createdAt;
|
||||
|
||||
String updatedAt;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is APIKeyResponseDto &&
|
||||
other.id == id &&
|
||||
other.name == name &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(id.hashCode) +
|
||||
(name.hashCode) +
|
||||
(createdAt.hashCode) +
|
||||
(updatedAt.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'APIKeyResponseDto[id=$id, name=$name, createdAt=$createdAt, updatedAt=$updatedAt]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final _json = <String, dynamic>{};
|
||||
_json[r'id'] = id;
|
||||
_json[r'name'] = name;
|
||||
_json[r'createdAt'] = createdAt;
|
||||
_json[r'updatedAt'] = updatedAt;
|
||||
return _json;
|
||||
}
|
||||
|
||||
/// Returns a new [APIKeyResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static APIKeyResponseDto? 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 "APIKeyResponseDto[$key]" is missing from JSON.');
|
||||
assert(json[key] != null, 'Required key "APIKeyResponseDto[$key]" has a null value in JSON.');
|
||||
});
|
||||
return true;
|
||||
}());
|
||||
|
||||
return APIKeyResponseDto(
|
||||
id: json[r'id'] == null
|
||||
? null
|
||||
: num.parse(json[r'id'].toString()),
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
||||
updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<APIKeyResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <APIKeyResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = APIKeyResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, APIKeyResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, APIKeyResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = APIKeyResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of APIKeyResponseDto-objects as value to a dart map
|
||||
static Map<String, List<APIKeyResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<APIKeyResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = APIKeyResponseDto.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>{
|
||||
'id',
|
||||
'name',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
};
|
||||
}
|
||||
|
111
mobile/openapi/lib/model/api_key_update_dto.dart
generated
Normal file
111
mobile/openapi/lib/model/api_key_update_dto.dart
generated
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 APIKeyUpdateDto {
|
||||
/// Returns a new [APIKeyUpdateDto] instance.
|
||||
APIKeyUpdateDto({
|
||||
required this.name,
|
||||
});
|
||||
|
||||
String name;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is APIKeyUpdateDto &&
|
||||
other.name == name;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(name.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'APIKeyUpdateDto[name=$name]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final _json = <String, dynamic>{};
|
||||
_json[r'name'] = name;
|
||||
return _json;
|
||||
}
|
||||
|
||||
/// Returns a new [APIKeyUpdateDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static APIKeyUpdateDto? 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 "APIKeyUpdateDto[$key]" is missing from JSON.');
|
||||
assert(json[key] != null, 'Required key "APIKeyUpdateDto[$key]" has a null value in JSON.');
|
||||
});
|
||||
return true;
|
||||
}());
|
||||
|
||||
return APIKeyUpdateDto(
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<APIKeyUpdateDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <APIKeyUpdateDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = APIKeyUpdateDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, APIKeyUpdateDto> mapFromJson(dynamic json) {
|
||||
final map = <String, APIKeyUpdateDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = APIKeyUpdateDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of APIKeyUpdateDto-objects as value to a dart map
|
||||
static Map<String, List<APIKeyUpdateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<APIKeyUpdateDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = APIKeyUpdateDto.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',
|
||||
};
|
||||
}
|
||||
|
46
mobile/openapi/test/api_key_api_test.dart
generated
Normal file
46
mobile/openapi/test/api_key_api_test.dart
generated
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
||||
/// tests for APIKeyApi
|
||||
void main() {
|
||||
// final instance = APIKeyApi();
|
||||
|
||||
group('tests for APIKeyApi', () {
|
||||
//Future<APIKeyCreateResponseDto> createKey(APIKeyCreateDto aPIKeyCreateDto) async
|
||||
test('test createKey', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future deleteKey(num id) async
|
||||
test('test deleteKey', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<APIKeyResponseDto> getKey(num id) async
|
||||
test('test getKey', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<List<APIKeyResponseDto>> getKeys() async
|
||||
test('test getKeys', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<APIKeyResponseDto> updateKey(num id, APIKeyUpdateDto aPIKeyUpdateDto) async
|
||||
test('test updateKey', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
});
|
||||
}
|
27
mobile/openapi/test/api_key_create_dto_test.dart
generated
Normal file
27
mobile/openapi/test/api_key_create_dto_test.dart
generated
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 APIKeyCreateDto
|
||||
void main() {
|
||||
// final instance = APIKeyCreateDto();
|
||||
|
||||
group('test APIKeyCreateDto', () {
|
||||
// String name
|
||||
test('to test the property `name`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
32
mobile/openapi/test/api_key_create_response_dto_test.dart
generated
Normal file
32
mobile/openapi/test/api_key_create_response_dto_test.dart
generated
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 APIKeyCreateResponseDto
|
||||
void main() {
|
||||
// final instance = APIKeyCreateResponseDto();
|
||||
|
||||
group('test APIKeyCreateResponseDto', () {
|
||||
// String secret
|
||||
test('to test the property `secret`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// APIKeyResponseDto apiKey
|
||||
test('to test the property `apiKey`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
42
mobile/openapi/test/api_key_response_dto_test.dart
generated
Normal file
42
mobile/openapi/test/api_key_response_dto_test.dart
generated
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// 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 APIKeyResponseDto
|
||||
void main() {
|
||||
// final instance = APIKeyResponseDto();
|
||||
|
||||
group('test APIKeyResponseDto', () {
|
||||
// num id
|
||||
test('to test the property `id`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String name
|
||||
test('to test the property `name`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String createdAt
|
||||
test('to test the property `createdAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String updatedAt
|
||||
test('to test the property `updatedAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
27
mobile/openapi/test/api_key_update_dto_test.dart
generated
Normal file
27
mobile/openapi/test/api_key_update_dto_test.dart
generated
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 APIKeyUpdateDto
|
||||
void main() {
|
||||
// final instance = APIKeyUpdateDto();
|
||||
|
||||
group('test APIKeyUpdateDto', () {
|
||||
// String name
|
||||
test('to test the property `name`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
48
server/apps/immich/src/api-v1/api-key/api-key.controller.ts
Normal file
48
server/apps/immich/src/api-v1/api-key/api-key.controller.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, ValidationPipe } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||
import { APIKeyService } from './api-key.service';
|
||||
import { APIKeyCreateDto } from './dto/api-key-create.dto';
|
||||
import { APIKeyUpdateDto } from './dto/api-key-update.dto';
|
||||
import { APIKeyCreateResponseDto } from './repsonse-dto/api-key-create-response.dto';
|
||||
import { APIKeyResponseDto } from './repsonse-dto/api-key-response.dto';
|
||||
|
||||
@ApiTags('API Key')
|
||||
@Controller('api-key')
|
||||
@Authenticated()
|
||||
export class APIKeyController {
|
||||
constructor(private service: APIKeyService) {}
|
||||
|
||||
@Post()
|
||||
createKey(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) dto: APIKeyCreateDto,
|
||||
): Promise<APIKeyCreateResponseDto> {
|
||||
return this.service.create(authUser, dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
getKeys(@GetAuthUser() authUser: AuthUserDto): Promise<APIKeyResponseDto[]> {
|
||||
return this.service.getAll(authUser);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getKey(@GetAuthUser() authUser: AuthUserDto, @Param('id', ParseIntPipe) id: number): Promise<APIKeyResponseDto> {
|
||||
return this.service.getById(authUser, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
updateKey(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body(ValidationPipe) dto: APIKeyUpdateDto,
|
||||
): Promise<APIKeyResponseDto> {
|
||||
return this.service.update(authUser, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
deleteKey(@GetAuthUser() authUser: AuthUserDto, @Param('id', ParseIntPipe) id: number): Promise<void> {
|
||||
return this.service.delete(authUser, id);
|
||||
}
|
||||
}
|
16
server/apps/immich/src/api-v1/api-key/api-key.module.ts
Normal file
16
server/apps/immich/src/api-v1/api-key/api-key.module.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { APIKeyEntity } from '@app/database';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { APIKeyController } from './api-key.controller';
|
||||
import { APIKeyRepository, IKeyRepository } from './api-key.repository';
|
||||
import { APIKeyService } from './api-key.service';
|
||||
|
||||
const KEY_REPOSITORY = { provide: IKeyRepository, useClass: APIKeyRepository };
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([APIKeyEntity])],
|
||||
controllers: [APIKeyController],
|
||||
providers: [APIKeyService, KEY_REPOSITORY],
|
||||
exports: [APIKeyService, KEY_REPOSITORY],
|
||||
})
|
||||
export class APIKeyModule {}
|
59
server/apps/immich/src/api-v1/api-key/api-key.repository.ts
Normal file
59
server/apps/immich/src/api-v1/api-key/api-key.repository.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { APIKeyEntity } from '@app/database';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
export const IKeyRepository = 'IKeyRepository';
|
||||
|
||||
export interface IKeyRepository {
|
||||
create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity>;
|
||||
update(userId: string, id: number, dto: Partial<APIKeyEntity>): Promise<APIKeyEntity>;
|
||||
delete(userId: string, id: number): Promise<void>;
|
||||
/**
|
||||
* Includes the hashed `key` for verification
|
||||
* @param id
|
||||
*/
|
||||
getKey(id: number): Promise<APIKeyEntity | null>;
|
||||
getById(userId: string, id: number): Promise<APIKeyEntity | null>;
|
||||
getByUserId(userId: string): Promise<APIKeyEntity[]>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class APIKeyRepository implements IKeyRepository {
|
||||
constructor(@InjectRepository(APIKeyEntity) private repository: Repository<APIKeyEntity>) {}
|
||||
|
||||
async create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity> {
|
||||
return this.repository.save(dto);
|
||||
}
|
||||
|
||||
async update(userId: string, id: number, dto: Partial<APIKeyEntity>): Promise<APIKeyEntity> {
|
||||
await this.repository.update({ userId, id }, dto);
|
||||
return this.repository.findOneOrFail({ where: { id: dto.id } });
|
||||
}
|
||||
|
||||
async delete(userId: string, id: number): Promise<void> {
|
||||
await this.repository.delete({ userId, id });
|
||||
}
|
||||
|
||||
getKey(id: number): Promise<APIKeyEntity | null> {
|
||||
return this.repository.findOne({
|
||||
select: {
|
||||
id: true,
|
||||
key: true,
|
||||
userId: true,
|
||||
},
|
||||
where: { id },
|
||||
relations: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getById(userId: string, id: number): Promise<APIKeyEntity | null> {
|
||||
return this.repository.findOne({ where: { userId, id } });
|
||||
}
|
||||
|
||||
getByUserId(userId: string): Promise<APIKeyEntity[]> {
|
||||
return this.repository.find({ where: { userId }, order: { createdAt: 'DESC' } });
|
||||
}
|
||||
}
|
74
server/apps/immich/src/api-v1/api-key/api-key.service.ts
Normal file
74
server/apps/immich/src/api-v1/api-key/api-key.service.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { UserEntity } from '@app/database';
|
||||
import { BadRequestException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { compareSync, hash } from 'bcrypt';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { IKeyRepository } from './api-key.repository';
|
||||
import { APIKeyCreateDto } from './dto/api-key-create.dto';
|
||||
import { APIKeyCreateResponseDto } from './repsonse-dto/api-key-create-response.dto';
|
||||
import { APIKeyResponseDto, mapKey } from './repsonse-dto/api-key-response.dto';
|
||||
|
||||
@Injectable()
|
||||
export class APIKeyService {
|
||||
constructor(@Inject(IKeyRepository) private repository: IKeyRepository) {}
|
||||
|
||||
async create(authUser: AuthUserDto, dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||
const key = randomBytes(24).toString('base64').replace(/\W/g, '');
|
||||
const entity = await this.repository.create({
|
||||
key: await hash(key, 10),
|
||||
name: dto.name || 'API Key',
|
||||
userId: authUser.id,
|
||||
});
|
||||
|
||||
const secret = Buffer.from(`${entity.id}:${key}`, 'utf8').toString('base64');
|
||||
|
||||
return { secret, apiKey: mapKey(entity) };
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, id: number, dto: APIKeyCreateDto): Promise<APIKeyResponseDto> {
|
||||
const exists = await this.repository.getById(authUser.id, id);
|
||||
if (!exists) {
|
||||
throw new BadRequestException('API Key not found');
|
||||
}
|
||||
|
||||
return this.repository.update(authUser.id, id, {
|
||||
name: dto.name,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(authUser: AuthUserDto, id: number): Promise<void> {
|
||||
const exists = await this.repository.getById(authUser.id, id);
|
||||
if (!exists) {
|
||||
throw new BadRequestException('API Key not found');
|
||||
}
|
||||
|
||||
await this.repository.delete(authUser.id, id);
|
||||
}
|
||||
|
||||
async getById(authUser: AuthUserDto, id: number): Promise<APIKeyResponseDto> {
|
||||
const key = await this.repository.getById(authUser.id, id);
|
||||
if (!key) {
|
||||
throw new BadRequestException('API Key not found');
|
||||
}
|
||||
return mapKey(key);
|
||||
}
|
||||
|
||||
async getAll(authUser: AuthUserDto): Promise<APIKeyResponseDto[]> {
|
||||
const keys = await this.repository.getByUserId(authUser.id);
|
||||
return keys.map(mapKey);
|
||||
}
|
||||
|
||||
async validate(token: string): Promise<UserEntity> {
|
||||
const [_id, key] = Buffer.from(token, 'base64').toString('utf8').split(':');
|
||||
const id = Number(_id);
|
||||
|
||||
if (id && key) {
|
||||
const entity = await this.repository.getKey(id);
|
||||
if (entity?.user && entity?.key && compareSync(key, entity.key)) {
|
||||
return entity.user as UserEntity;
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnauthorizedException('Invalid API Key');
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class APIKeyCreateDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
name?: string;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class APIKeyUpdateDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name!: string;
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { APIKeyResponseDto } from './api-key-response.dto';
|
||||
|
||||
export class APIKeyCreateResponseDto {
|
||||
secret!: string;
|
||||
apiKey!: APIKeyResponseDto;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { APIKeyEntity } from '@app/database';
|
||||
|
||||
export class APIKeyResponseDto {
|
||||
id!: number;
|
||||
name!: string;
|
||||
createdAt!: string;
|
||||
updatedAt!: string;
|
||||
}
|
||||
|
||||
export function mapKey(entity: APIKeyEntity): APIKeyResponseDto {
|
||||
return {
|
||||
id: entity.id,
|
||||
name: entity.name,
|
||||
createdAt: entity.createdAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
};
|
||||
}
|
@ -19,6 +19,7 @@ import { JobModule } from './api-v1/job/job.module';
|
||||
import { SystemConfigModule } from './api-v1/system-config/system-config.module';
|
||||
import { OAuthModule } from './api-v1/oauth/oauth.module';
|
||||
import { TagModule } from './api-v1/tag/tag.module';
|
||||
import { APIKeyModule } from './api-v1/api-key/api-key.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -27,6 +28,8 @@ import { TagModule } from './api-v1/tag/tag.module';
|
||||
DatabaseModule,
|
||||
UserModule,
|
||||
|
||||
APIKeyModule,
|
||||
|
||||
AssetModule,
|
||||
|
||||
AuthModule,
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { AdminRolesGuard } from '../middlewares/admin-role-guard.middleware';
|
||||
import { JwtAuthGuard } from '../modules/immich-jwt/guards/jwt-auth.guard';
|
||||
import { AuthGuard } from '../modules/immich-jwt/guards/auth.guard';
|
||||
|
||||
interface AuthenticatedOptions {
|
||||
admin?: boolean;
|
||||
}
|
||||
|
||||
export const Authenticated = (options?: AuthenticatedOptions) => {
|
||||
const guards: Parameters<typeof UseGuards> = [JwtAuthGuard];
|
||||
const guards: Parameters<typeof UseGuards> = [AuthGuard];
|
||||
options = options || {};
|
||||
if (options.admin) {
|
||||
guards.push(AdminRolesGuard);
|
||||
|
@ -1,12 +1,17 @@
|
||||
import { CanActivate, ExecutionContext, Injectable, Logger } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { UserResponseDto } from '../api-v1/user/response-dto/user-response.dto';
|
||||
|
||||
interface UserRequest extends Request {
|
||||
user: UserResponseDto;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AdminRolesGuard implements CanActivate {
|
||||
logger = new Logger(AdminRolesGuard.name);
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
const request = context.switchToHttp().getRequest<UserRequest>();
|
||||
const isAdmin = request.user?.isAdmin || false;
|
||||
if (!isAdmin) {
|
||||
this.logger.log(`Denied access to admin only route: ${request.path}`);
|
||||
|
@ -0,0 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthGuard as PassportAuthGuard } from '@nestjs/passport';
|
||||
import { API_KEY_STRATEGY } from '../strategies/api-key.strategy';
|
||||
import { JWT_STRATEGY } from '../strategies/jwt.strategy';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard extends PassportAuthGuard([JWT_STRATEGY, API_KEY_STRATEGY]) {}
|
@ -1,5 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
@ -5,10 +5,12 @@ import { jwtConfig } from '../../config/jwt.config';
|
||||
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UserEntity } from '@app/database';
|
||||
import { APIKeyModule } from '../../api-v1/api-key/api-key.module';
|
||||
import { APIKeyStrategy } from './strategies/api-key.strategy';
|
||||
|
||||
@Module({
|
||||
imports: [JwtModule.register(jwtConfig), TypeOrmModule.forFeature([UserEntity])],
|
||||
providers: [ImmichJwtService, JwtStrategy],
|
||||
imports: [JwtModule.register(jwtConfig), TypeOrmModule.forFeature([UserEntity]), APIKeyModule],
|
||||
providers: [ImmichJwtService, JwtStrategy, APIKeyStrategy],
|
||||
exports: [ImmichJwtService],
|
||||
})
|
||||
export class ImmichJwtModule {}
|
||||
|
@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { IStrategyOptions, Strategy } from 'passport-http-header-strategy';
|
||||
import { APIKeyService } from '../../../api-v1/api-key/api-key.service';
|
||||
|
||||
export const API_KEY_STRATEGY = 'api-key';
|
||||
|
||||
const options: IStrategyOptions = {
|
||||
header: 'x-api-key',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class APIKeyStrategy extends PassportStrategy(Strategy, API_KEY_STRATEGY) {
|
||||
constructor(private apiKeyService: APIKeyService) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
async validate(token: string) {
|
||||
return this.apiKeyService.validate(token);
|
||||
}
|
||||
}
|
@ -1,15 +1,17 @@
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { ExtractJwt, Strategy, StrategyOptions } from 'passport-jwt';
|
||||
import { Repository } from 'typeorm';
|
||||
import { JwtPayloadDto } from '../../../api-v1/auth/dto/jwt-payload.dto';
|
||||
import { UserEntity } from '@app/database';
|
||||
import { jwtSecret } from '../../../constants/jwt.constant';
|
||||
import { ImmichJwtService } from '../immich-jwt.service';
|
||||
|
||||
export const JWT_STRATEGY = 'jwt';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
export class JwtStrategy extends PassportStrategy(Strategy, JWT_STRATEGY) {
|
||||
constructor(
|
||||
@InjectRepository(UserEntity)
|
||||
private usersRepository: Repository<UserEntity>,
|
||||
@ -22,7 +24,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
]),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: jwtSecret,
|
||||
});
|
||||
} as StrategyOptions);
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayloadDto) {
|
||||
|
@ -3,7 +3,7 @@ import { TestingModuleBuilder } from '@nestjs/testing';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { IUserRepository } from '../src/api-v1/user/user-repository';
|
||||
import { AuthUserDto } from '../src/decorators/auth-user.decorator';
|
||||
import { JwtAuthGuard } from '../src/modules/immich-jwt/guards/jwt-auth.guard';
|
||||
import { AuthGuard } from '../src/modules/immich-jwt/guards/auth.guard';
|
||||
|
||||
type CustomAuthCallback = () => AuthUserDto;
|
||||
|
||||
@ -49,5 +49,5 @@ export function authCustom(builder: TestingModuleBuilder, callback: CustomAuthCa
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return builder.overrideGuard(JwtAuthGuard).useValue(canActivate);
|
||||
return builder.overrideGuard(AuthGuard).useValue(canActivate);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { immichAppConfig, immichBullAsyncConfig } from '@app/common/config';
|
||||
import { DatabaseModule, AssetEntity, ExifEntity, SmartInfoEntity, UserEntity } from '@app/database';
|
||||
import { DatabaseModule, AssetEntity, ExifEntity, SmartInfoEntity, UserEntity, APIKeyEntity } from '@app/database';
|
||||
import { StorageModule } from '@app/storage';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { Module } from '@nestjs/common';
|
||||
@ -23,7 +23,7 @@ import { immichSharedQueues } from '@app/job/constants/bull-queue-registration.c
|
||||
ConfigModule.forRoot(immichAppConfig),
|
||||
DatabaseModule,
|
||||
ImmichConfigModule,
|
||||
TypeOrmModule.forFeature([UserEntity, ExifEntity, AssetEntity, SmartInfoEntity]),
|
||||
TypeOrmModule.forFeature([UserEntity, ExifEntity, AssetEntity, SmartInfoEntity, APIKeyEntity]),
|
||||
StorageModule,
|
||||
BullModule.forRootAsync(immichBullAsyncConfig),
|
||||
BullModule.registerQueue(...immichSharedQueues),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { APP_UPLOAD_LOCATION, userUtils } from '@app/common';
|
||||
import { AssetEntity, UserEntity } from '@app/database';
|
||||
import { APIKeyEntity, AssetEntity, UserEntity } from '@app/database';
|
||||
import { QueueNameEnum, userDeletionProcessorName } from '@app/job';
|
||||
import { IUserDeletionJob } from '@app/job/interfaces/user-deletion.interface';
|
||||
import { Process, Processor } from '@nestjs/bull';
|
||||
@ -17,6 +17,9 @@ export class UserDeletionProcessor {
|
||||
|
||||
@InjectRepository(AssetEntity)
|
||||
private assetRepository: Repository<AssetEntity>,
|
||||
|
||||
@InjectRepository(APIKeyEntity)
|
||||
private apiKeyRepository: Repository<APIKeyEntity>,
|
||||
) {}
|
||||
|
||||
@Process(userDeletionProcessorName)
|
||||
@ -27,6 +30,7 @@ export class UserDeletionProcessor {
|
||||
const basePath = APP_UPLOAD_LOCATION;
|
||||
const userAssetDir = join(basePath, user.id);
|
||||
fs.rmSync(userAssetDir, { recursive: true, force: true });
|
||||
await this.apiKeyRepository.delete({ userId: user.id });
|
||||
await this.assetRepository.delete({ userId: user.id });
|
||||
await this.userRepository.remove(user);
|
||||
}
|
||||
|
@ -331,6 +331,148 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api-key": {
|
||||
"post": {
|
||||
"operationId": "createKey",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIKeyCreateDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIKeyCreateResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"API Key"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "getKeys",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/APIKeyResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"API Key"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api-key/{id}": {
|
||||
"get": {
|
||||
"operationId": "getKey",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIKeyResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"API Key"
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "updateKey",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIKeyUpdateDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/APIKeyResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"API Key"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "deleteKey",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"API Key"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/asset/upload": {
|
||||
"post": {
|
||||
"operationId": "uploadFile",
|
||||
@ -2467,6 +2609,63 @@
|
||||
"profileImagePath"
|
||||
]
|
||||
},
|
||||
"APIKeyCreateDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"APIKeyResponseDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"createdAt",
|
||||
"updatedAt"
|
||||
]
|
||||
},
|
||||
"APIKeyCreateResponseDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secret": {
|
||||
"type": "string"
|
||||
},
|
||||
"apiKey": {
|
||||
"$ref": "#/components/schemas/APIKeyResponseDto"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"secret",
|
||||
"apiKey"
|
||||
]
|
||||
},
|
||||
"APIKeyUpdateDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"AssetFileUploadDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
26
server/libs/database/src/entities/api-key.entity.ts
Normal file
26
server/libs/database/src/entities/api-key.entity.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Entity('api_keys')
|
||||
export class APIKeyEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column()
|
||||
name!: string;
|
||||
|
||||
@Column({ select: false })
|
||||
key?: string;
|
||||
|
||||
@Column()
|
||||
userId!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity)
|
||||
user?: UserEntity;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: string;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: string;
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
export * from './album.entity';
|
||||
export * from './api-key.entity';
|
||||
export * from './asset-album.entity';
|
||||
export * from './asset.entity';
|
||||
export * from './device-info.entity';
|
||||
|
@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddAPIKeys1672502270115 implements MigrationInterface {
|
||||
name = 'AddAPIKeys1672502270115'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "api_keys" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "key" character varying NOT NULL, "userId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_5c8a79801b44bd27b79228e1dad" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" ADD CONSTRAINT "FK_6c2e267ae764a9413b863a29342" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" DROP CONSTRAINT "FK_6c2e267ae764a9413b863a29342"`);
|
||||
await queryRunner.query(`DROP TABLE "api_keys"`);
|
||||
}
|
||||
|
||||
}
|
30
server/package-lock.json
generated
30
server/package-lock.json
generated
@ -47,6 +47,7 @@
|
||||
"nest-commander": "^3.3.0",
|
||||
"openid-client": "^5.2.1",
|
||||
"passport": "^0.6.0",
|
||||
"passport-http-header-strategy": "^1.1.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"pg": "^8.7.1",
|
||||
"redis": "^3.1.2",
|
||||
@ -2377,9 +2378,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/inquirer": {
|
||||
"version": "8.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.4.tgz",
|
||||
"integrity": "sha512-Pxxx3i3AyK7vKAj3LRM/vF7ETcHKiLJ/u5CnNgbz/eYj/vB3xGAYtRxI5IKtq0hpe5iFHD22BKV3n6WHUu0k4Q==",
|
||||
"version": "8.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.5.tgz",
|
||||
"integrity": "sha512-QXlzybid60YtAwfgG3cpykptRYUx2KomzNutMlWsQC64J/WG/gQSl+P4w7A21sGN0VIxRVava4rgnT7FQmFCdg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/through": "*"
|
||||
@ -8618,6 +8619,14 @@
|
||||
"url": "https://github.com/sponsors/jaredhanson"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-http-header-strategy": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-http-header-strategy/-/passport-http-header-strategy-1.1.0.tgz",
|
||||
"integrity": "sha512-Gn60rR55UE1wXbVhnnfG3yyeRSz5pzz3n6rppxa6xiOo4gGPh/onuw29HuGjpk9DSzXRFkJn95+8RT11kXHeWA==",
|
||||
"dependencies": {
|
||||
"passport-strategy": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-jwt": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz",
|
||||
@ -9848,6 +9857,7 @@
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||
"deprecated": "Please use @jridgewell/sourcemap-codec instead",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/spawn-command": {
|
||||
@ -13079,9 +13089,9 @@
|
||||
}
|
||||
},
|
||||
"@types/inquirer": {
|
||||
"version": "8.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.4.tgz",
|
||||
"integrity": "sha512-Pxxx3i3AyK7vKAj3LRM/vF7ETcHKiLJ/u5CnNgbz/eYj/vB3xGAYtRxI5IKtq0hpe5iFHD22BKV3n6WHUu0k4Q==",
|
||||
"version": "8.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.5.tgz",
|
||||
"integrity": "sha512-QXlzybid60YtAwfgG3cpykptRYUx2KomzNutMlWsQC64J/WG/gQSl+P4w7A21sGN0VIxRVava4rgnT7FQmFCdg==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@types/through": "*"
|
||||
@ -17917,6 +17927,14 @@
|
||||
"utils-merge": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"passport-http-header-strategy": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-http-header-strategy/-/passport-http-header-strategy-1.1.0.tgz",
|
||||
"integrity": "sha512-Gn60rR55UE1wXbVhnnfG3yyeRSz5pzz3n6rppxa6xiOo4gGPh/onuw29HuGjpk9DSzXRFkJn95+8RT11kXHeWA==",
|
||||
"requires": {
|
||||
"passport-strategy": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"passport-jwt": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz",
|
||||
|
@ -70,6 +70,7 @@
|
||||
"nest-commander": "^3.3.0",
|
||||
"openid-client": "^5.2.1",
|
||||
"passport": "^0.6.0",
|
||||
"passport-http-header-strategy": "^1.1.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"pg": "^8.7.1",
|
||||
"redis": "^3.1.2",
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { env } from '$env/dynamic/public';
|
||||
import {
|
||||
AlbumApi,
|
||||
APIKeyApi,
|
||||
AssetApi,
|
||||
AuthenticationApi,
|
||||
Configuration,
|
||||
@ -21,6 +22,7 @@ class ImmichApi {
|
||||
public deviceInfoApi: DeviceInfoApi;
|
||||
public serverInfoApi: ServerInfoApi;
|
||||
public jobApi: JobApi;
|
||||
public keyApi: APIKeyApi;
|
||||
public systemConfigApi: SystemConfigApi;
|
||||
|
||||
private config = new Configuration({ basePath: '/api' });
|
||||
@ -34,6 +36,7 @@ class ImmichApi {
|
||||
this.deviceInfoApi = new DeviceInfoApi(this.config);
|
||||
this.serverInfoApi = new ServerInfoApi(this.config);
|
||||
this.jobApi = new JobApi(this.config);
|
||||
this.keyApi = new APIKeyApi(this.config);
|
||||
this.systemConfigApi = new SystemConfigApi(this.config);
|
||||
}
|
||||
|
||||
|
433
web/src/api/open-api/api.ts
generated
433
web/src/api/open-api/api.ts
generated
@ -21,6 +21,82 @@ import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObj
|
||||
// @ts-ignore
|
||||
import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base';
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface APIKeyCreateDto
|
||||
*/
|
||||
export interface APIKeyCreateDto {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof APIKeyCreateDto
|
||||
*/
|
||||
'name'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface APIKeyCreateResponseDto
|
||||
*/
|
||||
export interface APIKeyCreateResponseDto {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof APIKeyCreateResponseDto
|
||||
*/
|
||||
'secret': string;
|
||||
/**
|
||||
*
|
||||
* @type {APIKeyResponseDto}
|
||||
* @memberof APIKeyCreateResponseDto
|
||||
*/
|
||||
'apiKey': APIKeyResponseDto;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface APIKeyResponseDto
|
||||
*/
|
||||
export interface APIKeyResponseDto {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof APIKeyResponseDto
|
||||
*/
|
||||
'id': number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof APIKeyResponseDto
|
||||
*/
|
||||
'name': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof APIKeyResponseDto
|
||||
*/
|
||||
'createdAt': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof APIKeyResponseDto
|
||||
*/
|
||||
'updatedAt': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface APIKeyUpdateDto
|
||||
*/
|
||||
export interface APIKeyUpdateDto {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof APIKeyUpdateDto
|
||||
*/
|
||||
'name': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -1990,6 +2066,363 @@ export interface ValidateAccessTokenResponseDto {
|
||||
'authStatus': boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* APIKeyApi - axios parameter creator
|
||||
* @export
|
||||
*/
|
||||
export const APIKeyApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @param {APIKeyCreateDto} aPIKeyCreateDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createKey: async (aPIKeyCreateDto: APIKeyCreateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'aPIKeyCreateDto' is not null or undefined
|
||||
assertParamExists('createKey', 'aPIKeyCreateDto', aPIKeyCreateDto)
|
||||
const localVarPath = `/api-key`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(aPIKeyCreateDto, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteKey: async (id: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('deleteKey', 'id', id)
|
||||
const localVarPath = `/api-key/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getKey: async (id: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('getKey', 'id', id)
|
||||
const localVarPath = `/api-key/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getKeys: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api-key`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {APIKeyUpdateDto} aPIKeyUpdateDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateKey: async (id: number, aPIKeyUpdateDto: APIKeyUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('updateKey', 'id', id)
|
||||
// verify required parameter 'aPIKeyUpdateDto' is not null or undefined
|
||||
assertParamExists('updateKey', 'aPIKeyUpdateDto', aPIKeyUpdateDto)
|
||||
const localVarPath = `/api-key/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(aPIKeyUpdateDto, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* APIKeyApi - functional programming interface
|
||||
* @export
|
||||
*/
|
||||
export const APIKeyApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = APIKeyApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @param {APIKeyCreateDto} aPIKeyCreateDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async createKey(aPIKeyCreateDto: APIKeyCreateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<APIKeyCreateResponseDto>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.createKey(aPIKeyCreateDto, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async deleteKey(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteKey(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getKey(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<APIKeyResponseDto>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getKey(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getKeys(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<APIKeyResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getKeys(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {APIKeyUpdateDto} aPIKeyUpdateDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async updateKey(id: number, aPIKeyUpdateDto: APIKeyUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<APIKeyResponseDto>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.updateKey(id, aPIKeyUpdateDto, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* APIKeyApi - factory interface
|
||||
* @export
|
||||
*/
|
||||
export const APIKeyApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||
const localVarFp = APIKeyApiFp(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @param {APIKeyCreateDto} aPIKeyCreateDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createKey(aPIKeyCreateDto: APIKeyCreateDto, options?: any): AxiosPromise<APIKeyCreateResponseDto> {
|
||||
return localVarFp.createKey(aPIKeyCreateDto, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteKey(id: number, options?: any): AxiosPromise<void> {
|
||||
return localVarFp.deleteKey(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getKey(id: number, options?: any): AxiosPromise<APIKeyResponseDto> {
|
||||
return localVarFp.getKey(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getKeys(options?: any): AxiosPromise<Array<APIKeyResponseDto>> {
|
||||
return localVarFp.getKeys(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {APIKeyUpdateDto} aPIKeyUpdateDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateKey(id: number, aPIKeyUpdateDto: APIKeyUpdateDto, options?: any): AxiosPromise<APIKeyResponseDto> {
|
||||
return localVarFp.updateKey(id, aPIKeyUpdateDto, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* APIKeyApi - object-oriented interface
|
||||
* @export
|
||||
* @class APIKeyApi
|
||||
* @extends {BaseAPI}
|
||||
*/
|
||||
export class APIKeyApi extends BaseAPI {
|
||||
/**
|
||||
*
|
||||
* @param {APIKeyCreateDto} aPIKeyCreateDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof APIKeyApi
|
||||
*/
|
||||
public createKey(aPIKeyCreateDto: APIKeyCreateDto, options?: AxiosRequestConfig) {
|
||||
return APIKeyApiFp(this.configuration).createKey(aPIKeyCreateDto, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof APIKeyApi
|
||||
*/
|
||||
public deleteKey(id: number, options?: AxiosRequestConfig) {
|
||||
return APIKeyApiFp(this.configuration).deleteKey(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof APIKeyApi
|
||||
*/
|
||||
public getKey(id: number, options?: AxiosRequestConfig) {
|
||||
return APIKeyApiFp(this.configuration).getKey(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof APIKeyApi
|
||||
*/
|
||||
public getKeys(options?: AxiosRequestConfig) {
|
||||
return APIKeyApiFp(this.configuration).getKeys(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {APIKeyUpdateDto} aPIKeyUpdateDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof APIKeyApi
|
||||
*/
|
||||
public updateKey(id: number, aPIKeyUpdateDto: APIKeyUpdateDto, options?: AxiosRequestConfig) {
|
||||
return APIKeyApiFp(this.configuration).updateKey(id, aPIKeyUpdateDto, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AlbumApi - axios parameter creator
|
||||
* @export
|
||||
|
57
web/src/lib/components/forms/api-key-form.svelte
Normal file
57
web/src/lib/components/forms/api-key-form.svelte
Normal file
@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import { APIKeyResponseDto } from '@api';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import KeyVariant from 'svelte-material-icons/KeyVariant.svelte';
|
||||
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
||||
|
||||
export let apiKey: Partial<APIKeyResponseDto>;
|
||||
export let title = 'API Key';
|
||||
export let cancelText = 'Cancel';
|
||||
export let submitText = 'Save';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const handleCancel = () => dispatch('cancel');
|
||||
const handleSubmit = () => dispatch('submit', { ...apiKey, name: apiKey.name });
|
||||
</script>
|
||||
|
||||
<FullScreenModal on:clickOutside={() => handleCancel()}>
|
||||
<div
|
||||
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] rounded-3xl py-8 dark:text-immich-dark-fg"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
|
||||
>
|
||||
<KeyVariant size="4em" />
|
||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
||||
{title}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off">
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="email">Name</label>
|
||||
<input
|
||||
class="immich-form-input"
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
bind:value={apiKey.name}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full px-4 gap-4 mt-8">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => handleCancel()}
|
||||
class="flex-1 transition-colors bg-gray-500 dark:bg-gray-200 hover:bg-gray-500/75 dark:hover:bg-gray-200/80 px-6 py-3 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium"
|
||||
>{cancelText}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
|
||||
>{submitText}</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</FullScreenModal>
|
69
web/src/lib/components/forms/api-key-secret.svelte
Normal file
69
web/src/lib/components/forms/api-key-secret.svelte
Normal file
@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import KeyVariant from 'svelte-material-icons/KeyVariant.svelte';
|
||||
import { handleError } from '../../utils/handle-error';
|
||||
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
||||
import {
|
||||
notificationController,
|
||||
NotificationType
|
||||
} from '../shared-components/notification/notification';
|
||||
|
||||
export let secret = '';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const handleDone = () => dispatch('done');
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(secret);
|
||||
notificationController.show({
|
||||
message: 'Copied to clipboard!',
|
||||
type: NotificationType.Info
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to copy to clipboard');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<FullScreenModal>
|
||||
<div
|
||||
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] rounded-3xl py-8 dark:text-immich-dark-fg"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
|
||||
>
|
||||
<KeyVariant size="4em" />
|
||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
||||
API Key
|
||||
</h1>
|
||||
|
||||
<p class="text-sm dark:text-immich-dark-fg">
|
||||
This value will only be shown once. Please be sure to copy it before closing the window.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<!-- <label class="immich-form-label" for="email">API Key</label> -->
|
||||
<textarea
|
||||
class="immich-form-input"
|
||||
id="secret"
|
||||
name="secret"
|
||||
readonly={true}
|
||||
value={secret}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full px-4 gap-4 mt-8">
|
||||
<button
|
||||
on:click={() => handleCopy()}
|
||||
class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
|
||||
>Copy to Clipboard</button
|
||||
>
|
||||
<button
|
||||
on:click={() => handleDone()}
|
||||
class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
|
||||
>Done</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</FullScreenModal>
|
@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import FullScreenModal from './full-screen-modal.svelte';
|
||||
|
||||
export let title = 'Confirm Delete';
|
||||
export let prompt = 'Are you sure you want to delete this item?';
|
||||
export let confirmText = 'Confirm';
|
||||
export let cancelText = 'Cancel';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const handleCancel = () => dispatch('cancel');
|
||||
const handleConfirm = () => dispatch('confirm');
|
||||
</script>
|
||||
|
||||
<FullScreenModal on:clickOutside={() => handleCancel()}>
|
||||
<div
|
||||
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] rounded-3xl py-8 dark:text-immich-dark-fg"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col place-items-center place-content-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
|
||||
>
|
||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
||||
{title}
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<p class="ml-4 text-md py-5 text-center">{prompt}</p>
|
||||
|
||||
<div class="flex w-full px-4 gap-4 mt-4">
|
||||
<button
|
||||
on:click={() => handleCancel()}
|
||||
class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
|
||||
>
|
||||
{cancelText}
|
||||
</button>
|
||||
<button
|
||||
on:click={() => handleConfirm()}
|
||||
class="flex-1 transition-colors bg-red-500 hover:bg-red-400 px-6 py-3 text-white rounded-full w-full font-medium"
|
||||
>
|
||||
{confirmText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FullScreenModal>
|
@ -0,0 +1,180 @@
|
||||
<script lang="ts">
|
||||
import { api, APIKeyResponseDto } from '@api';
|
||||
import { onMount } from 'svelte';
|
||||
import PencilOutline from 'svelte-material-icons/PencilOutline.svelte';
|
||||
import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { handleError } from '../../utils/handle-error';
|
||||
import APIKeyForm from '../forms/api-key-form.svelte';
|
||||
import APIKeySecret from '../forms/api-key-secret.svelte';
|
||||
import DeleteConfirmDialogue from '../shared-components/delete-confirm-dialogue.svelte';
|
||||
import {
|
||||
notificationController,
|
||||
NotificationType
|
||||
} from '../shared-components/notification/notification';
|
||||
|
||||
let keys: APIKeyResponseDto[] = [];
|
||||
|
||||
let newKey: Partial<APIKeyResponseDto> | null = null;
|
||||
let editKey: APIKeyResponseDto | null = null;
|
||||
let deleteKey: APIKeyResponseDto | null = null;
|
||||
let secret = '';
|
||||
|
||||
const locale = navigator.language;
|
||||
const format: Intl.DateTimeFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
refreshKeys();
|
||||
});
|
||||
|
||||
async function refreshKeys() {
|
||||
const { data } = await api.keyApi.getKeys();
|
||||
keys = data;
|
||||
}
|
||||
|
||||
const handleCreate = async (event: CustomEvent<APIKeyResponseDto>) => {
|
||||
try {
|
||||
const dto = event.detail;
|
||||
const { data } = await api.keyApi.createKey(dto);
|
||||
secret = data.secret;
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to create a new API Key');
|
||||
} finally {
|
||||
await refreshKeys();
|
||||
newKey = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = async (event: CustomEvent<APIKeyResponseDto>) => {
|
||||
if (!editKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dto = event.detail;
|
||||
|
||||
try {
|
||||
await api.keyApi.updateKey(editKey.id, { name: dto.name });
|
||||
notificationController.show({
|
||||
message: `Saved API Key`,
|
||||
type: NotificationType.Info
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to save API Key');
|
||||
} finally {
|
||||
await refreshKeys();
|
||||
editKey = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!deleteKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.keyApi.deleteKey(deleteKey.id);
|
||||
notificationController.show({
|
||||
message: `Removed API Key: ${deleteKey.name}`,
|
||||
type: NotificationType.Info
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to remove API Key');
|
||||
} finally {
|
||||
await refreshKeys();
|
||||
deleteKey = null;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if newKey}
|
||||
<APIKeyForm
|
||||
title="New API Key"
|
||||
submitText="Create"
|
||||
apiKey={newKey}
|
||||
on:submit={handleCreate}
|
||||
on:cancel={() => (newKey = null)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if secret}
|
||||
<APIKeySecret {secret} on:done={() => (secret = '')} />
|
||||
{/if}
|
||||
|
||||
{#if editKey}
|
||||
<APIKeyForm
|
||||
submitText="Save"
|
||||
apiKey={editKey}
|
||||
on:submit={handleUpdate}
|
||||
on:cancel={() => (editKey = null)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if deleteKey}
|
||||
<DeleteConfirmDialogue
|
||||
prompt="Are you sure you want to delete this API Key?"
|
||||
on:confirm={() => handleDelete()}
|
||||
on:cancel={() => (deleteKey = null)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<section class="my-4">
|
||||
<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
|
||||
<div class="flex justify-end mb-2">
|
||||
<button
|
||||
on:click={() => (newKey = { name: 'API Key' })}
|
||||
class="text-sm bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 px-4 py-2 text-white dark:text-immich-dark-gray rounded-full shadow-md font-medium disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>New API Key
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if keys.length > 0}
|
||||
<table class="text-left w-full">
|
||||
<thead
|
||||
class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full 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/3 font-medium text-sm">Name</th>
|
||||
<th class="text-center w-1/3 font-medium text-sm">Created</th>
|
||||
<th class="text-center w-1/3 font-medium text-sm">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="overflow-y-auto rounded-md w-full block border dark:border-immich-dark-gray">
|
||||
{#each keys as key, i}
|
||||
{#key key.id}
|
||||
<tr
|
||||
class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${
|
||||
i % 2 == 0
|
||||
? 'bg-immich-gray dark:bg-immich-dark-gray/75'
|
||||
: 'bg-immich-bg dark:bg-immich-dark-gray/50'
|
||||
}`}
|
||||
>
|
||||
<td class="text-sm px-4 w-1/3 text-ellipsis">{key.name}</td>
|
||||
<td class="text-sm px-4 w-1/3 text-ellipsis"
|
||||
>{new Date(key.createdAt).toLocaleDateString(locale, format)}
|
||||
</td>
|
||||
<td class="text-sm px-4 w-1/3 text-ellipsis">
|
||||
<button
|
||||
on:click={() => (editKey = key)}
|
||||
class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
|
||||
>
|
||||
<PencilOutline size="16" />
|
||||
</button>
|
||||
<button
|
||||
on:click={() => (deleteKey = key)}
|
||||
class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
|
||||
>
|
||||
<TrashCanOutline size="16" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/key}
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
@ -5,6 +5,7 @@
|
||||
import SettingAccordion from '../admin-page/settings/setting-accordion.svelte';
|
||||
import ChangePasswordSettings from './change-password-settings.svelte';
|
||||
import OAuthSettings from './oauth-settings.svelte';
|
||||
import UserAPIKeyList from './user-api-key-list.svelte';
|
||||
import UserProfileSettings from './user-profile-settings.svelte';
|
||||
|
||||
export let user: UserResponseDto;
|
||||
@ -32,6 +33,10 @@
|
||||
<ChangePasswordSettings />
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion title="API Keys" subtitle="View and manage your API keys">
|
||||
<UserAPIKeyList />
|
||||
</SettingAccordion>
|
||||
|
||||
{#if oauthEnabled}
|
||||
<SettingAccordion
|
||||
title="OAuth"
|
||||
|
Loading…
Reference in New Issue
Block a user