mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:29:32 -05:00 
			
		
		
		
	feat(server): add updatedAt to Asset, Album and User (#1566)
* feat: add updatedAt info to DTO and generate api * chore: remove unsued file * chore: Add update statement to add/remove asset/user to album * fix: test
This commit is contained in:
		
							parent
							
								
									b8d2f5b373
								
							
						
					
					
						commit
						29bb1f7ef2
					
				
							
								
								
									
										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.43.1
 | 
			
		||||
- API version: 1.45.0
 | 
			
		||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
 | 
			
		||||
 | 
			
		||||
## Requirements
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/AlbumResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/AlbumResponseDto.md
									
									
									
										generated
									
									
									
								
							@ -13,6 +13,7 @@ Name | Type | Description | Notes
 | 
			
		||||
**ownerId** | **String** |  | 
 | 
			
		||||
**albumName** | **String** |  | 
 | 
			
		||||
**createdAt** | **String** |  | 
 | 
			
		||||
**updatedAt** | **String** |  | 
 | 
			
		||||
**albumThumbnailAssetId** | **String** |  | 
 | 
			
		||||
**shared** | **bool** |  | 
 | 
			
		||||
**sharedUsers** | [**List<UserResponseDto>**](UserResponseDto.md) |  | [default to const []]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/AssetResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/AssetResponseDto.md
									
									
									
										generated
									
									
									
								
							@ -17,6 +17,7 @@ Name | Type | Description | Notes
 | 
			
		||||
**resizePath** | **String** |  | 
 | 
			
		||||
**createdAt** | **String** |  | 
 | 
			
		||||
**modifiedAt** | **String** |  | 
 | 
			
		||||
**updatedAt** | **String** |  | 
 | 
			
		||||
**isFavorite** | **bool** |  | 
 | 
			
		||||
**mimeType** | **String** |  | 
 | 
			
		||||
**duration** | **String** |  | 
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										102
									
								
								mobile/openapi/lib/model/album_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										102
									
								
								mobile/openapi/lib/model/album_response_dto.dart
									
									
									
										generated
									
									
									
								
							@ -18,6 +18,7 @@ class AlbumResponseDto {
 | 
			
		||||
    required this.ownerId,
 | 
			
		||||
    required this.albumName,
 | 
			
		||||
    required this.createdAt,
 | 
			
		||||
    required this.updatedAt,
 | 
			
		||||
    required this.albumThumbnailAssetId,
 | 
			
		||||
    required this.shared,
 | 
			
		||||
    this.sharedUsers = const [],
 | 
			
		||||
@ -34,6 +35,8 @@ class AlbumResponseDto {
 | 
			
		||||
 | 
			
		||||
  String createdAt;
 | 
			
		||||
 | 
			
		||||
  String updatedAt;
 | 
			
		||||
 | 
			
		||||
  String? albumThumbnailAssetId;
 | 
			
		||||
 | 
			
		||||
  bool shared;
 | 
			
		||||
@ -43,51 +46,51 @@ class AlbumResponseDto {
 | 
			
		||||
  List<AssetResponseDto> assets;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) =>
 | 
			
		||||
      identical(this, other) ||
 | 
			
		||||
      other is AlbumResponseDto &&
 | 
			
		||||
          other.assetCount == assetCount &&
 | 
			
		||||
          other.id == id &&
 | 
			
		||||
          other.ownerId == ownerId &&
 | 
			
		||||
          other.albumName == albumName &&
 | 
			
		||||
          other.createdAt == createdAt &&
 | 
			
		||||
          other.albumThumbnailAssetId == albumThumbnailAssetId &&
 | 
			
		||||
          other.shared == shared &&
 | 
			
		||||
          other.sharedUsers == sharedUsers &&
 | 
			
		||||
          other.assets == assets;
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto &&
 | 
			
		||||
     other.assetCount == assetCount &&
 | 
			
		||||
     other.id == id &&
 | 
			
		||||
     other.ownerId == ownerId &&
 | 
			
		||||
     other.albumName == albumName &&
 | 
			
		||||
     other.createdAt == createdAt &&
 | 
			
		||||
     other.updatedAt == updatedAt &&
 | 
			
		||||
     other.albumThumbnailAssetId == albumThumbnailAssetId &&
 | 
			
		||||
     other.shared == shared &&
 | 
			
		||||
     other.sharedUsers == sharedUsers &&
 | 
			
		||||
     other.assets == assets;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode =>
 | 
			
		||||
      // ignore: unnecessary_parenthesis
 | 
			
		||||
      (assetCount.hashCode) +
 | 
			
		||||
      (id.hashCode) +
 | 
			
		||||
      (ownerId.hashCode) +
 | 
			
		||||
      (albumName.hashCode) +
 | 
			
		||||
      (createdAt.hashCode) +
 | 
			
		||||
      (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
 | 
			
		||||
      (shared.hashCode) +
 | 
			
		||||
      (sharedUsers.hashCode) +
 | 
			
		||||
      (assets.hashCode);
 | 
			
		||||
    // ignore: unnecessary_parenthesis
 | 
			
		||||
    (assetCount.hashCode) +
 | 
			
		||||
    (id.hashCode) +
 | 
			
		||||
    (ownerId.hashCode) +
 | 
			
		||||
    (albumName.hashCode) +
 | 
			
		||||
    (createdAt.hashCode) +
 | 
			
		||||
    (updatedAt.hashCode) +
 | 
			
		||||
    (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
 | 
			
		||||
    (shared.hashCode) +
 | 
			
		||||
    (sharedUsers.hashCode) +
 | 
			
		||||
    (assets.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() =>
 | 
			
		||||
      'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
 | 
			
		||||
  String toString() => 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, updatedAt=$updatedAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
    json[r'assetCount'] = this.assetCount;
 | 
			
		||||
    json[r'id'] = this.id;
 | 
			
		||||
    json[r'ownerId'] = this.ownerId;
 | 
			
		||||
    json[r'albumName'] = this.albumName;
 | 
			
		||||
    json[r'createdAt'] = this.createdAt;
 | 
			
		||||
      json[r'assetCount'] = this.assetCount;
 | 
			
		||||
      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;
 | 
			
		||||
    if (this.albumThumbnailAssetId != null) {
 | 
			
		||||
      json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId;
 | 
			
		||||
    } else {
 | 
			
		||||
      // json[r'albumThumbnailAssetId'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    json[r'shared'] = this.shared;
 | 
			
		||||
    json[r'sharedUsers'] = this.sharedUsers;
 | 
			
		||||
    json[r'assets'] = this.assets;
 | 
			
		||||
      json[r'shared'] = this.shared;
 | 
			
		||||
      json[r'sharedUsers'] = this.sharedUsers;
 | 
			
		||||
      json[r'assets'] = this.assets;
 | 
			
		||||
    return json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -101,13 +104,13 @@ class AlbumResponseDto {
 | 
			
		||||
      // Ensure that the map contains the required keys.
 | 
			
		||||
      // Note 1: the values aren't checked for validity beyond being non-null.
 | 
			
		||||
      // Note 2: this code is stripped in release mode!
 | 
			
		||||
      // assert(() {
 | 
			
		||||
      //   requiredKeys.forEach((key) {
 | 
			
		||||
      //     assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.');
 | 
			
		||||
      //     assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.');
 | 
			
		||||
      //   });
 | 
			
		||||
      //   return true;
 | 
			
		||||
      // }());
 | 
			
		||||
      assert(() {
 | 
			
		||||
        requiredKeys.forEach((key) {
 | 
			
		||||
          assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.');
 | 
			
		||||
          assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.');
 | 
			
		||||
        });
 | 
			
		||||
        return true;
 | 
			
		||||
      }());
 | 
			
		||||
 | 
			
		||||
      return AlbumResponseDto(
 | 
			
		||||
        assetCount: mapValueOfType<int>(json, r'assetCount')!,
 | 
			
		||||
@ -115,8 +118,8 @@ class AlbumResponseDto {
 | 
			
		||||
        ownerId: mapValueOfType<String>(json, r'ownerId')!,
 | 
			
		||||
        albumName: mapValueOfType<String>(json, r'albumName')!,
 | 
			
		||||
        createdAt: mapValueOfType<String>(json, r'createdAt')!,
 | 
			
		||||
        albumThumbnailAssetId:
 | 
			
		||||
            mapValueOfType<String>(json, r'albumThumbnailAssetId'),
 | 
			
		||||
        updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
 | 
			
		||||
        albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
 | 
			
		||||
        shared: mapValueOfType<bool>(json, r'shared')!,
 | 
			
		||||
        sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!,
 | 
			
		||||
        assets: AssetResponseDto.listFromJson(json[r'assets'])!,
 | 
			
		||||
@ -125,10 +128,7 @@ class AlbumResponseDto {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static List<AlbumResponseDto>? listFromJson(
 | 
			
		||||
    dynamic json, {
 | 
			
		||||
    bool growable = false,
 | 
			
		||||
  }) {
 | 
			
		||||
  static List<AlbumResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <AlbumResponseDto>[];
 | 
			
		||||
    if (json is List && json.isNotEmpty) {
 | 
			
		||||
      for (final row in json) {
 | 
			
		||||
@ -156,18 +156,12 @@ class AlbumResponseDto {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // maps a json object with a list of AlbumResponseDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<AlbumResponseDto>> mapListFromJson(
 | 
			
		||||
    dynamic json, {
 | 
			
		||||
    bool growable = false,
 | 
			
		||||
  }) {
 | 
			
		||||
  static Map<String, List<AlbumResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<AlbumResponseDto>>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        final value = AlbumResponseDto.listFromJson(
 | 
			
		||||
          entry.value,
 | 
			
		||||
          growable: growable,
 | 
			
		||||
        );
 | 
			
		||||
        final value = AlbumResponseDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          map[entry.key] = value;
 | 
			
		||||
        }
 | 
			
		||||
@ -183,9 +177,11 @@ class AlbumResponseDto {
 | 
			
		||||
    'ownerId',
 | 
			
		||||
    'albumName',
 | 
			
		||||
    'createdAt',
 | 
			
		||||
    'updatedAt',
 | 
			
		||||
    'albumThumbnailAssetId',
 | 
			
		||||
    'shared',
 | 
			
		||||
    'sharedUsers',
 | 
			
		||||
    'assets',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								mobile/openapi/lib/model/asset_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/model/asset_response_dto.dart
									
									
									
										generated
									
									
									
								
							@ -22,6 +22,7 @@ class AssetResponseDto {
 | 
			
		||||
    required this.resizePath,
 | 
			
		||||
    required this.createdAt,
 | 
			
		||||
    required this.modifiedAt,
 | 
			
		||||
    required this.updatedAt,
 | 
			
		||||
    required this.isFavorite,
 | 
			
		||||
    required this.mimeType,
 | 
			
		||||
    required this.duration,
 | 
			
		||||
@ -51,6 +52,8 @@ class AssetResponseDto {
 | 
			
		||||
 | 
			
		||||
  String modifiedAt;
 | 
			
		||||
 | 
			
		||||
  String updatedAt;
 | 
			
		||||
 | 
			
		||||
  bool isFavorite;
 | 
			
		||||
 | 
			
		||||
  String? mimeType;
 | 
			
		||||
@ -92,6 +95,7 @@ class AssetResponseDto {
 | 
			
		||||
     other.resizePath == resizePath &&
 | 
			
		||||
     other.createdAt == createdAt &&
 | 
			
		||||
     other.modifiedAt == modifiedAt &&
 | 
			
		||||
     other.updatedAt == updatedAt &&
 | 
			
		||||
     other.isFavorite == isFavorite &&
 | 
			
		||||
     other.mimeType == mimeType &&
 | 
			
		||||
     other.duration == duration &&
 | 
			
		||||
@ -114,6 +118,7 @@ class AssetResponseDto {
 | 
			
		||||
    (resizePath == null ? 0 : resizePath!.hashCode) +
 | 
			
		||||
    (createdAt.hashCode) +
 | 
			
		||||
    (modifiedAt.hashCode) +
 | 
			
		||||
    (updatedAt.hashCode) +
 | 
			
		||||
    (isFavorite.hashCode) +
 | 
			
		||||
    (mimeType == null ? 0 : mimeType!.hashCode) +
 | 
			
		||||
    (duration.hashCode) +
 | 
			
		||||
@ -125,7 +130,7 @@ class AssetResponseDto {
 | 
			
		||||
    (tags.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]';
 | 
			
		||||
  String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
@ -142,6 +147,7 @@ class AssetResponseDto {
 | 
			
		||||
    }
 | 
			
		||||
      json[r'createdAt'] = this.createdAt;
 | 
			
		||||
      json[r'modifiedAt'] = this.modifiedAt;
 | 
			
		||||
      json[r'updatedAt'] = this.updatedAt;
 | 
			
		||||
      json[r'isFavorite'] = this.isFavorite;
 | 
			
		||||
    if (this.mimeType != null) {
 | 
			
		||||
      json[r'mimeType'] = this.mimeType;
 | 
			
		||||
@ -206,6 +212,7 @@ class AssetResponseDto {
 | 
			
		||||
        resizePath: mapValueOfType<String>(json, r'resizePath'),
 | 
			
		||||
        createdAt: mapValueOfType<String>(json, r'createdAt')!,
 | 
			
		||||
        modifiedAt: mapValueOfType<String>(json, r'modifiedAt')!,
 | 
			
		||||
        updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
 | 
			
		||||
        isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
 | 
			
		||||
        mimeType: mapValueOfType<String>(json, r'mimeType'),
 | 
			
		||||
        duration: mapValueOfType<String>(json, r'duration')!,
 | 
			
		||||
@ -273,6 +280,7 @@ class AssetResponseDto {
 | 
			
		||||
    'resizePath',
 | 
			
		||||
    'createdAt',
 | 
			
		||||
    'modifiedAt',
 | 
			
		||||
    'updatedAt',
 | 
			
		||||
    'isFavorite',
 | 
			
		||||
    'mimeType',
 | 
			
		||||
    'duration',
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								mobile/openapi/test/album_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/album_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							@ -41,6 +41,11 @@ void main() {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // String updatedAt
 | 
			
		||||
    test('to test the property `updatedAt`', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // String albumThumbnailAssetId
 | 
			
		||||
    test('to test the property `albumThumbnailAssetId`', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								mobile/openapi/test/asset_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/asset_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							@ -61,6 +61,11 @@ void main() {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // String updatedAt
 | 
			
		||||
    test('to test the property `updatedAt`', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // bool isFavorite
 | 
			
		||||
    test('to test the property `isFavorite`', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
 | 
			
		||||
@ -213,18 +213,27 @@ export class AlbumRepository implements IAlbumRepository {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async get(albumId: string): Promise<AlbumEntity | undefined> {
 | 
			
		||||
    const query = this.albumRepository.createQueryBuilder('album');
 | 
			
		||||
 | 
			
		||||
    const album = await query
 | 
			
		||||
      .where('album.id = :albumId', { albumId })
 | 
			
		||||
      .leftJoinAndSelect('album.sharedUsers', 'sharedUser')
 | 
			
		||||
      .leftJoinAndSelect('sharedUser.userInfo', 'userInfo')
 | 
			
		||||
      .leftJoinAndSelect('album.assets', 'assets')
 | 
			
		||||
      .leftJoinAndSelect('assets.assetInfo', 'assetInfo')
 | 
			
		||||
      .leftJoinAndSelect('assetInfo.exifInfo', 'exifInfo')
 | 
			
		||||
      .leftJoinAndSelect('album.sharedLinks', 'sharedLinks')
 | 
			
		||||
      .orderBy('"assetInfo"."createdAt"::timestamptz', 'ASC')
 | 
			
		||||
      .getOne();
 | 
			
		||||
    const album = await this.albumRepository.findOne({
 | 
			
		||||
      where: { id: albumId },
 | 
			
		||||
      relations: {
 | 
			
		||||
        sharedUsers: {
 | 
			
		||||
          userInfo: true,
 | 
			
		||||
        },
 | 
			
		||||
        assets: {
 | 
			
		||||
          assetInfo: {
 | 
			
		||||
            exifInfo: true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        sharedLinks: true,
 | 
			
		||||
      },
 | 
			
		||||
      order: {
 | 
			
		||||
        assets: {
 | 
			
		||||
          assetInfo: {
 | 
			
		||||
            createdAt: 'ASC',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (!album) {
 | 
			
		||||
      return;
 | 
			
		||||
@ -249,11 +258,14 @@ export class AlbumRepository implements IAlbumRepository {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await this.userAlbumRepository.save([...newRecords]);
 | 
			
		||||
    await this.albumRepository.update({ id: album.id }, { updatedAt: new Date().toISOString() });
 | 
			
		||||
 | 
			
		||||
    return this.get(album.id) as Promise<AlbumEntity>; // There is an album for sure
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async removeUser(album: AlbumEntity, userId: string): Promise<void> {
 | 
			
		||||
    await this.userAlbumRepository.delete({ albumId: album.id, sharedUserId: userId });
 | 
			
		||||
    await this.albumRepository.update({ id: album.id }, { updatedAt: new Date().toISOString() });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async removeAssets(album: AlbumEntity, removeAssetsDto: RemoveAssetsDto): Promise<number> {
 | 
			
		||||
@ -262,6 +274,8 @@ export class AlbumRepository implements IAlbumRepository {
 | 
			
		||||
      assetId: In(removeAssetsDto.assetIds),
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await this.albumRepository.update({ id: album.id }, { updatedAt: new Date().toISOString() });
 | 
			
		||||
 | 
			
		||||
    return res.affected || 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -290,6 +304,8 @@ export class AlbumRepository implements IAlbumRepository {
 | 
			
		||||
 | 
			
		||||
    await this.assetAlbumRepository.save([...newRecords]);
 | 
			
		||||
 | 
			
		||||
    await this.albumRepository.update({ id: album.id }, { updatedAt: new Date().toISOString() });
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      successfullyAdded: newRecords.length,
 | 
			
		||||
      alreadyInAlbum: alreadyExisting,
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@ describe('Album service', () => {
 | 
			
		||||
    albumEntity.id = albumId;
 | 
			
		||||
    albumEntity.albumName = 'name';
 | 
			
		||||
    albumEntity.createdAt = 'date';
 | 
			
		||||
    albumEntity.updatedAt = 'date';
 | 
			
		||||
    albumEntity.sharedUsers = [];
 | 
			
		||||
    albumEntity.assets = [];
 | 
			
		||||
    albumEntity.albumThumbnailAssetId = null;
 | 
			
		||||
@ -183,6 +184,7 @@ describe('Album service', () => {
 | 
			
		||||
      albumName: 'name',
 | 
			
		||||
      albumThumbnailAssetId: null,
 | 
			
		||||
      createdAt: 'date',
 | 
			
		||||
      updatedAt: 'date',
 | 
			
		||||
      id: 'f19ab956-4761-41ea-a5d6-bae948308d58',
 | 
			
		||||
      ownerId,
 | 
			
		||||
      shared: false,
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ export class AssetCore {
 | 
			
		||||
 | 
			
		||||
      createdAt: timeUtils.checkValidTimestamp(dto.createdAt) ? dto.createdAt : new Date().toISOString(),
 | 
			
		||||
      modifiedAt: timeUtils.checkValidTimestamp(dto.modifiedAt) ? dto.modifiedAt : new Date().toISOString(),
 | 
			
		||||
      updatedAt: new Date().toISOString(),
 | 
			
		||||
 | 
			
		||||
      deviceAssetId: dto.deviceAssetId,
 | 
			
		||||
      deviceId: dto.deviceId,
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ describe('TagService', () => {
 | 
			
		||||
    shouldChangePassword: true,
 | 
			
		||||
    createdAt: '2022-12-02T19:29:23.603Z',
 | 
			
		||||
    deletedAt: undefined,
 | 
			
		||||
    updatedAt: '2022-12-02T19:29:23.603Z',
 | 
			
		||||
    tags: [],
 | 
			
		||||
    oauthId: 'oauth-id-1',
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -3291,6 +3291,9 @@
 | 
			
		||||
          "modifiedAt": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "updatedAt": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "isFavorite": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
@ -3336,6 +3339,7 @@
 | 
			
		||||
          "resizePath",
 | 
			
		||||
          "createdAt",
 | 
			
		||||
          "modifiedAt",
 | 
			
		||||
          "updatedAt",
 | 
			
		||||
          "isFavorite",
 | 
			
		||||
          "mimeType",
 | 
			
		||||
          "duration",
 | 
			
		||||
@ -3361,6 +3365,9 @@
 | 
			
		||||
          "createdAt": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "updatedAt": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "albumThumbnailAssetId": {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "nullable": true
 | 
			
		||||
@ -3387,6 +3394,7 @@
 | 
			
		||||
          "ownerId",
 | 
			
		||||
          "albumName",
 | 
			
		||||
          "createdAt",
 | 
			
		||||
          "updatedAt",
 | 
			
		||||
          "albumThumbnailAssetId",
 | 
			
		||||
          "shared",
 | 
			
		||||
          "sharedUsers",
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ export class AlbumResponseDto {
 | 
			
		||||
  ownerId!: string;
 | 
			
		||||
  albumName!: string;
 | 
			
		||||
  createdAt!: string;
 | 
			
		||||
  updatedAt!: string;
 | 
			
		||||
  albumThumbnailAssetId!: string | null;
 | 
			
		||||
  shared!: boolean;
 | 
			
		||||
  sharedUsers!: UserResponseDto[];
 | 
			
		||||
@ -30,6 +31,7 @@ export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
 | 
			
		||||
    albumName: entity.albumName,
 | 
			
		||||
    albumThumbnailAssetId: entity.albumThumbnailAssetId,
 | 
			
		||||
    createdAt: entity.createdAt,
 | 
			
		||||
    updatedAt: entity.updatedAt,
 | 
			
		||||
    id: entity.id,
 | 
			
		||||
    ownerId: entity.ownerId,
 | 
			
		||||
    sharedUsers,
 | 
			
		||||
@ -52,6 +54,7 @@ export function mapAlbumExcludeAssetInfo(entity: AlbumEntity): AlbumResponseDto
 | 
			
		||||
    albumName: entity.albumName,
 | 
			
		||||
    albumThumbnailAssetId: entity.albumThumbnailAssetId,
 | 
			
		||||
    createdAt: entity.createdAt,
 | 
			
		||||
    updatedAt: entity.updatedAt,
 | 
			
		||||
    id: entity.id,
 | 
			
		||||
    ownerId: entity.ownerId,
 | 
			
		||||
    sharedUsers,
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ export class AssetResponseDto {
 | 
			
		||||
  resizePath!: string | null;
 | 
			
		||||
  createdAt!: string;
 | 
			
		||||
  modifiedAt!: string;
 | 
			
		||||
  updatedAt!: string;
 | 
			
		||||
  isFavorite!: boolean;
 | 
			
		||||
  mimeType!: string | null;
 | 
			
		||||
  duration!: string;
 | 
			
		||||
@ -38,6 +39,7 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto {
 | 
			
		||||
    resizePath: entity.resizePath,
 | 
			
		||||
    createdAt: entity.createdAt,
 | 
			
		||||
    modifiedAt: entity.modifiedAt,
 | 
			
		||||
    updatedAt: entity.updatedAt,
 | 
			
		||||
    isFavorite: entity.isFavorite,
 | 
			
		||||
    mimeType: entity.mimeType,
 | 
			
		||||
    webpPath: entity.webpPath,
 | 
			
		||||
@ -61,6 +63,7 @@ export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto {
 | 
			
		||||
    resizePath: entity.resizePath,
 | 
			
		||||
    createdAt: entity.createdAt,
 | 
			
		||||
    modifiedAt: entity.modifiedAt,
 | 
			
		||||
    updatedAt: entity.updatedAt,
 | 
			
		||||
    isFavorite: entity.isFavorite,
 | 
			
		||||
    mimeType: entity.mimeType,
 | 
			
		||||
    webpPath: entity.webpPath,
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,7 @@ const adminUser: UserEntity = Object.freeze({
 | 
			
		||||
  shouldChangePassword: false,
 | 
			
		||||
  profileImagePath: '',
 | 
			
		||||
  createdAt: '2021-01-01',
 | 
			
		||||
  updatedAt: '2021-01-01',
 | 
			
		||||
  tags: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -45,6 +46,7 @@ const immichUser: UserEntity = Object.freeze({
 | 
			
		||||
  shouldChangePassword: false,
 | 
			
		||||
  profileImagePath: '',
 | 
			
		||||
  createdAt: '2021-01-01',
 | 
			
		||||
  updatedAt: '2021-01-01',
 | 
			
		||||
  tags: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -59,6 +61,7 @@ const updatedImmichUser: UserEntity = Object.freeze({
 | 
			
		||||
  shouldChangePassword: true,
 | 
			
		||||
  profileImagePath: '',
 | 
			
		||||
  createdAt: '2021-01-01',
 | 
			
		||||
  updatedAt: '2021-01-01',
 | 
			
		||||
  tags: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,7 @@ const assetResponse: AssetResponseDto = {
 | 
			
		||||
  resizePath: '',
 | 
			
		||||
  createdAt: today.toISOString(),
 | 
			
		||||
  modifiedAt: today.toISOString(),
 | 
			
		||||
  updatedAt: today.toISOString(),
 | 
			
		||||
  isFavorite: false,
 | 
			
		||||
  mimeType: 'image/jpeg',
 | 
			
		||||
  smartInfo: {
 | 
			
		||||
@ -67,6 +68,7 @@ const albumResponse: AlbumResponseDto = {
 | 
			
		||||
  albumName: 'Test Album',
 | 
			
		||||
  albumThumbnailAssetId: null,
 | 
			
		||||
  createdAt: today.toISOString(),
 | 
			
		||||
  updatedAt: today.toISOString(),
 | 
			
		||||
  id: 'album-123',
 | 
			
		||||
  ownerId: 'admin_id',
 | 
			
		||||
  sharedUsers: [],
 | 
			
		||||
@ -126,6 +128,7 @@ export const userEntityStub = {
 | 
			
		||||
    shouldChangePassword: false,
 | 
			
		||||
    profileImagePath: '',
 | 
			
		||||
    createdAt: '2021-01-01',
 | 
			
		||||
    updatedAt: '2021-01-01',
 | 
			
		||||
    tags: [],
 | 
			
		||||
  }),
 | 
			
		||||
  user1: Object.freeze<UserEntity>({
 | 
			
		||||
@ -137,6 +140,7 @@ export const userEntityStub = {
 | 
			
		||||
    shouldChangePassword: false,
 | 
			
		||||
    profileImagePath: '',
 | 
			
		||||
    createdAt: '2021-01-01',
 | 
			
		||||
    updatedAt: '2021-01-01',
 | 
			
		||||
    tags: [],
 | 
			
		||||
  }),
 | 
			
		||||
};
 | 
			
		||||
@ -329,6 +333,7 @@ export const sharedLinkStub = {
 | 
			
		||||
      ownerId: authStub.admin.id,
 | 
			
		||||
      albumName: 'Test Album',
 | 
			
		||||
      createdAt: today.toISOString(),
 | 
			
		||||
      updatedAt: today.toISOString(),
 | 
			
		||||
      albumThumbnailAssetId: null,
 | 
			
		||||
      sharedUsers: [],
 | 
			
		||||
      sharedLinks: [],
 | 
			
		||||
@ -348,6 +353,7 @@ export const sharedLinkStub = {
 | 
			
		||||
            resizePath: '',
 | 
			
		||||
            createdAt: today.toISOString(),
 | 
			
		||||
            modifiedAt: today.toISOString(),
 | 
			
		||||
            updatedAt: today.toISOString(),
 | 
			
		||||
            isFavorite: false,
 | 
			
		||||
            mimeType: 'image/jpeg',
 | 
			
		||||
            smartInfo: {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
 | 
			
		||||
import { Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
 | 
			
		||||
import { AssetAlbumEntity } from './asset-album.entity';
 | 
			
		||||
import { SharedLinkEntity } from './shared-link.entity';
 | 
			
		||||
import { UserAlbumEntity } from './user-album.entity';
 | 
			
		||||
@ -17,6 +17,9 @@ export class AlbumEntity {
 | 
			
		||||
  @CreateDateColumn({ type: 'timestamptz' })
 | 
			
		||||
  createdAt!: string;
 | 
			
		||||
 | 
			
		||||
  @UpdateDateColumn({ type: 'timestamptz' })
 | 
			
		||||
  updatedAt!: string;
 | 
			
		||||
 | 
			
		||||
  @Column({ comment: 'Asset ID to be used as thumbnail', type: 'varchar', nullable: true })
 | 
			
		||||
  albumThumbnailAssetId!: string | null;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,14 @@
 | 
			
		||||
import { Column, Entity, Index, JoinTable, ManyToMany, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm';
 | 
			
		||||
import {
 | 
			
		||||
  Column,
 | 
			
		||||
  Entity,
 | 
			
		||||
  Index,
 | 
			
		||||
  JoinTable,
 | 
			
		||||
  ManyToMany,
 | 
			
		||||
  OneToOne,
 | 
			
		||||
  PrimaryGeneratedColumn,
 | 
			
		||||
  Unique,
 | 
			
		||||
  UpdateDateColumn,
 | 
			
		||||
} from 'typeorm';
 | 
			
		||||
import { ExifEntity } from './exif.entity';
 | 
			
		||||
import { SharedLinkEntity } from './shared-link.entity';
 | 
			
		||||
import { SmartInfoEntity } from './smart-info.entity';
 | 
			
		||||
@ -40,6 +50,9 @@ export class AssetEntity {
 | 
			
		||||
  @Column({ type: 'timestamptz' })
 | 
			
		||||
  modifiedAt!: string;
 | 
			
		||||
 | 
			
		||||
  @UpdateDateColumn({ type: 'timestamptz' })
 | 
			
		||||
  updatedAt!: string;
 | 
			
		||||
 | 
			
		||||
  @Column({ type: 'boolean', default: false })
 | 
			
		||||
  isFavorite!: boolean;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,12 @@
 | 
			
		||||
import { Column, CreateDateColumn, DeleteDateColumn, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
 | 
			
		||||
import {
 | 
			
		||||
  Column,
 | 
			
		||||
  CreateDateColumn,
 | 
			
		||||
  DeleteDateColumn,
 | 
			
		||||
  Entity,
 | 
			
		||||
  OneToMany,
 | 
			
		||||
  PrimaryGeneratedColumn,
 | 
			
		||||
  UpdateDateColumn,
 | 
			
		||||
} from 'typeorm';
 | 
			
		||||
import { TagEntity } from './tag.entity';
 | 
			
		||||
 | 
			
		||||
@Entity('users')
 | 
			
		||||
@ -36,6 +44,9 @@ export class UserEntity {
 | 
			
		||||
  @DeleteDateColumn()
 | 
			
		||||
  deletedAt?: Date;
 | 
			
		||||
 | 
			
		||||
  @UpdateDateColumn({ type: 'timestamptz' })
 | 
			
		||||
  updatedAt!: string;
 | 
			
		||||
 | 
			
		||||
  @OneToMany(() => TagEntity, (tag) => tag.user)
 | 
			
		||||
  tags!: TagEntity[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,17 @@
 | 
			
		||||
import { MigrationInterface, QueryRunner } from 'typeorm';
 | 
			
		||||
 | 
			
		||||
export class AddUpdatedAtColumnToAlbumsUsersAssets1675667878312 implements MigrationInterface {
 | 
			
		||||
  name = 'AddUpdatedAtColumnToAlbumsUsersAssets1675667878312';
 | 
			
		||||
 | 
			
		||||
  public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
    await queryRunner.query(`ALTER TABLE "albums" ADD "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
 | 
			
		||||
    await queryRunner.query(`ALTER TABLE "users" ADD "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
 | 
			
		||||
    await queryRunner.query(`ALTER TABLE "assets" ADD "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async down(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
    await queryRunner.query(`ALTER TABLE "albums" DROP COLUMN "updatedAt"`);
 | 
			
		||||
    await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "updatedAt"`);
 | 
			
		||||
    await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "updatedAt"`);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							@ -4,7 +4,7 @@
 | 
			
		||||
 * Immich
 | 
			
		||||
 * Immich API
 | 
			
		||||
 *
 | 
			
		||||
 * The version of the OpenAPI document: 1.43.1
 | 
			
		||||
 * The version of the OpenAPI document: 1.45.0
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 | 
			
		||||
@ -246,6 +246,12 @@ export interface AlbumResponseDto {
 | 
			
		||||
     * @memberof AlbumResponseDto
 | 
			
		||||
     */
 | 
			
		||||
    'createdAt': string;
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @type {string}
 | 
			
		||||
     * @memberof AlbumResponseDto
 | 
			
		||||
     */
 | 
			
		||||
    'updatedAt': string;
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @type {string}
 | 
			
		||||
@ -462,6 +468,12 @@ export interface AssetResponseDto {
 | 
			
		||||
     * @memberof AssetResponseDto
 | 
			
		||||
     */
 | 
			
		||||
    'modifiedAt': string;
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @type {string}
 | 
			
		||||
     * @memberof AssetResponseDto
 | 
			
		||||
     */
 | 
			
		||||
    'updatedAt': string;
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @type {boolean}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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.43.1
 | 
			
		||||
 * The version of the OpenAPI document: 1.45.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.43.1
 | 
			
		||||
 * The version of the OpenAPI document: 1.45.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.43.1
 | 
			
		||||
 * The version of the OpenAPI document: 1.45.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.43.1
 | 
			
		||||
 * The version of the OpenAPI document: 1.45.0
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user