mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	infra(server)!: fix typeorm asset entity relations (#1782)
* fix: add correct relations to asset typeorm entity * fix: add missing createdAt column to asset entity * ci: run check to make sure generated API is up-to-date * ci: cancel workflows that aren't for the latest commit in a branch * chore: add fvm config for flutter
This commit is contained in:
		
							
								
								
									
										4
									
								
								.github/workflows/build-mobile.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/build-mobile.yml
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/cache-cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/cache-cleanup.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,6 +4,10 @@ on:
 | 
			
		||||
    types:
 | 
			
		||||
      - closed
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  group: ${{ github.workflow }}-${{ github.ref }}
 | 
			
		||||
  cancel-in-progress: true
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  cleanup:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							@@ -20,6 +20,10 @@ on:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '20 13 * * 1'
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  group: ${{ github.workflow }}-${{ github.ref }}
 | 
			
		||||
  cancel-in-progress: true
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  analyze:
 | 
			
		||||
    name: Analyze
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/dispatch_sdk_update.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/dispatch_sdk_update.yml
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/github-repo-stats.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/github-repo-stats.yml
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/prepare-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/prepare-release.yml
									
									
									
									
										vendored
									
									
								
							@@ -17,6 +17,10 @@ on:
 | 
			
		||||
        required: false
 | 
			
		||||
        type: boolean
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  group: ${{ github.workflow }}-${{ github.ref }}
 | 
			
		||||
  cancel-in-progress: true
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  bump_version:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/static_analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/static_analysis.yml
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								mobile/.fvm/fvm_config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								mobile/.fvm/fvm_config.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
  "flutterSdkVersion": "3.7.0",
 | 
			
		||||
  "flavors": {}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								mobile/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								mobile/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -32,6 +32,7 @@
 | 
			
		||||
.pub-cache/
 | 
			
		||||
.pub/
 | 
			
		||||
/build/
 | 
			
		||||
.fvm/flutter_sdk
 | 
			
		||||
 | 
			
		||||
# Web related
 | 
			
		||||
lib/generated_plugin_registrant.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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = <String, dynamic>{};
 | 
			
		||||
 | 
			
		||||
    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<String, dynamic> 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;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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 ^
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
 | 
			
		||||
            selectedAlbumsBackupAssetsIds: const {},
 | 
			
		||||
            currentUploadAsset: CurrentUploadAsset(
 | 
			
		||||
              id: '...',
 | 
			
		||||
              createdAt: DateTime.parse('2020-10-04'),
 | 
			
		||||
              fileCreatedAt: DateTime.parse('2020-10-04'),
 | 
			
		||||
              fileName: '...',
 | 
			
		||||
              fileType: '...',
 | 
			
		||||
            ),
 | 
			
		||||
 
 | 
			
		||||
@@ -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'],
 | 
			
		||||
 
 | 
			
		||||
@@ -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(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
        },
 | 
			
		||||
      );
 | 
			
		||||
 
 | 
			
		||||
@@ -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"],
 | 
			
		||||
 
 | 
			
		||||
@@ -302,11 +302,11 @@ final assetGroupByMonthYearProvider = StateProvider((ref) {
 | 
			
		||||
      ref.watch(assetProvider).allAssets.where((e) => e.isRemote).toList();
 | 
			
		||||
 | 
			
		||||
  assets.sortByCompare<DateTime>(
 | 
			
		||||
    (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()),
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							@@ -3,7 +3,7 @@ Immich API
 | 
			
		||||
 | 
			
		||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
 | 
			
		||||
 | 
			
		||||
- API version: 1.47.2
 | 
			
		||||
- API version: 1.47.3
 | 
			
		||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
 | 
			
		||||
 | 
			
		||||
## Requirements
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								mobile/openapi/doc/APIKeyApi.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								mobile/openapi/doc/APIKeyApi.md
									
									
									
										generated
									
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								mobile/openapi/doc/APIKeyResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/doc/APIKeyResponseDto.md
									
									
									
										generated
									
									
									
								
							@@ -8,7 +8,7 @@ import 'package:openapi/api.dart';
 | 
			
		||||
## Properties
 | 
			
		||||
Name | Type | Description | Notes
 | 
			
		||||
------------ | ------------- | ------------- | -------------
 | 
			
		||||
**id** | **int** |  | 
 | 
			
		||||
**id** | **String** |  | 
 | 
			
		||||
**name** | **String** |  | 
 | 
			
		||||
**createdAt** | **String** |  | 
 | 
			
		||||
**updatedAt** | **String** |  | 
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								mobile/openapi/doc/AssetApi.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								mobile/openapi/doc/AssetApi.md
									
									
									
										generated
									
									
									
								
							@@ -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] 
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								mobile/openapi/doc/AssetResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								mobile/openapi/doc/AssetResponseDto.md
									
									
									
										generated
									
									
									
								
							@@ -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** |  | 
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								mobile/openapi/lib/api/api_key_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										30
									
								
								mobile/openapi/lib/api/api_key_api.dart
									
									
									
										generated
									
									
									
								
							@@ -74,11 +74,11 @@ class APIKeyApi {
 | 
			
		||||
  ///
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [num] id (required):
 | 
			
		||||
  Future<Response> deleteKeyWithHttpInfo(num id,) async {
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  Future<Response> 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<void> deleteKey(num id,) async {
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  Future<void> 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<Response> getKeyWithHttpInfo(num id,) async {
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  Future<Response> 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<APIKeyResponseDto?> getKey(num id,) async {
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  Future<APIKeyResponseDto?> 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<Response> updateKeyWithHttpInfo(num id, APIKeyUpdateDto aPIKeyUpdateDto,) async {
 | 
			
		||||
  Future<Response> 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<APIKeyResponseDto?> updateKey(num id, APIKeyUpdateDto aPIKeyUpdateDto,) async {
 | 
			
		||||
  Future<APIKeyResponseDto?> updateKey(String id, APIKeyUpdateDto aPIKeyUpdateDto,) async {
 | 
			
		||||
    final response = await updateKeyWithHttpInfo(id, aPIKeyUpdateDto,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								mobile/openapi/lib/api/asset_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										22
									
								
								mobile/openapi/lib/api/asset_api.dart
									
									
									
										generated
									
									
									
								
							@@ -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<Response> 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<Response> 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<AssetFileUploadResponseDto?> 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<AssetFileUploadResponseDto?> 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));
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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<int>(json, r'id')!,
 | 
			
		||||
        id: mapValueOfType<String>(json, r'id')!,
 | 
			
		||||
        name: mapValueOfType<String>(json, r'name')!,
 | 
			
		||||
        createdAt: mapValueOfType<String>(json, r'createdAt')!,
 | 
			
		||||
        updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								mobile/openapi/lib/model/asset_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										30
									
								
								mobile/openapi/lib/model/asset_response_dto.dart
									
									
									
										generated
									
									
									
								
							@@ -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<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
@@ -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<String>(json, r'deviceId')!,
 | 
			
		||||
        originalPath: mapValueOfType<String>(json, r'originalPath')!,
 | 
			
		||||
        resizePath: mapValueOfType<String>(json, r'resizePath'),
 | 
			
		||||
        createdAt: mapValueOfType<String>(json, r'createdAt')!,
 | 
			
		||||
        modifiedAt: mapValueOfType<String>(json, r'modifiedAt')!,
 | 
			
		||||
        fileCreatedAt: mapValueOfType<String>(json, r'fileCreatedAt')!,
 | 
			
		||||
        fileModifiedAt: mapValueOfType<String>(json, r'fileModifiedAt')!,
 | 
			
		||||
        updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
 | 
			
		||||
        isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
 | 
			
		||||
        mimeType: mapValueOfType<String>(json, r'mimeType'),
 | 
			
		||||
@@ -278,8 +278,8 @@ class AssetResponseDto {
 | 
			
		||||
    'deviceId',
 | 
			
		||||
    'originalPath',
 | 
			
		||||
    'resizePath',
 | 
			
		||||
    'createdAt',
 | 
			
		||||
    'modifiedAt',
 | 
			
		||||
    'fileCreatedAt',
 | 
			
		||||
    'fileModifiedAt',
 | 
			
		||||
    'updatedAt',
 | 
			
		||||
    'isFavorite',
 | 
			
		||||
    'mimeType',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								mobile/openapi/test/api_key_api_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								mobile/openapi/test/api_key_api_test.dart
									
									
									
										generated
									
									
									
								
							@@ -26,14 +26,14 @@ void main() {
 | 
			
		||||
 | 
			
		||||
    // 
 | 
			
		||||
    //
 | 
			
		||||
    //Future deleteKey(num id) async
 | 
			
		||||
    //Future deleteKey(String id) async
 | 
			
		||||
    test('test deleteKey', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 
 | 
			
		||||
    //
 | 
			
		||||
    //Future<APIKeyResponseDto> getKey(num id) async
 | 
			
		||||
    //Future<APIKeyResponseDto> getKey(String id) async
 | 
			
		||||
    test('test getKey', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
@@ -47,7 +47,7 @@ void main() {
 | 
			
		||||
 | 
			
		||||
    // 
 | 
			
		||||
    //
 | 
			
		||||
    //Future<APIKeyResponseDto> updateKey(num id, APIKeyUpdateDto aPIKeyUpdateDto) async
 | 
			
		||||
    //Future<APIKeyResponseDto> updateKey(String id, APIKeyUpdateDto aPIKeyUpdateDto) async
 | 
			
		||||
    test('test updateKey', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ void main() {
 | 
			
		||||
  // final instance = APIKeyResponseDto();
 | 
			
		||||
 | 
			
		||||
  group('test APIKeyResponseDto', () {
 | 
			
		||||
    // int id
 | 
			
		||||
    // String id
 | 
			
		||||
    test('to test the property `id`', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								mobile/openapi/test/asset_api_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/test/asset_api_test.dart
									
									
									
										generated
									
									
									
								
							@@ -173,7 +173,7 @@ void main() {
 | 
			
		||||
 | 
			
		||||
    // 
 | 
			
		||||
    //
 | 
			
		||||
    //Future<AssetFileUploadResponseDto> 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<AssetFileUploadResponseDto> 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
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								mobile/openapi/test/asset_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								mobile/openapi/test/asset_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							@@ -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
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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(),
 | 
			
		||||
  );
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@ export class AlbumRepository implements IAlbumRepository {
 | 
			
		||||
 | 
			
		||||
    const queryProperties: FindManyOptions<AlbumEntity> = {
 | 
			
		||||
      relations: { sharedUsers: true, assets: true, sharedLinks: true, owner: true },
 | 
			
		||||
      order: { assets: { createdAt: 'ASC' }, createdAt: 'ASC' },
 | 
			
		||||
      order: { assets: { fileCreatedAt: 'ASC' }, createdAt: 'ASC' },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let albumsQuery: Promise<AlbumEntity[]>;
 | 
			
		||||
@@ -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',
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,9 @@ import { AssetSearchDto } from './dto/asset-search.dto';
 | 
			
		||||
 | 
			
		||||
export interface IAssetRepository {
 | 
			
		||||
  get(id: string): Promise<AssetEntity | null>;
 | 
			
		||||
  create(asset: Omit<AssetEntity, 'id'>): Promise<AssetEntity>;
 | 
			
		||||
  create(
 | 
			
		||||
    asset: Omit<AssetEntity, 'id' | 'createdAt' | 'updatedAt' | 'ownerId' | 'livePhotoVideoId'>,
 | 
			
		||||
  ): Promise<AssetEntity>;
 | 
			
		||||
  remove(asset: AssetEntity): Promise<void>;
 | 
			
		||||
 | 
			
		||||
  update(userId: string, asset: AssetEntity, dto: UpdateAssetDto): Promise<AssetEntity>;
 | 
			
		||||
@@ -112,13 +114,13 @@ export class AssetRepository implements IAssetRepository {
 | 
			
		||||
      .getMany();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto> {
 | 
			
		||||
  async getAssetCountByUserId(ownerId: string): Promise<AssetCountByUserIdResponseDto> {
 | 
			
		||||
    // 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<SearchPropertiesDto[]> {
 | 
			
		||||
    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<AssetEntity[]> {
 | 
			
		||||
  async getAllByUserId(ownerId: string, dto: AssetSearchDto): Promise<AssetEntity[]> {
 | 
			
		||||
    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<AssetEntity, 'id'>): Promise<AssetEntity> {
 | 
			
		||||
  async create(
 | 
			
		||||
    asset: Omit<AssetEntity, 'id' | 'createdAt' | 'updatedAt' | 'ownerId' | 'livePhotoVideoId'>,
 | 
			
		||||
  ): Promise<AssetEntity> {
 | 
			
		||||
    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<string[]> - Array of assetIds belong to the device
 | 
			
		||||
   */
 | 
			
		||||
  async getAllByDeviceId(userId: string, deviceId: string): Promise<string[]> {
 | 
			
		||||
  async getAllByDeviceId(ownerId: string, deviceId: string): Promise<string[]> {
 | 
			
		||||
    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<AssetEntity> {
 | 
			
		||||
  getAssetByChecksum(ownerId: string, checksum: Buffer): Promise<AssetEntity> {
 | 
			
		||||
    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<CheckExistingAssetsResponseDto> {
 | 
			
		||||
    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<number> {
 | 
			
		||||
  async countByIdAndUser(assetId: string, ownerId: string): Promise<number> {
 | 
			
		||||
    return await this.assetRepository.count({
 | 
			
		||||
      where: {
 | 
			
		||||
        id: assetId,
 | 
			
		||||
        userId,
 | 
			
		||||
        ownerId,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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<AssetEntity> {
 | 
			
		||||
    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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -518,7 +518,7 @@ export class AssetService {
 | 
			
		||||
      where: {
 | 
			
		||||
        deviceAssetId: checkDuplicateAssetDto.deviceAssetId,
 | 
			
		||||
        deviceId: checkDuplicateAssetDto.deviceId,
 | 
			
		||||
        userId: authUser.id,
 | 
			
		||||
        ownerId: authUser.id,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,10 +16,10 @@ export class CreateAssetDto {
 | 
			
		||||
  assetType!: AssetType;
 | 
			
		||||
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  createdAt!: string;
 | 
			
		||||
  fileCreatedAt!: string;
 | 
			
		||||
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  modifiedAt!: string;
 | 
			
		||||
  fileModifiedAt!: string;
 | 
			
		||||
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  isFavorite!: boolean;
 | 
			
		||||
 
 | 
			
		||||
@@ -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<ImmichTags>(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);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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`);
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ export class VideoTranscodeProcessor {
 | 
			
		||||
  async videoConversion(job: Job<IVideoConversionProcessor>) {
 | 
			
		||||
    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 });
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
        ]
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -95,20 +95,23 @@ export const assetEntityStub = {
 | 
			
		||||
  image: Object.freeze<AssetEntity>({
 | 
			
		||||
    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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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[];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
import { MigrationInterface, QueryRunner } from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class FixAssetRelations1676680127415 implements MigrationInterface {
 | 
			
		||||
    name = 'FixAssetRelations1676680127415'
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        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<void> {
 | 
			
		||||
        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`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,14 @@
 | 
			
		||||
import { MigrationInterface, QueryRunner } from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class AssetCreatedAtField1676721296440 implements MigrationInterface {
 | 
			
		||||
    name = 'AssetCreatedAtField1676721296440'
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "assets" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async down(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "createdAt"`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -31,11 +31,11 @@ export class SharedLinkRepository implements ISharedLinkRepository {
 | 
			
		||||
      order: {
 | 
			
		||||
        createdAt: 'DESC',
 | 
			
		||||
        assets: {
 | 
			
		||||
          createdAt: 'ASC',
 | 
			
		||||
          fileCreatedAt: 'ASC',
 | 
			
		||||
        },
 | 
			
		||||
        album: {
 | 
			
		||||
          assets: {
 | 
			
		||||
            createdAt: 'ASC',
 | 
			
		||||
            fileCreatedAt: 'ASC',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								server/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								server/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -6,7 +6,7 @@
 | 
			
		||||
  "packages": {
 | 
			
		||||
    "": {
 | 
			
		||||
      "name": "immich",
 | 
			
		||||
      "version": "1.46.1",
 | 
			
		||||
      "version": "1.47.3",
 | 
			
		||||
      "license": "UNLICENSED",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@nestjs/bull": "^0.6.2",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										104
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										104
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							@@ -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<RequestArgs> => {
 | 
			
		||||
        deleteKey: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
 | 
			
		||||
            // 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<RequestArgs> => {
 | 
			
		||||
        getKey: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
 | 
			
		||||
            // 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<RequestArgs> => {
 | 
			
		||||
        updateKey: async (id: string, aPIKeyUpdateDto: APIKeyUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
 | 
			
		||||
            // verify required parameter 'id' is not null or undefined
 | 
			
		||||
            assertParamExists('updateKey', 'id', id)
 | 
			
		||||
            // verify required parameter 'aPIKeyUpdateDto' is not null or undefined
 | 
			
		||||
@@ -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<void>> {
 | 
			
		||||
        async deleteKey(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
 | 
			
		||||
            const localVarAxiosArgs = await localVarAxiosParamCreator.deleteKey(id, options);
 | 
			
		||||
            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * 
 | 
			
		||||
         * @param {number} id 
 | 
			
		||||
         * @param {string} id 
 | 
			
		||||
         * @param {*} [options] Override http request option.
 | 
			
		||||
         * @throws {RequiredError}
 | 
			
		||||
         */
 | 
			
		||||
        async getKey(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<APIKeyResponseDto>> {
 | 
			
		||||
        async getKey(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<APIKeyResponseDto>> {
 | 
			
		||||
            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<APIKeyResponseDto>> {
 | 
			
		||||
        async updateKey(id: string, aPIKeyUpdateDto: APIKeyUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<APIKeyResponseDto>> {
 | 
			
		||||
            const localVarAxiosArgs = await localVarAxiosParamCreator.updateKey(id, aPIKeyUpdateDto, options);
 | 
			
		||||
            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
 | 
			
		||||
        },
 | 
			
		||||
@@ -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<void> {
 | 
			
		||||
        deleteKey(id: string, options?: any): AxiosPromise<void> {
 | 
			
		||||
            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<APIKeyResponseDto> {
 | 
			
		||||
        getKey(id: string, options?: any): AxiosPromise<APIKeyResponseDto> {
 | 
			
		||||
            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<APIKeyResponseDto> {
 | 
			
		||||
        updateKey(id: string, aPIKeyUpdateDto: APIKeyUpdateDto, options?: any): AxiosPromise<APIKeyResponseDto> {
 | 
			
		||||
            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<RequestArgs> => {
 | 
			
		||||
        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<RequestArgs> => {
 | 
			
		||||
            // 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<AssetFileUploadResponseDto>> {
 | 
			
		||||
            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<AssetFileUploadResponseDto>> {
 | 
			
		||||
            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<AssetFileUploadResponseDto> {
 | 
			
		||||
            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<AssetFileUploadResponseDto> {
 | 
			
		||||
            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));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								web/src/api/open-api/base.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								web/src/api/open-api/base.ts
									
									
									
										generated
									
									
									
								
							@@ -4,7 +4,7 @@
 | 
			
		||||
 * Immich
 | 
			
		||||
 * Immich API
 | 
			
		||||
 *
 | 
			
		||||
 * The version of the OpenAPI document: 1.47.2
 | 
			
		||||
 * The version of the OpenAPI document: 1.47.3
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								web/src/api/open-api/common.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								web/src/api/open-api/common.ts
									
									
									
										generated
									
									
									
								
							@@ -4,7 +4,7 @@
 | 
			
		||||
 * Immich
 | 
			
		||||
 * Immich API
 | 
			
		||||
 *
 | 
			
		||||
 * The version of the OpenAPI document: 1.47.2
 | 
			
		||||
 * The version of the OpenAPI document: 1.47.3
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								web/src/api/open-api/configuration.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								web/src/api/open-api/configuration.ts
									
									
									
										generated
									
									
									
								
							@@ -4,7 +4,7 @@
 | 
			
		||||
 * Immich
 | 
			
		||||
 * Immich API
 | 
			
		||||
 *
 | 
			
		||||
 * The version of the OpenAPI document: 1.47.2
 | 
			
		||||
 * The version of the OpenAPI document: 1.47.3
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								web/src/api/open-api/index.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								web/src/api/open-api/index.ts
									
									
									
										generated
									
									
									
								
							@@ -4,7 +4,7 @@
 | 
			
		||||
 * Immich
 | 
			
		||||
 * Immich API
 | 
			
		||||
 *
 | 
			
		||||
 * The version of the OpenAPI document: 1.47.2
 | 
			
		||||
 * The version of the OpenAPI document: 1.47.3
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
		)}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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');
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user