Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6538e599dd | ||
|
|
789e3e3924 | ||
|
|
3d505e425d | ||
|
|
e7122d7a72 | ||
|
|
94d0705607 | ||
|
|
caba462703 | ||
|
|
ffe397247e | ||
|
|
e7ad622c02 | ||
|
|
bca4626708 | ||
|
|
7f0ad8e2d2 | ||
|
|
6c6c5ef651 |
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 81,
|
||||
"android.injected.version.name" => "1.58.0",
|
||||
"android.injected.version.code" => 82,
|
||||
"android.injected.version.name" => "1.59.0",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
|
||||
@@ -19,7 +19,7 @@ platform :ios do
|
||||
desc "iOS Beta"
|
||||
lane :beta do
|
||||
increment_version_number(
|
||||
version_number: "1.58.0"
|
||||
version_number: "1.59.0"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -219,8 +219,6 @@ class BackupService {
|
||||
|
||||
if (file != null) {
|
||||
String originalFileName = await entity.titleAsync;
|
||||
String fileNameWithoutPath =
|
||||
originalFileName.toString().split(".")[0];
|
||||
var fileExtension = p.extension(file.path);
|
||||
var mimeType = FileHelper.getMimeType(file.path);
|
||||
var fileStream = file.openRead();
|
||||
@@ -228,7 +226,7 @@ class BackupService {
|
||||
"assetData",
|
||||
fileStream,
|
||||
file.lengthSync(),
|
||||
filename: fileNameWithoutPath,
|
||||
filename: originalFileName,
|
||||
contentType: MediaType(
|
||||
mimeType["type"],
|
||||
mimeType["subType"],
|
||||
@@ -334,14 +332,13 @@ class BackupService {
|
||||
var motionFile = File(validPath);
|
||||
var fileStream = motionFile.openRead();
|
||||
String originalFileName = await entity.titleAsync;
|
||||
String fileNameWithoutPath = originalFileName.toString().split(".")[0];
|
||||
var mimeType = FileHelper.getMimeType(validPath);
|
||||
|
||||
return http.MultipartFile(
|
||||
"livePhotoData",
|
||||
fileStream,
|
||||
motionFile.lengthSync(),
|
||||
filename: fileNameWithoutPath,
|
||||
filename: originalFileName,
|
||||
contentType: MediaType(
|
||||
mimeType["type"],
|
||||
mimeType["subType"],
|
||||
|
||||
@@ -128,8 +128,8 @@ class Album {
|
||||
final Album a = Album(
|
||||
remoteId: dto.id,
|
||||
name: dto.albumName,
|
||||
createdAt: DateTime.parse(dto.createdAt),
|
||||
modifiedAt: DateTime.parse(dto.updatedAt),
|
||||
createdAt: dto.createdAt,
|
||||
modifiedAt: dto.updatedAt,
|
||||
shared: dto.shared,
|
||||
);
|
||||
a.owner.value = await db.users.getById(dto.ownerId);
|
||||
|
||||
@@ -15,9 +15,9 @@ class Asset {
|
||||
Asset.remote(AssetResponseDto remote)
|
||||
: remoteId = remote.id,
|
||||
isLocal = false,
|
||||
fileCreatedAt = DateTime.parse(remote.fileCreatedAt),
|
||||
fileModifiedAt = DateTime.parse(remote.fileModifiedAt),
|
||||
updatedAt = DateTime.parse(remote.updatedAt),
|
||||
fileCreatedAt = remote.fileCreatedAt,
|
||||
fileModifiedAt = remote.fileModifiedAt,
|
||||
updatedAt = remote.updatedAt,
|
||||
durationInSeconds = remote.duration.toDuration()?.inSeconds ?? 0,
|
||||
type = remote.type.toAssetType(),
|
||||
fileName = p.basename(remote.originalPath),
|
||||
|
||||
@@ -22,9 +22,7 @@ class User {
|
||||
|
||||
User.fromDto(UserResponseDto dto)
|
||||
: id = dto.id,
|
||||
updatedAt = dto.updatedAt != null
|
||||
? DateTime.parse(dto.updatedAt!).toUtc()
|
||||
: DateTime.now().toUtc(),
|
||||
updatedAt = dto.updatedAt,
|
||||
email = dto.email,
|
||||
firstName = dto.firstName,
|
||||
lastName = dto.lastName,
|
||||
|
||||
@@ -279,7 +279,7 @@ class SyncService {
|
||||
|
||||
album.name = dto.albumName;
|
||||
album.shared = dto.shared;
|
||||
album.modifiedAt = DateTime.parse(dto.updatedAt);
|
||||
album.modifiedAt = dto.updatedAt;
|
||||
if (album.thumbnail.value?.remoteId != dto.albumThumbnailAssetId) {
|
||||
album.thumbnail.value = await _db.assets
|
||||
.where()
|
||||
@@ -713,5 +713,5 @@ bool _hasAlbumResponseDtoChanged(AlbumResponseDto dto, Album a) {
|
||||
dto.albumThumbnailAssetId != a.thumbnail.value?.remoteId ||
|
||||
dto.shared != a.shared ||
|
||||
dto.sharedUsers.length != a.sharedUsers.length ||
|
||||
!DateTime.parse(dto.updatedAt).isAtSameMomentAs(a.modifiedAt);
|
||||
!dto.updatedAt.isAtSameMomentAs(a.modifiedAt);
|
||||
}
|
||||
|
||||
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@@ -3,7 +3,7 @@ Immich API
|
||||
|
||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 1.58.0
|
||||
- API version: 1.59.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
## Requirements
|
||||
|
||||
4
mobile/openapi/doc/APIKeyResponseDto.md
generated
4
mobile/openapi/doc/APIKeyResponseDto.md
generated
@@ -10,8 +10,8 @@ Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**id** | **String** | |
|
||||
**name** | **String** | |
|
||||
**createdAt** | **String** | |
|
||||
**updatedAt** | **String** | |
|
||||
**createdAt** | [**DateTime**](DateTime.md) | |
|
||||
**updatedAt** | [**DateTime**](DateTime.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)
|
||||
|
||||
|
||||
2
mobile/openapi/doc/AdminSignupResponseDto.md
generated
2
mobile/openapi/doc/AdminSignupResponseDto.md
generated
@@ -12,7 +12,7 @@ Name | Type | Description | Notes
|
||||
**email** | **String** | |
|
||||
**firstName** | **String** | |
|
||||
**lastName** | **String** | |
|
||||
**createdAt** | **String** | |
|
||||
**createdAt** | [**DateTime**](DateTime.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)
|
||||
|
||||
|
||||
4
mobile/openapi/doc/AlbumResponseDto.md
generated
4
mobile/openapi/doc/AlbumResponseDto.md
generated
@@ -12,8 +12,8 @@ Name | Type | Description | Notes
|
||||
**id** | **String** | |
|
||||
**ownerId** | **String** | |
|
||||
**albumName** | **String** | |
|
||||
**createdAt** | **String** | |
|
||||
**updatedAt** | **String** | |
|
||||
**createdAt** | [**DateTime**](DateTime.md) | |
|
||||
**updatedAt** | [**DateTime**](DateTime.md) | |
|
||||
**albumThumbnailAssetId** | **String** | |
|
||||
**shared** | **bool** | |
|
||||
**sharedUsers** | [**List<UserResponseDto>**](UserResponseDto.md) | | [default to const []]
|
||||
|
||||
8
mobile/openapi/doc/AssetApi.md
generated
8
mobile/openapi/doc/AssetApi.md
generated
@@ -1476,8 +1476,8 @@ final assetType = ; // AssetTypeEnum |
|
||||
final assetData = BINARY_DATA_HERE; // MultipartFile |
|
||||
final deviceAssetId = deviceAssetId_example; // String |
|
||||
final deviceId = deviceId_example; // String |
|
||||
final fileCreatedAt = fileCreatedAt_example; // String |
|
||||
final fileModifiedAt = fileModifiedAt_example; // String |
|
||||
final fileCreatedAt = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final fileModifiedAt = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final isFavorite = true; // bool |
|
||||
final fileExtension = fileExtension_example; // String |
|
||||
final key = key_example; // String |
|
||||
@@ -1503,8 +1503,8 @@ Name | Type | Description | Notes
|
||||
**assetData** | **MultipartFile**| |
|
||||
**deviceAssetId** | **String**| |
|
||||
**deviceId** | **String**| |
|
||||
**fileCreatedAt** | **String**| |
|
||||
**fileModifiedAt** | **String**| |
|
||||
**fileCreatedAt** | **DateTime**| |
|
||||
**fileModifiedAt** | **DateTime**| |
|
||||
**isFavorite** | **bool**| |
|
||||
**fileExtension** | **String**| |
|
||||
**key** | **String**| | [optional]
|
||||
|
||||
2
mobile/openapi/doc/AssetBulkUploadCheckItem.md
generated
2
mobile/openapi/doc/AssetBulkUploadCheckItem.md
generated
@@ -9,7 +9,7 @@ import 'package:openapi/api.dart';
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**id** | **String** | |
|
||||
**checksum** | **String** | |
|
||||
**checksum** | **String** | base64 or hex encoded sha1 hash |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
11
mobile/openapi/doc/AssetResponseDto.md
generated
11
mobile/openapi/doc/AssetResponseDto.md
generated
@@ -15,21 +15,20 @@ Name | Type | Description | Notes
|
||||
**deviceId** | **String** | |
|
||||
**originalPath** | **String** | |
|
||||
**originalFileName** | **String** | |
|
||||
**resizePath** | **String** | |
|
||||
**fileCreatedAt** | **String** | |
|
||||
**fileModifiedAt** | **String** | |
|
||||
**updatedAt** | **String** | |
|
||||
**resized** | **bool** | |
|
||||
**fileCreatedAt** | [**DateTime**](DateTime.md) | |
|
||||
**fileModifiedAt** | [**DateTime**](DateTime.md) | |
|
||||
**updatedAt** | [**DateTime**](DateTime.md) | |
|
||||
**isFavorite** | **bool** | |
|
||||
**isArchived** | **bool** | |
|
||||
**mimeType** | **String** | |
|
||||
**duration** | **String** | |
|
||||
**webpPath** | **String** | |
|
||||
**encodedVideoPath** | **String** | | [optional]
|
||||
**exifInfo** | [**ExifResponseDto**](ExifResponseDto.md) | | [optional]
|
||||
**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) | | [optional]
|
||||
**livePhotoVideoId** | **String** | | [optional]
|
||||
**tags** | [**List<TagResponseDto>**](TagResponseDto.md) | | [optional] [default to const []]
|
||||
**people** | [**List<PersonResponseDto>**](PersonResponseDto.md) | | [optional] [default to const []]
|
||||
**checksum** | **String** | base64 encoded sha1 hash |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
2
mobile/openapi/doc/CreateAssetsShareLinkDto.md
generated
2
mobile/openapi/doc/CreateAssetsShareLinkDto.md
generated
@@ -9,7 +9,7 @@ import 'package:openapi/api.dart';
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**assetIds** | **List<String>** | | [default to const []]
|
||||
**expiresAt** | **String** | | [optional]
|
||||
**expiresAt** | [**DateTime**](DateTime.md) | | [optional]
|
||||
**allowUpload** | **bool** | | [optional]
|
||||
**allowDownload** | **bool** | | [optional]
|
||||
**showExif** | **bool** | | [optional]
|
||||
|
||||
2
mobile/openapi/doc/EditSharedLinkDto.md
generated
2
mobile/openapi/doc/EditSharedLinkDto.md
generated
@@ -9,7 +9,7 @@ import 'package:openapi/api.dart';
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**description** | **String** | | [optional]
|
||||
**expiresAt** | **String** | | [optional]
|
||||
**expiresAt** | [**DateTime**](DateTime.md) | | [optional]
|
||||
**allowUpload** | **bool** | | [optional]
|
||||
**allowDownload** | **bool** | | [optional]
|
||||
**showExif** | **bool** | | [optional]
|
||||
|
||||
4
mobile/openapi/doc/SharedLinkResponseDto.md
generated
4
mobile/openapi/doc/SharedLinkResponseDto.md
generated
@@ -13,8 +13,8 @@ Name | Type | Description | Notes
|
||||
**description** | **String** | | [optional]
|
||||
**userId** | **String** | |
|
||||
**key** | **String** | |
|
||||
**createdAt** | **String** | |
|
||||
**expiresAt** | **String** | |
|
||||
**createdAt** | [**DateTime**](DateTime.md) | |
|
||||
**expiresAt** | [**DateTime**](DateTime.md) | |
|
||||
**assets** | [**List<AssetResponseDto>**](AssetResponseDto.md) | | [default to const []]
|
||||
**album** | [**AlbumResponseDto**](AlbumResponseDto.md) | | [optional]
|
||||
**allowUpload** | **bool** | |
|
||||
|
||||
6
mobile/openapi/doc/UserResponseDto.md
generated
6
mobile/openapi/doc/UserResponseDto.md
generated
@@ -13,12 +13,12 @@ Name | Type | Description | Notes
|
||||
**firstName** | **String** | |
|
||||
**lastName** | **String** | |
|
||||
**storageLabel** | **String** | |
|
||||
**createdAt** | **String** | |
|
||||
**profileImagePath** | **String** | |
|
||||
**shouldChangePassword** | **bool** | |
|
||||
**isAdmin** | **bool** | |
|
||||
**deletedAt** | [**DateTime**](DateTime.md) | | [optional]
|
||||
**updatedAt** | **String** | | [optional]
|
||||
**createdAt** | [**DateTime**](DateTime.md) | |
|
||||
**deletedAt** | [**DateTime**](DateTime.md) | |
|
||||
**updatedAt** | [**DateTime**](DateTime.md) | |
|
||||
**oauthId** | **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)
|
||||
|
||||
12
mobile/openapi/lib/api/asset_api.dart
generated
12
mobile/openapi/lib/api/asset_api.dart
generated
@@ -1405,9 +1405,9 @@ class AssetApi {
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
///
|
||||
/// * [String] fileCreatedAt (required):
|
||||
/// * [DateTime] fileCreatedAt (required):
|
||||
///
|
||||
/// * [String] fileModifiedAt (required):
|
||||
/// * [DateTime] fileModifiedAt (required):
|
||||
///
|
||||
/// * [bool] isFavorite (required):
|
||||
///
|
||||
@@ -1424,7 +1424,7 @@ class AssetApi {
|
||||
/// * [bool] isVisible:
|
||||
///
|
||||
/// * [String] duration:
|
||||
Future<Response> uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isArchived, bool? isVisible, String? duration, }) async {
|
||||
Future<Response> uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isArchived, bool? isVisible, String? duration, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/upload';
|
||||
|
||||
@@ -1523,9 +1523,9 @@ class AssetApi {
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
///
|
||||
/// * [String] fileCreatedAt (required):
|
||||
/// * [DateTime] fileCreatedAt (required):
|
||||
///
|
||||
/// * [String] fileModifiedAt (required):
|
||||
/// * [DateTime] fileModifiedAt (required):
|
||||
///
|
||||
/// * [bool] isFavorite (required):
|
||||
///
|
||||
@@ -1542,7 +1542,7 @@ class AssetApi {
|
||||
/// * [bool] isVisible:
|
||||
///
|
||||
/// * [String] duration:
|
||||
Future<AssetFileUploadResponseDto?> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isArchived, bool? isVisible, String? duration, }) async {
|
||||
Future<AssetFileUploadResponseDto?> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isArchived, bool? isVisible, String? duration, }) async {
|
||||
final response = await uploadFileWithHttpInfo(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key: key, livePhotoData: livePhotoData, sidecarData: sidecarData, isArchived: isArchived, isVisible: isVisible, duration: duration, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
|
||||
@@ -28,7 +28,7 @@ class AdminSignupResponseDto {
|
||||
|
||||
String lastName;
|
||||
|
||||
String createdAt;
|
||||
DateTime createdAt;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AdminSignupResponseDto &&
|
||||
@@ -56,7 +56,7 @@ class AdminSignupResponseDto {
|
||||
json[r'email'] = this.email;
|
||||
json[r'firstName'] = this.firstName;
|
||||
json[r'lastName'] = this.lastName;
|
||||
json[r'createdAt'] = this.createdAt;
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class AdminSignupResponseDto {
|
||||
email: mapValueOfType<String>(json, r'email')!,
|
||||
firstName: mapValueOfType<String>(json, r'firstName')!,
|
||||
lastName: mapValueOfType<String>(json, r'lastName')!,
|
||||
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
||||
createdAt: mapDateTime(json, r'createdAt', '')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
12
mobile/openapi/lib/model/album_response_dto.dart
generated
12
mobile/openapi/lib/model/album_response_dto.dart
generated
@@ -34,9 +34,9 @@ class AlbumResponseDto {
|
||||
|
||||
String albumName;
|
||||
|
||||
String createdAt;
|
||||
DateTime createdAt;
|
||||
|
||||
String updatedAt;
|
||||
DateTime updatedAt;
|
||||
|
||||
String? albumThumbnailAssetId;
|
||||
|
||||
@@ -86,8 +86,8 @@ class AlbumResponseDto {
|
||||
json[r'id'] = this.id;
|
||||
json[r'ownerId'] = this.ownerId;
|
||||
json[r'albumName'] = this.albumName;
|
||||
json[r'createdAt'] = this.createdAt;
|
||||
json[r'updatedAt'] = this.updatedAt;
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
if (this.albumThumbnailAssetId != null) {
|
||||
json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId;
|
||||
} else {
|
||||
@@ -123,8 +123,8 @@ class AlbumResponseDto {
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
||||
albumName: mapValueOfType<String>(json, r'albumName')!,
|
||||
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
||||
updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
|
||||
createdAt: mapDateTime(json, r'createdAt', '')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', '')!,
|
||||
albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
|
||||
shared: mapValueOfType<bool>(json, r'shared')!,
|
||||
sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers']),
|
||||
|
||||
12
mobile/openapi/lib/model/api_key_response_dto.dart
generated
12
mobile/openapi/lib/model/api_key_response_dto.dart
generated
@@ -23,9 +23,9 @@ class APIKeyResponseDto {
|
||||
|
||||
String name;
|
||||
|
||||
String createdAt;
|
||||
DateTime createdAt;
|
||||
|
||||
String updatedAt;
|
||||
DateTime updatedAt;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is APIKeyResponseDto &&
|
||||
@@ -49,8 +49,8 @@ class APIKeyResponseDto {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'id'] = this.id;
|
||||
json[r'name'] = this.name;
|
||||
json[r'createdAt'] = this.createdAt;
|
||||
json[r'updatedAt'] = this.updatedAt;
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ class APIKeyResponseDto {
|
||||
return APIKeyResponseDto(
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
||||
updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
|
||||
createdAt: mapDateTime(json, r'createdAt', '')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', '')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -19,6 +19,7 @@ class AssetBulkUploadCheckItem {
|
||||
|
||||
String id;
|
||||
|
||||
/// base64 or hex encoded sha1 hash
|
||||
String checksum;
|
||||
|
||||
@override
|
||||
|
||||
74
mobile/openapi/lib/model/asset_response_dto.dart
generated
74
mobile/openapi/lib/model/asset_response_dto.dart
generated
@@ -20,7 +20,7 @@ class AssetResponseDto {
|
||||
required this.deviceId,
|
||||
required this.originalPath,
|
||||
required this.originalFileName,
|
||||
required this.resizePath,
|
||||
required this.resized,
|
||||
required this.fileCreatedAt,
|
||||
required this.fileModifiedAt,
|
||||
required this.updatedAt,
|
||||
@@ -28,13 +28,12 @@ class AssetResponseDto {
|
||||
required this.isArchived,
|
||||
required this.mimeType,
|
||||
required this.duration,
|
||||
required this.webpPath,
|
||||
this.encodedVideoPath,
|
||||
this.exifInfo,
|
||||
this.smartInfo,
|
||||
this.livePhotoVideoId,
|
||||
this.tags = const [],
|
||||
this.people = const [],
|
||||
required this.checksum,
|
||||
});
|
||||
|
||||
AssetTypeEnum type;
|
||||
@@ -51,13 +50,13 @@ class AssetResponseDto {
|
||||
|
||||
String originalFileName;
|
||||
|
||||
String? resizePath;
|
||||
bool resized;
|
||||
|
||||
String fileCreatedAt;
|
||||
DateTime fileCreatedAt;
|
||||
|
||||
String fileModifiedAt;
|
||||
DateTime fileModifiedAt;
|
||||
|
||||
String updatedAt;
|
||||
DateTime updatedAt;
|
||||
|
||||
bool isFavorite;
|
||||
|
||||
@@ -67,10 +66,6 @@ class AssetResponseDto {
|
||||
|
||||
String duration;
|
||||
|
||||
String? webpPath;
|
||||
|
||||
String? encodedVideoPath;
|
||||
|
||||
///
|
||||
/// 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
|
||||
@@ -93,6 +88,9 @@ class AssetResponseDto {
|
||||
|
||||
List<PersonResponseDto> people;
|
||||
|
||||
/// base64 encoded sha1 hash
|
||||
String checksum;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
|
||||
other.type == type &&
|
||||
@@ -102,7 +100,7 @@ class AssetResponseDto {
|
||||
other.deviceId == deviceId &&
|
||||
other.originalPath == originalPath &&
|
||||
other.originalFileName == originalFileName &&
|
||||
other.resizePath == resizePath &&
|
||||
other.resized == resized &&
|
||||
other.fileCreatedAt == fileCreatedAt &&
|
||||
other.fileModifiedAt == fileModifiedAt &&
|
||||
other.updatedAt == updatedAt &&
|
||||
@@ -110,13 +108,12 @@ class AssetResponseDto {
|
||||
other.isArchived == isArchived &&
|
||||
other.mimeType == mimeType &&
|
||||
other.duration == duration &&
|
||||
other.webpPath == webpPath &&
|
||||
other.encodedVideoPath == encodedVideoPath &&
|
||||
other.exifInfo == exifInfo &&
|
||||
other.smartInfo == smartInfo &&
|
||||
other.livePhotoVideoId == livePhotoVideoId &&
|
||||
other.tags == tags &&
|
||||
other.people == people;
|
||||
other.people == people &&
|
||||
other.checksum == checksum;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -128,7 +125,7 @@ class AssetResponseDto {
|
||||
(deviceId.hashCode) +
|
||||
(originalPath.hashCode) +
|
||||
(originalFileName.hashCode) +
|
||||
(resizePath == null ? 0 : resizePath!.hashCode) +
|
||||
(resized.hashCode) +
|
||||
(fileCreatedAt.hashCode) +
|
||||
(fileModifiedAt.hashCode) +
|
||||
(updatedAt.hashCode) +
|
||||
@@ -136,16 +133,15 @@ class AssetResponseDto {
|
||||
(isArchived.hashCode) +
|
||||
(mimeType == null ? 0 : mimeType!.hashCode) +
|
||||
(duration.hashCode) +
|
||||
(webpPath == null ? 0 : webpPath!.hashCode) +
|
||||
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
||||
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
||||
(smartInfo == null ? 0 : smartInfo!.hashCode) +
|
||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||
(tags.hashCode) +
|
||||
(people.hashCode);
|
||||
(people.hashCode) +
|
||||
(checksum.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, originalFileName=$originalFileName, resizePath=$resizePath, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, isArchived=$isArchived, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags, people=$people]';
|
||||
String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, originalFileName=$originalFileName, resized=$resized, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, isArchived=$isArchived, mimeType=$mimeType, duration=$duration, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags, people=$people, checksum=$checksum]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -156,14 +152,10 @@ class AssetResponseDto {
|
||||
json[r'deviceId'] = this.deviceId;
|
||||
json[r'originalPath'] = this.originalPath;
|
||||
json[r'originalFileName'] = this.originalFileName;
|
||||
if (this.resizePath != null) {
|
||||
json[r'resizePath'] = this.resizePath;
|
||||
} else {
|
||||
// json[r'resizePath'] = null;
|
||||
}
|
||||
json[r'fileCreatedAt'] = this.fileCreatedAt;
|
||||
json[r'fileModifiedAt'] = this.fileModifiedAt;
|
||||
json[r'updatedAt'] = this.updatedAt;
|
||||
json[r'resized'] = this.resized;
|
||||
json[r'fileCreatedAt'] = this.fileCreatedAt.toUtc().toIso8601String();
|
||||
json[r'fileModifiedAt'] = this.fileModifiedAt.toUtc().toIso8601String();
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
if (this.mimeType != null) {
|
||||
@@ -172,16 +164,6 @@ class AssetResponseDto {
|
||||
// json[r'mimeType'] = null;
|
||||
}
|
||||
json[r'duration'] = this.duration;
|
||||
if (this.webpPath != null) {
|
||||
json[r'webpPath'] = this.webpPath;
|
||||
} else {
|
||||
// json[r'webpPath'] = null;
|
||||
}
|
||||
if (this.encodedVideoPath != null) {
|
||||
json[r'encodedVideoPath'] = this.encodedVideoPath;
|
||||
} else {
|
||||
// json[r'encodedVideoPath'] = null;
|
||||
}
|
||||
if (this.exifInfo != null) {
|
||||
json[r'exifInfo'] = this.exifInfo;
|
||||
} else {
|
||||
@@ -199,6 +181,7 @@ class AssetResponseDto {
|
||||
}
|
||||
json[r'tags'] = this.tags;
|
||||
json[r'people'] = this.people;
|
||||
json[r'checksum'] = this.checksum;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -228,21 +211,20 @@ class AssetResponseDto {
|
||||
deviceId: mapValueOfType<String>(json, r'deviceId')!,
|
||||
originalPath: mapValueOfType<String>(json, r'originalPath')!,
|
||||
originalFileName: mapValueOfType<String>(json, r'originalFileName')!,
|
||||
resizePath: mapValueOfType<String>(json, r'resizePath'),
|
||||
fileCreatedAt: mapValueOfType<String>(json, r'fileCreatedAt')!,
|
||||
fileModifiedAt: mapValueOfType<String>(json, r'fileModifiedAt')!,
|
||||
updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
|
||||
resized: mapValueOfType<bool>(json, r'resized')!,
|
||||
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', '')!,
|
||||
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', '')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', '')!,
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
|
||||
mimeType: mapValueOfType<String>(json, r'mimeType'),
|
||||
duration: mapValueOfType<String>(json, r'duration')!,
|
||||
webpPath: mapValueOfType<String>(json, r'webpPath'),
|
||||
encodedVideoPath: mapValueOfType<String>(json, r'encodedVideoPath'),
|
||||
exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
|
||||
smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']),
|
||||
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||
tags: TagResponseDto.listFromJson(json[r'tags']),
|
||||
people: PersonResponseDto.listFromJson(json[r'people']),
|
||||
checksum: mapValueOfType<String>(json, r'checksum')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -297,7 +279,7 @@ class AssetResponseDto {
|
||||
'deviceId',
|
||||
'originalPath',
|
||||
'originalFileName',
|
||||
'resizePath',
|
||||
'resized',
|
||||
'fileCreatedAt',
|
||||
'fileModifiedAt',
|
||||
'updatedAt',
|
||||
@@ -305,7 +287,7 @@ class AssetResponseDto {
|
||||
'isArchived',
|
||||
'mimeType',
|
||||
'duration',
|
||||
'webpPath',
|
||||
'checksum',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class CreateAssetsShareLinkDto {
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
String? expiresAt;
|
||||
DateTime? expiresAt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
@@ -89,7 +89,7 @@ class CreateAssetsShareLinkDto {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'assetIds'] = this.assetIds;
|
||||
if (this.expiresAt != null) {
|
||||
json[r'expiresAt'] = this.expiresAt;
|
||||
json[r'expiresAt'] = this.expiresAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'expiresAt'] = null;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class CreateAssetsShareLinkDto {
|
||||
assetIds: json[r'assetIds'] is Iterable
|
||||
? (json[r'assetIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
expiresAt: mapValueOfType<String>(json, r'expiresAt'),
|
||||
expiresAt: mapDateTime(json, r'expiresAt', ''),
|
||||
allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
|
||||
allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
|
||||
showExif: mapValueOfType<bool>(json, r'showExif'),
|
||||
|
||||
@@ -28,7 +28,7 @@ class EditSharedLinkDto {
|
||||
///
|
||||
String? description;
|
||||
|
||||
String? expiresAt;
|
||||
DateTime? expiresAt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
@@ -82,7 +82,7 @@ class EditSharedLinkDto {
|
||||
// json[r'description'] = null;
|
||||
}
|
||||
if (this.expiresAt != null) {
|
||||
json[r'expiresAt'] = this.expiresAt;
|
||||
json[r'expiresAt'] = this.expiresAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'expiresAt'] = null;
|
||||
}
|
||||
@@ -124,7 +124,7 @@ class EditSharedLinkDto {
|
||||
|
||||
return EditSharedLinkDto(
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
expiresAt: mapValueOfType<String>(json, r'expiresAt'),
|
||||
expiresAt: mapDateTime(json, r'expiresAt', ''),
|
||||
allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
|
||||
allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
|
||||
showExif: mapValueOfType<bool>(json, r'showExif'),
|
||||
|
||||
@@ -43,9 +43,9 @@ class SharedLinkResponseDto {
|
||||
|
||||
String key;
|
||||
|
||||
String createdAt;
|
||||
DateTime createdAt;
|
||||
|
||||
String? expiresAt;
|
||||
DateTime? expiresAt;
|
||||
|
||||
List<AssetResponseDto> assets;
|
||||
|
||||
@@ -108,9 +108,9 @@ class SharedLinkResponseDto {
|
||||
}
|
||||
json[r'userId'] = this.userId;
|
||||
json[r'key'] = this.key;
|
||||
json[r'createdAt'] = this.createdAt;
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
if (this.expiresAt != null) {
|
||||
json[r'expiresAt'] = this.expiresAt;
|
||||
json[r'expiresAt'] = this.expiresAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'expiresAt'] = null;
|
||||
}
|
||||
@@ -150,8 +150,8 @@ class SharedLinkResponseDto {
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
userId: mapValueOfType<String>(json, r'userId')!,
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
||||
expiresAt: mapValueOfType<String>(json, r'expiresAt'),
|
||||
createdAt: mapDateTime(json, r'createdAt', '')!,
|
||||
expiresAt: mapDateTime(json, r'expiresAt', ''),
|
||||
assets: AssetResponseDto.listFromJson(json[r'assets']),
|
||||
album: AlbumResponseDto.fromJson(json[r'album']),
|
||||
allowUpload: mapValueOfType<bool>(json, r'allowUpload')!,
|
||||
|
||||
48
mobile/openapi/lib/model/user_response_dto.dart
generated
48
mobile/openapi/lib/model/user_response_dto.dart
generated
@@ -18,12 +18,12 @@ class UserResponseDto {
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.storageLabel,
|
||||
required this.createdAt,
|
||||
required this.profileImagePath,
|
||||
required this.shouldChangePassword,
|
||||
required this.isAdmin,
|
||||
this.deletedAt,
|
||||
this.updatedAt,
|
||||
required this.createdAt,
|
||||
required this.deletedAt,
|
||||
required this.updatedAt,
|
||||
required this.oauthId,
|
||||
});
|
||||
|
||||
@@ -37,29 +37,17 @@ class UserResponseDto {
|
||||
|
||||
String? storageLabel;
|
||||
|
||||
String createdAt;
|
||||
|
||||
String profileImagePath;
|
||||
|
||||
bool shouldChangePassword;
|
||||
|
||||
bool isAdmin;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
DateTime createdAt;
|
||||
|
||||
DateTime? deletedAt;
|
||||
|
||||
///
|
||||
/// 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? updatedAt;
|
||||
DateTime updatedAt;
|
||||
|
||||
String oauthId;
|
||||
|
||||
@@ -70,10 +58,10 @@ class UserResponseDto {
|
||||
other.firstName == firstName &&
|
||||
other.lastName == lastName &&
|
||||
other.storageLabel == storageLabel &&
|
||||
other.createdAt == createdAt &&
|
||||
other.profileImagePath == profileImagePath &&
|
||||
other.shouldChangePassword == shouldChangePassword &&
|
||||
other.isAdmin == isAdmin &&
|
||||
other.createdAt == createdAt &&
|
||||
other.deletedAt == deletedAt &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.oauthId == oauthId;
|
||||
@@ -86,16 +74,16 @@ class UserResponseDto {
|
||||
(firstName.hashCode) +
|
||||
(lastName.hashCode) +
|
||||
(storageLabel == null ? 0 : storageLabel!.hashCode) +
|
||||
(createdAt.hashCode) +
|
||||
(profileImagePath.hashCode) +
|
||||
(shouldChangePassword.hashCode) +
|
||||
(isAdmin.hashCode) +
|
||||
(createdAt.hashCode) +
|
||||
(deletedAt == null ? 0 : deletedAt!.hashCode) +
|
||||
(updatedAt == null ? 0 : updatedAt!.hashCode) +
|
||||
(updatedAt.hashCode) +
|
||||
(oauthId.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, storageLabel=$storageLabel, createdAt=$createdAt, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, deletedAt=$deletedAt, updatedAt=$updatedAt, oauthId=$oauthId]';
|
||||
String toString() => 'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, storageLabel=$storageLabel, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, createdAt=$createdAt, deletedAt=$deletedAt, updatedAt=$updatedAt, oauthId=$oauthId]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -108,20 +96,16 @@ class UserResponseDto {
|
||||
} else {
|
||||
// json[r'storageLabel'] = null;
|
||||
}
|
||||
json[r'createdAt'] = this.createdAt;
|
||||
json[r'profileImagePath'] = this.profileImagePath;
|
||||
json[r'shouldChangePassword'] = this.shouldChangePassword;
|
||||
json[r'isAdmin'] = this.isAdmin;
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
if (this.deletedAt != null) {
|
||||
json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'deletedAt'] = null;
|
||||
}
|
||||
if (this.updatedAt != null) {
|
||||
json[r'updatedAt'] = this.updatedAt;
|
||||
} else {
|
||||
// json[r'updatedAt'] = null;
|
||||
}
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'oauthId'] = this.oauthId;
|
||||
return json;
|
||||
}
|
||||
@@ -150,12 +134,12 @@ class UserResponseDto {
|
||||
firstName: mapValueOfType<String>(json, r'firstName')!,
|
||||
lastName: mapValueOfType<String>(json, r'lastName')!,
|
||||
storageLabel: mapValueOfType<String>(json, r'storageLabel'),
|
||||
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
||||
profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
|
||||
shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
|
||||
isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
|
||||
createdAt: mapDateTime(json, r'createdAt', '')!,
|
||||
deletedAt: mapDateTime(json, r'deletedAt', ''),
|
||||
updatedAt: mapValueOfType<String>(json, r'updatedAt'),
|
||||
updatedAt: mapDateTime(json, r'updatedAt', '')!,
|
||||
oauthId: mapValueOfType<String>(json, r'oauthId')!,
|
||||
);
|
||||
}
|
||||
@@ -209,10 +193,12 @@ class UserResponseDto {
|
||||
'firstName',
|
||||
'lastName',
|
||||
'storageLabel',
|
||||
'createdAt',
|
||||
'profileImagePath',
|
||||
'shouldChangePassword',
|
||||
'isAdmin',
|
||||
'createdAt',
|
||||
'deletedAt',
|
||||
'updatedAt',
|
||||
'oauthId',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String createdAt
|
||||
// DateTime createdAt
|
||||
test('to test the property `createdAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
4
mobile/openapi/test/album_response_dto_test.dart
generated
4
mobile/openapi/test/album_response_dto_test.dart
generated
@@ -36,12 +36,12 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String createdAt
|
||||
// DateTime createdAt
|
||||
test('to test the property `createdAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String updatedAt
|
||||
// DateTime updatedAt
|
||||
test('to test the property `updatedAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
@@ -26,12 +26,12 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String createdAt
|
||||
// DateTime createdAt
|
||||
test('to test the property `createdAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String updatedAt
|
||||
// DateTime updatedAt
|
||||
test('to test the property `updatedAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
2
mobile/openapi/test/asset_api_test.dart
generated
2
mobile/openapi/test/asset_api_test.dart
generated
@@ -158,7 +158,7 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<AssetFileUploadResponseDto> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String key, MultipartFile livePhotoData, MultipartFile sidecarData, bool isArchived, bool isVisible, String duration }) async
|
||||
//Future<AssetFileUploadResponseDto> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, String fileExtension, { String key, MultipartFile livePhotoData, MultipartFile sidecarData, bool isArchived, bool isVisible, String duration }) async
|
||||
test('test uploadFile', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// base64 or hex encoded sha1 hash
|
||||
// String checksum
|
||||
test('to test the property `checksum`', () async {
|
||||
// TODO
|
||||
|
||||
26
mobile/openapi/test/asset_response_dto_test.dart
generated
26
mobile/openapi/test/asset_response_dto_test.dart
generated
@@ -51,22 +51,22 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String resizePath
|
||||
test('to test the property `resizePath`', () async {
|
||||
// bool resized
|
||||
test('to test the property `resized`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String fileCreatedAt
|
||||
// DateTime fileCreatedAt
|
||||
test('to test the property `fileCreatedAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String fileModifiedAt
|
||||
// DateTime fileModifiedAt
|
||||
test('to test the property `fileModifiedAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String updatedAt
|
||||
// DateTime updatedAt
|
||||
test('to test the property `updatedAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
@@ -91,16 +91,6 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String webpPath
|
||||
test('to test the property `webpPath`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String encodedVideoPath
|
||||
test('to test the property `encodedVideoPath`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// ExifResponseDto exifInfo
|
||||
test('to test the property `exifInfo`', () async {
|
||||
// TODO
|
||||
@@ -126,6 +116,12 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// base64 encoded sha1 hash
|
||||
// String checksum
|
||||
test('to test the property `checksum`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String expiresAt
|
||||
// DateTime expiresAt
|
||||
test('to test the property `expiresAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String expiresAt
|
||||
// DateTime expiresAt
|
||||
test('to test the property `expiresAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
@@ -41,12 +41,12 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String createdAt
|
||||
// DateTime createdAt
|
||||
test('to test the property `createdAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String expiresAt
|
||||
// DateTime expiresAt
|
||||
test('to test the property `expiresAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
12
mobile/openapi/test/user_response_dto_test.dart
generated
12
mobile/openapi/test/user_response_dto_test.dart
generated
@@ -41,11 +41,6 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String createdAt
|
||||
test('to test the property `createdAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String profileImagePath
|
||||
test('to test the property `profileImagePath`', () async {
|
||||
// TODO
|
||||
@@ -61,12 +56,17 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// DateTime createdAt
|
||||
test('to test the property `createdAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// DateTime deletedAt
|
||||
test('to test the property `deletedAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String updatedAt
|
||||
// DateTime updatedAt
|
||||
test('to test the property `updatedAt`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: "none"
|
||||
version: 1.58.0+81
|
||||
version: 1.59.0+82
|
||||
isar_version: &isar_version 3.1.0+1
|
||||
|
||||
environment:
|
||||
|
||||
@@ -61,7 +61,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||
|
||||
async addSharedUsers(album: AlbumEntity, addUsersDto: AddUsersDto): Promise<AlbumEntity> {
|
||||
album.sharedUsers.push(...addUsersDto.sharedUserIds.map((id) => ({ id } as UserEntity)));
|
||||
album.updatedAt = new Date().toISOString();
|
||||
album.updatedAt = new Date();
|
||||
|
||||
await this.albumRepository.save(album);
|
||||
|
||||
@@ -71,7 +71,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||
|
||||
async removeUser(album: AlbumEntity, userId: string): Promise<void> {
|
||||
album.sharedUsers = album.sharedUsers.filter((user) => user.id !== userId);
|
||||
album.updatedAt = new Date().toISOString();
|
||||
album.updatedAt = new Date();
|
||||
await this.albumRepository.save(album);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||
|
||||
const numRemovedAssets = assetCount - album.assets.length;
|
||||
if (numRemovedAssets > 0) {
|
||||
album.updatedAt = new Date().toISOString();
|
||||
album.updatedAt = new Date();
|
||||
}
|
||||
await this.albumRepository.save(album, {});
|
||||
|
||||
@@ -111,7 +111,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||
|
||||
const successfullyAdded = addAssetsDto.assetIds.length - alreadyExisting.length;
|
||||
if (successfullyAdded > 0) {
|
||||
album.updatedAt = new Date().toISOString();
|
||||
album.updatedAt = new Date();
|
||||
}
|
||||
await this.albumRepository.save(album);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Controller, Get, Post, Body, Param, Delete, Put, Query, Response } from '@nestjs/common';
|
||||
import { ParseMeUUIDPipe } from '../validation/parse-me-uuid-pipe';
|
||||
import { AlbumService } from './album.service';
|
||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||
import { Authenticated, SharedLinkRoute } from '../../decorators/authenticated.decorator';
|
||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||
import { AddAssetsDto } from './dto/add-assets.dto';
|
||||
import { AddUsersDto } from './dto/add-users.dto';
|
||||
@@ -32,24 +32,23 @@ const handleDownload = (download: DownloadArchive, res: Res) => {
|
||||
|
||||
@ApiTags('Album')
|
||||
@Controller('album')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
export class AlbumController {
|
||||
constructor(private readonly service: AlbumService) {}
|
||||
|
||||
@Authenticated()
|
||||
@Get('count-by-user-id')
|
||||
getAlbumCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise<AlbumCountResponseDto> {
|
||||
return this.service.getCountByUserId(authUser);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Put(':id/users')
|
||||
addUsersToAlbum(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto, @Body() dto: AddUsersDto) {
|
||||
// TODO: Handle nonexistent sharedUserIds.
|
||||
return this.service.addUsers(authUser, id, dto);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Put(':id/assets')
|
||||
addAssetsToAlbum(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -61,13 +60,12 @@ export class AlbumController {
|
||||
return this.service.addAssets(authUser, id, dto);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Get(':id')
|
||||
getAlbumInfo(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.get(authUser, id);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete(':id/assets')
|
||||
removeAssetFromAlbum(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -77,7 +75,6 @@ export class AlbumController {
|
||||
return this.service.removeAssets(authUser, id, dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete(':id/user/:userId')
|
||||
removeUserFromAlbum(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -87,7 +84,7 @@ export class AlbumController {
|
||||
return this.service.removeUser(authUser, id, userId);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Get(':id/download')
|
||||
@ApiOkResponse({ content: { 'application/zip': { schema: { type: 'string', format: 'binary' } } } })
|
||||
downloadArchive(
|
||||
@@ -100,7 +97,6 @@ export class AlbumController {
|
||||
return this.service.downloadArchive(authUser, id, dto).then((download) => handleDownload(download, res));
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('create-shared-link')
|
||||
createAlbumSharedLink(@GetAuthUser() authUser: AuthUserDto, @Body() dto: CreateAlbumSharedLinkDto) {
|
||||
return this.service.createSharedLink(authUser, dto);
|
||||
|
||||
@@ -32,8 +32,9 @@ describe('Album service', () => {
|
||||
...authUser,
|
||||
firstName: 'auth',
|
||||
lastName: 'user',
|
||||
createdAt: 'date',
|
||||
updatedAt: 'date',
|
||||
createdAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
profileImagePath: '',
|
||||
shouldChangePassword: false,
|
||||
oauthId: '',
|
||||
@@ -52,8 +53,8 @@ describe('Album service', () => {
|
||||
albumEntity.owner = albumOwner;
|
||||
albumEntity.id = albumId;
|
||||
albumEntity.albumName = 'name';
|
||||
albumEntity.createdAt = 'date';
|
||||
albumEntity.updatedAt = 'date';
|
||||
albumEntity.createdAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
albumEntity.updatedAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
albumEntity.sharedUsers = [];
|
||||
albumEntity.assets = [];
|
||||
albumEntity.albumThumbnailAssetId = null;
|
||||
@@ -67,7 +68,7 @@ describe('Album service', () => {
|
||||
albumEntity.owner = albumOwner;
|
||||
albumEntity.id = albumId;
|
||||
albumEntity.albumName = 'name';
|
||||
albumEntity.createdAt = 'date';
|
||||
albumEntity.createdAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
albumEntity.assets = [];
|
||||
albumEntity.albumThumbnailAssetId = null;
|
||||
albumEntity.sharedUsers = [
|
||||
@@ -86,7 +87,7 @@ describe('Album service', () => {
|
||||
albumEntity.owner = albumOwner;
|
||||
albumEntity.id = albumId;
|
||||
albumEntity.albumName = 'name';
|
||||
albumEntity.createdAt = 'date';
|
||||
albumEntity.createdAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
albumEntity.assets = [];
|
||||
albumEntity.albumThumbnailAssetId = null;
|
||||
albumEntity.sharedUsers = [
|
||||
@@ -109,7 +110,7 @@ describe('Album service', () => {
|
||||
albumEntity.ownerId = '5555';
|
||||
albumEntity.id = albumId;
|
||||
albumEntity.albumName = 'name';
|
||||
albumEntity.createdAt = 'date';
|
||||
albumEntity.createdAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
albumEntity.sharedUsers = [];
|
||||
albumEntity.assets = [];
|
||||
albumEntity.albumThumbnailAssetId = null;
|
||||
@@ -158,8 +159,8 @@ describe('Album service', () => {
|
||||
owner: mapUser(albumOwner),
|
||||
id: albumId,
|
||||
albumName: 'name',
|
||||
createdAt: 'date',
|
||||
updatedAt: 'date',
|
||||
createdAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
updatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
sharedUsers: [],
|
||||
assets: [],
|
||||
albumThumbnailAssetId: null,
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
|
||||
import { IsBoolean, IsISO8601, IsOptional, IsString } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsBoolean, IsDate, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateAlbumShareLinkDto {
|
||||
@ValidateUUID()
|
||||
albumId!: string;
|
||||
|
||||
@IsISO8601()
|
||||
@IsOptional()
|
||||
@ApiProperty({ format: 'date-time' })
|
||||
expiresAt?: string;
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
@ApiProperty()
|
||||
expiresAt?: Date;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
StreamableFile,
|
||||
ParseFilePipe,
|
||||
} from '@nestjs/common';
|
||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||
import { Authenticated, SharedLinkRoute } from '../../decorators/authenticated.decorator';
|
||||
import { AssetService } from './asset.service';
|
||||
import { FileFieldsInterceptor } from '@nestjs/platform-express';
|
||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||
@@ -68,10 +68,11 @@ function asStreamableFile({ stream, type, length }: ImmichReadStream) {
|
||||
|
||||
@ApiTags('Asset')
|
||||
@Controller('asset')
|
||||
@Authenticated()
|
||||
export class AssetController {
|
||||
constructor(private assetService: AssetService) {}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Post('upload')
|
||||
@UseInterceptors(
|
||||
FileFieldsInterceptor(
|
||||
@@ -116,7 +117,7 @@ export class AssetController {
|
||||
return responseDto;
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Get('/download/:assetId')
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
async downloadFile(
|
||||
@@ -127,7 +128,7 @@ export class AssetController {
|
||||
return this.assetService.downloadFile(authUser, assetId).then(asStreamableFile);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Post('/download-files')
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
async downloadFiles(
|
||||
@@ -148,7 +149,7 @@ export class AssetController {
|
||||
/**
|
||||
* Current this is not used in any UI element
|
||||
*/
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Get('/download-library')
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
async downloadLibrary(
|
||||
@@ -165,7 +166,7 @@ export class AssetController {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Get('/file/:assetId')
|
||||
@Header('Cache-Control', 'max-age=31536000')
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
@@ -180,7 +181,7 @@ export class AssetController {
|
||||
return this.assetService.serveFile(authUser, assetId, query, res, headers);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Get('/thumbnail/:assetId')
|
||||
@Header('Cache-Control', 'max-age=31536000')
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
@@ -195,25 +196,21 @@ export class AssetController {
|
||||
return this.assetService.getAssetThumbnail(assetId, query, res, headers);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('/curated-objects')
|
||||
async getCuratedObjects(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
||||
return this.assetService.getCuratedObject(authUser);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('/curated-locations')
|
||||
async getCuratedLocations(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedLocationsResponseDto[]> {
|
||||
return this.assetService.getCuratedLocation(authUser);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('/search-terms')
|
||||
async getAssetSearchTerms(@GetAuthUser() authUser: AuthUserDto): Promise<string[]> {
|
||||
return this.assetService.getAssetSearchTerm(authUser);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('/search')
|
||||
async searchAsset(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -222,7 +219,6 @@ export class AssetController {
|
||||
return this.assetService.searchAsset(authUser, searchAssetDto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('/count-by-time-bucket')
|
||||
async getAssetCountByTimeBucket(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -231,13 +227,11 @@ export class AssetController {
|
||||
return this.assetService.getAssetCountByTimeBucket(authUser, getAssetCountByTimeGroupDto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('/count-by-user-id')
|
||||
async getAssetCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> {
|
||||
return this.assetService.getAssetCountByUserId(authUser);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('/stat/archive')
|
||||
async getArchivedAssetCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> {
|
||||
return this.assetService.getArchivedAssetCountByUserId(authUser);
|
||||
@@ -245,7 +239,6 @@ export class AssetController {
|
||||
/**
|
||||
* Get all AssetEntity belong to the user
|
||||
*/
|
||||
@Authenticated()
|
||||
@Get('/')
|
||||
@ApiHeader({
|
||||
name: 'if-none-match',
|
||||
@@ -260,7 +253,6 @@ export class AssetController {
|
||||
return this.assetService.getAllAssets(authUser, dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('/time-bucket')
|
||||
async getAssetByTimeBucket(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -272,7 +264,6 @@ export class AssetController {
|
||||
/**
|
||||
* Get all asset of a device that are in the database, ID only.
|
||||
*/
|
||||
@Authenticated()
|
||||
@Get('/:deviceId')
|
||||
async getUserAssetsByDeviceId(@GetAuthUser() authUser: AuthUserDto, @Param() { deviceId }: DeviceIdDto) {
|
||||
return await this.assetService.getUserAssetsByDeviceId(authUser, deviceId);
|
||||
@@ -281,7 +272,7 @@ export class AssetController {
|
||||
/**
|
||||
* Get a single asset's information
|
||||
*/
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Get('/assetById/:assetId')
|
||||
async getAssetById(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -294,7 +285,6 @@ export class AssetController {
|
||||
/**
|
||||
* Update an asset
|
||||
*/
|
||||
@Authenticated()
|
||||
@Put('/:assetId')
|
||||
async updateAsset(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -305,7 +295,6 @@ export class AssetController {
|
||||
return await this.assetService.updateAsset(authUser, assetId, dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete('/')
|
||||
async deleteAsset(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -318,7 +307,7 @@ export class AssetController {
|
||||
/**
|
||||
* Check duplicated asset before uploading - for Web upload used
|
||||
*/
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Post('/check')
|
||||
@HttpCode(200)
|
||||
async checkDuplicateAsset(
|
||||
@@ -331,7 +320,6 @@ export class AssetController {
|
||||
/**
|
||||
* Checks if multiple assets exist on the server and returns all existing - used by background backup
|
||||
*/
|
||||
@Authenticated()
|
||||
@Post('/exist')
|
||||
@HttpCode(200)
|
||||
async checkExistingAssets(
|
||||
@@ -344,7 +332,6 @@ export class AssetController {
|
||||
/**
|
||||
* Checks if assets exist by checksums
|
||||
*/
|
||||
@Authenticated()
|
||||
@Post('/bulk-upload-check')
|
||||
@HttpCode(200)
|
||||
bulkUploadCheck(
|
||||
@@ -354,7 +341,6 @@ export class AssetController {
|
||||
return this.assetService.bulkUploadCheck(authUser, dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('/shared-link')
|
||||
async createAssetsSharedLink(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -363,7 +349,7 @@ export class AssetController {
|
||||
return await this.assetService.createAssetsSharedLink(authUser, dto);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Patch('/shared-link/add')
|
||||
async addAssetsToSharedLink(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -372,7 +358,7 @@ export class AssetController {
|
||||
return await this.assetService.addAssetsToSharedLink(authUser, dto);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Patch('/shared-link/remove')
|
||||
async removeAssetsFromSharedLink(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
|
||||
@@ -30,14 +30,15 @@ import {
|
||||
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||
import { when } from 'jest-when';
|
||||
import { AssetRejectReason, AssetUploadAction } from './response-dto/asset-check-response.dto';
|
||||
|
||||
const _getCreateAssetDto = (): CreateAssetDto => {
|
||||
const createAssetDto = new CreateAssetDto();
|
||||
createAssetDto.deviceAssetId = 'deviceAssetId';
|
||||
createAssetDto.deviceId = 'deviceId';
|
||||
createAssetDto.assetType = AssetType.OTHER;
|
||||
createAssetDto.fileCreatedAt = '2022-06-19T23:41:36.910Z';
|
||||
createAssetDto.fileModifiedAt = '2022-06-19T23:41:36.910Z';
|
||||
createAssetDto.fileCreatedAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
createAssetDto.fileModifiedAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
createAssetDto.isFavorite = false;
|
||||
createAssetDto.isArchived = false;
|
||||
createAssetDto.duration = '0:00:00.000000';
|
||||
@@ -55,9 +56,9 @@ const _getAsset_1 = () => {
|
||||
asset_1.type = AssetType.VIDEO;
|
||||
asset_1.originalPath = 'fake_path/asset_1.jpeg';
|
||||
asset_1.resizePath = '';
|
||||
asset_1.fileModifiedAt = '2022-06-19T23:41:36.910Z';
|
||||
asset_1.fileCreatedAt = '2022-06-19T23:41:36.910Z';
|
||||
asset_1.updatedAt = '2022-06-19T23:41:36.910Z';
|
||||
asset_1.fileModifiedAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
asset_1.fileCreatedAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
asset_1.updatedAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
asset_1.isFavorite = false;
|
||||
asset_1.isArchived = false;
|
||||
asset_1.mimeType = 'image/jpeg';
|
||||
@@ -80,9 +81,9 @@ const _getAsset_2 = () => {
|
||||
asset_2.type = AssetType.VIDEO;
|
||||
asset_2.originalPath = 'fake_path/asset_2.jpeg';
|
||||
asset_2.resizePath = '';
|
||||
asset_2.fileModifiedAt = '2022-06-19T23:41:36.910Z';
|
||||
asset_2.fileCreatedAt = '2022-06-19T23:41:36.910Z';
|
||||
asset_2.updatedAt = '2022-06-19T23:41:36.910Z';
|
||||
asset_2.fileModifiedAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
asset_2.fileCreatedAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
asset_2.updatedAt = new Date('2022-06-19T23:41:36.910Z');
|
||||
asset_2.isFavorite = false;
|
||||
asset_2.isArchived = false;
|
||||
asset_2.mimeType = 'image/jpeg';
|
||||
@@ -504,4 +505,32 @@ describe('AssetService', () => {
|
||||
expect(storageMock.createReadStream).toHaveBeenCalledWith('fake_path/asset_1.jpeg', 'image/jpeg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('bulkUploadCheck', () => {
|
||||
it('should accept hex and base64 checksums', async () => {
|
||||
const file1 = Buffer.from('d2947b871a706081be194569951b7db246907957', 'hex');
|
||||
const file2 = Buffer.from('53be335e99f18a66ff12e9a901c7a6171dd76573', 'hex');
|
||||
|
||||
assetRepositoryMock.getAssetsByChecksums.mockResolvedValue([
|
||||
{ id: 'asset-1', checksum: file1 },
|
||||
{ id: 'asset-2', checksum: file2 },
|
||||
]);
|
||||
|
||||
await expect(
|
||||
sut.bulkUploadCheck(authStub.admin, {
|
||||
assets: [
|
||||
{ id: '1', checksum: file1.toString('hex') },
|
||||
{ id: '2', checksum: file2.toString('base64') },
|
||||
],
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
results: [
|
||||
{ id: '1', assetId: 'asset-1', action: AssetUploadAction.REJECT, reason: AssetRejectReason.DUPLICATE },
|
||||
{ id: '2', assetId: 'asset-2', action: AssetUploadAction.REJECT, reason: AssetRejectReason.DUPLICATE },
|
||||
],
|
||||
});
|
||||
|
||||
expect(assetRepositoryMock.getAssetsByChecksums).toHaveBeenCalledWith(authStub.admin.id, [file1, file2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -486,17 +486,24 @@ export class AssetService {
|
||||
}
|
||||
|
||||
async bulkUploadCheck(authUser: AuthUserDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {
|
||||
// support base64 and hex checksums
|
||||
for (const asset of dto.assets) {
|
||||
if (asset.checksum.length === 28) {
|
||||
asset.checksum = Buffer.from(asset.checksum, 'base64').toString('hex');
|
||||
}
|
||||
}
|
||||
|
||||
const checksums: Buffer[] = dto.assets.map((asset) => Buffer.from(asset.checksum, 'hex'));
|
||||
const results = await this._assetRepository.getAssetsByChecksums(authUser.id, checksums);
|
||||
const resultsMap: Record<string, string> = {};
|
||||
const checksumMap: Record<string, string> = {};
|
||||
|
||||
for (const { id, checksum } of results) {
|
||||
resultsMap[checksum.toString('hex')] = id;
|
||||
checksumMap[checksum.toString('hex')] = id;
|
||||
}
|
||||
|
||||
return {
|
||||
results: dto.assets.map(({ id, checksum }) => {
|
||||
const duplicate = resultsMap[checksum];
|
||||
const duplicate = checksumMap[checksum];
|
||||
if (duplicate) {
|
||||
return {
|
||||
id,
|
||||
|
||||
@@ -6,6 +6,7 @@ export class AssetBulkUploadCheckItem {
|
||||
@IsNotEmpty()
|
||||
id!: string;
|
||||
|
||||
/** base64 or hex encoded sha1 hash */
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
checksum!: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsBoolean, IsDate, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateAssetsShareLinkDto {
|
||||
@IsArray()
|
||||
@@ -17,9 +18,10 @@ export class CreateAssetsShareLinkDto {
|
||||
})
|
||||
assetIds!: string[];
|
||||
|
||||
@IsString()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
@IsOptional()
|
||||
expiresAt?: string;
|
||||
expiresAt?: Date;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
|
||||
@@ -16,10 +16,10 @@ export class CreateAssetDto {
|
||||
assetType!: AssetType;
|
||||
|
||||
@IsNotEmpty()
|
||||
fileCreatedAt!: string;
|
||||
fileCreatedAt!: Date;
|
||||
|
||||
@IsNotEmpty()
|
||||
fileModifiedAt!: string;
|
||||
fileModifiedAt!: Date;
|
||||
|
||||
@IsNotEmpty()
|
||||
isFavorite!: boolean;
|
||||
|
||||
@@ -8,9 +8,9 @@ import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||
import { mapTag, TagResponseDto } from '@app/domain';
|
||||
import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto';
|
||||
|
||||
@Authenticated()
|
||||
@ApiTags('Tag')
|
||||
@Controller('tag')
|
||||
@Authenticated()
|
||||
export class TagController {
|
||||
constructor(private readonly tagService: TagService) {}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ describe('TagService', () => {
|
||||
email: 'testuser@email.com',
|
||||
profileImagePath: '',
|
||||
shouldChangePassword: true,
|
||||
createdAt: '2022-12-02T19:29:23.603Z',
|
||||
deletedAt: undefined,
|
||||
updatedAt: '2022-12-02T19:29:23.603Z',
|
||||
createdAt: new Date('2022-12-02T19:29:23.603Z'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2022-12-02T19:29:23.603Z'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
oauthId: 'oauth-id-1',
|
||||
|
||||
@@ -19,16 +19,18 @@ import { Body, Controller, Delete, Get, Param, Post, Req, Res } from '@nestjs/co
|
||||
import { ApiBadRequestResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { Request, Response } from 'express';
|
||||
import { GetAuthUser, GetLoginDetails } from '../decorators/auth-user.decorator';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
import { Authenticated, PublicRoute } from '../decorators/authenticated.decorator';
|
||||
import { UseValidation } from '../decorators/use-validation.decorator';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
@ApiTags('Authentication')
|
||||
@Controller('auth')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
export class AuthController {
|
||||
constructor(private readonly service: AuthService) {}
|
||||
|
||||
@PublicRoute()
|
||||
@Post('login')
|
||||
async login(
|
||||
@Body() loginCredential: LoginCredentialDto,
|
||||
@@ -40,43 +42,38 @@ export class AuthController {
|
||||
return response;
|
||||
}
|
||||
|
||||
@PublicRoute()
|
||||
@Post('admin-sign-up')
|
||||
@ApiBadRequestResponse({ description: 'The server already has an admin' })
|
||||
adminSignUp(@Body() signUpCredential: SignUpDto): Promise<AdminSignupResponseDto> {
|
||||
return this.service.adminSignUp(signUpCredential);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('devices')
|
||||
getAuthDevices(@GetAuthUser() authUser: AuthUserDto): Promise<AuthDeviceResponseDto[]> {
|
||||
return this.service.getDevices(authUser);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete('devices')
|
||||
logoutAuthDevices(@GetAuthUser() authUser: AuthUserDto): Promise<void> {
|
||||
return this.service.logoutDevices(authUser);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete('devices/:id')
|
||||
logoutAuthDevice(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.logoutDevice(authUser, id);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('validateToken')
|
||||
validateAccessToken(): ValidateAccessTokenResponseDto {
|
||||
return { authStatus: true };
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('change-password')
|
||||
changePassword(@GetAuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> {
|
||||
return this.service.changePassword(authUser, dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('logout')
|
||||
logout(
|
||||
@Req() req: Request,
|
||||
|
||||
@@ -12,15 +12,17 @@ import { Body, Controller, Get, HttpStatus, Post, Redirect, Req, Res } from '@ne
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Request, Response } from 'express';
|
||||
import { GetAuthUser, GetLoginDetails } from '../decorators/auth-user.decorator';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
import { Authenticated, PublicRoute } from '../decorators/authenticated.decorator';
|
||||
import { UseValidation } from '../decorators/use-validation.decorator';
|
||||
|
||||
@ApiTags('OAuth')
|
||||
@Controller('oauth')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
export class OAuthController {
|
||||
constructor(private service: OAuthService) {}
|
||||
|
||||
@PublicRoute()
|
||||
@Get('mobile-redirect')
|
||||
@Redirect()
|
||||
mobileRedirect(@Req() req: Request) {
|
||||
@@ -30,11 +32,13 @@ export class OAuthController {
|
||||
};
|
||||
}
|
||||
|
||||
@PublicRoute()
|
||||
@Post('config')
|
||||
generateConfig(@Body() dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> {
|
||||
return this.service.generateConfig(dto);
|
||||
}
|
||||
|
||||
@PublicRoute()
|
||||
@Post('callback')
|
||||
async callback(
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
@@ -46,13 +50,11 @@ export class OAuthController {
|
||||
return response;
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('link')
|
||||
link(@GetAuthUser() authUser: AuthUserDto, @Body() dto: OAuthCallbackDto): Promise<UserResponseDto> {
|
||||
return this.service.link(authUser, dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('unlink')
|
||||
unlink(@GetAuthUser() authUser: AuthUserDto): Promise<UserResponseDto> {
|
||||
return this.service.unlink(authUser);
|
||||
|
||||
@@ -8,11 +8,11 @@ import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
@ApiTags('Partner')
|
||||
@Controller('partner')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
export class PartnerController {
|
||||
constructor(private service: PartnerService) {}
|
||||
|
||||
@Authenticated()
|
||||
@Get()
|
||||
@ApiQuery({ name: 'direction', type: 'string', enum: PartnerDirection, required: true })
|
||||
getPartners(
|
||||
@@ -22,13 +22,11 @@ export class PartnerController {
|
||||
return this.service.getAll(authUser, direction);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post(':id')
|
||||
createPartner(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
|
||||
return this.service.create(authUser, id);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete(':id')
|
||||
removePartner(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(authUser, id);
|
||||
|
||||
@@ -7,32 +7,34 @@ import {
|
||||
} from '@app/domain';
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
import { AdminRoute, Authenticated, PublicRoute } from '../decorators/authenticated.decorator';
|
||||
import { UseValidation } from '../decorators/use-validation.decorator';
|
||||
|
||||
@ApiTags('Server Info')
|
||||
@Controller('server-info')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
export class ServerInfoController {
|
||||
constructor(private service: ServerInfoService) {}
|
||||
|
||||
@Authenticated()
|
||||
@Get()
|
||||
getServerInfo(): Promise<ServerInfoResponseDto> {
|
||||
return this.service.getInfo();
|
||||
}
|
||||
|
||||
@PublicRoute()
|
||||
@Get('/ping')
|
||||
pingServer(): ServerPingResponse {
|
||||
return this.service.ping();
|
||||
}
|
||||
|
||||
@PublicRoute()
|
||||
@Get('/version')
|
||||
getServerVersion(): ServerVersionReponseDto {
|
||||
return this.service.getVersion();
|
||||
}
|
||||
|
||||
@Authenticated({ admin: true })
|
||||
@AdminRoute()
|
||||
@Get('/stats')
|
||||
getStats(): Promise<ServerStatsResponseDto> {
|
||||
return this.service.getStats();
|
||||
|
||||
@@ -2,29 +2,28 @@ import { AuthUserDto, EditSharedLinkDto, SharedLinkResponseDto, ShareService } f
|
||||
import { Body, Controller, Delete, Get, Param, Patch } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { GetAuthUser } from '../decorators/auth-user.decorator';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
import { Authenticated, SharedLinkRoute } from '../decorators/authenticated.decorator';
|
||||
import { UseValidation } from '../decorators/use-validation.decorator';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
@ApiTags('share')
|
||||
@Controller('share')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
export class SharedLinkController {
|
||||
constructor(private readonly service: ShareService) {}
|
||||
|
||||
@Authenticated()
|
||||
@Get()
|
||||
getAllSharedLinks(@GetAuthUser() authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
|
||||
return this.service.getAll(authUser);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@SharedLinkRoute()
|
||||
@Get('me')
|
||||
getMySharedLink(@GetAuthUser() authUser: AuthUserDto): Promise<SharedLinkResponseDto> {
|
||||
return this.service.getMine(authUser);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get(':id')
|
||||
getSharedLinkById(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@@ -33,13 +32,11 @@ export class SharedLinkController {
|
||||
return this.service.getById(authUser, id, true);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete(':id')
|
||||
removeSharedLink(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(authUser, id);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Patch(':id')
|
||||
editSharedLink(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
Header,
|
||||
} from '@nestjs/common';
|
||||
import { UserService } from '@app/domain';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
import { AdminRoute, Authenticated, PublicRoute } from '../decorators/authenticated.decorator';
|
||||
import { AuthUserDto, GetAuthUser } from '../decorators/auth-user.decorator';
|
||||
import { CreateUserDto } from '@app/domain';
|
||||
import { UpdateUserDto } from '@app/domain';
|
||||
@@ -32,58 +32,55 @@ import { UserIdDto } from '@app/domain/user/dto/user-id.dto';
|
||||
|
||||
@ApiTags('User')
|
||||
@Controller('user')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
export class UserController {
|
||||
constructor(private service: UserService) {}
|
||||
|
||||
@Authenticated()
|
||||
@Get()
|
||||
getAllUsers(@GetAuthUser() authUser: AuthUserDto, @Query('isAll') isAll: boolean): Promise<UserResponseDto[]> {
|
||||
return this.service.getAllUsers(authUser, isAll);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('/info/:userId')
|
||||
getUserById(@Param() { userId }: UserIdDto): Promise<UserResponseDto> {
|
||||
return this.service.getUserById(userId);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('me')
|
||||
getMyUserInfo(@GetAuthUser() authUser: AuthUserDto): Promise<UserResponseDto> {
|
||||
return this.service.getUserInfo(authUser);
|
||||
}
|
||||
|
||||
@Authenticated({ admin: true })
|
||||
@AdminRoute()
|
||||
@Post()
|
||||
createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
|
||||
return this.service.createUser(createUserDto);
|
||||
}
|
||||
|
||||
@PublicRoute()
|
||||
@Get('/count')
|
||||
getUserCount(@Query() dto: UserCountDto): Promise<UserCountResponseDto> {
|
||||
return this.service.getUserCount(dto);
|
||||
}
|
||||
|
||||
@Authenticated({ admin: true })
|
||||
@AdminRoute()
|
||||
@Delete('/:userId')
|
||||
deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param() { userId }: UserIdDto): Promise<UserResponseDto> {
|
||||
return this.service.deleteUser(authUser, userId);
|
||||
}
|
||||
|
||||
@Authenticated({ admin: true })
|
||||
@AdminRoute()
|
||||
@Post('/:userId/restore')
|
||||
restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param() { userId }: UserIdDto): Promise<UserResponseDto> {
|
||||
return this.service.restoreUser(authUser, userId);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Put()
|
||||
updateUser(@GetAuthUser() authUser: AuthUserDto, @Body() updateUserDto: UpdateUserDto): Promise<UserResponseDto> {
|
||||
return this.service.updateUser(authUser, updateUserDto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@UseInterceptors(FileInterceptor('file', profileImageUploadOption))
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiBody({
|
||||
@@ -98,7 +95,6 @@ export class UserController {
|
||||
return this.service.createProfileImage(authUser, fileInfo);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('/profile-image/:userId')
|
||||
@Header('Cache-Control', 'max-age=600')
|
||||
async getProfileImage(@Param() { userId }: UserIdDto, @Response({ passthrough: true }) res: Res): Promise<any> {
|
||||
|
||||
@@ -11,8 +11,16 @@ export enum Metadata {
|
||||
AUTH_ROUTE = 'auth_route',
|
||||
ADMIN_ROUTE = 'admin_route',
|
||||
SHARED_ROUTE = 'shared_route',
|
||||
PUBLIC_SECURITY = 'public_security',
|
||||
}
|
||||
|
||||
const adminDecorator = SetMetadata(Metadata.ADMIN_ROUTE, true);
|
||||
|
||||
const sharedLinkDecorators = [
|
||||
SetMetadata(Metadata.SHARED_ROUTE, true),
|
||||
ApiQuery({ name: 'key', type: String, required: false }),
|
||||
];
|
||||
|
||||
export const Authenticated = (options: AuthenticatedOptions = {}) => {
|
||||
const decorators: MethodDecorator[] = [
|
||||
ApiBearerAuth(),
|
||||
@@ -22,13 +30,17 @@ export const Authenticated = (options: AuthenticatedOptions = {}) => {
|
||||
];
|
||||
|
||||
if (options.admin) {
|
||||
decorators.push(SetMetadata(Metadata.ADMIN_ROUTE, true));
|
||||
decorators.push(adminDecorator);
|
||||
}
|
||||
|
||||
if (options.isShared) {
|
||||
decorators.push(SetMetadata(Metadata.SHARED_ROUTE, true));
|
||||
decorators.push(ApiQuery({ name: 'key', type: String, required: false }));
|
||||
decorators.push(...sharedLinkDecorators);
|
||||
}
|
||||
|
||||
return applyDecorators(...decorators);
|
||||
};
|
||||
|
||||
export const PublicRoute = () =>
|
||||
applyDecorators(SetMetadata(Metadata.AUTH_ROUTE, false), ApiSecurity(Metadata.PUBLIC_SECURITY));
|
||||
export const SharedLinkRoute = () => applyDecorators(...sharedLinkDecorators);
|
||||
export const AdminRoute = () => adminDecorator;
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
MACHINE_LEARNING_ENABLED,
|
||||
SearchService,
|
||||
SERVER_VERSION,
|
||||
StorageService,
|
||||
} from '@app/domain';
|
||||
import { RedisIoAdapter } from '@app/infra';
|
||||
import { Logger } from '@nestjs/common';
|
||||
@@ -72,6 +73,8 @@ async function bootstrap() {
|
||||
customSiteTitle: 'Immich API Documentation',
|
||||
});
|
||||
|
||||
app.get(StorageService).init();
|
||||
|
||||
await app.listen(serverPort, () => {
|
||||
if (process.env.NODE_ENV == 'development') {
|
||||
// Generate API Documentation only in development mode
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { OpenAPIObject } from '@nestjs/swagger';
|
||||
import { Metadata } from '../decorators/authenticated.decorator';
|
||||
|
||||
export function patchOpenAPI(document: OpenAPIObject) {
|
||||
for (const path of Object.values(document.paths)) {
|
||||
@@ -18,6 +19,10 @@ export function patchOpenAPI(document: OpenAPIObject) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((operation.security || []).find((item) => !!item[Metadata.PUBLIC_SECURITY])) {
|
||||
delete operation.security;
|
||||
}
|
||||
|
||||
if (operation.summary === '') {
|
||||
delete operation.summary;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ export class MetadataExtractionProcessor {
|
||||
}
|
||||
|
||||
await this.exifRepository.upsert(newExif, { conflictPaths: ['assetId'] });
|
||||
await this.assetRepository.save({ id: asset.id, fileCreatedAt: fileCreatedAt?.toISOString() });
|
||||
await this.assetRepository.save({ id: asset.id, fileCreatedAt: fileCreatedAt || undefined });
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -227,9 +227,9 @@ export class MetadataExtractionProcessor {
|
||||
const videoTags = data.format.tags;
|
||||
if (videoTags) {
|
||||
if (videoTags['com.apple.quicktime.creationdate']) {
|
||||
fileCreatedAt = String(videoTags['com.apple.quicktime.creationdate']);
|
||||
fileCreatedAt = new Date(videoTags['com.apple.quicktime.creationdate']);
|
||||
} else if (videoTags['creation_time']) {
|
||||
fileCreatedAt = String(videoTags['creation_time']);
|
||||
fileCreatedAt = new Date(videoTags['creation_time']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ function web {
|
||||
wget -O apiInner.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/v6.0.1/modules/openapi-generator/src/main/resources/typescript-axios/apiInner.mustache
|
||||
patch -u apiInner.mustache < apiInner.mustache.patch
|
||||
cd ../../..
|
||||
npx --yes @openapitools/openapi-generator-cli generate -g typescript-axios -i ./immich-openapi-specs.json -o ../web/src/api/open-api -t ./openapi-generator/templates/web
|
||||
npx --yes @openapitools/openapi-generator-cli generate -g typescript-axios -i ./immich-openapi-specs.json -o ../web/src/api/open-api -t ./openapi-generator/templates/web --additional-properties=useSingleRequestParameter=true
|
||||
}
|
||||
|
||||
if [[ $1 == 'mobile' ]]; then
|
||||
|
||||
@@ -4148,7 +4148,7 @@
|
||||
"info": {
|
||||
"title": "Immich",
|
||||
"description": "Immich API",
|
||||
"version": "1.58.0",
|
||||
"version": "1.59.0",
|
||||
"contact": {}
|
||||
},
|
||||
"tags": [],
|
||||
@@ -4196,9 +4196,6 @@
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"profileImagePath": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -4208,11 +4205,17 @@
|
||||
"isAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"deletedAt": {
|
||||
"createdAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"deletedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"oauthId": {
|
||||
@@ -4225,10 +4228,12 @@
|
||||
"firstName",
|
||||
"lastName",
|
||||
"storageLabel",
|
||||
"createdAt",
|
||||
"profileImagePath",
|
||||
"shouldChangePassword",
|
||||
"isAdmin",
|
||||
"createdAt",
|
||||
"deletedAt",
|
||||
"updatedAt",
|
||||
"oauthId"
|
||||
]
|
||||
},
|
||||
@@ -4446,17 +4451,19 @@
|
||||
"originalFileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"resizePath": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
"resized": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"fileCreatedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"fileModifiedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"isFavorite": {
|
||||
@@ -4472,14 +4479,6 @@
|
||||
"duration": {
|
||||
"type": "string"
|
||||
},
|
||||
"webpPath": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"encodedVideoPath": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"exifInfo": {
|
||||
"$ref": "#/components/schemas/ExifResponseDto"
|
||||
},
|
||||
@@ -4501,6 +4500,10 @@
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonResponseDto"
|
||||
}
|
||||
},
|
||||
"checksum": {
|
||||
"type": "string",
|
||||
"description": "base64 encoded sha1 hash"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -4511,7 +4514,7 @@
|
||||
"deviceId",
|
||||
"originalPath",
|
||||
"originalFileName",
|
||||
"resizePath",
|
||||
"resized",
|
||||
"fileCreatedAt",
|
||||
"fileModifiedAt",
|
||||
"updatedAt",
|
||||
@@ -4519,7 +4522,7 @@
|
||||
"isArchived",
|
||||
"mimeType",
|
||||
"duration",
|
||||
"webpPath"
|
||||
"checksum"
|
||||
]
|
||||
},
|
||||
"AlbumResponseDto": {
|
||||
@@ -4538,9 +4541,11 @@
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"albumThumbnailAssetId": {
|
||||
@@ -4635,9 +4640,11 @@
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -4802,6 +4809,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -5398,9 +5406,11 @@
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"expiresAt": {
|
||||
"format": "date-time",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
@@ -5443,6 +5453,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"expiresAt": {
|
||||
"format": "date-time",
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
@@ -5788,9 +5799,11 @@
|
||||
"type": "string"
|
||||
},
|
||||
"fileCreatedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"fileModifiedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"isFavorite": {
|
||||
@@ -6173,7 +6186,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"checksum": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "base64 or hex encoded sha1 hash"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6254,6 +6268,7 @@
|
||||
}
|
||||
},
|
||||
"expiresAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"allowUpload": {
|
||||
@@ -6392,8 +6407,8 @@
|
||||
"format": "uuid"
|
||||
},
|
||||
"expiresAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"allowUpload": {
|
||||
"type": "boolean"
|
||||
|
||||
@@ -128,7 +128,6 @@ describe(AlbumService.name, () => {
|
||||
createdAt: expect.anything(),
|
||||
id: 'album-1',
|
||||
owner: {
|
||||
createdAt: '2021-01-01',
|
||||
email: 'admin@test.com',
|
||||
firstName: 'admin_first_name',
|
||||
id: 'admin_id',
|
||||
@@ -138,7 +137,9 @@ describe(AlbumService.name, () => {
|
||||
profileImagePath: '',
|
||||
shouldChangePassword: false,
|
||||
storageLabel: 'admin',
|
||||
updatedAt: '2021-01-01',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
},
|
||||
ownerId: 'admin_id',
|
||||
shared: false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AlbumEntity, AssetEntity, UserEntity } from '@app/infra/entities';
|
||||
import { BadRequestException, ForbiddenException, Inject, Injectable } from '@nestjs/common';
|
||||
import { IAssetRepository } from '../asset';
|
||||
import { IAssetRepository, mapAsset } from '../asset';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { IJobRepository, JobName } from '../job';
|
||||
import { IAlbumRepository } from './album.repository';
|
||||
@@ -40,6 +40,7 @@ export class AlbumService {
|
||||
return albums.map((album) => {
|
||||
return {
|
||||
...album,
|
||||
assets: album?.assets?.map(mapAsset),
|
||||
sharedLinks: undefined, // Don't return shared links
|
||||
shared: album.sharedLinks?.length > 0 || album.sharedUsers?.length > 0,
|
||||
assetCount: albumsAssetCountObj[album.id],
|
||||
|
||||
@@ -7,8 +7,8 @@ export class AlbumResponseDto {
|
||||
id!: string;
|
||||
ownerId!: string;
|
||||
albumName!: string;
|
||||
createdAt!: string;
|
||||
updatedAt!: string;
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
albumThumbnailAssetId!: string | null;
|
||||
shared!: boolean;
|
||||
sharedUsers!: UserResponseDto[];
|
||||
|
||||
@@ -8,8 +8,8 @@ export class APIKeyCreateResponseDto {
|
||||
export class APIKeyResponseDto {
|
||||
id!: string;
|
||||
name!: string;
|
||||
createdAt!: string;
|
||||
updatedAt!: string;
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
}
|
||||
|
||||
export function mapKey(entity: APIKeyEntity): APIKeyResponseDto {
|
||||
|
||||
@@ -15,8 +15,8 @@ export interface LivePhotoSearchOptions {
|
||||
|
||||
export interface MapMarkerSearchOptions {
|
||||
isFavorite?: boolean;
|
||||
fileCreatedBefore?: string;
|
||||
fileCreatedAfter?: string;
|
||||
fileCreatedBefore?: Date;
|
||||
fileCreatedAfter?: Date;
|
||||
}
|
||||
|
||||
export interface MapMarker {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { toBoolean } from 'apps/immich/src/utils/transform.util';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsBoolean, IsISO8601, IsOptional } from 'class-validator';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { IsBoolean, IsDate, IsOptional } from 'class-validator';
|
||||
|
||||
export class MapMarkerDto {
|
||||
@ApiProperty()
|
||||
@@ -10,13 +10,13 @@ export class MapMarkerDto {
|
||||
@Transform(toBoolean)
|
||||
isFavorite?: boolean;
|
||||
|
||||
@ApiProperty({ format: 'date-time' })
|
||||
@IsOptional()
|
||||
@IsISO8601({ strict: true, strictSeparator: true })
|
||||
fileCreatedAfter?: string;
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
fileCreatedAfter?: Date;
|
||||
|
||||
@ApiProperty({ format: 'date-time' })
|
||||
@IsOptional()
|
||||
@IsISO8601({ strict: true, strictSeparator: true })
|
||||
fileCreatedBefore?: string;
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
fileCreatedBefore?: Date;
|
||||
}
|
||||
|
||||
@@ -15,21 +15,21 @@ export class AssetResponseDto {
|
||||
type!: AssetType;
|
||||
originalPath!: string;
|
||||
originalFileName!: string;
|
||||
resizePath!: string | null;
|
||||
fileCreatedAt!: string;
|
||||
fileModifiedAt!: string;
|
||||
updatedAt!: string;
|
||||
resized!: boolean;
|
||||
fileCreatedAt!: Date;
|
||||
fileModifiedAt!: Date;
|
||||
updatedAt!: Date;
|
||||
isFavorite!: boolean;
|
||||
isArchived!: boolean;
|
||||
mimeType!: string | null;
|
||||
duration!: string;
|
||||
webpPath!: string | null;
|
||||
encodedVideoPath?: string | null;
|
||||
exifInfo?: ExifResponseDto;
|
||||
smartInfo?: SmartInfoResponseDto;
|
||||
livePhotoVideoId?: string | null;
|
||||
tags?: TagResponseDto[];
|
||||
people?: PersonResponseDto[];
|
||||
/**base64 encoded sha1 hash */
|
||||
checksum!: string;
|
||||
}
|
||||
|
||||
export function mapAsset(entity: AssetEntity): AssetResponseDto {
|
||||
@@ -41,21 +41,20 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto {
|
||||
type: entity.type,
|
||||
originalPath: entity.originalPath,
|
||||
originalFileName: entity.originalFileName,
|
||||
resizePath: entity.resizePath,
|
||||
resized: !!entity.resizePath,
|
||||
fileCreatedAt: entity.fileCreatedAt,
|
||||
fileModifiedAt: entity.fileModifiedAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
isFavorite: entity.isFavorite,
|
||||
isArchived: entity.isArchived,
|
||||
mimeType: entity.mimeType,
|
||||
webpPath: entity.webpPath,
|
||||
encodedVideoPath: entity.encodedVideoPath,
|
||||
duration: entity.duration ?? '0:00:00.00000',
|
||||
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
||||
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
||||
livePhotoVideoId: entity.livePhotoVideoId,
|
||||
tags: entity.tags?.map(mapTag),
|
||||
people: entity.faces?.map(mapFace),
|
||||
checksum: entity.checksum.toString('base64'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -68,20 +67,19 @@ export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto {
|
||||
type: entity.type,
|
||||
originalPath: entity.originalPath,
|
||||
originalFileName: entity.originalFileName,
|
||||
resizePath: entity.resizePath,
|
||||
resized: !!entity.resizePath,
|
||||
fileCreatedAt: entity.fileCreatedAt,
|
||||
fileModifiedAt: entity.fileModifiedAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
isFavorite: entity.isFavorite,
|
||||
isArchived: entity.isArchived,
|
||||
mimeType: entity.mimeType,
|
||||
webpPath: entity.webpPath,
|
||||
encodedVideoPath: entity.encodedVideoPath,
|
||||
duration: entity.duration ?? '0:00:00.00000',
|
||||
exifInfo: undefined,
|
||||
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
||||
livePhotoVideoId: entity.livePhotoVideoId,
|
||||
tags: entity.tags?.map(mapTag),
|
||||
people: entity.faces?.map(mapFace),
|
||||
checksum: entity.checksum.toString('base64'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,10 +231,10 @@ describe('AuthService', () => {
|
||||
|
||||
it('should sign up the admin', async () => {
|
||||
userMock.getAdmin.mockResolvedValue(null);
|
||||
userMock.create.mockResolvedValue({ ...dto, id: 'admin', createdAt: 'today' } as UserEntity);
|
||||
userMock.create.mockResolvedValue({ ...dto, id: 'admin', createdAt: new Date('2021-01-01') } as UserEntity);
|
||||
await expect(sut.adminSignUp(dto)).resolves.toEqual({
|
||||
id: 'admin',
|
||||
createdAt: 'today',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
email: 'test@immich.com',
|
||||
firstName: 'immich',
|
||||
lastName: 'admin',
|
||||
|
||||
@@ -5,7 +5,7 @@ export class AdminSignupResponseDto {
|
||||
email!: string;
|
||||
firstName!: string;
|
||||
lastName!: string;
|
||||
createdAt!: string;
|
||||
createdAt!: Date;
|
||||
}
|
||||
|
||||
export function mapAdminSignupResponse(entity: UserEntity): AdminSignupResponseDto {
|
||||
|
||||
@@ -6,8 +6,6 @@ import { PartnerService } from './partner.service';
|
||||
|
||||
const responseDto = {
|
||||
admin: {
|
||||
createdAt: '2021-01-01',
|
||||
deletedAt: undefined,
|
||||
email: 'admin@test.com',
|
||||
firstName: 'admin_first_name',
|
||||
id: 'admin_id',
|
||||
@@ -16,12 +14,12 @@ const responseDto = {
|
||||
oauthId: '',
|
||||
profileImagePath: '',
|
||||
shouldChangePassword: false,
|
||||
updatedAt: '2021-01-01',
|
||||
storageLabel: 'admin',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
},
|
||||
user1: {
|
||||
createdAt: '2021-01-01',
|
||||
deletedAt: undefined,
|
||||
email: 'immich@test.com',
|
||||
firstName: 'immich_first_name',
|
||||
id: 'user-id',
|
||||
@@ -30,8 +28,10 @@ const responseDto = {
|
||||
oauthId: '',
|
||||
profileImagePath: '',
|
||||
shouldChangePassword: false,
|
||||
updatedAt: '2021-01-01',
|
||||
storageLabel: null,
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { mapAlbum } from '../album';
|
||||
import { IAlbumRepository } from '../album/album.repository';
|
||||
import { mapAsset } from '../asset';
|
||||
import { AssetResponseDto, mapAsset } from '../asset';
|
||||
import { IAssetRepository } from '../asset/asset.repository';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { MACHINE_LEARNING_ENABLED } from '../domain.constant';
|
||||
@@ -103,9 +103,13 @@ export class SearchService {
|
||||
}
|
||||
}
|
||||
|
||||
async getExploreData(authUser: AuthUserDto): Promise<SearchExploreItem<AssetEntity>[]> {
|
||||
async getExploreData(authUser: AuthUserDto): Promise<SearchExploreItem<AssetResponseDto>[]> {
|
||||
this.assertEnabled();
|
||||
return this.searchRepository.explore(authUser.id);
|
||||
const results = await this.searchRepository.explore(authUser.id);
|
||||
return results.map(({ fieldName, items }) => ({
|
||||
fieldName,
|
||||
items: items.map(({ value, data }) => ({ value, data: mapAsset(data) })),
|
||||
}));
|
||||
}
|
||||
|
||||
async search(authUser: AuthUserDto, dto: SearchDto): Promise<SearchResponseDto> {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AlbumEntity, AssetEntity, SharedLinkType } from '@app/infra/entities';
|
||||
|
||||
export class CreateSharedLinkDto {
|
||||
description?: string;
|
||||
expiresAt?: string;
|
||||
expiresAt?: Date;
|
||||
type!: SharedLinkType;
|
||||
assets!: AssetEntity[];
|
||||
album?: AlbumEntity;
|
||||
|
||||
@@ -5,7 +5,7 @@ export class EditSharedLinkDto {
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
expiresAt?: string | null;
|
||||
expiresAt?: Date | null;
|
||||
|
||||
@IsOptional()
|
||||
allowUpload?: boolean;
|
||||
|
||||
@@ -12,8 +12,8 @@ export class SharedLinkResponseDto {
|
||||
|
||||
@ApiProperty({ enumName: 'SharedLinkType', enum: SharedLinkType })
|
||||
type!: SharedLinkType;
|
||||
createdAt!: string;
|
||||
expiresAt!: string | null;
|
||||
createdAt!: Date;
|
||||
expiresAt!: Date | null;
|
||||
assets!: AssetResponseDto[];
|
||||
album?: AlbumResponseDto;
|
||||
allowUpload!: boolean;
|
||||
|
||||
@@ -23,7 +23,7 @@ export class ShareCore {
|
||||
key: Buffer.from(this.cryptoRepository.randomBytes(50)),
|
||||
description: dto.description,
|
||||
userId,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
expiresAt: dto.expiresAt ?? null,
|
||||
type: dto.type,
|
||||
assets: dto.assets,
|
||||
|
||||
@@ -107,7 +107,7 @@ export class StorageTemplateCore {
|
||||
this.render(
|
||||
template,
|
||||
{
|
||||
fileCreatedAt: new Date().toISOString(),
|
||||
fileCreatedAt: new Date(),
|
||||
originalPath: '/upload/test/IMG_123.jpg',
|
||||
type: AssetType.IMAGE,
|
||||
} as AssetEntity,
|
||||
@@ -140,7 +140,7 @@ export class StorageTemplateCore {
|
||||
filetypefull: asset.type == AssetType.IMAGE ? 'IMAGE' : 'VIDEO',
|
||||
};
|
||||
|
||||
const dt = luxon.DateTime.fromISO(new Date(asset.fileCreatedAt).toISOString());
|
||||
const dt = luxon.DateTime.fromJSDate(asset.fileCreatedAt);
|
||||
|
||||
const dateTokens = [
|
||||
...supportedYearTokens,
|
||||
|
||||
@@ -15,6 +15,13 @@ describe(StorageService.name, () => {
|
||||
expect(sut).toBeDefined();
|
||||
});
|
||||
|
||||
describe('init', () => {
|
||||
it('should create the library folder on initialization', () => {
|
||||
sut.init();
|
||||
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/library');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleDeleteFiles', () => {
|
||||
it('should handle null values', async () => {
|
||||
await sut.handleDeleteFiles({ files: [undefined, null] });
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { IDeleteFilesJob } from '../job';
|
||||
import { StorageCore, StorageFolder } from './storage.core';
|
||||
import { IStorageRepository } from './storage.repository';
|
||||
|
||||
@Injectable()
|
||||
export class StorageService {
|
||||
private logger = new Logger(StorageService.name);
|
||||
private storageCore = new StorageCore();
|
||||
|
||||
constructor(@Inject(IStorageRepository) private storageRepository: IStorageRepository) {}
|
||||
|
||||
init() {
|
||||
const libraryBase = this.storageCore.getBaseFolder(StorageFolder.LIBRARY);
|
||||
this.storageRepository.mkdirSync(libraryBase);
|
||||
}
|
||||
|
||||
async handleDeleteFiles(job: IDeleteFilesJob) {
|
||||
const { files } = job;
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ export class UserResponseDto {
|
||||
firstName!: string;
|
||||
lastName!: string;
|
||||
storageLabel!: string | null;
|
||||
createdAt!: string;
|
||||
profileImagePath!: string;
|
||||
shouldChangePassword!: boolean;
|
||||
isAdmin!: boolean;
|
||||
deletedAt?: Date;
|
||||
updatedAt?: string;
|
||||
createdAt!: Date;
|
||||
deletedAt!: Date | null;
|
||||
updatedAt!: Date;
|
||||
oauthId!: string;
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ export function mapUser(entity: UserEntity): UserResponseDto {
|
||||
firstName: entity.firstName,
|
||||
lastName: entity.lastName,
|
||||
storageLabel: entity.storageLabel,
|
||||
createdAt: entity.createdAt,
|
||||
profileImagePath: entity.profileImagePath,
|
||||
shouldChangePassword: entity.shouldChangePassword,
|
||||
isAdmin: entity.isAdmin,
|
||||
createdAt: entity.createdAt,
|
||||
deletedAt: entity.deletedAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
oauthId: entity.oauthId,
|
||||
|
||||
@@ -47,8 +47,9 @@ const adminUser: UserEntity = Object.freeze({
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: '2021-01-01',
|
||||
updatedAt: '2021-01-01',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
storageLabel: 'admin',
|
||||
@@ -64,8 +65,9 @@ const immichUser: UserEntity = Object.freeze({
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: '2021-01-01',
|
||||
updatedAt: '2021-01-01',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
storageLabel: null,
|
||||
@@ -81,8 +83,9 @@ const updatedImmichUser: UserEntity = Object.freeze({
|
||||
oauthId: '',
|
||||
shouldChangePassword: true,
|
||||
profileImagePath: '',
|
||||
createdAt: '2021-01-01',
|
||||
updatedAt: '2021-01-01',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
storageLabel: null,
|
||||
@@ -91,15 +94,15 @@ const updatedImmichUser: UserEntity = Object.freeze({
|
||||
const adminUserResponse = Object.freeze({
|
||||
id: adminUserAuth.id,
|
||||
email: 'admin@test.com',
|
||||
deletedAt: undefined,
|
||||
firstName: 'admin_first_name',
|
||||
lastName: 'admin_last_name',
|
||||
isAdmin: true,
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: '2021-01-01',
|
||||
updatedAt: '2021-01-01',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
storageLabel: 'admin',
|
||||
});
|
||||
|
||||
@@ -140,15 +143,15 @@ describe(UserService.name, () => {
|
||||
{
|
||||
id: adminUserAuth.id,
|
||||
email: 'admin@test.com',
|
||||
deletedAt: undefined,
|
||||
firstName: 'admin_first_name',
|
||||
lastName: 'admin_last_name',
|
||||
isAdmin: true,
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: '2021-01-01',
|
||||
updatedAt: '2021-01-01',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
storageLabel: 'admin',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -85,8 +85,9 @@ export const userEntityStub = {
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: '2021-01-01',
|
||||
updatedAt: '2021-01-01',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
}),
|
||||
@@ -99,8 +100,9 @@ export const userEntityStub = {
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: '2021-01-01',
|
||||
updatedAt: '2021-01-01',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
}),
|
||||
@@ -113,8 +115,9 @@ export const userEntityStub = {
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: '2021-01-01',
|
||||
updatedAt: '2021-01-01',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
}),
|
||||
@@ -140,8 +143,8 @@ export const assetEntityStub = {
|
||||
id: 'asset-id',
|
||||
originalFileName: 'asset_1.jpeg',
|
||||
deviceAssetId: 'device-asset-id',
|
||||
fileModifiedAt: '2023-02-23T05:06:29.716Z',
|
||||
fileCreatedAt: '2023-02-23T05:06:29.716Z',
|
||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
owner: userEntityStub.user1,
|
||||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
@@ -151,8 +154,8 @@ export const assetEntityStub = {
|
||||
type: AssetType.IMAGE,
|
||||
webpPath: null,
|
||||
encodedVideoPath: null,
|
||||
createdAt: '2023-02-23T05:06:29.716Z',
|
||||
updatedAt: '2023-02-23T05:06:29.716Z',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
mimeType: null,
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
@@ -168,8 +171,8 @@ export const assetEntityStub = {
|
||||
image: Object.freeze<AssetEntity>({
|
||||
id: 'asset-id',
|
||||
deviceAssetId: 'device-asset-id',
|
||||
fileModifiedAt: '2023-02-23T05:06:29.716Z',
|
||||
fileCreatedAt: '2023-02-23T05:06:29.716Z',
|
||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
owner: userEntityStub.user1,
|
||||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
@@ -179,8 +182,8 @@ export const assetEntityStub = {
|
||||
type: AssetType.IMAGE,
|
||||
webpPath: null,
|
||||
encodedVideoPath: null,
|
||||
createdAt: '2023-02-23T05:06:29.716Z',
|
||||
updatedAt: '2023-02-23T05:06:29.716Z',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
mimeType: null,
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
@@ -198,8 +201,8 @@ export const assetEntityStub = {
|
||||
id: 'asset-id',
|
||||
originalFileName: 'asset-id.ext',
|
||||
deviceAssetId: 'device-asset-id',
|
||||
fileModifiedAt: '2023-02-23T05:06:29.716Z',
|
||||
fileCreatedAt: '2023-02-23T05:06:29.716Z',
|
||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
owner: userEntityStub.user1,
|
||||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
@@ -209,8 +212,8 @@ export const assetEntityStub = {
|
||||
type: AssetType.VIDEO,
|
||||
webpPath: null,
|
||||
encodedVideoPath: null,
|
||||
createdAt: '2023-02-23T05:06:29.716Z',
|
||||
updatedAt: '2023-02-23T05:06:29.716Z',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
mimeType: null,
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
@@ -229,8 +232,8 @@ export const assetEntityStub = {
|
||||
ownerId: authStub.user1.id,
|
||||
type: AssetType.VIDEO,
|
||||
isVisible: false,
|
||||
fileModifiedAt: '2022-06-19T23:41:36.910Z',
|
||||
fileCreatedAt: '2022-06-19T23:41:36.910Z',
|
||||
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
} as AssetEntity),
|
||||
|
||||
livePhotoStillAsset: Object.freeze({
|
||||
@@ -240,15 +243,15 @@ export const assetEntityStub = {
|
||||
type: AssetType.IMAGE,
|
||||
livePhotoVideoId: 'live-photo-motion-asset',
|
||||
isVisible: true,
|
||||
fileModifiedAt: '2022-06-19T23:41:36.910Z',
|
||||
fileCreatedAt: '2022-06-19T23:41:36.910Z',
|
||||
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
} as AssetEntity),
|
||||
|
||||
withLocation: Object.freeze<AssetEntity>({
|
||||
id: 'asset-with-favorite-id',
|
||||
deviceAssetId: 'device-asset-id',
|
||||
fileModifiedAt: '2023-02-23T05:06:29.716Z',
|
||||
fileCreatedAt: '2023-02-23T05:06:29.716Z',
|
||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
owner: userEntityStub.user1,
|
||||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
@@ -259,8 +262,8 @@ export const assetEntityStub = {
|
||||
type: AssetType.IMAGE,
|
||||
webpPath: null,
|
||||
encodedVideoPath: null,
|
||||
createdAt: '2023-02-23T05:06:29.716Z',
|
||||
updatedAt: '2023-02-23T05:06:29.716Z',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
mimeType: null,
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
@@ -280,8 +283,8 @@ export const assetEntityStub = {
|
||||
sidecar: Object.freeze<AssetEntity>({
|
||||
id: 'asset-id',
|
||||
deviceAssetId: 'device-asset-id',
|
||||
fileModifiedAt: '2023-02-23T05:06:29.716Z',
|
||||
fileCreatedAt: '2023-02-23T05:06:29.716Z',
|
||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
owner: userEntityStub.user1,
|
||||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
@@ -291,8 +294,8 @@ export const assetEntityStub = {
|
||||
type: AssetType.IMAGE,
|
||||
webpPath: null,
|
||||
encodedVideoPath: null,
|
||||
createdAt: '2023-02-23T05:06:29.716Z',
|
||||
updatedAt: '2023-02-23T05:06:29.716Z',
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
mimeType: null,
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
@@ -317,8 +320,8 @@ export const albumStub = {
|
||||
assets: [],
|
||||
albumThumbnailAsset: null,
|
||||
albumThumbnailAssetId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
sharedLinks: [],
|
||||
sharedUsers: [],
|
||||
}),
|
||||
@@ -330,8 +333,8 @@ export const albumStub = {
|
||||
assets: [],
|
||||
albumThumbnailAsset: null,
|
||||
albumThumbnailAssetId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
sharedLinks: [],
|
||||
sharedUsers: [userEntityStub.user1],
|
||||
}),
|
||||
@@ -343,8 +346,8 @@ export const albumStub = {
|
||||
assets: [],
|
||||
albumThumbnailAsset: null,
|
||||
albumThumbnailAssetId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
sharedLinks: [],
|
||||
sharedUsers: [userEntityStub.admin],
|
||||
}),
|
||||
@@ -356,8 +359,8 @@ export const albumStub = {
|
||||
assets: [assetEntityStub.image],
|
||||
albumThumbnailAsset: null,
|
||||
albumThumbnailAssetId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
sharedLinks: [],
|
||||
sharedUsers: [],
|
||||
}),
|
||||
@@ -369,8 +372,8 @@ export const albumStub = {
|
||||
assets: [],
|
||||
albumThumbnailAsset: assetEntityStub.image,
|
||||
albumThumbnailAssetId: assetEntityStub.image.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
sharedLinks: [],
|
||||
sharedUsers: [],
|
||||
}),
|
||||
@@ -382,8 +385,8 @@ export const albumStub = {
|
||||
assets: [],
|
||||
albumThumbnailAsset: null,
|
||||
albumThumbnailAssetId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
sharedLinks: [],
|
||||
sharedUsers: [],
|
||||
}),
|
||||
@@ -395,8 +398,8 @@ export const albumStub = {
|
||||
assets: [assetEntityStub.image],
|
||||
albumThumbnailAsset: assetEntityStub.livePhotoMotionAsset,
|
||||
albumThumbnailAssetId: assetEntityStub.livePhotoMotionAsset.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
sharedLinks: [],
|
||||
sharedUsers: [],
|
||||
}),
|
||||
@@ -408,8 +411,8 @@ export const albumStub = {
|
||||
assets: [assetEntityStub.image],
|
||||
albumThumbnailAsset: assetEntityStub.image,
|
||||
albumThumbnailAssetId: assetEntityStub.image.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
sharedLinks: [],
|
||||
sharedUsers: [],
|
||||
}),
|
||||
@@ -446,10 +449,10 @@ const assetResponse: AssetResponseDto = {
|
||||
type: AssetType.VIDEO,
|
||||
originalPath: 'fake_path/jpeg',
|
||||
originalFileName: 'asset_1.jpeg',
|
||||
resizePath: '',
|
||||
fileModifiedAt: today.toISOString(),
|
||||
fileCreatedAt: today.toISOString(),
|
||||
updatedAt: today.toISOString(),
|
||||
resized: false,
|
||||
fileModifiedAt: today,
|
||||
fileCreatedAt: today,
|
||||
updatedAt: today,
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
mimeType: 'image/jpeg',
|
||||
@@ -457,20 +460,19 @@ const assetResponse: AssetResponseDto = {
|
||||
tags: [],
|
||||
objects: ['a', 'b', 'c'],
|
||||
},
|
||||
webpPath: '',
|
||||
encodedVideoPath: '',
|
||||
duration: '0:00:00.00000',
|
||||
exifInfo: assetInfo,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
people: [],
|
||||
checksum: 'ZmlsZSBoYXNo',
|
||||
};
|
||||
|
||||
const albumResponse: AlbumResponseDto = {
|
||||
albumName: 'Test Album',
|
||||
albumThumbnailAssetId: null,
|
||||
createdAt: today.toISOString(),
|
||||
updatedAt: today.toISOString(),
|
||||
createdAt: today,
|
||||
updatedAt: today,
|
||||
id: 'album-123',
|
||||
ownerId: 'admin_id',
|
||||
owner: mapUser(userEntityStub.admin),
|
||||
@@ -646,8 +648,8 @@ export const sharedLinkStub = {
|
||||
user: userEntityStub.admin,
|
||||
key: Buffer.from('secret-key', 'utf8'),
|
||||
type: SharedLinkType.ALBUM,
|
||||
createdAt: today.toISOString(),
|
||||
expiresAt: tomorrow.toISOString(),
|
||||
createdAt: today,
|
||||
expiresAt: tomorrow,
|
||||
allowUpload: true,
|
||||
allowDownload: true,
|
||||
showExif: true,
|
||||
@@ -660,8 +662,8 @@ export const sharedLinkStub = {
|
||||
user: userEntityStub.admin,
|
||||
key: Buffer.from('secret-key', 'utf8'),
|
||||
type: SharedLinkType.ALBUM,
|
||||
createdAt: today.toISOString(),
|
||||
expiresAt: yesterday.toISOString(),
|
||||
createdAt: today,
|
||||
expiresAt: yesterday,
|
||||
allowUpload: true,
|
||||
allowDownload: true,
|
||||
showExif: true,
|
||||
@@ -673,8 +675,8 @@ export const sharedLinkStub = {
|
||||
user: userEntityStub.admin,
|
||||
key: Buffer.from('secret-key', 'utf8'),
|
||||
type: SharedLinkType.ALBUM,
|
||||
createdAt: today.toISOString(),
|
||||
expiresAt: tomorrow.toISOString(),
|
||||
createdAt: today,
|
||||
expiresAt: tomorrow,
|
||||
allowUpload: false,
|
||||
allowDownload: false,
|
||||
showExif: true,
|
||||
@@ -684,8 +686,8 @@ export const sharedLinkStub = {
|
||||
ownerId: authStub.admin.id,
|
||||
owner: userEntityStub.admin,
|
||||
albumName: 'Test Album',
|
||||
createdAt: today.toISOString(),
|
||||
updatedAt: today.toISOString(),
|
||||
createdAt: today,
|
||||
updatedAt: today,
|
||||
albumThumbnailAsset: null,
|
||||
albumThumbnailAssetId: null,
|
||||
sharedUsers: [],
|
||||
@@ -701,10 +703,10 @@ export const sharedLinkStub = {
|
||||
originalPath: 'fake_path/jpeg',
|
||||
resizePath: '',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
fileModifiedAt: today.toISOString(),
|
||||
fileCreatedAt: today.toISOString(),
|
||||
createdAt: today.toISOString(),
|
||||
updatedAt: today.toISOString(),
|
||||
fileModifiedAt: today,
|
||||
fileCreatedAt: today,
|
||||
createdAt: today,
|
||||
updatedAt: today,
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
mimeType: 'image/jpeg',
|
||||
@@ -764,9 +766,9 @@ export const sharedLinkResponseStub = {
|
||||
allowDownload: true,
|
||||
allowUpload: true,
|
||||
assets: [],
|
||||
createdAt: today.toISOString(),
|
||||
createdAt: today,
|
||||
description: undefined,
|
||||
expiresAt: tomorrow.toISOString(),
|
||||
expiresAt: tomorrow,
|
||||
id: '123',
|
||||
key: '7365637265742d6b6579',
|
||||
showExif: true,
|
||||
@@ -778,9 +780,9 @@ export const sharedLinkResponseStub = {
|
||||
allowDownload: true,
|
||||
allowUpload: true,
|
||||
assets: [],
|
||||
createdAt: today.toISOString(),
|
||||
createdAt: today,
|
||||
description: undefined,
|
||||
expiresAt: yesterday.toISOString(),
|
||||
expiresAt: yesterday,
|
||||
id: '123',
|
||||
key: '7365637265742d6b6579',
|
||||
showExif: true,
|
||||
@@ -792,8 +794,8 @@ export const sharedLinkResponseStub = {
|
||||
userId: 'admin_id',
|
||||
key: '7365637265742d6b6579',
|
||||
type: SharedLinkType.ALBUM,
|
||||
createdAt: today.toISOString(),
|
||||
expiresAt: tomorrow.toISOString(),
|
||||
createdAt: today,
|
||||
expiresAt: tomorrow,
|
||||
description: undefined,
|
||||
allowUpload: false,
|
||||
allowDownload: false,
|
||||
@@ -806,8 +808,8 @@ export const sharedLinkResponseStub = {
|
||||
userId: 'admin_id',
|
||||
key: '7365637265742d6b6579',
|
||||
type: SharedLinkType.ALBUM,
|
||||
createdAt: today.toISOString(),
|
||||
expiresAt: tomorrow.toISOString(),
|
||||
createdAt: today,
|
||||
expiresAt: tomorrow,
|
||||
description: undefined,
|
||||
allowUpload: false,
|
||||
allowDownload: false,
|
||||
|
||||
@@ -28,10 +28,10 @@ export class AlbumEntity {
|
||||
albumName!: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: string;
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: string;
|
||||
updatedAt!: Date;
|
||||
|
||||
@ManyToOne(() => AssetEntity, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' })
|
||||
albumThumbnailAsset!: AssetEntity | null;
|
||||
|
||||
@@ -19,8 +19,8 @@ export class APIKeyEntity {
|
||||
userId!: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: string;
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: string;
|
||||
updatedAt!: Date;
|
||||
}
|
||||
|
||||
@@ -55,16 +55,16 @@ export class AssetEntity {
|
||||
encodedVideoPath!: string | null;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: string;
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: string;
|
||||
updatedAt!: Date;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
fileCreatedAt!: string;
|
||||
fileCreatedAt!: Date;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
fileModifiedAt!: string;
|
||||
fileModifiedAt!: Date;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isFavorite!: boolean;
|
||||
|
||||
@@ -35,10 +35,10 @@ export class SharedLinkEntity {
|
||||
type!: SharedLinkType;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: string;
|
||||
createdAt!: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
expiresAt!: string | null;
|
||||
expiresAt!: Date | null;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
allowUpload!: boolean;
|
||||
|
||||
@@ -42,14 +42,14 @@ export class UserEntity {
|
||||
@Column({ default: true })
|
||||
shouldChangePassword!: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: string;
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@DeleteDateColumn()
|
||||
deletedAt?: Date;
|
||||
@DeleteDateColumn({ type: 'timestamptz' })
|
||||
deletedAt!: Date | null;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: string;
|
||||
updatedAt!: Date;
|
||||
|
||||
@OneToMany(() => TagEntity, (tag) => tag.user)
|
||||
tags!: TagEntity[];
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class UserDatesTimestamptz1685370430343 implements MigrationInterface {
|
||||
name = 'UserDatesTimestamptz1685370430343'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "createdAt" TYPE TIMESTAMP WITH TIME ZONE`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "deletedAt" TYPE TIMESTAMP WITH TIME ZONE`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "deletedAt" TYPE TIMESTAMP`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "createdAt" TYPE TIMESTAMP`);
|
||||
}
|
||||
|
||||
}
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.58.0",
|
||||
"version": "1.59.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich",
|
||||
"version": "1.58.0",
|
||||
"version": "1.59.0",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.58.0",
|
||||
"version": "1.59.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
@@ -141,7 +141,7 @@
|
||||
"./libs/domain/": {
|
||||
"branches": 80,
|
||||
"functions": 87,
|
||||
"lines": 93.8,
|
||||
"lines": 93.7,
|
||||
"statements": 93
|
||||
}
|
||||
},
|
||||
|
||||
49
web/package-lock.json
generated
49
web/package-lock.json
generated
@@ -8,10 +8,10 @@
|
||||
"name": "immich-web",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@zoom-image/svelte": "^0.1.0",
|
||||
"axios": "^0.27.2",
|
||||
"copy-image-clipboard": "^2.1.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"justified-layout": "^4.1.0",
|
||||
"leaflet": "^1.9.3",
|
||||
"leaflet.markercluster": "^1.5.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
@@ -4031,6 +4031,30 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@zoom-image/core": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.14.0.tgz",
|
||||
"integrity": "sha512-uNHjWzGtsif2MnbuYlGe+TAyHeHqPAJIJ09/gTRH/9DcEYiO7z1bLisVYtSXu2KDTbbtGYM5UnTKdLMmuDng3w==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/willnguyen1312"
|
||||
}
|
||||
},
|
||||
"node_modules/@zoom-image/svelte": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.1.0.tgz",
|
||||
"integrity": "sha512-HGB9ZBsaEMplHiZZ8r/mkrHAumun9caeVamNgX0v17hI7a71C3dXKuKrwAiIOllDYKihm1e5RYUCc2C7UKFtsg==",
|
||||
"dependencies": {
|
||||
"@zoom-image/core": "0.14.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/willnguyen1312"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
@@ -9052,11 +9076,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/justified-layout": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-4.1.0.tgz",
|
||||
"integrity": "sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg=="
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -14457,6 +14476,19 @@
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@zoom-image/core": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.14.0.tgz",
|
||||
"integrity": "sha512-uNHjWzGtsif2MnbuYlGe+TAyHeHqPAJIJ09/gTRH/9DcEYiO7z1bLisVYtSXu2KDTbbtGYM5UnTKdLMmuDng3w=="
|
||||
},
|
||||
"@zoom-image/svelte": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.1.0.tgz",
|
||||
"integrity": "sha512-HGB9ZBsaEMplHiZZ8r/mkrHAumun9caeVamNgX0v17hI7a71C3dXKuKrwAiIOllDYKihm1e5RYUCc2C7UKFtsg==",
|
||||
"requires": {
|
||||
"@zoom-image/core": "0.14.0"
|
||||
}
|
||||
},
|
||||
"abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
@@ -18154,11 +18186,6 @@
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true
|
||||
},
|
||||
"justified-layout": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-4.1.0.tgz",
|
||||
"integrity": "sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg=="
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@zoom-image/svelte": "^0.1.0",
|
||||
"axios": "^0.27.2",
|
||||
"copy-image-clipboard": "^2.1.2",
|
||||
"handlebars": "^4.7.7",
|
||||
|
||||
1946
web/src/api/open-api/api.ts
generated
1946
web/src/api/open-api/api.ts
generated
File diff suppressed because it is too large
Load Diff
2
web/src/api/open-api/base.ts
generated
2
web/src/api/open-api/base.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.58.0
|
||||
* The version of the OpenAPI document: 1.59.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
2
web/src/api/open-api/common.ts
generated
2
web/src/api/open-api/common.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.58.0
|
||||
* The version of the OpenAPI document: 1.59.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
2
web/src/api/open-api/configuration.ts
generated
2
web/src/api/open-api/configuration.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.58.0
|
||||
* The version of the OpenAPI document: 1.59.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
2
web/src/api/open-api/index.ts
generated
2
web/src/api/open-api/index.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.58.0
|
||||
* The version of the OpenAPI document: 1.59.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
@@ -21,13 +21,13 @@ export const oauth = {
|
||||
getConfig: (location: Location) => {
|
||||
const redirectUri = location.href.split('?')[0];
|
||||
console.log(`OAuth Redirect URI: ${redirectUri}`);
|
||||
return api.oauthApi.generateConfig({ redirectUri });
|
||||
return api.oauthApi.generateConfig({ oAuthConfigDto: { redirectUri } });
|
||||
},
|
||||
login: (location: Location) => {
|
||||
return api.oauthApi.callback({ url: location.href });
|
||||
return api.oauthApi.callback({ oAuthCallbackDto: { url: location.href } });
|
||||
},
|
||||
link: (location: Location): AxiosPromise<UserResponseDto> => {
|
||||
return api.oauthApi.link({ url: location.href });
|
||||
return api.oauthApi.link({ oAuthCallbackDto: { url: location.href } });
|
||||
},
|
||||
unlink: () => {
|
||||
return api.oauthApi.unlink();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user