Compare commits
6 Commits
chore/temp
...
fix/19543
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0291b6ad6 | ||
|
|
2d2673c114 | ||
|
|
56e5236a39 | ||
|
|
8529f92ebc | ||
|
|
761ac074c9 | ||
|
|
7e377d3e42 |
@@ -176,7 +176,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleBackup({bool processBulk = true}) async {
|
Future<void> _handleBackup({bool processBulk = true}) async {
|
||||||
if (!_isBackupEnabled) {
|
if (!_isBackupEnabled || _isCleanedUp) {
|
||||||
_logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine");
|
_logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -205,30 +205,27 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _syncAssets({Duration? hashTimeout}) async {
|
Future<void> _syncAssets({Duration? hashTimeout}) async {
|
||||||
final futures = <Future<void>>[];
|
await _ref.read(backgroundSyncProvider).syncLocal();
|
||||||
|
if (_isCleanedUp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final localSyncFuture = _ref.read(backgroundSyncProvider).syncLocal().then((_) async {
|
await _ref.read(backgroundSyncProvider).syncRemote();
|
||||||
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
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hashFuture;
|
await hashFuture;
|
||||||
});
|
|
||||||
|
|
||||||
futures.add(localSyncFuture);
|
|
||||||
futures.add(_ref.read(backgroundSyncProvider).syncRemote());
|
|
||||||
|
|
||||||
await Future.wait(futures);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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(
|
||||||
@@ -19,7 +20,9 @@ class SyncLinkedAlbumService {
|
|||||||
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
||||||
final DriftAlbumApiRepository _albumApiRepository;
|
final DriftAlbumApiRepository _albumApiRepository;
|
||||||
|
|
||||||
const SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository);
|
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();
|
||||||
@@ -48,8 +51,12 @@ class SyncLinkedAlbumService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> manageLinkedAlbums(List<LocalAlbum> localAlbums, String ownerId) async {
|
Future<void> manageLinkedAlbums(List<LocalAlbum> localAlbums, String ownerId) async {
|
||||||
for (final album in localAlbums) {
|
try {
|
||||||
await _processLocalAlbum(album, ownerId);
|
for (final album in localAlbums) {
|
||||||
|
await _processLocalAlbum(album, ownerId);
|
||||||
|
}
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_log.severe("Error managing linked albums", error, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
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';
|
||||||
@@ -42,49 +44,13 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
|||||||
|
|
||||||
Future<void> _handleMigration() async {
|
Future<void> _handleMigration() async {
|
||||||
try {
|
try {
|
||||||
if (widget.switchingToBeta) {
|
await _performMigrationLogic().timeout(
|
||||||
final assetNotifier = ref.read(assetProvider.notifier);
|
const Duration(minutes: 3),
|
||||||
if (assetNotifier.mounted) {
|
onTimeout: () async {
|
||||||
assetNotifier.dispose();
|
await IsarStoreRepository(ref.read(isarProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);
|
||||||
}
|
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(() {
|
||||||
@@ -102,6 +68,52 @@ 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,6 +4,7 @@ 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';
|
||||||
@@ -47,11 +48,23 @@ 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(
|
||||||
(a) {
|
(_) async {
|
||||||
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,8 +7,6 @@ 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';
|
||||||
@@ -17,11 +15,7 @@ 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 {
|
||||||
@@ -32,28 +26,6 @@ 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,30 +148,25 @@ 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
|
||||||
_safeRun(backgroundManager.syncLocal(), "syncLocal");
|
await _safeRun(backgroundManager.syncLocal(), "syncLocal");
|
||||||
_safeRun(backgroundManager.hashAssets(), "hashAssets");
|
await _safeRun(backgroundManager.syncRemote(), "syncRemote");
|
||||||
_safeRun(backgroundManager.syncRemote(), "syncRemote").then((_) {
|
await _safeRun(backgroundManager.hashAssets(), "hashAssets");
|
||||||
if (isAlbumLinkedSyncEnable) {
|
if (isAlbumLinkedSyncEnable) {
|
||||||
_safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum");
|
await _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) {
|
||||||
_safeRun(_ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id), "handleBackupResume");
|
await _safeRun(
|
||||||
|
_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,7 +5,6 @@ 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';
|
||||||
@@ -23,13 +22,8 @@ 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';
|
||||||
|
|
||||||
@@ -69,7 +63,10 @@ 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
|
||||||
if (version == 15) {
|
final needBetaMigration = Store.tryGet(StoreKey.needBetaMigration);
|
||||||
|
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);
|
||||||
|
|
||||||
@@ -77,6 +74,7 @@ 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);
|
||||||
@@ -310,25 +308,6 @@ 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,9 +10,11 @@ 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';
|
||||||
@@ -161,6 +163,18 @@ 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();
|
||||||
|
|
||||||
@@ -178,7 +192,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;
|
||||||
}
|
}
|
||||||
@@ -276,6 +290,7 @@ 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,7 +104,6 @@ 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),
|
||||||
@@ -270,7 +269,10 @@ 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 | replaceAsset
|
*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace the asset with new file, without changing its id
|
||||||
*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,6 +128,7 @@ 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:
|
||||||
///
|
///
|
||||||
/// * [num] count:
|
/// * [int] count:
|
||||||
Future<Response> getRandomWithHttpInfo({ num? count, }) async {
|
Future<Response> getRandomWithHttpInfo({ int? 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:
|
||||||
///
|
///
|
||||||
/// * [num] count:
|
/// * [int] count:
|
||||||
Future<List<AssetResponseDto>?> getRandom({ num? count, }) async {
|
Future<List<AssetResponseDto>?> getRandom({ int? 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// replaceAsset
|
/// Replace the asset with new file, without changing its id
|
||||||
///
|
///
|
||||||
/// Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
|
/// 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].
|
/// Note: This method returns the HTTP [Response].
|
||||||
///
|
///
|
||||||
@@ -823,9 +823,9 @@ class AssetsApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// replaceAsset
|
/// Replace the asset with new file, without changing its id
|
||||||
///
|
///
|
||||||
/// Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
|
/// 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:
|
/// 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:
|
||||||
///
|
///
|
||||||
/// * [num] count:
|
/// * [int] count:
|
||||||
Future<Response> getRandomWithHttpInfo({ num? count, }) async {
|
Future<Response> getRandomWithHttpInfo({ int? 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:
|
||||||
///
|
///
|
||||||
/// * [num] count:
|
/// * [int] count:
|
||||||
Future<List<AssetResponseDto>?> getRandom({ num? count, }) async {
|
Future<List<AssetResponseDto>?> getRandom({ int? 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,4 +127,138 @@ 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:
|
||||||
///
|
///
|
||||||
/// * [num] page:
|
/// * [int] page:
|
||||||
/// Page number for pagination
|
/// Page number for pagination
|
||||||
///
|
///
|
||||||
/// * [num] size:
|
/// * [int] size:
|
||||||
/// Number of items per page
|
/// Number of items per page
|
||||||
///
|
///
|
||||||
/// * [bool] withHidden:
|
/// * [bool] withHidden:
|
||||||
Future<Response> getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async {
|
Future<Response> getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, int? page, int? 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:
|
||||||
///
|
///
|
||||||
/// * [num] page:
|
/// * [int] page:
|
||||||
/// Page number for pagination
|
/// Page number for pagination
|
||||||
///
|
///
|
||||||
/// * [num] size:
|
/// * [int] size:
|
||||||
/// Number of items per page
|
/// Number of items per page
|
||||||
///
|
///
|
||||||
/// * [bool] withHidden:
|
/// * [bool] withHidden:
|
||||||
Future<PeopleResponseDto?> getAllPeople({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async {
|
Future<PeopleResponseDto?> getAllPeople({ String? closestAssetId, String? closestPersonId, int? page, int? 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:
|
||||||
///
|
///
|
||||||
/// * [num] rating:
|
/// * [int] rating:
|
||||||
///
|
///
|
||||||
/// * [num] size:
|
/// * [int] 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, 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 {
|
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 {
|
||||||
// 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:
|
||||||
///
|
///
|
||||||
/// * [num] rating:
|
/// * [int] rating:
|
||||||
///
|
///
|
||||||
/// * [num] size:
|
/// * [int] 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, 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 {
|
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 {
|
||||||
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.
|
||||||
///
|
///
|
||||||
num? dateTimeRelative;
|
int? 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.
|
||||||
///
|
///
|
||||||
num? latitude;
|
double? 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.
|
||||||
///
|
///
|
||||||
num? longitude;
|
double? 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.
|
||||||
///
|
///
|
||||||
num? rating;
|
int? 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: num.parse('${json[r'dateTimeRelative']}'),
|
dateTimeRelative: mapValueOfType<int>(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: num.parse('${json[r'latitude']}'),
|
latitude: (mapValueOfType<num>(json, r'latitude')).toDouble(),
|
||||||
longitude: num.parse('${json[r'longitude']}'),
|
longitude: (mapValueOfType<num>(json, r'longitude')).toDouble(),
|
||||||
rating: num.parse('${json[r'rating']}'),
|
rating: mapValueOfType<int>(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
|
||||||
num keepLastAmount;
|
int 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: num.parse('${json[r'keepLastAmount']}'),
|
keepLastAmount: mapValueOfType<int>(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;
|
||||||
|
|
||||||
num? exifImageHeight;
|
int? exifImageHeight;
|
||||||
|
|
||||||
num? exifImageWidth;
|
int? exifImageWidth;
|
||||||
|
|
||||||
String? exposureTime;
|
String? exposureTime;
|
||||||
|
|
||||||
num? fNumber;
|
double? fNumber;
|
||||||
|
|
||||||
int? fileSizeInByte;
|
int? fileSizeInByte;
|
||||||
|
|
||||||
num? focalLength;
|
double? focalLength;
|
||||||
|
|
||||||
num? iso;
|
int? iso;
|
||||||
|
|
||||||
num? latitude;
|
double? latitude;
|
||||||
|
|
||||||
String? lensModel;
|
String? lensModel;
|
||||||
|
|
||||||
num? longitude;
|
double? longitude;
|
||||||
|
|
||||||
String? make;
|
String? make;
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ class ExifResponseDto {
|
|||||||
|
|
||||||
String? projectionType;
|
String? projectionType;
|
||||||
|
|
||||||
num? rating;
|
int? rating;
|
||||||
|
|
||||||
String? state;
|
String? state;
|
||||||
|
|
||||||
@@ -263,38 +263,22 @@ 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: json[r'exifImageHeight'] == null
|
exifImageHeight: mapValueOfType<int>(json, r'exifImageHeight'),
|
||||||
? null
|
exifImageWidth: mapValueOfType<int>(json, r'exifImageWidth'),
|
||||||
: 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: json[r'fNumber'] == null
|
fNumber: (mapValueOfType<num>(json, r'fNumber'))?.toDouble(),
|
||||||
? null
|
|
||||||
: num.parse('${json[r'fNumber']}'),
|
|
||||||
fileSizeInByte: mapValueOfType<int>(json, r'fileSizeInByte'),
|
fileSizeInByte: mapValueOfType<int>(json, r'fileSizeInByte'),
|
||||||
focalLength: json[r'focalLength'] == null
|
focalLength: (mapValueOfType<num>(json, r'focalLength'))?.toDouble(),
|
||||||
? null
|
iso: mapValueOfType<int>(json, r'iso'),
|
||||||
: num.parse('${json[r'focalLength']}'),
|
latitude: (mapValueOfType<num>(json, r'latitude'))?.toDouble(),
|
||||||
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: json[r'longitude'] == null
|
longitude: (mapValueOfType<num>(json, r'longitude'))?.toDouble(),
|
||||||
? 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: json[r'rating'] == null
|
rating: mapValueOfType<int>(json, r'rating'),
|
||||||
? 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.
|
||||||
///
|
///
|
||||||
num? page;
|
int? 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.
|
||||||
///
|
///
|
||||||
num? rating;
|
int? 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.
|
||||||
///
|
///
|
||||||
num? size;
|
int? 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: num.parse('${json[r'page']}'),
|
page: mapValueOfType<int>(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: num.parse('${json[r'rating']}'),
|
rating: mapValueOfType<int>(json, r'rating'),
|
||||||
size: num.parse('${json[r'size']}'),
|
size: mapValueOfType<int>(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
|
||||||
num year;
|
int 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: num.parse('${json[r'year']}'),
|
year: mapValueOfType<int>(json, r'year')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ class PlacesResponseDto {
|
|||||||
///
|
///
|
||||||
String? admin2name;
|
String? admin2name;
|
||||||
|
|
||||||
num latitude;
|
double latitude;
|
||||||
|
|
||||||
num longitude;
|
double 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: num.parse('${json[r'latitude']}'),
|
latitude: (mapValueOfType<num>(json, r'latitude')!).toDouble(),
|
||||||
longitude: num.parse('${json[r'longitude']}'),
|
longitude: (mapValueOfType<num>(json, r'longitude')!).toDouble(),
|
||||||
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.
|
||||||
///
|
///
|
||||||
num? rating;
|
int? 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.
|
||||||
///
|
///
|
||||||
num? size;
|
int? 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: num.parse('${json[r'rating']}'),
|
rating: mapValueOfType<int>(json, r'rating'),
|
||||||
size: num.parse('${json[r'size']}'),
|
size: mapValueOfType<int>(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.
|
||||||
///
|
///
|
||||||
num? duration;
|
int? 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: num.parse('${json[r'duration']}'),
|
duration: mapValueOfType<int>(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.
|
||||||
///
|
///
|
||||||
num? page;
|
int? 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.
|
||||||
///
|
///
|
||||||
num? rating;
|
int? 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.
|
||||||
///
|
///
|
||||||
num? size;
|
int? 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: num.parse('${json[r'page']}'),
|
page: mapValueOfType<int>(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: num.parse('${json[r'rating']}'),
|
rating: mapValueOfType<int>(json, r'rating'),
|
||||||
size: num.parse('${json[r'size']}'),
|
size: mapValueOfType<int>(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.
|
||||||
///
|
///
|
||||||
num? rating;
|
int? 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: num.parse('${json[r'rating']}'),
|
rating: mapValueOfType<int>(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
|
||||||
num port;
|
int 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: num.parse('${json[r'port']}'),
|
port: mapValueOfType<int>(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.
|
||||||
///
|
///
|
||||||
num? latitude;
|
double? 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.
|
||||||
///
|
///
|
||||||
num? longitude;
|
double? 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.
|
||||||
///
|
///
|
||||||
num? rating;
|
int? 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: num.parse('${json[r'latitude']}'),
|
latitude: (mapValueOfType<num>(json, r'latitude')).toDouble(),
|
||||||
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||||
longitude: num.parse('${json[r'longitude']}'),
|
longitude: (mapValueOfType<num>(json, r'longitude')).toDouble(),
|
||||||
rating: num.parse('${json[r'rating']}'),
|
rating: mapValueOfType<int>(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": "number"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -2504,7 +2504,8 @@
|
|||||||
"description": "This endpoint requires the `asset.download` permission."
|
"description": "This endpoint requires the `asset.download` permission."
|
||||||
},
|
},
|
||||||
"put": {
|
"put": {
|
||||||
"description": "Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.",
|
"deprecated": true,
|
||||||
|
"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": [
|
||||||
{
|
{
|
||||||
@@ -2566,12 +2567,14 @@
|
|||||||
"api_key": []
|
"api_key": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"summary": "replaceAsset",
|
"summary": "Replace the asset with new file, without changing its id",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
@@ -5239,7 +5242,7 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"default": 1,
|
"default": 1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5251,7 +5254,7 @@
|
|||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"default": 500,
|
"default": 500,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5952,7 +5955,7 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5962,7 +5965,7 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -10405,7 +10408,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"dateTimeRelative": {
|
"dateTimeRelative": {
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -10425,15 +10428,17 @@
|
|||||||
"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": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"timeZone": {
|
"timeZone": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -11568,7 +11573,7 @@
|
|||||||
},
|
},
|
||||||
"keepLastAmount": {
|
"keepLastAmount": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -11761,12 +11766,12 @@
|
|||||||
"exifImageHeight": {
|
"exifImageHeight": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"exifImageWidth": {
|
"exifImageWidth": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"exposureTime": {
|
"exposureTime": {
|
||||||
"default": null,
|
"default": null,
|
||||||
@@ -11775,6 +11780,7 @@
|
|||||||
},
|
},
|
||||||
"fNumber": {
|
"fNumber": {
|
||||||
"default": null,
|
"default": null,
|
||||||
|
"format": "double",
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
@@ -11786,16 +11792,18 @@
|
|||||||
},
|
},
|
||||||
"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": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"latitude": {
|
"latitude": {
|
||||||
"default": null,
|
"default": null,
|
||||||
|
"format": "double",
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
@@ -11806,6 +11814,7 @@
|
|||||||
},
|
},
|
||||||
"longitude": {
|
"longitude": {
|
||||||
"default": null,
|
"default": null,
|
||||||
|
"format": "double",
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
@@ -11838,7 +11847,7 @@
|
|||||||
"rating": {
|
"rating": {
|
||||||
"default": null,
|
"default": null,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"default": null,
|
"default": null,
|
||||||
@@ -12591,7 +12600,7 @@
|
|||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"personIds": {
|
"personIds": {
|
||||||
"items": {
|
"items": {
|
||||||
@@ -12606,12 +12615,12 @@
|
|||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
@@ -12888,7 +12897,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"year": {
|
"year": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -13437,9 +13446,11 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"latitude": {
|
"latitude": {
|
||||||
|
"format": "double",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"longitude": {
|
"longitude": {
|
||||||
|
"format": "double",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
@@ -13563,12 +13574,12 @@
|
|||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
@@ -14251,7 +14262,7 @@
|
|||||||
"duration": {
|
"duration": {
|
||||||
"description": "session duration, in seconds",
|
"description": "session duration, in seconds",
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -14647,7 +14658,7 @@
|
|||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"personIds": {
|
"personIds": {
|
||||||
"items": {
|
"items": {
|
||||||
@@ -14666,12 +14677,12 @@
|
|||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
@@ -14860,7 +14871,7 @@
|
|||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
@@ -16680,7 +16691,7 @@
|
|||||||
"port": {
|
"port": {
|
||||||
"maximum": 65535,
|
"maximum": 65535,
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -17296,6 +17307,7 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"latitude": {
|
"latitude": {
|
||||||
|
"format": "double",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"livePhotoVideoId": {
|
"livePhotoVideoId": {
|
||||||
@@ -17304,12 +17316,13 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"longitude": {
|
"longitude": {
|
||||||
|
"format": "double",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"rating": {
|
"rating": {
|
||||||
"maximum": 5,
|
"maximum": 5,
|
||||||
"minimum": -1,
|
"minimum": -1,
|
||||||
"type": "number"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"visibility": {
|
"visibility": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
|
|||||||
@@ -2368,7 +2368,7 @@ export function downloadAsset({ id, key, slug }: {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* replaceAsset
|
* Replace the asset with new file, without changing its id
|
||||||
*/
|
*/
|
||||||
export function replaceAsset({ id, key, slug, assetMediaReplaceDto }: {
|
export function replaceAsset({ id, key, slug, assetMediaReplaceDto }: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -96,8 +96,9 @@ export class AssetMediaController {
|
|||||||
@Put(':id/original')
|
@Put(':id/original')
|
||||||
@UseInterceptors(FileUploadInterceptor)
|
@UseInterceptors(FileUploadInterceptor)
|
||||||
@ApiConsumes('multipart/form-data')
|
@ApiConsumes('multipart/form-data')
|
||||||
@EndpointLifecycle({ addedAt: 'v1.106.0' })
|
@EndpointLifecycle({
|
||||||
@ApiOperation({
|
addedAt: 'v1.106.0',
|
||||||
|
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, ApiProperty, ApiTags } from '@nestjs/swagger';
|
import { ApiExtension, ApiOperation, ApiOperationOptions, 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,12 +159,21 @@ type LifecycleMetadata = {
|
|||||||
deprecatedAt?: LifecycleRelease;
|
deprecatedAt?: LifecycleRelease;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EndpointLifecycle = ({ addedAt, deprecatedAt }: LifecycleMetadata) => {
|
export const EndpointLifecycle = ({
|
||||||
|
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({ deprecated: true, description: DEPRECATED_IN_PREFIX + deprecatedAt }),
|
ApiOperation({
|
||||||
|
deprecated: true,
|
||||||
|
description: DEPRECATED_IN_PREFIX + deprecatedAt + (description ? `. ${description}` : ''),
|
||||||
|
...options,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
@@ -46,17 +46,20 @@ 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()
|
||||||
@@ -74,6 +77,7 @@ export class AssetBulkUpdateDto extends UpdateAssetBase {
|
|||||||
@IsNotSiblingOf(['dateTimeOriginal'])
|
@IsNotSiblingOf(['dateTimeOriginal'])
|
||||||
@Optional()
|
@Optional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
|
@ApiPropertyOptional({ type: 'integer' })
|
||||||
dateTimeRelative?: number;
|
dateTimeRelative?: number;
|
||||||
|
|
||||||
@IsNotSiblingOf(['dateTimeOriginal'])
|
@IsNotSiblingOf(['dateTimeOriginal'])
|
||||||
@@ -92,6 +96,7 @@ export class RandomAssetsDto {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
|
@ApiPropertyOptional({ type: 'integer' })
|
||||||
count?: number;
|
count?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,37 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiPropertyOptional } 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({ format: 'double' })
|
@ApiProperty({ type: 'number', 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({ format: 'double' })
|
@ApiProperty({ type: 'number', 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,6 +32,7 @@ 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()
|
@ApiPropertyOptional({ type: 'integer' })
|
||||||
@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()
|
@ApiPropertyOptional({ type: 'integer' })
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(1)
|
@Min(1)
|
||||||
@Max(1000)
|
@Max(1000)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } 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,6 +100,7 @@ class BaseSearchDto {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
@Max(5)
|
@Max(5)
|
||||||
@Min(-1)
|
@Min(-1)
|
||||||
|
@ApiPropertyOptional({ type: 'integer' })
|
||||||
rating?: number;
|
rating?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +116,7 @@ class BaseSearchWithResultsDto extends BaseSearchDto {
|
|||||||
@Max(1000)
|
@Max(1000)
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@Optional()
|
@Optional()
|
||||||
|
@ApiPropertyOptional({ type: 'integer' })
|
||||||
size?: number;
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +188,7 @@ export class MetadataSearchDto extends RandomSearchDto {
|
|||||||
@Min(1)
|
@Min(1)
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@Optional()
|
@Optional()
|
||||||
|
@ApiPropertyOptional({ type: 'integer' })
|
||||||
page?: number;
|
page?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,6 +218,7 @@ export class SmartSearchDto extends BaseSearchWithResultsDto {
|
|||||||
@Min(1)
|
@Min(1)
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@Optional()
|
@Optional()
|
||||||
|
@ApiPropertyOptional({ type: 'integer' })
|
||||||
page?: number;
|
page?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +239,9 @@ 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,3 +1,4 @@
|
|||||||
|
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';
|
||||||
@@ -9,6 +10,7 @@ export class SessionCreateDto {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@Optional()
|
@Optional()
|
||||||
|
@ApiPropertyOptional({ type: 'integer' })
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export class DatabaseBackupConfig {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
keepLastAmount!: number;
|
keepLastAmount!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,6 +452,7 @@ 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 } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } 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()
|
||||||
@ApiProperty({ type: 'integer' })
|
@ApiPropertyOptional({ type: 'integer' })
|
||||||
archiveSize?: number;
|
archiveSize?: number;
|
||||||
|
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } 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)
|
||||||
@ApiProperty({ type: 'integer', format: 'int64' })
|
@ApiPropertyOptional({ 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)
|
||||||
@ApiProperty({ type: 'integer', format: 'int64' })
|
@ApiPropertyOptional({ type: 'integer', format: 'int64' })
|
||||||
quotaSizeInBytes?: number | null;
|
quotaSizeInBytes?: number | null;
|
||||||
|
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<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';
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
abortController = new AbortController();
|
abortController = new AbortController();
|
||||||
|
|
||||||
let albumInfo: AlbumResponseDto = await getAlbumInfo({ id: album.id, withoutAssets: false });
|
let albumInfo: AlbumResponseDto = await getAlbumInfo({ id: album.id, withoutAssets: false, ...authManager.params });
|
||||||
|
|
||||||
let markers: MapMarkerResponseDto[] = [];
|
let markers: MapMarkerResponseDto[] = [];
|
||||||
for (const asset of albumInfo.assets) {
|
for (const asset of albumInfo.assets) {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
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,21 +16,14 @@
|
|||||||
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, handlePromiseError } from '$lib/utils';
|
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import { delay, isFlipped } from '$lib/utils/asset-utils';
|
import { delay, getDimensions } 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 {
|
import { AssetMediaSize, getAssetInfo, updateAsset, type AlbumResponseDto, type AssetResponseDto } from '@immich/sdk';
|
||||||
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,
|
||||||
@@ -61,17 +54,28 @@
|
|||||||
|
|
||||||
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(() => {
|
||||||
@@ -84,42 +88,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -131,10 +99,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRefreshPeople = async () => {
|
const handleRefreshPeople = async () => {
|
||||||
await getAssetInfo({ id: asset.id }).then((data) => {
|
asset = await getAssetInfo({ id: asset.id });
|
||||||
people = data?.people || [];
|
|
||||||
unassignedFaces = data?.unassignedFaces || [];
|
|
||||||
});
|
|
||||||
showEditFaces = false;
|
showEditFaces = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
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 { type AssetResponseDto } from '@immich/sdk';
|
import { getAssetInfo, 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';
|
||||||
@@ -42,32 +43,32 @@
|
|||||||
assetViewingStore.showAssetViewer(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onNext = () => {
|
const onNext = async () => {
|
||||||
const index = getAssetIndex($viewingAsset.id) + 1;
|
const index = getAssetIndex($viewingAsset.id) + 1;
|
||||||
if (index >= assets.length) {
|
if (index >= assets.length) {
|
||||||
return Promise.resolve(false);
|
return false;
|
||||||
}
|
}
|
||||||
setAsset(assets[index]);
|
await onViewAsset(assets[index]);
|
||||||
return Promise.resolve(true);
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPrevious = () => {
|
const onPrevious = async () => {
|
||||||
const index = getAssetIndex($viewingAsset.id) - 1;
|
const index = getAssetIndex($viewingAsset.id) - 1;
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
return Promise.resolve(false);
|
return false;
|
||||||
}
|
}
|
||||||
setAsset(assets[index]);
|
await onViewAsset(assets[index]);
|
||||||
return Promise.resolve(true);
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRandom = () => {
|
const onRandom = async () => {
|
||||||
if (assets.length <= 0) {
|
if (assets.length <= 0) {
|
||||||
return Promise.resolve(undefined);
|
return;
|
||||||
}
|
}
|
||||||
const index = Math.floor(Math.random() * assets.length);
|
const index = Math.floor(Math.random() * assets.length);
|
||||||
const asset = assets[index];
|
const asset = assets[index];
|
||||||
setAsset(asset);
|
await onViewAsset(asset);
|
||||||
return Promise.resolve(asset);
|
return { id: asset.id };
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelectAsset = (asset: AssetResponseDto) => {
|
const onSelectAsset = (asset: AssetResponseDto) => {
|
||||||
@@ -86,6 +87,12 @@
|
|||||||
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);
|
||||||
@@ -102,9 +109,7 @@
|
|||||||
{ shortcut: { key: 'a' }, onShortcut: onSelectAll },
|
{ shortcut: { key: 'a' }, onShortcut: onSelectAll },
|
||||||
{
|
{
|
||||||
shortcut: { key: 's' },
|
shortcut: { key: 's' },
|
||||||
onShortcut: () => {
|
onShortcut: () => onViewAsset(assets[0]),
|
||||||
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 },
|
||||||
@@ -166,12 +171,7 @@
|
|||||||
|
|
||||||
<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
|
<DuplicateAsset {asset} {onSelectAsset} isSelected={selectedAssetIds.has(asset.id)} {onViewAsset} />
|
||||||
{asset}
|
|
||||||
{onSelectAsset}
|
|
||||||
isSelected={selectedAssetIds.has(asset.id)}
|
|
||||||
onViewAsset={(asset) => setAsset(asset)}
|
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ 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,
|
||||||
@@ -328,6 +329,15 @@ 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