Compare commits
1 Commits
fix/19543
...
chore/temp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2344939b7d |
9
.github/workflows/prepare-release.yml
vendored
9
.github/workflows/prepare-release.yml
vendored
@@ -24,15 +24,6 @@ concurrency:
|
|||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
merge_translations:
|
|
||||||
uses: ./.github/workflows/merge-translations.yml
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
secrets:
|
|
||||||
PUSH_O_MATIC_APP_ID: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
|
||||||
PUSH_O_MATIC_APP_KEY: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
|
||||||
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
|
|
||||||
|
|
||||||
bump_version:
|
bump_version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleBackup({bool processBulk = true}) async {
|
Future<void> _handleBackup({bool processBulk = true}) async {
|
||||||
if (!_isBackupEnabled || _isCleanedUp) {
|
if (!_isBackupEnabled) {
|
||||||
_logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine");
|
_logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -205,27 +205,30 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _syncAssets({Duration? hashTimeout}) async {
|
Future<void> _syncAssets({Duration? hashTimeout}) async {
|
||||||
await _ref.read(backgroundSyncProvider).syncLocal();
|
final futures = <Future<void>>[];
|
||||||
if (_isCleanedUp) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _ref.read(backgroundSyncProvider).syncRemote();
|
final localSyncFuture = _ref.read(backgroundSyncProvider).syncLocal().then((_) async {
|
||||||
if (_isCleanedUp) {
|
if (_isCleanedUp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hashFuture = _ref.read(backgroundSyncProvider).hashAssets();
|
var hashFuture = _ref.read(backgroundSyncProvider).hashAssets();
|
||||||
if (hashTimeout != null) {
|
if (hashTimeout != null) {
|
||||||
hashFuture = hashFuture.timeout(
|
hashFuture = hashFuture.timeout(
|
||||||
hashTimeout,
|
hashTimeout,
|
||||||
onTimeout: () {
|
onTimeout: () {
|
||||||
// Consume cancellation errors as we want to continue processing
|
// Consume cancellation errors as we want to continue processing
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await hashFuture;
|
return hashFuture;
|
||||||
|
});
|
||||||
|
|
||||||
|
futures.add(localSyncFuture);
|
||||||
|
futures.add(_ref.read(backgroundSyncProvider).syncRemote());
|
||||||
|
|
||||||
|
await Future.wait(futures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:immich_mobile/infrastructure/repositories/local_album.repository
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
|
|
||||||
final syncLinkedAlbumServiceProvider = Provider(
|
final syncLinkedAlbumServiceProvider = Provider(
|
||||||
(ref) => SyncLinkedAlbumService(
|
(ref) => SyncLinkedAlbumService(
|
||||||
@@ -20,9 +19,7 @@ class SyncLinkedAlbumService {
|
|||||||
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
||||||
final DriftAlbumApiRepository _albumApiRepository;
|
final DriftAlbumApiRepository _albumApiRepository;
|
||||||
|
|
||||||
SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository);
|
const SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository);
|
||||||
|
|
||||||
final _log = Logger("SyncLinkedAlbumService");
|
|
||||||
|
|
||||||
Future<void> syncLinkedAlbums(String userId) async {
|
Future<void> syncLinkedAlbums(String userId) async {
|
||||||
final selectedAlbums = await _localAlbumRepository.getBackupAlbums();
|
final selectedAlbums = await _localAlbumRepository.getBackupAlbums();
|
||||||
@@ -51,12 +48,8 @@ class SyncLinkedAlbumService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> manageLinkedAlbums(List<LocalAlbum> localAlbums, String ownerId) async {
|
Future<void> manageLinkedAlbums(List<LocalAlbum> localAlbums, String ownerId) async {
|
||||||
try {
|
for (final album in localAlbums) {
|
||||||
for (final album in localAlbums) {
|
await _processLocalAlbum(album, ownerId);
|
||||||
await _processLocalAlbum(album, ownerId);
|
|
||||||
}
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
_log.severe("Error managing linked albums", error, stackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -44,13 +42,49 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
|||||||
|
|
||||||
Future<void> _handleMigration() async {
|
Future<void> _handleMigration() async {
|
||||||
try {
|
try {
|
||||||
await _performMigrationLogic().timeout(
|
if (widget.switchingToBeta) {
|
||||||
const Duration(minutes: 3),
|
final assetNotifier = ref.read(assetProvider.notifier);
|
||||||
onTimeout: () async {
|
if (assetNotifier.mounted) {
|
||||||
await IsarStoreRepository(ref.read(isarProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);
|
assetNotifier.dispose();
|
||||||
await DriftStoreRepository(ref.read(driftProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);
|
}
|
||||||
},
|
final albumNotifier = ref.read(albumProvider.notifier);
|
||||||
);
|
if (albumNotifier.mounted) {
|
||||||
|
albumNotifier.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel uploads
|
||||||
|
await Store.put(StoreKey.backgroundBackup, false);
|
||||||
|
ref
|
||||||
|
.read(backupProvider.notifier)
|
||||||
|
.configureBackgroundBackup(enabled: false, onBatteryInfo: () {}, onError: (_) {});
|
||||||
|
ref.read(backupProvider.notifier).setAutoBackup(false);
|
||||||
|
ref.read(backupProvider.notifier).cancelBackup();
|
||||||
|
ref.read(manualUploadProvider.notifier).cancelBackup();
|
||||||
|
// Start listening to new websocket events
|
||||||
|
ref.read(websocketProvider.notifier).stopListenToOldEvents();
|
||||||
|
ref.read(websocketProvider.notifier).startListeningToBetaEvents();
|
||||||
|
|
||||||
|
final permission = await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
||||||
|
|
||||||
|
if (permission.isGranted) {
|
||||||
|
await ref.read(backgroundSyncProvider).syncLocal(full: true);
|
||||||
|
await migrateDeviceAssetToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
||||||
|
await migrateBackupAlbumsToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
||||||
|
await migrateStoreToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
||||||
|
await ref.read(backgroundServiceProvider).disableService();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await ref.read(backgroundSyncProvider).cancel();
|
||||||
|
ref.read(websocketProvider.notifier).stopListeningToBetaEvents();
|
||||||
|
ref.read(websocketProvider.notifier).startListeningToOldEvents();
|
||||||
|
ref.read(readonlyModeProvider.notifier).setReadonlyMode(false);
|
||||||
|
await migrateStoreToIsar(ref.read(isarProvider), ref.read(driftProvider));
|
||||||
|
await ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
||||||
|
await ref.read(backgroundWorkerFgServiceProvider).disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
await IsarStoreRepository(ref.read(isarProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);
|
||||||
|
await DriftStoreRepository(ref.read(driftProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -68,52 +102,6 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _performMigrationLogic() async {
|
|
||||||
if (widget.switchingToBeta) {
|
|
||||||
final assetNotifier = ref.read(assetProvider.notifier);
|
|
||||||
if (assetNotifier.mounted) {
|
|
||||||
assetNotifier.dispose();
|
|
||||||
}
|
|
||||||
final albumNotifier = ref.read(albumProvider.notifier);
|
|
||||||
if (albumNotifier.mounted) {
|
|
||||||
albumNotifier.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel uploads
|
|
||||||
await Store.put(StoreKey.backgroundBackup, false);
|
|
||||||
ref
|
|
||||||
.read(backupProvider.notifier)
|
|
||||||
.configureBackgroundBackup(enabled: false, onBatteryInfo: () {}, onError: (_) {});
|
|
||||||
ref.read(backupProvider.notifier).setAutoBackup(false);
|
|
||||||
ref.read(backupProvider.notifier).cancelBackup();
|
|
||||||
ref.read(manualUploadProvider.notifier).cancelBackup();
|
|
||||||
// Start listening to new websocket events
|
|
||||||
ref.read(websocketProvider.notifier).stopListenToOldEvents();
|
|
||||||
ref.read(websocketProvider.notifier).startListeningToBetaEvents();
|
|
||||||
|
|
||||||
final permission = await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
|
||||||
|
|
||||||
if (permission.isGranted) {
|
|
||||||
await ref.read(backgroundSyncProvider).syncLocal(full: true);
|
|
||||||
await migrateDeviceAssetToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
|
||||||
await migrateBackupAlbumsToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
|
||||||
await migrateStoreToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
|
||||||
await ref.read(backgroundServiceProvider).disableService();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await ref.read(backgroundSyncProvider).cancel();
|
|
||||||
ref.read(websocketProvider.notifier).stopListeningToBetaEvents();
|
|
||||||
ref.read(websocketProvider.notifier).startListeningToOldEvents();
|
|
||||||
ref.read(readonlyModeProvider.notifier).setReadonlyMode(false);
|
|
||||||
await migrateStoreToIsar(ref.read(isarProvider), ref.read(driftProvider));
|
|
||||||
await ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
|
||||||
await ref.read(backgroundWorkerFgServiceProvider).disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
await IsarStoreRepository(ref.read(isarProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);
|
|
||||||
await DriftStoreRepository(ref.read(driftProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
@@ -48,23 +47,11 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
if (accessToken != null && serverUrl != null && endpoint != null) {
|
if (accessToken != null && serverUrl != null && endpoint != null) {
|
||||||
final infoProvider = ref.read(serverInfoProvider.notifier);
|
final infoProvider = ref.read(serverInfoProvider.notifier);
|
||||||
final wsProvider = ref.read(websocketProvider.notifier);
|
final wsProvider = ref.read(websocketProvider.notifier);
|
||||||
final backgroundManager = ref.read(backgroundSyncProvider);
|
|
||||||
|
|
||||||
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
|
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
|
||||||
(_) async {
|
(a) {
|
||||||
try {
|
try {
|
||||||
wsProvider.connect();
|
wsProvider.connect();
|
||||||
infoProvider.getServerInfo();
|
infoProvider.getServerInfo();
|
||||||
|
|
||||||
if (Store.isBetaTimelineEnabled) {
|
|
||||||
await backgroundManager.syncLocal();
|
|
||||||
await backgroundManager.syncRemote();
|
|
||||||
await backgroundManager.hashAssets();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Store.get(StoreKey.syncAlbums, false)) {
|
|
||||||
await backgroundManager.syncLinkedAlbum();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.severe('Failed establishing connection to the server: $e');
|
log.severe('Failed establishing connection to the server: $e');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
|
||||||
@@ -15,7 +17,11 @@ import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.da
|
|||||||
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
|
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
|
||||||
import 'package:immich_mobile/providers/tab.provider.dart';
|
import 'package:immich_mobile/providers/tab.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/migration.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class TabShellPage extends ConsumerStatefulWidget {
|
class TabShellPage extends ConsumerStatefulWidget {
|
||||||
@@ -26,6 +32,28 @@ class TabShellPage extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _TabShellPageState extends ConsumerState<TabShellPage> {
|
class _TabShellPageState extends ConsumerState<TabShellPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
ref.read(websocketProvider.notifier).connect();
|
||||||
|
|
||||||
|
final isEnableBackup = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||||
|
|
||||||
|
await runNewSync(ref, full: true).then((_) async {
|
||||||
|
if (isEnableBackup) {
|
||||||
|
final currentUser = ref.read(currentUserProvider);
|
||||||
|
if (currentUser == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isScreenLandscape = context.orientation == Orientation.landscape;
|
final isScreenLandscape = context.orientation == Orientation.landscape;
|
||||||
|
|||||||
@@ -148,25 +148,30 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Run operations sequentially with state checks and error handling for each
|
// Run operations sequentially with state checks and error handling for each
|
||||||
await _safeRun(backgroundManager.syncLocal(), "syncLocal");
|
_safeRun(backgroundManager.syncLocal(), "syncLocal");
|
||||||
await _safeRun(backgroundManager.syncRemote(), "syncRemote");
|
_safeRun(backgroundManager.hashAssets(), "hashAssets");
|
||||||
await _safeRun(backgroundManager.hashAssets(), "hashAssets");
|
_safeRun(backgroundManager.syncRemote(), "syncRemote").then((_) {
|
||||||
if (isAlbumLinkedSyncEnable) {
|
if (isAlbumLinkedSyncEnable) {
|
||||||
await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum");
|
_safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Handle backup resume only if still active
|
// Handle backup resume only if still active
|
||||||
if (isEnableBackup) {
|
if (isEnableBackup) {
|
||||||
final currentUser = _ref.read(currentUserProvider);
|
final currentUser = _ref.read(currentUserProvider);
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
await _safeRun(
|
_safeRun(_ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id), "handleBackupResume");
|
||||||
_ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id),
|
|
||||||
"handleBackupResume",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe("Error during background sync", e, stackTrace);
|
_log.severe("Error during background sync", e, stackTrace);
|
||||||
|
} finally {
|
||||||
|
// Ensure lock is released even if operations fail
|
||||||
|
try {
|
||||||
|
_log.info("Lock released after background sync operations");
|
||||||
|
} catch (lockError) {
|
||||||
|
_log.warning("Failed to release lock after error: $lockError");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'dart:io';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
@@ -22,8 +23,13 @@ import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/utils/diff.dart';
|
import 'package:immich_mobile/utils/diff.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
// ignore: import_rule_photo_manager
|
// ignore: import_rule_photo_manager
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
@@ -63,10 +69,7 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
|||||||
|
|
||||||
// Handle migration only for this version
|
// Handle migration only for this version
|
||||||
// TODO: remove when old timeline is removed
|
// TODO: remove when old timeline is removed
|
||||||
final needBetaMigration = Store.tryGet(StoreKey.needBetaMigration);
|
if (version == 15) {
|
||||||
if (version == 15 && needBetaMigration == null) {
|
|
||||||
// Check both databases directly instead of relying on cache
|
|
||||||
|
|
||||||
final isBeta = Store.tryGet(StoreKey.betaTimeline);
|
final isBeta = Store.tryGet(StoreKey.betaTimeline);
|
||||||
final isNewInstallation = await _isNewInstallation(db, drift);
|
final isNewInstallation = await _isNewInstallation(db, drift);
|
||||||
|
|
||||||
@@ -74,7 +77,6 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
|||||||
// For existing installations, only migrate if beta timeline is not enabled (null or false)
|
// For existing installations, only migrate if beta timeline is not enabled (null or false)
|
||||||
if (isNewInstallation || isBeta == true) {
|
if (isNewInstallation || isBeta == true) {
|
||||||
await Store.put(StoreKey.needBetaMigration, false);
|
await Store.put(StoreKey.needBetaMigration, false);
|
||||||
await Store.put(StoreKey.betaTimeline, true);
|
|
||||||
} else {
|
} else {
|
||||||
await resetDriftDatabase(drift);
|
await resetDriftDatabase(drift);
|
||||||
await Store.put(StoreKey.needBetaMigration, true);
|
await Store.put(StoreKey.needBetaMigration, true);
|
||||||
@@ -308,6 +310,25 @@ class _DeviceAsset {
|
|||||||
const _DeviceAsset({required this.assetId, this.hash, this.dateTime});
|
const _DeviceAsset({required this.assetId, this.hash, this.dateTime});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<void>> runNewSync(WidgetRef ref, {bool full = false}) {
|
||||||
|
ref.read(backupProvider.notifier).cancelBackup();
|
||||||
|
|
||||||
|
final backgroundManager = ref.read(backgroundSyncProvider);
|
||||||
|
final isAlbumLinkedSyncEnable = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||||
|
|
||||||
|
return Future.wait([
|
||||||
|
backgroundManager.syncLocal(full: full).then((_) {
|
||||||
|
Logger("runNewSync").fine("Hashing assets after syncLocal");
|
||||||
|
return backgroundManager.hashAssets();
|
||||||
|
}),
|
||||||
|
backgroundManager.syncRemote().then((_) {
|
||||||
|
if (isAlbumLinkedSyncEnable) {
|
||||||
|
return backgroundManager.syncLinkedAlbum();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> resetDriftDatabase(Drift drift) async {
|
Future<void> resetDriftDatabase(Drift drift) async {
|
||||||
// https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94
|
// https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94
|
||||||
final database = drift.attachedDatabase;
|
final database = drift.attachedDatabase;
|
||||||
|
|||||||
@@ -10,11 +10,9 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/oauth.provider.dart';
|
import 'package:immich_mobile/providers/oauth.provider.dart';
|
||||||
@@ -163,18 +161,6 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
serverEndpointController.text = 'http://10.1.15.216:2283/api';
|
serverEndpointController.text = 'http://10.1.15.216:2283/api';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleSyncFlow() async {
|
|
||||||
final backgroundManager = ref.read(backgroundSyncProvider);
|
|
||||||
|
|
||||||
await backgroundManager.syncLocal(full: true);
|
|
||||||
await backgroundManager.syncRemote();
|
|
||||||
await backgroundManager.hashAssets();
|
|
||||||
|
|
||||||
if (Store.get(StoreKey.syncAlbums, false)) {
|
|
||||||
await backgroundManager.syncLinkedAlbum();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
login() async {
|
login() async {
|
||||||
TextInput.finishAutofillContext();
|
TextInput.finishAutofillContext();
|
||||||
|
|
||||||
@@ -192,7 +178,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
final isBeta = Store.isBetaTimelineEnabled;
|
final isBeta = Store.isBetaTimelineEnabled;
|
||||||
if (isBeta) {
|
if (isBeta) {
|
||||||
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
||||||
handleSyncFlow();
|
|
||||||
context.replaceRoute(const TabShellRoute());
|
context.replaceRoute(const TabShellRoute());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -290,7 +276,6 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
if (isBeta) {
|
if (isBeta) {
|
||||||
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
||||||
handleSyncFlow();
|
|
||||||
context.replaceRoute(const TabShellRoute());
|
context.replaceRoute(const TabShellRoute());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class SyncStatusAndActions extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.only(top: 16, bottom: 32),
|
padding: const EdgeInsets.only(top: 16, bottom: 32),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
|
_SectionHeaderText(text: "assets".t(context: context)),
|
||||||
const _SyncStatsCounts(),
|
const _SyncStatsCounts(),
|
||||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
@@ -269,10 +270,7 @@ class _SyncStatsCounts extends ConsumerWidget {
|
|||||||
final localHashedCount = snapshot.data![4]! as int;
|
final localHashedCount = snapshot.data![4]! as int;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
_SectionHeaderText(text: "assets".t(context: context)),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||||
child: Flex(
|
child: Flex(
|
||||||
|
|||||||
3
mobile/openapi/README.md
generated
3
mobile/openapi/README.md
generated
@@ -107,7 +107,7 @@ Class | Method | HTTP request | Description
|
|||||||
*AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics |
|
*AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics |
|
||||||
*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random |
|
*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random |
|
||||||
*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback |
|
*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback |
|
||||||
*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace the asset with new file, without changing its id
|
*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | replaceAsset
|
||||||
*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs |
|
*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs |
|
||||||
*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} |
|
*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} |
|
||||||
*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata |
|
*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata |
|
||||||
@@ -128,7 +128,6 @@ Class | Method | HTTP request | Description
|
|||||||
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
|
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
|
||||||
*DeprecatedApi* | [**createPartnerDeprecated**](doc//DeprecatedApi.md#createpartnerdeprecated) | **POST** /partners/{id} |
|
*DeprecatedApi* | [**createPartnerDeprecated**](doc//DeprecatedApi.md#createpartnerdeprecated) | **POST** /partners/{id} |
|
||||||
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random |
|
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random |
|
||||||
*DeprecatedApi* | [**replaceAsset**](doc//DeprecatedApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace the asset with new file, without changing its id
|
|
||||||
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive |
|
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive |
|
||||||
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info |
|
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info |
|
||||||
*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} |
|
*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} |
|
||||||
|
|||||||
16
mobile/openapi/lib/api/assets_api.dart
generated
16
mobile/openapi/lib/api/assets_api.dart
generated
@@ -608,8 +608,8 @@ class AssetsApi {
|
|||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [int] count:
|
/// * [num] count:
|
||||||
Future<Response> getRandomWithHttpInfo({ int? count, }) async {
|
Future<Response> getRandomWithHttpInfo({ num? count, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/assets/random';
|
final apiPath = r'/assets/random';
|
||||||
|
|
||||||
@@ -642,8 +642,8 @@ class AssetsApi {
|
|||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [int] count:
|
/// * [num] count:
|
||||||
Future<List<AssetResponseDto>?> getRandom({ int? count, }) async {
|
Future<List<AssetResponseDto>?> getRandom({ num? count, }) async {
|
||||||
final response = await getRandomWithHttpInfo( count: count, );
|
final response = await getRandomWithHttpInfo( count: count, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
@@ -729,9 +729,9 @@ class AssetsApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace the asset with new file, without changing its id
|
/// replaceAsset
|
||||||
///
|
///
|
||||||
/// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
|
/// Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
|
||||||
///
|
///
|
||||||
/// Note: This method returns the HTTP [Response].
|
/// Note: This method returns the HTTP [Response].
|
||||||
///
|
///
|
||||||
@@ -823,9 +823,9 @@ class AssetsApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace the asset with new file, without changing its id
|
/// replaceAsset
|
||||||
///
|
///
|
||||||
/// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
|
/// Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
|
||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
|
|||||||
142
mobile/openapi/lib/api/deprecated_api.dart
generated
142
mobile/openapi/lib/api/deprecated_api.dart
generated
@@ -75,8 +75,8 @@ class DeprecatedApi {
|
|||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [int] count:
|
/// * [num] count:
|
||||||
Future<Response> getRandomWithHttpInfo({ int? count, }) async {
|
Future<Response> getRandomWithHttpInfo({ num? count, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/assets/random';
|
final apiPath = r'/assets/random';
|
||||||
|
|
||||||
@@ -109,8 +109,8 @@ class DeprecatedApi {
|
|||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [int] count:
|
/// * [num] count:
|
||||||
Future<List<AssetResponseDto>?> getRandom({ int? count, }) async {
|
Future<List<AssetResponseDto>?> getRandom({ num? count, }) async {
|
||||||
final response = await getRandomWithHttpInfo( count: count, );
|
final response = await getRandomWithHttpInfo( count: count, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
@@ -127,138 +127,4 @@ class DeprecatedApi {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace the asset with new file, without changing its id
|
|
||||||
///
|
|
||||||
/// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
|
|
||||||
///
|
|
||||||
/// Note: This method returns the HTTP [Response].
|
|
||||||
///
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
///
|
|
||||||
/// * [MultipartFile] assetData (required):
|
|
||||||
///
|
|
||||||
/// * [String] deviceAssetId (required):
|
|
||||||
///
|
|
||||||
/// * [String] deviceId (required):
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileCreatedAt (required):
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileModifiedAt (required):
|
|
||||||
///
|
|
||||||
/// * [String] key:
|
|
||||||
///
|
|
||||||
/// * [String] slug:
|
|
||||||
///
|
|
||||||
/// * [String] duration:
|
|
||||||
///
|
|
||||||
/// * [String] filename:
|
|
||||||
Future<Response> replaceAssetWithHttpInfo(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async {
|
|
||||||
// ignore: prefer_const_declarations
|
|
||||||
final apiPath = r'/assets/{id}/original'
|
|
||||||
.replaceAll('{id}', id);
|
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
|
||||||
Object? postBody;
|
|
||||||
|
|
||||||
final queryParams = <QueryParam>[];
|
|
||||||
final headerParams = <String, String>{};
|
|
||||||
final formParams = <String, String>{};
|
|
||||||
|
|
||||||
if (key != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'key', key));
|
|
||||||
}
|
|
||||||
if (slug != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'slug', slug));
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentTypes = <String>['multipart/form-data'];
|
|
||||||
|
|
||||||
bool hasFields = false;
|
|
||||||
final mp = MultipartRequest('PUT', Uri.parse(apiPath));
|
|
||||||
if (assetData != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'assetData'] = assetData.field;
|
|
||||||
mp.files.add(assetData);
|
|
||||||
}
|
|
||||||
if (deviceAssetId != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'deviceAssetId'] = parameterToString(deviceAssetId);
|
|
||||||
}
|
|
||||||
if (deviceId != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'deviceId'] = parameterToString(deviceId);
|
|
||||||
}
|
|
||||||
if (duration != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'duration'] = parameterToString(duration);
|
|
||||||
}
|
|
||||||
if (fileCreatedAt != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'fileCreatedAt'] = parameterToString(fileCreatedAt);
|
|
||||||
}
|
|
||||||
if (fileModifiedAt != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt);
|
|
||||||
}
|
|
||||||
if (filename != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'filename'] = parameterToString(filename);
|
|
||||||
}
|
|
||||||
if (hasFields) {
|
|
||||||
postBody = mp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiClient.invokeAPI(
|
|
||||||
apiPath,
|
|
||||||
'PUT',
|
|
||||||
queryParams,
|
|
||||||
postBody,
|
|
||||||
headerParams,
|
|
||||||
formParams,
|
|
||||||
contentTypes.isEmpty ? null : contentTypes.first,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replace the asset with new file, without changing its id
|
|
||||||
///
|
|
||||||
/// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
|
|
||||||
///
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
///
|
|
||||||
/// * [MultipartFile] assetData (required):
|
|
||||||
///
|
|
||||||
/// * [String] deviceAssetId (required):
|
|
||||||
///
|
|
||||||
/// * [String] deviceId (required):
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileCreatedAt (required):
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileModifiedAt (required):
|
|
||||||
///
|
|
||||||
/// * [String] key:
|
|
||||||
///
|
|
||||||
/// * [String] slug:
|
|
||||||
///
|
|
||||||
/// * [String] duration:
|
|
||||||
///
|
|
||||||
/// * [String] filename:
|
|
||||||
Future<AssetMediaResponseDto?> replaceAsset(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async {
|
|
||||||
final response = await replaceAssetWithHttpInfo(id, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, slug: slug, duration: duration, filename: filename, );
|
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
|
||||||
}
|
|
||||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
|
||||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
|
||||||
// FormatException when trying to decode an empty string.
|
|
||||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
|
||||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetMediaResponseDto',) as AssetMediaResponseDto;
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
mobile/openapi/lib/api/people_api.dart
generated
12
mobile/openapi/lib/api/people_api.dart
generated
@@ -167,14 +167,14 @@ class PeopleApi {
|
|||||||
///
|
///
|
||||||
/// * [String] closestPersonId:
|
/// * [String] closestPersonId:
|
||||||
///
|
///
|
||||||
/// * [int] page:
|
/// * [num] page:
|
||||||
/// Page number for pagination
|
/// Page number for pagination
|
||||||
///
|
///
|
||||||
/// * [int] size:
|
/// * [num] size:
|
||||||
/// Number of items per page
|
/// Number of items per page
|
||||||
///
|
///
|
||||||
/// * [bool] withHidden:
|
/// * [bool] withHidden:
|
||||||
Future<Response> getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, int? page, int? size, bool? withHidden, }) async {
|
Future<Response> getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/people';
|
final apiPath = r'/people';
|
||||||
|
|
||||||
@@ -223,14 +223,14 @@ class PeopleApi {
|
|||||||
///
|
///
|
||||||
/// * [String] closestPersonId:
|
/// * [String] closestPersonId:
|
||||||
///
|
///
|
||||||
/// * [int] page:
|
/// * [num] page:
|
||||||
/// Page number for pagination
|
/// Page number for pagination
|
||||||
///
|
///
|
||||||
/// * [int] size:
|
/// * [num] size:
|
||||||
/// Number of items per page
|
/// Number of items per page
|
||||||
///
|
///
|
||||||
/// * [bool] withHidden:
|
/// * [bool] withHidden:
|
||||||
Future<PeopleResponseDto?> getAllPeople({ String? closestAssetId, String? closestPersonId, int? page, int? size, bool? withHidden, }) async {
|
Future<PeopleResponseDto?> getAllPeople({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async {
|
||||||
final response = await getAllPeopleWithHttpInfo( closestAssetId: closestAssetId, closestPersonId: closestPersonId, page: page, size: size, withHidden: withHidden, );
|
final response = await getAllPeopleWithHttpInfo( closestAssetId: closestAssetId, closestPersonId: closestPersonId, page: page, size: size, withHidden: withHidden, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
|||||||
12
mobile/openapi/lib/api/search_api.dart
generated
12
mobile/openapi/lib/api/search_api.dart
generated
@@ -348,9 +348,9 @@ class SearchApi {
|
|||||||
///
|
///
|
||||||
/// * [List<String>] personIds:
|
/// * [List<String>] personIds:
|
||||||
///
|
///
|
||||||
/// * [int] rating:
|
/// * [num] rating:
|
||||||
///
|
///
|
||||||
/// * [int] size:
|
/// * [num] size:
|
||||||
///
|
///
|
||||||
/// * [String] state:
|
/// * [String] state:
|
||||||
///
|
///
|
||||||
@@ -375,7 +375,7 @@ class SearchApi {
|
|||||||
/// * [bool] withDeleted:
|
/// * [bool] withDeleted:
|
||||||
///
|
///
|
||||||
/// * [bool] withExif:
|
/// * [bool] withExif:
|
||||||
Future<Response> searchLargeAssetsWithHttpInfo({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, List<String>? personIds, int? rating, int? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async {
|
Future<Response> searchLargeAssetsWithHttpInfo({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, List<String>? personIds, num? rating, num? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/search/large-assets';
|
final apiPath = r'/search/large-assets';
|
||||||
|
|
||||||
@@ -532,9 +532,9 @@ class SearchApi {
|
|||||||
///
|
///
|
||||||
/// * [List<String>] personIds:
|
/// * [List<String>] personIds:
|
||||||
///
|
///
|
||||||
/// * [int] rating:
|
/// * [num] rating:
|
||||||
///
|
///
|
||||||
/// * [int] size:
|
/// * [num] size:
|
||||||
///
|
///
|
||||||
/// * [String] state:
|
/// * [String] state:
|
||||||
///
|
///
|
||||||
@@ -559,7 +559,7 @@ class SearchApi {
|
|||||||
/// * [bool] withDeleted:
|
/// * [bool] withDeleted:
|
||||||
///
|
///
|
||||||
/// * [bool] withExif:
|
/// * [bool] withExif:
|
||||||
Future<List<AssetResponseDto>?> searchLargeAssets({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, List<String>? personIds, int? rating, int? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async {
|
Future<List<AssetResponseDto>?> searchLargeAssets({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, List<String>? personIds, num? rating, num? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async {
|
||||||
final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceId: deviceId, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, );
|
final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceId: deviceId, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
|||||||
16
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
16
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
@@ -40,7 +40,7 @@ class AssetBulkUpdateDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? dateTimeRelative;
|
num? dateTimeRelative;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
@@ -68,7 +68,7 @@ class AssetBulkUpdateDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
double? latitude;
|
num? latitude;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
@@ -76,7 +76,7 @@ class AssetBulkUpdateDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
double? longitude;
|
num? longitude;
|
||||||
|
|
||||||
/// Minimum value: -1
|
/// Minimum value: -1
|
||||||
/// Maximum value: 5
|
/// Maximum value: 5
|
||||||
@@ -86,7 +86,7 @@ class AssetBulkUpdateDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? rating;
|
num? rating;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
@@ -202,16 +202,16 @@ class AssetBulkUpdateDto {
|
|||||||
|
|
||||||
return AssetBulkUpdateDto(
|
return AssetBulkUpdateDto(
|
||||||
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
|
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
|
||||||
dateTimeRelative: mapValueOfType<int>(json, r'dateTimeRelative'),
|
dateTimeRelative: num.parse('${json[r'dateTimeRelative']}'),
|
||||||
description: mapValueOfType<String>(json, r'description'),
|
description: mapValueOfType<String>(json, r'description'),
|
||||||
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
|
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
|
||||||
ids: json[r'ids'] is Iterable
|
ids: json[r'ids'] is Iterable
|
||||||
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||||
latitude: (mapValueOfType<num>(json, r'latitude')).toDouble(),
|
latitude: num.parse('${json[r'latitude']}'),
|
||||||
longitude: (mapValueOfType<num>(json, r'longitude')).toDouble(),
|
longitude: num.parse('${json[r'longitude']}'),
|
||||||
rating: mapValueOfType<int>(json, r'rating'),
|
rating: num.parse('${json[r'rating']}'),
|
||||||
timeZone: mapValueOfType<String>(json, r'timeZone'),
|
timeZone: mapValueOfType<String>(json, r'timeZone'),
|
||||||
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class DatabaseBackupConfig {
|
|||||||
bool enabled;
|
bool enabled;
|
||||||
|
|
||||||
/// Minimum value: 1
|
/// Minimum value: 1
|
||||||
int keepLastAmount;
|
num keepLastAmount;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupConfig &&
|
bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupConfig &&
|
||||||
@@ -60,7 +60,7 @@ class DatabaseBackupConfig {
|
|||||||
return DatabaseBackupConfig(
|
return DatabaseBackupConfig(
|
||||||
cronExpression: mapValueOfType<String>(json, r'cronExpression')!,
|
cronExpression: mapValueOfType<String>(json, r'cronExpression')!,
|
||||||
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||||
keepLastAmount: mapValueOfType<int>(json, r'keepLastAmount')!,
|
keepLastAmount: num.parse('${json[r'keepLastAmount']}'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
48
mobile/openapi/lib/model/exif_response_dto.dart
generated
48
mobile/openapi/lib/model/exif_response_dto.dart
generated
@@ -45,25 +45,25 @@ class ExifResponseDto {
|
|||||||
|
|
||||||
String? description;
|
String? description;
|
||||||
|
|
||||||
int? exifImageHeight;
|
num? exifImageHeight;
|
||||||
|
|
||||||
int? exifImageWidth;
|
num? exifImageWidth;
|
||||||
|
|
||||||
String? exposureTime;
|
String? exposureTime;
|
||||||
|
|
||||||
double? fNumber;
|
num? fNumber;
|
||||||
|
|
||||||
int? fileSizeInByte;
|
int? fileSizeInByte;
|
||||||
|
|
||||||
double? focalLength;
|
num? focalLength;
|
||||||
|
|
||||||
int? iso;
|
num? iso;
|
||||||
|
|
||||||
double? latitude;
|
num? latitude;
|
||||||
|
|
||||||
String? lensModel;
|
String? lensModel;
|
||||||
|
|
||||||
double? longitude;
|
num? longitude;
|
||||||
|
|
||||||
String? make;
|
String? make;
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ class ExifResponseDto {
|
|||||||
|
|
||||||
String? projectionType;
|
String? projectionType;
|
||||||
|
|
||||||
int? rating;
|
num? rating;
|
||||||
|
|
||||||
String? state;
|
String? state;
|
||||||
|
|
||||||
@@ -263,22 +263,38 @@ class ExifResponseDto {
|
|||||||
country: mapValueOfType<String>(json, r'country'),
|
country: mapValueOfType<String>(json, r'country'),
|
||||||
dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', r''),
|
dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', r''),
|
||||||
description: mapValueOfType<String>(json, r'description'),
|
description: mapValueOfType<String>(json, r'description'),
|
||||||
exifImageHeight: mapValueOfType<int>(json, r'exifImageHeight'),
|
exifImageHeight: json[r'exifImageHeight'] == null
|
||||||
exifImageWidth: mapValueOfType<int>(json, r'exifImageWidth'),
|
? null
|
||||||
|
: num.parse('${json[r'exifImageHeight']}'),
|
||||||
|
exifImageWidth: json[r'exifImageWidth'] == null
|
||||||
|
? null
|
||||||
|
: num.parse('${json[r'exifImageWidth']}'),
|
||||||
exposureTime: mapValueOfType<String>(json, r'exposureTime'),
|
exposureTime: mapValueOfType<String>(json, r'exposureTime'),
|
||||||
fNumber: (mapValueOfType<num>(json, r'fNumber'))?.toDouble(),
|
fNumber: json[r'fNumber'] == null
|
||||||
|
? null
|
||||||
|
: num.parse('${json[r'fNumber']}'),
|
||||||
fileSizeInByte: mapValueOfType<int>(json, r'fileSizeInByte'),
|
fileSizeInByte: mapValueOfType<int>(json, r'fileSizeInByte'),
|
||||||
focalLength: (mapValueOfType<num>(json, r'focalLength'))?.toDouble(),
|
focalLength: json[r'focalLength'] == null
|
||||||
iso: mapValueOfType<int>(json, r'iso'),
|
? null
|
||||||
latitude: (mapValueOfType<num>(json, r'latitude'))?.toDouble(),
|
: num.parse('${json[r'focalLength']}'),
|
||||||
|
iso: json[r'iso'] == null
|
||||||
|
? null
|
||||||
|
: num.parse('${json[r'iso']}'),
|
||||||
|
latitude: json[r'latitude'] == null
|
||||||
|
? null
|
||||||
|
: num.parse('${json[r'latitude']}'),
|
||||||
lensModel: mapValueOfType<String>(json, r'lensModel'),
|
lensModel: mapValueOfType<String>(json, r'lensModel'),
|
||||||
longitude: (mapValueOfType<num>(json, r'longitude'))?.toDouble(),
|
longitude: json[r'longitude'] == null
|
||||||
|
? null
|
||||||
|
: num.parse('${json[r'longitude']}'),
|
||||||
make: mapValueOfType<String>(json, r'make'),
|
make: mapValueOfType<String>(json, r'make'),
|
||||||
model: mapValueOfType<String>(json, r'model'),
|
model: mapValueOfType<String>(json, r'model'),
|
||||||
modifyDate: mapDateTime(json, r'modifyDate', r''),
|
modifyDate: mapDateTime(json, r'modifyDate', r''),
|
||||||
orientation: mapValueOfType<String>(json, r'orientation'),
|
orientation: mapValueOfType<String>(json, r'orientation'),
|
||||||
projectionType: mapValueOfType<String>(json, r'projectionType'),
|
projectionType: mapValueOfType<String>(json, r'projectionType'),
|
||||||
rating: mapValueOfType<int>(json, r'rating'),
|
rating: json[r'rating'] == null
|
||||||
|
? null
|
||||||
|
: num.parse('${json[r'rating']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
timeZone: mapValueOfType<String>(json, r'timeZone'),
|
timeZone: mapValueOfType<String>(json, r'timeZone'),
|
||||||
);
|
);
|
||||||
|
|||||||
12
mobile/openapi/lib/model/metadata_search_dto.dart
generated
12
mobile/openapi/lib/model/metadata_search_dto.dart
generated
@@ -207,7 +207,7 @@ class MetadataSearchDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? page;
|
num? page;
|
||||||
|
|
||||||
List<String> personIds;
|
List<String> personIds;
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ class MetadataSearchDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? rating;
|
num? rating;
|
||||||
|
|
||||||
/// Minimum value: 1
|
/// Minimum value: 1
|
||||||
/// Maximum value: 1000
|
/// Maximum value: 1000
|
||||||
@@ -237,7 +237,7 @@ class MetadataSearchDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? size;
|
num? size;
|
||||||
|
|
||||||
String? state;
|
String? state;
|
||||||
|
|
||||||
@@ -685,13 +685,13 @@ class MetadataSearchDto {
|
|||||||
order: AssetOrder.fromJson(json[r'order']) ?? AssetOrder.desc,
|
order: AssetOrder.fromJson(json[r'order']) ?? AssetOrder.desc,
|
||||||
originalFileName: mapValueOfType<String>(json, r'originalFileName'),
|
originalFileName: mapValueOfType<String>(json, r'originalFileName'),
|
||||||
originalPath: mapValueOfType<String>(json, r'originalPath'),
|
originalPath: mapValueOfType<String>(json, r'originalPath'),
|
||||||
page: mapValueOfType<int>(json, r'page'),
|
page: num.parse('${json[r'page']}'),
|
||||||
personIds: json[r'personIds'] is Iterable
|
personIds: json[r'personIds'] is Iterable
|
||||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
previewPath: mapValueOfType<String>(json, r'previewPath'),
|
previewPath: mapValueOfType<String>(json, r'previewPath'),
|
||||||
rating: mapValueOfType<int>(json, r'rating'),
|
rating: num.parse('${json[r'rating']}'),
|
||||||
size: mapValueOfType<int>(json, r'size'),
|
size: num.parse('${json[r'size']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
|
|||||||
4
mobile/openapi/lib/model/on_this_day_dto.dart
generated
4
mobile/openapi/lib/model/on_this_day_dto.dart
generated
@@ -17,7 +17,7 @@ class OnThisDayDto {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/// Minimum value: 1
|
/// Minimum value: 1
|
||||||
int year;
|
num year;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is OnThisDayDto &&
|
bool operator ==(Object other) => identical(this, other) || other is OnThisDayDto &&
|
||||||
@@ -46,7 +46,7 @@ class OnThisDayDto {
|
|||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return OnThisDayDto(
|
return OnThisDayDto(
|
||||||
year: mapValueOfType<int>(json, r'year')!,
|
year: num.parse('${json[r'year']}'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ class PlacesResponseDto {
|
|||||||
///
|
///
|
||||||
String? admin2name;
|
String? admin2name;
|
||||||
|
|
||||||
double latitude;
|
num latitude;
|
||||||
|
|
||||||
double longitude;
|
num longitude;
|
||||||
|
|
||||||
String name;
|
String name;
|
||||||
|
|
||||||
@@ -91,8 +91,8 @@ class PlacesResponseDto {
|
|||||||
return PlacesResponseDto(
|
return PlacesResponseDto(
|
||||||
admin1name: mapValueOfType<String>(json, r'admin1name'),
|
admin1name: mapValueOfType<String>(json, r'admin1name'),
|
||||||
admin2name: mapValueOfType<String>(json, r'admin2name'),
|
admin2name: mapValueOfType<String>(json, r'admin2name'),
|
||||||
latitude: (mapValueOfType<num>(json, r'latitude')!).toDouble(),
|
latitude: num.parse('${json[r'latitude']}'),
|
||||||
longitude: (mapValueOfType<num>(json, r'longitude')!).toDouble(),
|
longitude: num.parse('${json[r'longitude']}'),
|
||||||
name: mapValueOfType<String>(json, r'name')!,
|
name: mapValueOfType<String>(json, r'name')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
8
mobile/openapi/lib/model/random_search_dto.dart
generated
8
mobile/openapi/lib/model/random_search_dto.dart
generated
@@ -141,7 +141,7 @@ class RandomSearchDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? rating;
|
num? rating;
|
||||||
|
|
||||||
/// Minimum value: 1
|
/// Minimum value: 1
|
||||||
/// Maximum value: 1000
|
/// Maximum value: 1000
|
||||||
@@ -151,7 +151,7 @@ class RandomSearchDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? size;
|
num? size;
|
||||||
|
|
||||||
String? state;
|
String? state;
|
||||||
|
|
||||||
@@ -513,8 +513,8 @@ class RandomSearchDto {
|
|||||||
personIds: json[r'personIds'] is Iterable
|
personIds: json[r'personIds'] is Iterable
|
||||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
rating: mapValueOfType<int>(json, r'rating'),
|
rating: num.parse('${json[r'rating']}'),
|
||||||
size: mapValueOfType<int>(json, r'size'),
|
size: num.parse('${json[r'size']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
|
|||||||
4
mobile/openapi/lib/model/session_create_dto.dart
generated
4
mobile/openapi/lib/model/session_create_dto.dart
generated
@@ -43,7 +43,7 @@ class SessionCreateDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? duration;
|
num? duration;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is SessionCreateDto &&
|
bool operator ==(Object other) => identical(this, other) || other is SessionCreateDto &&
|
||||||
@@ -92,7 +92,7 @@ class SessionCreateDto {
|
|||||||
return SessionCreateDto(
|
return SessionCreateDto(
|
||||||
deviceOS: mapValueOfType<String>(json, r'deviceOS'),
|
deviceOS: mapValueOfType<String>(json, r'deviceOS'),
|
||||||
deviceType: mapValueOfType<String>(json, r'deviceType'),
|
deviceType: mapValueOfType<String>(json, r'deviceType'),
|
||||||
duration: mapValueOfType<int>(json, r'duration'),
|
duration: num.parse('${json[r'duration']}'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
12
mobile/openapi/lib/model/smart_search_dto.dart
generated
12
mobile/openapi/lib/model/smart_search_dto.dart
generated
@@ -148,7 +148,7 @@ class SmartSearchDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? page;
|
num? page;
|
||||||
|
|
||||||
List<String> personIds;
|
List<String> personIds;
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ class SmartSearchDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? rating;
|
num? rating;
|
||||||
|
|
||||||
/// Minimum value: 1
|
/// Minimum value: 1
|
||||||
/// Maximum value: 1000
|
/// Maximum value: 1000
|
||||||
@@ -186,7 +186,7 @@ class SmartSearchDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? size;
|
num? size;
|
||||||
|
|
||||||
String? state;
|
String? state;
|
||||||
|
|
||||||
@@ -544,14 +544,14 @@ class SmartSearchDto {
|
|||||||
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
||||||
make: mapValueOfType<String>(json, r'make'),
|
make: mapValueOfType<String>(json, r'make'),
|
||||||
model: mapValueOfType<String>(json, r'model'),
|
model: mapValueOfType<String>(json, r'model'),
|
||||||
page: mapValueOfType<int>(json, r'page'),
|
page: num.parse('${json[r'page']}'),
|
||||||
personIds: json[r'personIds'] is Iterable
|
personIds: json[r'personIds'] is Iterable
|
||||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
query: mapValueOfType<String>(json, r'query'),
|
query: mapValueOfType<String>(json, r'query'),
|
||||||
queryAssetId: mapValueOfType<String>(json, r'queryAssetId'),
|
queryAssetId: mapValueOfType<String>(json, r'queryAssetId'),
|
||||||
rating: mapValueOfType<int>(json, r'rating'),
|
rating: num.parse('${json[r'rating']}'),
|
||||||
size: mapValueOfType<int>(json, r'size'),
|
size: num.parse('${json[r'size']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class StatisticsSearchDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? rating;
|
num? rating;
|
||||||
|
|
||||||
String? state;
|
String? state;
|
||||||
|
|
||||||
@@ -448,7 +448,7 @@ class StatisticsSearchDto {
|
|||||||
personIds: json[r'personIds'] is Iterable
|
personIds: json[r'personIds'] is Iterable
|
||||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
rating: mapValueOfType<int>(json, r'rating'),
|
rating: num.parse('${json[r'rating']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class SystemConfigSmtpTransportDto {
|
|||||||
|
|
||||||
/// Minimum value: 0
|
/// Minimum value: 0
|
||||||
/// Maximum value: 65535
|
/// Maximum value: 65535
|
||||||
int port;
|
num port;
|
||||||
|
|
||||||
String username;
|
String username;
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class SystemConfigSmtpTransportDto {
|
|||||||
host: mapValueOfType<String>(json, r'host')!,
|
host: mapValueOfType<String>(json, r'host')!,
|
||||||
ignoreCert: mapValueOfType<bool>(json, r'ignoreCert')!,
|
ignoreCert: mapValueOfType<bool>(json, r'ignoreCert')!,
|
||||||
password: mapValueOfType<String>(json, r'password')!,
|
password: mapValueOfType<String>(json, r'password')!,
|
||||||
port: mapValueOfType<int>(json, r'port')!,
|
port: num.parse('${json[r'port']}'),
|
||||||
username: mapValueOfType<String>(json, r'username')!,
|
username: mapValueOfType<String>(json, r'username')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
12
mobile/openapi/lib/model/update_asset_dto.dart
generated
12
mobile/openapi/lib/model/update_asset_dto.dart
generated
@@ -53,7 +53,7 @@ class UpdateAssetDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
double? latitude;
|
num? latitude;
|
||||||
|
|
||||||
String? livePhotoVideoId;
|
String? livePhotoVideoId;
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ class UpdateAssetDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
double? longitude;
|
num? longitude;
|
||||||
|
|
||||||
/// Minimum value: -1
|
/// Minimum value: -1
|
||||||
/// Maximum value: 5
|
/// Maximum value: 5
|
||||||
@@ -73,7 +73,7 @@ class UpdateAssetDto {
|
|||||||
/// source code must fall back to having a nullable type.
|
/// source code must fall back to having a nullable type.
|
||||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
///
|
///
|
||||||
int? rating;
|
num? rating;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
@@ -166,10 +166,10 @@ class UpdateAssetDto {
|
|||||||
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
|
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
|
||||||
description: mapValueOfType<String>(json, r'description'),
|
description: mapValueOfType<String>(json, r'description'),
|
||||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||||
latitude: (mapValueOfType<num>(json, r'latitude')).toDouble(),
|
latitude: num.parse('${json[r'latitude']}'),
|
||||||
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||||
longitude: (mapValueOfType<num>(json, r'longitude')).toDouble(),
|
longitude: num.parse('${json[r'longitude']}'),
|
||||||
rating: mapValueOfType<int>(json, r'rating'),
|
rating: num.parse('${json[r'rating']}'),
|
||||||
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2035,7 +2035,7 @@
|
|||||||
"in": "query",
|
"in": "query",
|
||||||
"schema": {
|
"schema": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -2504,8 +2504,7 @@
|
|||||||
"description": "This endpoint requires the `asset.download` permission."
|
"description": "This endpoint requires the `asset.download` permission."
|
||||||
},
|
},
|
||||||
"put": {
|
"put": {
|
||||||
"deprecated": true,
|
"description": "Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.",
|
||||||
"description": "This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.",
|
|
||||||
"operationId": "replaceAsset",
|
"operationId": "replaceAsset",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -2567,14 +2566,12 @@
|
|||||||
"api_key": []
|
"api_key": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"summary": "Replace the asset with new file, without changing its id",
|
"summary": "replaceAsset",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Assets",
|
"Assets"
|
||||||
"Deprecated"
|
|
||||||
],
|
],
|
||||||
"x-immich-lifecycle": {
|
"x-immich-lifecycle": {
|
||||||
"addedAt": "v1.106.0",
|
"addedAt": "v1.106.0"
|
||||||
"deprecatedAt": "v1.142.0"
|
|
||||||
},
|
},
|
||||||
"x-immich-permission": "asset.replace"
|
"x-immich-permission": "asset.replace"
|
||||||
}
|
}
|
||||||
@@ -5242,7 +5239,7 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"default": 1,
|
"default": 1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5254,7 +5251,7 @@
|
|||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"default": 500,
|
"default": 500,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5955,7 +5952,7 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5965,7 +5962,7 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -10408,7 +10405,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"dateTimeRelative": {
|
"dateTimeRelative": {
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -10428,17 +10425,15 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"latitude": {
|
"latitude": {
|
||||||
"format": "double",
|
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"longitude": {
|
"longitude": {
|
||||||
"format": "double",
|
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"timeZone": {
|
"timeZone": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -11573,7 +11568,7 @@
|
|||||||
},
|
},
|
||||||
"keepLastAmount": {
|
"keepLastAmount": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -11766,12 +11761,12 @@
|
|||||||
"exifImageHeight": {
|
"exifImageHeight": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"exifImageWidth": {
|
"exifImageWidth": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"exposureTime": {
|
"exposureTime": {
|
||||||
"default": null,
|
"default": null,
|
||||||
@@ -11780,7 +11775,6 @@
|
|||||||
},
|
},
|
||||||
"fNumber": {
|
"fNumber": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"format": "double",
|
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
@@ -11792,18 +11786,16 @@
|
|||||||
},
|
},
|
||||||
"focalLength": {
|
"focalLength": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"format": "double",
|
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"iso": {
|
"iso": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"latitude": {
|
"latitude": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"format": "double",
|
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
@@ -11814,7 +11806,6 @@
|
|||||||
},
|
},
|
||||||
"longitude": {
|
"longitude": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"format": "double",
|
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
@@ -11847,7 +11838,7 @@
|
|||||||
"rating": {
|
"rating": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"default": null,
|
"default": null,
|
||||||
@@ -12600,7 +12591,7 @@
|
|||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"personIds": {
|
"personIds": {
|
||||||
"items": {
|
"items": {
|
||||||
@@ -12615,12 +12606,12 @@
|
|||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
@@ -12897,7 +12888,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"year": {
|
"year": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -13446,11 +13437,9 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"latitude": {
|
"latitude": {
|
||||||
"format": "double",
|
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"longitude": {
|
"longitude": {
|
||||||
"format": "double",
|
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
@@ -13574,12 +13563,12 @@
|
|||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
@@ -14262,7 +14251,7 @@
|
|||||||
"duration": {
|
"duration": {
|
||||||
"description": "session duration, in seconds",
|
"description": "session duration, in seconds",
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -14658,7 +14647,7 @@
|
|||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"personIds": {
|
"personIds": {
|
||||||
"items": {
|
"items": {
|
||||||
@@ -14677,12 +14666,12 @@
|
|||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
@@ -14871,7 +14860,7 @@
|
|||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
@@ -16691,7 +16680,7 @@
|
|||||||
"port": {
|
"port": {
|
||||||
"maximum": 65535,
|
"maximum": 65535,
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -17307,7 +17296,6 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"latitude": {
|
"latitude": {
|
||||||
"format": "double",
|
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"livePhotoVideoId": {
|
"livePhotoVideoId": {
|
||||||
@@ -17316,13 +17304,12 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"longitude": {
|
"longitude": {
|
||||||
"format": "double",
|
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "integer"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"visibility": {
|
"visibility": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
|
|||||||
@@ -2368,7 +2368,7 @@ export function downloadAsset({ id, key, slug }: {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Replace the asset with new file, without changing its id
|
* replaceAsset
|
||||||
*/
|
*/
|
||||||
export function replaceAsset({ id, key, slug, assetMediaReplaceDto }: {
|
export function replaceAsset({ id, key, slug, assetMediaReplaceDto }: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -96,9 +96,8 @@ export class AssetMediaController {
|
|||||||
@Put(':id/original')
|
@Put(':id/original')
|
||||||
@UseInterceptors(FileUploadInterceptor)
|
@UseInterceptors(FileUploadInterceptor)
|
||||||
@ApiConsumes('multipart/form-data')
|
@ApiConsumes('multipart/form-data')
|
||||||
@EndpointLifecycle({
|
@EndpointLifecycle({ addedAt: 'v1.106.0' })
|
||||||
addedAt: 'v1.106.0',
|
@ApiOperation({
|
||||||
deprecatedAt: 'v1.142.0',
|
|
||||||
summary: 'replaceAsset',
|
summary: 'replaceAsset',
|
||||||
description: 'Replace the asset with new file, without changing its id',
|
description: 'Replace the asset with new file, without changing its id',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { SetMetadata, applyDecorators } from '@nestjs/common';
|
import { SetMetadata, applyDecorators } from '@nestjs/common';
|
||||||
import { ApiExtension, ApiOperation, ApiOperationOptions, ApiProperty, ApiTags } from '@nestjs/swagger';
|
import { ApiExtension, ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagger';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ADDED_IN_PREFIX, DEPRECATED_IN_PREFIX, LIFECYCLE_EXTENSION } from 'src/constants';
|
import { ADDED_IN_PREFIX, DEPRECATED_IN_PREFIX, LIFECYCLE_EXTENSION } from 'src/constants';
|
||||||
import { ImmichWorker, JobName, MetadataKey, QueueName } from 'src/enum';
|
import { ImmichWorker, JobName, MetadataKey, QueueName } from 'src/enum';
|
||||||
@@ -159,21 +159,12 @@ type LifecycleMetadata = {
|
|||||||
deprecatedAt?: LifecycleRelease;
|
deprecatedAt?: LifecycleRelease;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EndpointLifecycle = ({
|
export const EndpointLifecycle = ({ addedAt, deprecatedAt }: LifecycleMetadata) => {
|
||||||
addedAt,
|
|
||||||
deprecatedAt,
|
|
||||||
description,
|
|
||||||
...options
|
|
||||||
}: LifecycleMetadata & ApiOperationOptions) => {
|
|
||||||
const decorators: MethodDecorator[] = [ApiExtension(LIFECYCLE_EXTENSION, { addedAt, deprecatedAt })];
|
const decorators: MethodDecorator[] = [ApiExtension(LIFECYCLE_EXTENSION, { addedAt, deprecatedAt })];
|
||||||
if (deprecatedAt) {
|
if (deprecatedAt) {
|
||||||
decorators.push(
|
decorators.push(
|
||||||
ApiTags('Deprecated'),
|
ApiTags('Deprecated'),
|
||||||
ApiOperation({
|
ApiOperation({ deprecated: true, description: DEPRECATED_IN_PREFIX + deprecatedAt }),
|
||||||
deprecated: true,
|
|
||||||
description: DEPRECATED_IN_PREFIX + deprecatedAt + (description ? `. ${description}` : ''),
|
|
||||||
...options,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
@@ -46,20 +46,17 @@ export class UpdateAssetBase {
|
|||||||
@ValidateGPS()
|
@ValidateGPS()
|
||||||
@IsLatitude()
|
@IsLatitude()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@ApiPropertyOptional({ type: 'number', format: 'double' })
|
|
||||||
latitude?: number;
|
latitude?: number;
|
||||||
|
|
||||||
@ValidateGPS()
|
@ValidateGPS()
|
||||||
@IsLongitude()
|
@IsLongitude()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@ApiPropertyOptional({ type: 'number', format: 'double' })
|
|
||||||
longitude?: number;
|
longitude?: number;
|
||||||
|
|
||||||
@Optional()
|
@Optional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Max(5)
|
@Max(5)
|
||||||
@Min(-1)
|
@Min(-1)
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
rating?: number;
|
rating?: number;
|
||||||
|
|
||||||
@Optional()
|
@Optional()
|
||||||
@@ -77,7 +74,6 @@ export class AssetBulkUpdateDto extends UpdateAssetBase {
|
|||||||
@IsNotSiblingOf(['dateTimeOriginal'])
|
@IsNotSiblingOf(['dateTimeOriginal'])
|
||||||
@Optional()
|
@Optional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
dateTimeRelative?: number;
|
dateTimeRelative?: number;
|
||||||
|
|
||||||
@IsNotSiblingOf(['dateTimeOriginal'])
|
@IsNotSiblingOf(['dateTimeOriginal'])
|
||||||
@@ -96,7 +92,6 @@ export class RandomAssetsDto {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
count?: number;
|
count?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,30 @@
|
|||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Exif } from 'src/database';
|
import { Exif } from 'src/database';
|
||||||
|
|
||||||
export class ExifResponseDto {
|
export class ExifResponseDto {
|
||||||
make?: string | null = null;
|
make?: string | null = null;
|
||||||
model?: string | null = null;
|
model?: string | null = null;
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
exifImageWidth?: number | null = null;
|
exifImageWidth?: number | null = null;
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
exifImageHeight?: number | null = null;
|
exifImageHeight?: number | null = null;
|
||||||
@ApiPropertyOptional({ type: 'integer', format: 'int64' })
|
|
||||||
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
fileSizeInByte?: number | null = null;
|
fileSizeInByte?: number | null = null;
|
||||||
orientation?: string | null = null;
|
orientation?: string | null = null;
|
||||||
dateTimeOriginal?: Date | null = null;
|
dateTimeOriginal?: Date | null = null;
|
||||||
modifyDate?: Date | null = null;
|
modifyDate?: Date | null = null;
|
||||||
timeZone?: string | null = null;
|
timeZone?: string | null = null;
|
||||||
lensModel?: string | null = null;
|
lensModel?: string | null = null;
|
||||||
@ApiPropertyOptional({ type: 'number', format: 'double' })
|
|
||||||
fNumber?: number | null = null;
|
fNumber?: number | null = null;
|
||||||
@ApiPropertyOptional({ type: 'number', format: 'double' })
|
|
||||||
focalLength?: number | null = null;
|
focalLength?: number | null = null;
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
iso?: number | null = null;
|
iso?: number | null = null;
|
||||||
exposureTime?: string | null = null;
|
exposureTime?: string | null = null;
|
||||||
@ApiPropertyOptional({ type: 'number', format: 'double' })
|
|
||||||
latitude?: number | null = null;
|
latitude?: number | null = null;
|
||||||
@ApiPropertyOptional({ type: 'number', format: 'double' })
|
|
||||||
longitude?: number | null = null;
|
longitude?: number | null = null;
|
||||||
city?: string | null = null;
|
city?: string | null = null;
|
||||||
state?: string | null = null;
|
state?: string | null = null;
|
||||||
country?: string | null = null;
|
country?: string | null = null;
|
||||||
description?: string | null = null;
|
description?: string | null = null;
|
||||||
projectionType?: string | null = null;
|
projectionType?: string | null = null;
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
rating?: number | null = null;
|
rating?: number | null = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { IsLatitude, IsLongitude } from 'class-validator';
|
|||||||
import { ValidateBoolean, ValidateDate } from 'src/validation';
|
import { ValidateBoolean, ValidateDate } from 'src/validation';
|
||||||
|
|
||||||
export class MapReverseGeocodeDto {
|
export class MapReverseGeocodeDto {
|
||||||
@ApiProperty({ type: 'number', format: 'double' })
|
@ApiProperty({ format: 'double' })
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@IsLatitude({ message: ({ property }) => `${property} must be a number between -90 and 90` })
|
@IsLatitude({ message: ({ property }) => `${property} must be a number between -90 and 90` })
|
||||||
lat!: number;
|
lat!: number;
|
||||||
|
|
||||||
@ApiProperty({ type: 'number', format: 'double' })
|
@ApiProperty({ format: 'double' })
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@IsLongitude({ message: ({ property }) => `${property} must be a number between -180 and 180` })
|
@IsLongitude({ message: ({ property }) => `${property} must be a number between -180 and 180` })
|
||||||
lon!: number;
|
lon!: number;
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export class MemorySearchDto {
|
|||||||
class OnThisDayDto {
|
class OnThisDayDto {
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
year!: number;
|
year!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,14 +89,14 @@ export class PersonSearchDto {
|
|||||||
closestAssetId?: string;
|
closestAssetId?: string;
|
||||||
|
|
||||||
/** Page number for pagination */
|
/** Page number for pagination */
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
@ApiPropertyOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(1)
|
@Min(1)
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
page: number = 1;
|
page: number = 1;
|
||||||
|
|
||||||
/** Number of items per page */
|
/** Number of items per page */
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
@ApiPropertyOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(1)
|
@Min(1)
|
||||||
@Max(1000)
|
@Max(1000)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
|
import { IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
|
||||||
import { Place } from 'src/database';
|
import { Place } from 'src/database';
|
||||||
@@ -100,7 +100,6 @@ class BaseSearchDto {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
@Max(5)
|
@Max(5)
|
||||||
@Min(-1)
|
@Min(-1)
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
rating?: number;
|
rating?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +115,6 @@ class BaseSearchWithResultsDto extends BaseSearchDto {
|
|||||||
@Max(1000)
|
@Max(1000)
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@Optional()
|
@Optional()
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
size?: number;
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +186,6 @@ export class MetadataSearchDto extends RandomSearchDto {
|
|||||||
@Min(1)
|
@Min(1)
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@Optional()
|
@Optional()
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
page?: number;
|
page?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +215,6 @@ export class SmartSearchDto extends BaseSearchWithResultsDto {
|
|||||||
@Min(1)
|
@Min(1)
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@Optional()
|
@Optional()
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
page?: number;
|
page?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,9 +235,7 @@ export class SearchPeopleDto {
|
|||||||
|
|
||||||
export class PlacesResponseDto {
|
export class PlacesResponseDto {
|
||||||
name!: string;
|
name!: string;
|
||||||
@ApiProperty({ type: 'number', format: 'double' })
|
|
||||||
latitude!: number;
|
latitude!: number;
|
||||||
@ApiProperty({ type: 'number', format: 'double' })
|
|
||||||
longitude!: number;
|
longitude!: number;
|
||||||
admin1name?: string;
|
admin1name?: string;
|
||||||
admin2name?: string;
|
admin2name?: string;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
||||||
import { Equals, IsInt, IsPositive, IsString } from 'class-validator';
|
import { Equals, IsInt, IsPositive, IsString } from 'class-validator';
|
||||||
import { Session } from 'src/database';
|
import { Session } from 'src/database';
|
||||||
import { Optional, ValidateBoolean } from 'src/validation';
|
import { Optional, ValidateBoolean } from 'src/validation';
|
||||||
@@ -10,7 +9,6 @@ export class SessionCreateDto {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@Optional()
|
@Optional()
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ export class DatabaseBackupConfig {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
keepLastAmount!: number;
|
keepLastAmount!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,7 +451,6 @@ class SystemConfigSmtpTransportDto {
|
|||||||
@IsNumber()
|
@IsNumber()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@Max(65_535)
|
@Max(65_535)
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
port!: number;
|
port!: number;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { IsDateString, IsInt, IsPositive, ValidateNested } from 'class-validator';
|
import { IsDateString, IsInt, IsPositive, ValidateNested } from 'class-validator';
|
||||||
import { AssetOrder, UserAvatarColor } from 'src/enum';
|
import { AssetOrder, UserAvatarColor } from 'src/enum';
|
||||||
@@ -72,7 +72,7 @@ class DownloadUpdate implements Partial<DownloadResponse> {
|
|||||||
@Optional()
|
@Optional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@ApiPropertyOptional({ type: 'integer' })
|
@ApiProperty({ type: 'integer' })
|
||||||
archiveSize?: number;
|
archiveSize?: number;
|
||||||
|
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Transform } from 'class-transformer';
|
import { Transform } from 'class-transformer';
|
||||||
import { IsEmail, IsInt, IsNotEmpty, IsString, Min } from 'class-validator';
|
import { IsEmail, IsInt, IsNotEmpty, IsString, Min } from 'class-validator';
|
||||||
import { User, UserAdmin } from 'src/database';
|
import { User, UserAdmin } from 'src/database';
|
||||||
@@ -93,7 +93,7 @@ export class UserAdminCreateDto {
|
|||||||
@Optional({ nullable: true })
|
@Optional({ nullable: true })
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@ApiPropertyOptional({ type: 'integer', format: 'int64' })
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
quotaSizeInBytes?: number | null;
|
quotaSizeInBytes?: number | null;
|
||||||
|
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
@@ -139,7 +139,7 @@ export class UserAdminUpdateDto {
|
|||||||
@Optional({ nullable: true })
|
@Optional({ nullable: true })
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@ApiPropertyOptional({ type: 'integer', format: 'int64' })
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
quotaSizeInBytes?: number | null;
|
quotaSizeInBytes?: number | null;
|
||||||
|
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
|
||||||
import MapModal from '$lib/modals/MapModal.svelte';
|
import MapModal from '$lib/modals/MapModal.svelte';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { getAlbumInfo, type AlbumResponseDto, type MapMarkerResponseDto } from '@immich/sdk';
|
import { getAlbumInfo, type AlbumResponseDto, type MapMarkerResponseDto } from '@immich/sdk';
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
abortController = new AbortController();
|
abortController = new AbortController();
|
||||||
|
|
||||||
let albumInfo: AlbumResponseDto = await getAlbumInfo({ id: album.id, withoutAssets: false, ...authManager.params });
|
let albumInfo: AlbumResponseDto = await getAlbumInfo({ id: album.id, withoutAssets: false });
|
||||||
|
|
||||||
let markers: MapMarkerResponseDto[] = [];
|
let markers: MapMarkerResponseDto[] = [];
|
||||||
for (const asset of albumInfo.assets) {
|
for (const asset of albumInfo.assets) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
const handleAddTag = async () => {
|
const handleAddTag = async () => {
|
||||||
const success = await modalManager.show(AssetTagModal, { assetIds: [asset.id] });
|
const success = await modalManager.show(AssetTagModal, { assetIds: [asset.id] });
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
asset = await getAssetInfo({ id: asset.id });
|
asset = await getAssetInfo({ id: asset.id });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,21 @@
|
|||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import { preferences, user } from '$lib/stores/user.store';
|
import { preferences, user } from '$lib/stores/user.store';
|
||||||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
import { getAssetThumbnailUrl, getPeopleThumbnailUrl, handlePromiseError } from '$lib/utils';
|
||||||
import { delay, getDimensions } from '$lib/utils/asset-utils';
|
import { delay, isFlipped } from '$lib/utils/asset-utils';
|
||||||
import { getByteUnitString } from '$lib/utils/byte-units';
|
import { getByteUnitString } from '$lib/utils/byte-units';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
||||||
import { fromISODateTime, fromISODateTimeUTC } from '$lib/utils/timeline-util';
|
import { fromISODateTime, fromISODateTimeUTC } from '$lib/utils/timeline-util';
|
||||||
import { getParentPath } from '$lib/utils/tree-utils';
|
import { getParentPath } from '$lib/utils/tree-utils';
|
||||||
import { AssetMediaSize, getAssetInfo, updateAsset, type AlbumResponseDto, type AssetResponseDto } from '@immich/sdk';
|
import {
|
||||||
|
AssetMediaSize,
|
||||||
|
getAssetInfo,
|
||||||
|
updateAsset,
|
||||||
|
type AlbumResponseDto,
|
||||||
|
type AssetResponseDto,
|
||||||
|
type ExifResponseDto,
|
||||||
|
} from '@immich/sdk';
|
||||||
import { IconButton } from '@immich/ui';
|
import { IconButton } from '@immich/ui';
|
||||||
import {
|
import {
|
||||||
mdiCalendar,
|
mdiCalendar,
|
||||||
@@ -54,28 +61,17 @@
|
|||||||
|
|
||||||
let { asset, albums = [], currentAlbum = null, onClose }: Props = $props();
|
let { asset, albums = [], currentAlbum = null, onClose }: Props = $props();
|
||||||
|
|
||||||
|
const getDimensions = (exifInfo: ExifResponseDto) => {
|
||||||
|
const { exifImageWidth: width, exifImageHeight: height } = exifInfo;
|
||||||
|
if (isFlipped(exifInfo.orientation)) {
|
||||||
|
return { width: height, height: width };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { width, height };
|
||||||
|
};
|
||||||
|
|
||||||
let showAssetPath = $state(false);
|
let showAssetPath = $state(false);
|
||||||
let showEditFaces = $state(false);
|
let showEditFaces = $state(false);
|
||||||
let isOwner = $derived($user?.id === asset.ownerId);
|
|
||||||
let people = $derived(asset.people || []);
|
|
||||||
let unassignedFaces = $derived(asset.unassignedFaces || []);
|
|
||||||
let showingHiddenPeople = $state(false);
|
|
||||||
let timeZone = $derived(asset.exifInfo?.timeZone);
|
|
||||||
let dateTime = $derived(
|
|
||||||
timeZone && asset.exifInfo?.dateTimeOriginal
|
|
||||||
? fromISODateTime(asset.exifInfo.dateTimeOriginal, timeZone)
|
|
||||||
: fromISODateTimeUTC(asset.localDateTime),
|
|
||||||
);
|
|
||||||
let latlng = $derived(
|
|
||||||
(() => {
|
|
||||||
const lat = asset.exifInfo?.latitude;
|
|
||||||
const lng = asset.exifInfo?.longitude;
|
|
||||||
|
|
||||||
if (lat && lng) {
|
|
||||||
return { lat: Number(lat.toFixed(7)), lng: Number(lng.toFixed(7)) };
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
);
|
|
||||||
let previousId: string | undefined = $state();
|
let previousId: string | undefined = $state();
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -88,6 +84,42 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let isOwner = $derived($user?.id === asset.ownerId);
|
||||||
|
|
||||||
|
const handleNewAsset = async (newAsset: AssetResponseDto) => {
|
||||||
|
// TODO: check if reloading asset data is necessary
|
||||||
|
if (newAsset.id && !authManager.isSharedLink) {
|
||||||
|
const data = await getAssetInfo({ id: asset.id });
|
||||||
|
people = data?.people || [];
|
||||||
|
unassignedFaces = data?.unassignedFaces || [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
handlePromiseError(handleNewAsset(asset));
|
||||||
|
});
|
||||||
|
|
||||||
|
let latlng = $derived(
|
||||||
|
(() => {
|
||||||
|
const lat = asset.exifInfo?.latitude;
|
||||||
|
const lng = asset.exifInfo?.longitude;
|
||||||
|
|
||||||
|
if (lat && lng) {
|
||||||
|
return { lat: Number(lat.toFixed(7)), lng: Number(lng.toFixed(7)) };
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let people = $state(asset.people || []);
|
||||||
|
let unassignedFaces = $state(asset.unassignedFaces || []);
|
||||||
|
let showingHiddenPeople = $state(false);
|
||||||
|
let timeZone = $derived(asset.exifInfo?.timeZone);
|
||||||
|
let dateTime = $derived(
|
||||||
|
timeZone && asset.exifInfo?.dateTimeOriginal
|
||||||
|
? fromISODateTime(asset.exifInfo.dateTimeOriginal, timeZone)
|
||||||
|
: fromISODateTimeUTC(asset.localDateTime),
|
||||||
|
);
|
||||||
|
|
||||||
const getMegapixel = (width: number, height: number): number | undefined => {
|
const getMegapixel = (width: number, height: number): number | undefined => {
|
||||||
const megapixel = Math.round((height * width) / 1_000_000);
|
const megapixel = Math.round((height * width) / 1_000_000);
|
||||||
|
|
||||||
@@ -99,7 +131,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRefreshPeople = async () => {
|
const handleRefreshPeople = async () => {
|
||||||
asset = await getAssetInfo({ id: asset.id });
|
await getAssetInfo({ id: asset.id }).then((data) => {
|
||||||
|
people = data?.people || [];
|
||||||
|
unassignedFaces = data?.unassignedFaces || [];
|
||||||
|
});
|
||||||
showEditFaces = false;
|
showEditFaces = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
import { shortcuts } from '$lib/actions/shortcut';
|
import { shortcuts } from '$lib/actions/shortcut';
|
||||||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||||
import DuplicateAsset from '$lib/components/utilities-page/duplicates/duplicate-asset.svelte';
|
import DuplicateAsset from '$lib/components/utilities-page/duplicates/duplicate-asset.svelte';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { suggestDuplicate } from '$lib/utils/duplicate-utils';
|
import { suggestDuplicate } from '$lib/utils/duplicate-utils';
|
||||||
import { navigate } from '$lib/utils/navigation';
|
import { navigate } from '$lib/utils/navigation';
|
||||||
import { getAssetInfo, type AssetResponseDto } from '@immich/sdk';
|
import { type AssetResponseDto } from '@immich/sdk';
|
||||||
import { Button } from '@immich/ui';
|
import { Button } from '@immich/ui';
|
||||||
import { mdiCheck, mdiImageMultipleOutline, mdiTrashCanOutline } from '@mdi/js';
|
import { mdiCheck, mdiImageMultipleOutline, mdiTrashCanOutline } from '@mdi/js';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
@@ -43,32 +42,32 @@
|
|||||||
assetViewingStore.showAssetViewer(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onNext = async () => {
|
const onNext = () => {
|
||||||
const index = getAssetIndex($viewingAsset.id) + 1;
|
const index = getAssetIndex($viewingAsset.id) + 1;
|
||||||
if (index >= assets.length) {
|
if (index >= assets.length) {
|
||||||
return false;
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
await onViewAsset(assets[index]);
|
setAsset(assets[index]);
|
||||||
return true;
|
return Promise.resolve(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPrevious = async () => {
|
const onPrevious = () => {
|
||||||
const index = getAssetIndex($viewingAsset.id) - 1;
|
const index = getAssetIndex($viewingAsset.id) - 1;
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
return false;
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
await onViewAsset(assets[index]);
|
setAsset(assets[index]);
|
||||||
return true;
|
return Promise.resolve(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRandom = async () => {
|
const onRandom = () => {
|
||||||
if (assets.length <= 0) {
|
if (assets.length <= 0) {
|
||||||
return;
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
const index = Math.floor(Math.random() * assets.length);
|
const index = Math.floor(Math.random() * assets.length);
|
||||||
const asset = assets[index];
|
const asset = assets[index];
|
||||||
await onViewAsset(asset);
|
setAsset(asset);
|
||||||
return { id: asset.id };
|
return Promise.resolve(asset);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelectAsset = (asset: AssetResponseDto) => {
|
const onSelectAsset = (asset: AssetResponseDto) => {
|
||||||
@@ -87,12 +86,6 @@
|
|||||||
selectedAssetIds = new SvelteSet(assets.map((asset) => asset.id));
|
selectedAssetIds = new SvelteSet(assets.map((asset) => asset.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onViewAsset = async ({ id }: AssetResponseDto) => {
|
|
||||||
const asset = await getAssetInfo({ ...authManager.params, id });
|
|
||||||
setAsset(asset);
|
|
||||||
await navigate({ targetRoute: 'current', assetId: asset.id });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleResolve = () => {
|
const handleResolve = () => {
|
||||||
const trashIds = assets.map((asset) => asset.id).filter((id) => !selectedAssetIds.has(id));
|
const trashIds = assets.map((asset) => asset.id).filter((id) => !selectedAssetIds.has(id));
|
||||||
const duplicateAssetIds = assets.map((asset) => asset.id);
|
const duplicateAssetIds = assets.map((asset) => asset.id);
|
||||||
@@ -109,7 +102,9 @@
|
|||||||
{ shortcut: { key: 'a' }, onShortcut: onSelectAll },
|
{ shortcut: { key: 'a' }, onShortcut: onSelectAll },
|
||||||
{
|
{
|
||||||
shortcut: { key: 's' },
|
shortcut: { key: 's' },
|
||||||
onShortcut: () => onViewAsset(assets[0]),
|
onShortcut: () => {
|
||||||
|
setAsset(assets[0]);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ shortcut: { key: 'd' }, onShortcut: onSelectNone },
|
{ shortcut: { key: 'd' }, onShortcut: onSelectNone },
|
||||||
{ shortcut: { key: 'c', shift: true }, onShortcut: handleResolve },
|
{ shortcut: { key: 'c', shift: true }, onShortcut: handleResolve },
|
||||||
@@ -171,7 +166,12 @@
|
|||||||
|
|
||||||
<div class="flex flex-wrap gap-1 mb-4 place-items-center place-content-center px-4 pt-4">
|
<div class="flex flex-wrap gap-1 mb-4 place-items-center place-content-center px-4 pt-4">
|
||||||
{#each assets as asset (asset.id)}
|
{#each assets as asset (asset.id)}
|
||||||
<DuplicateAsset {asset} {onSelectAsset} isSelected={selectedAssetIds.has(asset.id)} {onViewAsset} />
|
<DuplicateAsset
|
||||||
|
{asset}
|
||||||
|
{onSelectAsset}
|
||||||
|
isSelected={selectedAssetIds.has(asset.id)}
|
||||||
|
onViewAsset={(asset) => setAsset(asset)}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import {
|
|||||||
type AssetResponseDto,
|
type AssetResponseDto,
|
||||||
type AssetTypeEnum,
|
type AssetTypeEnum,
|
||||||
type DownloadInfoDto,
|
type DownloadInfoDto,
|
||||||
type ExifResponseDto,
|
|
||||||
type StackResponseDto,
|
type StackResponseDto,
|
||||||
type UserPreferencesResponseDto,
|
type UserPreferencesResponseDto,
|
||||||
type UserResponseDto,
|
type UserResponseDto,
|
||||||
@@ -329,15 +328,6 @@ export function isFlipped(orientation?: string | null) {
|
|||||||
return value && (isRotated270CW(value) || isRotated90CW(value));
|
return value && (isRotated270CW(value) || isRotated90CW(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDimensions = (exifInfo: ExifResponseDto) => {
|
|
||||||
const { exifImageWidth: width, exifImageHeight: height } = exifInfo;
|
|
||||||
if (isFlipped(exifInfo.orientation)) {
|
|
||||||
return { width: height, height: width };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { width, height };
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getFileSize(asset: AssetResponseDto, maxPrecision = 4): string {
|
export function getFileSize(asset: AssetResponseDto, maxPrecision = 4): string {
|
||||||
const size = asset.exifInfo?.fileSizeInByte || 0;
|
const size = asset.exifInfo?.fileSizeInByte || 0;
|
||||||
return size > 0 ? getByteUnitString(size, undefined, maxPrecision) : 'Invalid Data';
|
return size > 0 ? getByteUnitString(size, undefined, maxPrecision) : 'Invalid Data';
|
||||||
|
|||||||
Reference in New Issue
Block a user