Przeglądaj źródła

refactor(server): use date type for entities (#2602)

Michel Heusschen 2 lat temu
rodzic
commit
789e3e3924
49 zmienionych plików z 243 dodań i 217 usunięć
  1. 2 2
      mobile/lib/shared/models/album.dart
  2. 1 3
      mobile/lib/shared/models/user.dart
  3. 2 2
      mobile/lib/shared/services/sync.service.dart
  4. 2 2
      mobile/openapi/doc/APIKeyResponseDto.md
  5. 1 1
      mobile/openapi/doc/AdminSignupResponseDto.md
  6. 2 2
      mobile/openapi/doc/AlbumResponseDto.md
  7. 1 1
      mobile/openapi/doc/CreateAssetsShareLinkDto.md
  8. 1 1
      mobile/openapi/doc/EditSharedLinkDto.md
  9. 2 2
      mobile/openapi/doc/SharedLinkResponseDto.md
  10. 3 3
      mobile/openapi/doc/UserResponseDto.md
  11. 3 3
      mobile/openapi/lib/model/admin_signup_response_dto.dart
  12. 6 6
      mobile/openapi/lib/model/album_response_dto.dart
  13. 6 6
      mobile/openapi/lib/model/api_key_response_dto.dart
  14. 3 3
      mobile/openapi/lib/model/create_assets_share_link_dto.dart
  15. 3 3
      mobile/openapi/lib/model/edit_shared_link_dto.dart
  16. 6 6
      mobile/openapi/lib/model/shared_link_response_dto.dart
  17. 17 31
      mobile/openapi/lib/model/user_response_dto.dart
  18. 1 1
      mobile/openapi/test/admin_signup_response_dto_test.dart
  19. 2 2
      mobile/openapi/test/album_response_dto_test.dart
  20. 2 2
      mobile/openapi/test/api_key_response_dto_test.dart
  21. 1 1
      mobile/openapi/test/create_assets_share_link_dto_test.dart
  22. 1 1
      mobile/openapi/test/edit_shared_link_dto_test.dart
  23. 2 2
      mobile/openapi/test/shared_link_response_dto_test.dart
  24. 6 6
      mobile/openapi/test/user_response_dto_test.dart
  25. 4 4
      server/apps/immich/src/api-v1/album/album-repository.ts
  26. 10 9
      server/apps/immich/src/api-v1/album/album.service.spec.ts
  27. 6 4
      server/apps/immich/src/api-v1/album/dto/create-album-shared-link.dto.ts
  28. 5 3
      server/apps/immich/src/api-v1/asset/dto/create-asset-shared-link.dto.ts
  29. 3 3
      server/apps/immich/src/api-v1/tag/tag.service.spec.ts
  30. 21 7
      server/immich-openapi-specs.json
  31. 3 2
      server/libs/domain/src/album/album.service.spec.ts
  32. 2 2
      server/libs/domain/src/album/response-dto/album-response.dto.ts
  33. 2 2
      server/libs/domain/src/api-key/api-key-response.dto.ts
  34. 2 2
      server/libs/domain/src/auth/auth.service.spec.ts
  35. 1 1
      server/libs/domain/src/auth/response-dto/admin-signup-response.dto.ts
  36. 6 6
      server/libs/domain/src/partner/partner.service.spec.ts
  37. 1 1
      server/libs/domain/src/share/dto/create-shared-link.dto.ts
  38. 1 1
      server/libs/domain/src/share/dto/edit-shared-link.dto.ts
  39. 2 2
      server/libs/domain/src/share/response-dto/shared-link-response.dto.ts
  40. 1 1
      server/libs/domain/src/share/share.core.ts
  41. 4 4
      server/libs/domain/src/user/response-dto/user-response.dto.ts
  42. 15 12
      server/libs/domain/src/user/user.service.spec.ts
  43. 43 40
      server/libs/domain/test/fixtures.ts
  44. 2 2
      server/libs/infra/src/entities/album.entity.ts
  45. 2 2
      server/libs/infra/src/entities/api-key.entity.ts
  46. 2 2
      server/libs/infra/src/entities/shared-link.entity.ts
  47. 5 5
      server/libs/infra/src/entities/user.entity.ts
  48. 16 0
      server/libs/infra/src/migrations/1685370430343-UserDatesTimestamptz.ts
  49. 8 8
      web/src/api/open-api/api.ts

+ 2 - 2
mobile/lib/shared/models/album.dart

@@ -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);

+ 1 - 3
mobile/lib/shared/models/user.dart

@@ -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,

+ 2 - 2
mobile/lib/shared/services/sync.service.dart

@@ -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 - 2
mobile/openapi/doc/APIKeyResponseDto.md

@@ -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)
 

+ 1 - 1
mobile/openapi/doc/AdminSignupResponseDto.md

@@ -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)
 

+ 2 - 2
mobile/openapi/doc/AlbumResponseDto.md

@@ -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 []]

+ 1 - 1
mobile/openapi/doc/CreateAssetsShareLinkDto.md

@@ -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] 

+ 1 - 1
mobile/openapi/doc/EditSharedLinkDto.md

@@ -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] 

+ 2 - 2
mobile/openapi/doc/SharedLinkResponseDto.md

@@ -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** |  | 

+ 3 - 3
mobile/openapi/doc/UserResponseDto.md

@@ -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)

+ 3 - 3
mobile/openapi/lib/model/admin_signup_response_dto.dart

@@ -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;

+ 6 - 6
mobile/openapi/lib/model/album_response_dto.dart

@@ -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']),

+ 6 - 6
mobile/openapi/lib/model/api_key_response_dto.dart

@@ -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;

+ 3 - 3
mobile/openapi/lib/model/create_assets_share_link_dto.dart

@@ -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'),

+ 3 - 3
mobile/openapi/lib/model/edit_shared_link_dto.dart

@@ -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'),

+ 6 - 6
mobile/openapi/lib/model/shared_link_response_dto.dart

@@ -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')!,

+ 17 - 31
mobile/openapi/lib/model/user_response_dto.dart

@@ -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',
   };
 }

+ 1 - 1
mobile/openapi/test/admin_signup_response_dto_test.dart

@@ -36,7 +36,7 @@ void main() {
       // TODO
     });
 
-    // String createdAt
+    // DateTime createdAt
     test('to test the property `createdAt`', () async {
       // TODO
     });

+ 2 - 2
mobile/openapi/test/album_response_dto_test.dart

@@ -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
     });

+ 2 - 2
mobile/openapi/test/api_key_response_dto_test.dart

@@ -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
     });

+ 1 - 1
mobile/openapi/test/create_assets_share_link_dto_test.dart

@@ -21,7 +21,7 @@ void main() {
       // TODO
     });
 
-    // String expiresAt
+    // DateTime expiresAt
     test('to test the property `expiresAt`', () async {
       // TODO
     });

+ 1 - 1
mobile/openapi/test/edit_shared_link_dto_test.dart

@@ -21,7 +21,7 @@ void main() {
       // TODO
     });
 
-    // String expiresAt
+    // DateTime expiresAt
     test('to test the property `expiresAt`', () async {
       // TODO
     });

+ 2 - 2
mobile/openapi/test/shared_link_response_dto_test.dart

@@ -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
     });

+ 6 - 6
mobile/openapi/test/user_response_dto_test.dart

@@ -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
     });

+ 4 - 4
server/apps/immich/src/api-v1/album/album-repository.ts

@@ -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);
 

+ 10 - 9
server/apps/immich/src/api-v1/album/album.service.spec.ts

@@ -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,

+ 6 - 4
server/apps/immich/src/api-v1/album/dto/create-album-shared-link.dto.ts

@@ -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()

+ 5 - 3
server/apps/immich/src/api-v1/asset/dto/create-asset-shared-link.dto.ts

@@ -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()

+ 3 - 3
server/apps/immich/src/api-v1/tag/tag.service.spec.ts

@@ -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',

+ 21 - 7
server/immich-openapi-specs.json

@@ -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"
         ]
       },
@@ -4536,9 +4541,11 @@
             "type": "string"
           },
           "createdAt": {
+            "format": "date-time",
             "type": "string"
           },
           "updatedAt": {
+            "format": "date-time",
             "type": "string"
           },
           "albumThumbnailAssetId": {
@@ -4633,9 +4640,11 @@
             "type": "string"
           },
           "createdAt": {
+            "format": "date-time",
             "type": "string"
           },
           "updatedAt": {
+            "format": "date-time",
             "type": "string"
           }
         },
@@ -4800,6 +4809,7 @@
             "type": "string"
           },
           "createdAt": {
+            "format": "date-time",
             "type": "string"
           }
         },
@@ -5396,9 +5406,11 @@
             "type": "string"
           },
           "createdAt": {
+            "format": "date-time",
             "type": "string"
           },
           "expiresAt": {
+            "format": "date-time",
             "type": "string",
             "nullable": true
           },
@@ -5441,6 +5453,7 @@
             "type": "string"
           },
           "expiresAt": {
+            "format": "date-time",
             "type": "string",
             "nullable": true
           },
@@ -6255,6 +6268,7 @@
             }
           },
           "expiresAt": {
+            "format": "date-time",
             "type": "string"
           },
           "allowUpload": {
@@ -6393,8 +6407,8 @@
             "format": "uuid"
           },
           "expiresAt": {
-            "type": "string",
-            "format": "date-time"
+            "format": "date-time",
+            "type": "string"
           },
           "allowUpload": {
             "type": "boolean"

+ 3 - 2
server/libs/domain/src/album/album.service.spec.ts

@@ -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,

+ 2 - 2
server/libs/domain/src/album/response-dto/album-response.dto.ts

@@ -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[];

+ 2 - 2
server/libs/domain/src/api-key/api-key-response.dto.ts

@@ -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 {

+ 2 - 2
server/libs/domain/src/auth/auth.service.spec.ts

@@ -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',

+ 1 - 1
server/libs/domain/src/auth/response-dto/admin-signup-response.dto.ts

@@ -5,7 +5,7 @@ export class AdminSignupResponseDto {
   email!: string;
   firstName!: string;
   lastName!: string;
-  createdAt!: string;
+  createdAt!: Date;
 }
 
 export function mapAdminSignupResponse(entity: UserEntity): AdminSignupResponseDto {

+ 6 - 6
server/libs/domain/src/partner/partner.service.spec.ts

@@ -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'),
   },
 };
 

+ 1 - 1
server/libs/domain/src/share/dto/create-shared-link.dto.ts

@@ -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;

+ 1 - 1
server/libs/domain/src/share/dto/edit-shared-link.dto.ts

@@ -5,7 +5,7 @@ export class EditSharedLinkDto {
   description?: string;
 
   @IsOptional()
-  expiresAt?: string | null;
+  expiresAt?: Date | null;
 
   @IsOptional()
   allowUpload?: boolean;

+ 2 - 2
server/libs/domain/src/share/response-dto/shared-link-response.dto.ts

@@ -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;

+ 1 - 1
server/libs/domain/src/share/share.core.ts

@@ -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,

+ 4 - 4
server/libs/domain/src/user/response-dto/user-response.dto.ts

@@ -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,

+ 15 - 12
server/libs/domain/src/user/user.service.spec.ts

@@ -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',
         },
       ]);

+ 43 - 40
server/libs/domain/test/fixtures.ts

@@ -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: [],
   }),
@@ -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: [],
   }),
@@ -468,8 +471,8 @@ const assetResponse: AssetResponseDto = {
 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),
@@ -645,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,
@@ -659,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,
@@ -672,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,
@@ -683,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: [],
@@ -763,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,
@@ -777,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,
@@ -791,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,
@@ -805,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,

+ 2 - 2
server/libs/infra/src/entities/album.entity.ts

@@ -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;

+ 2 - 2
server/libs/infra/src/entities/api-key.entity.ts

@@ -19,8 +19,8 @@ export class APIKeyEntity {
   userId!: string;
 
   @CreateDateColumn({ type: 'timestamptz' })
-  createdAt!: string;
+  createdAt!: Date;
 
   @UpdateDateColumn({ type: 'timestamptz' })
-  updatedAt!: string;
+  updatedAt!: Date;
 }

+ 2 - 2
server/libs/infra/src/entities/shared-link.entity.ts

@@ -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;

+ 5 - 5
server/libs/infra/src/entities/user.entity.ts

@@ -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[];

+ 16 - 0
server/libs/infra/src/migrations/1685370430343-UserDatesTimestamptz.ts

@@ -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`);
+    }
+
+}

+ 8 - 8
web/src/api/open-api/api.ts

@@ -2712,12 +2712,6 @@ export interface UserResponseDto {
      * @memberof UserResponseDto
      */
     'storageLabel': string | null;
-    /**
-     * 
-     * @type {string}
-     * @memberof UserResponseDto
-     */
-    'createdAt': string;
     /**
      * 
      * @type {string}
@@ -2741,13 +2735,19 @@ export interface UserResponseDto {
      * @type {string}
      * @memberof UserResponseDto
      */
-    'deletedAt'?: string;
+    'createdAt': string;
     /**
      * 
      * @type {string}
      * @memberof UserResponseDto
      */
-    'updatedAt'?: string;
+    'deletedAt': string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof UserResponseDto
+     */
+    'updatedAt': string;
     /**
      * 
      * @type {string}