Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a460940430 | ||
|
|
fc2455be80 | ||
|
|
fd4357cf23 | ||
|
|
e41e0df27e | ||
|
|
f370dc3929 | ||
|
|
1c2d83e2c7 | ||
|
|
d6756f3d81 | ||
|
|
71ef7685c5 | ||
|
|
b7516f31c6 | ||
|
|
065fb166c2 | ||
|
|
4cc6e3b966 | ||
|
|
1c293a2759 | ||
|
|
062e2eca6f | ||
|
|
bcc2c34eef | ||
|
|
1613ae9185 | ||
|
|
d827a6182b | ||
|
|
83df14d379 | ||
|
|
7c1dae918d | ||
|
|
1b54c4f8e7 | ||
|
|
49b74e9091 | ||
|
|
a1f1e5bc37 | ||
|
|
2dc8a93685 | ||
|
|
c2145cbe11 | ||
|
|
50a792a81a | ||
|
|
e2bd7e1e08 | ||
|
|
11a5a990d0 | ||
|
|
ecc894ac82 | ||
|
|
50b649cd3e | ||
|
|
99b018cd49 | ||
|
|
6aa2800275 |
@@ -4,7 +4,7 @@ services:
|
||||
immich-server:
|
||||
container_name: immich_server
|
||||
image: ghcr.io/immich-app/immich-server:release
|
||||
entrypoint: ["/bin/sh", "./start-server.sh"]
|
||||
command: ["start-server.sh"]
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
env_file:
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
immich-microservices:
|
||||
container_name: immich_microservices
|
||||
image: ghcr.io/immich-app/immich-server:release
|
||||
entrypoint: ["/bin/sh", "./start-microservices.sh"]
|
||||
command: ["start-microservices.sh"]
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
env_file:
|
||||
@@ -42,7 +42,6 @@ services:
|
||||
immich-web:
|
||||
container_name: immich_web
|
||||
image: ghcr.io/immich-app/immich-web:release
|
||||
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
|
||||
BIN
docs/docs/features/img/sidecar-jobs.png
Normal file
BIN
docs/docs/features/img/sidecar-jobs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/docs/features/img/xmp-sidecars.png
Normal file
BIN
docs/docs/features/img/xmp-sidecars.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
13
docs/docs/features/xmp-sidecars.md
Normal file
13
docs/docs/features/xmp-sidecars.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# XMP Sidecars
|
||||
|
||||
Immich can ingest XMP sidecars on file upload (via the CLI) as well as detect new sidecars that are placed in the filesystem for existing images.
|
||||
|
||||
<img src={require('./img/xmp-sidecars.png').default} title='XMP sidecars' />
|
||||
|
||||
XMP sidecars are external XML files that contain metadata related to media files. Many applications read and write these files either exclusively or in addition to the metadata written to image files. They can be a powerful tool for editing and storing metadata of a media file without modifying the mdia file itself. When Immich receives or detects an XMP sidecar for a media file, it will attempt to extract the metadata from both the sidecar as well as the media file. It will prioritize the metadata for fields in the sidecar but will fall back and use the metadata in the media file if necessary.
|
||||
|
||||
When importing files via the CLI bulk uploader, Immich will automatically detect XMP sidecar files as files that exist next to the original media file and have the exact same name with an additional `.xmp` file extension (i.e., `PXL_20230401_203352928.MP.jpg` and `PXL_20230401_203352928.MP.jpg.xmp`).
|
||||
|
||||
There are 2 administrator jobs associated with sidecar files: `SYNC` and `DISCOVER`. The sync job will re-scan all media with existing sidecar files and queue them for a metadata refresh. This is a great use case when third-party applications are used to modify the metadata of media. The discover job will attempt to scan the filesystem for new sidecar files for all media that does not currently have a sidecar file associated with it.
|
||||
|
||||
<img src={require('./img/sidecar-jobs.png').default} title='Sidecar Administrator Jobs' />
|
||||
5
mobile/.gitignore
vendored
5
mobile/.gitignore
vendored
@@ -49,3 +49,8 @@ app.*.map.json
|
||||
|
||||
# Fastlane
|
||||
ios/fastlane/report.xml
|
||||
|
||||
# Isar
|
||||
default.isar
|
||||
default.isar.lock
|
||||
libisar.so
|
||||
Submodule mobile/.isar updated: 70da4e0bbd...6643d064ab
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 80,
|
||||
"android.injected.version.name" => "1.57.0",
|
||||
"android.injected.version.code" => 81,
|
||||
"android.injected.version.name" => "1.58.0",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
* Remove Hive box
|
||||
@@ -5,19 +5,17 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.00032">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000296">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="29.247439">
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="64.042552">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="22.794249">
|
||||
|
||||
<failure message="/usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/actions/actions_helper.rb:67:in `execute_action' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/runner.rb:255:in `block in execute_action' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/runner.rb:229:in `chdir' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/runner.rb:229:in `execute_action' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/runner.rb:157:in `trigger_action_by_name' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/fast_file.rb:159:in `method_missing' Fastfile:42:in `block (2 levels) in parsing_binding' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/lane.rb:33:in `call' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/runner.rb:49:in `block in execute' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/runner.rb:45:in `chdir' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/runner.rb:45:in `execute' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/lane_manager.rb:47:in `cruise_lane' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/command_line_handler.rb:36:in `handle' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/commands_generator.rb:110:in `block (2 levels) in run' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in `call' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in `run' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in `run_active_command' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in `run!' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in `run!' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/commands_generator.rb:354:in `run' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/commands_generator.rb:43:in `start' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in `take_off' /usr/local/Cellar/fastlane/2.212.2/libexec/gems/fastlane-2.212.2/bin/fastlane:23:in `<top (required)>' /usr/local/Cellar/fastlane/2.212.2/libexec/bin/fastlane:25:in `load' /usr/local/Cellar/fastlane/2.212.2/libexec/bin/fastlane:25:in `<main>' Google Api Error: Invalid request - APK specifies a version code that has already been used." />
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="29.676557">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -257,6 +257,15 @@
|
||||
"sharing_page_empty_list": "EMPTY LIST",
|
||||
"sharing_silver_appbar_create_shared_album": "Create shared album",
|
||||
"sharing_silver_appbar_share_partner": "Share with partner",
|
||||
"partner_page_title": "Partner",
|
||||
"partner_page_no_more_users": "No more users to add",
|
||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
||||
"partner_page_shared_to_title": "Shared to",
|
||||
"partner_page_select_partner": "Select partner",
|
||||
"partner_page_add_partner": "Add partner",
|
||||
"partner_page_partner_add_failed": "Failed to add partner",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"tab_controller_nav_library": "Library",
|
||||
"tab_controller_nav_photos": "Photos",
|
||||
"tab_controller_nav_search": "Search",
|
||||
|
||||
@@ -379,7 +379,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 95;
|
||||
CURRENT_PROJECT_VERSION = 97;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -515,7 +515,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 95;
|
||||
CURRENT_PROJECT_VERSION = 97;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -543,7 +543,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 95;
|
||||
CURRENT_PROJECT_VERSION = 97;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -45,11 +45,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.55.0</string>
|
||||
<string>1.57.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>95</string>
|
||||
<string>97</string>
|
||||
<key>FLTEnableImpeller</key>
|
||||
<true />
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
||||
@@ -19,7 +19,7 @@ platform :ios do
|
||||
desc "iOS Beta"
|
||||
lane :beta do
|
||||
increment_version_number(
|
||||
version_number: "1.57.0"
|
||||
version_number: "1.58.0"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -5,29 +5,32 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000282">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000407">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.815995">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.988375">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="30.927419">
|
||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="45.42439">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="1.464698">
|
||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="2.381359">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="4: build_app" time="66.988561">
|
||||
<testcase classname="fastlane.lanes" name="4: build_app" time="94.653021">
|
||||
|
||||
<failure message="/usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/actions/actions_helper.rb:67:in `execute_action' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/runner.rb:255:in `block in execute_action' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/runner.rb:229:in `chdir' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/runner.rb:229:in `execute_action' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/runner.rb:157:in `trigger_action_by_name' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/fast_file.rb:159:in `method_missing' Fastfile:27:in `block (2 levels) in parsing_binding' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/lane.rb:33:in `call' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/runner.rb:49:in `block in execute' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/runner.rb:45:in `chdir' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/runner.rb:45:in `execute' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/lane_manager.rb:47:in `cruise_lane' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/command_line_handler.rb:36:in `handle' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/commands_generator.rb:110:in `block (2 levels) in run' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in `call' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in `run' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in `run_active_command' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in `run!' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in `run!' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/commands_generator.rb:354:in `run' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/commands_generator.rb:43:in `start' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in `take_off' /usr/local/Cellar/fastlane/2.212.1/libexec/gems/fastlane-2.212.1/bin/fastlane:23:in `<top (required)>' /usr/local/Cellar/fastlane/2.212.1/libexec/bin/fastlane:25:in `load' /usr/local/Cellar/fastlane/2.212.1/libexec/bin/fastlane:25:in `<main>' Error packaging up the application" />
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="58.237354">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/etag.dart';
|
||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||
import 'package:immich_mobile/shared/models/logger_message.model.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
@@ -89,6 +90,7 @@ Future<Isar> loadDb() async {
|
||||
BackupAlbumSchema,
|
||||
DuplicatedAssetSchema,
|
||||
LoggerMessageSchema,
|
||||
ETagSchema,
|
||||
],
|
||||
directory: dir.path,
|
||||
maxSizeMiB: 256,
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
class SharedAlbumNotifier extends StateNotifier<List<Album>> {
|
||||
@@ -73,7 +74,9 @@ final sharedAlbumProvider =
|
||||
});
|
||||
|
||||
final sharedAlbumDetailProvider =
|
||||
StreamProvider.autoDispose.family<Album, int>((ref, albumId) async* {
|
||||
StreamProvider.family<Album, int>((ref, albumId) async* {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) return;
|
||||
final AlbumService sharedAlbumService = ref.watch(albumServiceProvider);
|
||||
|
||||
await for (final a in sharedAlbumService.watchAlbum(albumId)) {
|
||||
|
||||
@@ -2,8 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/services/user.service.dart';
|
||||
|
||||
final suggestedSharedUsersProvider =
|
||||
FutureProvider.autoDispose<List<User>>((ref) {
|
||||
final otherUsersProvider = FutureProvider.autoDispose<List<User>>((ref) {
|
||||
UserService userService = ref.watch(userServiceProvider);
|
||||
|
||||
return userService.getUsersInDb();
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
class SharingSliverAppBar extends StatelessWidget {
|
||||
const SharingSliverAppBar({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverAppBar(
|
||||
centerTitle: true,
|
||||
floating: false,
|
||||
pinned: true,
|
||||
snap: false,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Text(
|
||||
'IMMICH',
|
||||
style: TextStyle(
|
||||
fontFamily: 'SnowburstOne',
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(50.0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
AutoRouter.of(context)
|
||||
.push(CreateAlbumRoute(isSharedAlbum: true));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.photo_album_outlined,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text(
|
||||
"sharing_silver_appbar_create_shared_album",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 11,
|
||||
// color: Theme.of(context).primaryColor,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 4.0),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: null,
|
||||
icon: const Icon(
|
||||
Icons.swap_horizontal_circle_outlined,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text(
|
||||
"sharing_silver_appbar_share_partner",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 11,
|
||||
),
|
||||
maxLines: 1,
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structu
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
|
||||
class AssetSelectionPage extends HookConsumerWidget {
|
||||
const AssetSelectionPage({
|
||||
@@ -21,7 +22,8 @@ class AssetSelectionPage extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final renderList = ref.watch(remoteAssetsProvider);
|
||||
final currentUser = ref.watch(currentUserProvider);
|
||||
final renderList = ref.watch(remoteAssetsProvider(currentUser?.isarId));
|
||||
final selected = useState<Set<Asset>>(existingAssets);
|
||||
final selectionEnabledHook = useState(true);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final AsyncValue<List<User>> suggestedShareUsers =
|
||||
ref.watch(suggestedSharedUsersProvider);
|
||||
ref.watch(otherUsersProvider);
|
||||
final sharedUsersList = useState<Set<User>>({});
|
||||
|
||||
addNewUsersHandler() {
|
||||
|
||||
@@ -20,8 +20,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final sharedUsersList = useState<Set<User>>({});
|
||||
AsyncValue<List<User>> suggestedShareUsers =
|
||||
ref.watch(suggestedSharedUsersProvider);
|
||||
final suggestedShareUsers = ref.watch(otherUsersProvider);
|
||||
|
||||
createSharedAlbum() async {
|
||||
var newAlbum =
|
||||
|
||||
@@ -5,10 +5,11 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/album_thumbnail_card.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/sharing_sliver_appbar.dart';
|
||||
import 'package:immich_mobile/modules/partner/providers/partner.provider.dart';
|
||||
import 'package:immich_mobile/modules/partner/ui/partner_list.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart' as store;
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||
|
||||
class SharingPage extends HookConsumerWidget {
|
||||
@@ -17,7 +18,8 @@ class SharingPage extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final List<Album> sharedAlbums = ref.watch(sharedAlbumProvider);
|
||||
final userId = store.Store.get(store.StoreKey.currentUser).id;
|
||||
final userId = ref.watch(currentUserProvider)?.id;
|
||||
final partner = ref.watch(partnerSharedWithProvider);
|
||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
useEffect(
|
||||
@@ -63,8 +65,7 @@ class SharingPage extends HookConsumerWidget {
|
||||
final isOwner = album.ownerId == userId;
|
||||
|
||||
return ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: ImmichImage(
|
||||
@@ -93,7 +94,8 @@ class SharingPage extends HookConsumerWidget {
|
||||
)
|
||||
: album.ownerName != null
|
||||
? Text(
|
||||
'album_thumbnail_shared_by'.tr(args: [album.ownerName!]),
|
||||
'album_thumbnail_shared_by'
|
||||
.tr(args: [album.ownerName!]),
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
),
|
||||
@@ -110,6 +112,75 @@ class SharingPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
buildTopBottons() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
right: 12.0,
|
||||
bottom: 12.0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
AutoRouter.of(context)
|
||||
.push(CreateAlbumRoute(isSharedAlbum: true));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.photo_album_outlined,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text(
|
||||
"sharing_silver_appbar_create_shared_album",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 11,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12.0),
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () =>
|
||||
AutoRouter.of(context).push(const PartnerRoute()),
|
||||
icon: const Icon(
|
||||
Icons.swap_horizontal_circle_outlined,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text(
|
||||
"sharing_silver_appbar_share_partner",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 11,
|
||||
),
|
||||
maxLines: 1,
|
||||
).tr(),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
AppBar buildAppBar() {
|
||||
return AppBar(
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
title: const Text(
|
||||
'IMMICH',
|
||||
style: TextStyle(
|
||||
fontFamily: 'SnowburstOne',
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
buildEmptyListIndication() {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
@@ -123,7 +194,6 @@ class SharingPage extends HookConsumerWidget {
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
// color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
child: Column(
|
||||
@@ -160,11 +230,27 @@ class SharingPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: buildAppBar(),
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
const SharingSliverAppBar(),
|
||||
SliverToBoxAdapter(child: buildTopBottons()),
|
||||
if (partner.isNotEmpty)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, bottom: 4),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: const Text(
|
||||
"partner_page_title",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
if (partner.isNotEmpty) PartnerList(partner: partner),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
padding: EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
top: partner.isEmpty ? 0 : 16,
|
||||
),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: const Text(
|
||||
"sharing_page_album",
|
||||
|
||||
@@ -3,16 +3,18 @@ import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structu
|
||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
final archiveProvider = StreamProvider<RenderList>((ref) async* {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) return;
|
||||
final query = ref
|
||||
.watch(dbProvider)
|
||||
.assets
|
||||
.filter()
|
||||
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
||||
.ownerIdEqualTo(user.isarId)
|
||||
.isArchivedEqualTo(true)
|
||||
.sortByFileCreatedAt();
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
|
||||
@@ -4,9 +4,9 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/asset_description.provider.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart' as store;
|
||||
|
||||
class DescriptionInput extends HookConsumerWidget {
|
||||
DescriptionInput({
|
||||
@@ -25,9 +25,10 @@ class DescriptionInput extends HookConsumerWidget {
|
||||
final focusNode = useFocusNode();
|
||||
final isFocus = useState(false);
|
||||
final isTextEmpty = useState(controller.text.isEmpty);
|
||||
final descriptionProvider = ref.watch(assetDescriptionProvider(asset).notifier);
|
||||
final descriptionProvider =
|
||||
ref.watch(assetDescriptionProvider(asset).notifier);
|
||||
final description = ref.watch(assetDescriptionProvider(asset));
|
||||
final owner = store.Store.get(store.StoreKey.currentUser);
|
||||
final owner = ref.watch(currentUserProvider);
|
||||
final hasError = useState(false);
|
||||
|
||||
controller.text = description;
|
||||
@@ -67,7 +68,7 @@ class DescriptionInput extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return TextField(
|
||||
enabled: owner.isarId == asset.ownerId,
|
||||
enabled: owner?.isarId == asset.ownerId,
|
||||
focusNode: focusNode,
|
||||
onTap: () => isFocus.value = true,
|
||||
onChanged: (value) {
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'backup_album.model.dart';
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetBackupAlbumCollection on Isar {
|
||||
IsarCollection<BackupAlbum> get backupAlbums => this.collection();
|
||||
@@ -45,7 +45,7 @@ const BackupAlbumSchema = CollectionSchema(
|
||||
getId: _backupAlbumGetId,
|
||||
getLinks: _backupAlbumGetLinks,
|
||||
attach: _backupAlbumAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _backupAlbumEstimateSize(
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'duplicated_asset.model.dart';
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetDuplicatedAssetCollection on Isar {
|
||||
IsarCollection<DuplicatedAsset> get duplicatedAssets => this.collection();
|
||||
@@ -34,7 +34,7 @@ const DuplicatedAssetSchema = CollectionSchema(
|
||||
getId: _duplicatedAssetGetId,
|
||||
getLinks: _duplicatedAssetGetLinks,
|
||||
attach: _duplicatedAssetAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _duplicatedAssetEstimateSize(
|
||||
|
||||
@@ -3,16 +3,18 @@ import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structu
|
||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
final favoriteAssetsProvider = StreamProvider<RenderList>((ref) async* {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) return;
|
||||
final query = ref
|
||||
.watch(dbProvider)
|
||||
.assets
|
||||
.filter()
|
||||
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
||||
.ownerIdEqualTo(user.isarId)
|
||||
.isFavoriteEqualTo(true)
|
||||
.sortByFileCreatedAt();
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
|
||||
@@ -1,47 +1,16 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
|
||||
|
||||
class DeleteDialog extends ConsumerWidget {
|
||||
class DeleteDialog extends ConfirmDialog {
|
||||
final Function onDelete;
|
||||
|
||||
const DeleteDialog({Key? key, required this.onDelete}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
|
||||
return AlertDialog(
|
||||
// backgroundColor: Colors.grey[200],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
title: const Text("delete_dialog_title").tr(),
|
||||
content: const Text("delete_dialog_alert").tr(),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
"delete_dialog_cancel",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
onDelete();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
"delete_dialog_ok",
|
||||
style: TextStyle(
|
||||
color: Colors.red[400],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
const DeleteDialog({Key? key, required this.onDelete})
|
||||
: super(
|
||||
key: key,
|
||||
title: "delete_dialog_title",
|
||||
content: "delete_dialog_alert",
|
||||
cancel: "delete_dialog_cancel",
|
||||
ok: "delete_dialog_ok",
|
||||
onOk: onDelete,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/share.service.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
@@ -38,6 +39,7 @@ class HomePage extends HookConsumerWidget {
|
||||
final albums = ref.watch(albumProvider).where((a) => a.isRemote).toList();
|
||||
final sharedAlbums = ref.watch(sharedAlbumProvider);
|
||||
final albumService = ref.watch(albumServiceProvider);
|
||||
final currentUser = ref.watch(currentUserProvider);
|
||||
|
||||
final tipOneOpacity = useState(0.0);
|
||||
final refreshCount = useState(0);
|
||||
@@ -300,7 +302,7 @@ class HomePage extends HookConsumerWidget {
|
||||
bottom: false,
|
||||
child: Stack(
|
||||
children: [
|
||||
ref.watch(assetsProvider).when(
|
||||
ref.watch(assetsProvider(currentUser?.isarId)).when(
|
||||
data: (data) => data.isEmpty
|
||||
? buildLoadingIndicator()
|
||||
: ImmichAssetGrid(
|
||||
|
||||
50
mobile/lib/modules/partner/providers/partner.provider.dart
Normal file
50
mobile/lib/modules/partner/providers/partner.provider.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
class PartnerSharedWithNotifier extends StateNotifier<List<User>> {
|
||||
PartnerSharedWithNotifier(Isar db) : super([]) {
|
||||
final query = db.users.filter().isPartnerSharedWithEqualTo(true);
|
||||
query.findAll().then((partners) => state = partners);
|
||||
query.watch().listen((partners) => state = partners);
|
||||
}
|
||||
}
|
||||
|
||||
final partnerSharedWithProvider =
|
||||
StateNotifierProvider<PartnerSharedWithNotifier, List<User>>((ref) {
|
||||
return PartnerSharedWithNotifier(ref.watch(dbProvider));
|
||||
});
|
||||
|
||||
class PartnerSharedByNotifier extends StateNotifier<List<User>> {
|
||||
PartnerSharedByNotifier(Isar db) : super([]) {
|
||||
final query = db.users.filter().isPartnerSharedByEqualTo(true);
|
||||
query.findAll().then((partners) => state = partners);
|
||||
streamSub = query.watch().listen((partners) => state = partners);
|
||||
}
|
||||
|
||||
late final StreamSubscription<List<User>> streamSub;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
streamSub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
final partnerSharedByProvider =
|
||||
StateNotifierProvider<PartnerSharedByNotifier, List<User>>((ref) {
|
||||
return PartnerSharedByNotifier(ref.watch(dbProvider));
|
||||
});
|
||||
|
||||
final partnerAvailableProvider =
|
||||
FutureProvider.autoDispose<List<User>>((ref) async {
|
||||
final otherUsers = await ref.watch(otherUsersProvider.future);
|
||||
final currentPartners = ref.watch(partnerSharedByProvider);
|
||||
final available = Set<User>.of(otherUsers);
|
||||
available.removeAll(currentPartners);
|
||||
return available.toList();
|
||||
});
|
||||
72
mobile/lib/modules/partner/services/partner.service.dart
Normal file
72
mobile/lib/modules/partner/services/partner.service.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
final partnerServiceProvider = Provider(
|
||||
(ref) => PartnerService(
|
||||
ref.watch(apiServiceProvider),
|
||||
ref.watch(dbProvider),
|
||||
),
|
||||
);
|
||||
|
||||
enum PartnerDirection {
|
||||
sharedWith("shared-with"),
|
||||
sharedBy("shared-by");
|
||||
|
||||
const PartnerDirection(
|
||||
this._value,
|
||||
);
|
||||
|
||||
final String _value;
|
||||
}
|
||||
|
||||
class PartnerService {
|
||||
final ApiService _apiService;
|
||||
final Isar _db;
|
||||
final Logger _log = Logger("PartnerService");
|
||||
|
||||
PartnerService(this._apiService, this._db);
|
||||
|
||||
Future<List<User>?> getPartners(PartnerDirection direction) async {
|
||||
try {
|
||||
final userDtos =
|
||||
await _apiService.partnerApi.getPartners(direction._value);
|
||||
if (userDtos != null) {
|
||||
return userDtos.map((u) => User.fromDto(u)).toList();
|
||||
}
|
||||
} catch (e) {
|
||||
_log.warning("failed to get partners for direction $direction:\n$e");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<bool> removePartner(User partner) async {
|
||||
try {
|
||||
await _apiService.partnerApi.removePartner(partner.id);
|
||||
partner.isPartnerSharedBy = false;
|
||||
await _db.writeTxn(() => _db.users.put(partner));
|
||||
} catch (e) {
|
||||
_log.warning("failed to remove partner ${partner.id}:\n$e");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> addPartner(User partner) async {
|
||||
try {
|
||||
final dto = await _apiService.partnerApi.createPartner(partner.id);
|
||||
if (dto != null) {
|
||||
partner.isPartnerSharedBy = true;
|
||||
await _db.writeTxn(() => _db.users.put(partner));
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
_log.warning("failed to add partner ${partner.id}:\n$e");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
30
mobile/lib/modules/partner/ui/partner_list.dart
Normal file
30
mobile/lib/modules/partner/ui/partner_list.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/ui/user_avatar.dart';
|
||||
|
||||
class PartnerList extends HookConsumerWidget {
|
||||
const PartnerList({Key? key, required this.partner}) : super(key: key);
|
||||
|
||||
final List<User> partner;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return SliverList(
|
||||
delegate:
|
||||
SliverChildBuilderDelegate(listEntry, childCount: partner.length),
|
||||
);
|
||||
}
|
||||
|
||||
Widget listEntry(BuildContext context, int index) {
|
||||
final User p = partner[index];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
leading: userAvatar(context, p, radius: 30),
|
||||
title: Text("${p.firstName} ${p.lastName}"),
|
||||
onTap: () => AutoRouter.of(context).push(PartnerDetailRoute(partner: p)),
|
||||
);
|
||||
}
|
||||
}
|
||||
40
mobile/lib/modules/partner/views/partner_detail_page.dart
Normal file
40
mobile/lib/modules/partner/views/partner_detail_page.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
|
||||
class PartnerDetailPage extends HookConsumerWidget {
|
||||
const PartnerDetailPage({Key? key, required this.partner}) : super(key: key);
|
||||
|
||||
final User partner;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final assets = ref.watch(assetsProvider(partner.isarId));
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("${partner.firstName} ${partner.lastName}"),
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
),
|
||||
body: assets.when(
|
||||
data: (renderList) => renderList.isEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
"It seems ${partner.firstName} does not have any photos...\n"
|
||||
"Or your server version does not match the app version."),
|
||||
)
|
||||
: ImmichAssetGrid(
|
||||
renderList: renderList,
|
||||
onRefresh: () => ref.read(assetProvider.notifier).getAllAsset(),
|
||||
),
|
||||
error: (e, _) => Text("Error loading partners:\n$e"),
|
||||
loading: () => const Center(child: ImmichLoadingIndicator()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
160
mobile/lib/modules/partner/views/partner_page.dart
Normal file
160
mobile/lib/modules/partner/views/partner_page.dart
Normal file
@@ -0,0 +1,160 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/partner/providers/partner.provider.dart';
|
||||
import 'package:immich_mobile/modules/partner/services/partner.service.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
import 'package:immich_mobile/shared/ui/user_avatar.dart';
|
||||
|
||||
class PartnerPage extends HookConsumerWidget {
|
||||
const PartnerPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final List<User> partners = ref.watch(partnerSharedByProvider);
|
||||
final availableUsers = ref.watch(partnerAvailableProvider);
|
||||
|
||||
addNewUsersHandler() async {
|
||||
final users = availableUsers.value;
|
||||
if (users == null || users.isEmpty) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "partner_page_no_more_users".tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final selectedUser = await showDialog<User>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
title: const Text("partner_page_select_partner").tr(),
|
||||
children: [
|
||||
for (User u in users)
|
||||
SimpleDialogOption(
|
||||
onPressed: () => Navigator.pop(context, u),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: userAvatar(context, u),
|
||||
),
|
||||
Text("${u.firstName} ${u.lastName}"),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
if (selectedUser != null) {
|
||||
final ok =
|
||||
await ref.read(partnerServiceProvider).addPartner(selectedUser);
|
||||
if (ok) {
|
||||
ref.invalidate(partnerSharedByProvider);
|
||||
} else {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "partner_page_partner_add_failed".tr(),
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDeleteUser(User u) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return ConfirmDialog(
|
||||
title: "partner_page_stop_sharing_title",
|
||||
content:
|
||||
"partner_page_stop_sharing_content".tr(args: [u.firstName]),
|
||||
onOk: () => ref.read(partnerServiceProvider).removePartner(u),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
buildUserList(List<User> users) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
|
||||
child: const Text(
|
||||
"partner_page_shared_to_title",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
if (users.isNotEmpty)
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: users.length,
|
||||
itemBuilder: ((context, index) {
|
||||
return ListTile(
|
||||
leading: userAvatar(context, users[index]),
|
||||
title: Text(
|
||||
users[index].email,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.person_remove),
|
||||
onPressed: () => onDeleteUser(users[index]),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
if (users.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: const Text(
|
||||
"partner_page_empty_message",
|
||||
style: TextStyle(fontSize: 14),
|
||||
).tr(),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: availableUsers.whenOrNull(
|
||||
data: (data) => addNewUsersHandler,
|
||||
),
|
||||
icon: const Icon(Icons.person_add),
|
||||
label: const Text("partner_page_add_partner").tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("partner_page_title").tr(),
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed:
|
||||
availableUsers.whenOrNull(data: (data) => addNewUsersHandler),
|
||||
icon: const Icon(Icons.person_add),
|
||||
tooltip: "partner_page_add_partner".tr(),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: buildUserList(partners),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import 'package:immich_mobile/modules/album/views/album_viewer_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/asset_selection_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/create_album_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/library_page.dart';
|
||||
import 'package:immich_mobile/modules/partner/views/partner_detail_page.dart';
|
||||
import 'package:immich_mobile/modules/partner/views/partner_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/select_additional_user_for_sharing_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/select_user_for_sharing_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/sharing_page.dart';
|
||||
@@ -35,6 +37,7 @@ import 'package:immich_mobile/routing/duplicate_guard.dart';
|
||||
import 'package:immich_mobile/routing/gallery_permission_guard.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/models/logger_message.model.dart';
|
||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
@@ -136,6 +139,8 @@ part 'router.gr.dart';
|
||||
DuplicateGuard,
|
||||
],
|
||||
),
|
||||
AutoRoute(page: PartnerPage, guards: [AuthGuard, DuplicateGuard]),
|
||||
AutoRoute(page: PartnerDetailPage, guards: [AuthGuard, DuplicateGuard])
|
||||
],
|
||||
)
|
||||
class AppRouter extends _$AppRouter {
|
||||
|
||||
@@ -256,6 +256,22 @@ class _$AppRouter extends RootStackRouter {
|
||||
child: const ArchivePage(),
|
||||
);
|
||||
},
|
||||
PartnerRoute.name: (routeData) {
|
||||
return MaterialPageX<dynamic>(
|
||||
routeData: routeData,
|
||||
child: const PartnerPage(),
|
||||
);
|
||||
},
|
||||
PartnerDetailRoute.name: (routeData) {
|
||||
final args = routeData.argsAs<PartnerDetailRouteArgs>();
|
||||
return MaterialPageX<dynamic>(
|
||||
routeData: routeData,
|
||||
child: PartnerDetailPage(
|
||||
key: args.key,
|
||||
partner: args.partner,
|
||||
),
|
||||
);
|
||||
},
|
||||
HomeRoute.name: (routeData) {
|
||||
return MaterialPageX<dynamic>(
|
||||
routeData: routeData,
|
||||
@@ -523,6 +539,22 @@ class _$AppRouter extends RootStackRouter {
|
||||
duplicateGuard,
|
||||
],
|
||||
),
|
||||
RouteConfig(
|
||||
PartnerRoute.name,
|
||||
path: '/partner-page',
|
||||
guards: [
|
||||
authGuard,
|
||||
duplicateGuard,
|
||||
],
|
||||
),
|
||||
RouteConfig(
|
||||
PartnerDetailRoute.name,
|
||||
path: '/partner-detail-page',
|
||||
guards: [
|
||||
authGuard,
|
||||
duplicateGuard,
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1113,6 +1145,52 @@ class ArchiveRoute extends PageRouteInfo<void> {
|
||||
static const String name = 'ArchiveRoute';
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [PartnerPage]
|
||||
class PartnerRoute extends PageRouteInfo<void> {
|
||||
const PartnerRoute()
|
||||
: super(
|
||||
PartnerRoute.name,
|
||||
path: '/partner-page',
|
||||
);
|
||||
|
||||
static const String name = 'PartnerRoute';
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [PartnerDetailPage]
|
||||
class PartnerDetailRoute extends PageRouteInfo<PartnerDetailRouteArgs> {
|
||||
PartnerDetailRoute({
|
||||
Key? key,
|
||||
required User partner,
|
||||
}) : super(
|
||||
PartnerDetailRoute.name,
|
||||
path: '/partner-detail-page',
|
||||
args: PartnerDetailRouteArgs(
|
||||
key: key,
|
||||
partner: partner,
|
||||
),
|
||||
);
|
||||
|
||||
static const String name = 'PartnerDetailRoute';
|
||||
}
|
||||
|
||||
class PartnerDetailRouteArgs {
|
||||
const PartnerDetailRouteArgs({
|
||||
this.key,
|
||||
required this.partner,
|
||||
});
|
||||
|
||||
final Key? key;
|
||||
|
||||
final User partner;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PartnerDetailRouteArgs{key: $key, partner: $partner}';
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [HomePage]
|
||||
class HomeRoute extends PageRouteInfo<void> {
|
||||
|
||||
@@ -87,8 +87,8 @@ class Album {
|
||||
remoteId == other.remoteId &&
|
||||
localId == other.localId &&
|
||||
name == other.name &&
|
||||
createdAt == other.createdAt &&
|
||||
modifiedAt == other.modifiedAt &&
|
||||
createdAt.isAtSameMomentAs(other.createdAt) &&
|
||||
modifiedAt.isAtSameMomentAs(other.modifiedAt) &&
|
||||
shared == other.shared &&
|
||||
owner.value == other.owner.value &&
|
||||
thumbnail.value == other.thumbnail.value &&
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'album.dart';
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetAlbumCollection on Isar {
|
||||
IsarCollection<Album> get albums => this.collection();
|
||||
@@ -111,7 +111,7 @@ const AlbumSchema = CollectionSchema(
|
||||
getId: _albumGetId,
|
||||
getLinks: _albumGetLinks,
|
||||
attach: _albumAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _albumEstimateSize(
|
||||
|
||||
@@ -179,9 +179,9 @@ class Asset {
|
||||
localId == other.localId &&
|
||||
deviceId == other.deviceId &&
|
||||
ownerId == other.ownerId &&
|
||||
fileCreatedAt == other.fileCreatedAt &&
|
||||
fileModifiedAt == other.fileModifiedAt &&
|
||||
updatedAt == other.updatedAt &&
|
||||
fileCreatedAt.isAtSameMomentAs(other.fileCreatedAt) &&
|
||||
fileModifiedAt.isAtSameMomentAs(other.fileModifiedAt) &&
|
||||
updatedAt.isAtSameMomentAs(other.updatedAt) &&
|
||||
durationInSeconds == other.durationInSeconds &&
|
||||
type == other.type &&
|
||||
width == other.width &&
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'asset.dart';
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetAssetCollection on Isar {
|
||||
IsarCollection<Asset> get assets => this.collection();
|
||||
@@ -142,7 +142,7 @@ const AssetSchema = CollectionSchema(
|
||||
getId: _assetGetId,
|
||||
getLinks: _assetGetLinks,
|
||||
attach: _assetAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _assetEstimateSize(
|
||||
|
||||
13
mobile/lib/shared/models/etag.dart
Normal file
13
mobile/lib/shared/models/etag.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:immich_mobile/utils/hash.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'etag.g.dart';
|
||||
|
||||
@Collection(inheritance: false)
|
||||
class ETag {
|
||||
ETag({required this.id, this.value});
|
||||
Id get isarId => fastHash(id);
|
||||
@Index(unique: true, replace: true, type: IndexType.hash)
|
||||
String id;
|
||||
String? value;
|
||||
}
|
||||
724
mobile/lib/shared/models/etag.g.dart
Normal file
724
mobile/lib/shared/models/etag.g.dart
Normal file
@@ -0,0 +1,724 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'etag.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// IsarCollectionGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetETagCollection on Isar {
|
||||
IsarCollection<ETag> get eTags => this.collection();
|
||||
}
|
||||
|
||||
const ETagSchema = CollectionSchema(
|
||||
name: r'ETag',
|
||||
id: -644290296585643859,
|
||||
properties: {
|
||||
r'id': PropertySchema(
|
||||
id: 0,
|
||||
name: r'id',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'value': PropertySchema(
|
||||
id: 1,
|
||||
name: r'value',
|
||||
type: IsarType.string,
|
||||
)
|
||||
},
|
||||
estimateSize: _eTagEstimateSize,
|
||||
serialize: _eTagSerialize,
|
||||
deserialize: _eTagDeserialize,
|
||||
deserializeProp: _eTagDeserializeProp,
|
||||
idName: r'isarId',
|
||||
indexes: {
|
||||
r'id': IndexSchema(
|
||||
id: -3268401673993471357,
|
||||
name: r'id',
|
||||
unique: true,
|
||||
replace: true,
|
||||
properties: [
|
||||
IndexPropertySchema(
|
||||
name: r'id',
|
||||
type: IndexType.hash,
|
||||
caseSensitive: true,
|
||||
)
|
||||
],
|
||||
)
|
||||
},
|
||||
links: {},
|
||||
embeddedSchemas: {},
|
||||
getId: _eTagGetId,
|
||||
getLinks: _eTagGetLinks,
|
||||
attach: _eTagAttach,
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _eTagEstimateSize(
|
||||
ETag object,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
var bytesCount = offsets.last;
|
||||
bytesCount += 3 + object.id.length * 3;
|
||||
{
|
||||
final value = object.value;
|
||||
if (value != null) {
|
||||
bytesCount += 3 + value.length * 3;
|
||||
}
|
||||
}
|
||||
return bytesCount;
|
||||
}
|
||||
|
||||
void _eTagSerialize(
|
||||
ETag object,
|
||||
IsarWriter writer,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
writer.writeString(offsets[0], object.id);
|
||||
writer.writeString(offsets[1], object.value);
|
||||
}
|
||||
|
||||
ETag _eTagDeserialize(
|
||||
Id id,
|
||||
IsarReader reader,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
final object = ETag(
|
||||
id: reader.readString(offsets[0]),
|
||||
value: reader.readStringOrNull(offsets[1]),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
||||
P _eTagDeserializeProp<P>(
|
||||
IsarReader reader,
|
||||
int propertyId,
|
||||
int offset,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
switch (propertyId) {
|
||||
case 0:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 1:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
}
|
||||
}
|
||||
|
||||
Id _eTagGetId(ETag object) {
|
||||
return object.isarId;
|
||||
}
|
||||
|
||||
List<IsarLinkBase<dynamic>> _eTagGetLinks(ETag object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
void _eTagAttach(IsarCollection<dynamic> col, Id id, ETag object) {}
|
||||
|
||||
extension ETagByIndex on IsarCollection<ETag> {
|
||||
Future<ETag?> getById(String id) {
|
||||
return getByIndex(r'id', [id]);
|
||||
}
|
||||
|
||||
ETag? getByIdSync(String id) {
|
||||
return getByIndexSync(r'id', [id]);
|
||||
}
|
||||
|
||||
Future<bool> deleteById(String id) {
|
||||
return deleteByIndex(r'id', [id]);
|
||||
}
|
||||
|
||||
bool deleteByIdSync(String id) {
|
||||
return deleteByIndexSync(r'id', [id]);
|
||||
}
|
||||
|
||||
Future<List<ETag?>> getAllById(List<String> idValues) {
|
||||
final values = idValues.map((e) => [e]).toList();
|
||||
return getAllByIndex(r'id', values);
|
||||
}
|
||||
|
||||
List<ETag?> getAllByIdSync(List<String> idValues) {
|
||||
final values = idValues.map((e) => [e]).toList();
|
||||
return getAllByIndexSync(r'id', values);
|
||||
}
|
||||
|
||||
Future<int> deleteAllById(List<String> idValues) {
|
||||
final values = idValues.map((e) => [e]).toList();
|
||||
return deleteAllByIndex(r'id', values);
|
||||
}
|
||||
|
||||
int deleteAllByIdSync(List<String> idValues) {
|
||||
final values = idValues.map((e) => [e]).toList();
|
||||
return deleteAllByIndexSync(r'id', values);
|
||||
}
|
||||
|
||||
Future<Id> putById(ETag object) {
|
||||
return putByIndex(r'id', object);
|
||||
}
|
||||
|
||||
Id putByIdSync(ETag object, {bool saveLinks = true}) {
|
||||
return putByIndexSync(r'id', object, saveLinks: saveLinks);
|
||||
}
|
||||
|
||||
Future<List<Id>> putAllById(List<ETag> objects) {
|
||||
return putAllByIndex(r'id', objects);
|
||||
}
|
||||
|
||||
List<Id> putAllByIdSync(List<ETag> objects, {bool saveLinks = true}) {
|
||||
return putAllByIndexSync(r'id', objects, saveLinks: saveLinks);
|
||||
}
|
||||
}
|
||||
|
||||
extension ETagQueryWhereSort on QueryBuilder<ETag, ETag, QWhere> {
|
||||
QueryBuilder<ETag, ETag, QAfterWhere> anyIsarId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(const IdWhereClause.any());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension ETagQueryWhere on QueryBuilder<ETag, ETag, QWhereClause> {
|
||||
QueryBuilder<ETag, ETag, QAfterWhereClause> isarIdEqualTo(Id isarId) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IdWhereClause.between(
|
||||
lower: isarId,
|
||||
upper: isarId,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterWhereClause> isarIdNotEqualTo(Id isarId) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
if (query.whereSort == Sort.asc) {
|
||||
return query
|
||||
.addWhereClause(
|
||||
IdWhereClause.lessThan(upper: isarId, includeUpper: false),
|
||||
)
|
||||
.addWhereClause(
|
||||
IdWhereClause.greaterThan(lower: isarId, includeLower: false),
|
||||
);
|
||||
} else {
|
||||
return query
|
||||
.addWhereClause(
|
||||
IdWhereClause.greaterThan(lower: isarId, includeLower: false),
|
||||
)
|
||||
.addWhereClause(
|
||||
IdWhereClause.lessThan(upper: isarId, includeUpper: false),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterWhereClause> isarIdGreaterThan(Id isarId,
|
||||
{bool include = false}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(
|
||||
IdWhereClause.greaterThan(lower: isarId, includeLower: include),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterWhereClause> isarIdLessThan(Id isarId,
|
||||
{bool include = false}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(
|
||||
IdWhereClause.lessThan(upper: isarId, includeUpper: include),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterWhereClause> isarIdBetween(
|
||||
Id lowerIsarId,
|
||||
Id upperIsarId, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IdWhereClause.between(
|
||||
lower: lowerIsarId,
|
||||
includeLower: includeLower,
|
||||
upper: upperIsarId,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterWhereClause> idEqualTo(String id) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||
indexName: r'id',
|
||||
value: [id],
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterWhereClause> idNotEqualTo(String id) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
if (query.whereSort == Sort.asc) {
|
||||
return query
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'id',
|
||||
lower: [],
|
||||
upper: [id],
|
||||
includeUpper: false,
|
||||
))
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'id',
|
||||
lower: [id],
|
||||
includeLower: false,
|
||||
upper: [],
|
||||
));
|
||||
} else {
|
||||
return query
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'id',
|
||||
lower: [id],
|
||||
includeLower: false,
|
||||
upper: [],
|
||||
))
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'id',
|
||||
lower: [],
|
||||
upper: [id],
|
||||
includeUpper: false,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension ETagQueryFilter on QueryBuilder<ETag, ETag, QFilterCondition> {
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idEqualTo(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'id',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idGreaterThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'id',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idLessThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'id',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idBetween(
|
||||
String lower,
|
||||
String upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'id',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'id',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'id',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idContains(String value,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'id',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idMatches(String pattern,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'id',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'id',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> idIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'id',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> isarIdEqualTo(Id value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'isarId',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> isarIdGreaterThan(
|
||||
Id value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'isarId',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> isarIdLessThan(
|
||||
Id value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'isarId',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> isarIdBetween(
|
||||
Id lower,
|
||||
Id upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'isarId',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'value',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'value',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueEqualTo(
|
||||
String? value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'value',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueGreaterThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'value',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueLessThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'value',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueBetween(
|
||||
String? lower,
|
||||
String? upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'value',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'value',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'value',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueContains(String value,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'value',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueMatches(String pattern,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'value',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'value',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterFilterCondition> valueIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'value',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension ETagQueryObject on QueryBuilder<ETag, ETag, QFilterCondition> {}
|
||||
|
||||
extension ETagQueryLinks on QueryBuilder<ETag, ETag, QFilterCondition> {}
|
||||
|
||||
extension ETagQuerySortBy on QueryBuilder<ETag, ETag, QSortBy> {
|
||||
QueryBuilder<ETag, ETag, QAfterSortBy> sortById() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'id', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterSortBy> sortByIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'id', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterSortBy> sortByValue() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'value', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterSortBy> sortByValueDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'value', Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension ETagQuerySortThenBy on QueryBuilder<ETag, ETag, QSortThenBy> {
|
||||
QueryBuilder<ETag, ETag, QAfterSortBy> thenById() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'id', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterSortBy> thenByIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'id', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterSortBy> thenByIsarId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isarId', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterSortBy> thenByIsarIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isarId', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterSortBy> thenByValue() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'value', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QAfterSortBy> thenByValueDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'value', Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension ETagQueryWhereDistinct on QueryBuilder<ETag, ETag, QDistinct> {
|
||||
QueryBuilder<ETag, ETag, QDistinct> distinctById(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'id', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, ETag, QDistinct> distinctByValue(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'value', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension ETagQueryProperty on QueryBuilder<ETag, ETag, QQueryProperty> {
|
||||
QueryBuilder<ETag, int, QQueryOperations> isarIdProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'isarId');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, String, QQueryOperations> idProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'id');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<ETag, String?, QQueryOperations> valueProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'value');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ part of 'exif_info.dart';
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetExifInfoCollection on Isar {
|
||||
IsarCollection<ExifInfo> get exifInfos => this.collection();
|
||||
@@ -99,7 +99,7 @@ const ExifInfoSchema = CollectionSchema(
|
||||
getId: _exifInfoGetId,
|
||||
getLinks: _exifInfoGetLinks,
|
||||
attach: _exifInfoAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _exifInfoEstimateSize(
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'logger_message.model.dart';
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetLoggerMessageCollection on Isar {
|
||||
IsarCollection<LoggerMessage> get loggerMessages => this.collection();
|
||||
@@ -55,7 +55,7 @@ const LoggerMessageSchema = CollectionSchema(
|
||||
getId: _loggerMessageGetId,
|
||||
getLinks: _loggerMessageGetLinks,
|
||||
attach: _loggerMessageAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _loggerMessageEstimateSize(
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'store.dart';
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetStoreValueCollection on Isar {
|
||||
IsarCollection<StoreValue> get storeValues => this.collection();
|
||||
@@ -39,7 +39,7 @@ const StoreValueSchema = CollectionSchema(
|
||||
getId: _storeValueGetId,
|
||||
getLinks: _storeValueGetLinks,
|
||||
attach: _storeValueAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _storeValueEstimateSize(
|
||||
|
||||
@@ -14,6 +14,8 @@ class User {
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.isAdmin,
|
||||
this.isPartnerSharedBy = false,
|
||||
this.isPartnerSharedWith = false,
|
||||
});
|
||||
|
||||
Id get isarId => fastHash(id);
|
||||
@@ -26,6 +28,8 @@ class User {
|
||||
email = dto.email,
|
||||
firstName = dto.firstName,
|
||||
lastName = dto.lastName,
|
||||
isPartnerSharedBy = false,
|
||||
isPartnerSharedWith = false,
|
||||
isAdmin = dto.isAdmin;
|
||||
|
||||
@Index(unique: true, replace: false, type: IndexType.hash)
|
||||
@@ -34,6 +38,8 @@ class User {
|
||||
String email;
|
||||
String firstName;
|
||||
String lastName;
|
||||
bool isPartnerSharedBy;
|
||||
bool isPartnerSharedWith;
|
||||
bool isAdmin;
|
||||
@Backlink(to: 'owner')
|
||||
final IsarLinks<Album> albums = IsarLinks<Album>();
|
||||
@@ -44,10 +50,12 @@ class User {
|
||||
bool operator ==(other) {
|
||||
if (other is! User) return false;
|
||||
return id == other.id &&
|
||||
updatedAt == other.updatedAt &&
|
||||
updatedAt.isAtSameMomentAs(other.updatedAt) &&
|
||||
email == other.email &&
|
||||
firstName == other.firstName &&
|
||||
lastName == other.lastName &&
|
||||
isPartnerSharedBy == other.isPartnerSharedBy &&
|
||||
isPartnerSharedWith == other.isPartnerSharedWith &&
|
||||
isAdmin == other.isAdmin;
|
||||
}
|
||||
|
||||
@@ -59,5 +67,7 @@ class User {
|
||||
email.hashCode ^
|
||||
firstName.hashCode ^
|
||||
lastName.hashCode ^
|
||||
isPartnerSharedBy.hashCode ^
|
||||
isPartnerSharedWith.hashCode ^
|
||||
isAdmin.hashCode;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'user.dart';
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetUserCollection on Isar {
|
||||
IsarCollection<User> get users => this.collection();
|
||||
@@ -37,13 +37,23 @@ const UserSchema = CollectionSchema(
|
||||
name: r'isAdmin',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'lastName': PropertySchema(
|
||||
r'isPartnerSharedBy': PropertySchema(
|
||||
id: 4,
|
||||
name: r'isPartnerSharedBy',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'isPartnerSharedWith': PropertySchema(
|
||||
id: 5,
|
||||
name: r'isPartnerSharedWith',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'lastName': PropertySchema(
|
||||
id: 6,
|
||||
name: r'lastName',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'updatedAt': PropertySchema(
|
||||
id: 5,
|
||||
id: 7,
|
||||
name: r'updatedAt',
|
||||
type: IsarType.dateTime,
|
||||
)
|
||||
@@ -88,7 +98,7 @@ const UserSchema = CollectionSchema(
|
||||
getId: _userGetId,
|
||||
getLinks: _userGetLinks,
|
||||
attach: _userAttach,
|
||||
version: '3.0.5',
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _userEstimateSize(
|
||||
@@ -114,8 +124,10 @@ void _userSerialize(
|
||||
writer.writeString(offsets[1], object.firstName);
|
||||
writer.writeString(offsets[2], object.id);
|
||||
writer.writeBool(offsets[3], object.isAdmin);
|
||||
writer.writeString(offsets[4], object.lastName);
|
||||
writer.writeDateTime(offsets[5], object.updatedAt);
|
||||
writer.writeBool(offsets[4], object.isPartnerSharedBy);
|
||||
writer.writeBool(offsets[5], object.isPartnerSharedWith);
|
||||
writer.writeString(offsets[6], object.lastName);
|
||||
writer.writeDateTime(offsets[7], object.updatedAt);
|
||||
}
|
||||
|
||||
User _userDeserialize(
|
||||
@@ -129,8 +141,10 @@ User _userDeserialize(
|
||||
firstName: reader.readString(offsets[1]),
|
||||
id: reader.readString(offsets[2]),
|
||||
isAdmin: reader.readBool(offsets[3]),
|
||||
lastName: reader.readString(offsets[4]),
|
||||
updatedAt: reader.readDateTime(offsets[5]),
|
||||
isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false,
|
||||
isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false,
|
||||
lastName: reader.readString(offsets[6]),
|
||||
updatedAt: reader.readDateTime(offsets[7]),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
@@ -151,8 +165,12 @@ P _userDeserializeProp<P>(
|
||||
case 3:
|
||||
return (reader.readBool(offset)) as P;
|
||||
case 4:
|
||||
return (reader.readString(offset)) as P;
|
||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||
case 5:
|
||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||
case 6:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 7:
|
||||
return (reader.readDateTime(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
@@ -741,6 +759,26 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterFilterCondition> isPartnerSharedByEqualTo(
|
||||
bool value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'isPartnerSharedBy',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterFilterCondition> isPartnerSharedWithEqualTo(
|
||||
bool value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'isPartnerSharedWith',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterFilterCondition> isarIdEqualTo(Id value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
@@ -1140,6 +1178,30 @@ extension UserQuerySortBy on QueryBuilder<User, User, QSortBy> {
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterSortBy> sortByIsPartnerSharedBy() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isPartnerSharedBy', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterSortBy> sortByIsPartnerSharedByDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isPartnerSharedBy', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterSortBy> sortByIsPartnerSharedWith() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isPartnerSharedWith', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterSortBy> sortByIsPartnerSharedWithDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isPartnerSharedWith', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterSortBy> sortByLastName() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'lastName', Sort.asc);
|
||||
@@ -1214,6 +1276,30 @@ extension UserQuerySortThenBy on QueryBuilder<User, User, QSortThenBy> {
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterSortBy> thenByIsPartnerSharedBy() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isPartnerSharedBy', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterSortBy> thenByIsPartnerSharedByDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isPartnerSharedBy', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterSortBy> thenByIsPartnerSharedWith() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isPartnerSharedWith', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterSortBy> thenByIsPartnerSharedWithDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isPartnerSharedWith', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QAfterSortBy> thenByIsarId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isarId', Sort.asc);
|
||||
@@ -1279,6 +1365,18 @@ extension UserQueryWhereDistinct on QueryBuilder<User, User, QDistinct> {
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QDistinct> distinctByIsPartnerSharedBy() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'isPartnerSharedBy');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QDistinct> distinctByIsPartnerSharedWith() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'isPartnerSharedWith');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, User, QDistinct> distinctByLastName(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
@@ -1324,6 +1422,18 @@ extension UserQueryProperty on QueryBuilder<User, User, QQueryProperty> {
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, bool, QQueryOperations> isPartnerSharedByProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'isPartnerSharedBy');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, bool, QQueryOperations> isPartnerSharedWithProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'isPartnerSharedWith');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<User, String, QQueryOperations> lastNameProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'lastName');
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/asset.service.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
||||
@@ -10,6 +11,7 @@ import 'package:immich_mobile/modules/settings/providers/app_settings.provider.d
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/services/sync.service.dart';
|
||||
import 'package:immich_mobile/shared/services/user.service.dart';
|
||||
import 'package:immich_mobile/utils/db.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -23,6 +25,7 @@ class AssetsState {}
|
||||
class AssetNotifier extends StateNotifier<AssetsState> {
|
||||
final AssetService _assetService;
|
||||
final AlbumService _albumService;
|
||||
final UserService _userService;
|
||||
final SyncService _syncService;
|
||||
final Isar _db;
|
||||
final log = Logger('AssetNotifier');
|
||||
@@ -32,6 +35,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
|
||||
AssetNotifier(
|
||||
this._assetService,
|
||||
this._albumService,
|
||||
this._userService,
|
||||
this._syncService,
|
||||
this._db,
|
||||
) : super(AssetsState());
|
||||
@@ -51,6 +55,12 @@ class AssetNotifier extends StateNotifier<AssetsState> {
|
||||
final bool newRemote = await _assetService.refreshRemoteAssets();
|
||||
final bool newLocal = await _albumService.refreshDeviceAlbums();
|
||||
debugPrint("newRemote: $newRemote, newLocal: $newLocal");
|
||||
await _userService.refreshUsers();
|
||||
final List<User> partners =
|
||||
await _db.users.filter().isPartnerSharedWithEqualTo(true).findAll();
|
||||
for (User u in partners) {
|
||||
await _assetService.refreshRemoteAssets(u);
|
||||
}
|
||||
log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
|
||||
} finally {
|
||||
_getAllAssetInProgress = false;
|
||||
@@ -147,6 +157,7 @@ final assetProvider = StateNotifierProvider<AssetNotifier, AssetsState>((ref) {
|
||||
return AssetNotifier(
|
||||
ref.watch(assetServiceProvider),
|
||||
ref.watch(albumServiceProvider),
|
||||
ref.watch(userServiceProvider),
|
||||
ref.watch(syncServiceProvider),
|
||||
ref.watch(dbProvider),
|
||||
);
|
||||
@@ -161,12 +172,14 @@ final assetDetailProvider =
|
||||
}
|
||||
});
|
||||
|
||||
final assetsProvider = StreamProvider.autoDispose<RenderList>((ref) async* {
|
||||
final assetsProvider =
|
||||
StreamProvider.family<RenderList, int?>((ref, userId) async* {
|
||||
if (userId == null) return;
|
||||
final query = ref
|
||||
.watch(dbProvider)
|
||||
.assets
|
||||
.filter()
|
||||
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
||||
.ownerIdEqualTo(userId)
|
||||
.isArchivedEqualTo(false)
|
||||
.sortByFileCreatedAtDesc();
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
@@ -179,14 +192,15 @@ final assetsProvider = StreamProvider.autoDispose<RenderList>((ref) async* {
|
||||
});
|
||||
|
||||
final remoteAssetsProvider =
|
||||
StreamProvider.autoDispose<RenderList>((ref) async* {
|
||||
StreamProvider.family<RenderList, int?>((ref, userId) async* {
|
||||
if (userId == null) return;
|
||||
final query = ref
|
||||
.watch(dbProvider)
|
||||
.assets
|
||||
.where()
|
||||
.remoteIdIsNotNull()
|
||||
.filter()
|
||||
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
||||
.ownerIdEqualTo(userId)
|
||||
.sortByFileCreatedAt();
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
final groupBy =
|
||||
|
||||
26
mobile/lib/shared/providers/user.provider.dart
Normal file
26
mobile/lib/shared/providers/user.provider.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
|
||||
class CurrentUserProvider extends StateNotifier<User?> {
|
||||
CurrentUserProvider() : super(null) {
|
||||
state = Store.tryGet(StoreKey.currentUser);
|
||||
streamSub =
|
||||
Store.watch(StoreKey.currentUser).listen((user) => state = user);
|
||||
}
|
||||
|
||||
late final StreamSubscription<User?> streamSub;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
streamSub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
final currentUserProvider =
|
||||
StateNotifierProvider<CurrentUserProvider, User?>((ref) {
|
||||
return CurrentUserProvider();
|
||||
});
|
||||
@@ -16,6 +16,7 @@ class ApiService {
|
||||
late AssetApi assetApi;
|
||||
late SearchApi searchApi;
|
||||
late ServerInfoApi serverInfoApi;
|
||||
late PartnerApi partnerApi;
|
||||
|
||||
ApiService() {
|
||||
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||
@@ -37,6 +38,7 @@ class ApiService {
|
||||
assetApi = AssetApi(_apiClient);
|
||||
serverInfoApi = ServerInfoApi(_apiClient);
|
||||
searchApi = SearchApi(_apiClient);
|
||||
partnerApi = PartnerApi(_apiClient);
|
||||
}
|
||||
|
||||
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
||||
|
||||
@@ -3,8 +3,10 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/etag.dart';
|
||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
@@ -36,37 +38,47 @@ class AssetService {
|
||||
|
||||
/// Checks the server for updated assets and updates the local database if
|
||||
/// required. Returns `true` if there were any changes.
|
||||
Future<bool> refreshRemoteAssets() async {
|
||||
Future<bool> refreshRemoteAssets([User? user]) async {
|
||||
user ??= Store.get(StoreKey.currentUser);
|
||||
final Stopwatch sw = Stopwatch()..start();
|
||||
final int numOwnedRemoteAssets = await _db.assets
|
||||
.where()
|
||||
.remoteIdIsNotNull()
|
||||
.filter()
|
||||
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
||||
.ownerIdEqualTo(user!.isarId)
|
||||
.count();
|
||||
final bool changes = await _syncService.syncRemoteAssetsToDb(
|
||||
() async => (await _getRemoteAssets(hasCache: numOwnedRemoteAssets > 0))
|
||||
?.map(Asset.remote)
|
||||
.toList(),
|
||||
user,
|
||||
() async => (await _getRemoteAssets(
|
||||
hasCache: numOwnedRemoteAssets > 0,
|
||||
user: user!,
|
||||
)),
|
||||
);
|
||||
debugPrint("refreshRemoteAssets full took ${sw.elapsedMilliseconds}ms");
|
||||
return changes;
|
||||
}
|
||||
|
||||
/// Returns `null` if the server state did not change, else list of assets
|
||||
Future<List<AssetResponseDto>?> _getRemoteAssets({
|
||||
Future<List<Asset>?> _getRemoteAssets({
|
||||
required bool hasCache,
|
||||
required User user,
|
||||
}) async {
|
||||
try {
|
||||
final etag = hasCache ? Store.tryGet(StoreKey.assetETag) : null;
|
||||
final etag = hasCache ? _db.eTags.getByIdSync(user.id)?.value : null;
|
||||
final (List<AssetResponseDto>? assets, String? newETag) =
|
||||
await _apiService.assetApi.getAllAssetsWithETag(eTag: etag);
|
||||
await _apiService.assetApi
|
||||
.getAllAssetsWithETag(eTag: etag, userId: user.id);
|
||||
if (assets == null) {
|
||||
return null;
|
||||
} else if (assets.isNotEmpty && assets.first.ownerId != user.id) {
|
||||
log.warning("Make sure that server and app versions match!"
|
||||
" The server returned assets for user ${assets.first.ownerId}"
|
||||
" while requesting assets of user ${user.id}");
|
||||
return null;
|
||||
} else if (newETag != etag) {
|
||||
Store.put(StoreKey.assetETag, newETag);
|
||||
_db.writeTxn(() => _db.eTags.put(ETag(id: user.id, value: newETag)));
|
||||
}
|
||||
return assets;
|
||||
return assets.map(Asset.remote).toList();
|
||||
} catch (e, stack) {
|
||||
log.severe('Error while getting remote assets', e, stack);
|
||||
return null;
|
||||
|
||||
@@ -40,7 +40,9 @@ class SyncService {
|
||||
dbUsers,
|
||||
compare: (User a, User b) => a.id.compareTo(b.id),
|
||||
both: (User a, User b) {
|
||||
if (!a.updatedAt.isAtSameMomentAs(b.updatedAt)) {
|
||||
if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) ||
|
||||
a.isPartnerSharedBy != b.isPartnerSharedBy ||
|
||||
a.isPartnerSharedWith != b.isPartnerSharedWith) {
|
||||
toUpsert.add(a);
|
||||
return true;
|
||||
}
|
||||
@@ -61,9 +63,10 @@ class SyncService {
|
||||
/// Syncs remote assets owned by the logged-in user to the DB
|
||||
/// Returns `true` if there were any changes
|
||||
Future<bool> syncRemoteAssetsToDb(
|
||||
User user,
|
||||
FutureOr<List<Asset>?> Function() loadAssets,
|
||||
) =>
|
||||
_lock.run(() => _syncRemoteAssetsToDb(loadAssets));
|
||||
_lock.run(() => _syncRemoteAssetsToDb(user, loadAssets));
|
||||
|
||||
/// Syncs remote albums to the database
|
||||
/// returns `true` if there were any changes
|
||||
@@ -149,13 +152,13 @@ class SyncService {
|
||||
/// Syncs remote assets to the databas
|
||||
/// returns `true` if there were any changes
|
||||
Future<bool> _syncRemoteAssetsToDb(
|
||||
User user,
|
||||
FutureOr<List<Asset>?> Function() loadAssets,
|
||||
) async {
|
||||
final List<Asset>? remote = await loadAssets();
|
||||
if (remote == null) {
|
||||
return false;
|
||||
}
|
||||
final User user = Store.get(StoreKey.currentUser);
|
||||
final List<Asset> inDb = await _db.assets
|
||||
.filter()
|
||||
.ownerIdEqualTo(user.isarId)
|
||||
@@ -349,10 +352,19 @@ class SyncService {
|
||||
);
|
||||
} else if (album.shared) {
|
||||
final User user = Store.get(StoreKey.currentUser);
|
||||
// delete assets in DB unless they belong to this user or are part of some other shared album
|
||||
deleteCandidates.addAll(
|
||||
await album.assets.filter().not().ownerIdEqualTo(user.isarId).findAll(),
|
||||
);
|
||||
// delete assets in DB unless they belong to this user or are part of some other shared album or belong to a partner
|
||||
final userIds = await _db.users
|
||||
.filter()
|
||||
.isPartnerSharedWithEqualTo(true)
|
||||
.isarIdProperty()
|
||||
.findAll();
|
||||
userIds.add(user.isarId);
|
||||
final orphanedAssets = await album.assets
|
||||
.filter()
|
||||
.not()
|
||||
.anyOf(userIds, (q, int id) => q.ownerIdEqualTo(id))
|
||||
.findAll();
|
||||
deleteCandidates.addAll(orphanedAssets);
|
||||
}
|
||||
try {
|
||||
final bool ok = await _db.writeTxn(() => _db.albums.delete(album.id));
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:immich_mobile/modules/partner/services/partner.service.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
import 'package:immich_mobile/shared/services/sync.service.dart';
|
||||
import 'package:immich_mobile/utils/diff.dart';
|
||||
import 'package:immich_mobile/utils/files_helper.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
final userServiceProvider = Provider(
|
||||
@@ -18,6 +21,7 @@ final userServiceProvider = Provider(
|
||||
ref.watch(apiServiceProvider),
|
||||
ref.watch(dbProvider),
|
||||
ref.watch(syncServiceProvider),
|
||||
ref.watch(partnerServiceProvider),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -25,15 +29,22 @@ class UserService {
|
||||
final ApiService _apiService;
|
||||
final Isar _db;
|
||||
final SyncService _syncService;
|
||||
final PartnerService _partnerService;
|
||||
final Logger _log = Logger("UserService");
|
||||
|
||||
UserService(this._apiService, this._db, this._syncService);
|
||||
UserService(
|
||||
this._apiService,
|
||||
this._db,
|
||||
this._syncService,
|
||||
this._partnerService,
|
||||
);
|
||||
|
||||
Future<List<User>?> _getAllUsers({required bool isAll}) async {
|
||||
try {
|
||||
final dto = await _apiService.userApi.getAllUsers(isAll);
|
||||
return dto?.map(User.fromDto).toList();
|
||||
} catch (e) {
|
||||
debugPrint("Error [getAllUsersInfo] ${e.toString()}");
|
||||
_log.warning("Failed get all users:\n$e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -62,16 +73,45 @@ class UserService {
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint("Error [uploadProfileImage] ${e.toString()}");
|
||||
_log.warning("Failed to upload profile image:\n$e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> refreshUsers() async {
|
||||
final List<User>? users = await _getAllUsers(isAll: true);
|
||||
if (users == null) {
|
||||
final List<User>? sharedBy =
|
||||
await _partnerService.getPartners(PartnerDirection.sharedBy);
|
||||
final List<User>? sharedWith =
|
||||
await _partnerService.getPartners(PartnerDirection.sharedWith);
|
||||
|
||||
if (users == null || sharedBy == null || sharedWith == null) {
|
||||
_log.warning("Failed to refresh users");
|
||||
return false;
|
||||
}
|
||||
|
||||
users.sortBy((u) => u.id);
|
||||
sharedBy.sortBy((u) => u.id);
|
||||
sharedWith.sortBy((u) => u.id);
|
||||
|
||||
diffSortedListsSync(
|
||||
users,
|
||||
sharedBy,
|
||||
compare: (User a, User b) => a.id.compareTo(b.id),
|
||||
both: (User a, User b) => a.isPartnerSharedBy = true,
|
||||
onlyFirst: (_) {},
|
||||
onlySecond: (_) {},
|
||||
);
|
||||
|
||||
diffSortedListsSync(
|
||||
users,
|
||||
sharedWith,
|
||||
compare: (User a, User b) => a.id.compareTo(b.id),
|
||||
both: (User a, User b) => a.isPartnerSharedWith = true,
|
||||
onlyFirst: (_) {},
|
||||
onlySecond: (_) {},
|
||||
);
|
||||
|
||||
return _syncService.syncUsersFromServer(users);
|
||||
}
|
||||
}
|
||||
|
||||
54
mobile/lib/shared/ui/confirm_dialog.dart
Normal file
54
mobile/lib/shared/ui/confirm_dialog.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class ConfirmDialog extends ConsumerWidget {
|
||||
final Function onOk;
|
||||
final String title;
|
||||
final String content;
|
||||
final String cancel;
|
||||
final String ok;
|
||||
|
||||
const ConfirmDialog({
|
||||
Key? key,
|
||||
required this.onOk,
|
||||
required this.title,
|
||||
required this.content,
|
||||
this.cancel = "delete_dialog_cancel",
|
||||
this.ok = "backup_controller_page_background_battery_info_ok",
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
title: Text(title).tr(),
|
||||
content: Text(content).tr(),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
cancel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
onOk();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
ok,
|
||||
style: TextStyle(
|
||||
color: Colors.red[400],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
21
mobile/lib/shared/ui/user_avatar.dart
Normal file
21
mobile/lib/shared/ui/user_avatar.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
|
||||
Widget userAvatar(BuildContext context, User u, {double? radius}) {
|
||||
final url =
|
||||
"${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${u.id}";
|
||||
return CircleAvatar(
|
||||
radius: radius,
|
||||
backgroundColor: Theme.of(context).primaryColor.withAlpha(50),
|
||||
foregroundImage: CachedNetworkImageProvider(
|
||||
url,
|
||||
headers: {"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}"},
|
||||
cacheKey: "user-${u.id}-profile",
|
||||
),
|
||||
// silence errors if user has no profile image, use initials as fallback
|
||||
onForegroundImageError: (exception, stackTrace) {},
|
||||
child: Text((u.firstName[0] + u.lastName[0]).toUpperCase()),
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/etag.dart';
|
||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
Future<void> clearAssetsAndAlbums(Isar db) async {
|
||||
@@ -10,5 +12,7 @@ Future<void> clearAssetsAndAlbums(Isar db) async {
|
||||
await db.assets.clear();
|
||||
await db.exifInfos.clear();
|
||||
await db.albums.clear();
|
||||
await db.eTags.clear();
|
||||
await db.users.clear();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ extension WithETag on AssetApi {
|
||||
/// ETag of data already cached on the client
|
||||
Future<(List<AssetResponseDto>? assets, String? eTag)> getAllAssetsWithETag({
|
||||
String? eTag,
|
||||
String? userId,
|
||||
}) async {
|
||||
final response = await getAllAssetsWithHttpInfo(
|
||||
ifNoneMatch: eTag,
|
||||
userId: userId,
|
||||
);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
|
||||
12
mobile/openapi/.openapi-generator/FILES
generated
12
mobile/openapi/.openapi-generator/FILES
generated
@@ -17,6 +17,10 @@ doc/AlbumCountResponseDto.md
|
||||
doc/AlbumResponseDto.md
|
||||
doc/AllJobStatusResponseDto.md
|
||||
doc/AssetApi.md
|
||||
doc/AssetBulkUploadCheckDto.md
|
||||
doc/AssetBulkUploadCheckItem.md
|
||||
doc/AssetBulkUploadCheckResponseDto.md
|
||||
doc/AssetBulkUploadCheckResult.md
|
||||
doc/AssetCountByTimeBucket.md
|
||||
doc/AssetCountByTimeBucketResponseDto.md
|
||||
doc/AssetCountByUserIdResponseDto.md
|
||||
@@ -142,6 +146,10 @@ lib/model/api_key_create_dto.dart
|
||||
lib/model/api_key_create_response_dto.dart
|
||||
lib/model/api_key_response_dto.dart
|
||||
lib/model/api_key_update_dto.dart
|
||||
lib/model/asset_bulk_upload_check_dto.dart
|
||||
lib/model/asset_bulk_upload_check_item.dart
|
||||
lib/model/asset_bulk_upload_check_response_dto.dart
|
||||
lib/model/asset_bulk_upload_check_result.dart
|
||||
lib/model/asset_count_by_time_bucket.dart
|
||||
lib/model/asset_count_by_time_bucket_response_dto.dart
|
||||
lib/model/asset_count_by_user_id_response_dto.dart
|
||||
@@ -236,6 +244,10 @@ test/api_key_create_response_dto_test.dart
|
||||
test/api_key_response_dto_test.dart
|
||||
test/api_key_update_dto_test.dart
|
||||
test/asset_api_test.dart
|
||||
test/asset_bulk_upload_check_dto_test.dart
|
||||
test/asset_bulk_upload_check_item_test.dart
|
||||
test/asset_bulk_upload_check_response_dto_test.dart
|
||||
test/asset_bulk_upload_check_result_test.dart
|
||||
test/asset_count_by_time_bucket_response_dto_test.dart
|
||||
test/asset_count_by_time_bucket_test.dart
|
||||
test/asset_count_by_user_id_response_dto_test.dart
|
||||
|
||||
23
mobile/openapi/README.md
generated
23
mobile/openapi/README.md
generated
@@ -3,7 +3,7 @@ Immich API
|
||||
|
||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 1.57.0
|
||||
- API version: 1.58.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
## Requirements
|
||||
@@ -77,19 +77,20 @@ Class | Method | HTTP request | Description
|
||||
*APIKeyApi* | [**getKey**](doc//APIKeyApi.md#getkey) | **GET** /api-key/{id} |
|
||||
*APIKeyApi* | [**getKeys**](doc//APIKeyApi.md#getkeys) | **GET** /api-key |
|
||||
*APIKeyApi* | [**updateKey**](doc//APIKeyApi.md#updatekey) | **PUT** /api-key/{id} |
|
||||
*AlbumApi* | [**addAssetsToAlbum**](doc//AlbumApi.md#addassetstoalbum) | **PUT** /album/{albumId}/assets |
|
||||
*AlbumApi* | [**addUsersToAlbum**](doc//AlbumApi.md#adduserstoalbum) | **PUT** /album/{albumId}/users |
|
||||
*AlbumApi* | [**addAssetsToAlbum**](doc//AlbumApi.md#addassetstoalbum) | **PUT** /album/{id}/assets |
|
||||
*AlbumApi* | [**addUsersToAlbum**](doc//AlbumApi.md#adduserstoalbum) | **PUT** /album/{id}/users |
|
||||
*AlbumApi* | [**createAlbum**](doc//AlbumApi.md#createalbum) | **POST** /album |
|
||||
*AlbumApi* | [**createAlbumSharedLink**](doc//AlbumApi.md#createalbumsharedlink) | **POST** /album/create-shared-link |
|
||||
*AlbumApi* | [**deleteAlbum**](doc//AlbumApi.md#deletealbum) | **DELETE** /album/{albumId} |
|
||||
*AlbumApi* | [**downloadArchive**](doc//AlbumApi.md#downloadarchive) | **GET** /album/{albumId}/download |
|
||||
*AlbumApi* | [**deleteAlbum**](doc//AlbumApi.md#deletealbum) | **DELETE** /album/{id} |
|
||||
*AlbumApi* | [**downloadArchive**](doc//AlbumApi.md#downloadarchive) | **GET** /album/{id}/download |
|
||||
*AlbumApi* | [**getAlbumCountByUserId**](doc//AlbumApi.md#getalbumcountbyuserid) | **GET** /album/count-by-user-id |
|
||||
*AlbumApi* | [**getAlbumInfo**](doc//AlbumApi.md#getalbuminfo) | **GET** /album/{albumId} |
|
||||
*AlbumApi* | [**getAlbumInfo**](doc//AlbumApi.md#getalbuminfo) | **GET** /album/{id} |
|
||||
*AlbumApi* | [**getAllAlbums**](doc//AlbumApi.md#getallalbums) | **GET** /album |
|
||||
*AlbumApi* | [**removeAssetFromAlbum**](doc//AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{albumId}/assets |
|
||||
*AlbumApi* | [**removeUserFromAlbum**](doc//AlbumApi.md#removeuserfromalbum) | **DELETE** /album/{albumId}/user/{userId} |
|
||||
*AlbumApi* | [**updateAlbumInfo**](doc//AlbumApi.md#updatealbuminfo) | **PATCH** /album/{albumId} |
|
||||
*AlbumApi* | [**removeAssetFromAlbum**](doc//AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{id}/assets |
|
||||
*AlbumApi* | [**removeUserFromAlbum**](doc//AlbumApi.md#removeuserfromalbum) | **DELETE** /album/{id}/user/{userId} |
|
||||
*AlbumApi* | [**updateAlbumInfo**](doc//AlbumApi.md#updatealbuminfo) | **PATCH** /album/{id} |
|
||||
*AssetApi* | [**addAssetsToSharedLink**](doc//AssetApi.md#addassetstosharedlink) | **PATCH** /asset/shared-link/add |
|
||||
*AssetApi* | [**bulkUploadCheck**](doc//AssetApi.md#bulkuploadcheck) | **POST** /asset/bulk-upload-check |
|
||||
*AssetApi* | [**checkDuplicateAsset**](doc//AssetApi.md#checkduplicateasset) | **POST** /asset/check |
|
||||
*AssetApi* | [**checkExistingAssets**](doc//AssetApi.md#checkexistingassets) | **POST** /asset/exist |
|
||||
*AssetApi* | [**createAssetsSharedLink**](doc//AssetApi.md#createassetssharedlink) | **POST** /asset/shared-link |
|
||||
@@ -183,6 +184,10 @@ Class | Method | HTTP request | Description
|
||||
- [AlbumCountResponseDto](doc//AlbumCountResponseDto.md)
|
||||
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
||||
- [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md)
|
||||
- [AssetBulkUploadCheckDto](doc//AssetBulkUploadCheckDto.md)
|
||||
- [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md)
|
||||
- [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.md)
|
||||
- [AssetBulkUploadCheckResult](doc//AssetBulkUploadCheckResult.md)
|
||||
- [AssetCountByTimeBucket](doc//AssetCountByTimeBucket.md)
|
||||
- [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md)
|
||||
- [AssetCountByUserIdResponseDto](doc//AssetCountByUserIdResponseDto.md)
|
||||
|
||||
80
mobile/openapi/doc/AlbumApi.md
generated
80
mobile/openapi/doc/AlbumApi.md
generated
@@ -9,22 +9,22 @@ All URIs are relative to */api*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**addAssetsToAlbum**](AlbumApi.md#addassetstoalbum) | **PUT** /album/{albumId}/assets |
|
||||
[**addUsersToAlbum**](AlbumApi.md#adduserstoalbum) | **PUT** /album/{albumId}/users |
|
||||
[**addAssetsToAlbum**](AlbumApi.md#addassetstoalbum) | **PUT** /album/{id}/assets |
|
||||
[**addUsersToAlbum**](AlbumApi.md#adduserstoalbum) | **PUT** /album/{id}/users |
|
||||
[**createAlbum**](AlbumApi.md#createalbum) | **POST** /album |
|
||||
[**createAlbumSharedLink**](AlbumApi.md#createalbumsharedlink) | **POST** /album/create-shared-link |
|
||||
[**deleteAlbum**](AlbumApi.md#deletealbum) | **DELETE** /album/{albumId} |
|
||||
[**downloadArchive**](AlbumApi.md#downloadarchive) | **GET** /album/{albumId}/download |
|
||||
[**deleteAlbum**](AlbumApi.md#deletealbum) | **DELETE** /album/{id} |
|
||||
[**downloadArchive**](AlbumApi.md#downloadarchive) | **GET** /album/{id}/download |
|
||||
[**getAlbumCountByUserId**](AlbumApi.md#getalbumcountbyuserid) | **GET** /album/count-by-user-id |
|
||||
[**getAlbumInfo**](AlbumApi.md#getalbuminfo) | **GET** /album/{albumId} |
|
||||
[**getAlbumInfo**](AlbumApi.md#getalbuminfo) | **GET** /album/{id} |
|
||||
[**getAllAlbums**](AlbumApi.md#getallalbums) | **GET** /album |
|
||||
[**removeAssetFromAlbum**](AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{albumId}/assets |
|
||||
[**removeUserFromAlbum**](AlbumApi.md#removeuserfromalbum) | **DELETE** /album/{albumId}/user/{userId} |
|
||||
[**updateAlbumInfo**](AlbumApi.md#updatealbuminfo) | **PATCH** /album/{albumId} |
|
||||
[**removeAssetFromAlbum**](AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{id}/assets |
|
||||
[**removeUserFromAlbum**](AlbumApi.md#removeuserfromalbum) | **DELETE** /album/{id}/user/{userId} |
|
||||
[**updateAlbumInfo**](AlbumApi.md#updatealbuminfo) | **PATCH** /album/{id} |
|
||||
|
||||
|
||||
# **addAssetsToAlbum**
|
||||
> AddAssetsResponseDto addAssetsToAlbum(albumId, addAssetsDto, key)
|
||||
> AddAssetsResponseDto addAssetsToAlbum(id, addAssetsDto, key)
|
||||
|
||||
|
||||
|
||||
@@ -47,12 +47,12 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AlbumApi();
|
||||
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final addAssetsDto = AddAssetsDto(); // AddAssetsDto |
|
||||
final key = key_example; // String |
|
||||
|
||||
try {
|
||||
final result = api_instance.addAssetsToAlbum(albumId, addAssetsDto, key);
|
||||
final result = api_instance.addAssetsToAlbum(id, addAssetsDto, key);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AlbumApi->addAssetsToAlbum: $e\n');
|
||||
@@ -63,7 +63,7 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**albumId** | **String**| |
|
||||
**id** | **String**| |
|
||||
**addAssetsDto** | [**AddAssetsDto**](AddAssetsDto.md)| |
|
||||
**key** | **String**| | [optional]
|
||||
|
||||
@@ -83,7 +83,7 @@ Name | Type | Description | Notes
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **addUsersToAlbum**
|
||||
> AlbumResponseDto addUsersToAlbum(albumId, addUsersDto)
|
||||
> AlbumResponseDto addUsersToAlbum(id, addUsersDto)
|
||||
|
||||
|
||||
|
||||
@@ -106,11 +106,11 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AlbumApi();
|
||||
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final addUsersDto = AddUsersDto(); // AddUsersDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.addUsersToAlbum(albumId, addUsersDto);
|
||||
final result = api_instance.addUsersToAlbum(id, addUsersDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AlbumApi->addUsersToAlbum: $e\n');
|
||||
@@ -121,7 +121,7 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**albumId** | **String**| |
|
||||
**id** | **String**| |
|
||||
**addUsersDto** | [**AddUsersDto**](AddUsersDto.md)| |
|
||||
|
||||
### Return type
|
||||
@@ -250,7 +250,7 @@ Name | Type | Description | Notes
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **deleteAlbum**
|
||||
> deleteAlbum(albumId)
|
||||
> deleteAlbum(id)
|
||||
|
||||
|
||||
|
||||
@@ -273,10 +273,10 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AlbumApi();
|
||||
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
|
||||
try {
|
||||
api_instance.deleteAlbum(albumId);
|
||||
api_instance.deleteAlbum(id);
|
||||
} catch (e) {
|
||||
print('Exception when calling AlbumApi->deleteAlbum: $e\n');
|
||||
}
|
||||
@@ -286,7 +286,7 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**albumId** | **String**| |
|
||||
**id** | **String**| |
|
||||
|
||||
### Return type
|
||||
|
||||
@@ -304,7 +304,7 @@ void (empty response body)
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **downloadArchive**
|
||||
> MultipartFile downloadArchive(albumId, name, skip, key)
|
||||
> MultipartFile downloadArchive(id, name, skip, key)
|
||||
|
||||
|
||||
|
||||
@@ -327,13 +327,13 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AlbumApi();
|
||||
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final name = name_example; // String |
|
||||
final skip = 8.14; // num |
|
||||
final key = key_example; // String |
|
||||
|
||||
try {
|
||||
final result = api_instance.downloadArchive(albumId, name, skip, key);
|
||||
final result = api_instance.downloadArchive(id, name, skip, key);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AlbumApi->downloadArchive: $e\n');
|
||||
@@ -344,7 +344,7 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**albumId** | **String**| |
|
||||
**id** | **String**| |
|
||||
**name** | **String**| | [optional]
|
||||
**skip** | **num**| | [optional]
|
||||
**key** | **String**| | [optional]
|
||||
@@ -416,7 +416,7 @@ This endpoint does not need any parameter.
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **getAlbumInfo**
|
||||
> AlbumResponseDto getAlbumInfo(albumId, key)
|
||||
> AlbumResponseDto getAlbumInfo(id, key)
|
||||
|
||||
|
||||
|
||||
@@ -439,11 +439,11 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AlbumApi();
|
||||
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final key = key_example; // String |
|
||||
|
||||
try {
|
||||
final result = api_instance.getAlbumInfo(albumId, key);
|
||||
final result = api_instance.getAlbumInfo(id, key);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AlbumApi->getAlbumInfo: $e\n');
|
||||
@@ -454,7 +454,7 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**albumId** | **String**| |
|
||||
**id** | **String**| |
|
||||
**key** | **String**| | [optional]
|
||||
|
||||
### Return type
|
||||
@@ -530,7 +530,7 @@ Name | Type | Description | Notes
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **removeAssetFromAlbum**
|
||||
> AlbumResponseDto removeAssetFromAlbum(albumId, removeAssetsDto)
|
||||
> AlbumResponseDto removeAssetFromAlbum(id, removeAssetsDto)
|
||||
|
||||
|
||||
|
||||
@@ -553,11 +553,11 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AlbumApi();
|
||||
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final removeAssetsDto = RemoveAssetsDto(); // RemoveAssetsDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.removeAssetFromAlbum(albumId, removeAssetsDto);
|
||||
final result = api_instance.removeAssetFromAlbum(id, removeAssetsDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AlbumApi->removeAssetFromAlbum: $e\n');
|
||||
@@ -568,7 +568,7 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**albumId** | **String**| |
|
||||
**id** | **String**| |
|
||||
**removeAssetsDto** | [**RemoveAssetsDto**](RemoveAssetsDto.md)| |
|
||||
|
||||
### Return type
|
||||
@@ -587,7 +587,7 @@ Name | Type | Description | Notes
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **removeUserFromAlbum**
|
||||
> removeUserFromAlbum(albumId, userId)
|
||||
> removeUserFromAlbum(id, userId)
|
||||
|
||||
|
||||
|
||||
@@ -610,11 +610,11 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AlbumApi();
|
||||
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final userId = userId_example; // String |
|
||||
|
||||
try {
|
||||
api_instance.removeUserFromAlbum(albumId, userId);
|
||||
api_instance.removeUserFromAlbum(id, userId);
|
||||
} catch (e) {
|
||||
print('Exception when calling AlbumApi->removeUserFromAlbum: $e\n');
|
||||
}
|
||||
@@ -624,7 +624,7 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**albumId** | **String**| |
|
||||
**id** | **String**| |
|
||||
**userId** | **String**| |
|
||||
|
||||
### Return type
|
||||
@@ -643,7 +643,7 @@ void (empty response body)
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **updateAlbumInfo**
|
||||
> AlbumResponseDto updateAlbumInfo(albumId, updateAlbumDto)
|
||||
> AlbumResponseDto updateAlbumInfo(id, updateAlbumDto)
|
||||
|
||||
|
||||
|
||||
@@ -666,11 +666,11 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AlbumApi();
|
||||
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final updateAlbumDto = UpdateAlbumDto(); // UpdateAlbumDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.updateAlbumInfo(albumId, updateAlbumDto);
|
||||
final result = api_instance.updateAlbumInfo(id, updateAlbumDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AlbumApi->updateAlbumInfo: $e\n');
|
||||
@@ -681,7 +681,7 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**albumId** | **String**| |
|
||||
**id** | **String**| |
|
||||
**updateAlbumDto** | [**UpdateAlbumDto**](UpdateAlbumDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
1
mobile/openapi/doc/AllJobStatusResponseDto.md
generated
1
mobile/openapi/doc/AllJobStatusResponseDto.md
generated
@@ -17,6 +17,7 @@ Name | Type | Description | Notes
|
||||
**backgroundTaskQueue** | [**JobStatusDto**](JobStatusDto.md) | |
|
||||
**searchQueue** | [**JobStatusDto**](JobStatusDto.md) | |
|
||||
**recognizeFacesQueue** | [**JobStatusDto**](JobStatusDto.md) | |
|
||||
**sidecarQueue** | [**JobStatusDto**](JobStatusDto.md) | |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
78
mobile/openapi/doc/AssetApi.md
generated
78
mobile/openapi/doc/AssetApi.md
generated
@@ -10,6 +10,7 @@ All URIs are relative to */api*
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**addAssetsToSharedLink**](AssetApi.md#addassetstosharedlink) | **PATCH** /asset/shared-link/add |
|
||||
[**bulkUploadCheck**](AssetApi.md#bulkuploadcheck) | **POST** /asset/bulk-upload-check |
|
||||
[**checkDuplicateAsset**](AssetApi.md#checkduplicateasset) | **POST** /asset/check |
|
||||
[**checkExistingAssets**](AssetApi.md#checkexistingassets) | **POST** /asset/exist |
|
||||
[**createAssetsSharedLink**](AssetApi.md#createassetssharedlink) | **POST** /asset/shared-link |
|
||||
@@ -93,6 +94,63 @@ Name | Type | Description | Notes
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **bulkUploadCheck**
|
||||
> AssetBulkUploadCheckResponseDto bulkUploadCheck(assetBulkUploadCheckDto)
|
||||
|
||||
|
||||
|
||||
Checks if assets exist by checksums
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
// TODO Configure API key authorization: cookie
|
||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
|
||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
|
||||
// TODO Configure API key authorization: api_key
|
||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
|
||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
|
||||
// TODO Configure HTTP Bearer authorization: bearer
|
||||
// Case 1. Use String Token
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
|
||||
// Case 2. Use Function which generate token.
|
||||
// String yourTokenGeneratorFunction() { ... }
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AssetApi();
|
||||
final assetBulkUploadCheckDto = AssetBulkUploadCheckDto(); // AssetBulkUploadCheckDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.bulkUploadCheck(assetBulkUploadCheckDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->bulkUploadCheck: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**assetBulkUploadCheckDto** | [**AssetBulkUploadCheckDto**](AssetBulkUploadCheckDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**AssetBulkUploadCheckResponseDto**](AssetBulkUploadCheckResponseDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **checkDuplicateAsset**
|
||||
> CheckDuplicateAssetResponseDto checkDuplicateAsset(checkDuplicateAssetDto, key)
|
||||
|
||||
@@ -495,7 +553,7 @@ Name | Type | Description | Notes
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **getAllAssets**
|
||||
> List<AssetResponseDto> getAllAssets(isFavorite, isArchived, skip, ifNoneMatch)
|
||||
> List<AssetResponseDto> getAllAssets(userId, isFavorite, isArchived, skip, ifNoneMatch)
|
||||
|
||||
|
||||
|
||||
@@ -520,13 +578,14 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AssetApi();
|
||||
final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final isFavorite = true; // bool |
|
||||
final isArchived = true; // bool |
|
||||
final skip = 8.14; // num |
|
||||
final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client
|
||||
|
||||
try {
|
||||
final result = api_instance.getAllAssets(isFavorite, isArchived, skip, ifNoneMatch);
|
||||
final result = api_instance.getAllAssets(userId, isFavorite, isArchived, skip, ifNoneMatch);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->getAllAssets: $e\n');
|
||||
@@ -537,6 +596,7 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**userId** | **String**| | [optional]
|
||||
**isFavorite** | **bool**| | [optional]
|
||||
**isArchived** | **bool**| | [optional]
|
||||
**skip** | **num**| | [optional]
|
||||
@@ -1041,7 +1101,7 @@ This endpoint does not need any parameter.
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **getMapMarkers**
|
||||
> List<MapMarkerResponseDto> getMapMarkers(isFavorite)
|
||||
> List<MapMarkerResponseDto> getMapMarkers(isFavorite, fileCreatedAfter, fileCreatedBefore)
|
||||
|
||||
|
||||
|
||||
@@ -1065,9 +1125,11 @@ import 'package:openapi/api.dart';
|
||||
|
||||
final api_instance = AssetApi();
|
||||
final isFavorite = true; // bool |
|
||||
final fileCreatedAfter = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final fileCreatedBefore = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
|
||||
try {
|
||||
final result = api_instance.getMapMarkers(isFavorite);
|
||||
final result = api_instance.getMapMarkers(isFavorite, fileCreatedAfter, fileCreatedBefore);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->getMapMarkers: $e\n');
|
||||
@@ -1079,6 +1141,8 @@ try {
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**isFavorite** | **bool**| | [optional]
|
||||
**fileCreatedAfter** | **DateTime**| | [optional]
|
||||
**fileCreatedBefore** | **DateTime**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
@@ -1385,7 +1449,7 @@ Name | Type | Description | Notes
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **uploadFile**
|
||||
> AssetFileUploadResponseDto uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isArchived, isVisible, duration)
|
||||
> AssetFileUploadResponseDto uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, sidecarData, isArchived, isVisible, duration)
|
||||
|
||||
|
||||
|
||||
@@ -1418,12 +1482,13 @@ final isFavorite = true; // bool |
|
||||
final fileExtension = fileExtension_example; // String |
|
||||
final key = key_example; // String |
|
||||
final livePhotoData = BINARY_DATA_HERE; // MultipartFile |
|
||||
final sidecarData = BINARY_DATA_HERE; // MultipartFile |
|
||||
final isArchived = true; // bool |
|
||||
final isVisible = true; // bool |
|
||||
final duration = duration_example; // String |
|
||||
|
||||
try {
|
||||
final result = api_instance.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isArchived, isVisible, duration);
|
||||
final result = api_instance.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, sidecarData, isArchived, isVisible, duration);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->uploadFile: $e\n');
|
||||
@@ -1444,6 +1509,7 @@ Name | Type | Description | Notes
|
||||
**fileExtension** | **String**| |
|
||||
**key** | **String**| | [optional]
|
||||
**livePhotoData** | **MultipartFile**| | [optional]
|
||||
**sidecarData** | **MultipartFile**| | [optional]
|
||||
**isArchived** | **bool**| | [optional]
|
||||
**isVisible** | **bool**| | [optional]
|
||||
**duration** | **String**| | [optional]
|
||||
|
||||
15
mobile/openapi/doc/AssetBulkUploadCheckDto.md
generated
Normal file
15
mobile/openapi/doc/AssetBulkUploadCheckDto.md
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
# openapi.model.AssetBulkUploadCheckDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**assets** | [**List<AssetBulkUploadCheckItem>**](AssetBulkUploadCheckItem.md) | | [default to const []]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
16
mobile/openapi/doc/AssetBulkUploadCheckItem.md
generated
Normal file
16
mobile/openapi/doc/AssetBulkUploadCheckItem.md
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
# openapi.model.AssetBulkUploadCheckItem
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**id** | **String** | |
|
||||
**checksum** | **String** | |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
15
mobile/openapi/doc/AssetBulkUploadCheckResponseDto.md
generated
Normal file
15
mobile/openapi/doc/AssetBulkUploadCheckResponseDto.md
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
# openapi.model.AssetBulkUploadCheckResponseDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**results** | [**List<AssetBulkUploadCheckResult>**](AssetBulkUploadCheckResult.md) | | [default to const []]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
18
mobile/openapi/doc/AssetBulkUploadCheckResult.md
generated
Normal file
18
mobile/openapi/doc/AssetBulkUploadCheckResult.md
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
# openapi.model.AssetBulkUploadCheckResult
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**id** | **String** | |
|
||||
**action** | **String** | |
|
||||
**reason** | **String** | | [optional]
|
||||
**assetId** | **String** | | [optional]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
1
mobile/openapi/doc/GetAssetByTimeBucketDto.md
generated
1
mobile/openapi/doc/GetAssetByTimeBucketDto.md
generated
@@ -10,6 +10,7 @@ Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**timeBucket** | **List<String>** | | [default to const []]
|
||||
**userId** | **String** | | [optional]
|
||||
**withoutThumbs** | **bool** | Include assets without thumbnails | [optional]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
4
mobile/openapi/lib/api.dart
generated
4
mobile/openapi/lib/api.dart
generated
@@ -54,6 +54,10 @@ part 'model/admin_signup_response_dto.dart';
|
||||
part 'model/album_count_response_dto.dart';
|
||||
part 'model/album_response_dto.dart';
|
||||
part 'model/all_job_status_response_dto.dart';
|
||||
part 'model/asset_bulk_upload_check_dto.dart';
|
||||
part 'model/asset_bulk_upload_check_item.dart';
|
||||
part 'model/asset_bulk_upload_check_response_dto.dart';
|
||||
part 'model/asset_bulk_upload_check_result.dart';
|
||||
part 'model/asset_count_by_time_bucket.dart';
|
||||
part 'model/asset_count_by_time_bucket_response_dto.dart';
|
||||
part 'model/asset_count_by_user_id_response_dto.dart';
|
||||
|
||||
128
mobile/openapi/lib/api/album_api.dart
generated
128
mobile/openapi/lib/api/album_api.dart
generated
@@ -16,18 +16,18 @@ class AlbumApi {
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// Performs an HTTP 'PUT /album/{albumId}/assets' operation and returns the [Response].
|
||||
/// Performs an HTTP 'PUT /album/{id}/assets' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AddAssetsDto] addAssetsDto (required):
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<Response> addAssetsToAlbumWithHttpInfo(String albumId, AddAssetsDto addAssetsDto, { String? key, }) async {
|
||||
Future<Response> addAssetsToAlbumWithHttpInfo(String id, AddAssetsDto addAssetsDto, { String? key, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/album/{albumId}/assets'
|
||||
.replaceAll('{albumId}', albumId);
|
||||
final path = r'/album/{id}/assets'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = addAssetsDto;
|
||||
@@ -56,13 +56,13 @@ class AlbumApi {
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AddAssetsDto] addAssetsDto (required):
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<AddAssetsResponseDto?> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto, { String? key, }) async {
|
||||
final response = await addAssetsToAlbumWithHttpInfo(albumId, addAssetsDto, key: key, );
|
||||
Future<AddAssetsResponseDto?> addAssetsToAlbum(String id, AddAssetsDto addAssetsDto, { String? key, }) async {
|
||||
final response = await addAssetsToAlbumWithHttpInfo(id, addAssetsDto, key: key, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -76,16 +76,16 @@ class AlbumApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'PUT /album/{albumId}/users' operation and returns the [Response].
|
||||
/// Performs an HTTP 'PUT /album/{id}/users' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AddUsersDto] addUsersDto (required):
|
||||
Future<Response> addUsersToAlbumWithHttpInfo(String albumId, AddUsersDto addUsersDto,) async {
|
||||
Future<Response> addUsersToAlbumWithHttpInfo(String id, AddUsersDto addUsersDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/album/{albumId}/users'
|
||||
.replaceAll('{albumId}', albumId);
|
||||
final path = r'/album/{id}/users'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = addUsersDto;
|
||||
@@ -110,11 +110,11 @@ class AlbumApi {
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AddUsersDto] addUsersDto (required):
|
||||
Future<AlbumResponseDto?> addUsersToAlbum(String albumId, AddUsersDto addUsersDto,) async {
|
||||
final response = await addUsersToAlbumWithHttpInfo(albumId, addUsersDto,);
|
||||
Future<AlbumResponseDto?> addUsersToAlbum(String id, AddUsersDto addUsersDto,) async {
|
||||
final response = await addUsersToAlbumWithHttpInfo(id, addUsersDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -222,14 +222,14 @@ class AlbumApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'DELETE /album/{albumId}' operation and returns the [Response].
|
||||
/// Performs an HTTP 'DELETE /album/{id}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
Future<Response> deleteAlbumWithHttpInfo(String albumId,) async {
|
||||
/// * [String] id (required):
|
||||
Future<Response> deleteAlbumWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/album/{albumId}'
|
||||
.replaceAll('{albumId}', albumId);
|
||||
final path = r'/album/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
@@ -254,28 +254,28 @@ class AlbumApi {
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
Future<void> deleteAlbum(String albumId,) async {
|
||||
final response = await deleteAlbumWithHttpInfo(albumId,);
|
||||
/// * [String] id (required):
|
||||
Future<void> deleteAlbum(String id,) async {
|
||||
final response = await deleteAlbumWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /album/{albumId}/download' operation and returns the [Response].
|
||||
/// Performs an HTTP 'GET /album/{id}/download' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [String] name:
|
||||
///
|
||||
/// * [num] skip:
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<Response> downloadArchiveWithHttpInfo(String albumId, { String? name, num? skip, String? key, }) async {
|
||||
Future<Response> downloadArchiveWithHttpInfo(String id, { String? name, num? skip, String? key, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/album/{albumId}/download'
|
||||
.replaceAll('{albumId}', albumId);
|
||||
final path = r'/album/{id}/download'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
@@ -310,15 +310,15 @@ class AlbumApi {
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [String] name:
|
||||
///
|
||||
/// * [num] skip:
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<MultipartFile?> downloadArchive(String albumId, { String? name, num? skip, String? key, }) async {
|
||||
final response = await downloadArchiveWithHttpInfo(albumId, name: name, skip: skip, key: key, );
|
||||
Future<MultipartFile?> downloadArchive(String id, { String? name, num? skip, String? key, }) async {
|
||||
final response = await downloadArchiveWithHttpInfo(id, name: name, skip: skip, key: key, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -373,16 +373,16 @@ class AlbumApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /album/{albumId}' operation and returns the [Response].
|
||||
/// Performs an HTTP 'GET /album/{id}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<Response> getAlbumInfoWithHttpInfo(String albumId, { String? key, }) async {
|
||||
Future<Response> getAlbumInfoWithHttpInfo(String id, { String? key, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/album/{albumId}'
|
||||
.replaceAll('{albumId}', albumId);
|
||||
final path = r'/album/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
@@ -411,11 +411,11 @@ class AlbumApi {
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<AlbumResponseDto?> getAlbumInfo(String albumId, { String? key, }) async {
|
||||
final response = await getAlbumInfoWithHttpInfo(albumId, key: key, );
|
||||
Future<AlbumResponseDto?> getAlbumInfo(String id, { String? key, }) async {
|
||||
final response = await getAlbumInfoWithHttpInfo(id, key: key, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -492,16 +492,16 @@ class AlbumApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'DELETE /album/{albumId}/assets' operation and returns the [Response].
|
||||
/// Performs an HTTP 'DELETE /album/{id}/assets' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [RemoveAssetsDto] removeAssetsDto (required):
|
||||
Future<Response> removeAssetFromAlbumWithHttpInfo(String albumId, RemoveAssetsDto removeAssetsDto,) async {
|
||||
Future<Response> removeAssetFromAlbumWithHttpInfo(String id, RemoveAssetsDto removeAssetsDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/album/{albumId}/assets'
|
||||
.replaceAll('{albumId}', albumId);
|
||||
final path = r'/album/{id}/assets'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = removeAssetsDto;
|
||||
@@ -526,11 +526,11 @@ class AlbumApi {
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [RemoveAssetsDto] removeAssetsDto (required):
|
||||
Future<AlbumResponseDto?> removeAssetFromAlbum(String albumId, RemoveAssetsDto removeAssetsDto,) async {
|
||||
final response = await removeAssetFromAlbumWithHttpInfo(albumId, removeAssetsDto,);
|
||||
Future<AlbumResponseDto?> removeAssetFromAlbum(String id, RemoveAssetsDto removeAssetsDto,) async {
|
||||
final response = await removeAssetFromAlbumWithHttpInfo(id, removeAssetsDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -544,16 +544,16 @@ class AlbumApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'DELETE /album/{albumId}/user/{userId}' operation and returns the [Response].
|
||||
/// Performs an HTTP 'DELETE /album/{id}/user/{userId}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [String] userId (required):
|
||||
Future<Response> removeUserFromAlbumWithHttpInfo(String albumId, String userId,) async {
|
||||
Future<Response> removeUserFromAlbumWithHttpInfo(String id, String userId,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/album/{albumId}/user/{userId}'
|
||||
.replaceAll('{albumId}', albumId)
|
||||
final path = r'/album/{id}/user/{userId}'
|
||||
.replaceAll('{id}', id)
|
||||
.replaceAll('{userId}', userId);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
@@ -579,26 +579,26 @@ class AlbumApi {
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [String] userId (required):
|
||||
Future<void> removeUserFromAlbum(String albumId, String userId,) async {
|
||||
final response = await removeUserFromAlbumWithHttpInfo(albumId, userId,);
|
||||
Future<void> removeUserFromAlbum(String id, String userId,) async {
|
||||
final response = await removeUserFromAlbumWithHttpInfo(id, userId,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'PATCH /album/{albumId}' operation and returns the [Response].
|
||||
/// Performs an HTTP 'PATCH /album/{id}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdateAlbumDto] updateAlbumDto (required):
|
||||
Future<Response> updateAlbumInfoWithHttpInfo(String albumId, UpdateAlbumDto updateAlbumDto,) async {
|
||||
Future<Response> updateAlbumInfoWithHttpInfo(String id, UpdateAlbumDto updateAlbumDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/album/{albumId}'
|
||||
.replaceAll('{albumId}', albumId);
|
||||
final path = r'/album/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = updateAlbumDto;
|
||||
@@ -623,11 +623,11 @@ class AlbumApi {
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdateAlbumDto] updateAlbumDto (required):
|
||||
Future<AlbumResponseDto?> updateAlbumInfo(String albumId, UpdateAlbumDto updateAlbumDto,) async {
|
||||
final response = await updateAlbumInfoWithHttpInfo(albumId, updateAlbumDto,);
|
||||
Future<AlbumResponseDto?> updateAlbumInfo(String id, UpdateAlbumDto updateAlbumDto,) async {
|
||||
final response = await updateAlbumInfoWithHttpInfo(id, updateAlbumDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
||||
100
mobile/openapi/lib/api/asset_api.dart
generated
100
mobile/openapi/lib/api/asset_api.dart
generated
@@ -71,6 +71,58 @@ class AssetApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Checks if assets exist by checksums
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [AssetBulkUploadCheckDto] assetBulkUploadCheckDto (required):
|
||||
Future<Response> bulkUploadCheckWithHttpInfo(AssetBulkUploadCheckDto assetBulkUploadCheckDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/bulk-upload-check';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = assetBulkUploadCheckDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Checks if assets exist by checksums
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [AssetBulkUploadCheckDto] assetBulkUploadCheckDto (required):
|
||||
Future<AssetBulkUploadCheckResponseDto?> bulkUploadCheck(AssetBulkUploadCheckDto assetBulkUploadCheckDto,) async {
|
||||
final response = await bulkUploadCheckWithHttpInfo(assetBulkUploadCheckDto,);
|
||||
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), 'AssetBulkUploadCheckResponseDto',) as AssetBulkUploadCheckResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Check duplicated asset before uploading - for Web upload used
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
@@ -467,6 +519,8 @@ class AssetApi {
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] userId:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
@@ -475,7 +529,7 @@ class AssetApi {
|
||||
///
|
||||
/// * [String] ifNoneMatch:
|
||||
/// ETag of data already cached on the client
|
||||
Future<Response> getAllAssetsWithHttpInfo({ bool? isFavorite, bool? isArchived, num? skip, String? ifNoneMatch, }) async {
|
||||
Future<Response> getAllAssetsWithHttpInfo({ String? userId, bool? isFavorite, bool? isArchived, num? skip, String? ifNoneMatch, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset';
|
||||
|
||||
@@ -486,6 +540,9 @@ class AssetApi {
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
if (userId != null) {
|
||||
queryParams.addAll(_queryParams('', 'userId', userId));
|
||||
}
|
||||
if (isFavorite != null) {
|
||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||
}
|
||||
@@ -518,6 +575,8 @@ class AssetApi {
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] userId:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
@@ -526,8 +585,8 @@ class AssetApi {
|
||||
///
|
||||
/// * [String] ifNoneMatch:
|
||||
/// ETag of data already cached on the client
|
||||
Future<List<AssetResponseDto>?> getAllAssets({ bool? isFavorite, bool? isArchived, num? skip, String? ifNoneMatch, }) async {
|
||||
final response = await getAllAssetsWithHttpInfo( isFavorite: isFavorite, isArchived: isArchived, skip: skip, ifNoneMatch: ifNoneMatch, );
|
||||
Future<List<AssetResponseDto>?> getAllAssets({ String? userId, bool? isFavorite, bool? isArchived, num? skip, String? ifNoneMatch, }) async {
|
||||
final response = await getAllAssetsWithHttpInfo( userId: userId, isFavorite: isFavorite, isArchived: isArchived, skip: skip, ifNoneMatch: ifNoneMatch, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -983,7 +1042,11 @@ class AssetApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
Future<Response> getMapMarkersWithHttpInfo({ bool? isFavorite, }) async {
|
||||
///
|
||||
/// * [DateTime] fileCreatedAfter:
|
||||
///
|
||||
/// * [DateTime] fileCreatedBefore:
|
||||
Future<Response> getMapMarkersWithHttpInfo({ bool? isFavorite, DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/map-marker';
|
||||
|
||||
@@ -997,6 +1060,12 @@ class AssetApi {
|
||||
if (isFavorite != null) {
|
||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||
}
|
||||
if (fileCreatedAfter != null) {
|
||||
queryParams.addAll(_queryParams('', 'fileCreatedAfter', fileCreatedAfter));
|
||||
}
|
||||
if (fileCreatedBefore != null) {
|
||||
queryParams.addAll(_queryParams('', 'fileCreatedBefore', fileCreatedBefore));
|
||||
}
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
@@ -1015,8 +1084,12 @@ class AssetApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
Future<List<MapMarkerResponseDto>?> getMapMarkers({ bool? isFavorite, }) async {
|
||||
final response = await getMapMarkersWithHttpInfo( isFavorite: isFavorite, );
|
||||
///
|
||||
/// * [DateTime] fileCreatedAfter:
|
||||
///
|
||||
/// * [DateTime] fileCreatedBefore:
|
||||
Future<List<MapMarkerResponseDto>?> getMapMarkers({ bool? isFavorite, DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, }) async {
|
||||
final response = await getMapMarkersWithHttpInfo( isFavorite: isFavorite, fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -1344,12 +1417,14 @@ class AssetApi {
|
||||
///
|
||||
/// * [MultipartFile] livePhotoData:
|
||||
///
|
||||
/// * [MultipartFile] sidecarData:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isVisible:
|
||||
///
|
||||
/// * [String] duration:
|
||||
Future<Response> uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, bool? isArchived, bool? isVisible, String? duration, }) async {
|
||||
Future<Response> uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isArchived, bool? isVisible, String? duration, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/upload';
|
||||
|
||||
@@ -1382,6 +1457,11 @@ class AssetApi {
|
||||
mp.fields[r'livePhotoData'] = livePhotoData.field;
|
||||
mp.files.add(livePhotoData);
|
||||
}
|
||||
if (sidecarData != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'sidecarData'] = sidecarData.field;
|
||||
mp.files.add(sidecarData);
|
||||
}
|
||||
if (deviceAssetId != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'deviceAssetId'] = parameterToString(deviceAssetId);
|
||||
@@ -1455,13 +1535,15 @@ class AssetApi {
|
||||
///
|
||||
/// * [MultipartFile] livePhotoData:
|
||||
///
|
||||
/// * [MultipartFile] sidecarData:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isVisible:
|
||||
///
|
||||
/// * [String] duration:
|
||||
Future<AssetFileUploadResponseDto?> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, bool? isArchived, bool? isVisible, String? duration, }) async {
|
||||
final response = await uploadFileWithHttpInfo(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key: key, livePhotoData: livePhotoData, isArchived: isArchived, isVisible: isVisible, duration: duration, );
|
||||
Future<AssetFileUploadResponseDto?> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isArchived, bool? isVisible, String? duration, }) async {
|
||||
final response = await uploadFileWithHttpInfo(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key: key, livePhotoData: livePhotoData, sidecarData: sidecarData, isArchived: isArchived, isVisible: isVisible, duration: duration, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
||||
8
mobile/openapi/lib/api_client.dart
generated
8
mobile/openapi/lib/api_client.dart
generated
@@ -203,6 +203,14 @@ class ApiClient {
|
||||
return AlbumResponseDto.fromJson(value);
|
||||
case 'AllJobStatusResponseDto':
|
||||
return AllJobStatusResponseDto.fromJson(value);
|
||||
case 'AssetBulkUploadCheckDto':
|
||||
return AssetBulkUploadCheckDto.fromJson(value);
|
||||
case 'AssetBulkUploadCheckItem':
|
||||
return AssetBulkUploadCheckItem.fromJson(value);
|
||||
case 'AssetBulkUploadCheckResponseDto':
|
||||
return AssetBulkUploadCheckResponseDto.fromJson(value);
|
||||
case 'AssetBulkUploadCheckResult':
|
||||
return AssetBulkUploadCheckResult.fromJson(value);
|
||||
case 'AssetCountByTimeBucket':
|
||||
return AssetCountByTimeBucket.fromJson(value);
|
||||
case 'AssetCountByTimeBucketResponseDto':
|
||||
|
||||
@@ -22,6 +22,7 @@ class AllJobStatusResponseDto {
|
||||
required this.backgroundTaskQueue,
|
||||
required this.searchQueue,
|
||||
required this.recognizeFacesQueue,
|
||||
required this.sidecarQueue,
|
||||
});
|
||||
|
||||
JobStatusDto thumbnailGenerationQueue;
|
||||
@@ -42,6 +43,8 @@ class AllJobStatusResponseDto {
|
||||
|
||||
JobStatusDto recognizeFacesQueue;
|
||||
|
||||
JobStatusDto sidecarQueue;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AllJobStatusResponseDto &&
|
||||
other.thumbnailGenerationQueue == thumbnailGenerationQueue &&
|
||||
@@ -52,7 +55,8 @@ class AllJobStatusResponseDto {
|
||||
other.storageTemplateMigrationQueue == storageTemplateMigrationQueue &&
|
||||
other.backgroundTaskQueue == backgroundTaskQueue &&
|
||||
other.searchQueue == searchQueue &&
|
||||
other.recognizeFacesQueue == recognizeFacesQueue;
|
||||
other.recognizeFacesQueue == recognizeFacesQueue &&
|
||||
other.sidecarQueue == sidecarQueue;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -65,10 +69,11 @@ class AllJobStatusResponseDto {
|
||||
(storageTemplateMigrationQueue.hashCode) +
|
||||
(backgroundTaskQueue.hashCode) +
|
||||
(searchQueue.hashCode) +
|
||||
(recognizeFacesQueue.hashCode);
|
||||
(recognizeFacesQueue.hashCode) +
|
||||
(sidecarQueue.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AllJobStatusResponseDto[thumbnailGenerationQueue=$thumbnailGenerationQueue, metadataExtractionQueue=$metadataExtractionQueue, videoConversionQueue=$videoConversionQueue, objectTaggingQueue=$objectTaggingQueue, clipEncodingQueue=$clipEncodingQueue, storageTemplateMigrationQueue=$storageTemplateMigrationQueue, backgroundTaskQueue=$backgroundTaskQueue, searchQueue=$searchQueue, recognizeFacesQueue=$recognizeFacesQueue]';
|
||||
String toString() => 'AllJobStatusResponseDto[thumbnailGenerationQueue=$thumbnailGenerationQueue, metadataExtractionQueue=$metadataExtractionQueue, videoConversionQueue=$videoConversionQueue, objectTaggingQueue=$objectTaggingQueue, clipEncodingQueue=$clipEncodingQueue, storageTemplateMigrationQueue=$storageTemplateMigrationQueue, backgroundTaskQueue=$backgroundTaskQueue, searchQueue=$searchQueue, recognizeFacesQueue=$recognizeFacesQueue, sidecarQueue=$sidecarQueue]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -81,6 +86,7 @@ class AllJobStatusResponseDto {
|
||||
json[r'background-task-queue'] = this.backgroundTaskQueue;
|
||||
json[r'search-queue'] = this.searchQueue;
|
||||
json[r'recognize-faces-queue'] = this.recognizeFacesQueue;
|
||||
json[r'sidecar-queue'] = this.sidecarQueue;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -112,6 +118,7 @@ class AllJobStatusResponseDto {
|
||||
backgroundTaskQueue: JobStatusDto.fromJson(json[r'background-task-queue'])!,
|
||||
searchQueue: JobStatusDto.fromJson(json[r'search-queue'])!,
|
||||
recognizeFacesQueue: JobStatusDto.fromJson(json[r'recognize-faces-queue'])!,
|
||||
sidecarQueue: JobStatusDto.fromJson(json[r'sidecar-queue'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -168,6 +175,7 @@ class AllJobStatusResponseDto {
|
||||
'background-task-queue',
|
||||
'search-queue',
|
||||
'recognize-faces-queue',
|
||||
'sidecar-queue',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
109
mobile/openapi/lib/model/asset_bulk_upload_check_dto.dart
generated
Normal file
109
mobile/openapi/lib/model/asset_bulk_upload_check_dto.dart
generated
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetBulkUploadCheckDto {
|
||||
/// Returns a new [AssetBulkUploadCheckDto] instance.
|
||||
AssetBulkUploadCheckDto({
|
||||
this.assets = const [],
|
||||
});
|
||||
|
||||
List<AssetBulkUploadCheckItem> assets;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckDto &&
|
||||
other.assets == assets;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(assets.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetBulkUploadCheckDto[assets=$assets]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'assets'] = this.assets;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetBulkUploadCheckDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetBulkUploadCheckDto? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
// Ensure that the map contains the required keys.
|
||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||
// Note 2: this code is stripped in release mode!
|
||||
assert(() {
|
||||
requiredKeys.forEach((key) {
|
||||
assert(json.containsKey(key), 'Required key "AssetBulkUploadCheckDto[$key]" is missing from JSON.');
|
||||
assert(json[key] != null, 'Required key "AssetBulkUploadCheckDto[$key]" has a null value in JSON.');
|
||||
});
|
||||
return true;
|
||||
}());
|
||||
|
||||
return AssetBulkUploadCheckDto(
|
||||
assets: AssetBulkUploadCheckItem.listFromJson(json[r'assets']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetBulkUploadCheckDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetBulkUploadCheckDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetBulkUploadCheckDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetBulkUploadCheckDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetBulkUploadCheckDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetBulkUploadCheckDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetBulkUploadCheckDto-objects as value to a dart map
|
||||
static Map<String, List<AssetBulkUploadCheckDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetBulkUploadCheckDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetBulkUploadCheckDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'assets',
|
||||
};
|
||||
}
|
||||
|
||||
117
mobile/openapi/lib/model/asset_bulk_upload_check_item.dart
generated
Normal file
117
mobile/openapi/lib/model/asset_bulk_upload_check_item.dart
generated
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetBulkUploadCheckItem {
|
||||
/// Returns a new [AssetBulkUploadCheckItem] instance.
|
||||
AssetBulkUploadCheckItem({
|
||||
required this.id,
|
||||
required this.checksum,
|
||||
});
|
||||
|
||||
String id;
|
||||
|
||||
String checksum;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckItem &&
|
||||
other.id == id &&
|
||||
other.checksum == checksum;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(id.hashCode) +
|
||||
(checksum.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetBulkUploadCheckItem[id=$id, checksum=$checksum]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'id'] = this.id;
|
||||
json[r'checksum'] = this.checksum;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetBulkUploadCheckItem] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetBulkUploadCheckItem? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
// Ensure that the map contains the required keys.
|
||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||
// Note 2: this code is stripped in release mode!
|
||||
assert(() {
|
||||
requiredKeys.forEach((key) {
|
||||
assert(json.containsKey(key), 'Required key "AssetBulkUploadCheckItem[$key]" is missing from JSON.');
|
||||
assert(json[key] != null, 'Required key "AssetBulkUploadCheckItem[$key]" has a null value in JSON.');
|
||||
});
|
||||
return true;
|
||||
}());
|
||||
|
||||
return AssetBulkUploadCheckItem(
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
checksum: mapValueOfType<String>(json, r'checksum')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetBulkUploadCheckItem> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetBulkUploadCheckItem>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetBulkUploadCheckItem.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetBulkUploadCheckItem> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetBulkUploadCheckItem>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetBulkUploadCheckItem.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetBulkUploadCheckItem-objects as value to a dart map
|
||||
static Map<String, List<AssetBulkUploadCheckItem>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetBulkUploadCheckItem>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetBulkUploadCheckItem.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'id',
|
||||
'checksum',
|
||||
};
|
||||
}
|
||||
|
||||
109
mobile/openapi/lib/model/asset_bulk_upload_check_response_dto.dart
generated
Normal file
109
mobile/openapi/lib/model/asset_bulk_upload_check_response_dto.dart
generated
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetBulkUploadCheckResponseDto {
|
||||
/// Returns a new [AssetBulkUploadCheckResponseDto] instance.
|
||||
AssetBulkUploadCheckResponseDto({
|
||||
this.results = const [],
|
||||
});
|
||||
|
||||
List<AssetBulkUploadCheckResult> results;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckResponseDto &&
|
||||
other.results == results;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(results.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetBulkUploadCheckResponseDto[results=$results]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'results'] = this.results;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetBulkUploadCheckResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetBulkUploadCheckResponseDto? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
// Ensure that the map contains the required keys.
|
||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||
// Note 2: this code is stripped in release mode!
|
||||
assert(() {
|
||||
requiredKeys.forEach((key) {
|
||||
assert(json.containsKey(key), 'Required key "AssetBulkUploadCheckResponseDto[$key]" is missing from JSON.');
|
||||
assert(json[key] != null, 'Required key "AssetBulkUploadCheckResponseDto[$key]" has a null value in JSON.');
|
||||
});
|
||||
return true;
|
||||
}());
|
||||
|
||||
return AssetBulkUploadCheckResponseDto(
|
||||
results: AssetBulkUploadCheckResult.listFromJson(json[r'results']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetBulkUploadCheckResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetBulkUploadCheckResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetBulkUploadCheckResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetBulkUploadCheckResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetBulkUploadCheckResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetBulkUploadCheckResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetBulkUploadCheckResponseDto-objects as value to a dart map
|
||||
static Map<String, List<AssetBulkUploadCheckResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetBulkUploadCheckResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetBulkUploadCheckResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'results',
|
||||
};
|
||||
}
|
||||
|
||||
293
mobile/openapi/lib/model/asset_bulk_upload_check_result.dart
generated
Normal file
293
mobile/openapi/lib/model/asset_bulk_upload_check_result.dart
generated
Normal file
@@ -0,0 +1,293 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetBulkUploadCheckResult {
|
||||
/// Returns a new [AssetBulkUploadCheckResult] instance.
|
||||
AssetBulkUploadCheckResult({
|
||||
required this.id,
|
||||
required this.action,
|
||||
this.reason,
|
||||
this.assetId,
|
||||
});
|
||||
|
||||
String id;
|
||||
|
||||
AssetBulkUploadCheckResultActionEnum action;
|
||||
|
||||
AssetBulkUploadCheckResultReasonEnum? reason;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
String? assetId;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckResult &&
|
||||
other.id == id &&
|
||||
other.action == action &&
|
||||
other.reason == reason &&
|
||||
other.assetId == assetId;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(id.hashCode) +
|
||||
(action.hashCode) +
|
||||
(reason == null ? 0 : reason!.hashCode) +
|
||||
(assetId == null ? 0 : assetId!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetBulkUploadCheckResult[id=$id, action=$action, reason=$reason, assetId=$assetId]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'id'] = this.id;
|
||||
json[r'action'] = this.action;
|
||||
if (this.reason != null) {
|
||||
json[r'reason'] = this.reason;
|
||||
} else {
|
||||
// json[r'reason'] = null;
|
||||
}
|
||||
if (this.assetId != null) {
|
||||
json[r'assetId'] = this.assetId;
|
||||
} else {
|
||||
// json[r'assetId'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetBulkUploadCheckResult] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetBulkUploadCheckResult? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
// Ensure that the map contains the required keys.
|
||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||
// Note 2: this code is stripped in release mode!
|
||||
assert(() {
|
||||
requiredKeys.forEach((key) {
|
||||
assert(json.containsKey(key), 'Required key "AssetBulkUploadCheckResult[$key]" is missing from JSON.');
|
||||
assert(json[key] != null, 'Required key "AssetBulkUploadCheckResult[$key]" has a null value in JSON.');
|
||||
});
|
||||
return true;
|
||||
}());
|
||||
|
||||
return AssetBulkUploadCheckResult(
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
action: AssetBulkUploadCheckResultActionEnum.fromJson(json[r'action'])!,
|
||||
reason: AssetBulkUploadCheckResultReasonEnum.fromJson(json[r'reason']),
|
||||
assetId: mapValueOfType<String>(json, r'assetId'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetBulkUploadCheckResult> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetBulkUploadCheckResult>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetBulkUploadCheckResult.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetBulkUploadCheckResult> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetBulkUploadCheckResult>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetBulkUploadCheckResult.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetBulkUploadCheckResult-objects as value to a dart map
|
||||
static Map<String, List<AssetBulkUploadCheckResult>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetBulkUploadCheckResult>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetBulkUploadCheckResult.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'id',
|
||||
'action',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
class AssetBulkUploadCheckResultActionEnum {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetBulkUploadCheckResultActionEnum._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const accept = AssetBulkUploadCheckResultActionEnum._(r'accept');
|
||||
static const reject = AssetBulkUploadCheckResultActionEnum._(r'reject');
|
||||
|
||||
/// List of all possible values in this [enum][AssetBulkUploadCheckResultActionEnum].
|
||||
static const values = <AssetBulkUploadCheckResultActionEnum>[
|
||||
accept,
|
||||
reject,
|
||||
];
|
||||
|
||||
static AssetBulkUploadCheckResultActionEnum? fromJson(dynamic value) => AssetBulkUploadCheckResultActionEnumTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetBulkUploadCheckResultActionEnum>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetBulkUploadCheckResultActionEnum>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetBulkUploadCheckResultActionEnum.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetBulkUploadCheckResultActionEnum] to String,
|
||||
/// and [decode] dynamic data back to [AssetBulkUploadCheckResultActionEnum].
|
||||
class AssetBulkUploadCheckResultActionEnumTypeTransformer {
|
||||
factory AssetBulkUploadCheckResultActionEnumTypeTransformer() => _instance ??= const AssetBulkUploadCheckResultActionEnumTypeTransformer._();
|
||||
|
||||
const AssetBulkUploadCheckResultActionEnumTypeTransformer._();
|
||||
|
||||
String encode(AssetBulkUploadCheckResultActionEnum data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetBulkUploadCheckResultActionEnum.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
AssetBulkUploadCheckResultActionEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'accept': return AssetBulkUploadCheckResultActionEnum.accept;
|
||||
case r'reject': return AssetBulkUploadCheckResultActionEnum.reject;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetBulkUploadCheckResultActionEnumTypeTransformer] instance.
|
||||
static AssetBulkUploadCheckResultActionEnumTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
class AssetBulkUploadCheckResultReasonEnum {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetBulkUploadCheckResultReasonEnum._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const duplicate = AssetBulkUploadCheckResultReasonEnum._(r'duplicate');
|
||||
static const unsupportedFormat = AssetBulkUploadCheckResultReasonEnum._(r'unsupported-format');
|
||||
|
||||
/// List of all possible values in this [enum][AssetBulkUploadCheckResultReasonEnum].
|
||||
static const values = <AssetBulkUploadCheckResultReasonEnum>[
|
||||
duplicate,
|
||||
unsupportedFormat,
|
||||
];
|
||||
|
||||
static AssetBulkUploadCheckResultReasonEnum? fromJson(dynamic value) => AssetBulkUploadCheckResultReasonEnumTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetBulkUploadCheckResultReasonEnum>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetBulkUploadCheckResultReasonEnum>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetBulkUploadCheckResultReasonEnum.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetBulkUploadCheckResultReasonEnum] to String,
|
||||
/// and [decode] dynamic data back to [AssetBulkUploadCheckResultReasonEnum].
|
||||
class AssetBulkUploadCheckResultReasonEnumTypeTransformer {
|
||||
factory AssetBulkUploadCheckResultReasonEnumTypeTransformer() => _instance ??= const AssetBulkUploadCheckResultReasonEnumTypeTransformer._();
|
||||
|
||||
const AssetBulkUploadCheckResultReasonEnumTypeTransformer._();
|
||||
|
||||
String encode(AssetBulkUploadCheckResultReasonEnum data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetBulkUploadCheckResultReasonEnum.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
AssetBulkUploadCheckResultReasonEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'duplicate': return AssetBulkUploadCheckResultReasonEnum.duplicate;
|
||||
case r'unsupported-format': return AssetBulkUploadCheckResultReasonEnum.unsupportedFormat;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetBulkUploadCheckResultReasonEnumTypeTransformer] instance.
|
||||
static AssetBulkUploadCheckResultReasonEnumTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ class GetAssetByTimeBucketDto {
|
||||
GetAssetByTimeBucketDto({
|
||||
this.timeBucket = const [],
|
||||
this.userId,
|
||||
this.withoutThumbs,
|
||||
});
|
||||
|
||||
List<String> timeBucket;
|
||||
@@ -27,19 +28,30 @@ class GetAssetByTimeBucketDto {
|
||||
///
|
||||
String? userId;
|
||||
|
||||
/// Include assets without thumbnails
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? withoutThumbs;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is GetAssetByTimeBucketDto &&
|
||||
other.timeBucket == timeBucket &&
|
||||
other.userId == userId;
|
||||
other.userId == userId &&
|
||||
other.withoutThumbs == withoutThumbs;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(timeBucket.hashCode) +
|
||||
(userId == null ? 0 : userId!.hashCode);
|
||||
(userId == null ? 0 : userId!.hashCode) +
|
||||
(withoutThumbs == null ? 0 : withoutThumbs!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'GetAssetByTimeBucketDto[timeBucket=$timeBucket, userId=$userId]';
|
||||
String toString() => 'GetAssetByTimeBucketDto[timeBucket=$timeBucket, userId=$userId, withoutThumbs=$withoutThumbs]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -49,6 +61,11 @@ class GetAssetByTimeBucketDto {
|
||||
} else {
|
||||
// json[r'userId'] = null;
|
||||
}
|
||||
if (this.withoutThumbs != null) {
|
||||
json[r'withoutThumbs'] = this.withoutThumbs;
|
||||
} else {
|
||||
// json[r'withoutThumbs'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -75,6 +92,7 @@ class GetAssetByTimeBucketDto {
|
||||
? (json[r'timeBucket'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
userId: mapValueOfType<String>(json, r'userId'),
|
||||
withoutThumbs: mapValueOfType<bool>(json, r'withoutThumbs'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
3
mobile/openapi/lib/model/job_name.dart
generated
3
mobile/openapi/lib/model/job_name.dart
generated
@@ -32,6 +32,7 @@ class JobName {
|
||||
static const backgroundTaskQueue = JobName._(r'background-task-queue');
|
||||
static const storageTemplateMigrationQueue = JobName._(r'storage-template-migration-queue');
|
||||
static const searchQueue = JobName._(r'search-queue');
|
||||
static const sidecarQueue = JobName._(r'sidecar-queue');
|
||||
|
||||
/// List of all possible values in this [enum][JobName].
|
||||
static const values = <JobName>[
|
||||
@@ -44,6 +45,7 @@ class JobName {
|
||||
backgroundTaskQueue,
|
||||
storageTemplateMigrationQueue,
|
||||
searchQueue,
|
||||
sidecarQueue,
|
||||
];
|
||||
|
||||
static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value);
|
||||
@@ -91,6 +93,7 @@ class JobNameTypeTransformer {
|
||||
case r'background-task-queue': return JobName.backgroundTaskQueue;
|
||||
case r'storage-template-migration-queue': return JobName.storageTemplateMigrationQueue;
|
||||
case r'search-queue': return JobName.searchQueue;
|
||||
case r'sidecar-queue': return JobName.sidecarQueue;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
|
||||
16
mobile/openapi/test/album_api_test.dart
generated
16
mobile/openapi/test/album_api_test.dart
generated
@@ -17,12 +17,12 @@ void main() {
|
||||
// final instance = AlbumApi();
|
||||
|
||||
group('tests for AlbumApi', () {
|
||||
//Future<AddAssetsResponseDto> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto, { String key }) async
|
||||
//Future<AddAssetsResponseDto> addAssetsToAlbum(String id, AddAssetsDto addAssetsDto, { String key }) async
|
||||
test('test addAssetsToAlbum', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<AlbumResponseDto> addUsersToAlbum(String albumId, AddUsersDto addUsersDto) async
|
||||
//Future<AlbumResponseDto> addUsersToAlbum(String id, AddUsersDto addUsersDto) async
|
||||
test('test addUsersToAlbum', () async {
|
||||
// TODO
|
||||
});
|
||||
@@ -37,12 +37,12 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future deleteAlbum(String albumId) async
|
||||
//Future deleteAlbum(String id) async
|
||||
test('test deleteAlbum', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<MultipartFile> downloadArchive(String albumId, { String name, num skip, String key }) async
|
||||
//Future<MultipartFile> downloadArchive(String id, { String name, num skip, String key }) async
|
||||
test('test downloadArchive', () async {
|
||||
// TODO
|
||||
});
|
||||
@@ -52,7 +52,7 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<AlbumResponseDto> getAlbumInfo(String albumId, { String key }) async
|
||||
//Future<AlbumResponseDto> getAlbumInfo(String id, { String key }) async
|
||||
test('test getAlbumInfo', () async {
|
||||
// TODO
|
||||
});
|
||||
@@ -62,17 +62,17 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<AlbumResponseDto> removeAssetFromAlbum(String albumId, RemoveAssetsDto removeAssetsDto) async
|
||||
//Future<AlbumResponseDto> removeAssetFromAlbum(String id, RemoveAssetsDto removeAssetsDto) async
|
||||
test('test removeAssetFromAlbum', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future removeUserFromAlbum(String albumId, String userId) async
|
||||
//Future removeUserFromAlbum(String id, String userId) async
|
||||
test('test removeUserFromAlbum', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<AlbumResponseDto> updateAlbumInfo(String albumId, UpdateAlbumDto updateAlbumDto) async
|
||||
//Future<AlbumResponseDto> updateAlbumInfo(String id, UpdateAlbumDto updateAlbumDto) async
|
||||
test('test updateAlbumInfo', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
@@ -61,6 +61,11 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// JobStatusDto sidecarQueue
|
||||
test('to test the property `sidecarQueue`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
13
mobile/openapi/test/asset_api_test.dart
generated
13
mobile/openapi/test/asset_api_test.dart
generated
@@ -22,6 +22,13 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// Checks if assets exist by checksums
|
||||
//
|
||||
//Future<AssetBulkUploadCheckResponseDto> bulkUploadCheck(AssetBulkUploadCheckDto assetBulkUploadCheckDto) async
|
||||
test('test bulkUploadCheck', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// Check duplicated asset before uploading - for Web upload used
|
||||
//
|
||||
//Future<CheckDuplicateAssetResponseDto> checkDuplicateAsset(CheckDuplicateAssetDto checkDuplicateAssetDto, { String key }) async
|
||||
@@ -65,7 +72,7 @@ void main() {
|
||||
|
||||
// Get all AssetEntity belong to the user
|
||||
//
|
||||
//Future<List<AssetResponseDto>> getAllAssets({ bool isFavorite, bool isArchived, num skip, String ifNoneMatch }) async
|
||||
//Future<List<AssetResponseDto>> getAllAssets({ String userId, bool isFavorite, bool isArchived, num skip, String ifNoneMatch }) async
|
||||
test('test getAllAssets', () async {
|
||||
// TODO
|
||||
});
|
||||
@@ -117,7 +124,7 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<List<MapMarkerResponseDto>> getMapMarkers({ bool isFavorite }) async
|
||||
//Future<List<MapMarkerResponseDto>> getMapMarkers({ bool isFavorite, DateTime fileCreatedAfter, DateTime fileCreatedBefore }) async
|
||||
test('test getMapMarkers', () async {
|
||||
// TODO
|
||||
});
|
||||
@@ -151,7 +158,7 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<AssetFileUploadResponseDto> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String key, MultipartFile livePhotoData, bool isArchived, bool isVisible, String duration }) async
|
||||
//Future<AssetFileUploadResponseDto> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String key, MultipartFile livePhotoData, MultipartFile sidecarData, bool isArchived, bool isVisible, String duration }) async
|
||||
test('test uploadFile', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
27
mobile/openapi/test/asset_bulk_upload_check_dto_test.dart
generated
Normal file
27
mobile/openapi/test/asset_bulk_upload_check_dto_test.dart
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// tests for AssetBulkUploadCheckDto
|
||||
void main() {
|
||||
// final instance = AssetBulkUploadCheckDto();
|
||||
|
||||
group('test AssetBulkUploadCheckDto', () {
|
||||
// List<AssetBulkUploadCheckItem> assets (default value: const [])
|
||||
test('to test the property `assets`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
32
mobile/openapi/test/asset_bulk_upload_check_item_test.dart
generated
Normal file
32
mobile/openapi/test/asset_bulk_upload_check_item_test.dart
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// tests for AssetBulkUploadCheckItem
|
||||
void main() {
|
||||
// final instance = AssetBulkUploadCheckItem();
|
||||
|
||||
group('test AssetBulkUploadCheckItem', () {
|
||||
// String id
|
||||
test('to test the property `id`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String checksum
|
||||
test('to test the property `checksum`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
27
mobile/openapi/test/asset_bulk_upload_check_response_dto_test.dart
generated
Normal file
27
mobile/openapi/test/asset_bulk_upload_check_response_dto_test.dart
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// tests for AssetBulkUploadCheckResponseDto
|
||||
void main() {
|
||||
// final instance = AssetBulkUploadCheckResponseDto();
|
||||
|
||||
group('test AssetBulkUploadCheckResponseDto', () {
|
||||
// List<AssetBulkUploadCheckResult> results (default value: const [])
|
||||
test('to test the property `results`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
42
mobile/openapi/test/asset_bulk_upload_check_result_test.dart
generated
Normal file
42
mobile/openapi/test/asset_bulk_upload_check_result_test.dart
generated
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// tests for AssetBulkUploadCheckResult
|
||||
void main() {
|
||||
// final instance = AssetBulkUploadCheckResult();
|
||||
|
||||
group('test AssetBulkUploadCheckResult', () {
|
||||
// String id
|
||||
test('to test the property `id`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String action
|
||||
test('to test the property `action`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String reason
|
||||
test('to test the property `reason`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String assetId
|
||||
test('to test the property `assetId`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
@@ -26,6 +26,12 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// Include assets without thumbnails
|
||||
// bool withoutThumbs
|
||||
test('to test the property `withoutThumbs`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -624,26 +624,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: isar
|
||||
sha256: "5be35dbc489880fccc535da3d1c4b3f5fdeee6ebfcacd4b149e39e803c4029cd"
|
||||
sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.1.0+1"
|
||||
isar_flutter_libs:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: isar_flutter_libs
|
||||
sha256: "9794524734856a8a3629652f9f359b66e3fea3cebeec4dbdeb3e3a8fb253073e"
|
||||
sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.1.0+1"
|
||||
isar_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: isar_generator
|
||||
sha256: ee4ab5d5b251bc7e86e1257793b57af100065831f00f3a12404b177ae53c2d69
|
||||
sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.1.0+1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -2,8 +2,8 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: "none"
|
||||
version: 1.57.0+80
|
||||
isar_version: &isar_version 3.0.5
|
||||
version: 1.58.0+81
|
||||
isar_version: &isar_version 3.1.0+1
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0-0 <4.0.0"
|
||||
|
||||
@@ -47,11 +47,20 @@ void main() {
|
||||
LoggerMessageSchema
|
||||
],
|
||||
maxSizeMiB: 256,
|
||||
directory: ".",
|
||||
);
|
||||
}
|
||||
|
||||
group('Test SyncService grouped', () {
|
||||
late final Isar db;
|
||||
final owner = User(
|
||||
id: "1",
|
||||
updatedAt: DateTime.now(),
|
||||
email: "a@b.c",
|
||||
firstName: "first",
|
||||
lastName: "last",
|
||||
isAdmin: false,
|
||||
);
|
||||
setUpAll(() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Isar.initializeIsarCore(download: true);
|
||||
@@ -59,17 +68,7 @@ void main() {
|
||||
ImmichLogger();
|
||||
db.writeTxnSync(() => db.clearSync());
|
||||
Store.init(db);
|
||||
await Store.put(
|
||||
StoreKey.currentUser,
|
||||
User(
|
||||
id: "1",
|
||||
updatedAt: DateTime.now(),
|
||||
email: "a@b.c",
|
||||
firstName: "first",
|
||||
lastName: "last",
|
||||
isAdmin: false,
|
||||
),
|
||||
);
|
||||
await Store.put(StoreKey.currentUser, owner);
|
||||
});
|
||||
final List<Asset> initialAssets = [
|
||||
makeAsset(localId: "1", remoteId: "0-1", deviceId: 0),
|
||||
@@ -92,7 +91,7 @@ void main() {
|
||||
makeAsset(localId: "1", remoteId: "1-1"),
|
||||
];
|
||||
expect(db.assets.countSync(), 5);
|
||||
final bool c1 = await s.syncRemoteAssetsToDb(() => remoteAssets);
|
||||
final bool c1 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||
expect(c1, false);
|
||||
expect(db.assets.countSync(), 5);
|
||||
});
|
||||
@@ -108,7 +107,7 @@ void main() {
|
||||
makeAsset(localId: "1", remoteId: "3-1", deviceId: 3),
|
||||
];
|
||||
expect(db.assets.countSync(), 5);
|
||||
final bool c1 = await s.syncRemoteAssetsToDb(() => remoteAssets);
|
||||
final bool c1 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||
expect(c1, true);
|
||||
expect(db.assets.countSync(), 7);
|
||||
});
|
||||
@@ -124,19 +123,19 @@ void main() {
|
||||
makeAsset(localId: "1", remoteId: "2-1d", deviceId: 2),
|
||||
];
|
||||
expect(db.assets.countSync(), 5);
|
||||
final bool c1 = await s.syncRemoteAssetsToDb(() => remoteAssets);
|
||||
final bool c1 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||
expect(c1, true);
|
||||
expect(db.assets.countSync(), 8);
|
||||
final bool c2 = await s.syncRemoteAssetsToDb(() => remoteAssets);
|
||||
final bool c2 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||
expect(c2, false);
|
||||
expect(db.assets.countSync(), 8);
|
||||
remoteAssets.removeAt(4);
|
||||
final bool c3 = await s.syncRemoteAssetsToDb(() => remoteAssets);
|
||||
final bool c3 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||
expect(c3, true);
|
||||
expect(db.assets.countSync(), 7);
|
||||
remoteAssets.add(makeAsset(localId: "1", remoteId: "2-1e", deviceId: 2));
|
||||
remoteAssets.add(makeAsset(localId: "2", remoteId: "2-2", deviceId: 2));
|
||||
final bool c4 = await s.syncRemoteAssetsToDb(() => remoteAssets);
|
||||
final bool c4 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||
expect(c4, true);
|
||||
expect(db.assets.countSync(), 9);
|
||||
});
|
||||
|
||||
@@ -39,3 +39,5 @@ RUN npm link && npm cache clean --force
|
||||
VOLUME /usr/src/app/upload
|
||||
|
||||
EXPOSE 3001
|
||||
|
||||
ENTRYPOINT ["/bin/sh"]
|
||||
|
||||
@@ -5,21 +5,16 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AddAssetsDto } from './dto/add-assets.dto';
|
||||
import { AddUsersDto } from './dto/add-users.dto';
|
||||
import { CreateAlbumDto } from './dto/create-album.dto';
|
||||
import { RemoveAssetsDto } from './dto/remove-assets.dto';
|
||||
import { UpdateAlbumDto } from './dto/update-album.dto';
|
||||
import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
|
||||
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
|
||||
|
||||
export interface IAlbumRepository {
|
||||
create(ownerId: string, createAlbumDto: CreateAlbumDto): Promise<AlbumEntity>;
|
||||
get(albumId: string): Promise<AlbumEntity | null>;
|
||||
delete(album: AlbumEntity): Promise<void>;
|
||||
addSharedUsers(album: AlbumEntity, addUsersDto: AddUsersDto): Promise<AlbumEntity>;
|
||||
removeUser(album: AlbumEntity, userId: string): Promise<void>;
|
||||
removeAssets(album: AlbumEntity, removeAssets: RemoveAssetsDto): Promise<number>;
|
||||
addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AddAssetsResponseDto>;
|
||||
updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity>;
|
||||
updateThumbnails(): Promise<number | undefined>;
|
||||
getCountByUserId(userId: string): Promise<AlbumCountResponseDto>;
|
||||
getSharedWithUserAlbumCount(userId: string, assetId: string): Promise<number>;
|
||||
@@ -45,19 +40,6 @@ export class AlbumRepository implements IAlbumRepository {
|
||||
return new AlbumCountResponseDto(ownedAlbums.length, sharedAlbums, sharedAlbumCount);
|
||||
}
|
||||
|
||||
async create(ownerId: string, dto: CreateAlbumDto): Promise<AlbumEntity> {
|
||||
const album = await this.albumRepository.save({
|
||||
ownerId,
|
||||
albumName: dto.albumName,
|
||||
sharedUsers: dto.sharedWithUserIds?.map((value) => ({ id: value } as UserEntity)) ?? [],
|
||||
assets: dto.assetIds?.map((value) => ({ id: value } as AssetEntity)) ?? [],
|
||||
albumThumbnailAssetId: dto.assetIds?.[0] || null,
|
||||
});
|
||||
|
||||
// need to re-load the relations
|
||||
return this.get(album.id) as Promise<AlbumEntity>;
|
||||
}
|
||||
|
||||
async get(albumId: string): Promise<AlbumEntity | null> {
|
||||
return this.albumRepository.findOne({
|
||||
where: { id: albumId },
|
||||
@@ -77,10 +59,6 @@ export class AlbumRepository implements IAlbumRepository {
|
||||
});
|
||||
}
|
||||
|
||||
async delete(album: AlbumEntity): Promise<void> {
|
||||
await this.albumRepository.delete({ id: album.id, ownerId: album.ownerId });
|
||||
}
|
||||
|
||||
async addSharedUsers(album: AlbumEntity, addUsersDto: AddUsersDto): Promise<AlbumEntity> {
|
||||
album.sharedUsers.push(...addUsersDto.sharedUserIds.map((id) => ({ id } as UserEntity)));
|
||||
album.updatedAt = new Date().toISOString();
|
||||
@@ -143,13 +121,6 @@ export class AlbumRepository implements IAlbumRepository {
|
||||
};
|
||||
}
|
||||
|
||||
updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity> {
|
||||
album.albumName = updateAlbumDto.albumName || album.albumName;
|
||||
album.albumThumbnailAssetId = updateAlbumDto.albumThumbnailAssetId || album.albumThumbnailAssetId;
|
||||
|
||||
return this.albumRepository.save(album);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure all thumbnails for albums are updated by:
|
||||
* - Removing thumbnails from albums without assets
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, Put, Query, Response } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Body, Param, Delete, Put, Query, Response } from '@nestjs/common';
|
||||
import { ParseMeUUIDPipe } from '../validation/parse-me-uuid-pipe';
|
||||
import { AlbumService } from './album.service';
|
||||
import { CreateAlbumDto } from './dto/create-album.dto';
|
||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||
import { AddAssetsDto } from './dto/add-assets.dto';
|
||||
import { AddUsersDto } from './dto/add-users.dto';
|
||||
import { RemoveAssetsDto } from './dto/remove-assets.dto';
|
||||
import { UpdateAlbumDto } from './dto/update-album.dto';
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { AlbumResponseDto } from '@app/domain';
|
||||
import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
|
||||
@@ -20,124 +18,91 @@ import {
|
||||
} from '../../constants/download.constant';
|
||||
import { DownloadDto } from '../asset/dto/download-library.dto';
|
||||
import { CreateAlbumShareLinkDto as CreateAlbumSharedLinkDto } from './dto/create-album-shared-link.dto';
|
||||
import { AlbumIdDto } from './dto/album-id.dto';
|
||||
import { UseValidation } from '../../decorators/use-validation.decorator';
|
||||
import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto';
|
||||
import { DownloadArchive } from '../../modules/download/download.service';
|
||||
|
||||
const handleDownload = (download: DownloadArchive, res: Res) => {
|
||||
res.attachment(download.fileName);
|
||||
res.setHeader(IMMICH_CONTENT_LENGTH_HINT, download.fileSize);
|
||||
res.setHeader(IMMICH_ARCHIVE_FILE_COUNT, download.fileCount);
|
||||
res.setHeader(IMMICH_ARCHIVE_COMPLETE, `${download.complete}`);
|
||||
return download.stream;
|
||||
};
|
||||
|
||||
@ApiTags('Album')
|
||||
@Controller('album')
|
||||
@UseValidation()
|
||||
export class AlbumController {
|
||||
constructor(private readonly albumService: AlbumService) {}
|
||||
constructor(private readonly service: AlbumService) {}
|
||||
|
||||
@Authenticated()
|
||||
@Get('count-by-user-id')
|
||||
async getAlbumCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise<AlbumCountResponseDto> {
|
||||
return this.albumService.getAlbumCountByUserId(authUser);
|
||||
getAlbumCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise<AlbumCountResponseDto> {
|
||||
return this.service.getCountByUserId(authUser);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post()
|
||||
async createAlbum(@GetAuthUser() authUser: AuthUserDto, @Body() createAlbumDto: CreateAlbumDto) {
|
||||
// TODO: Handle nonexistent sharedWithUserIds and assetIds.
|
||||
return this.albumService.create(authUser, createAlbumDto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Put('/:albumId/users')
|
||||
async addUsersToAlbum(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body() addUsersDto: AddUsersDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
) {
|
||||
@Put(':id/users')
|
||||
addUsersToAlbum(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto, @Body() dto: AddUsersDto) {
|
||||
// TODO: Handle nonexistent sharedUserIds.
|
||||
return this.albumService.addUsersToAlbum(authUser, addUsersDto, albumId);
|
||||
return this.service.addUsers(authUser, id, dto);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@Put('/:albumId/assets')
|
||||
async addAssetsToAlbum(
|
||||
@Put(':id/assets')
|
||||
addAssetsToAlbum(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body() addAssetsDto: AddAssetsDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: AddAssetsDto,
|
||||
): Promise<AddAssetsResponseDto> {
|
||||
// TODO: Handle nonexistent assetIds.
|
||||
// TODO: Disallow adding assets of another user to an album.
|
||||
return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId);
|
||||
return this.service.addAssets(authUser, id, dto);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@Get('/:albumId')
|
||||
async getAlbumInfo(@GetAuthUser() authUser: AuthUserDto, @Param() { albumId }: AlbumIdDto) {
|
||||
return this.albumService.getAlbumInfo(authUser, albumId);
|
||||
@Get(':id')
|
||||
getAlbumInfo(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.get(authUser, id);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete('/:albumId/assets')
|
||||
async removeAssetFromAlbum(
|
||||
@Delete(':id/assets')
|
||||
removeAssetFromAlbum(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body() removeAssetsDto: RemoveAssetsDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
@Body() dto: RemoveAssetsDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
): Promise<AlbumResponseDto> {
|
||||
return this.albumService.removeAssetsFromAlbum(authUser, removeAssetsDto, albumId);
|
||||
return this.service.removeAssets(authUser, id, dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete('/:albumId')
|
||||
async deleteAlbum(@GetAuthUser() authUser: AuthUserDto, @Param() { albumId }: AlbumIdDto) {
|
||||
return this.albumService.deleteAlbum(authUser, albumId);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete('/:albumId/user/:userId')
|
||||
async removeUserFromAlbum(
|
||||
@Delete(':id/user/:userId')
|
||||
removeUserFromAlbum(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string,
|
||||
) {
|
||||
return this.albumService.removeUserFromAlbum(authUser, albumId, userId);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Patch('/:albumId')
|
||||
async updateAlbumInfo(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body() updateAlbumInfoDto: UpdateAlbumDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
) {
|
||||
// TODO: Handle nonexistent albumThumbnailAssetId.
|
||||
// TODO: Disallow setting asset from other user as albumThumbnailAssetId.
|
||||
return this.albumService.updateAlbumInfo(authUser, updateAlbumInfoDto, albumId);
|
||||
return this.service.removeUser(authUser, id, userId);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@Get('/:albumId/download')
|
||||
@Get(':id/download')
|
||||
@ApiOkResponse({ content: { 'application/zip': { schema: { type: 'string', format: 'binary' } } } })
|
||||
async downloadArchive(
|
||||
downloadArchive(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Param() { albumId }: AlbumIdDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Query() dto: DownloadDto,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
) {
|
||||
this.albumService.checkDownloadAccess(authUser);
|
||||
|
||||
const { stream, fileName, fileSize, fileCount, complete } = await this.albumService.downloadArchive(
|
||||
authUser,
|
||||
albumId,
|
||||
dto,
|
||||
);
|
||||
res.attachment(fileName);
|
||||
res.setHeader(IMMICH_CONTENT_LENGTH_HINT, fileSize);
|
||||
res.setHeader(IMMICH_ARCHIVE_FILE_COUNT, fileCount);
|
||||
res.setHeader(IMMICH_ARCHIVE_COMPLETE, `${complete}`);
|
||||
return stream;
|
||||
this.service.checkDownloadAccess(authUser);
|
||||
return this.service.downloadArchive(authUser, id, dto).then((download) => handleDownload(download, res));
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('/create-shared-link')
|
||||
async createAlbumSharedLink(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body() createAlbumShareLinkDto: CreateAlbumSharedLinkDto,
|
||||
) {
|
||||
return this.albumService.createAlbumSharedLink(authUser, createAlbumShareLinkDto);
|
||||
@Post('create-shared-link')
|
||||
createAlbumSharedLink(@GetAuthUser() authUser: AuthUserDto, @Body() dto: CreateAlbumSharedLinkDto) {
|
||||
return this.service.createSharedLink(authUser, dto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AlbumService } from './album.service';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
|
||||
import { AlbumEntity, UserEntity } from '@app/infra/entities';
|
||||
import { AlbumResponseDto, ICryptoRepository, IJobRepository, JobName, mapUser } from '@app/domain';
|
||||
import { AlbumResponseDto, ICryptoRepository, IJobRepository, mapUser } from '@app/domain';
|
||||
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
|
||||
import { IAlbumRepository } from './album-repository';
|
||||
import { DownloadService } from '../../modules/download/download.service';
|
||||
@@ -121,12 +121,9 @@ describe('Album service', () => {
|
||||
albumRepositoryMock = {
|
||||
addAssets: jest.fn(),
|
||||
addSharedUsers: jest.fn(),
|
||||
create: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
get: jest.fn(),
|
||||
removeAssets: jest.fn(),
|
||||
removeUser: jest.fn(),
|
||||
updateAlbum: jest.fn(),
|
||||
updateThumbnails: jest.fn(),
|
||||
getCountByUserId: jest.fn(),
|
||||
getSharedWithUserAlbumCount: jest.fn(),
|
||||
@@ -150,19 +147,6 @@ describe('Album service', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('creates album', async () => {
|
||||
const albumEntity = _getOwnedAlbum();
|
||||
albumRepositoryMock.create.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
|
||||
const result = await sut.create(authUser, {
|
||||
albumName: albumEntity.albumName,
|
||||
});
|
||||
|
||||
expect(result.id).toEqual(albumEntity.id);
|
||||
expect(result.albumName).toEqual(albumEntity.albumName);
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [albumEntity.id] } });
|
||||
});
|
||||
|
||||
it('gets an owned album', async () => {
|
||||
const albumId = 'f19ab956-4761-41ea-a5d6-bae948308d58';
|
||||
|
||||
@@ -182,14 +166,14 @@ describe('Album service', () => {
|
||||
shared: false,
|
||||
assetCount: 0,
|
||||
};
|
||||
await expect(sut.getAlbumInfo(authUser, albumId)).resolves.toEqual(expectedResult);
|
||||
await expect(sut.get(authUser, albumId)).resolves.toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('gets a shared album', async () => {
|
||||
const albumEntity = _getSharedWithAuthUserAlbum();
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
|
||||
const result = await sut.getAlbumInfo(authUser, albumId);
|
||||
const result = await sut.get(authUser, albumId);
|
||||
expect(result.id).toEqual(albumId);
|
||||
expect(result.ownerId).toEqual(sharedAlbumOwnerId);
|
||||
expect(result.shared).toEqual(true);
|
||||
@@ -203,34 +187,19 @@ describe('Album service', () => {
|
||||
const albumId = albumEntity.id;
|
||||
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
await expect(sut.getAlbumInfo(authUser, albumId)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(sut.get(authUser, albumId)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
});
|
||||
|
||||
it('throws a not found exception if the album is not found', async () => {
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve(null));
|
||||
await expect(sut.getAlbumInfo(authUser, '0002')).rejects.toBeInstanceOf(NotFoundException);
|
||||
});
|
||||
|
||||
it('deletes an owned album', async () => {
|
||||
const albumEntity = _getOwnedAlbum();
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
albumRepositoryMock.delete.mockImplementation(() => Promise.resolve());
|
||||
await sut.deleteAlbum(authUser, albumId);
|
||||
expect(albumRepositoryMock.delete).toHaveBeenCalledTimes(1);
|
||||
expect(albumRepositoryMock.delete).toHaveBeenCalledWith(albumEntity);
|
||||
});
|
||||
|
||||
it('prevents deleting a shared album (shared with auth user)', async () => {
|
||||
const albumEntity = _getSharedWithAuthUserAlbum();
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
await expect(sut.deleteAlbum(authUser, albumId)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(sut.get(authUser, '0002')).rejects.toBeInstanceOf(NotFoundException);
|
||||
});
|
||||
|
||||
it('removes a shared user from an owned album', async () => {
|
||||
const albumEntity = _getOwnedSharedAlbum();
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
albumRepositoryMock.removeUser.mockImplementation(() => Promise.resolve());
|
||||
await expect(sut.removeUserFromAlbum(authUser, albumEntity.id, ownedAlbumSharedWithId)).resolves.toBeUndefined();
|
||||
await expect(sut.removeUser(authUser, albumEntity.id, ownedAlbumSharedWithId)).resolves.toBeUndefined();
|
||||
expect(albumRepositoryMock.removeUser).toHaveBeenCalledTimes(1);
|
||||
expect(albumRepositoryMock.removeUser).toHaveBeenCalledWith(albumEntity, ownedAlbumSharedWithId);
|
||||
});
|
||||
@@ -242,7 +211,7 @@ describe('Album service', () => {
|
||||
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
|
||||
await expect(sut.removeUserFromAlbum(authUser, albumId, userIdToRemove)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(sut.removeUser(authUser, albumId, userIdToRemove)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
expect(albumRepositoryMock.removeUser).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -251,7 +220,7 @@ describe('Album service', () => {
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
albumRepositoryMock.removeUser.mockImplementation(() => Promise.resolve());
|
||||
|
||||
await sut.removeUserFromAlbum(authUser, albumEntity.id, authUser.id);
|
||||
await sut.removeUser(authUser, albumEntity.id, authUser.id);
|
||||
expect(albumRepositoryMock.removeUser).toHaveReturnedTimes(1);
|
||||
expect(albumRepositoryMock.removeUser).toHaveBeenCalledWith(albumEntity, authUser.id);
|
||||
});
|
||||
@@ -261,7 +230,7 @@ describe('Album service', () => {
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
albumRepositoryMock.removeUser.mockImplementation(() => Promise.resolve());
|
||||
|
||||
await sut.removeUserFromAlbum(authUser, albumEntity.id, 'me');
|
||||
await sut.removeUser(authUser, albumEntity.id, 'me');
|
||||
expect(albumRepositoryMock.removeUser).toHaveReturnedTimes(1);
|
||||
expect(albumRepositoryMock.removeUser).toHaveBeenCalledWith(albumEntity, authUser.id);
|
||||
});
|
||||
@@ -270,55 +239,7 @@ describe('Album service', () => {
|
||||
const albumEntity = _getOwnedAlbum();
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
|
||||
await expect(sut.removeUserFromAlbum(authUser, albumEntity.id, authUser.id)).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
});
|
||||
|
||||
it('updates a owned album', async () => {
|
||||
const albumEntity = _getOwnedAlbum();
|
||||
const albumId = albumEntity.id;
|
||||
const updatedAlbumName = 'new album name';
|
||||
const updatedAlbumThumbnailAssetId = '69d2f917-0b31-48d8-9d7d-673b523f1aac';
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
const updatedAlbum = { ...albumEntity, albumName: updatedAlbumName };
|
||||
albumRepositoryMock.updateAlbum.mockResolvedValue(updatedAlbum);
|
||||
|
||||
const result = await sut.updateAlbumInfo(
|
||||
authUser,
|
||||
{
|
||||
albumName: updatedAlbumName,
|
||||
albumThumbnailAssetId: updatedAlbumThumbnailAssetId,
|
||||
},
|
||||
albumId,
|
||||
);
|
||||
|
||||
expect(result.id).toEqual(albumId);
|
||||
expect(result.albumName).toEqual(updatedAlbumName);
|
||||
expect(albumRepositoryMock.updateAlbum).toHaveBeenCalledTimes(1);
|
||||
expect(albumRepositoryMock.updateAlbum).toHaveBeenCalledWith(albumEntity, {
|
||||
albumName: updatedAlbumName,
|
||||
albumThumbnailAssetId: updatedAlbumThumbnailAssetId,
|
||||
});
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [updatedAlbum.id] } });
|
||||
});
|
||||
|
||||
it('prevents updating a not owned album (shared with auth user)', async () => {
|
||||
const albumEntity = _getSharedWithAuthUserAlbum();
|
||||
const albumId = albumEntity.id;
|
||||
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
|
||||
await expect(
|
||||
sut.updateAlbumInfo(
|
||||
authUser,
|
||||
{
|
||||
albumName: 'new album name',
|
||||
albumThumbnailAssetId: '69d2f917-0b31-48d8-9d7d-673b523f1aac',
|
||||
},
|
||||
albumId,
|
||||
),
|
||||
).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(sut.removeUser(authUser, albumEntity.id, authUser.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
|
||||
it('adds assets to owned album', async () => {
|
||||
@@ -334,13 +255,7 @@ describe('Album service', () => {
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
|
||||
|
||||
const result = (await sut.addAssetsToAlbum(
|
||||
authUser,
|
||||
{
|
||||
assetIds: ['1'],
|
||||
},
|
||||
albumId,
|
||||
)) as AddAssetsResponseDto;
|
||||
const result = (await sut.addAssets(authUser, albumId, { assetIds: ['1'] })) as AddAssetsResponseDto;
|
||||
|
||||
// TODO: stub and expect album rendered
|
||||
expect(result.album?.id).toEqual(albumId);
|
||||
@@ -359,13 +274,7 @@ describe('Album service', () => {
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
|
||||
|
||||
const result = (await sut.addAssetsToAlbum(
|
||||
authUser,
|
||||
{
|
||||
assetIds: ['1'],
|
||||
},
|
||||
albumId,
|
||||
)) as AddAssetsResponseDto;
|
||||
const result = (await sut.addAssets(authUser, albumId, { assetIds: ['1'] })) as AddAssetsResponseDto;
|
||||
|
||||
// TODO: stub and expect album rendered
|
||||
expect(result.album?.id).toEqual(albumId);
|
||||
@@ -384,15 +293,7 @@ describe('Album service', () => {
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
|
||||
|
||||
await expect(
|
||||
sut.addAssetsToAlbum(
|
||||
authUser,
|
||||
{
|
||||
assetIds: ['1'],
|
||||
},
|
||||
albumId,
|
||||
),
|
||||
).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(sut.addAssets(authUser, albumId, { assetIds: ['1'] })).rejects.toBeInstanceOf(ForbiddenException);
|
||||
});
|
||||
|
||||
// it('removes assets from owned album', async () => {
|
||||
@@ -448,14 +349,6 @@ describe('Album service', () => {
|
||||
albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
|
||||
|
||||
await expect(
|
||||
sut.removeAssetsFromAlbum(
|
||||
authUser,
|
||||
{
|
||||
assetIds: ['1'],
|
||||
},
|
||||
albumId,
|
||||
),
|
||||
).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(sut.removeAssets(authUser, albumId, { assetIds: ['1'] })).rejects.toBeInstanceOf(ForbiddenException);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { BadRequestException, Inject, Injectable, NotFoundException, ForbiddenException, Logger } from '@nestjs/common';
|
||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||
import { CreateAlbumDto } from './dto/create-album.dto';
|
||||
import { AlbumEntity, SharedLinkType } from '@app/infra/entities';
|
||||
import { AddUsersDto } from './dto/add-users.dto';
|
||||
import { RemoveAssetsDto } from './dto/remove-assets.dto';
|
||||
import { UpdateAlbumDto } from './dto/update-album.dto';
|
||||
import { AlbumResponseDto, IJobRepository, JobName, mapAlbum } from '@app/domain';
|
||||
import { AlbumResponseDto, IJobRepository, mapAlbum } from '@app/domain';
|
||||
import { IAlbumRepository } from './album-repository';
|
||||
import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
|
||||
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
|
||||
@@ -55,35 +53,18 @@ export class AlbumService {
|
||||
return album;
|
||||
}
|
||||
|
||||
async create(authUser: AuthUserDto, createAlbumDto: CreateAlbumDto): Promise<AlbumResponseDto> {
|
||||
const albumEntity = await this.albumRepository.create(authUser.id, createAlbumDto);
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [albumEntity.id] } });
|
||||
return mapAlbum(albumEntity);
|
||||
}
|
||||
|
||||
async getAlbumInfo(authUser: AuthUserDto, albumId: string): Promise<AlbumResponseDto> {
|
||||
async get(authUser: AuthUserDto, albumId: string): Promise<AlbumResponseDto> {
|
||||
const album = await this._getAlbum({ authUser, albumId, validateIsOwner: false });
|
||||
return mapAlbum(album);
|
||||
}
|
||||
|
||||
async addUsersToAlbum(authUser: AuthUserDto, addUsersDto: AddUsersDto, albumId: string): Promise<AlbumResponseDto> {
|
||||
async addUsers(authUser: AuthUserDto, albumId: string, dto: AddUsersDto): Promise<AlbumResponseDto> {
|
||||
const album = await this._getAlbum({ authUser, albumId });
|
||||
const updatedAlbum = await this.albumRepository.addSharedUsers(album, addUsersDto);
|
||||
const updatedAlbum = await this.albumRepository.addSharedUsers(album, dto);
|
||||
return mapAlbum(updatedAlbum);
|
||||
}
|
||||
|
||||
async deleteAlbum(authUser: AuthUserDto, albumId: string): Promise<void> {
|
||||
const album = await this._getAlbum({ authUser, albumId });
|
||||
|
||||
for (const sharedLink of album.sharedLinks) {
|
||||
await this.shareCore.remove(authUser.id, sharedLink.id);
|
||||
}
|
||||
|
||||
await this.albumRepository.delete(album);
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_ALBUM, data: { ids: [albumId] } });
|
||||
}
|
||||
|
||||
async removeUserFromAlbum(authUser: AuthUserDto, albumId: string, userId: string | 'me'): Promise<void> {
|
||||
async removeUser(authUser: AuthUserDto, albumId: string, userId: string | 'me'): Promise<void> {
|
||||
const sharedUserId = userId == 'me' ? authUser.id : userId;
|
||||
const album = await this._getAlbum({ authUser, albumId, validateIsOwner: false });
|
||||
if (album.ownerId != authUser.id && authUser.id != sharedUserId) {
|
||||
@@ -95,34 +76,26 @@ export class AlbumService {
|
||||
await this.albumRepository.removeUser(album, sharedUserId);
|
||||
}
|
||||
|
||||
async removeAssetsFromAlbum(
|
||||
authUser: AuthUserDto,
|
||||
removeAssetsDto: RemoveAssetsDto,
|
||||
albumId: string,
|
||||
): Promise<AlbumResponseDto> {
|
||||
async removeAssets(authUser: AuthUserDto, albumId: string, dto: RemoveAssetsDto): Promise<AlbumResponseDto> {
|
||||
const album = await this._getAlbum({ authUser, albumId });
|
||||
const deletedCount = await this.albumRepository.removeAssets(album, removeAssetsDto);
|
||||
const deletedCount = await this.albumRepository.removeAssets(album, dto);
|
||||
const newAlbum = await this._getAlbum({ authUser, albumId });
|
||||
|
||||
if (deletedCount !== removeAssetsDto.assetIds.length) {
|
||||
if (deletedCount !== dto.assetIds.length) {
|
||||
throw new BadRequestException('Some assets were not found in the album');
|
||||
}
|
||||
|
||||
return mapAlbum(newAlbum);
|
||||
}
|
||||
|
||||
async addAssetsToAlbum(
|
||||
authUser: AuthUserDto,
|
||||
addAssetsDto: AddAssetsDto,
|
||||
albumId: string,
|
||||
): Promise<AddAssetsResponseDto> {
|
||||
async addAssets(authUser: AuthUserDto, albumId: string, dto: AddAssetsDto): Promise<AddAssetsResponseDto> {
|
||||
if (authUser.isPublicUser && !authUser.isAllowUpload) {
|
||||
this.logger.warn('Deny public user attempt to add asset to album');
|
||||
throw new ForbiddenException('Public user is not allowed to upload');
|
||||
}
|
||||
|
||||
const album = await this._getAlbum({ authUser, albumId, validateIsOwner: false });
|
||||
const result = await this.albumRepository.addAssets(album, addAssetsDto);
|
||||
const result = await this.albumRepository.addAssets(album, dto);
|
||||
const newAlbum = await this._getAlbum({ authUser, albumId, validateIsOwner: false });
|
||||
|
||||
return {
|
||||
@@ -131,25 +104,7 @@ export class AlbumService {
|
||||
};
|
||||
}
|
||||
|
||||
async updateAlbumInfo(
|
||||
authUser: AuthUserDto,
|
||||
updateAlbumDto: UpdateAlbumDto,
|
||||
albumId: string,
|
||||
): Promise<AlbumResponseDto> {
|
||||
const album = await this._getAlbum({ authUser, albumId });
|
||||
|
||||
if (authUser.id != album.ownerId) {
|
||||
throw new BadRequestException('Unauthorized to change album info');
|
||||
}
|
||||
|
||||
const updatedAlbum = await this.albumRepository.updateAlbum(album, updateAlbumDto);
|
||||
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ALBUM, data: { ids: [updatedAlbum.id] } });
|
||||
|
||||
return mapAlbum(updatedAlbum);
|
||||
}
|
||||
|
||||
async getAlbumCountByUserId(authUser: AuthUserDto): Promise<AlbumCountResponseDto> {
|
||||
async getCountByUserId(authUser: AuthUserDto): Promise<AlbumCountResponseDto> {
|
||||
return this.albumRepository.getCountByUserId(authUser.id);
|
||||
}
|
||||
|
||||
@@ -160,7 +115,7 @@ export class AlbumService {
|
||||
return this.downloadService.downloadArchive(album.albumName, assets);
|
||||
}
|
||||
|
||||
async createAlbumSharedLink(authUser: AuthUserDto, dto: CreateAlbumShareLinkDto): Promise<SharedLinkResponseDto> {
|
||||
async createSharedLink(authUser: AuthUserDto, dto: CreateAlbumShareLinkDto): Promise<SharedLinkResponseDto> {
|
||||
const album = await this._getAlbum({ authUser, albumId: dto.albumId });
|
||||
|
||||
const sharedLink = await this.shareCore.create(authUser.id, {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { ValidateUUID } from 'apps/immich/src/decorators/validate-uuid.decorator';
|
||||
|
||||
export class AlbumIdDto {
|
||||
@ValidateUUID()
|
||||
albumId!: string;
|
||||
}
|
||||
@@ -10,13 +10,17 @@ import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
|
||||
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
||||
import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
|
||||
import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto';
|
||||
import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto';
|
||||
import { In } from 'typeorm/find-options/operator/In';
|
||||
import { UpdateAssetDto } from './dto/update-asset.dto';
|
||||
import { ITagRepository } from '../tag/tag.repository';
|
||||
import { IsNull, Not } from 'typeorm';
|
||||
import { AssetSearchDto } from './dto/asset-search.dto';
|
||||
|
||||
export interface AssetCheck {
|
||||
id: string;
|
||||
checksum: Buffer;
|
||||
}
|
||||
|
||||
export interface IAssetRepository {
|
||||
get(id: string): Promise<AssetEntity | null>;
|
||||
create(
|
||||
@@ -38,11 +42,8 @@ export interface IAssetRepository {
|
||||
getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>;
|
||||
getArchivedAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>;
|
||||
getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>;
|
||||
getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity>;
|
||||
getExistingAssets(
|
||||
userId: string,
|
||||
checkDuplicateAssetDto: CheckExistingAssetsDto,
|
||||
): Promise<CheckExistingAssetsResponseDto>;
|
||||
getAssetsByChecksums(userId: string, checksums: Buffer[]): Promise<AssetCheck[]>;
|
||||
getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise<string[]>;
|
||||
countByIdAndUser(assetId: string, userId: string): Promise<number>;
|
||||
}
|
||||
|
||||
@@ -103,19 +104,23 @@ export class AssetRepository implements IAssetRepository {
|
||||
return this.getAssetCount(items);
|
||||
}
|
||||
|
||||
async getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]> {
|
||||
async getAssetByTimeBucket(userId: string, dto: GetAssetByTimeBucketDto): Promise<AssetEntity[]> {
|
||||
// Get asset entity from a list of time buckets
|
||||
return await this.assetRepository
|
||||
let builder = this.assetRepository
|
||||
.createQueryBuilder('asset')
|
||||
.where('asset.ownerId = :userId', { userId: userId })
|
||||
.andWhere(`date_trunc('month', "fileCreatedAt") IN (:...buckets)`, {
|
||||
buckets: [...getAssetByTimeBucketDto.timeBucket],
|
||||
buckets: [...dto.timeBucket],
|
||||
})
|
||||
.andWhere('asset.resizePath is not NULL')
|
||||
.andWhere('asset.isVisible = true')
|
||||
.andWhere('asset.isArchived = false')
|
||||
.orderBy('asset.fileCreatedAt', 'DESC')
|
||||
.getMany();
|
||||
.orderBy('asset.fileCreatedAt', 'DESC');
|
||||
|
||||
if (!dto.withoutThumbs) {
|
||||
builder = builder.andWhere('asset.resizePath is not NULL');
|
||||
}
|
||||
|
||||
return builder.getMany();
|
||||
}
|
||||
|
||||
async getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum) {
|
||||
@@ -310,41 +315,39 @@ export class AssetRepository implements IAssetRepository {
|
||||
* @returns Promise<string[]> - Array of assetIds belong to the device
|
||||
*/
|
||||
async getAllByDeviceId(ownerId: string, deviceId: string): Promise<string[]> {
|
||||
const rows = await this.assetRepository.find({
|
||||
const items = await this.assetRepository.find({
|
||||
select: { deviceAssetId: true },
|
||||
where: {
|
||||
ownerId,
|
||||
deviceId,
|
||||
isVisible: true,
|
||||
},
|
||||
select: ['deviceAssetId'],
|
||||
});
|
||||
const res: string[] = [];
|
||||
rows.forEach((v) => res.push(v.deviceAssetId));
|
||||
|
||||
return res;
|
||||
return items.map((asset) => asset.deviceAssetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get asset by checksum on the database
|
||||
* Get assets by checksums on the database
|
||||
* @param ownerId
|
||||
* @param checksum
|
||||
* @param checksums
|
||||
*
|
||||
*/
|
||||
getAssetByChecksum(ownerId: string, checksum: Buffer): Promise<AssetEntity> {
|
||||
return this.assetRepository.findOneOrFail({
|
||||
async getAssetsByChecksums(ownerId: string, checksums: Buffer[]): Promise<AssetCheck[]> {
|
||||
return this.assetRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
checksum: true,
|
||||
},
|
||||
where: {
|
||||
ownerId,
|
||||
checksum,
|
||||
checksum: In(checksums),
|
||||
},
|
||||
relations: ['exifInfo'],
|
||||
});
|
||||
}
|
||||
|
||||
async getExistingAssets(
|
||||
ownerId: string,
|
||||
checkDuplicateAssetDto: CheckExistingAssetsDto,
|
||||
): Promise<CheckExistingAssetsResponseDto> {
|
||||
const existingAssets = await this.assetRepository.find({
|
||||
async getExistingAssets(ownerId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise<string[]> {
|
||||
const assets = await this.assetRepository.find({
|
||||
select: { deviceAssetId: true },
|
||||
where: {
|
||||
deviceAssetId: In(checkDuplicateAssetDto.deviceAssetIds),
|
||||
@@ -352,7 +355,7 @@ export class AssetRepository implements IAssetRepository {
|
||||
ownerId,
|
||||
},
|
||||
});
|
||||
return new CheckExistingAssetsResponseDto(existingAssets.map((a) => a.deviceAssetId));
|
||||
return assets.map((asset) => asset.deviceAssetId);
|
||||
}
|
||||
|
||||
async countByIdAndUser(assetId: string, ownerId: string): Promise<number> {
|
||||
|
||||
@@ -57,6 +57,8 @@ import { AssetSearchDto } from './dto/asset-search.dto';
|
||||
import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config';
|
||||
import FileNotEmptyValidator from '../validation/file-not-empty-validator';
|
||||
import { RemoveAssetsDto } from '../album/dto/remove-assets.dto';
|
||||
import { AssetBulkUploadCheckDto } from './dto/asset-check.dto';
|
||||
import { AssetBulkUploadCheckResponseDto } from './response-dto/asset-check-response.dto';
|
||||
import { AssetIdDto } from './dto/asset-id.dto';
|
||||
import { DeviceIdDto } from './dto/device-id.dto';
|
||||
|
||||
@@ -76,6 +78,7 @@ export class AssetController {
|
||||
[
|
||||
{ name: 'assetData', maxCount: 1 },
|
||||
{ name: 'livePhotoData', maxCount: 1 },
|
||||
{ name: 'sidecarData', maxCount: 1 },
|
||||
],
|
||||
assetUploadOption,
|
||||
),
|
||||
@@ -88,18 +91,24 @@ export class AssetController {
|
||||
async uploadFile(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator(['assetData'])] }))
|
||||
files: { assetData: ImmichFile[]; livePhotoData?: ImmichFile[] },
|
||||
files: { assetData: ImmichFile[]; livePhotoData?: ImmichFile[]; sidecarData: ImmichFile[] },
|
||||
@Body(new ValidationPipe()) dto: CreateAssetDto,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
): Promise<AssetFileUploadResponseDto> {
|
||||
const file = mapToUploadFile(files.assetData[0]);
|
||||
const _livePhotoFile = files.livePhotoData?.[0];
|
||||
const _sidecarFile = files.sidecarData?.[0];
|
||||
let livePhotoFile;
|
||||
if (_livePhotoFile) {
|
||||
livePhotoFile = mapToUploadFile(_livePhotoFile);
|
||||
}
|
||||
|
||||
const responseDto = await this.assetService.uploadFile(authUser, dto, file, livePhotoFile);
|
||||
let sidecarFile;
|
||||
if (_sidecarFile) {
|
||||
sidecarFile = mapToUploadFile(_sidecarFile);
|
||||
}
|
||||
|
||||
const responseDto = await this.assetService.uploadFile(authUser, dto, file, livePhotoFile, sidecarFile);
|
||||
if (responseDto.duplicate) {
|
||||
res.status(200);
|
||||
}
|
||||
@@ -332,6 +341,19 @@ export class AssetController {
|
||||
return await this.assetService.checkExistingAssets(authUser, checkExistingAssetsDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if assets exist by checksums
|
||||
*/
|
||||
@Authenticated()
|
||||
@Post('/bulk-upload-check')
|
||||
@HttpCode(200)
|
||||
bulkUploadCheck(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) dto: AssetBulkUploadCheckDto,
|
||||
): Promise<AssetBulkUploadCheckResponseDto> {
|
||||
return this.assetService.bulkUploadCheck(authUser, dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('/shared-link')
|
||||
async createAssetsSharedLink(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AuthUserDto, IJobRepository, JobName } from '@app/domain';
|
||||
import { AssetEntity, UserEntity } from '@app/infra/entities';
|
||||
import { AssetEntity, AssetType, UserEntity } from '@app/infra/entities';
|
||||
import { IAssetRepository } from './asset-repository';
|
||||
import { CreateAssetDto, UploadFile } from './dto/create-asset.dto';
|
||||
import { parse } from 'node:path';
|
||||
@@ -12,12 +12,13 @@ export class AssetCore {
|
||||
dto: CreateAssetDto,
|
||||
file: UploadFile,
|
||||
livePhotoAssetId?: string,
|
||||
sidecarFile?: UploadFile,
|
||||
): Promise<AssetEntity> {
|
||||
const asset = await this.repository.create({
|
||||
owner: { id: authUser.id } as UserEntity,
|
||||
|
||||
mimeType: file.mimeType,
|
||||
checksum: file.checksum || null,
|
||||
checksum: file.checksum,
|
||||
originalPath: file.originalPath,
|
||||
|
||||
deviceAssetId: dto.deviceAssetId,
|
||||
@@ -39,9 +40,13 @@ export class AssetCore {
|
||||
sharedLinks: [],
|
||||
originalFileName: parse(file.originalName).name,
|
||||
faces: [],
|
||||
sidecarPath: sidecarFile?.originalPath || null,
|
||||
});
|
||||
|
||||
await this.jobRepository.queue({ name: JobName.ASSET_UPLOADED, data: { asset } });
|
||||
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: asset.id, source: 'upload' } });
|
||||
if (asset.type === AssetType.VIDEO) {
|
||||
await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { id: asset.id } });
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ describe('AssetService', () => {
|
||||
getLocationsByUserId: jest.fn(),
|
||||
getSearchPropertiesByUserId: jest.fn(),
|
||||
getAssetByTimeBucket: jest.fn(),
|
||||
getAssetByChecksum: jest.fn(),
|
||||
getAssetsByChecksums: jest.fn(),
|
||||
getAssetCountByUserId: jest.fn(),
|
||||
getArchivedAssetCountByUserId: jest.fn(),
|
||||
getExistingAssets: jest.fn(),
|
||||
@@ -299,13 +299,13 @@ describe('AssetService', () => {
|
||||
(error as any).constraint = 'UQ_userid_checksum';
|
||||
|
||||
assetRepositoryMock.create.mockRejectedValue(error);
|
||||
assetRepositoryMock.getAssetByChecksum.mockResolvedValue(_getAsset_1());
|
||||
assetRepositoryMock.getAssetsByChecksums.mockResolvedValue([_getAsset_1()]);
|
||||
|
||||
await expect(sut.uploadFile(authStub.user1, dto, file)).resolves.toEqual({ duplicate: true, id: 'id_1' });
|
||||
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({
|
||||
name: JobName.DELETE_FILES,
|
||||
data: { files: ['fake_path/asset_1.jpeg', undefined] },
|
||||
data: { files: ['fake_path/asset_1.jpeg', undefined, undefined] },
|
||||
});
|
||||
expect(storageMock.moveFile).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -328,8 +328,14 @@ describe('AssetService', () => {
|
||||
});
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[{ name: JobName.ASSET_UPLOADED, data: { asset: assetEntityStub.livePhotoMotionAsset } }],
|
||||
[{ name: JobName.ASSET_UPLOADED, data: { asset: assetEntityStub.livePhotoStillAsset } }],
|
||||
[
|
||||
{
|
||||
name: JobName.METADATA_EXTRACTION,
|
||||
data: { id: assetEntityStub.livePhotoMotionAsset.id, source: 'upload' },
|
||||
},
|
||||
],
|
||||
[{ name: JobName.VIDEO_CONVERSION, data: { id: assetEntityStub.livePhotoMotionAsset.id } }],
|
||||
[{ name: JobName.METADATA_EXTRACTION, data: { id: assetEntityStub.livePhotoStillAsset.id, source: 'upload' } }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -413,10 +419,12 @@ describe('AssetService', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
'fake_path/asset_1.mp4',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -462,10 +470,12 @@ describe('AssetService', () => {
|
||||
'web-path-1',
|
||||
'resize-path-1',
|
||||
undefined,
|
||||
undefined,
|
||||
'original-path-2',
|
||||
'web-path-2',
|
||||
'resize-path-2',
|
||||
'encoded-video-path-2',
|
||||
undefined,
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -63,6 +63,12 @@ import { mapSharedLink, SharedLinkResponseDto } from '@app/domain';
|
||||
import { AssetSearchDto } from './dto/asset-search.dto';
|
||||
import { AddAssetsDto } from '../album/dto/add-assets.dto';
|
||||
import { RemoveAssetsDto } from '../album/dto/remove-assets.dto';
|
||||
import { AssetBulkUploadCheckDto } from './dto/asset-check.dto';
|
||||
import {
|
||||
AssetUploadAction,
|
||||
AssetRejectReason,
|
||||
AssetBulkUploadCheckResponseDto,
|
||||
} from './response-dto/asset-check-response.dto';
|
||||
|
||||
const fileInfo = promisify(stat);
|
||||
|
||||
@@ -100,6 +106,7 @@ export class AssetService {
|
||||
dto: CreateAssetDto,
|
||||
file: UploadFile,
|
||||
livePhotoFile?: UploadFile,
|
||||
sidecarFile?: UploadFile,
|
||||
): Promise<AssetFileUploadResponseDto> {
|
||||
if (livePhotoFile) {
|
||||
livePhotoFile = {
|
||||
@@ -116,19 +123,20 @@ export class AssetService {
|
||||
livePhotoAsset = await this.assetCore.create(authUser, livePhotoDto, livePhotoFile);
|
||||
}
|
||||
|
||||
const asset = await this.assetCore.create(authUser, dto, file, livePhotoAsset?.id);
|
||||
const asset = await this.assetCore.create(authUser, dto, file, livePhotoAsset?.id, sidecarFile);
|
||||
|
||||
return { id: asset.id, duplicate: false };
|
||||
} catch (error: any) {
|
||||
// clean up files
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.DELETE_FILES,
|
||||
data: { files: [file.originalPath, livePhotoFile?.originalPath] },
|
||||
data: { files: [file.originalPath, livePhotoFile?.originalPath, sidecarFile?.originalPath] },
|
||||
});
|
||||
|
||||
// handle duplicates with a success response
|
||||
if (error instanceof QueryFailedError && (error as any).constraint === 'UQ_userid_checksum') {
|
||||
const duplicate = await this.getAssetByChecksum(authUser.id, file.checksum);
|
||||
const checksums = [file.checksum, livePhotoFile?.checksum].filter((checksum): checksum is Buffer => !!checksum);
|
||||
const [duplicate] = await this._assetRepository.getAssetsByChecksums(authUser.id, checksums);
|
||||
return { id: duplicate.id, duplicate: true };
|
||||
}
|
||||
|
||||
@@ -142,7 +150,10 @@ export class AssetService {
|
||||
}
|
||||
|
||||
public async getAllAssets(authUser: AuthUserDto, dto: AssetSearchDto): Promise<AssetResponseDto[]> {
|
||||
const assets = await this._assetRepository.getAllByUserId(authUser.id, dto);
|
||||
if (dto.userId && dto.userId !== authUser.id) {
|
||||
await this.checkUserAccess(authUser, dto.userId);
|
||||
}
|
||||
const assets = await this._assetRepository.getAllByUserId(dto.userId || authUser.id, dto);
|
||||
|
||||
return assets.map((asset) => mapAsset(asset));
|
||||
}
|
||||
@@ -359,7 +370,13 @@ export class AssetService {
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: [id] } });
|
||||
|
||||
result.push({ id, status: DeleteAssetStatusEnum.SUCCESS });
|
||||
deleteQueue.push(asset.originalPath, asset.webpPath, asset.resizePath, asset.encodedVideoPath);
|
||||
deleteQueue.push(
|
||||
asset.originalPath,
|
||||
asset.webpPath,
|
||||
asset.resizePath,
|
||||
asset.encodedVideoPath,
|
||||
asset.sidecarPath,
|
||||
);
|
||||
|
||||
// TODO refactor this to use cascades
|
||||
if (asset.livePhotoVideoId && !ids.includes(asset.livePhotoVideoId)) {
|
||||
@@ -463,7 +480,40 @@ export class AssetService {
|
||||
authUser: AuthUserDto,
|
||||
checkExistingAssetsDto: CheckExistingAssetsDto,
|
||||
): Promise<CheckExistingAssetsResponseDto> {
|
||||
return this._assetRepository.getExistingAssets(authUser.id, checkExistingAssetsDto);
|
||||
return {
|
||||
existingIds: await this._assetRepository.getExistingAssets(authUser.id, checkExistingAssetsDto),
|
||||
};
|
||||
}
|
||||
|
||||
async bulkUploadCheck(authUser: AuthUserDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {
|
||||
const checksums: Buffer[] = dto.assets.map((asset) => Buffer.from(asset.checksum, 'hex'));
|
||||
const results = await this._assetRepository.getAssetsByChecksums(authUser.id, checksums);
|
||||
const resultsMap: Record<string, string> = {};
|
||||
|
||||
for (const { id, checksum } of results) {
|
||||
resultsMap[checksum.toString('hex')] = id;
|
||||
}
|
||||
|
||||
return {
|
||||
results: dto.assets.map(({ id, checksum }) => {
|
||||
const duplicate = resultsMap[checksum];
|
||||
if (duplicate) {
|
||||
return {
|
||||
id,
|
||||
assetId: duplicate,
|
||||
action: AssetUploadAction.REJECT,
|
||||
reason: AssetRejectReason.DUPLICATE,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO mime-check
|
||||
|
||||
return {
|
||||
id,
|
||||
action: AssetUploadAction.ACCEPT,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
async getAssetCountByTimeBucket(
|
||||
@@ -482,10 +532,6 @@ export class AssetService {
|
||||
return mapAssetCountByTimeBucket(result);
|
||||
}
|
||||
|
||||
getAssetByChecksum(userId: string, checksum: Buffer) {
|
||||
return this._assetRepository.getAssetByChecksum(userId, checksum);
|
||||
}
|
||||
|
||||
getAssetCountByUserId(authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> {
|
||||
return this._assetRepository.getAssetCountByUserId(authUser.id);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user