diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 1e7c29dbbc..539a96753e 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -11,6 +11,10 @@ on: push: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build-sign-android: name: Build and sign Android @@ -24,7 +28,7 @@ jobs: github_ref="${{ github.sha }}" ref="${input_ref:-$github_ref}" echo "ref=$ref" >> $GITHUB_OUTPUT - + - uses: actions/checkout@v3 with: ref: ${{ steps.get-ref.outputs.ref }} diff --git a/.github/workflows/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml index 0b8541432d..c2ffda547c 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -4,24 +4,28 @@ on: types: - closed +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: cleanup: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v3 - + - name: Cleanup run: | gh extension install actions/gh-actions-cache - + REPO=${{ github.repository }} BRANCH=${{ github.ref }} echo "Fetching list of cache keys" cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 ) - ## Setting this to not fail the workflow while deleting cache keys. + ## Setting this to not fail the workflow while deleting cache keys. set +e echo "Deleting caches..." for cacheKey in $cacheKeysForPR diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index dd16f9131f..a4401e982c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -20,6 +20,10 @@ on: schedule: - cron: '20 13 * * 1' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: analyze: name: Analyze @@ -48,11 +52,11 @@ jobs: # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. - + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild @@ -61,7 +65,7 @@ jobs: # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # If the Autobuild fails above, remove it and uncomment the following three lines. + # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | diff --git a/.github/workflows/dispatch_sdk_update.yml b/.github/workflows/dispatch_sdk_update.yml index 4789e7c186..06a29faf55 100644 --- a/.github/workflows/dispatch_sdk_update.yml +++ b/.github/workflows/dispatch_sdk_update.yml @@ -5,6 +5,10 @@ on: push: branches: ["main"] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: update-sdk-repos: runs-on: ubuntu-latest diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 180cf89ae2..c96dbdbb9f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,6 +9,10 @@ on: release: types: [published] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build_and_push: runs-on: ubuntu-latest diff --git a/.github/workflows/github-repo-stats.yml b/.github/workflows/github-repo-stats.yml index 85156d21e9..4037503898 100644 --- a/.github/workflows/github-repo-stats.yml +++ b/.github/workflows/github-repo-stats.yml @@ -7,6 +7,10 @@ on: - cron: "0 23 * * *" workflow_dispatch: # Allow for running this manually. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: j1: name: github-repo-stats diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 7010d08c7a..c197bab737 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -17,13 +17,17 @@ on: required: false type: boolean +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: bump_version: runs-on: ubuntu-latest - + outputs: ref: ${{ steps.push-tag.outputs.commit_long_sha }} - + steps: - name: Checkout uses: actions/checkout@v3 @@ -42,7 +46,7 @@ jobs: message: "Version ${{ env.IMMICH_VERSION }}" tag: ${{ env.IMMICH_VERSION }} push: true - + build_mobile: uses: ./.github/workflows/build-mobile.yml needs: bump_version @@ -59,7 +63,7 @@ jobs: uses: actions/checkout@v3 with: token: ${{ secrets.ORG_RELEASE_TOKEN }} - + - name: Download APK uses: actions/download-artifact@v3 with: diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 0eed542372..c46cad4a13 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -5,6 +5,10 @@ on: push: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: mobile-dart-analyze: name: Run Dart Code Analysis diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6509db356..995e7b450c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,10 @@ on: push: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: e2e-tests: name: Run end-to-end test suites @@ -54,6 +58,27 @@ jobs: working-directory: ./mobile run: flutter test + generated-api-up-to-date: + name: Check generated files are up-to-date + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run API generation + run: cd server && npm ci && npm run api:generate + - name: Find file changes + uses: tj-actions/verify-changed-files@v13.1 + id: verify-changed-files + with: + files: | + mobile/openapi + web/src/api/open-api + - name: Verify files have not changed + if: steps.verify-changed-files.outputs.files_changed == 'true' + run: | + echo "ERROR: Generated files not up to date!" + echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}" + exit 1 + mobile-integration-tests: name: Run mobile end-to-end integration tests runs-on: macos-latest diff --git a/mobile/.fvm/fvm_config.json b/mobile/.fvm/fvm_config.json new file mode 100644 index 0000000000..c2dd9f5372 --- /dev/null +++ b/mobile/.fvm/fvm_config.json @@ -0,0 +1,4 @@ +{ + "flutterSdkVersion": "3.7.0", + "flavors": {} +} \ No newline at end of file diff --git a/mobile/.gitignore b/mobile/.gitignore index 6c8e3cf5a8..c27f5ca3f1 100644 --- a/mobile/.gitignore +++ b/mobile/.gitignore @@ -32,6 +32,7 @@ .pub-cache/ .pub/ /build/ +.fvm/flutter_sdk # Web related lib/generated_plugin_registrant.dart @@ -48,4 +49,4 @@ app.*.map.json /android/app/release # Fastlane -ios/fastlane/report.xml \ No newline at end of file +ios/fastlane/report.xml diff --git a/mobile/lib/modules/album/views/album_viewer_page.dart b/mobile/lib/modules/album/views/album_viewer_page.dart index 23675c1cbb..834e847ec4 100644 --- a/mobile/lib/modules/album/views/album_viewer_page.dart +++ b/mobile/lib/modules/album/views/album_viewer_page.dart @@ -120,8 +120,8 @@ class AlbumViewerPage extends HookConsumerWidget { } Widget buildAlbumDateRange(Album album) { - final DateTime startDate = album.assets.first.createdAt; - final DateTime endDate = album.assets.last.createdAt; //Need default. + final DateTime startDate = album.assets.first.fileCreatedAt; + final DateTime endDate = album.assets.last.fileCreatedAt; //Need default. final String startDateText = DateFormat(startDate.year == endDate.year ? 'LLL d' : 'LLL d, y') .format(startDate); diff --git a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart index e528bb1b78..214196c162 100644 --- a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart +++ b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart @@ -146,7 +146,7 @@ class ExifBottomSheet extends HookConsumerWidget { buildDate() { return Text( DateFormat('date_format'.tr()).format( - assetDetail.createdAt.toLocal(), + assetDetail.fileCreatedAt.toLocal(), ), style: const TextStyle( fontWeight: FontWeight.bold, diff --git a/mobile/lib/modules/backup/models/current_upload_asset.model.dart b/mobile/lib/modules/backup/models/current_upload_asset.model.dart index 493fc55c94..ebcd99a209 100644 --- a/mobile/lib/modules/backup/models/current_upload_asset.model.dart +++ b/mobile/lib/modules/backup/models/current_upload_asset.model.dart @@ -2,26 +2,26 @@ import 'dart:convert'; class CurrentUploadAsset { final String id; - final DateTime createdAt; + final DateTime fileCreatedAt; final String fileName; final String fileType; CurrentUploadAsset({ required this.id, - required this.createdAt, + required this.fileCreatedAt, required this.fileName, required this.fileType, }); CurrentUploadAsset copyWith({ String? id, - DateTime? createdAt, + DateTime? fileCreatedAt, String? fileName, String? fileType, }) { return CurrentUploadAsset( id: id ?? this.id, - createdAt: createdAt ?? this.createdAt, + fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt, fileName: fileName ?? this.fileName, fileType: fileType ?? this.fileType, ); @@ -31,7 +31,7 @@ class CurrentUploadAsset { final result = {}; result.addAll({'id': id}); - result.addAll({'createdAt': createdAt.millisecondsSinceEpoch}); + result.addAll({'fileCreatedAt': fileCreatedAt.millisecondsSinceEpoch}); result.addAll({'fileName': fileName}); result.addAll({'fileType': fileType}); @@ -41,7 +41,7 @@ class CurrentUploadAsset { factory CurrentUploadAsset.fromMap(Map map) { return CurrentUploadAsset( id: map['id'] ?? '', - createdAt: DateTime.fromMillisecondsSinceEpoch(map['createdAt']), + fileCreatedAt: DateTime.fromMillisecondsSinceEpoch(map['fileCreatedAt']), fileName: map['fileName'] ?? '', fileType: map['fileType'] ?? '', ); @@ -54,7 +54,7 @@ class CurrentUploadAsset { @override String toString() { - return 'CurrentUploadAsset(id: $id, createdAt: $createdAt, fileName: $fileName, fileType: $fileType)'; + return 'CurrentUploadAsset(id: $id, fileCreatedAt: $fileCreatedAt, fileName: $fileName, fileType: $fileType)'; } @override @@ -63,7 +63,7 @@ class CurrentUploadAsset { return other is CurrentUploadAsset && other.id == id && - other.createdAt == createdAt && + other.fileCreatedAt == fileCreatedAt && other.fileName == fileName && other.fileType == fileType; } @@ -71,7 +71,7 @@ class CurrentUploadAsset { @override int get hashCode { return id.hashCode ^ - createdAt.hashCode ^ + fileCreatedAt.hashCode ^ fileName.hashCode ^ fileType.hashCode; } diff --git a/mobile/lib/modules/backup/models/error_upload_asset.model.dart b/mobile/lib/modules/backup/models/error_upload_asset.model.dart index d99872ef9c..b63592eda8 100644 --- a/mobile/lib/modules/backup/models/error_upload_asset.model.dart +++ b/mobile/lib/modules/backup/models/error_upload_asset.model.dart @@ -2,7 +2,7 @@ import 'package:photo_manager/photo_manager.dart'; class ErrorUploadAsset { final String id; - final DateTime createdAt; + final DateTime fileCreatedAt; final String fileName; final String fileType; final AssetEntity asset; @@ -10,7 +10,7 @@ class ErrorUploadAsset { const ErrorUploadAsset({ required this.id, - required this.createdAt, + required this.fileCreatedAt, required this.fileName, required this.fileType, required this.asset, @@ -19,7 +19,7 @@ class ErrorUploadAsset { ErrorUploadAsset copyWith({ String? id, - DateTime? createdAt, + DateTime? fileCreatedAt, String? fileName, String? fileType, AssetEntity? asset, @@ -27,7 +27,7 @@ class ErrorUploadAsset { }) { return ErrorUploadAsset( id: id ?? this.id, - createdAt: createdAt ?? this.createdAt, + fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt, fileName: fileName ?? this.fileName, fileType: fileType ?? this.fileType, asset: asset ?? this.asset, @@ -37,7 +37,7 @@ class ErrorUploadAsset { @override String toString() { - return 'ErrorUploadAsset(id: $id, createdAt: $createdAt, fileName: $fileName, fileType: $fileType, asset: $asset, errorMessage: $errorMessage)'; + return 'ErrorUploadAsset(id: $id, fileCreatedAt: $fileCreatedAt, fileName: $fileName, fileType: $fileType, asset: $asset, errorMessage: $errorMessage)'; } @override @@ -46,7 +46,7 @@ class ErrorUploadAsset { return other is ErrorUploadAsset && other.id == id && - other.createdAt == createdAt && + other.fileCreatedAt == fileCreatedAt && other.fileName == fileName && other.fileType == fileType && other.asset == asset && @@ -56,7 +56,7 @@ class ErrorUploadAsset { @override int get hashCode { return id.hashCode ^ - createdAt.hashCode ^ + fileCreatedAt.hashCode ^ fileName.hashCode ^ fileType.hashCode ^ asset.hashCode ^ diff --git a/mobile/lib/modules/backup/providers/backup.provider.dart b/mobile/lib/modules/backup/providers/backup.provider.dart index 218d4db21b..38b4670b23 100644 --- a/mobile/lib/modules/backup/providers/backup.provider.dart +++ b/mobile/lib/modules/backup/providers/backup.provider.dart @@ -55,7 +55,7 @@ class BackupNotifier extends StateNotifier { selectedAlbumsBackupAssetsIds: const {}, currentUploadAsset: CurrentUploadAsset( id: '...', - createdAt: DateTime.parse('2020-10-04'), + fileCreatedAt: DateTime.parse('2020-10-04'), fileName: '...', fileType: '...', ), diff --git a/mobile/lib/modules/backup/services/backup.service.dart b/mobile/lib/modules/backup/services/backup.service.dart index 1c4a6c8c37..0eac69ee31 100644 --- a/mobile/lib/modules/backup/services/backup.service.dart +++ b/mobile/lib/modules/backup/services/backup.service.dart @@ -260,8 +260,8 @@ class BackupService { req.fields['deviceAssetId'] = entity.id; req.fields['deviceId'] = deviceId; req.fields['assetType'] = _getAssetType(entity.type); - req.fields['createdAt'] = entity.createDateTime.toIso8601String(); - req.fields['modifiedAt'] = entity.modifiedDateTime.toIso8601String(); + req.fields['fileCreatedAt'] = entity.createDateTime.toIso8601String(); + req.fields['fileModifiedAt'] = entity.modifiedDateTime.toIso8601String(); req.fields['isFavorite'] = entity.isFavorite.toString(); req.fields['fileExtension'] = fileExtension; req.fields['duration'] = entity.videoDuration.toString(); @@ -278,7 +278,7 @@ class BackupService { setCurrentUploadAssetCb( CurrentUploadAsset( id: entity.id, - createdAt: entity.createDateTime.year == 1970 + fileCreatedAt: entity.createDateTime.year == 1970 ? entity.modifiedDateTime : entity.createDateTime, fileName: originalFileName, @@ -308,7 +308,7 @@ class BackupService { ErrorUploadAsset( asset: entity, id: entity.id, - createdAt: entity.createDateTime, + fileCreatedAt: entity.createDateTime, fileName: originalFileName, fileType: _getAssetType(entity.type), errorMessage: error['error'], diff --git a/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart b/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart index 2c08c9a45d..63a783de63 100644 --- a/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart +++ b/mobile/lib/modules/backup/ui/current_backup_asset_info_box.dart @@ -20,7 +20,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget { String getAssetCreationDate() { return DateFormat.yMMMMd('en_US').format( DateTime.parse( - asset.createdAt.toString(), + asset.fileCreatedAt.toString(), ).toLocal(), ); } diff --git a/mobile/lib/modules/backup/views/failed_backup_status_page.dart b/mobile/lib/modules/backup/views/failed_backup_status_page.dart index 056af2ecf2..46cf573fb1 100644 --- a/mobile/lib/modules/backup/views/failed_backup_status_page.dart +++ b/mobile/lib/modules/backup/views/failed_backup_status_page.dart @@ -89,7 +89,7 @@ class FailedBackupStatusPage extends HookConsumerWidget { Text( DateFormat.yMMMMd('en_US').format( DateTime.parse( - errorAsset.createdAt.toString(), + errorAsset.fileCreatedAt.toString(), ).toLocal(), ), style: TextStyle( diff --git a/mobile/lib/modules/home/ui/asset_grid/asset_grid_data_structure.dart b/mobile/lib/modules/home/ui/asset_grid/asset_grid_data_structure.dart index 4db0b1eb82..8e003e8add 100644 --- a/mobile/lib/modules/home/ui/asset_grid/asset_grid_data_structure.dart +++ b/mobile/lib/modules/home/ui/asset_grid/asset_grid_data_structure.dart @@ -82,14 +82,14 @@ class RenderList { if (groupBy == GroupAssetsBy.day) { return assets.groupListsBy( (element) { - final date = element.createdAt.toLocal(); + final date = element.fileCreatedAt.toLocal(); return DateTime(date.year, date.month, date.day); }, ); } else if (groupBy == GroupAssetsBy.month) { return assets.groupListsBy( (element) { - final date = element.createdAt.toLocal(); + final date = element.fileCreatedAt.toLocal(); return DateTime(date.year, date.month); }, ); diff --git a/mobile/lib/shared/models/asset.dart b/mobile/lib/shared/models/asset.dart index 567e42a1a0..af6f43b57c 100644 --- a/mobile/lib/shared/models/asset.dart +++ b/mobile/lib/shared/models/asset.dart @@ -10,8 +10,8 @@ import 'package:path/path.dart' as p; class Asset { Asset.remote(AssetResponseDto remote) : remoteId = remote.id, - createdAt = DateTime.parse(remote.createdAt), - modifiedAt = DateTime.parse(remote.modifiedAt), + fileCreatedAt = DateTime.parse(remote.fileCreatedAt), + fileModifiedAt = DateTime.parse(remote.fileModifiedAt), durationInSeconds = remote.duration.toDuration().inSeconds, fileName = p.basename(remote.originalPath), height = remote.exifInfo?.exifImageHeight?.toInt(), @@ -37,11 +37,11 @@ class Asset { deviceAssetId = local.id, deviceId = Hive.box(userInfoBox).get(deviceIdKey), ownerId = owner, - modifiedAt = local.modifiedDateTime.toUtc(), + fileModifiedAt = local.modifiedDateTime.toUtc(), isFavorite = local.isFavorite, - createdAt = local.createDateTime.toUtc() { - if (createdAt.year == 1970) { - createdAt = modifiedAt; + fileCreatedAt = local.createDateTime.toUtc() { + if (fileCreatedAt.year == 1970) { + fileCreatedAt = fileModifiedAt; } } @@ -51,8 +51,8 @@ class Asset { required this.deviceAssetId, required this.deviceId, required this.ownerId, - required this.createdAt, - required this.modifiedAt, + required this.fileCreatedAt, + required this.fileModifiedAt, this.latitude, this.longitude, required this.durationInSeconds, @@ -74,10 +74,10 @@ class Asset { width: width!, height: height!, duration: durationInSeconds, - createDateSecond: createdAt.millisecondsSinceEpoch ~/ 1000, + createDateSecond: fileCreatedAt.millisecondsSinceEpoch ~/ 1000, latitude: latitude, longitude: longitude, - modifiedDateSecond: modifiedAt.millisecondsSinceEpoch ~/ 1000, + modifiedDateSecond: fileModifiedAt.millisecondsSinceEpoch ~/ 1000, title: fileName, ); } @@ -94,9 +94,9 @@ class Asset { String ownerId; - DateTime createdAt; + DateTime fileCreatedAt; - DateTime modifiedAt; + DateTime fileModifiedAt; double? latitude; @@ -146,8 +146,8 @@ class Asset { json["deviceAssetId"] = deviceAssetId; json["deviceId"] = deviceId; json["ownerId"] = ownerId; - json["createdAt"] = createdAt.millisecondsSinceEpoch; - json["modifiedAt"] = modifiedAt.millisecondsSinceEpoch; + json["fileCreatedAt"] = fileCreatedAt.millisecondsSinceEpoch; + json["fileModifiedAt"] = fileModifiedAt.millisecondsSinceEpoch; json["latitude"] = latitude; json["longitude"] = longitude; json["durationInSeconds"] = durationInSeconds; @@ -171,10 +171,10 @@ class Asset { deviceAssetId: json["deviceAssetId"], deviceId: json["deviceId"], ownerId: json["ownerId"], - createdAt: - DateTime.fromMillisecondsSinceEpoch(json["createdAt"], isUtc: true), - modifiedAt: DateTime.fromMillisecondsSinceEpoch( - json["modifiedAt"], + fileCreatedAt: + DateTime.fromMillisecondsSinceEpoch(json["fileCreatedAt"], isUtc: true), + fileModifiedAt: DateTime.fromMillisecondsSinceEpoch( + json["fileModifiedAt"], isUtc: true, ), latitude: json["latitude"], diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index c124418943..5805187c72 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -302,11 +302,11 @@ final assetGroupByMonthYearProvider = StateProvider((ref) { ref.watch(assetProvider).allAssets.where((e) => e.isRemote).toList(); assets.sortByCompare( - (e) => e.createdAt, + (e) => e.fileCreatedAt, (a, b) => b.compareTo(a), ); return assets.groupListsBy( - (element) => DateFormat('MMMM, y').format(element.createdAt.toLocal()), + (element) => DateFormat('MMMM, y').format(element.fileCreatedAt.toLocal()), ); }); diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index e95dc19083..5801f0d8b6 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -3,7 +3,7 @@ Immich API This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: -- API version: 1.47.2 +- API version: 1.47.3 - Build package: org.openapitools.codegen.languages.DartClientCodegen ## Requirements diff --git a/mobile/openapi/doc/APIKeyApi.md b/mobile/openapi/doc/APIKeyApi.md index 4918e61616..72af85513a 100644 --- a/mobile/openapi/doc/APIKeyApi.md +++ b/mobile/openapi/doc/APIKeyApi.md @@ -71,7 +71,7 @@ No authorization required import 'package:openapi/api.dart'; final api_instance = APIKeyApi(); -final id = 8.14; // num | +final id = id_example; // String | try { api_instance.deleteKey(id); @@ -84,7 +84,7 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **id** | **num**| | + **id** | **String**| | ### Return type @@ -113,7 +113,7 @@ No authorization required import 'package:openapi/api.dart'; final api_instance = APIKeyApi(); -final id = 8.14; // num | +final id = id_example; // String | try { final result = api_instance.getKey(id); @@ -127,7 +127,7 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **id** | **num**| | + **id** | **String**| | ### Return type @@ -195,7 +195,7 @@ No authorization required import 'package:openapi/api.dart'; final api_instance = APIKeyApi(); -final id = 8.14; // num | +final id = id_example; // String | final aPIKeyUpdateDto = APIKeyUpdateDto(); // APIKeyUpdateDto | try { @@ -210,7 +210,7 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **id** | **num**| | + **id** | **String**| | **aPIKeyUpdateDto** | [**APIKeyUpdateDto**](APIKeyUpdateDto.md)| | ### Return type diff --git a/mobile/openapi/doc/APIKeyResponseDto.md b/mobile/openapi/doc/APIKeyResponseDto.md index 78ccbc19ca..81367fb184 100644 --- a/mobile/openapi/doc/APIKeyResponseDto.md +++ b/mobile/openapi/doc/APIKeyResponseDto.md @@ -8,7 +8,7 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**id** | **int** | | +**id** | **String** | | **name** | **String** | | **createdAt** | **String** | | **updatedAt** | **String** | | diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 595a21d0c9..ac35640dd3 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -1109,7 +1109,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **uploadFile** -> AssetFileUploadResponseDto uploadFile(assetType, assetData, deviceAssetId, deviceId, createdAt, modifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration) +> AssetFileUploadResponseDto uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration) @@ -1130,8 +1130,8 @@ final assetType = ; // AssetTypeEnum | final assetData = BINARY_DATA_HERE; // MultipartFile | final deviceAssetId = deviceAssetId_example; // String | final deviceId = deviceId_example; // String | -final createdAt = createdAt_example; // String | -final modifiedAt = modifiedAt_example; // String | +final fileCreatedAt = fileCreatedAt_example; // String | +final fileModifiedAt = fileModifiedAt_example; // String | final isFavorite = true; // bool | final fileExtension = fileExtension_example; // String | final livePhotoData = BINARY_DATA_HERE; // MultipartFile | @@ -1139,7 +1139,7 @@ final isVisible = true; // bool | final duration = duration_example; // String | try { - final result = api_instance.uploadFile(assetType, assetData, deviceAssetId, deviceId, createdAt, modifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration); + final result = api_instance.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration); print(result); } catch (e) { print('Exception when calling AssetApi->uploadFile: $e\n'); @@ -1154,8 +1154,8 @@ Name | Type | Description | Notes **assetData** | **MultipartFile**| | **deviceAssetId** | **String**| | **deviceId** | **String**| | - **createdAt** | **String**| | - **modifiedAt** | **String**| | + **fileCreatedAt** | **String**| | + **fileModifiedAt** | **String**| | **isFavorite** | **bool**| | **fileExtension** | **String**| | **livePhotoData** | **MultipartFile**| | [optional] diff --git a/mobile/openapi/doc/AssetResponseDto.md b/mobile/openapi/doc/AssetResponseDto.md index fca4ec6ea6..44754a6c4d 100644 --- a/mobile/openapi/doc/AssetResponseDto.md +++ b/mobile/openapi/doc/AssetResponseDto.md @@ -15,8 +15,8 @@ Name | Type | Description | Notes **deviceId** | **String** | | **originalPath** | **String** | | **resizePath** | **String** | | -**createdAt** | **String** | | -**modifiedAt** | **String** | | +**fileCreatedAt** | **String** | | +**fileModifiedAt** | **String** | | **updatedAt** | **String** | | **isFavorite** | **bool** | | **mimeType** | **String** | | diff --git a/mobile/openapi/lib/api/api_key_api.dart b/mobile/openapi/lib/api/api_key_api.dart index 7cc9a3589c..e13eb32e1a 100644 --- a/mobile/openapi/lib/api/api_key_api.dart +++ b/mobile/openapi/lib/api/api_key_api.dart @@ -74,11 +74,11 @@ class APIKeyApi { /// /// Parameters: /// - /// * [num] id (required): - Future deleteKeyWithHttpInfo(num id,) async { + /// * [String] id (required): + Future deleteKeyWithHttpInfo(String id,) async { // ignore: prefer_const_declarations final path = r'/api-key/{id}' - .replaceAll('{id}', id.toString()); + .replaceAll('{id}', id); // ignore: prefer_final_locals Object? postBody; @@ -105,8 +105,8 @@ class APIKeyApi { /// /// Parameters: /// - /// * [num] id (required): - Future deleteKey(num id,) async { + /// * [String] id (required): + Future deleteKey(String id,) async { final response = await deleteKeyWithHttpInfo(id,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -119,11 +119,11 @@ class APIKeyApi { /// /// Parameters: /// - /// * [num] id (required): - Future getKeyWithHttpInfo(num id,) async { + /// * [String] id (required): + Future getKeyWithHttpInfo(String id,) async { // ignore: prefer_const_declarations final path = r'/api-key/{id}' - .replaceAll('{id}', id.toString()); + .replaceAll('{id}', id); // ignore: prefer_final_locals Object? postBody; @@ -150,8 +150,8 @@ class APIKeyApi { /// /// Parameters: /// - /// * [num] id (required): - Future getKey(num id,) async { + /// * [String] id (required): + Future getKey(String id,) async { final response = await getKeyWithHttpInfo(id,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -219,13 +219,13 @@ class APIKeyApi { /// /// Parameters: /// - /// * [num] id (required): + /// * [String] id (required): /// /// * [APIKeyUpdateDto] aPIKeyUpdateDto (required): - Future updateKeyWithHttpInfo(num id, APIKeyUpdateDto aPIKeyUpdateDto,) async { + Future updateKeyWithHttpInfo(String id, APIKeyUpdateDto aPIKeyUpdateDto,) async { // ignore: prefer_const_declarations final path = r'/api-key/{id}' - .replaceAll('{id}', id.toString()); + .replaceAll('{id}', id); // ignore: prefer_final_locals Object? postBody = aPIKeyUpdateDto; @@ -252,10 +252,10 @@ class APIKeyApi { /// /// Parameters: /// - /// * [num] id (required): + /// * [String] id (required): /// /// * [APIKeyUpdateDto] aPIKeyUpdateDto (required): - Future updateKey(num id, APIKeyUpdateDto aPIKeyUpdateDto,) async { + Future updateKey(String id, APIKeyUpdateDto aPIKeyUpdateDto,) async { final response = await updateKeyWithHttpInfo(id, aPIKeyUpdateDto,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index cf360d2418..3bedc290f8 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -1224,9 +1224,9 @@ class AssetApi { /// /// * [String] deviceId (required): /// - /// * [String] createdAt (required): + /// * [String] fileCreatedAt (required): /// - /// * [String] modifiedAt (required): + /// * [String] fileModifiedAt (required): /// /// * [bool] isFavorite (required): /// @@ -1237,7 +1237,7 @@ class AssetApi { /// * [bool] isVisible: /// /// * [String] duration: - Future uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String createdAt, String modifiedAt, bool isFavorite, String fileExtension, { MultipartFile? livePhotoData, bool? isVisible, String? duration, }) async { + Future uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { MultipartFile? livePhotoData, bool? isVisible, String? duration, }) async { // ignore: prefer_const_declarations final path = r'/asset/upload'; @@ -1274,13 +1274,13 @@ class AssetApi { hasFields = true; mp.fields[r'deviceId'] = parameterToString(deviceId); } - if (createdAt != null) { + if (fileCreatedAt != null) { hasFields = true; - mp.fields[r'createdAt'] = parameterToString(createdAt); + mp.fields[r'fileCreatedAt'] = parameterToString(fileCreatedAt); } - if (modifiedAt != null) { + if (fileModifiedAt != null) { hasFields = true; - mp.fields[r'modifiedAt'] = parameterToString(modifiedAt); + mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt); } if (isFavorite != null) { hasFields = true; @@ -1325,9 +1325,9 @@ class AssetApi { /// /// * [String] deviceId (required): /// - /// * [String] createdAt (required): + /// * [String] fileCreatedAt (required): /// - /// * [String] modifiedAt (required): + /// * [String] fileModifiedAt (required): /// /// * [bool] isFavorite (required): /// @@ -1338,8 +1338,8 @@ class AssetApi { /// * [bool] isVisible: /// /// * [String] duration: - Future uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String createdAt, String modifiedAt, bool isFavorite, String fileExtension, { MultipartFile? livePhotoData, bool? isVisible, String? duration, }) async { - final response = await uploadFileWithHttpInfo(assetType, assetData, deviceAssetId, deviceId, createdAt, modifiedAt, isFavorite, fileExtension, livePhotoData: livePhotoData, isVisible: isVisible, duration: duration, ); + Future uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { MultipartFile? livePhotoData, bool? isVisible, String? duration, }) async { + final response = await uploadFileWithHttpInfo(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, livePhotoData: livePhotoData, isVisible: isVisible, duration: duration, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/model/api_key_response_dto.dart b/mobile/openapi/lib/model/api_key_response_dto.dart index 3930829e26..5d40014ecb 100644 --- a/mobile/openapi/lib/model/api_key_response_dto.dart +++ b/mobile/openapi/lib/model/api_key_response_dto.dart @@ -19,7 +19,7 @@ class APIKeyResponseDto { required this.updatedAt, }); - int id; + String id; String name; @@ -73,7 +73,7 @@ class APIKeyResponseDto { }()); return APIKeyResponseDto( - id: mapValueOfType(json, r'id')!, + id: mapValueOfType(json, r'id')!, name: mapValueOfType(json, r'name')!, createdAt: mapValueOfType(json, r'createdAt')!, updatedAt: mapValueOfType(json, r'updatedAt')!, diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index 0db3be6be9..013ac00ff4 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -20,8 +20,8 @@ class AssetResponseDto { required this.deviceId, required this.originalPath, required this.resizePath, - required this.createdAt, - required this.modifiedAt, + required this.fileCreatedAt, + required this.fileModifiedAt, required this.updatedAt, required this.isFavorite, required this.mimeType, @@ -48,9 +48,9 @@ class AssetResponseDto { String? resizePath; - String createdAt; + String fileCreatedAt; - String modifiedAt; + String fileModifiedAt; String updatedAt; @@ -93,8 +93,8 @@ class AssetResponseDto { other.deviceId == deviceId && other.originalPath == originalPath && other.resizePath == resizePath && - other.createdAt == createdAt && - other.modifiedAt == modifiedAt && + other.fileCreatedAt == fileCreatedAt && + other.fileModifiedAt == fileModifiedAt && other.updatedAt == updatedAt && other.isFavorite == isFavorite && other.mimeType == mimeType && @@ -116,8 +116,8 @@ class AssetResponseDto { (deviceId.hashCode) + (originalPath.hashCode) + (resizePath == null ? 0 : resizePath!.hashCode) + - (createdAt.hashCode) + - (modifiedAt.hashCode) + + (fileCreatedAt.hashCode) + + (fileModifiedAt.hashCode) + (updatedAt.hashCode) + (isFavorite.hashCode) + (mimeType == null ? 0 : mimeType!.hashCode) + @@ -130,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, updatedAt=$updatedAt, 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, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]'; Map toJson() { final json = {}; @@ -145,8 +145,8 @@ class AssetResponseDto { } else { // json[r'resizePath'] = null; } - json[r'createdAt'] = this.createdAt; - json[r'modifiedAt'] = this.modifiedAt; + json[r'fileCreatedAt'] = this.fileCreatedAt; + json[r'fileModifiedAt'] = this.fileModifiedAt; json[r'updatedAt'] = this.updatedAt; json[r'isFavorite'] = this.isFavorite; if (this.mimeType != null) { @@ -210,8 +210,8 @@ class AssetResponseDto { deviceId: mapValueOfType(json, r'deviceId')!, originalPath: mapValueOfType(json, r'originalPath')!, resizePath: mapValueOfType(json, r'resizePath'), - createdAt: mapValueOfType(json, r'createdAt')!, - modifiedAt: mapValueOfType(json, r'modifiedAt')!, + fileCreatedAt: mapValueOfType(json, r'fileCreatedAt')!, + fileModifiedAt: mapValueOfType(json, r'fileModifiedAt')!, updatedAt: mapValueOfType(json, r'updatedAt')!, isFavorite: mapValueOfType(json, r'isFavorite')!, mimeType: mapValueOfType(json, r'mimeType'), @@ -278,8 +278,8 @@ class AssetResponseDto { 'deviceId', 'originalPath', 'resizePath', - 'createdAt', - 'modifiedAt', + 'fileCreatedAt', + 'fileModifiedAt', 'updatedAt', 'isFavorite', 'mimeType', diff --git a/mobile/openapi/test/api_key_api_test.dart b/mobile/openapi/test/api_key_api_test.dart index df8272065c..a91662168b 100644 --- a/mobile/openapi/test/api_key_api_test.dart +++ b/mobile/openapi/test/api_key_api_test.dart @@ -26,14 +26,14 @@ void main() { // // - //Future deleteKey(num id) async + //Future deleteKey(String id) async test('test deleteKey', () async { // TODO }); // // - //Future getKey(num id) async + //Future getKey(String id) async test('test getKey', () async { // TODO }); @@ -47,7 +47,7 @@ void main() { // // - //Future updateKey(num id, APIKeyUpdateDto aPIKeyUpdateDto) async + //Future updateKey(String id, APIKeyUpdateDto aPIKeyUpdateDto) async test('test updateKey', () async { // TODO }); diff --git a/mobile/openapi/test/api_key_response_dto_test.dart b/mobile/openapi/test/api_key_response_dto_test.dart index a9c7b7e858..702f474116 100644 --- a/mobile/openapi/test/api_key_response_dto_test.dart +++ b/mobile/openapi/test/api_key_response_dto_test.dart @@ -16,7 +16,7 @@ void main() { // final instance = APIKeyResponseDto(); group('test APIKeyResponseDto', () { - // int id + // String id test('to test the property `id`', () async { // TODO }); diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index 6c23680d27..877b5b0aa3 100644 --- a/mobile/openapi/test/asset_api_test.dart +++ b/mobile/openapi/test/asset_api_test.dart @@ -173,7 +173,7 @@ void main() { // // - //Future uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String createdAt, String modifiedAt, bool isFavorite, String fileExtension, { MultipartFile livePhotoData, bool isVisible, String duration }) async + //Future uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { MultipartFile livePhotoData, bool isVisible, String duration }) async test('test uploadFile', () async { // TODO }); diff --git a/mobile/openapi/test/asset_response_dto_test.dart b/mobile/openapi/test/asset_response_dto_test.dart index f4cce051e2..35a98737b9 100644 --- a/mobile/openapi/test/asset_response_dto_test.dart +++ b/mobile/openapi/test/asset_response_dto_test.dart @@ -51,13 +51,13 @@ void main() { // TODO }); - // String createdAt - test('to test the property `createdAt`', () async { + // String fileCreatedAt + test('to test the property `fileCreatedAt`', () async { // TODO }); - // String modifiedAt - test('to test the property `modifiedAt`', () async { + // String fileModifiedAt + test('to test the property `fileModifiedAt`', () async { // TODO }); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index fa32ec7177..9bf3e466e3 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -1513,5 +1513,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <4.0.0" flutter: ">=3.3.0" diff --git a/mobile/test/asset_grid_data_structure_test.dart b/mobile/test/asset_grid_data_structure_test.dart index 6b715d20b4..a5631b2c70 100644 --- a/mobile/test/asset_grid_data_structure_test.dart +++ b/mobile/test/asset_grid_data_structure_test.dart @@ -16,8 +16,8 @@ void main() { deviceAssetId: '$i', deviceId: '', ownerId: '', - createdAt: date, - modifiedAt: date, + fileCreatedAt: date, + fileModifiedAt: date, durationInSeconds: 0, fileName: '', isFavorite: false, @@ -29,25 +29,25 @@ void main() { assets.addAll( testAssets.sublist(0, 5).map((e) { - e.createdAt = DateTime(2022, 1, 5); + e.fileCreatedAt = DateTime(2022, 1, 5); return e; }).toList(), ); assets.addAll( testAssets.sublist(5, 10).map((e) { - e.createdAt = DateTime(2022, 1, 10); + e.fileCreatedAt = DateTime(2022, 1, 10); return e; }).toList(), ); assets.addAll( testAssets.sublist(10, 15).map((e) { - e.createdAt = DateTime(2022, 2, 17); + e.fileCreatedAt = DateTime(2022, 2, 17); return e; }).toList(), ); assets.addAll( testAssets.sublist(15, 30).map((e) { - e.createdAt = DateTime(2022, 10, 15); + e.fileCreatedAt = DateTime(2022, 10, 15); return e; }).toList(), ); diff --git a/server/apps/immich/src/api-v1/album/album-repository.ts b/server/apps/immich/src/api-v1/album/album-repository.ts index 11d0260fde..9e8ad66357 100644 --- a/server/apps/immich/src/api-v1/album/album-repository.ts +++ b/server/apps/immich/src/api-v1/album/album-repository.ts @@ -79,7 +79,7 @@ export class AlbumRepository implements IAlbumRepository { const queryProperties: FindManyOptions = { relations: { sharedUsers: true, assets: true, sharedLinks: true, owner: true }, - order: { assets: { createdAt: 'ASC' }, createdAt: 'ASC' }, + order: { assets: { fileCreatedAt: 'ASC' }, createdAt: 'ASC' }, }; let albumsQuery: Promise; @@ -123,7 +123,7 @@ export class AlbumRepository implements IAlbumRepository { const albums = await this.albumRepository.find({ where: { ownerId: userId, assets: { id: assetId } }, relations: { owner: true, assets: true, sharedUsers: true }, - order: { assets: { createdAt: 'ASC' } }, + order: { assets: { fileCreatedAt: 'ASC' } }, }); return albums; @@ -142,7 +142,7 @@ export class AlbumRepository implements IAlbumRepository { }, order: { assets: { - createdAt: 'ASC', + fileCreatedAt: 'ASC', }, }, }); diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts index 1a6ec82612..9670ee86c3 100644 --- a/server/apps/immich/src/api-v1/asset/asset-repository.ts +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -19,7 +19,9 @@ import { AssetSearchDto } from './dto/asset-search.dto'; export interface IAssetRepository { get(id: string): Promise; - create(asset: Omit): Promise; + create( + asset: Omit, + ): Promise; remove(asset: AssetEntity): Promise; update(userId: string, asset: AssetEntity, dto: UpdateAssetDto): Promise; @@ -112,13 +114,13 @@ export class AssetRepository implements IAssetRepository { .getMany(); } - async getAssetCountByUserId(userId: string): Promise { + async getAssetCountByUserId(ownerId: string): Promise { // Get asset count by AssetType const items = await this.assetRepository .createQueryBuilder('asset') .select(`COUNT(asset.id)`, 'count') .addSelect(`asset.type`, 'type') - .where('"userId" = :userId', { userId: userId }) + .where('"ownerId" = :ownerId', { ownerId: ownerId }) .andWhere('asset.isVisible = true') .groupBy('asset.type') .getRawMany(); @@ -149,7 +151,7 @@ export class AssetRepository implements IAssetRepository { // Get asset entity from a list of time buckets return await this.assetRepository .createQueryBuilder('asset') - .where('asset.userId = :userId', { userId: userId }) + .where('asset.ownerId = :userId', { userId: userId }) .andWhere(`date_trunc('month', "createdAt") IN (:...buckets)`, { buckets: [...getAssetByTimeBucketDto.timeBucket], }) @@ -167,7 +169,7 @@ export class AssetRepository implements IAssetRepository { .createQueryBuilder('asset') .select(`COUNT(asset.id)::int`, 'count') .addSelect(`date_trunc('month', "createdAt")`, 'timeBucket') - .where('"userId" = :userId', { userId: userId }) + .where('"ownerId" = :userId', { userId: userId }) .andWhere('asset.resizePath is not NULL') .andWhere('asset.isVisible = true') .groupBy(`date_trunc('month', "createdAt")`) @@ -178,7 +180,7 @@ export class AssetRepository implements IAssetRepository { .createQueryBuilder('asset') .select(`COUNT(asset.id)::int`, 'count') .addSelect(`date_trunc('day', "createdAt")`, 'timeBucket') - .where('"userId" = :userId', { userId: userId }) + .where('"ownerId" = :userId', { userId: userId }) .andWhere('asset.resizePath is not NULL') .andWhere('asset.isVisible = true') .groupBy(`date_trunc('day', "createdAt")`) @@ -192,7 +194,7 @@ export class AssetRepository implements IAssetRepository { async getSearchPropertiesByUserId(userId: string): Promise { return await this.assetRepository .createQueryBuilder('asset') - .where('asset.userId = :userId', { userId: userId }) + .where('asset.ownerId = :userId', { userId: userId }) .andWhere('asset.isVisible = true') .leftJoin('asset.exifInfo', 'ei') .leftJoin('asset.smartInfo', 'si') @@ -216,7 +218,7 @@ export class AssetRepository implements IAssetRepository { SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId" FROM assets a LEFT JOIN smart_info si ON a.id = si."assetId" - WHERE a."userId" = $1 + WHERE a."ownerId" = $1 AND a."isVisible" = true AND si.objects IS NOT NULL `, @@ -230,7 +232,7 @@ export class AssetRepository implements IAssetRepository { SELECT DISTINCT ON (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId" FROM assets a LEFT JOIN exif e ON a.id = e."assetId" - WHERE a."userId" = $1 + WHERE a."ownerId" = $1 AND a."isVisible" = true AND e.city IS NOT NULL AND a.type = 'IMAGE'; @@ -255,12 +257,12 @@ export class AssetRepository implements IAssetRepository { /** * Get all assets belong to the user on the database - * @param userId + * @param ownerId */ - async getAllByUserId(userId: string, dto: AssetSearchDto): Promise { + async getAllByUserId(ownerId: string, dto: AssetSearchDto): Promise { return this.assetRepository.find({ where: { - userId, + ownerId, resizePath: Not(IsNull()), isVisible: true, isFavorite: dto.isFavorite, @@ -271,7 +273,7 @@ export class AssetRepository implements IAssetRepository { }, skip: dto.skip || 0, order: { - createdAt: 'DESC', + fileCreatedAt: 'DESC', }, }); } @@ -280,7 +282,9 @@ export class AssetRepository implements IAssetRepository { return this.assetRepository.findOne({ where: { id } }); } - async create(asset: Omit): Promise { + async create( + asset: Omit, + ): Promise { return this.assetRepository.save(asset); } @@ -304,16 +308,16 @@ export class AssetRepository implements IAssetRepository { /** * Get assets by device's Id on the database - * @param userId + * @param ownerId * @param deviceId * * @returns Promise - Array of assetIds belong to the device */ - async getAllByDeviceId(userId: string, deviceId: string): Promise { + async getAllByDeviceId(ownerId: string, deviceId: string): Promise { const rows = await this.assetRepository.find({ where: { - userId: userId, - deviceId: deviceId, + ownerId, + deviceId, isVisible: true, }, select: ['deviceAssetId'], @@ -326,14 +330,14 @@ export class AssetRepository implements IAssetRepository { /** * Get asset by checksum on the database - * @param userId + * @param ownerId * @param checksum * */ - getAssetByChecksum(userId: string, checksum: Buffer): Promise { + getAssetByChecksum(ownerId: string, checksum: Buffer): Promise { return this.assetRepository.findOneOrFail({ where: { - userId, + ownerId, checksum, }, relations: ['exifInfo'], @@ -341,7 +345,7 @@ export class AssetRepository implements IAssetRepository { } async getExistingAssets( - userId: string, + ownerId: string, checkDuplicateAssetDto: CheckExistingAssetsDto, ): Promise { const existingAssets = await this.assetRepository.find({ @@ -349,17 +353,17 @@ export class AssetRepository implements IAssetRepository { where: { deviceAssetId: In(checkDuplicateAssetDto.deviceAssetIds), deviceId: checkDuplicateAssetDto.deviceId, - userId, + ownerId, }, }); return new CheckExistingAssetsResponseDto(existingAssets.map((a) => a.deviceAssetId)); } - async countByIdAndUser(assetId: string, userId: string): Promise { + async countByIdAndUser(assetId: string, ownerId: string): Promise { return await this.assetRepository.count({ where: { id: assetId, - userId, + ownerId, }, }); } diff --git a/server/apps/immich/src/api-v1/asset/asset.core.ts b/server/apps/immich/src/api-v1/asset/asset.core.ts index 5fbe9c8816..f8b5c6ddb4 100644 --- a/server/apps/immich/src/api-v1/asset/asset.core.ts +++ b/server/apps/immich/src/api-v1/asset/asset.core.ts @@ -1,6 +1,5 @@ -import { timeUtils } from '@app/common'; import { AuthUserDto, IJobRepository, JobName } from '@app/domain'; -import { AssetEntity } from '@app/infra/db/entities'; +import { AssetEntity, UserEntity } from '@app/infra/db/entities'; import { StorageService } from '@app/storage'; import { IAssetRepository } from './asset-repository'; import { CreateAssetDto, UploadFile } from './dto/create-asset.dto'; @@ -19,24 +18,23 @@ export class AssetCore { livePhotoAssetId?: string, ): Promise { let asset = await this.repository.create({ - userId: authUser.id, + owner: { id: authUser.id } as UserEntity, mimeType: file.mimeType, checksum: file.checksum || null, originalPath: file.originalPath, - 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, + fileCreatedAt: dto.fileCreatedAt, + fileModifiedAt: dto.fileModifiedAt, + type: dto.assetType, isFavorite: dto.isFavorite, duration: dto.duration || null, isVisible: dto.isVisible ?? true, - livePhotoVideoId: livePhotoAssetId || null, + livePhotoVideo: livePhotoAssetId != null ? ({ id: livePhotoAssetId } as AssetEntity) : null, resizePath: null, webpPath: null, encodedVideoPath: null, diff --git a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts index 6d9f1750a8..305ee64c66 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts @@ -27,8 +27,8 @@ const _getCreateAssetDto = (): CreateAssetDto => { createAssetDto.deviceAssetId = 'deviceAssetId'; createAssetDto.deviceId = 'deviceId'; createAssetDto.assetType = AssetType.OTHER; - createAssetDto.createdAt = '2022-06-19T23:41:36.910Z'; - createAssetDto.modifiedAt = '2022-06-19T23:41:36.910Z'; + createAssetDto.fileCreatedAt = '2022-06-19T23:41:36.910Z'; + createAssetDto.fileModifiedAt = '2022-06-19T23:41:36.910Z'; createAssetDto.isFavorite = false; createAssetDto.duration = '0:00:00.000000'; @@ -39,14 +39,15 @@ const _getAsset_1 = () => { const asset_1 = new AssetEntity(); asset_1.id = 'id_1'; - asset_1.userId = 'user_id_1'; + asset_1.ownerId = 'user_id_1'; asset_1.deviceAssetId = 'device_asset_id_1'; asset_1.deviceId = 'device_id_1'; asset_1.type = AssetType.VIDEO; asset_1.originalPath = 'fake_path/asset_1.jpeg'; asset_1.resizePath = ''; - asset_1.createdAt = '2022-06-19T23:41:36.910Z'; - asset_1.modifiedAt = '2022-06-19T23:41:36.910Z'; + 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.isFavorite = false; asset_1.mimeType = 'image/jpeg'; asset_1.webpPath = ''; @@ -59,14 +60,15 @@ const _getAsset_2 = () => { const asset_2 = new AssetEntity(); asset_2.id = 'id_2'; - asset_2.userId = 'user_id_1'; + asset_2.ownerId = 'user_id_1'; asset_2.deviceAssetId = 'device_asset_id_2'; asset_2.deviceId = 'device_id_1'; asset_2.type = AssetType.VIDEO; asset_2.originalPath = 'fake_path/asset_2.jpeg'; asset_2.resizePath = ''; - asset_2.createdAt = '2022-06-19T23:41:36.910Z'; - asset_2.modifiedAt = '2022-06-19T23:41:36.910Z'; + 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.isFavorite = false; asset_2.mimeType = 'image/jpeg'; asset_2.webpPath = ''; @@ -292,7 +294,7 @@ describe('AssetService', () => { const asset = { id: 'live-photo-asset', originalPath: file.originalPath, - userId: authStub.user1.id, + ownerId: authStub.user1.id, type: AssetType.IMAGE, isVisible: true, } as AssetEntity; @@ -307,7 +309,7 @@ describe('AssetService', () => { const livePhotoAsset = { id: 'live-photo-motion', originalPath: livePhotoFile.originalPath, - userId: authStub.user1.id, + ownerId: authStub.user1.id, type: AssetType.VIDEO, isVisible: false, } as AssetEntity; diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index 959571f382..81ce12c641 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -518,7 +518,7 @@ export class AssetService { where: { deviceAssetId: checkDuplicateAssetDto.deviceAssetId, deviceId: checkDuplicateAssetDto.deviceId, - userId: authUser.id, + ownerId: authUser.id, }, }); diff --git a/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts b/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts index d27d2ff0fc..66d8ec8835 100644 --- a/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts +++ b/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts @@ -16,10 +16,10 @@ export class CreateAssetDto { assetType!: AssetType; @IsNotEmpty() - createdAt!: string; + fileCreatedAt!: string; @IsNotEmpty() - modifiedAt!: string; + fileModifiedAt!: string; @IsNotEmpty() isFavorite!: boolean; diff --git a/server/apps/microservices/src/processors/metadata-extraction.processor.ts b/server/apps/microservices/src/processors/metadata-extraction.processor.ts index c2e8182109..3216f9592a 100644 --- a/server/apps/microservices/src/processors/metadata-extraction.processor.ts +++ b/server/apps/microservices/src/processors/metadata-extraction.processor.ts @@ -159,8 +159,8 @@ export class MetadataExtractionProcessor { return exifDate.toDate(); }; - const createdAt = exifToDate(exifData?.DateTimeOriginal ?? exifData?.CreateDate ?? asset.createdAt); - const modifyDate = exifToDate(exifData?.ModifyDate ?? asset.modifiedAt); + const fileCreatedAt = exifToDate(exifData?.DateTimeOriginal ?? exifData?.CreateDate ?? asset.fileCreatedAt); + const fileModifiedAt = exifToDate(exifData?.ModifyDate ?? asset.fileModifiedAt); const fileStats = fs.statSync(asset.originalPath); const fileSizeInBytes = fileStats.size; @@ -174,8 +174,8 @@ export class MetadataExtractionProcessor { newExif.exifImageWidth = exifData?.ExifImageWidth || exifData?.ImageWidth || null; newExif.exposureTime = exifData?.ExposureTime || null; newExif.orientation = exifData?.Orientation?.toString() || null; - newExif.dateTimeOriginal = createdAt; - newExif.modifyDate = modifyDate; + newExif.dateTimeOriginal = fileCreatedAt; + newExif.modifyDate = fileModifiedAt; newExif.lensModel = exifData?.LensModel || null; newExif.fNumber = exifData?.FNumber || null; newExif.focalLength = exifData?.FocalLength ? parseFloat(exifData.FocalLength) : null; @@ -186,7 +186,7 @@ export class MetadataExtractionProcessor { await this.assetRepository.save({ id: asset.id, - createdAt: createdAt?.toISOString(), + fileCreatedAt: fileCreatedAt?.toISOString(), }); if (newExif.livePhotoCID && !asset.livePhotoVideoId) { @@ -273,7 +273,7 @@ export class MetadataExtractionProcessor { }), ); let durationString = asset.duration; - let createdAt = asset.createdAt; + let fileCreatedAt = asset.fileCreatedAt; if (data.format.duration) { durationString = this.extractDuration(data.format.duration); @@ -282,14 +282,10 @@ export class MetadataExtractionProcessor { const videoTags = data.format.tags; if (videoTags) { if (videoTags['com.apple.quicktime.creationdate']) { - createdAt = String(videoTags['com.apple.quicktime.creationdate']); + fileCreatedAt = String(videoTags['com.apple.quicktime.creationdate']); } else if (videoTags['creation_time']) { - createdAt = String(videoTags['creation_time']); - } else { - createdAt = asset.createdAt; + fileCreatedAt = String(videoTags['creation_time']); } - } else { - createdAt = asset.createdAt; } const exifData = await exiftool.read(asset.originalPath).catch((e) => { @@ -302,7 +298,7 @@ export class MetadataExtractionProcessor { newExif.description = ''; newExif.imageName = path.parse(fileName).name || null; newExif.fileSizeInByte = data.format.size || null; - newExif.dateTimeOriginal = createdAt ? new Date(createdAt) : null; + newExif.dateTimeOriginal = fileCreatedAt ? new Date(fileCreatedAt) : null; newExif.modifyDate = null; newExif.latitude = null; newExif.longitude = null; @@ -382,8 +378,9 @@ export class MetadataExtractionProcessor { } await this.exifRepository.upsert(newExif, { conflictPaths: ['assetId'] }); - await this.assetRepository.update({ id: asset.id }, { duration: durationString, createdAt: createdAt }); + await this.assetRepository.update({ id: asset.id }, { duration: durationString, fileCreatedAt }); } catch (err) { + ``; // do nothing console.log('Error in video metadata extraction', err); } diff --git a/server/apps/microservices/src/processors/thumbnail.processor.ts b/server/apps/microservices/src/processors/thumbnail.processor.ts index 14ad30aae1..fd0387588a 100644 --- a/server/apps/microservices/src/processors/thumbnail.processor.ts +++ b/server/apps/microservices/src/processors/thumbnail.processor.ts @@ -40,7 +40,7 @@ export class ThumbnailGeneratorProcessor { const { asset } = job.data; const sanitizedDeviceId = sanitize(String(asset.deviceId)); - const resizePath = join(basePath, asset.userId, 'thumb', sanitizedDeviceId); + const resizePath = join(basePath, asset.ownerId, 'thumb', sanitizedDeviceId); if (!existsSync(resizePath)) { mkdirSync(resizePath, { recursive: true }); @@ -75,7 +75,7 @@ export class ThumbnailGeneratorProcessor { await this.machineLearningQueue.add(JobName.IMAGE_TAGGING, { asset }); await this.machineLearningQueue.add(JobName.OBJECT_DETECTION, { asset }); - this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset))); + this.wsCommunicationGateway.server.to(asset.ownerId).emit('on_upload_success', JSON.stringify(mapAsset(asset))); } if (asset.type == AssetType.VIDEO) { @@ -106,7 +106,7 @@ export class ThumbnailGeneratorProcessor { await this.machineLearningQueue.add(JobName.IMAGE_TAGGING, { asset }); await this.machineLearningQueue.add(JobName.OBJECT_DETECTION, { asset }); - this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset))); + this.wsCommunicationGateway.server.to(asset.ownerId).emit('on_upload_success', JSON.stringify(mapAsset(asset))); } } diff --git a/server/apps/microservices/src/processors/user-deletion.processor.ts b/server/apps/microservices/src/processors/user-deletion.processor.ts index b8579d81eb..756b402758 100644 --- a/server/apps/microservices/src/processors/user-deletion.processor.ts +++ b/server/apps/microservices/src/processors/user-deletion.processor.ts @@ -61,7 +61,7 @@ export class UserDeletionProcessor { await this.albumRepository.remove(albums); await this.apiKeyRepository.delete({ userId: user.id }); - await this.assetRepository.delete({ userId: user.id }); + await this.assetRepository.delete({ ownerId: user.id }); await this.userRepository.remove(user); } catch (error: any) { this.logger.error(`Failed to remove user`); diff --git a/server/apps/microservices/src/processors/video-transcode.processor.ts b/server/apps/microservices/src/processors/video-transcode.processor.ts index 34ea3e297a..3512990902 100644 --- a/server/apps/microservices/src/processors/video-transcode.processor.ts +++ b/server/apps/microservices/src/processors/video-transcode.processor.ts @@ -22,7 +22,7 @@ export class VideoTranscodeProcessor { async videoConversion(job: Job) { const { asset } = job.data; const basePath = APP_UPLOAD_LOCATION; - const encodedVideoPath = `${basePath}/${asset.userId}/encoded-video`; + const encodedVideoPath = `${basePath}/${asset.ownerId}/encoded-video`; if (!existsSync(encodedVideoPath)) { mkdirSync(encodedVideoPath, { recursive: true }); diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 680003da50..12b939797b 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -3324,10 +3324,10 @@ "type": "string", "nullable": true }, - "createdAt": { + "fileCreatedAt": { "type": "string" }, - "modifiedAt": { + "fileModifiedAt": { "type": "string" }, "updatedAt": { @@ -3376,8 +3376,8 @@ "deviceId", "originalPath", "resizePath", - "createdAt", - "modifiedAt", + "fileCreatedAt", + "fileModifiedAt", "updatedAt", "isFavorite", "mimeType", @@ -3817,10 +3817,10 @@ "deviceId": { "type": "string" }, - "createdAt": { + "fileCreatedAt": { "type": "string" }, - "modifiedAt": { + "fileModifiedAt": { "type": "string" }, "isFavorite": { @@ -3841,8 +3841,8 @@ "assetData", "deviceAssetId", "deviceId", - "createdAt", - "modifiedAt", + "fileCreatedAt", + "fileModifiedAt", "isFavorite", "fileExtension" ] diff --git a/server/libs/domain/src/asset/response-dto/asset-response.dto.ts b/server/libs/domain/src/asset/response-dto/asset-response.dto.ts index 48b550a7fb..01275fd6c7 100644 --- a/server/libs/domain/src/asset/response-dto/asset-response.dto.ts +++ b/server/libs/domain/src/asset/response-dto/asset-response.dto.ts @@ -14,8 +14,8 @@ export class AssetResponseDto { type!: AssetType; originalPath!: string; resizePath!: string | null; - createdAt!: string; - modifiedAt!: string; + fileCreatedAt!: string; + fileModifiedAt!: string; updatedAt!: string; isFavorite!: boolean; mimeType!: string | null; @@ -32,13 +32,13 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto { return { id: entity.id, deviceAssetId: entity.deviceAssetId, - ownerId: entity.userId, + ownerId: entity.ownerId, deviceId: entity.deviceId, type: entity.type, originalPath: entity.originalPath, resizePath: entity.resizePath, - createdAt: entity.createdAt, - modifiedAt: entity.modifiedAt, + fileCreatedAt: entity.fileCreatedAt, + fileModifiedAt: entity.fileModifiedAt, updatedAt: entity.updatedAt, isFavorite: entity.isFavorite, mimeType: entity.mimeType, @@ -56,13 +56,13 @@ export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto { return { id: entity.id, deviceAssetId: entity.deviceAssetId, - ownerId: entity.userId, + ownerId: entity.ownerId, deviceId: entity.deviceId, type: entity.type, originalPath: entity.originalPath, resizePath: entity.resizePath, - createdAt: entity.createdAt, - modifiedAt: entity.modifiedAt, + fileCreatedAt: entity.fileCreatedAt, + fileModifiedAt: entity.fileModifiedAt, updatedAt: entity.updatedAt, isFavorite: entity.isFavorite, mimeType: entity.mimeType, diff --git a/server/libs/domain/test/fixtures.ts b/server/libs/domain/test/fixtures.ts index 85062402d4..ce07f4eb76 100644 --- a/server/libs/domain/test/fixtures.ts +++ b/server/libs/domain/test/fixtures.ts @@ -95,20 +95,23 @@ export const assetEntityStub = { image: Object.freeze({ id: 'asset-id', deviceAssetId: 'device-asset-id', - modifiedAt: today.toISOString(), - createdAt: today.toISOString(), - userId: 'user-id', + fileModifiedAt: today.toISOString(), + fileCreatedAt: today.toISOString(), + owner: userEntityStub.user1, + ownerId: 'user-id', deviceId: 'device-id', originalPath: '/original/path', resizePath: null, type: AssetType.IMAGE, webpPath: null, encodedVideoPath: null, + createdAt: today.toISOString(), updatedAt: today.toISOString(), mimeType: null, isFavorite: true, duration: null, isVisible: true, + livePhotoVideo: null, livePhotoVideoId: null, tags: [], sharedLinks: [], @@ -146,8 +149,8 @@ const assetResponse: AssetResponseDto = { type: AssetType.VIDEO, originalPath: 'fake_path/jpeg', resizePath: '', - createdAt: today.toISOString(), - modifiedAt: today.toISOString(), + fileModifiedAt: today.toISOString(), + fileCreatedAt: today.toISOString(), updatedAt: today.toISOString(), isFavorite: false, mimeType: 'image/jpeg', @@ -374,14 +377,16 @@ export const sharedLinkStub = { assets: [ { id: 'id_1', - userId: 'user_id_1', + owner: userEntityStub.user1, + ownerId: 'user_id_1', deviceAssetId: 'device_asset_id_1', deviceId: 'device_id_1', type: AssetType.VIDEO, originalPath: 'fake_path/jpeg', resizePath: '', + fileModifiedAt: today.toISOString(), + fileCreatedAt: today.toISOString(), createdAt: today.toISOString(), - modifiedAt: today.toISOString(), updatedAt: today.toISOString(), isFavorite: false, mimeType: 'image/jpeg', @@ -396,6 +401,7 @@ export const sharedLinkStub = { encodedVideoPath: '', duration: null, isVisible: true, + livePhotoVideo: null, livePhotoVideoId: null, exifInfo: { livePhotoCID: null, diff --git a/server/libs/infra/src/db/entities/asset.entity.ts b/server/libs/infra/src/db/entities/asset.entity.ts index 5f716da34e..de8ee22959 100644 --- a/server/libs/infra/src/db/entities/asset.entity.ts +++ b/server/libs/infra/src/db/entities/asset.entity.ts @@ -1,9 +1,12 @@ import { Column, + CreateDateColumn, Entity, Index, + JoinColumn, JoinTable, ManyToMany, + ManyToOne, OneToOne, PrimaryGeneratedColumn, Unique, @@ -13,9 +16,10 @@ import { ExifEntity } from './exif.entity'; import { SharedLinkEntity } from './shared-link.entity'; import { SmartInfoEntity } from './smart-info.entity'; import { TagEntity } from './tag.entity'; +import { UserEntity } from './user.entity'; @Entity('assets') -@Unique('UQ_userid_checksum', ['userId', 'checksum']) +@Unique('UQ_userid_checksum', ['owner', 'checksum']) export class AssetEntity { @PrimaryGeneratedColumn('uuid') id!: string; @@ -23,8 +27,11 @@ export class AssetEntity { @Column() deviceAssetId!: string; + @ManyToOne(() => UserEntity, { eager: true, onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) + owner!: UserEntity; + @Column() - userId!: string; + ownerId!: string; @Column() deviceId!: string; @@ -44,15 +51,18 @@ export class AssetEntity { @Column({ type: 'varchar', nullable: true, default: '' }) encodedVideoPath!: string | null; - @Column({ type: 'timestamptz' }) + @CreateDateColumn({ type: 'timestamptz' }) createdAt!: string; - @Column({ type: 'timestamptz' }) - modifiedAt!: string; - @UpdateDateColumn({ type: 'timestamptz' }) updatedAt!: string; + @Column({ type: 'timestamptz' }) + fileCreatedAt!: string; + + @Column({ type: 'timestamptz' }) + fileModifiedAt!: string; + @Column({ type: 'boolean', default: false }) isFavorite!: boolean; @@ -69,7 +79,11 @@ export class AssetEntity { @Column({ type: 'boolean', default: true }) isVisible!: boolean; - @Column({ type: 'uuid', nullable: true }) + @OneToOne(() => AssetEntity, { nullable: true, onUpdate: 'CASCADE', onDelete: 'SET NULL' }) + @JoinColumn() + livePhotoVideo!: AssetEntity | null; + + @Column({ nullable: true }) livePhotoVideoId!: string | null; @OneToOne(() => ExifEntity, (exifEntity) => exifEntity.asset) @@ -78,12 +92,11 @@ export class AssetEntity { @OneToOne(() => SmartInfoEntity, (smartInfoEntity) => smartInfoEntity.asset) smartInfo?: SmartInfoEntity; - // https://github.com/typeorm/typeorm/blob/master/docs/many-to-many-relations.md - @ManyToMany(() => TagEntity, (tag) => tag.assets, { cascade: true }) + @ManyToMany(() => TagEntity, (tag) => tag.assets, { cascade: true, eager: true }) @JoinTable({ name: 'tag_asset' }) tags!: TagEntity[]; - @ManyToMany(() => SharedLinkEntity, (link) => link.assets, { cascade: true }) + @ManyToMany(() => SharedLinkEntity, (link) => link.assets, { cascade: true, eager: true }) @JoinTable({ name: 'shared_link__asset' }) sharedLinks!: SharedLinkEntity[]; } diff --git a/server/libs/infra/src/db/migrations/1676680127415-FixAssetRelations.ts b/server/libs/infra/src/db/migrations/1676680127415-FixAssetRelations.ts new file mode 100644 index 0000000000..439e86a78a --- /dev/null +++ b/server/libs/infra/src/db/migrations/1676680127415-FixAssetRelations.ts @@ -0,0 +1,30 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class FixAssetRelations1676680127415 implements MigrationInterface { + name = 'FixAssetRelations1676680127415' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "modifiedAt" TO "fileModifiedAt"`); + await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "createdAt" TO "fileCreatedAt"`); + + await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "userId" TO "ownerId"`); + await queryRunner.query(`ALTER TABLE assets ALTER COLUMN "ownerId" TYPE uuid USING "ownerId"::uuid;`); + + await queryRunner.query(`ALTER TABLE "assets" ADD CONSTRAINT "UQ_16294b83fa8c0149719a1f631ef" UNIQUE ("livePhotoVideoId")`); + await queryRunner.query(`ALTER TABLE "assets" ADD CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "assets" ADD CONSTRAINT "FK_16294b83fa8c0149719a1f631ef" FOREIGN KEY ("livePhotoVideoId") REFERENCES "assets"("id") ON DELETE SET NULL ON UPDATE CASCADE`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_16294b83fa8c0149719a1f631ef"`); + await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d"`); + await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "UQ_16294b83fa8c0149719a1f631ef"`); + + await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "fileCreatedAt" TO "createdAt"`); + await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "fileModifiedAt" TO "modifiedAt"`); + + await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "ownerId" TO "userId"`); + await queryRunner.query(`ALTER TABLE assets ALTER COLUMN "userId" TYPE varchar`); + } + +} diff --git a/server/libs/infra/src/db/migrations/1676721296440-AssetCreatedAtField.ts b/server/libs/infra/src/db/migrations/1676721296440-AssetCreatedAtField.ts new file mode 100644 index 0000000000..304c7b2190 --- /dev/null +++ b/server/libs/infra/src/db/migrations/1676721296440-AssetCreatedAtField.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AssetCreatedAtField1676721296440 implements MigrationInterface { + name = 'AssetCreatedAtField1676721296440' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assets" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "createdAt"`); + } + +} diff --git a/server/libs/infra/src/db/repository/shared-link.repository.ts b/server/libs/infra/src/db/repository/shared-link.repository.ts index 09e706d0a2..baacc07a50 100644 --- a/server/libs/infra/src/db/repository/shared-link.repository.ts +++ b/server/libs/infra/src/db/repository/shared-link.repository.ts @@ -31,11 +31,11 @@ export class SharedLinkRepository implements ISharedLinkRepository { order: { createdAt: 'DESC', assets: { - createdAt: 'ASC', + fileCreatedAt: 'ASC', }, album: { assets: { - createdAt: 'ASC', + fileCreatedAt: 'ASC', }, }, }, diff --git a/server/libs/storage/src/storage.service.ts b/server/libs/storage/src/storage.service.ts index 08b7bc185b..ed4ddb3acd 100644 --- a/server/libs/storage/src/storage.service.ts +++ b/server/libs/storage/src/storage.service.ts @@ -50,7 +50,7 @@ export class StorageService { const source = asset.originalPath; const ext = path.extname(source).split('.').pop() as string; const sanitized = sanitize(path.basename(filename, `.${ext}`)); - const rootPath = path.join(APP_UPLOAD_LOCATION, asset.userId); + const rootPath = path.join(APP_UPLOAD_LOCATION, asset.ownerId); const storagePath = this.render(this.storageTemplate, asset, sanitized, ext); const fullPath = path.normalize(path.join(rootPath, storagePath)); let destination = `${fullPath}.${ext}`; @@ -132,7 +132,7 @@ export class StorageService { this.render( template, { - createdAt: new Date().toISOString(), + fileCreatedAt: new Date().toISOString(), originalPath: '/upload/test/IMG_123.jpg', type: AssetType.IMAGE, } as AssetEntity, @@ -161,7 +161,7 @@ export class StorageService { const fileType = asset.type == AssetType.IMAGE ? 'IMG' : 'VID'; const fileTypeFull = asset.type == AssetType.IMAGE ? 'IMAGE' : 'VIDEO'; - const dt = luxon.DateTime.fromISO(new Date(asset.createdAt).toISOString()); + const dt = luxon.DateTime.fromISO(new Date(asset.fileCreatedAt).toISOString()); const dateTokens = [ ...supportedYearTokens, diff --git a/server/package-lock.json b/server/package-lock.json index 3cea0f489d..e39493bf2f 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "immich", - "version": "1.46.1", + "version": "1.47.3", "license": "UNLICENSED", "dependencies": { "@nestjs/bull": "^0.6.2", diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index a7cfed1873..af632f290f 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.47.2 + * The version of the OpenAPI document: 1.47.3 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -61,10 +61,10 @@ export interface APIKeyCreateResponseDto { export interface APIKeyResponseDto { /** * - * @type {number} + * @type {string} * @memberof APIKeyResponseDto */ - 'id': number; + 'id': string; /** * * @type {string} @@ -467,13 +467,13 @@ export interface AssetResponseDto { * @type {string} * @memberof AssetResponseDto */ - 'createdAt': string; + 'fileCreatedAt': string; /** * * @type {string} * @memberof AssetResponseDto */ - 'modifiedAt': string; + 'fileModifiedAt': string; /** * * @type {string} @@ -2356,11 +2356,11 @@ export const APIKeyApiAxiosParamCreator = function (configuration?: Configuratio }, /** * - * @param {number} id + * @param {string} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - deleteKey: async (id: number, options: AxiosRequestConfig = {}): Promise => { + deleteKey: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('deleteKey', 'id', id) const localVarPath = `/api-key/{id}` @@ -2389,11 +2389,11 @@ export const APIKeyApiAxiosParamCreator = function (configuration?: Configuratio }, /** * - * @param {number} id + * @param {string} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getKey: async (id: number, options: AxiosRequestConfig = {}): Promise => { + getKey: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getKey', 'id', id) const localVarPath = `/api-key/{id}` @@ -2451,12 +2451,12 @@ export const APIKeyApiAxiosParamCreator = function (configuration?: Configuratio }, /** * - * @param {number} id + * @param {string} id * @param {APIKeyUpdateDto} aPIKeyUpdateDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - updateKey: async (id: number, aPIKeyUpdateDto: APIKeyUpdateDto, options: AxiosRequestConfig = {}): Promise => { + updateKey: async (id: string, aPIKeyUpdateDto: APIKeyUpdateDto, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('updateKey', 'id', id) // verify required parameter 'aPIKeyUpdateDto' is not null or undefined @@ -2510,21 +2510,21 @@ export const APIKeyApiFp = function(configuration?: Configuration) { }, /** * - * @param {number} id + * @param {string} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async deleteKey(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async deleteKey(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.deleteKey(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** * - * @param {number} id + * @param {string} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getKey(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async getKey(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.getKey(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -2539,12 +2539,12 @@ export const APIKeyApiFp = function(configuration?: Configuration) { }, /** * - * @param {number} id + * @param {string} id * @param {APIKeyUpdateDto} aPIKeyUpdateDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async updateKey(id: number, aPIKeyUpdateDto: APIKeyUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async updateKey(id: string, aPIKeyUpdateDto: APIKeyUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.updateKey(id, aPIKeyUpdateDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -2569,20 +2569,20 @@ export const APIKeyApiFactory = function (configuration?: Configuration, basePat }, /** * - * @param {number} id + * @param {string} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - deleteKey(id: number, options?: any): AxiosPromise { + deleteKey(id: string, options?: any): AxiosPromise { return localVarFp.deleteKey(id, options).then((request) => request(axios, basePath)); }, /** * - * @param {number} id + * @param {string} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getKey(id: number, options?: any): AxiosPromise { + getKey(id: string, options?: any): AxiosPromise { return localVarFp.getKey(id, options).then((request) => request(axios, basePath)); }, /** @@ -2595,12 +2595,12 @@ export const APIKeyApiFactory = function (configuration?: Configuration, basePat }, /** * - * @param {number} id + * @param {string} id * @param {APIKeyUpdateDto} aPIKeyUpdateDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - updateKey(id: number, aPIKeyUpdateDto: APIKeyUpdateDto, options?: any): AxiosPromise { + updateKey(id: string, aPIKeyUpdateDto: APIKeyUpdateDto, options?: any): AxiosPromise { return localVarFp.updateKey(id, aPIKeyUpdateDto, options).then((request) => request(axios, basePath)); }, }; @@ -2626,23 +2626,23 @@ export class APIKeyApi extends BaseAPI { /** * - * @param {number} id + * @param {string} id * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof APIKeyApi */ - public deleteKey(id: number, options?: AxiosRequestConfig) { + public deleteKey(id: string, options?: AxiosRequestConfig) { return APIKeyApiFp(this.configuration).deleteKey(id, options).then((request) => request(this.axios, this.basePath)); } /** * - * @param {number} id + * @param {string} id * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof APIKeyApi */ - public getKey(id: number, options?: AxiosRequestConfig) { + public getKey(id: string, options?: AxiosRequestConfig) { return APIKeyApiFp(this.configuration).getKey(id, options).then((request) => request(this.axios, this.basePath)); } @@ -2658,13 +2658,13 @@ export class APIKeyApi extends BaseAPI { /** * - * @param {number} id + * @param {string} id * @param {APIKeyUpdateDto} aPIKeyUpdateDto * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof APIKeyApi */ - public updateKey(id: number, aPIKeyUpdateDto: APIKeyUpdateDto, options?: AxiosRequestConfig) { + public updateKey(id: string, aPIKeyUpdateDto: APIKeyUpdateDto, options?: AxiosRequestConfig) { return APIKeyApiFp(this.configuration).updateKey(id, aPIKeyUpdateDto, options).then((request) => request(this.axios, this.basePath)); } } @@ -4432,8 +4432,8 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {any} assetData * @param {string} deviceAssetId * @param {string} deviceId - * @param {string} createdAt - * @param {string} modifiedAt + * @param {string} fileCreatedAt + * @param {string} fileModifiedAt * @param {boolean} isFavorite * @param {string} fileExtension * @param {any} [livePhotoData] @@ -4442,7 +4442,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {*} [options] Override http request option. * @throws {RequiredError} */ - uploadFile: async (assetType: AssetTypeEnum, assetData: any, deviceAssetId: string, deviceId: string, createdAt: string, modifiedAt: string, isFavorite: boolean, fileExtension: string, livePhotoData?: any, isVisible?: boolean, duration?: string, options: AxiosRequestConfig = {}): Promise => { + uploadFile: async (assetType: AssetTypeEnum, assetData: any, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, livePhotoData?: any, isVisible?: boolean, duration?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'assetType' is not null or undefined assertParamExists('uploadFile', 'assetType', assetType) // verify required parameter 'assetData' is not null or undefined @@ -4451,10 +4451,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration assertParamExists('uploadFile', 'deviceAssetId', deviceAssetId) // verify required parameter 'deviceId' is not null or undefined assertParamExists('uploadFile', 'deviceId', deviceId) - // verify required parameter 'createdAt' is not null or undefined - assertParamExists('uploadFile', 'createdAt', createdAt) - // verify required parameter 'modifiedAt' is not null or undefined - assertParamExists('uploadFile', 'modifiedAt', modifiedAt) + // verify required parameter 'fileCreatedAt' is not null or undefined + assertParamExists('uploadFile', 'fileCreatedAt', fileCreatedAt) + // verify required parameter 'fileModifiedAt' is not null or undefined + assertParamExists('uploadFile', 'fileModifiedAt', fileModifiedAt) // verify required parameter 'isFavorite' is not null or undefined assertParamExists('uploadFile', 'isFavorite', isFavorite) // verify required parameter 'fileExtension' is not null or undefined @@ -4497,12 +4497,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarFormParams.append('deviceId', deviceId as any); } - if (createdAt !== undefined) { - localVarFormParams.append('createdAt', createdAt as any); + if (fileCreatedAt !== undefined) { + localVarFormParams.append('fileCreatedAt', fileCreatedAt as any); } - if (modifiedAt !== undefined) { - localVarFormParams.append('modifiedAt', modifiedAt as any); + if (fileModifiedAt !== undefined) { + localVarFormParams.append('fileModifiedAt', fileModifiedAt as any); } if (isFavorite !== undefined) { @@ -4772,8 +4772,8 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {any} assetData * @param {string} deviceAssetId * @param {string} deviceId - * @param {string} createdAt - * @param {string} modifiedAt + * @param {string} fileCreatedAt + * @param {string} fileModifiedAt * @param {boolean} isFavorite * @param {string} fileExtension * @param {any} [livePhotoData] @@ -4782,8 +4782,8 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async uploadFile(assetType: AssetTypeEnum, assetData: any, deviceAssetId: string, deviceId: string, createdAt: string, modifiedAt: string, isFavorite: boolean, fileExtension: string, livePhotoData?: any, isVisible?: boolean, duration?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(assetType, assetData, deviceAssetId, deviceId, createdAt, modifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration, options); + async uploadFile(assetType: AssetTypeEnum, assetData: any, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, livePhotoData?: any, isVisible?: boolean, duration?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -5002,8 +5002,8 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @param {any} assetData * @param {string} deviceAssetId * @param {string} deviceId - * @param {string} createdAt - * @param {string} modifiedAt + * @param {string} fileCreatedAt + * @param {string} fileModifiedAt * @param {boolean} isFavorite * @param {string} fileExtension * @param {any} [livePhotoData] @@ -5012,8 +5012,8 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @param {*} [options] Override http request option. * @throws {RequiredError} */ - uploadFile(assetType: AssetTypeEnum, assetData: any, deviceAssetId: string, deviceId: string, createdAt: string, modifiedAt: string, isFavorite: boolean, fileExtension: string, livePhotoData?: any, isVisible?: boolean, duration?: string, options?: any): AxiosPromise { - return localVarFp.uploadFile(assetType, assetData, deviceAssetId, deviceId, createdAt, modifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration, options).then((request) => request(axios, basePath)); + uploadFile(assetType: AssetTypeEnum, assetData: any, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, livePhotoData?: any, isVisible?: boolean, duration?: string, options?: any): AxiosPromise { + return localVarFp.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration, options).then((request) => request(axios, basePath)); }, }; }; @@ -5275,8 +5275,8 @@ export class AssetApi extends BaseAPI { * @param {any} assetData * @param {string} deviceAssetId * @param {string} deviceId - * @param {string} createdAt - * @param {string} modifiedAt + * @param {string} fileCreatedAt + * @param {string} fileModifiedAt * @param {boolean} isFavorite * @param {string} fileExtension * @param {any} [livePhotoData] @@ -5286,8 +5286,8 @@ export class AssetApi extends BaseAPI { * @throws {RequiredError} * @memberof AssetApi */ - public uploadFile(assetType: AssetTypeEnum, assetData: any, deviceAssetId: string, deviceId: string, createdAt: string, modifiedAt: string, isFavorite: boolean, fileExtension: string, livePhotoData?: any, isVisible?: boolean, duration?: string, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).uploadFile(assetType, assetData, deviceAssetId, deviceId, createdAt, modifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration, options).then((request) => request(this.axios, this.basePath)); + public uploadFile(assetType: AssetTypeEnum, assetData: any, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, livePhotoData?: any, isVisible?: boolean, duration?: string, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration, options).then((request) => request(this.axios, this.basePath)); } } diff --git a/web/src/api/open-api/base.ts b/web/src/api/open-api/base.ts index aa00dd944e..65aa2e2841 100644 --- a/web/src/api/open-api/base.ts +++ b/web/src/api/open-api/base.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.47.2 + * The version of the OpenAPI document: 1.47.3 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/common.ts b/web/src/api/open-api/common.ts index 8f295ff064..8d73891c26 100644 --- a/web/src/api/open-api/common.ts +++ b/web/src/api/open-api/common.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.47.2 + * The version of the OpenAPI document: 1.47.3 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/configuration.ts b/web/src/api/open-api/configuration.ts index af2967072f..376741d5b8 100644 --- a/web/src/api/open-api/configuration.ts +++ b/web/src/api/open-api/configuration.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.47.2 + * The version of the OpenAPI document: 1.47.3 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/index.ts b/web/src/api/open-api/index.ts index b7490d5298..c05c360ea8 100644 --- a/web/src/api/open-api/index.ts +++ b/web/src/api/open-api/index.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.47.2 + * The version of the OpenAPI document: 1.47.3 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 10228c1327..aa0f7b4256 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -96,8 +96,8 @@ }; const getDateRange = () => { - const startDate = new Date(album.assets[0].createdAt); - const endDate = new Date(album.assets[album.assetCount - 1].createdAt); + const startDate = new Date(album.assets[0].fileCreatedAt); + const endDate = new Date(album.assets[album.assetCount - 1].fileCreatedAt); const startDateString = startDate.toLocaleDateString(locale, albumDateFormat); const endDateString = endDate.toLocaleDateString(locale, albumDateFormat); diff --git a/web/src/lib/components/photos-page/asset-date-group.svelte b/web/src/lib/components/photos-page/asset-date-group.svelte index 93ee84ffc1..ae2013608c 100644 --- a/web/src/lib/components/photos-page/asset-date-group.svelte +++ b/web/src/lib/components/photos-page/asset-date-group.svelte @@ -31,7 +31,7 @@ let hoveredDateGroup = ''; $: assetsGroupByDate = lodash .chain(assets) - .groupBy((a) => new Date(a.createdAt).toLocaleDateString(locale, groupDateFormat)) + .groupBy((a) => new Date(a.fileCreatedAt).toLocaleDateString(locale, groupDateFormat)) .sortBy((group) => assets.indexOf(group[0])) .value(); @@ -114,7 +114,7 @@ bind:clientHeight={actualBucketHeight} > {#each assetsGroupByDate as assetsInDateGroup, groupIndex (assetsInDateGroup[0].id)} - {@const dateGroupTitle = new Date(assetsInDateGroup[0].createdAt).toLocaleDateString( + {@const dateGroupTitle = new Date(assetsInDateGroup[0].fileCreatedAt).toLocaleDateString( locale, groupDateFormat )} diff --git a/web/src/lib/stores/asset-interaction.store.ts b/web/src/lib/stores/asset-interaction.store.ts index 7ed28e1258..40dae128ba 100644 --- a/web/src/lib/stores/asset-interaction.store.ts +++ b/web/src/lib/stores/asset-interaction.store.ts @@ -65,7 +65,7 @@ function createAssetInteractionStore() { const navigateAsset = async (direction: 'next' | 'previous') => { // Flatten and sort the asset by date if there are new assets if (assetSortedByDate.length === 0 || savedAssetLength !== _assetGridState.assets.length) { - assetSortedByDate = sortBy(_assetGridState.assets, (a) => a.createdAt); + assetSortedByDate = sortBy(_assetGridState.assets, (a) => a.fileCreatedAt); savedAssetLength = _assetGridState.assets.length; } diff --git a/web/src/lib/utils/file-uploader.ts b/web/src/lib/utils/file-uploader.ts index dc7c622922..3d3529d5a4 100644 --- a/web/src/lib/utils/file-uploader.ts +++ b/web/src/lib/utils/file-uploader.ts @@ -69,7 +69,7 @@ async function fileUploader( const assetType = mimeType.split('/')[0].toUpperCase(); const fileExtension = getFilenameExtension(asset.name); const formData = new FormData(); - const createdAt = new Date(asset.lastModified).toISOString(); + const fileCreatedAt = new Date(asset.lastModified).toISOString(); const deviceAssetId = 'web' + '-' + asset.name + '-' + asset.lastModified; try { @@ -83,10 +83,10 @@ async function fileUploader( formData.append('assetType', assetType); // Get Asset Created Date - formData.append('createdAt', createdAt); + formData.append('fileCreatedAt', fileCreatedAt); // Get Asset Modified At - formData.append('modifiedAt', new Date(asset.lastModified).toISOString()); + formData.append('fileModifiedAt', new Date(asset.lastModified).toISOString()); // Set Asset is Favorite to false formData.append('isFavorite', 'false');