Compare commits
1 Commits
v1.137.2
...
feat/group
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a881022c3 |
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.76",
|
||||
"version": "2.2.73",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.76",
|
||||
"version": "2.2.73",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.3",
|
||||
@@ -54,7 +54,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.76",
|
||||
"version": "2.2.73",
|
||||
"description": "Command Line Interface (CLI) for Immich",
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
|
||||
@@ -64,7 +64,7 @@ Once you have a new OAuth client application configured, Immich can be configure
|
||||
| Storage Label Claim | string | preferred_username | Claim mapping for the user's storage label**¹** |
|
||||
| Role Claim | string | immich_role | Claim mapping for the user's role. (should return "user" or "admin")**¹** |
|
||||
| Storage Quota Claim | string | immich_quota | Claim mapping for the user's storage**¹** |
|
||||
| Default Storage Quota (GiB) | number | 0 | Default quota for user without storage quota claim (empty for unlimited quota) |
|
||||
| Default Storage Quota (GiB) | number | 0 | Default quota for user without storage quota claim (Enter 0 for unlimited quota) |
|
||||
| Button Text | string | Login with OAuth | Text for the OAuth button on the web |
|
||||
| Auto Register | boolean | true | When true, will automatically register a user the first time they sign in |
|
||||
| [Auto Launch](#auto-launch) | boolean | false | When true, will skip the login page and automatically start the OAuth login process |
|
||||
@@ -106,89 +106,6 @@ Immich has a route (`/api/oauth/mobile-redirect`) that is already configured to
|
||||
|
||||
## Example Configuration
|
||||
|
||||
<details>
|
||||
<summary>Authelia Example</summary>
|
||||
|
||||
### Authelia Example
|
||||
|
||||
Here's an example of OAuth configured for Authelia:
|
||||
|
||||
This assumes there exist an attribute `immichquota` in the user schema, which is used to set the user's storage quota in Immich.
|
||||
The configuration concerning the quota is optional.
|
||||
|
||||
```yaml
|
||||
authentication_backend:
|
||||
ldap:
|
||||
# The LDAP server configuration goes here.
|
||||
# See: https://www.authelia.com/c/ldap
|
||||
attributes:
|
||||
extra:
|
||||
immichquota: # The attribute name from LDAP
|
||||
name: 'immich_quota'
|
||||
multi_valued: false
|
||||
value_type: 'integer'
|
||||
identity_providers:
|
||||
oidc:
|
||||
## The other portions of the mandatory OpenID Connect 1.0 configuration go here.
|
||||
## See: https://www.authelia.com/c/oidc
|
||||
claims_policies:
|
||||
immich_policy:
|
||||
custom_claims:
|
||||
immich_quota:
|
||||
attribute: 'immich_quota'
|
||||
scopes:
|
||||
immich_scope:
|
||||
claims:
|
||||
- 'immich_quota'
|
||||
|
||||
clients:
|
||||
- client_id: 'immich'
|
||||
client_name: 'Immich'
|
||||
# https://www.authelia.com/integration/openid-connect/frequently-asked-questions/#how-do-i-generate-a-client-identifier-or-client-secret
|
||||
client_secret: $pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng'
|
||||
public: false
|
||||
require_pkce: false
|
||||
redirect_uris:
|
||||
- 'https://example.immich.app/auth/login'
|
||||
- 'https://example.immich.app/user-settings'
|
||||
- 'app.immich:///oauth-callback'
|
||||
scopes:
|
||||
- 'openid'
|
||||
- 'profile'
|
||||
- 'email'
|
||||
- 'immich_scope'
|
||||
claims_policy: 'immich_policy'
|
||||
response_types:
|
||||
- 'code'
|
||||
grant_types:
|
||||
- 'authorization_code'
|
||||
id_token_signed_response_alg: 'RS256'
|
||||
userinfo_signed_response_alg: 'RS256'
|
||||
token_endpoint_auth_method: 'client_secret_post'
|
||||
```
|
||||
|
||||
Configuration of OAuth in Immich System Settings
|
||||
|
||||
| Setting | Value |
|
||||
| ---------------------------------- | ------------------------------------------------------------------- |
|
||||
| Issuer URL | `https://example.immich.app/.well-known/openid-configuration` |
|
||||
| Client ID | immich |
|
||||
| Client Secret | 0v89FXkQOWO\***\*\*\*\*\***\*\*\***\*\*\*\*\***mprbvXD549HH6s1iw... |
|
||||
| Token Endpoint Auth Method | client_secret_post |
|
||||
| Scope | openid email profile immich_scope |
|
||||
| ID Token Signed Response Algorithm | RS256 |
|
||||
| Userinfo Signed Response Algorithm | RS256 |
|
||||
| Storage Label Claim | uid |
|
||||
| Storage Quota Claim | immich_quota |
|
||||
| Default Storage Quota (GiB) | 0 (empty for unlimited quota) |
|
||||
| Button Text | Sign in with Authelia (optional) |
|
||||
| Auto Register | Enabled (optional) |
|
||||
| Auto Launch | Enabled (optional) |
|
||||
| Mobile Redirect URI Override | Disable |
|
||||
| Mobile Redirect URI | |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Authentik Example</summary>
|
||||
|
||||
@@ -211,7 +128,7 @@ Configuration of OAuth in Immich System Settings
|
||||
| Signing Algorithm | RS256 |
|
||||
| Storage Label Claim | preferred_username |
|
||||
| Storage Quota Claim | immich_quota |
|
||||
| Default Storage Quota (GiB) | 0 (empty for unlimited quota) |
|
||||
| Default Storage Quota (GiB) | 0 (0 for unlimited quota) |
|
||||
| Button Text | Sign in with Authentik (optional) |
|
||||
| Auto Register | Enabled (optional) |
|
||||
| Auto Launch | Enabled (optional) |
|
||||
@@ -242,7 +159,7 @@ Configuration of OAuth in Immich System Settings
|
||||
| Signing Algorithm | RS256 |
|
||||
| Storage Label Claim | preferred_username |
|
||||
| Storage Quota Claim | immich_quota |
|
||||
| Default Storage Quota (GiB) | 0 (empty for unlimited quota) |
|
||||
| Default Storage Quota (GiB) | 0 (0 for unlimited quota) |
|
||||
| Button Text | Sign in with Google (optional) |
|
||||
| Auto Register | Enabled (optional) |
|
||||
| Auto Launch | Enabled |
|
||||
|
||||
12
docs/static/archived-versions.json
vendored
12
docs/static/archived-versions.json
vendored
@@ -1,16 +1,4 @@
|
||||
[
|
||||
{
|
||||
"label": "v1.137.2",
|
||||
"url": "https://v1.137.2.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v1.137.1",
|
||||
"url": "https://v1.137.1.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v1.137.0",
|
||||
"url": "https://v1.137.0.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v1.136.0",
|
||||
"url": "https://v1.136.0.archive.immich.app"
|
||||
|
||||
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-e2e",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
@@ -46,7 +46,7 @@
|
||||
},
|
||||
"../cli": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.76",
|
||||
"version": "2.2.73",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
@@ -95,7 +95,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
19
i18n/en.json
19
i18n/en.json
@@ -25,6 +25,7 @@
|
||||
"add_photos": "Add photos",
|
||||
"add_tag": "Add tag",
|
||||
"add_to": "Add to…",
|
||||
"confirm_delete_name": "Are you sure you want to delete {name}?",
|
||||
"add_to_album": "Add to album",
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||
@@ -33,8 +34,10 @@
|
||||
"added_to_archive": "Added to archive",
|
||||
"added_to_favorites": "Added to favorites",
|
||||
"added_to_favorites_count": "Added {count, number} to favorites",
|
||||
"empty_group_message": "This group is empty",
|
||||
"admin": {
|
||||
"add_exclusion_pattern_description": "Add exclusion patterns. Globbing using *, **, and ? is supported. To ignore all files in any directory named \"Raw\", use \"**/Raw/**\". To ignore all files ending in \".tif\", use \"**/*.tif\". To ignore an absolute path, use \"/path/to/ignore/**\".",
|
||||
"group_details": "Group Details",
|
||||
"admin_user": "Admin User",
|
||||
"asset_offline_description": "This external library asset is no longer found on disk and has been moved to trash. If the file was moved within the library, check your timeline for the new corresponding asset. To restore this asset, please ensure that the file path below can be accessed by Immich and scan the library.",
|
||||
"authentication_settings": "Authentication Settings",
|
||||
@@ -363,6 +366,7 @@
|
||||
"user_delete_immediately_checkbox": "Queue user and assets for immediate deletion",
|
||||
"user_details": "User Details",
|
||||
"user_management": "User Management",
|
||||
"group_management": "Group Management",
|
||||
"user_password_has_been_reset": "The user's password has been reset:",
|
||||
"user_password_reset_description": "Please provide the temporary password to the user and inform them they will need to change the password at their next login.",
|
||||
"user_restore_description": "<b>{user}</b>'s account will be restored.",
|
||||
@@ -429,6 +433,8 @@
|
||||
"album_viewer_appbar_share_leave": "Leave album",
|
||||
"album_viewer_appbar_share_to": "Share To",
|
||||
"album_viewer_page_share_add_users": "Add users",
|
||||
"edit_users": "Edit users",
|
||||
"add_users": "Add users",
|
||||
"album_with_link_access": "Let anyone with the link see photos and people in this album.",
|
||||
"albums": "Albums",
|
||||
"albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albums}}",
|
||||
@@ -469,6 +475,9 @@
|
||||
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
|
||||
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
|
||||
"asset_added_to_album": "Added to album",
|
||||
"group": "Group",
|
||||
"group_users": "Group users",
|
||||
"other_users": "Other users",
|
||||
"asset_adding_to_album": "Adding to album…",
|
||||
"asset_description_updated": "Asset description has been updated",
|
||||
"asset_filename_is_offline": "Asset {filename} is offline",
|
||||
@@ -653,7 +662,6 @@
|
||||
"clear": "Clear",
|
||||
"clear_all": "Clear all",
|
||||
"clear_all_recent_searches": "Clear all recent searches",
|
||||
"clear_file_cache": "Clear File Cache",
|
||||
"clear_message": "Clear message",
|
||||
"clear_value": "Clear value",
|
||||
"client_cert_dialog_msg_confirm": "OK",
|
||||
@@ -722,11 +730,14 @@
|
||||
"create_new_person": "Create new person",
|
||||
"create_new_person_hint": "Assign selected assets to a new person",
|
||||
"create_new_user": "Create new user",
|
||||
"create_new_group": "Create new group",
|
||||
"groups": "Groups",
|
||||
"create_shared_album_page_share_add_assets": "ADD ASSETS",
|
||||
"create_shared_album_page_share_select_photos": "Select Photos",
|
||||
"create_tag": "Create tag",
|
||||
"create_tag_description": "Create a new tag. For nested tags, please enter the full path of the tag including forward slashes.",
|
||||
"create_user": "Create user",
|
||||
"create_group": "Create group",
|
||||
"created": "Created",
|
||||
"created_at": "Created",
|
||||
"crop": "Crop",
|
||||
@@ -782,6 +793,7 @@
|
||||
"delete_tag": "Delete tag",
|
||||
"delete_tag_confirmation_prompt": "Are you sure you want to delete {tagName} tag?",
|
||||
"delete_user": "Delete user",
|
||||
"delete_group": "Delete group",
|
||||
"deleted_shared_link": "Deleted shared link",
|
||||
"deletes_missing_assets": "Deletes assets missing from disk",
|
||||
"description": "Description",
|
||||
@@ -835,7 +847,6 @@
|
||||
"edit_birthday": "Edit Birthday",
|
||||
"edit_date": "Edit date",
|
||||
"edit_date_and_time": "Edit date and time",
|
||||
"edit_date_and_time_action_prompt": "{count} date and time edited",
|
||||
"edit_description": "Edit description",
|
||||
"edit_description_prompt": "Please select a new description:",
|
||||
"edit_exclusion_pattern": "Edit exclusion pattern",
|
||||
@@ -852,6 +863,7 @@
|
||||
"edit_tag": "Edit tag",
|
||||
"edit_title": "Edit Title",
|
||||
"edit_user": "Edit user",
|
||||
"edit_group": "Edit group",
|
||||
"edited": "Edited",
|
||||
"editor": "Editor",
|
||||
"editor_close_without_save_prompt": "The changes will not be saved",
|
||||
@@ -939,6 +951,7 @@
|
||||
"unable_to_create_api_key": "Unable to create a new API Key",
|
||||
"unable_to_create_library": "Unable to create library",
|
||||
"unable_to_create_user": "Unable to create user",
|
||||
"unable_to_create_group": "Unable to create group",
|
||||
"unable_to_delete_album": "Unable to delete album",
|
||||
"unable_to_delete_asset": "Unable to delete asset",
|
||||
"unable_to_delete_assets": "Error deleting assets",
|
||||
@@ -997,6 +1010,7 @@
|
||||
"unable_to_update_settings": "Unable to update settings",
|
||||
"unable_to_update_timeline_display_status": "Unable to update timeline display status",
|
||||
"unable_to_update_user": "Unable to update user",
|
||||
"unable_to_update_group": "Unable to update group",
|
||||
"unable_to_upload_file": "Unable to upload file"
|
||||
},
|
||||
"exif": "Exif",
|
||||
@@ -2033,6 +2047,7 @@
|
||||
"view_qr_code": "View QR code",
|
||||
"view_stack": "View Stack",
|
||||
"view_user": "View User",
|
||||
"view_group": "View Group",
|
||||
"viewer_remove_from_stack": "Remove from Stack",
|
||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||
"viewer_unstack": "Un-Stack",
|
||||
|
||||
@@ -36,7 +36,7 @@ platform :android do
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 205,
|
||||
"android.injected.version.name" => "1.137.2",
|
||||
"android.injected.version.name" => "1.136.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')
|
||||
|
||||
@@ -22,7 +22,7 @@ platform :ios do
|
||||
path: "./Runner.xcodeproj",
|
||||
)
|
||||
increment_version_number(
|
||||
version_number: "1.137.2"
|
||||
version_number: "1.136.0"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -104,7 +104,6 @@ class HashService {
|
||||
DLog.log("Hashed ${hashed.length}/${toHash.length} assets");
|
||||
|
||||
await _localAssetRepository.updateHashes(hashed);
|
||||
await _storageRepository.clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,23 +186,6 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateDateTime(List<String> ids, DateTime dateTime) {
|
||||
return _db.batch((batch) async {
|
||||
for (final id in ids) {
|
||||
batch.update(
|
||||
_db.remoteExifEntity,
|
||||
RemoteExifEntityCompanion(dateTimeOriginal: Value(dateTime)),
|
||||
where: (e) => e.assetId.equals(id),
|
||||
);
|
||||
batch.update(
|
||||
_db.remoteAssetEntity,
|
||||
RemoteAssetEntityCompanion(createdAt: Value(dateTime)),
|
||||
where: (e) => e.id.equals(id),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> stack(String userId, StackResponse stack) {
|
||||
return _db.transaction(() async {
|
||||
final stackIds = await _db.managers.stackEntity
|
||||
|
||||
@@ -66,14 +66,4 @@ class StorageRepository {
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
Future<void> clearCache() async {
|
||||
final log = Logger('StorageRepository');
|
||||
|
||||
try {
|
||||
await PhotoManager.clearFileCache();
|
||||
} catch (error, stackTrace) {
|
||||
log.warning("Error clearing cache", error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||
@@ -13,6 +14,7 @@ import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart';
|
||||
import 'package:immich_mobile/widgets/common/search_field.dart';
|
||||
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftBackupAlbumSelectionPage extends ConsumerStatefulWidget {
|
||||
@@ -65,14 +67,14 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
||||
final selectedBackupAlbums = albums.where((album) => album.backupSelection == BackupSelection.selected).toList();
|
||||
final excludedBackupAlbums = albums.where((album) => album.backupSelection == BackupSelection.excluded).toList();
|
||||
|
||||
// handleSyncAlbumToggle(bool isEnable) async {
|
||||
// if (isEnable) {
|
||||
// await ref.read(albumProvider.notifier).refreshRemoteAlbums();
|
||||
// for (final album in selectedBackupAlbums) {
|
||||
// await ref.read(albumProvider.notifier).createSyncAlbum(album.name);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
handleSyncAlbumToggle(bool isEnable) async {
|
||||
if (isEnable) {
|
||||
await ref.read(albumProvider.notifier).refreshRemoteAlbums();
|
||||
for (final album in selectedBackupAlbums) {
|
||||
await ref.read(albumProvider.notifier).createSyncAlbum(album.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
@@ -165,15 +167,16 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
||||
),
|
||||
),
|
||||
|
||||
// SettingsSwitchListTile(
|
||||
// valueNotifier: _enableSyncUploadAlbum,
|
||||
// title: "sync_albums".t(context: context),
|
||||
// subtitle: "sync_upload_album_setting_subtitle".t(context: context),
|
||||
// contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
// titleStyle: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
// subtitleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.primary),
|
||||
// onChanged: handleSyncAlbumToggle,
|
||||
// ),
|
||||
SettingsSwitchListTile(
|
||||
valueNotifier: _enableSyncUploadAlbum,
|
||||
title: "sync_albums".t(context: context),
|
||||
subtitle: "sync_upload_album_setting_subtitle".t(context: context),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
titleStyle: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
subtitleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.primary),
|
||||
onChanged: handleSyncAlbumToggle,
|
||||
),
|
||||
|
||||
ListTile(
|
||||
title: Text(
|
||||
"albums_on_device_count".t(context: context, args: {'count': albumCount.toString()}),
|
||||
|
||||
@@ -28,15 +28,13 @@ class RemoteAlbumPage extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
late RemoteAlbum _album;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_album = widget.album;
|
||||
}
|
||||
|
||||
Future<void> addAssets(BuildContext context) async {
|
||||
final albumAssets = await ref.read(remoteAlbumProvider.notifier).getAssets(_album.id);
|
||||
final albumAssets = await ref.read(remoteAlbumProvider.notifier).getAssets(widget.album.id);
|
||||
|
||||
final newAssets = await context.pushRoute<Set<BaseAsset>>(
|
||||
DriftAssetSelectionTimelineRoute(lockedSelectionAssets: albumAssets.toSet()),
|
||||
@@ -49,7 +47,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
final added = await ref
|
||||
.read(remoteAlbumProvider.notifier)
|
||||
.addAssets(
|
||||
_album.id,
|
||||
widget.album.id,
|
||||
newAssets.map((asset) {
|
||||
final remoteAsset = asset as RemoteAsset;
|
||||
return remoteAsset.id;
|
||||
@@ -66,14 +64,14 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
}
|
||||
|
||||
Future<void> addUsers(BuildContext context) async {
|
||||
final newUsers = await context.pushRoute<List<String>>(DriftUserSelectionRoute(album: _album));
|
||||
final newUsers = await context.pushRoute<List<String>>(DriftUserSelectionRoute(album: widget.album));
|
||||
|
||||
if (newUsers == null || newUsers.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ref.read(remoteAlbumProvider.notifier).addUsers(_album.id, newUsers);
|
||||
await ref.read(remoteAlbumProvider.notifier).addUsers(widget.album.id, newUsers);
|
||||
|
||||
if (newUsers.isNotEmpty) {
|
||||
ImmichToast.show(
|
||||
@@ -83,7 +81,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
);
|
||||
}
|
||||
|
||||
ref.invalidate(remoteAlbumSharedUsersProvider(_album.id));
|
||||
ref.invalidate(remoteAlbumSharedUsersProvider(widget.album.id));
|
||||
} catch (e) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
@@ -94,7 +92,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
}
|
||||
|
||||
Future<void> toggleAlbumOrder() async {
|
||||
await ref.read(remoteAlbumProvider.notifier).toggleAlbumOrder(_album.id);
|
||||
await ref.read(remoteAlbumProvider.notifier).toggleAlbumOrder(widget.album.id);
|
||||
|
||||
ref.invalidate(timelineServiceProvider);
|
||||
}
|
||||
@@ -108,7 +106,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('album_delete_confirmation'.t(context: context, args: {'album': _album.name})),
|
||||
Text('album_delete_confirmation'.t(context: context, args: {'album': widget.album.name})),
|
||||
const SizedBox(height: 8),
|
||||
Text('album_delete_confirmation_description'.t(context: context)),
|
||||
],
|
||||
@@ -130,7 +128,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
await ref.read(remoteAlbumProvider.notifier).deleteAlbum(_album.id);
|
||||
await ref.read(remoteAlbumProvider.notifier).deleteAlbum(widget.album.id);
|
||||
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
@@ -153,20 +151,17 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
final result = await showDialog<_EditAlbumData?>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (context) => _EditAlbumDialog(album: _album),
|
||||
builder: (context) => _EditAlbumDialog(album: widget.album),
|
||||
);
|
||||
|
||||
if (result != null && context.mounted) {
|
||||
setState(() {
|
||||
_album = _album.copyWith(name: result.name, description: result.description ?? '');
|
||||
});
|
||||
HapticFeedback.mediumImpact();
|
||||
}
|
||||
}
|
||||
|
||||
void showOptionSheet(BuildContext context) {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
final isOwner = user != null ? user.id == _album.ownerId : false;
|
||||
final isOwner = user != null ? user.id == widget.album.ownerId : false;
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -210,7 +205,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith((ref) {
|
||||
final timelineService = ref.watch(timelineFactoryProvider).remoteAlbum(albumId: _album.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).remoteAlbum(albumId: widget.album.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
}),
|
||||
@@ -222,7 +217,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
onToggleAlbumOrder: () => toggleAlbumOrder(),
|
||||
onEditTitle: () => showEditTitleAndDescription(context),
|
||||
),
|
||||
bottomSheet: RemoteAlbumBottomSheet(album: _album),
|
||||
bottomSheet: RemoteAlbumBottomSheet(album: widget.album),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,44 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
class EditDateTimeActionButton extends ConsumerWidget {
|
||||
final ActionSource source;
|
||||
|
||||
const EditDateTimeActionButton({super.key, required this.source});
|
||||
|
||||
_onTap(BuildContext context, WidgetRef ref) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).editDateTime(source, context);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'edit_date_and_time_action_prompt'.t(
|
||||
context: context,
|
||||
args: {'count': result.count.toString()},
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
const EditDateTimeActionButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -46,7 +12,6 @@ class EditDateTimeActionButton extends ConsumerWidget {
|
||||
maxWidth: 95.0,
|
||||
iconData: Icons.edit_calendar_outlined,
|
||||
label: "control_bottom_app_bar_edit_time".t(context: context),
|
||||
onPressed: () => _onTap(context, ref),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
@@ -579,7 +578,6 @@ class AddToAlbumHeader extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(currentRemoteAlbumProvider.notifier).setAlbum(newAlbum);
|
||||
context.pushRoute(RemoteAlbumRoute(album: newAlbum));
|
||||
}
|
||||
|
||||
|
||||
@@ -143,18 +143,12 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
final exifInfo = ref.watch(currentAssetExifProvider).valueOrNull;
|
||||
final cameraTitle = _getCameraInfoTitle(exifInfo);
|
||||
|
||||
Future<void> editDateTime() async {
|
||||
await ref.read(actionProvider.notifier).editDateTime(ActionSource.viewer, context);
|
||||
}
|
||||
|
||||
return SliverList.list(
|
||||
children: [
|
||||
// Asset Date and Time
|
||||
_SheetTile(
|
||||
title: _getDateTime(context, asset),
|
||||
titleStyle: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
trailing: asset.hasRemote ? const Icon(Icons.edit, size: 18) : null,
|
||||
onTap: asset.hasRemote ? () async => await editDateTime() : null,
|
||||
),
|
||||
if (exifInfo != null) _SheetAssetDescription(exif: exifInfo),
|
||||
const SheetPeopleDetails(),
|
||||
@@ -200,21 +194,11 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
class _SheetTile extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget? leading;
|
||||
final Widget? trailing;
|
||||
final String? subtitle;
|
||||
final TextStyle? titleStyle;
|
||||
final TextStyle? subtitleStyle;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const _SheetTile({
|
||||
required this.title,
|
||||
this.titleStyle,
|
||||
this.leading,
|
||||
this.subtitle,
|
||||
this.subtitleStyle,
|
||||
this.trailing,
|
||||
this.onTap,
|
||||
});
|
||||
const _SheetTile({required this.title, this.titleStyle, this.leading, this.subtitle, this.subtitleStyle});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -250,10 +234,8 @@ class _SheetTile extends StatelessWidget {
|
||||
title: titleWidget,
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: leading,
|
||||
trailing: trailing,
|
||||
contentPadding: leading == null ? null : const EdgeInsets.only(left: 25),
|
||||
subtitle: subtitleWidget,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class _SheetPeopleDetailsState extends ConsumerState<SheetPeopleDetails> {
|
||||
context.back();
|
||||
return;
|
||||
}
|
||||
context.pop();
|
||||
context.back();
|
||||
context.pushRoute(DriftPersonRoute(person: person));
|
||||
},
|
||||
onNameTap: () => showNameEditModal(person),
|
||||
|
||||
@@ -40,7 +40,7 @@ class ArchiveBottomSheet extends ConsumerWidget {
|
||||
isTrashEnable
|
||||
? const TrashActionButton(source: ActionSource.timeline)
|
||||
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
||||
const EditDateTimeActionButton(source: ActionSource.timeline),
|
||||
const EditDateTimeActionButton(),
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
const StackActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -40,7 +40,7 @@ class FavoriteBottomSheet extends ConsumerWidget {
|
||||
isTrashEnable
|
||||
? const TrashActionButton(source: ActionSource.timeline)
|
||||
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
||||
const EditDateTimeActionButton(source: ActionSource.timeline),
|
||||
const EditDateTimeActionButton(),
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
const StackActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -76,7 +76,7 @@ class GeneralBottomSheet extends ConsumerWidget {
|
||||
if (multiselect.hasLocal || multiselect.hasMerged) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
const EditDateTimeActionButton(source: ActionSource.timeline),
|
||||
const EditDateTimeActionButton(),
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
const StackActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -43,7 +43,7 @@ class RemoteAlbumBottomSheet extends ConsumerWidget {
|
||||
isTrashEnable
|
||||
? const TrashActionButton(source: ActionSource.timeline)
|
||||
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
||||
const EditDateTimeActionButton(source: ActionSource.timeline),
|
||||
const EditDateTimeActionButton(),
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
const StackActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||
@@ -11,7 +11,6 @@ import 'package:immich_mobile/presentation/widgets/timeline/header.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/segment_builder.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
@@ -126,14 +125,10 @@ class _FixedSegmentRow extends ConsumerWidget {
|
||||
textDirection: Directionality.of(context),
|
||||
children: [
|
||||
for (int i = 0; i < assets.length; i++)
|
||||
TimelineAssetIndexWrapper(
|
||||
_AssetTileWidget(
|
||||
key: ValueKey(Object.hash(assets[i].heroTag, assetIndex + i, timelineService.hashCode)),
|
||||
asset: assets[i],
|
||||
assetIndex: assetIndex + i,
|
||||
segmentIndex: 0, // For simplicity, using 0 for now
|
||||
child: _AssetTileWidget(
|
||||
key: ValueKey(Object.hash(assets[i].heroTag, assetIndex + i, timelineService.hashCode)),
|
||||
asset: assets[i],
|
||||
assetIndex: assetIndex + i,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||
@@ -18,7 +15,6 @@ import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_s
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
@@ -92,29 +88,10 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
final _scrollController = ScrollController();
|
||||
StreamSubscription? _eventSubscription;
|
||||
|
||||
// Drag selection state
|
||||
bool _dragging = false;
|
||||
TimelineAssetIndex? _dragAnchorIndex;
|
||||
final Set<BaseAsset> _draggedAssets = HashSet();
|
||||
ScrollPhysics? _scrollPhysics;
|
||||
|
||||
int _perRow = 4;
|
||||
double _scaleFactor = 3.0;
|
||||
double _baseScaleFactor = 3.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_eventSubscription = EventStream.shared.listen(_onEvent);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final currentTilesPerRow = ref.read(settingsProvider).get(Setting.tilesPerRow);
|
||||
setState(() {
|
||||
_perRow = currentTilesPerRow;
|
||||
_scaleFactor = 7.0 - _perRow;
|
||||
_baseScaleFactor = _scaleFactor;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _onEvent(Event event) {
|
||||
@@ -173,71 +150,6 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
});
|
||||
}
|
||||
|
||||
// Drag selection methods
|
||||
void _setDragStartIndex(TimelineAssetIndex index) {
|
||||
setState(() {
|
||||
_scrollPhysics = const ClampingScrollPhysics();
|
||||
_dragAnchorIndex = index;
|
||||
_dragging = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _stopDrag() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// Update the physics post frame to prevent sudden change in physics on iOS.
|
||||
setState(() {
|
||||
_scrollPhysics = null;
|
||||
});
|
||||
});
|
||||
setState(() {
|
||||
_dragging = false;
|
||||
_draggedAssets.clear();
|
||||
});
|
||||
// Reset the scrolling state after a small delay to allow bottom sheet to expand again
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
if (mounted) {
|
||||
ref.read(timelineStateProvider.notifier).setScrolling(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _dragScroll(ScrollDirection direction) {
|
||||
_scrollController.animateTo(
|
||||
_scrollController.offset + (direction == ScrollDirection.forward ? 175 : -175),
|
||||
duration: const Duration(milliseconds: 125),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleDragAssetEnter(TimelineAssetIndex index) {
|
||||
if (_dragAnchorIndex == null || !_dragging) return;
|
||||
|
||||
final timelineService = ref.read(timelineServiceProvider);
|
||||
final dragAnchorIndex = _dragAnchorIndex!;
|
||||
|
||||
// Calculate the range of assets to select
|
||||
final startIndex = math.min(dragAnchorIndex.assetIndex, index.assetIndex);
|
||||
final endIndex = math.max(dragAnchorIndex.assetIndex, index.assetIndex);
|
||||
final count = endIndex - startIndex + 1;
|
||||
|
||||
// Load the assets in the range
|
||||
if (timelineService.hasRange(startIndex, count)) {
|
||||
final selectedAssets = timelineService.getAssets(startIndex, count);
|
||||
|
||||
// Clear previous drag selection and add new range
|
||||
final multiSelectNotifier = ref.read(multiSelectProvider.notifier);
|
||||
for (final asset in _draggedAssets) {
|
||||
multiSelectNotifier.deselectAsset(asset);
|
||||
}
|
||||
_draggedAssets.clear();
|
||||
|
||||
for (final asset in selectedAssets) {
|
||||
multiSelectNotifier.selectAsset(asset);
|
||||
_draggedAssets.add(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext _) {
|
||||
final asyncSegments = ref.watch(timelineSegmentProvider);
|
||||
@@ -265,83 +177,43 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
|
||||
return PrimaryScrollController(
|
||||
controller: _scrollController,
|
||||
child: RawGestureDetector(
|
||||
gestures: {
|
||||
CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers<CustomScaleGestureRecognizer>(
|
||||
() => CustomScaleGestureRecognizer(),
|
||||
(CustomScaleGestureRecognizer scale) {
|
||||
scale.onStart = (details) {
|
||||
_baseScaleFactor = _scaleFactor;
|
||||
};
|
||||
|
||||
scale.onUpdate = (details) {
|
||||
final newScaleFactor = math.max(math.min(5.0, _baseScaleFactor * details.scale), 1.0);
|
||||
final newPerRow = 7 - newScaleFactor.toInt();
|
||||
|
||||
if (newPerRow != _perRow) {
|
||||
setState(() {
|
||||
_scaleFactor = newScaleFactor;
|
||||
_perRow = newPerRow;
|
||||
});
|
||||
|
||||
ref.read(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow);
|
||||
}
|
||||
};
|
||||
},
|
||||
),
|
||||
},
|
||||
child: TimelineDragRegion(
|
||||
onStart: _setDragStartIndex,
|
||||
onAssetEnter: _handleDragAssetEnter,
|
||||
onEnd: _stopDrag,
|
||||
onScroll: _dragScroll,
|
||||
onScrollStart: () {
|
||||
// Minimize the bottom sheet when drag selection starts
|
||||
ref.read(timelineStateProvider.notifier).setScrolling(true);
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
Scrubber(
|
||||
layoutSegments: segments,
|
||||
timelineHeight: maxHeight,
|
||||
topPadding: topPadding,
|
||||
bottomPadding: bottomPadding,
|
||||
monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight,
|
||||
child: CustomScrollView(
|
||||
primary: true,
|
||||
physics: _scrollPhysics,
|
||||
cacheExtent: maxHeight * 2,
|
||||
slivers: [
|
||||
if (isSelectionMode)
|
||||
const SelectionSliverAppBar()
|
||||
else if (widget.appBar != null)
|
||||
widget.appBar!,
|
||||
if (widget.topSliverWidget != null) widget.topSliverWidget!,
|
||||
_SliverSegmentedList(
|
||||
segments: segments,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(ctx, index) {
|
||||
if (index >= childCount) return null;
|
||||
final segment = segments.findByIndex(index);
|
||||
return segment?.builder(ctx, index) ?? const SizedBox.shrink();
|
||||
},
|
||||
childCount: childCount,
|
||||
addAutomaticKeepAlives: false,
|
||||
// We add repaint boundary around tiles, so skip the auto boundaries
|
||||
addRepaintBoundaries: false,
|
||||
),
|
||||
),
|
||||
const SliverPadding(padding: EdgeInsets.only(bottom: scrubberBottomPadding)),
|
||||
],
|
||||
child: Stack(
|
||||
children: [
|
||||
Scrubber(
|
||||
layoutSegments: segments,
|
||||
timelineHeight: maxHeight,
|
||||
topPadding: topPadding,
|
||||
bottomPadding: bottomPadding,
|
||||
monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight,
|
||||
child: CustomScrollView(
|
||||
primary: true,
|
||||
cacheExtent: maxHeight * 2,
|
||||
slivers: [
|
||||
if (isSelectionMode) const SelectionSliverAppBar() else if (widget.appBar != null) widget.appBar!,
|
||||
if (widget.topSliverWidget != null) widget.topSliverWidget!,
|
||||
_SliverSegmentedList(
|
||||
segments: segments,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(ctx, index) {
|
||||
if (index >= childCount) return null;
|
||||
final segment = segments.findByIndex(index);
|
||||
return segment?.builder(ctx, index) ?? const SizedBox.shrink();
|
||||
},
|
||||
childCount: childCount,
|
||||
addAutomaticKeepAlives: false,
|
||||
// We add repaint boundary around tiles, so skip the auto boundaries
|
||||
addRepaintBoundaries: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isSelectionMode && isMultiSelectEnabled) ...[
|
||||
const Positioned(top: 60, left: 25, child: _MultiSelectStatusButton()),
|
||||
if (widget.bottomSheet != null) widget.bottomSheet!,
|
||||
const SliverPadding(padding: EdgeInsets.only(bottom: scrubberBottomPadding)),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isSelectionMode && isMultiSelectEnabled) ...[
|
||||
const Positioned(top: 60, left: 25, child: _MultiSelectStatusButton()),
|
||||
if (widget.bottomSheet != null) widget.bottomSheet!,
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -571,11 +443,3 @@ class _MultiSelectStatusButton extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// accepts a gesture even though it should reject it (because child won)
|
||||
class CustomScaleGestureRecognizer extends ScaleGestureRecognizer {
|
||||
@override
|
||||
void rejectGesture(int pointer) {
|
||||
acceptGesture(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class TimelineDragRegion extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
final void Function(TimelineAssetIndex valueKey)? onStart;
|
||||
final void Function(TimelineAssetIndex valueKey)? onAssetEnter;
|
||||
final void Function()? onEnd;
|
||||
final void Function()? onScrollStart;
|
||||
final void Function(ScrollDirection direction)? onScroll;
|
||||
|
||||
const TimelineDragRegion({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.onStart,
|
||||
this.onAssetEnter,
|
||||
this.onEnd,
|
||||
this.onScrollStart,
|
||||
this.onScroll,
|
||||
});
|
||||
|
||||
@override
|
||||
State createState() => _TimelineDragRegionState();
|
||||
}
|
||||
|
||||
class _TimelineDragRegionState extends State<TimelineDragRegion> {
|
||||
late TimelineAssetIndex? assetUnderPointer;
|
||||
late TimelineAssetIndex? anchorAsset;
|
||||
|
||||
// Scroll related state
|
||||
static const double scrollOffset = 0.10;
|
||||
double? topScrollOffset;
|
||||
double? bottomScrollOffset;
|
||||
Timer? scrollTimer;
|
||||
late bool scrollNotified;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
assetUnderPointer = null;
|
||||
anchorAsset = null;
|
||||
scrollNotified = false;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
topScrollOffset = null;
|
||||
bottomScrollOffset = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawGestureDetector(
|
||||
gestures: {
|
||||
_CustomLongPressGestureRecognizer: GestureRecognizerFactoryWithHandlers<_CustomLongPressGestureRecognizer>(
|
||||
() => _CustomLongPressGestureRecognizer(),
|
||||
_registerCallbacks,
|
||||
),
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _registerCallbacks(_CustomLongPressGestureRecognizer recognizer) {
|
||||
recognizer.onLongPressMoveUpdate = (details) => _onLongPressMove(details);
|
||||
recognizer.onLongPressStart = (details) => _onLongPressStart(details);
|
||||
recognizer.onLongPressUp = _onLongPressEnd;
|
||||
}
|
||||
|
||||
TimelineAssetIndex? _getValueKeyAtPosition(Offset position) {
|
||||
final box = context.findAncestorRenderObjectOfType<RenderBox>();
|
||||
if (box == null) return null;
|
||||
|
||||
final hitTestResult = BoxHitTestResult();
|
||||
final local = box.globalToLocal(position);
|
||||
if (!box.hitTest(hitTestResult, position: local)) return null;
|
||||
|
||||
return (hitTestResult.path.firstWhereOrNull((hit) => hit.target is _TimelineAssetIndexProxy)?.target
|
||||
as _TimelineAssetIndexProxy?)
|
||||
?.index;
|
||||
}
|
||||
|
||||
void _onLongPressStart(LongPressStartDetails event) {
|
||||
/// Calculate widget height and scroll offset when long press starting instead of in [initState]
|
||||
/// or [didChangeDependencies] as the grid might still be rendering into view to get the actual size
|
||||
final height = context.size?.height;
|
||||
if (height != null && (topScrollOffset == null || bottomScrollOffset == null)) {
|
||||
topScrollOffset = height * scrollOffset;
|
||||
bottomScrollOffset = height - topScrollOffset!;
|
||||
}
|
||||
|
||||
final initialHit = _getValueKeyAtPosition(event.globalPosition);
|
||||
anchorAsset = initialHit;
|
||||
if (initialHit == null) return;
|
||||
|
||||
if (anchorAsset != null) {
|
||||
widget.onStart?.call(anchorAsset!);
|
||||
}
|
||||
}
|
||||
|
||||
void _onLongPressEnd() {
|
||||
scrollNotified = false;
|
||||
scrollTimer?.cancel();
|
||||
widget.onEnd?.call();
|
||||
}
|
||||
|
||||
void _onLongPressMove(LongPressMoveUpdateDetails event) {
|
||||
if (anchorAsset == null) return;
|
||||
if (topScrollOffset == null || bottomScrollOffset == null) return;
|
||||
|
||||
final currentDy = event.localPosition.dy;
|
||||
|
||||
if (currentDy > bottomScrollOffset!) {
|
||||
scrollTimer ??= Timer.periodic(
|
||||
const Duration(milliseconds: 50),
|
||||
(_) => widget.onScroll?.call(ScrollDirection.forward),
|
||||
);
|
||||
} else if (currentDy < topScrollOffset!) {
|
||||
scrollTimer ??= Timer.periodic(
|
||||
const Duration(milliseconds: 50),
|
||||
(_) => widget.onScroll?.call(ScrollDirection.reverse),
|
||||
);
|
||||
} else {
|
||||
scrollTimer?.cancel();
|
||||
scrollTimer = null;
|
||||
}
|
||||
|
||||
final currentlyTouchingAsset = _getValueKeyAtPosition(event.globalPosition);
|
||||
if (currentlyTouchingAsset == null) return;
|
||||
|
||||
if (assetUnderPointer != currentlyTouchingAsset) {
|
||||
if (!scrollNotified) {
|
||||
scrollNotified = true;
|
||||
widget.onScrollStart?.call();
|
||||
}
|
||||
|
||||
widget.onAssetEnter?.call(currentlyTouchingAsset);
|
||||
assetUnderPointer = currentlyTouchingAsset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomLongPressGestureRecognizer extends LongPressGestureRecognizer {
|
||||
@override
|
||||
void rejectGesture(int pointer) {
|
||||
acceptGesture(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineAssetIndexWrapper extends SingleChildRenderObjectWidget {
|
||||
final int assetIndex;
|
||||
final int segmentIndex;
|
||||
|
||||
const TimelineAssetIndexWrapper({
|
||||
required Widget super.child,
|
||||
required this.assetIndex,
|
||||
required this.segmentIndex,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
_TimelineAssetIndexProxy createRenderObject(BuildContext context) {
|
||||
return _TimelineAssetIndexProxy(
|
||||
index: TimelineAssetIndex(assetIndex: assetIndex, segmentIndex: segmentIndex),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
// ignore: library_private_types_in_public_api
|
||||
_TimelineAssetIndexProxy renderObject,
|
||||
) {
|
||||
renderObject.index = TimelineAssetIndex(assetIndex: assetIndex, segmentIndex: segmentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
class _TimelineAssetIndexProxy extends RenderProxyBox {
|
||||
TimelineAssetIndex index;
|
||||
|
||||
_TimelineAssetIndexProxy({required this.index});
|
||||
}
|
||||
|
||||
class TimelineAssetIndex {
|
||||
final int assetIndex;
|
||||
final int segmentIndex;
|
||||
|
||||
const TimelineAssetIndex({required this.assetIndex, required this.segmentIndex});
|
||||
|
||||
@override
|
||||
bool operator ==(covariant TimelineAssetIndex other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.assetIndex == assetIndex && other.segmentIndex == segmentIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => assetIndex.hashCode ^ segmentIndex.hashCode;
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/models/download/livephotos_medatada.model.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/services/action.service.dart';
|
||||
import 'package:immich_mobile/services/download.service.dart';
|
||||
import 'package:immich_mobile/services/timeline.service.dart';
|
||||
import 'package:immich_mobile/services/upload.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -33,7 +30,6 @@ class ActionNotifier extends Notifier<void> {
|
||||
final Logger _logger = Logger('ActionNotifier');
|
||||
late ActionService _service;
|
||||
late UploadService _uploadService;
|
||||
late DownloadService _downloadService;
|
||||
|
||||
ActionNotifier() : super();
|
||||
|
||||
@@ -41,29 +37,6 @@ class ActionNotifier extends Notifier<void> {
|
||||
void build() {
|
||||
_uploadService = ref.watch(uploadServiceProvider);
|
||||
_service = ref.watch(actionServiceProvider);
|
||||
_downloadService = ref.watch(downloadServiceProvider);
|
||||
_downloadService.onImageDownloadStatus = _downloadImageCallback;
|
||||
_downloadService.onVideoDownloadStatus = _downloadVideoCallback;
|
||||
_downloadService.onLivePhotoDownloadStatus = _downloadLivePhotoCallback;
|
||||
}
|
||||
|
||||
void _downloadImageCallback(TaskStatusUpdate update) {
|
||||
if (update.status == TaskStatus.complete) {
|
||||
_downloadService.saveImageWithPath(update.task);
|
||||
}
|
||||
}
|
||||
|
||||
void _downloadVideoCallback(TaskStatusUpdate update) {
|
||||
if (update.status == TaskStatus.complete) {
|
||||
_downloadService.saveVideo(update.task);
|
||||
}
|
||||
}
|
||||
|
||||
void _downloadLivePhotoCallback(TaskStatusUpdate update) async {
|
||||
if (update.status == TaskStatus.complete) {
|
||||
final livePhotosId = LivePhotosMetadata.fromJson(update.task.metaData).id;
|
||||
_downloadService.saveLivePhotos(update.task, livePhotosId);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> _getRemoteIdsForSource(ActionSource source) {
|
||||
@@ -266,21 +239,6 @@ class ActionNotifier extends Notifier<void> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<ActionResult?> editDateTime(ActionSource source, BuildContext context) async {
|
||||
final ids = _getOwnedRemoteIdsForSource(source);
|
||||
try {
|
||||
final isEdited = await _service.editDateTime(ids, context);
|
||||
if (!isEdited) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ActionResult(count: ids.length, success: true);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Failed to edit date and time for assets', error, stack);
|
||||
return ActionResult(count: ids.length, success: false, error: error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<ActionResult> removeFromAlbum(ActionSource source, String albumId) async {
|
||||
final ids = _getRemoteIdsForSource(source);
|
||||
try {
|
||||
|
||||
@@ -66,10 +66,6 @@ class AssetApiRepository extends ApiRepository {
|
||||
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, latitude: location.latitude, longitude: location.longitude));
|
||||
}
|
||||
|
||||
Future<void> updateDateTime(List<String> ids, DateTime dateTime) async {
|
||||
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, dateTimeOriginal: dateTime.toIso8601String()));
|
||||
}
|
||||
|
||||
Future<StackResponse> stack(List<String> ids) async {
|
||||
final responseDto = await checkNull(_stacksApi.createStack(StackCreateDto(assetIds: ids)));
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/common/date_time_picker.dart';
|
||||
import 'package:immich_mobile/widgets/common/location_picker.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart' as maplibre;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -160,44 +159,6 @@ class ActionService {
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> editDateTime(List<String> remoteIds, BuildContext context) async {
|
||||
DateTime? initialDate;
|
||||
String? timeZone;
|
||||
Duration? offset;
|
||||
|
||||
if (remoteIds.length == 1) {
|
||||
final assetId = remoteIds.first;
|
||||
final asset = await _remoteAssetRepository.get(assetId);
|
||||
if (asset == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final exifData = await _remoteAssetRepository.getExif(assetId);
|
||||
initialDate = asset.createdAt.toLocal();
|
||||
offset = initialDate.timeZoneOffset;
|
||||
timeZone = exifData?.timeZone;
|
||||
}
|
||||
|
||||
final dateTime = await showDateTimePicker(
|
||||
context: context,
|
||||
initialDateTime: initialDate,
|
||||
initialTZ: timeZone,
|
||||
initialTZOffset: offset,
|
||||
);
|
||||
|
||||
if (dateTime == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// convert dateTime to DateTime object
|
||||
final parsedDateTime = DateTime.parse(dateTime);
|
||||
|
||||
await _assetApiRepository.updateDateTime(remoteIds, parsedDateTime);
|
||||
await _remoteAssetRepository.updateDateTime(remoteIds, parsedDateTime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<int> removeFromAlbum(List<String> remoteIds, String albumId) async {
|
||||
int removedCount = 0;
|
||||
final result = await _albumApiRepository.removeAssets(albumId, remoteIds);
|
||||
|
||||
@@ -99,7 +99,6 @@ class UploadService {
|
||||
}
|
||||
|
||||
Future<void> manualBackup(List<LocalAsset> localAssets) async {
|
||||
await _storageRepository.clearCache();
|
||||
List<UploadTask> tasks = [];
|
||||
for (final asset in localAssets) {
|
||||
final task = await _getUploadTask(
|
||||
@@ -121,8 +120,6 @@ class UploadService {
|
||||
/// Build the upload tasks
|
||||
/// Enqueue the tasks
|
||||
Future<void> startBackup(String userId, void Function(EnqueueStatus status) onEnqueueTasks) async {
|
||||
await _storageRepository.clearCache();
|
||||
|
||||
shouldAbortQueuingTasks = false;
|
||||
|
||||
final candidates = await _backupRepository.getCandidates(userId);
|
||||
@@ -162,7 +159,6 @@ class UploadService {
|
||||
Future<int> cancelBackup() async {
|
||||
shouldAbortQueuingTasks = true;
|
||||
|
||||
await _storageRepository.clearCache();
|
||||
await _uploadRepository.reset(kBackupGroup);
|
||||
await _uploadRepository.deleteDatabaseRecords(kBackupGroup);
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ class _DateTimePicker extends HookWidget {
|
||||
1,
|
||||
),
|
||||
trailing: Icon(Icons.edit_outlined, size: 18, color: context.primaryColor),
|
||||
title: Text(DateFormat("dd-MM-yyyy hh:mm a").format(date.value), style: context.textTheme.bodyMedium),
|
||||
title: Text(DateFormat("dd-MM-yyyy hh:mm a").format(date.value), style: context.textTheme.bodyMedium).tr(),
|
||||
onTap: pickDate,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
@@ -10,7 +10,6 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
||||
import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
@@ -105,10 +104,6 @@ class BetaSyncSettings extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearFileCache() async {
|
||||
await ref.read(storageRepositoryProvider).clearCache();
|
||||
}
|
||||
|
||||
return FutureBuilder<List<dynamic>>(
|
||||
future: loadCounts(),
|
||||
builder: (context, snapshot) {
|
||||
@@ -246,14 +241,6 @@ class BetaSyncSettings extends HookConsumerWidget {
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
const SizedBox(height: 24),
|
||||
_SectionHeaderText(text: "actions".t(context: context)),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"clear_file_cache".t(context: context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
leading: const Icon(Icons.playlist_remove_rounded),
|
||||
onTap: clearFileCache,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
"export_database".t(context: context),
|
||||
|
||||
34
mobile/openapi/README.md
generated
34
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.137.2
|
||||
- API version: 1.136.0
|
||||
- Generator version: 7.8.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
@@ -83,14 +83,18 @@ Class | Method | HTTP request | Description
|
||||
*ActivitiesApi* | [**getActivities**](doc//ActivitiesApi.md#getactivities) | **GET** /activities |
|
||||
*ActivitiesApi* | [**getActivityStatistics**](doc//ActivitiesApi.md#getactivitystatistics) | **GET** /activities/statistics |
|
||||
*AlbumsApi* | [**addAssetsToAlbum**](doc//AlbumsApi.md#addassetstoalbum) | **PUT** /albums/{id}/assets |
|
||||
*AlbumsApi* | [**addGroupsToAlbum**](doc//AlbumsApi.md#addgroupstoalbum) | **PUT** /albums/{id}/groups |
|
||||
*AlbumsApi* | [**addUsersToAlbum**](doc//AlbumsApi.md#adduserstoalbum) | **PUT** /albums/{id}/users |
|
||||
*AlbumsApi* | [**createAlbum**](doc//AlbumsApi.md#createalbum) | **POST** /albums |
|
||||
*AlbumsApi* | [**deleteAlbum**](doc//AlbumsApi.md#deletealbum) | **DELETE** /albums/{id} |
|
||||
*AlbumsApi* | [**getAlbumInfo**](doc//AlbumsApi.md#getalbuminfo) | **GET** /albums/{id} |
|
||||
*AlbumsApi* | [**getAlbumStatistics**](doc//AlbumsApi.md#getalbumstatistics) | **GET** /albums/statistics |
|
||||
*AlbumsApi* | [**getAllAlbums**](doc//AlbumsApi.md#getallalbums) | **GET** /albums |
|
||||
*AlbumsApi* | [**getGroupsForAlbum**](doc//AlbumsApi.md#getgroupsforalbum) | **GET** /albums/{id}/groups |
|
||||
*AlbumsApi* | [**removeAssetFromAlbum**](doc//AlbumsApi.md#removeassetfromalbum) | **DELETE** /albums/{id}/assets |
|
||||
*AlbumsApi* | [**removeGroupsFromAlbum**](doc//AlbumsApi.md#removegroupsfromalbum) | **DELETE** /albums/{id}/groups |
|
||||
*AlbumsApi* | [**removeUserFromAlbum**](doc//AlbumsApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} |
|
||||
*AlbumsApi* | [**updateAlbumGroup**](doc//AlbumsApi.md#updatealbumgroup) | **PUT** /albums/{id}/groups/{groupId} |
|
||||
*AlbumsApi* | [**updateAlbumInfo**](doc//AlbumsApi.md#updatealbuminfo) | **PATCH** /albums/{id} |
|
||||
*AlbumsApi* | [**updateAlbumUser**](doc//AlbumsApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} |
|
||||
*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | checkBulkUpload
|
||||
@@ -129,6 +133,19 @@ Class | Method | HTTP request | Description
|
||||
*FacesApi* | [**deleteFace**](doc//FacesApi.md#deleteface) | **DELETE** /faces/{id} |
|
||||
*FacesApi* | [**getFaces**](doc//FacesApi.md#getfaces) | **GET** /faces |
|
||||
*FacesApi* | [**reassignFacesById**](doc//FacesApi.md#reassignfacesbyid) | **PUT** /faces/{id} |
|
||||
*GroupsApi* | [**getMyGroup**](doc//GroupsApi.md#getmygroup) | **GET** /groups/{id} |
|
||||
*GroupsApi* | [**getMyGroupUsers**](doc//GroupsApi.md#getmygroupusers) | **GET** /groups/{id}/users |
|
||||
*GroupsApi* | [**leaveMyGroup**](doc//GroupsApi.md#leavemygroup) | **DELETE** /groups/{id} |
|
||||
*GroupsApi* | [**searchMyGroups**](doc//GroupsApi.md#searchmygroups) | **GET** /groups |
|
||||
*GroupsAdminApi* | [**addUsersToGroupAdmin**](doc//GroupsAdminApi.md#adduserstogroupadmin) | **PUT** /admin/groups/{id}/users |
|
||||
*GroupsAdminApi* | [**createGroupAdmin**](doc//GroupsAdminApi.md#creategroupadmin) | **POST** /admin/groups |
|
||||
*GroupsAdminApi* | [**deleteGroupAdmin**](doc//GroupsAdminApi.md#deletegroupadmin) | **DELETE** /admin/groups/{id} |
|
||||
*GroupsAdminApi* | [**getGroupAdmin**](doc//GroupsAdminApi.md#getgroupadmin) | **GET** /admin/groups/{id} |
|
||||
*GroupsAdminApi* | [**getUsersForGroupAdmin**](doc//GroupsAdminApi.md#getusersforgroupadmin) | **GET** /admin/groups/{id}/users |
|
||||
*GroupsAdminApi* | [**removeUserFromGroupAdmin**](doc//GroupsAdminApi.md#removeuserfromgroupadmin) | **DELETE** /admin/groups/{id}/user/{userId} |
|
||||
*GroupsAdminApi* | [**removeUsersFromGroupAdmin**](doc//GroupsAdminApi.md#removeusersfromgroupadmin) | **DELETE** /admin/groups/{id}/user |
|
||||
*GroupsAdminApi* | [**searchGroupsAdmin**](doc//GroupsAdminApi.md#searchgroupsadmin) | **GET** /admin/groups |
|
||||
*GroupsAdminApi* | [**updateGroupAdmin**](doc//GroupsAdminApi.md#updategroupadmin) | **PUT** /admin/groups/{id} |
|
||||
*JobsApi* | [**createJob**](doc//JobsApi.md#createjob) | **POST** /jobs |
|
||||
*JobsApi* | [**getAllJobsStatus**](doc//JobsApi.md#getalljobsstatus) | **GET** /jobs |
|
||||
*JobsApi* | [**sendJobCommand**](doc//JobsApi.md#sendjobcommand) | **PUT** /jobs/{id} |
|
||||
@@ -292,6 +309,12 @@ Class | Method | HTTP request | Description
|
||||
- [ActivityStatisticsResponseDto](doc//ActivityStatisticsResponseDto.md)
|
||||
- [AddUsersDto](doc//AddUsersDto.md)
|
||||
- [AdminOnboardingUpdateDto](doc//AdminOnboardingUpdateDto.md)
|
||||
- [AlbumGroupCreateAllDto](doc//AlbumGroupCreateAllDto.md)
|
||||
- [AlbumGroupDeleteAllDto](doc//AlbumGroupDeleteAllDto.md)
|
||||
- [AlbumGroupDto](doc//AlbumGroupDto.md)
|
||||
- [AlbumGroupMetadata](doc//AlbumGroupMetadata.md)
|
||||
- [AlbumGroupResponseDto](doc//AlbumGroupResponseDto.md)
|
||||
- [AlbumGroupUpdateDto](doc//AlbumGroupUpdateDto.md)
|
||||
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
||||
- [AlbumStatisticsResponseDto](doc//AlbumStatisticsResponseDto.md)
|
||||
- [AlbumUserAddDto](doc//AlbumUserAddDto.md)
|
||||
@@ -360,6 +383,15 @@ Class | Method | HTTP request | Description
|
||||
- [FacialRecognitionConfig](doc//FacialRecognitionConfig.md)
|
||||
- [FoldersResponse](doc//FoldersResponse.md)
|
||||
- [FoldersUpdate](doc//FoldersUpdate.md)
|
||||
- [GroupAdminCreateDto](doc//GroupAdminCreateDto.md)
|
||||
- [GroupAdminResponseDto](doc//GroupAdminResponseDto.md)
|
||||
- [GroupAdminUpdateDto](doc//GroupAdminUpdateDto.md)
|
||||
- [GroupResponseDto](doc//GroupResponseDto.md)
|
||||
- [GroupUserCreateAllDto](doc//GroupUserCreateAllDto.md)
|
||||
- [GroupUserDeleteAllDto](doc//GroupUserDeleteAllDto.md)
|
||||
- [GroupUserDto](doc//GroupUserDto.md)
|
||||
- [GroupUserMetadata](doc//GroupUserMetadata.md)
|
||||
- [GroupUserResponseDto](doc//GroupUserResponseDto.md)
|
||||
- [ImageFormat](doc//ImageFormat.md)
|
||||
- [JobCommand](doc//JobCommand.md)
|
||||
- [JobCommandDto](doc//JobCommandDto.md)
|
||||
|
||||
17
mobile/openapi/lib/api.dart
generated
17
mobile/openapi/lib/api.dart
generated
@@ -39,6 +39,8 @@ part 'api/deprecated_api.dart';
|
||||
part 'api/download_api.dart';
|
||||
part 'api/duplicates_api.dart';
|
||||
part 'api/faces_api.dart';
|
||||
part 'api/groups_api.dart';
|
||||
part 'api/groups_admin_api.dart';
|
||||
part 'api/jobs_api.dart';
|
||||
part 'api/libraries_api.dart';
|
||||
part 'api/map_api.dart';
|
||||
@@ -72,6 +74,12 @@ part 'model/activity_response_dto.dart';
|
||||
part 'model/activity_statistics_response_dto.dart';
|
||||
part 'model/add_users_dto.dart';
|
||||
part 'model/admin_onboarding_update_dto.dart';
|
||||
part 'model/album_group_create_all_dto.dart';
|
||||
part 'model/album_group_delete_all_dto.dart';
|
||||
part 'model/album_group_dto.dart';
|
||||
part 'model/album_group_metadata.dart';
|
||||
part 'model/album_group_response_dto.dart';
|
||||
part 'model/album_group_update_dto.dart';
|
||||
part 'model/album_response_dto.dart';
|
||||
part 'model/album_statistics_response_dto.dart';
|
||||
part 'model/album_user_add_dto.dart';
|
||||
@@ -140,6 +148,15 @@ part 'model/face_dto.dart';
|
||||
part 'model/facial_recognition_config.dart';
|
||||
part 'model/folders_response.dart';
|
||||
part 'model/folders_update.dart';
|
||||
part 'model/group_admin_create_dto.dart';
|
||||
part 'model/group_admin_response_dto.dart';
|
||||
part 'model/group_admin_update_dto.dart';
|
||||
part 'model/group_response_dto.dart';
|
||||
part 'model/group_user_create_all_dto.dart';
|
||||
part 'model/group_user_delete_all_dto.dart';
|
||||
part 'model/group_user_dto.dart';
|
||||
part 'model/group_user_metadata.dart';
|
||||
part 'model/group_user_response_dto.dart';
|
||||
part 'model/image_format.dart';
|
||||
part 'model/job_command.dart';
|
||||
part 'model/job_command_dto.dart';
|
||||
|
||||
227
mobile/openapi/lib/api/albums_api.dart
generated
227
mobile/openapi/lib/api/albums_api.dart
generated
@@ -91,6 +91,66 @@ class AlbumsApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumGroup.create` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AlbumGroupCreateAllDto] albumGroupCreateAllDto (required):
|
||||
Future<Response> addGroupsToAlbumWithHttpInfo(String id, AlbumGroupCreateAllDto albumGroupCreateAllDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/albums/{id}/groups'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = albumGroupCreateAllDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumGroup.create` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AlbumGroupCreateAllDto] albumGroupCreateAllDto (required):
|
||||
Future<List<AlbumGroupResponseDto>?> addGroupsToAlbum(String id, AlbumGroupCreateAllDto albumGroupCreateAllDto,) async {
|
||||
final response = await addGroupsToAlbumWithHttpInfo(id, albumGroupCreateAllDto,);
|
||||
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) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<AlbumGroupResponseDto>') as List)
|
||||
.cast<AlbumGroupResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumUser.create` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
@@ -432,6 +492,62 @@ class AlbumsApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumGroup.read` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> getGroupsForAlbumWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/albums/{id}/groups'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumGroup.read` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<List<AlbumGroupResponseDto>?> getGroupsForAlbum(String id,) async {
|
||||
final response = await getGroupsForAlbumWithHttpInfo(id,);
|
||||
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) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<AlbumGroupResponseDto>') as List)
|
||||
.cast<AlbumGroupResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumAsset.delete` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
@@ -492,6 +608,55 @@ class AlbumsApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumGroup.delete` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AlbumGroupDeleteAllDto] albumGroupDeleteAllDto (required):
|
||||
Future<Response> removeGroupsFromAlbumWithHttpInfo(String id, AlbumGroupDeleteAllDto albumGroupDeleteAllDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/albums/{id}/groups'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = albumGroupDeleteAllDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'DELETE',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumGroup.delete` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AlbumGroupDeleteAllDto] albumGroupDeleteAllDto (required):
|
||||
Future<void> removeGroupsFromAlbum(String id, AlbumGroupDeleteAllDto albumGroupDeleteAllDto,) async {
|
||||
final response = await removeGroupsFromAlbumWithHttpInfo(id, albumGroupDeleteAllDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumUser.delete` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
@@ -542,6 +707,68 @@ class AlbumsApi {
|
||||
}
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumGroup.update` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] groupId (required):
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AlbumGroupUpdateDto] albumGroupUpdateDto (required):
|
||||
Future<Response> updateAlbumGroupWithHttpInfo(String groupId, String id, AlbumGroupUpdateDto albumGroupUpdateDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/albums/{id}/groups/{groupId}'
|
||||
.replaceAll('{groupId}', groupId)
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = albumGroupUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `albumGroup.update` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] groupId (required):
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AlbumGroupUpdateDto] albumGroupUpdateDto (required):
|
||||
Future<AlbumGroupResponseDto?> updateAlbumGroup(String groupId, String id, AlbumGroupUpdateDto albumGroupUpdateDto,) async {
|
||||
final response = await updateAlbumGroupWithHttpInfo(groupId, id, albumGroupUpdateDto,);
|
||||
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), 'AlbumGroupResponseDto',) as AlbumGroupResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint requires the `album.update` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
|
||||
506
mobile/openapi/lib/api/groups_admin_api.dart
generated
Normal file
506
mobile/openapi/lib/api/groups_admin_api.dart
generated
Normal file
@@ -0,0 +1,506 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupsAdminApi {
|
||||
GroupsAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroupUser.create` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [GroupUserCreateAllDto] groupUserCreateAllDto (required):
|
||||
Future<Response> addUsersToGroupAdminWithHttpInfo(String id, GroupUserCreateAllDto groupUserCreateAllDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/groups/{id}/users'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = groupUserCreateAllDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroupUser.create` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [GroupUserCreateAllDto] groupUserCreateAllDto (required):
|
||||
Future<List<GroupUserResponseDto>?> addUsersToGroupAdmin(String id, GroupUserCreateAllDto groupUserCreateAllDto,) async {
|
||||
final response = await addUsersToGroupAdminWithHttpInfo(id, groupUserCreateAllDto,);
|
||||
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) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<GroupUserResponseDto>') as List)
|
||||
.cast<GroupUserResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroup.create` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [GroupAdminCreateDto] groupAdminCreateDto (required):
|
||||
Future<Response> createGroupAdminWithHttpInfo(GroupAdminCreateDto groupAdminCreateDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/groups';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = groupAdminCreateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroup.create` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [GroupAdminCreateDto] groupAdminCreateDto (required):
|
||||
Future<GroupAdminResponseDto?> createGroupAdmin(GroupAdminCreateDto groupAdminCreateDto,) async {
|
||||
final response = await createGroupAdminWithHttpInfo(groupAdminCreateDto,);
|
||||
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), 'GroupAdminResponseDto',) as GroupAdminResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroup.delete` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> deleteGroupAdminWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/groups/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'DELETE',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroup.delete` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<void> deleteGroupAdmin(String id,) async {
|
||||
final response = await deleteGroupAdminWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroup.read` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> getGroupAdminWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/groups/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroup.read` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<GroupAdminResponseDto?> getGroupAdmin(String id,) async {
|
||||
final response = await getGroupAdminWithHttpInfo(id,);
|
||||
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), 'GroupAdminResponseDto',) as GroupAdminResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroupUser.read` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> getUsersForGroupAdminWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/groups/{id}/users'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroupUser.read` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<List<GroupUserResponseDto>?> getUsersForGroupAdmin(String id,) async {
|
||||
final response = await getUsersForGroupAdminWithHttpInfo(id,);
|
||||
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) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<GroupUserResponseDto>') as List)
|
||||
.cast<GroupUserResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint requires the `adminGroupUser.delete` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [String] userId (required):
|
||||
Future<Response> removeUserFromGroupAdminWithHttpInfo(String id, String userId,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/groups/{id}/user/{userId}'
|
||||
.replaceAll('{id}', id)
|
||||
.replaceAll('{userId}', userId);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'DELETE',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `adminGroupUser.delete` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [String] userId (required):
|
||||
Future<void> removeUserFromGroupAdmin(String id, String userId,) async {
|
||||
final response = await removeUserFromGroupAdminWithHttpInfo(id, userId,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// This endpoint requires the `adminGroupUser.delete` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [GroupUserDeleteAllDto] groupUserDeleteAllDto (required):
|
||||
Future<Response> removeUsersFromGroupAdminWithHttpInfo(String id, GroupUserDeleteAllDto groupUserDeleteAllDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/groups/{id}/user'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = groupUserDeleteAllDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'DELETE',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `adminGroupUser.delete` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [GroupUserDeleteAllDto] groupUserDeleteAllDto (required):
|
||||
Future<void> removeUsersFromGroupAdmin(String id, GroupUserDeleteAllDto groupUserDeleteAllDto,) async {
|
||||
final response = await removeUsersFromGroupAdminWithHttpInfo(id, groupUserDeleteAllDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroup.read` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id:
|
||||
///
|
||||
/// * [String] userId:
|
||||
Future<Response> searchGroupsAdminWithHttpInfo({ String? id, String? userId, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/groups';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
if (id != null) {
|
||||
queryParams.addAll(_queryParams('', 'id', id));
|
||||
}
|
||||
if (userId != null) {
|
||||
queryParams.addAll(_queryParams('', 'userId', userId));
|
||||
}
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroup.read` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id:
|
||||
///
|
||||
/// * [String] userId:
|
||||
Future<List<GroupAdminResponseDto>?> searchGroupsAdmin({ String? id, String? userId, }) async {
|
||||
final response = await searchGroupsAdminWithHttpInfo( id: id, userId: userId, );
|
||||
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) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<GroupAdminResponseDto>') as List)
|
||||
.cast<GroupAdminResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroup.update` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [GroupAdminUpdateDto] groupAdminUpdateDto (required):
|
||||
Future<Response> updateGroupAdminWithHttpInfo(String id, GroupAdminUpdateDto groupAdminUpdateDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/groups/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = groupAdminUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `adminGroup.update` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [GroupAdminUpdateDto] groupAdminUpdateDto (required):
|
||||
Future<GroupAdminResponseDto?> updateGroupAdmin(String id, GroupAdminUpdateDto groupAdminUpdateDto,) async {
|
||||
final response = await updateGroupAdminWithHttpInfo(id, groupAdminUpdateDto,);
|
||||
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), 'GroupAdminResponseDto',) as GroupAdminResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
219
mobile/openapi/lib/api/groups_api.dart
generated
Normal file
219
mobile/openapi/lib/api/groups_api.dart
generated
Normal file
@@ -0,0 +1,219 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupsApi {
|
||||
GroupsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// This endpoint requires the `group.read` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> getMyGroupWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/groups/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `group.read` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<GroupResponseDto?> getMyGroup(String id,) async {
|
||||
final response = await getMyGroupWithHttpInfo(id,);
|
||||
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), 'GroupResponseDto',) as GroupResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `group.read` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> getMyGroupUsersWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/groups/{id}/users'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint is an admin-only route, and requires the `group.read` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<List<GroupUserResponseDto>?> getMyGroupUsers(String id,) async {
|
||||
final response = await getMyGroupUsersWithHttpInfo(id,);
|
||||
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) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<GroupUserResponseDto>') as List)
|
||||
.cast<GroupUserResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// This endpoint requires the `group.delete` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> leaveMyGroupWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/groups/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'DELETE',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `group.delete` permission.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<void> leaveMyGroup(String id,) async {
|
||||
final response = await leaveMyGroupWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// This endpoint requires the `group.read` permission.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
Future<Response> searchMyGroupsWithHttpInfo() async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/groups';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// This endpoint requires the `group.read` permission.
|
||||
Future<List<GroupResponseDto>?> searchMyGroups() async {
|
||||
final response = await searchMyGroupsWithHttpInfo();
|
||||
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) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<GroupResponseDto>') as List)
|
||||
.cast<GroupResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
30
mobile/openapi/lib/api_client.dart
generated
30
mobile/openapi/lib/api_client.dart
generated
@@ -200,6 +200,18 @@ class ApiClient {
|
||||
return AddUsersDto.fromJson(value);
|
||||
case 'AdminOnboardingUpdateDto':
|
||||
return AdminOnboardingUpdateDto.fromJson(value);
|
||||
case 'AlbumGroupCreateAllDto':
|
||||
return AlbumGroupCreateAllDto.fromJson(value);
|
||||
case 'AlbumGroupDeleteAllDto':
|
||||
return AlbumGroupDeleteAllDto.fromJson(value);
|
||||
case 'AlbumGroupDto':
|
||||
return AlbumGroupDto.fromJson(value);
|
||||
case 'AlbumGroupMetadata':
|
||||
return AlbumGroupMetadata.fromJson(value);
|
||||
case 'AlbumGroupResponseDto':
|
||||
return AlbumGroupResponseDto.fromJson(value);
|
||||
case 'AlbumGroupUpdateDto':
|
||||
return AlbumGroupUpdateDto.fromJson(value);
|
||||
case 'AlbumResponseDto':
|
||||
return AlbumResponseDto.fromJson(value);
|
||||
case 'AlbumStatisticsResponseDto':
|
||||
@@ -336,6 +348,24 @@ class ApiClient {
|
||||
return FoldersResponse.fromJson(value);
|
||||
case 'FoldersUpdate':
|
||||
return FoldersUpdate.fromJson(value);
|
||||
case 'GroupAdminCreateDto':
|
||||
return GroupAdminCreateDto.fromJson(value);
|
||||
case 'GroupAdminResponseDto':
|
||||
return GroupAdminResponseDto.fromJson(value);
|
||||
case 'GroupAdminUpdateDto':
|
||||
return GroupAdminUpdateDto.fromJson(value);
|
||||
case 'GroupResponseDto':
|
||||
return GroupResponseDto.fromJson(value);
|
||||
case 'GroupUserCreateAllDto':
|
||||
return GroupUserCreateAllDto.fromJson(value);
|
||||
case 'GroupUserDeleteAllDto':
|
||||
return GroupUserDeleteAllDto.fromJson(value);
|
||||
case 'GroupUserDto':
|
||||
return GroupUserDto.fromJson(value);
|
||||
case 'GroupUserMetadata':
|
||||
return GroupUserMetadata.fromJson(value);
|
||||
case 'GroupUserResponseDto':
|
||||
return GroupUserResponseDto.fromJson(value);
|
||||
case 'ImageFormat':
|
||||
return ImageFormatTypeTransformer().decode(value);
|
||||
case 'JobCommand':
|
||||
|
||||
99
mobile/openapi/lib/model/album_group_create_all_dto.dart
generated
Normal file
99
mobile/openapi/lib/model/album_group_create_all_dto.dart
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 AlbumGroupCreateAllDto {
|
||||
/// Returns a new [AlbumGroupCreateAllDto] instance.
|
||||
AlbumGroupCreateAllDto({
|
||||
this.groups = const [],
|
||||
});
|
||||
|
||||
List<AlbumGroupDto> groups;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AlbumGroupCreateAllDto &&
|
||||
_deepEquality.equals(other.groups, groups);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(groups.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AlbumGroupCreateAllDto[groups=$groups]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'groups'] = this.groups;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AlbumGroupCreateAllDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AlbumGroupCreateAllDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AlbumGroupCreateAllDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AlbumGroupCreateAllDto(
|
||||
groups: AlbumGroupDto.listFromJson(json[r'groups']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AlbumGroupCreateAllDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AlbumGroupCreateAllDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AlbumGroupCreateAllDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AlbumGroupCreateAllDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AlbumGroupCreateAllDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AlbumGroupCreateAllDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AlbumGroupCreateAllDto-objects as value to a dart map
|
||||
static Map<String, List<AlbumGroupCreateAllDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AlbumGroupCreateAllDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AlbumGroupCreateAllDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'groups',
|
||||
};
|
||||
}
|
||||
|
||||
101
mobile/openapi/lib/model/album_group_delete_all_dto.dart
generated
Normal file
101
mobile/openapi/lib/model/album_group_delete_all_dto.dart
generated
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 AlbumGroupDeleteAllDto {
|
||||
/// Returns a new [AlbumGroupDeleteAllDto] instance.
|
||||
AlbumGroupDeleteAllDto({
|
||||
this.groupIds = const [],
|
||||
});
|
||||
|
||||
List<String> groupIds;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AlbumGroupDeleteAllDto &&
|
||||
_deepEquality.equals(other.groupIds, groupIds);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(groupIds.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AlbumGroupDeleteAllDto[groupIds=$groupIds]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'groupIds'] = this.groupIds;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AlbumGroupDeleteAllDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AlbumGroupDeleteAllDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AlbumGroupDeleteAllDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AlbumGroupDeleteAllDto(
|
||||
groupIds: json[r'groupIds'] is Iterable
|
||||
? (json[r'groupIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AlbumGroupDeleteAllDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AlbumGroupDeleteAllDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AlbumGroupDeleteAllDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AlbumGroupDeleteAllDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AlbumGroupDeleteAllDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AlbumGroupDeleteAllDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AlbumGroupDeleteAllDto-objects as value to a dart map
|
||||
static Map<String, List<AlbumGroupDeleteAllDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AlbumGroupDeleteAllDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AlbumGroupDeleteAllDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'groupIds',
|
||||
};
|
||||
}
|
||||
|
||||
116
mobile/openapi/lib/model/album_group_dto.dart
generated
Normal file
116
mobile/openapi/lib/model/album_group_dto.dart
generated
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 AlbumGroupDto {
|
||||
/// Returns a new [AlbumGroupDto] instance.
|
||||
AlbumGroupDto({
|
||||
required this.groupId,
|
||||
this.role,
|
||||
});
|
||||
|
||||
String groupId;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
AlbumUserRole? role;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AlbumGroupDto &&
|
||||
other.groupId == groupId &&
|
||||
other.role == role;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(groupId.hashCode) +
|
||||
(role == null ? 0 : role!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AlbumGroupDto[groupId=$groupId, role=$role]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'groupId'] = this.groupId;
|
||||
if (this.role != null) {
|
||||
json[r'role'] = this.role;
|
||||
} else {
|
||||
// json[r'role'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AlbumGroupDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AlbumGroupDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AlbumGroupDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AlbumGroupDto(
|
||||
groupId: mapValueOfType<String>(json, r'groupId')!,
|
||||
role: AlbumUserRole.fromJson(json[r'role']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AlbumGroupDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AlbumGroupDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AlbumGroupDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AlbumGroupDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AlbumGroupDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AlbumGroupDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AlbumGroupDto-objects as value to a dart map
|
||||
static Map<String, List<AlbumGroupDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AlbumGroupDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AlbumGroupDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'groupId',
|
||||
};
|
||||
}
|
||||
|
||||
107
mobile/openapi/lib/model/album_group_metadata.dart
generated
Normal file
107
mobile/openapi/lib/model/album_group_metadata.dart
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 AlbumGroupMetadata {
|
||||
/// Returns a new [AlbumGroupMetadata] instance.
|
||||
AlbumGroupMetadata({
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
DateTime createdAt;
|
||||
|
||||
DateTime updatedAt;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AlbumGroupMetadata &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(createdAt.hashCode) +
|
||||
(updatedAt.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AlbumGroupMetadata[createdAt=$createdAt, updatedAt=$updatedAt]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AlbumGroupMetadata] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AlbumGroupMetadata? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AlbumGroupMetadata");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AlbumGroupMetadata(
|
||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AlbumGroupMetadata> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AlbumGroupMetadata>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AlbumGroupMetadata.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AlbumGroupMetadata> mapFromJson(dynamic json) {
|
||||
final map = <String, AlbumGroupMetadata>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AlbumGroupMetadata.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AlbumGroupMetadata-objects as value to a dart map
|
||||
static Map<String, List<AlbumGroupMetadata>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AlbumGroupMetadata>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AlbumGroupMetadata.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
};
|
||||
}
|
||||
|
||||
127
mobile/openapi/lib/model/album_group_response_dto.dart
generated
Normal file
127
mobile/openapi/lib/model/album_group_response_dto.dart
generated
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 AlbumGroupResponseDto {
|
||||
/// Returns a new [AlbumGroupResponseDto] instance.
|
||||
AlbumGroupResponseDto({
|
||||
required this.description,
|
||||
required this.id,
|
||||
required this.metadata,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
String? description;
|
||||
|
||||
String id;
|
||||
|
||||
AlbumGroupMetadata metadata;
|
||||
|
||||
String name;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AlbumGroupResponseDto &&
|
||||
other.description == description &&
|
||||
other.id == id &&
|
||||
other.metadata == metadata &&
|
||||
other.name == name;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(id.hashCode) +
|
||||
(metadata.hashCode) +
|
||||
(name.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AlbumGroupResponseDto[description=$description, id=$id, metadata=$metadata, name=$name]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.description != null) {
|
||||
json[r'description'] = this.description;
|
||||
} else {
|
||||
// json[r'description'] = null;
|
||||
}
|
||||
json[r'id'] = this.id;
|
||||
json[r'metadata'] = this.metadata;
|
||||
json[r'name'] = this.name;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AlbumGroupResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AlbumGroupResponseDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AlbumGroupResponseDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AlbumGroupResponseDto(
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
metadata: AlbumGroupMetadata.fromJson(json[r'metadata'])!,
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AlbumGroupResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AlbumGroupResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AlbumGroupResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AlbumGroupResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AlbumGroupResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AlbumGroupResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AlbumGroupResponseDto-objects as value to a dart map
|
||||
static Map<String, List<AlbumGroupResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AlbumGroupResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AlbumGroupResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'description',
|
||||
'id',
|
||||
'metadata',
|
||||
'name',
|
||||
};
|
||||
}
|
||||
|
||||
99
mobile/openapi/lib/model/album_group_update_dto.dart
generated
Normal file
99
mobile/openapi/lib/model/album_group_update_dto.dart
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 AlbumGroupUpdateDto {
|
||||
/// Returns a new [AlbumGroupUpdateDto] instance.
|
||||
AlbumGroupUpdateDto({
|
||||
required this.role,
|
||||
});
|
||||
|
||||
AlbumUserRole role;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AlbumGroupUpdateDto &&
|
||||
other.role == role;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(role.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AlbumGroupUpdateDto[role=$role]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'role'] = this.role;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AlbumGroupUpdateDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AlbumGroupUpdateDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AlbumGroupUpdateDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AlbumGroupUpdateDto(
|
||||
role: AlbumUserRole.fromJson(json[r'role'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AlbumGroupUpdateDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AlbumGroupUpdateDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AlbumGroupUpdateDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AlbumGroupUpdateDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AlbumGroupUpdateDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AlbumGroupUpdateDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AlbumGroupUpdateDto-objects as value to a dart map
|
||||
static Map<String, List<AlbumGroupUpdateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AlbumGroupUpdateDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AlbumGroupUpdateDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'role',
|
||||
};
|
||||
}
|
||||
|
||||
13
mobile/openapi/lib/model/create_album_dto.dart
generated
13
mobile/openapi/lib/model/create_album_dto.dart
generated
@@ -17,6 +17,7 @@ class CreateAlbumDto {
|
||||
this.albumUsers = const [],
|
||||
this.assetIds = const [],
|
||||
this.description,
|
||||
this.groups = const [],
|
||||
});
|
||||
|
||||
String albumName;
|
||||
@@ -33,12 +34,15 @@ class CreateAlbumDto {
|
||||
///
|
||||
String? description;
|
||||
|
||||
List<AlbumGroupDto> groups;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is CreateAlbumDto &&
|
||||
other.albumName == albumName &&
|
||||
_deepEquality.equals(other.albumUsers, albumUsers) &&
|
||||
_deepEquality.equals(other.assetIds, assetIds) &&
|
||||
other.description == description;
|
||||
other.description == description &&
|
||||
_deepEquality.equals(other.groups, groups);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -46,10 +50,11 @@ class CreateAlbumDto {
|
||||
(albumName.hashCode) +
|
||||
(albumUsers.hashCode) +
|
||||
(assetIds.hashCode) +
|
||||
(description == null ? 0 : description!.hashCode);
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(groups.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'CreateAlbumDto[albumName=$albumName, albumUsers=$albumUsers, assetIds=$assetIds, description=$description]';
|
||||
String toString() => 'CreateAlbumDto[albumName=$albumName, albumUsers=$albumUsers, assetIds=$assetIds, description=$description, groups=$groups]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -61,6 +66,7 @@ class CreateAlbumDto {
|
||||
} else {
|
||||
// json[r'description'] = null;
|
||||
}
|
||||
json[r'groups'] = this.groups;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -79,6 +85,7 @@ class CreateAlbumDto {
|
||||
? (json[r'assetIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
groups: AlbumGroupDto.listFromJson(json[r'groups']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
117
mobile/openapi/lib/model/group_admin_create_dto.dart
generated
Normal file
117
mobile/openapi/lib/model/group_admin_create_dto.dart
generated
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupAdminCreateDto {
|
||||
/// Returns a new [GroupAdminCreateDto] instance.
|
||||
GroupAdminCreateDto({
|
||||
this.description,
|
||||
required this.name,
|
||||
this.users = const [],
|
||||
});
|
||||
|
||||
String? description;
|
||||
|
||||
String name;
|
||||
|
||||
List<GroupUserDto> users;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is GroupAdminCreateDto &&
|
||||
other.description == description &&
|
||||
other.name == name &&
|
||||
_deepEquality.equals(other.users, users);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(name.hashCode) +
|
||||
(users.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'GroupAdminCreateDto[description=$description, name=$name, users=$users]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.description != null) {
|
||||
json[r'description'] = this.description;
|
||||
} else {
|
||||
// json[r'description'] = null;
|
||||
}
|
||||
json[r'name'] = this.name;
|
||||
json[r'users'] = this.users;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [GroupAdminCreateDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static GroupAdminCreateDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "GroupAdminCreateDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return GroupAdminCreateDto(
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
users: GroupUserDto.listFromJson(json[r'users']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<GroupAdminCreateDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <GroupAdminCreateDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = GroupAdminCreateDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, GroupAdminCreateDto> mapFromJson(dynamic json) {
|
||||
final map = <String, GroupAdminCreateDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = GroupAdminCreateDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of GroupAdminCreateDto-objects as value to a dart map
|
||||
static Map<String, List<GroupAdminCreateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<GroupAdminCreateDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = GroupAdminCreateDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'name',
|
||||
};
|
||||
}
|
||||
|
||||
135
mobile/openapi/lib/model/group_admin_response_dto.dart
generated
Normal file
135
mobile/openapi/lib/model/group_admin_response_dto.dart
generated
Normal file
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupAdminResponseDto {
|
||||
/// Returns a new [GroupAdminResponseDto] instance.
|
||||
GroupAdminResponseDto({
|
||||
required this.createdAt,
|
||||
required this.description,
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
DateTime createdAt;
|
||||
|
||||
String? description;
|
||||
|
||||
String id;
|
||||
|
||||
String name;
|
||||
|
||||
DateTime updatedAt;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is GroupAdminResponseDto &&
|
||||
other.createdAt == createdAt &&
|
||||
other.description == description &&
|
||||
other.id == id &&
|
||||
other.name == name &&
|
||||
other.updatedAt == updatedAt;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(createdAt.hashCode) +
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(id.hashCode) +
|
||||
(name.hashCode) +
|
||||
(updatedAt.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'GroupAdminResponseDto[createdAt=$createdAt, description=$description, id=$id, name=$name, updatedAt=$updatedAt]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
if (this.description != null) {
|
||||
json[r'description'] = this.description;
|
||||
} else {
|
||||
// json[r'description'] = null;
|
||||
}
|
||||
json[r'id'] = this.id;
|
||||
json[r'name'] = this.name;
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [GroupAdminResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static GroupAdminResponseDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "GroupAdminResponseDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return GroupAdminResponseDto(
|
||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<GroupAdminResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <GroupAdminResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = GroupAdminResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, GroupAdminResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, GroupAdminResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = GroupAdminResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of GroupAdminResponseDto-objects as value to a dart map
|
||||
static Map<String, List<GroupAdminResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<GroupAdminResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = GroupAdminResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'createdAt',
|
||||
'description',
|
||||
'id',
|
||||
'name',
|
||||
'updatedAt',
|
||||
};
|
||||
}
|
||||
|
||||
119
mobile/openapi/lib/model/group_admin_update_dto.dart
generated
Normal file
119
mobile/openapi/lib/model/group_admin_update_dto.dart
generated
Normal file
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupAdminUpdateDto {
|
||||
/// Returns a new [GroupAdminUpdateDto] instance.
|
||||
GroupAdminUpdateDto({
|
||||
this.description,
|
||||
this.name,
|
||||
});
|
||||
|
||||
String? description;
|
||||
|
||||
///
|
||||
/// 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? name;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is GroupAdminUpdateDto &&
|
||||
other.description == description &&
|
||||
other.name == name;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(name == null ? 0 : name!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'GroupAdminUpdateDto[description=$description, name=$name]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.description != null) {
|
||||
json[r'description'] = this.description;
|
||||
} else {
|
||||
// json[r'description'] = null;
|
||||
}
|
||||
if (this.name != null) {
|
||||
json[r'name'] = this.name;
|
||||
} else {
|
||||
// json[r'name'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [GroupAdminUpdateDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static GroupAdminUpdateDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "GroupAdminUpdateDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return GroupAdminUpdateDto(
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
name: mapValueOfType<String>(json, r'name'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<GroupAdminUpdateDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <GroupAdminUpdateDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = GroupAdminUpdateDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, GroupAdminUpdateDto> mapFromJson(dynamic json) {
|
||||
final map = <String, GroupAdminUpdateDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = GroupAdminUpdateDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of GroupAdminUpdateDto-objects as value to a dart map
|
||||
static Map<String, List<GroupAdminUpdateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<GroupAdminUpdateDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = GroupAdminUpdateDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
119
mobile/openapi/lib/model/group_response_dto.dart
generated
Normal file
119
mobile/openapi/lib/model/group_response_dto.dart
generated
Normal file
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupResponseDto {
|
||||
/// Returns a new [GroupResponseDto] instance.
|
||||
GroupResponseDto({
|
||||
required this.description,
|
||||
required this.id,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
String? description;
|
||||
|
||||
String id;
|
||||
|
||||
String name;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is GroupResponseDto &&
|
||||
other.description == description &&
|
||||
other.id == id &&
|
||||
other.name == name;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(id.hashCode) +
|
||||
(name.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'GroupResponseDto[description=$description, id=$id, name=$name]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.description != null) {
|
||||
json[r'description'] = this.description;
|
||||
} else {
|
||||
// json[r'description'] = null;
|
||||
}
|
||||
json[r'id'] = this.id;
|
||||
json[r'name'] = this.name;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [GroupResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static GroupResponseDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "GroupResponseDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return GroupResponseDto(
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<GroupResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <GroupResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = GroupResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, GroupResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, GroupResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = GroupResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of GroupResponseDto-objects as value to a dart map
|
||||
static Map<String, List<GroupResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<GroupResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = GroupResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'description',
|
||||
'id',
|
||||
'name',
|
||||
};
|
||||
}
|
||||
|
||||
99
mobile/openapi/lib/model/group_user_create_all_dto.dart
generated
Normal file
99
mobile/openapi/lib/model/group_user_create_all_dto.dart
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupUserCreateAllDto {
|
||||
/// Returns a new [GroupUserCreateAllDto] instance.
|
||||
GroupUserCreateAllDto({
|
||||
this.users = const [],
|
||||
});
|
||||
|
||||
List<GroupUserDto> users;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is GroupUserCreateAllDto &&
|
||||
_deepEquality.equals(other.users, users);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(users.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'GroupUserCreateAllDto[users=$users]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'users'] = this.users;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [GroupUserCreateAllDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static GroupUserCreateAllDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "GroupUserCreateAllDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return GroupUserCreateAllDto(
|
||||
users: GroupUserDto.listFromJson(json[r'users']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<GroupUserCreateAllDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <GroupUserCreateAllDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = GroupUserCreateAllDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, GroupUserCreateAllDto> mapFromJson(dynamic json) {
|
||||
final map = <String, GroupUserCreateAllDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = GroupUserCreateAllDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of GroupUserCreateAllDto-objects as value to a dart map
|
||||
static Map<String, List<GroupUserCreateAllDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<GroupUserCreateAllDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = GroupUserCreateAllDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'users',
|
||||
};
|
||||
}
|
||||
|
||||
101
mobile/openapi/lib/model/group_user_delete_all_dto.dart
generated
Normal file
101
mobile/openapi/lib/model/group_user_delete_all_dto.dart
generated
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupUserDeleteAllDto {
|
||||
/// Returns a new [GroupUserDeleteAllDto] instance.
|
||||
GroupUserDeleteAllDto({
|
||||
this.userIds = const [],
|
||||
});
|
||||
|
||||
List<String> userIds;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is GroupUserDeleteAllDto &&
|
||||
_deepEquality.equals(other.userIds, userIds);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(userIds.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'GroupUserDeleteAllDto[userIds=$userIds]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'userIds'] = this.userIds;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [GroupUserDeleteAllDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static GroupUserDeleteAllDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "GroupUserDeleteAllDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return GroupUserDeleteAllDto(
|
||||
userIds: json[r'userIds'] is Iterable
|
||||
? (json[r'userIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<GroupUserDeleteAllDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <GroupUserDeleteAllDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = GroupUserDeleteAllDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, GroupUserDeleteAllDto> mapFromJson(dynamic json) {
|
||||
final map = <String, GroupUserDeleteAllDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = GroupUserDeleteAllDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of GroupUserDeleteAllDto-objects as value to a dart map
|
||||
static Map<String, List<GroupUserDeleteAllDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<GroupUserDeleteAllDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = GroupUserDeleteAllDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'userIds',
|
||||
};
|
||||
}
|
||||
|
||||
99
mobile/openapi/lib/model/group_user_dto.dart
generated
Normal file
99
mobile/openapi/lib/model/group_user_dto.dart
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupUserDto {
|
||||
/// Returns a new [GroupUserDto] instance.
|
||||
GroupUserDto({
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
String userId;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is GroupUserDto &&
|
||||
other.userId == userId;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(userId.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'GroupUserDto[userId=$userId]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'userId'] = this.userId;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [GroupUserDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static GroupUserDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "GroupUserDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return GroupUserDto(
|
||||
userId: mapValueOfType<String>(json, r'userId')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<GroupUserDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <GroupUserDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = GroupUserDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, GroupUserDto> mapFromJson(dynamic json) {
|
||||
final map = <String, GroupUserDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = GroupUserDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of GroupUserDto-objects as value to a dart map
|
||||
static Map<String, List<GroupUserDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<GroupUserDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = GroupUserDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'userId',
|
||||
};
|
||||
}
|
||||
|
||||
107
mobile/openapi/lib/model/group_user_metadata.dart
generated
Normal file
107
mobile/openapi/lib/model/group_user_metadata.dart
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupUserMetadata {
|
||||
/// Returns a new [GroupUserMetadata] instance.
|
||||
GroupUserMetadata({
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
DateTime createdAt;
|
||||
|
||||
DateTime updatedAt;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is GroupUserMetadata &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(createdAt.hashCode) +
|
||||
(updatedAt.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'GroupUserMetadata[createdAt=$createdAt, updatedAt=$updatedAt]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [GroupUserMetadata] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static GroupUserMetadata? fromJson(dynamic value) {
|
||||
upgradeDto(value, "GroupUserMetadata");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return GroupUserMetadata(
|
||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<GroupUserMetadata> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <GroupUserMetadata>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = GroupUserMetadata.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, GroupUserMetadata> mapFromJson(dynamic json) {
|
||||
final map = <String, GroupUserMetadata>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = GroupUserMetadata.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of GroupUserMetadata-objects as value to a dart map
|
||||
static Map<String, List<GroupUserMetadata>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<GroupUserMetadata>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = GroupUserMetadata.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
};
|
||||
}
|
||||
|
||||
147
mobile/openapi/lib/model/group_user_response_dto.dart
generated
Normal file
147
mobile/openapi/lib/model/group_user_response_dto.dart
generated
Normal file
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 GroupUserResponseDto {
|
||||
/// Returns a new [GroupUserResponseDto] instance.
|
||||
GroupUserResponseDto({
|
||||
required this.avatarColor,
|
||||
required this.email,
|
||||
required this.id,
|
||||
required this.metadata,
|
||||
required this.name,
|
||||
required this.profileChangedAt,
|
||||
required this.profileImagePath,
|
||||
});
|
||||
|
||||
UserAvatarColor avatarColor;
|
||||
|
||||
String email;
|
||||
|
||||
String id;
|
||||
|
||||
GroupUserMetadata metadata;
|
||||
|
||||
String name;
|
||||
|
||||
DateTime profileChangedAt;
|
||||
|
||||
String profileImagePath;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is GroupUserResponseDto &&
|
||||
other.avatarColor == avatarColor &&
|
||||
other.email == email &&
|
||||
other.id == id &&
|
||||
other.metadata == metadata &&
|
||||
other.name == name &&
|
||||
other.profileChangedAt == profileChangedAt &&
|
||||
other.profileImagePath == profileImagePath;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(avatarColor.hashCode) +
|
||||
(email.hashCode) +
|
||||
(id.hashCode) +
|
||||
(metadata.hashCode) +
|
||||
(name.hashCode) +
|
||||
(profileChangedAt.hashCode) +
|
||||
(profileImagePath.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'GroupUserResponseDto[avatarColor=$avatarColor, email=$email, id=$id, metadata=$metadata, name=$name, profileChangedAt=$profileChangedAt, profileImagePath=$profileImagePath]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'avatarColor'] = this.avatarColor;
|
||||
json[r'email'] = this.email;
|
||||
json[r'id'] = this.id;
|
||||
json[r'metadata'] = this.metadata;
|
||||
json[r'name'] = this.name;
|
||||
json[r'profileChangedAt'] = this.profileChangedAt.toUtc().toIso8601String();
|
||||
json[r'profileImagePath'] = this.profileImagePath;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [GroupUserResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static GroupUserResponseDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "GroupUserResponseDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return GroupUserResponseDto(
|
||||
avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!,
|
||||
email: mapValueOfType<String>(json, r'email')!,
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
metadata: GroupUserMetadata.fromJson(json[r'metadata'])!,
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
profileChangedAt: mapDateTime(json, r'profileChangedAt', r'')!,
|
||||
profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<GroupUserResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <GroupUserResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = GroupUserResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, GroupUserResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, GroupUserResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = GroupUserResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of GroupUserResponseDto-objects as value to a dart map
|
||||
static Map<String, List<GroupUserResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<GroupUserResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = GroupUserResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'avatarColor',
|
||||
'email',
|
||||
'id',
|
||||
'metadata',
|
||||
'name',
|
||||
'profileChangedAt',
|
||||
'profileImagePath',
|
||||
};
|
||||
}
|
||||
|
||||
42
mobile/openapi/lib/model/permission.dart
generated
42
mobile/openapi/lib/model/permission.dart
generated
@@ -54,6 +54,10 @@ class Permission {
|
||||
static const albumUserPeriodCreate = Permission._(r'albumUser.create');
|
||||
static const albumUserPeriodUpdate = Permission._(r'albumUser.update');
|
||||
static const albumUserPeriodDelete = Permission._(r'albumUser.delete');
|
||||
static const albumGroupPeriodCreate = Permission._(r'albumGroup.create');
|
||||
static const albumGroupPeriodRead = Permission._(r'albumGroup.read');
|
||||
static const albumGroupPeriodUpdate = Permission._(r'albumGroup.update');
|
||||
static const albumGroupPeriodDelete = Permission._(r'albumGroup.delete');
|
||||
static const authPeriodChangePassword = Permission._(r'auth.changePassword');
|
||||
static const authDevicePeriodDelete = Permission._(r'authDevice.delete');
|
||||
static const archivePeriodRead = Permission._(r'archive.read');
|
||||
@@ -63,6 +67,8 @@ class Permission {
|
||||
static const facePeriodRead = Permission._(r'face.read');
|
||||
static const facePeriodUpdate = Permission._(r'face.update');
|
||||
static const facePeriodDelete = Permission._(r'face.delete');
|
||||
static const groupPeriodRead = Permission._(r'group.read');
|
||||
static const groupPeriodDelete = Permission._(r'group.delete');
|
||||
static const jobPeriodCreate = Permission._(r'job.create');
|
||||
static const jobPeriodRead = Permission._(r'job.read');
|
||||
static const libraryPeriodCreate = Permission._(r'library.create');
|
||||
@@ -145,6 +151,14 @@ class Permission {
|
||||
static const userProfileImagePeriodRead = Permission._(r'userProfileImage.read');
|
||||
static const userProfileImagePeriodUpdate = Permission._(r'userProfileImage.update');
|
||||
static const userProfileImagePeriodDelete = Permission._(r'userProfileImage.delete');
|
||||
static const adminGroupPeriodCreate = Permission._(r'adminGroup.create');
|
||||
static const adminGroupPeriodRead = Permission._(r'adminGroup.read');
|
||||
static const adminGroupPeriodUpdate = Permission._(r'adminGroup.update');
|
||||
static const adminGroupPeriodDelete = Permission._(r'adminGroup.delete');
|
||||
static const adminGroupUserPeriodCreate = Permission._(r'adminGroupUser.create');
|
||||
static const adminGroupUserPeriodRead = Permission._(r'adminGroupUser.read');
|
||||
static const adminGroupUserPeriodUpdate = Permission._(r'adminGroupUser.update');
|
||||
static const adminGroupUserPeriodDelete = Permission._(r'adminGroupUser.delete');
|
||||
static const adminUserPeriodCreate = Permission._(r'adminUser.create');
|
||||
static const adminUserPeriodRead = Permission._(r'adminUser.read');
|
||||
static const adminUserPeriodUpdate = Permission._(r'adminUser.update');
|
||||
@@ -183,6 +197,10 @@ class Permission {
|
||||
albumUserPeriodCreate,
|
||||
albumUserPeriodUpdate,
|
||||
albumUserPeriodDelete,
|
||||
albumGroupPeriodCreate,
|
||||
albumGroupPeriodRead,
|
||||
albumGroupPeriodUpdate,
|
||||
albumGroupPeriodDelete,
|
||||
authPeriodChangePassword,
|
||||
authDevicePeriodDelete,
|
||||
archivePeriodRead,
|
||||
@@ -192,6 +210,8 @@ class Permission {
|
||||
facePeriodRead,
|
||||
facePeriodUpdate,
|
||||
facePeriodDelete,
|
||||
groupPeriodRead,
|
||||
groupPeriodDelete,
|
||||
jobPeriodCreate,
|
||||
jobPeriodRead,
|
||||
libraryPeriodCreate,
|
||||
@@ -274,6 +294,14 @@ class Permission {
|
||||
userProfileImagePeriodRead,
|
||||
userProfileImagePeriodUpdate,
|
||||
userProfileImagePeriodDelete,
|
||||
adminGroupPeriodCreate,
|
||||
adminGroupPeriodRead,
|
||||
adminGroupPeriodUpdate,
|
||||
adminGroupPeriodDelete,
|
||||
adminGroupUserPeriodCreate,
|
||||
adminGroupUserPeriodRead,
|
||||
adminGroupUserPeriodUpdate,
|
||||
adminGroupUserPeriodDelete,
|
||||
adminUserPeriodCreate,
|
||||
adminUserPeriodRead,
|
||||
adminUserPeriodUpdate,
|
||||
@@ -347,6 +375,10 @@ class PermissionTypeTransformer {
|
||||
case r'albumUser.create': return Permission.albumUserPeriodCreate;
|
||||
case r'albumUser.update': return Permission.albumUserPeriodUpdate;
|
||||
case r'albumUser.delete': return Permission.albumUserPeriodDelete;
|
||||
case r'albumGroup.create': return Permission.albumGroupPeriodCreate;
|
||||
case r'albumGroup.read': return Permission.albumGroupPeriodRead;
|
||||
case r'albumGroup.update': return Permission.albumGroupPeriodUpdate;
|
||||
case r'albumGroup.delete': return Permission.albumGroupPeriodDelete;
|
||||
case r'auth.changePassword': return Permission.authPeriodChangePassword;
|
||||
case r'authDevice.delete': return Permission.authDevicePeriodDelete;
|
||||
case r'archive.read': return Permission.archivePeriodRead;
|
||||
@@ -356,6 +388,8 @@ class PermissionTypeTransformer {
|
||||
case r'face.read': return Permission.facePeriodRead;
|
||||
case r'face.update': return Permission.facePeriodUpdate;
|
||||
case r'face.delete': return Permission.facePeriodDelete;
|
||||
case r'group.read': return Permission.groupPeriodRead;
|
||||
case r'group.delete': return Permission.groupPeriodDelete;
|
||||
case r'job.create': return Permission.jobPeriodCreate;
|
||||
case r'job.read': return Permission.jobPeriodRead;
|
||||
case r'library.create': return Permission.libraryPeriodCreate;
|
||||
@@ -438,6 +472,14 @@ class PermissionTypeTransformer {
|
||||
case r'userProfileImage.read': return Permission.userProfileImagePeriodRead;
|
||||
case r'userProfileImage.update': return Permission.userProfileImagePeriodUpdate;
|
||||
case r'userProfileImage.delete': return Permission.userProfileImagePeriodDelete;
|
||||
case r'adminGroup.create': return Permission.adminGroupPeriodCreate;
|
||||
case r'adminGroup.read': return Permission.adminGroupPeriodRead;
|
||||
case r'adminGroup.update': return Permission.adminGroupPeriodUpdate;
|
||||
case r'adminGroup.delete': return Permission.adminGroupPeriodDelete;
|
||||
case r'adminGroupUser.create': return Permission.adminGroupUserPeriodCreate;
|
||||
case r'adminGroupUser.read': return Permission.adminGroupUserPeriodRead;
|
||||
case r'adminGroupUser.update': return Permission.adminGroupUserPeriodUpdate;
|
||||
case r'adminGroupUser.delete': return Permission.adminGroupUserPeriodDelete;
|
||||
case r'adminUser.create': return Permission.adminUserPeriodCreate;
|
||||
case r'adminUser.read': return Permission.adminUserPeriodRead;
|
||||
case r'adminUser.update': return Permission.adminUserPeriodUpdate;
|
||||
|
||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: 'none'
|
||||
version: 1.137.2+3002
|
||||
version: 1.136.0+3000
|
||||
|
||||
environment:
|
||||
sdk: '>=3.8.0 <4.0.0'
|
||||
|
||||
@@ -40,7 +40,6 @@ void main() {
|
||||
registerFallbackValue(LocalAssetStub.image1);
|
||||
|
||||
when(() => mockAssetRepo.updateHashes(any())).thenAnswer((_) async => {});
|
||||
when(() => mockStorageRepo.clearCache()).thenAnswer((_) async => {});
|
||||
});
|
||||
|
||||
group('HashService hashAssets', () {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
4
open-api/typescript-sdk/package-lock.json
generated
4
open-api/typescript-sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||
"type": "module",
|
||||
"main": "./build/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Immich
|
||||
* 1.137.2
|
||||
* 1.136.0
|
||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||
* See https://www.npmjs.com/package/oazapfts
|
||||
*/
|
||||
@@ -40,6 +40,44 @@ export type ActivityStatisticsResponseDto = {
|
||||
comments: number;
|
||||
likes: number;
|
||||
};
|
||||
export type GroupAdminResponseDto = {
|
||||
createdAt: string;
|
||||
description: string | null;
|
||||
id: string;
|
||||
name: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
export type GroupUserDto = {
|
||||
userId: string;
|
||||
};
|
||||
export type GroupAdminCreateDto = {
|
||||
description?: string | null;
|
||||
name: string;
|
||||
users?: GroupUserDto[];
|
||||
};
|
||||
export type GroupAdminUpdateDto = {
|
||||
description?: string | null;
|
||||
name?: string;
|
||||
};
|
||||
export type GroupUserDeleteAllDto = {
|
||||
userIds: string[];
|
||||
};
|
||||
export type GroupUserMetadata = {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
export type GroupUserResponseDto = {
|
||||
avatarColor: UserAvatarColor;
|
||||
email: string;
|
||||
id: string;
|
||||
metadata: GroupUserMetadata;
|
||||
name: string;
|
||||
profileChangedAt: string;
|
||||
profileImagePath: string;
|
||||
};
|
||||
export type GroupUserCreateAllDto = {
|
||||
users: GroupUserDto[];
|
||||
};
|
||||
export type NotificationCreateDto = {
|
||||
data?: object;
|
||||
description?: string | null;
|
||||
@@ -378,11 +416,16 @@ export type AlbumUserCreateDto = {
|
||||
role: AlbumUserRole;
|
||||
userId: string;
|
||||
};
|
||||
export type AlbumGroupDto = {
|
||||
groupId: string;
|
||||
role?: AlbumUserRole;
|
||||
};
|
||||
export type CreateAlbumDto = {
|
||||
albumName: string;
|
||||
albumUsers?: AlbumUserCreateDto[];
|
||||
assetIds?: string[];
|
||||
description?: string;
|
||||
groups?: AlbumGroupDto[];
|
||||
};
|
||||
export type AlbumStatisticsResponseDto = {
|
||||
notShared: number;
|
||||
@@ -404,6 +447,25 @@ export type BulkIdResponseDto = {
|
||||
id: string;
|
||||
success: boolean;
|
||||
};
|
||||
export type AlbumGroupDeleteAllDto = {
|
||||
groupIds: string[];
|
||||
};
|
||||
export type AlbumGroupMetadata = {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
export type AlbumGroupResponseDto = {
|
||||
description: string | null;
|
||||
id: string;
|
||||
metadata: AlbumGroupMetadata;
|
||||
name: string;
|
||||
};
|
||||
export type AlbumGroupCreateAllDto = {
|
||||
groups: AlbumGroupDto[];
|
||||
};
|
||||
export type AlbumGroupUpdateDto = {
|
||||
role: AlbumUserRole;
|
||||
};
|
||||
export type UpdateAlbumUserDto = {
|
||||
role: AlbumUserRole;
|
||||
};
|
||||
@@ -627,6 +689,11 @@ export type AssetFaceDeleteDto = {
|
||||
export type FaceDto = {
|
||||
id: string;
|
||||
};
|
||||
export type GroupResponseDto = {
|
||||
description: string | null;
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
export type JobCountsDto = {
|
||||
active: number;
|
||||
completed: number;
|
||||
@@ -1644,6 +1711,132 @@ export function deleteActivity({ id }: {
|
||||
method: "DELETE"
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint is an admin-only route, and requires the `adminGroup.read` permission.
|
||||
*/
|
||||
export function searchGroupsAdmin({ id, userId }: {
|
||||
id?: string;
|
||||
userId?: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: GroupAdminResponseDto[];
|
||||
}>(`/admin/groups${QS.query(QS.explode({
|
||||
id,
|
||||
userId
|
||||
}))}`, {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint is an admin-only route, and requires the `adminGroup.create` permission.
|
||||
*/
|
||||
export function createGroupAdmin({ groupAdminCreateDto }: {
|
||||
groupAdminCreateDto: GroupAdminCreateDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 201;
|
||||
data: GroupAdminResponseDto;
|
||||
}>("/admin/groups", oazapfts.json({
|
||||
...opts,
|
||||
method: "POST",
|
||||
body: groupAdminCreateDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* This endpoint is an admin-only route, and requires the `adminGroup.delete` permission.
|
||||
*/
|
||||
export function deleteGroupAdmin({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText(`/admin/groups/${encodeURIComponent(id)}`, {
|
||||
...opts,
|
||||
method: "DELETE"
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint is an admin-only route, and requires the `adminGroup.read` permission.
|
||||
*/
|
||||
export function getGroupAdmin({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: GroupAdminResponseDto;
|
||||
}>(`/admin/groups/${encodeURIComponent(id)}`, {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint is an admin-only route, and requires the `adminGroup.update` permission.
|
||||
*/
|
||||
export function updateGroupAdmin({ id, groupAdminUpdateDto }: {
|
||||
id: string;
|
||||
groupAdminUpdateDto: GroupAdminUpdateDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: GroupAdminResponseDto;
|
||||
}>(`/admin/groups/${encodeURIComponent(id)}`, oazapfts.json({
|
||||
...opts,
|
||||
method: "PUT",
|
||||
body: groupAdminUpdateDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* This endpoint requires the `adminGroupUser.delete` permission.
|
||||
*/
|
||||
export function removeUsersFromGroupAdmin({ id, groupUserDeleteAllDto }: {
|
||||
id: string;
|
||||
groupUserDeleteAllDto: GroupUserDeleteAllDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText(`/admin/groups/${encodeURIComponent(id)}/user`, oazapfts.json({
|
||||
...opts,
|
||||
method: "DELETE",
|
||||
body: groupUserDeleteAllDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* This endpoint requires the `adminGroupUser.delete` permission.
|
||||
*/
|
||||
export function removeUserFromGroupAdmin({ id, userId }: {
|
||||
id: string;
|
||||
userId: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText(`/admin/groups/${encodeURIComponent(id)}/user/${encodeURIComponent(userId)}`, {
|
||||
...opts,
|
||||
method: "DELETE"
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint is an admin-only route, and requires the `adminGroupUser.read` permission.
|
||||
*/
|
||||
export function getUsersForGroupAdmin({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: GroupUserResponseDto[];
|
||||
}>(`/admin/groups/${encodeURIComponent(id)}/users`, {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint is an admin-only route, and requires the `adminGroupUser.create` permission.
|
||||
*/
|
||||
export function addUsersToGroupAdmin({ id, groupUserCreateAllDto }: {
|
||||
id: string;
|
||||
groupUserCreateAllDto: GroupUserCreateAllDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: GroupUserResponseDto[];
|
||||
}>(`/admin/groups/${encodeURIComponent(id)}/users`, oazapfts.json({
|
||||
...opts,
|
||||
method: "PUT",
|
||||
body: groupUserCreateAllDto
|
||||
})));
|
||||
}
|
||||
export function createNotification({ notificationCreateDto }: {
|
||||
notificationCreateDto: NotificationCreateDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
@@ -1948,6 +2141,65 @@ export function addAssetsToAlbum({ id, key, slug, bulkIdsDto }: {
|
||||
body: bulkIdsDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* This endpoint requires the `albumGroup.delete` permission.
|
||||
*/
|
||||
export function removeGroupsFromAlbum({ id, albumGroupDeleteAllDto }: {
|
||||
id: string;
|
||||
albumGroupDeleteAllDto: AlbumGroupDeleteAllDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText(`/albums/${encodeURIComponent(id)}/groups`, oazapfts.json({
|
||||
...opts,
|
||||
method: "DELETE",
|
||||
body: albumGroupDeleteAllDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* This endpoint requires the `albumGroup.read` permission.
|
||||
*/
|
||||
export function getGroupsForAlbum({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: AlbumGroupResponseDto[];
|
||||
}>(`/albums/${encodeURIComponent(id)}/groups`, {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint requires the `albumGroup.create` permission.
|
||||
*/
|
||||
export function addGroupsToAlbum({ id, albumGroupCreateAllDto }: {
|
||||
id: string;
|
||||
albumGroupCreateAllDto: AlbumGroupCreateAllDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: AlbumGroupResponseDto[];
|
||||
}>(`/albums/${encodeURIComponent(id)}/groups`, oazapfts.json({
|
||||
...opts,
|
||||
method: "PUT",
|
||||
body: albumGroupCreateAllDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* This endpoint requires the `albumGroup.update` permission.
|
||||
*/
|
||||
export function updateAlbumGroup({ groupId, id, albumGroupUpdateDto }: {
|
||||
groupId: string;
|
||||
id: string;
|
||||
albumGroupUpdateDto: AlbumGroupUpdateDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: AlbumGroupResponseDto;
|
||||
}>(`/albums/${encodeURIComponent(id)}/groups/${encodeURIComponent(groupId)}`, oazapfts.json({
|
||||
...opts,
|
||||
method: "PUT",
|
||||
body: albumGroupUpdateDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* This endpoint requires the `albumUser.delete` permission.
|
||||
*/
|
||||
@@ -2547,6 +2799,54 @@ export function reassignFacesById({ id, faceDto }: {
|
||||
body: faceDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* This endpoint requires the `group.read` permission.
|
||||
*/
|
||||
export function searchMyGroups(opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: GroupResponseDto[];
|
||||
}>("/groups", {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint requires the `group.delete` permission.
|
||||
*/
|
||||
export function leaveMyGroup({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText(`/groups/${encodeURIComponent(id)}`, {
|
||||
...opts,
|
||||
method: "DELETE"
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint requires the `group.read` permission.
|
||||
*/
|
||||
export function getMyGroup({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: GroupResponseDto;
|
||||
}>(`/groups/${encodeURIComponent(id)}`, {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint is an admin-only route, and requires the `group.read` permission.
|
||||
*/
|
||||
export function getMyGroupUsers({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: GroupUserResponseDto[];
|
||||
}>(`/groups/${encodeURIComponent(id)}/users`, {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* This endpoint is an admin-only route, and requires the `job.read` permission.
|
||||
*/
|
||||
@@ -4569,6 +4869,10 @@ export enum Permission {
|
||||
AlbumUserCreate = "albumUser.create",
|
||||
AlbumUserUpdate = "albumUser.update",
|
||||
AlbumUserDelete = "albumUser.delete",
|
||||
AlbumGroupCreate = "albumGroup.create",
|
||||
AlbumGroupRead = "albumGroup.read",
|
||||
AlbumGroupUpdate = "albumGroup.update",
|
||||
AlbumGroupDelete = "albumGroup.delete",
|
||||
AuthChangePassword = "auth.changePassword",
|
||||
AuthDeviceDelete = "authDevice.delete",
|
||||
ArchiveRead = "archive.read",
|
||||
@@ -4578,6 +4882,8 @@ export enum Permission {
|
||||
FaceRead = "face.read",
|
||||
FaceUpdate = "face.update",
|
||||
FaceDelete = "face.delete",
|
||||
GroupRead = "group.read",
|
||||
GroupDelete = "group.delete",
|
||||
JobCreate = "job.create",
|
||||
JobRead = "job.read",
|
||||
LibraryCreate = "library.create",
|
||||
@@ -4660,6 +4966,14 @@ export enum Permission {
|
||||
UserProfileImageRead = "userProfileImage.read",
|
||||
UserProfileImageUpdate = "userProfileImage.update",
|
||||
UserProfileImageDelete = "userProfileImage.delete",
|
||||
AdminGroupCreate = "adminGroup.create",
|
||||
AdminGroupRead = "adminGroup.read",
|
||||
AdminGroupUpdate = "adminGroup.update",
|
||||
AdminGroupDelete = "adminGroup.delete",
|
||||
AdminGroupUserCreate = "adminGroupUser.create",
|
||||
AdminGroupUserRead = "adminGroupUser.read",
|
||||
AdminGroupUserUpdate = "adminGroupUser.update",
|
||||
AdminGroupUserDelete = "adminGroupUser.delete",
|
||||
AdminUserCreate = "adminUser.create",
|
||||
AdminUserRead = "adminUser.read",
|
||||
AdminUserUpdate = "adminUser.update",
|
||||
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@nestjs/bullmq": "^11.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.137.2",
|
||||
"version": "1.136.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
AlbumGroupCreateAllDto,
|
||||
AlbumGroupDeleteAllDto,
|
||||
AlbumGroupResponseDto,
|
||||
AlbumGroupUpdateDto,
|
||||
} from 'src/dtos/album-group.dto';
|
||||
import {
|
||||
AddUsersDto,
|
||||
AlbumInfoDto,
|
||||
@@ -15,7 +21,7 @@ import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||
import { AlbumService } from 'src/services/album.service';
|
||||
import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation';
|
||||
import { GroupIdAndIdParamDto, ParseMeUUIDPipe, UUIDParamDto } from 'src/validation';
|
||||
|
||||
@ApiTags('Albums')
|
||||
@Controller('albums')
|
||||
@@ -86,6 +92,43 @@ export class AlbumController {
|
||||
return this.service.removeAssets(auth, id, dto);
|
||||
}
|
||||
|
||||
@Get(':id/groups')
|
||||
@Authenticated({ permission: Permission.AlbumGroupRead })
|
||||
getGroupsForAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AlbumGroupResponseDto[]> {
|
||||
return this.service.getGroups(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id/groups')
|
||||
@Authenticated({ permission: Permission.AlbumGroupCreate })
|
||||
addGroupsToAlbum(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: AlbumGroupCreateAllDto,
|
||||
): Promise<AlbumGroupResponseDto[]> {
|
||||
return this.service.upsertGroups(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id/groups')
|
||||
@Authenticated({ permission: Permission.AlbumGroupDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
removeGroupsFromAlbum(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: AlbumGroupDeleteAllDto,
|
||||
): Promise<void> {
|
||||
return this.service.removeGroups(auth, id, dto);
|
||||
}
|
||||
|
||||
@Put(':id/groups/:groupId')
|
||||
@Authenticated({ permission: Permission.AlbumGroupUpdate })
|
||||
updateAlbumGroup(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id, groupId }: GroupIdAndIdParamDto,
|
||||
@Body() dto: AlbumGroupUpdateDto,
|
||||
): Promise<AlbumGroupResponseDto> {
|
||||
return this.service.updateGroup(auth, id, groupId, dto);
|
||||
}
|
||||
|
||||
@Put(':id/users')
|
||||
@Authenticated({ permission: Permission.AlbumUserCreate })
|
||||
addUsersToAlbum(
|
||||
|
||||
85
server/src/controllers/group-admin.controller.ts
Normal file
85
server/src/controllers/group-admin.controller.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { GroupUserCreateAllDto, GroupUserDeleteAllDto, GroupUserResponseDto } from 'src/dtos/group-user.dto';
|
||||
import {
|
||||
GroupAdminCreateDto,
|
||||
GroupAdminResponseDto,
|
||||
GroupAdminSearchDto,
|
||||
GroupAdminUpdateDto,
|
||||
} from 'src/dtos/group.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||
import { GroupAdminService } from 'src/services/group-admin.service';
|
||||
import { UserIdAndIdParamDto, UUIDParamDto } from 'src/validation';
|
||||
|
||||
@ApiTags('Groups (admin)')
|
||||
@Controller('admin/groups')
|
||||
export class GroupAdminController {
|
||||
constructor(private service: GroupAdminService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.AdminGroupRead, admin: true })
|
||||
searchGroupsAdmin(@Auth() auth: AuthDto, @Query() dto: GroupAdminSearchDto): Promise<GroupAdminResponseDto[]> {
|
||||
return this.service.search(auth, dto);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.AdminGroupCreate, admin: true })
|
||||
createGroupAdmin(@Auth() auth: AuthDto, @Body() dto: GroupAdminCreateDto): Promise<GroupAdminResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.AdminGroupRead, admin: true })
|
||||
getGroupAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<GroupAdminResponseDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.AdminGroupUpdate, admin: true })
|
||||
updateGroupAdmin(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: GroupAdminUpdateDto,
|
||||
): Promise<GroupAdminResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.AdminGroupDelete, admin: true })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
deleteGroupAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@Get(':id/users')
|
||||
@Authenticated({ permission: Permission.AdminGroupUserRead, admin: true })
|
||||
getUsersForGroupAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<GroupUserResponseDto[]> {
|
||||
return this.service.getUsers(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id/users')
|
||||
@Authenticated({ permission: Permission.AdminGroupUserCreate, admin: true })
|
||||
addUsersToGroupAdmin(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: GroupUserCreateAllDto,
|
||||
): Promise<GroupUserResponseDto[]> {
|
||||
return this.service.addUsers(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id/user')
|
||||
@Authenticated({ permission: Permission.AdminGroupUserDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
removeUsersFromGroupAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: GroupUserDeleteAllDto) {
|
||||
return this.service.removeUsers(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id/user/:userId')
|
||||
@Authenticated({ permission: Permission.AdminGroupUserDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
removeUserFromGroupAdmin(@Auth() auth: AuthDto, @Param() { id, userId }: UserIdAndIdParamDto) {
|
||||
return this.service.removeUser(auth, id, userId);
|
||||
}
|
||||
}
|
||||
40
server/src/controllers/group.controller.ts
Normal file
40
server/src/controllers/group.controller.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Controller, Delete, Get, HttpCode, HttpStatus, Param } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { GroupUserResponseDto } from 'src/dtos/group-user.dto';
|
||||
import { GroupResponseDto } from 'src/dtos/group.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||
import { GroupService } from 'src/services/group.service';
|
||||
import { UUIDParamDto } from 'src/validation';
|
||||
|
||||
@ApiTags('Groups')
|
||||
@Controller('groups')
|
||||
export class GroupController {
|
||||
constructor(private service: GroupService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.GroupRead })
|
||||
searchMyGroups(@Auth() auth: AuthDto): Promise<GroupResponseDto[]> {
|
||||
return this.service.search(auth);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.GroupRead })
|
||||
getMyGroup(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<GroupResponseDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.GroupDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
leaveMyGroup(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@Get(':id/users')
|
||||
@Authenticated({ permission: Permission.GroupRead, admin: true })
|
||||
getMyGroupUsers(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<GroupUserResponseDto[]> {
|
||||
return this.service.getUsers(auth, id);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import { AuthController } from 'src/controllers/auth.controller';
|
||||
import { DownloadController } from 'src/controllers/download.controller';
|
||||
import { DuplicateController } from 'src/controllers/duplicate.controller';
|
||||
import { FaceController } from 'src/controllers/face.controller';
|
||||
import { GroupAdminController } from 'src/controllers/group-admin.controller';
|
||||
import { GroupController } from 'src/controllers/group.controller';
|
||||
import { JobController } from 'src/controllers/job.controller';
|
||||
import { LibraryController } from 'src/controllers/library.controller';
|
||||
import { MapController } from 'src/controllers/map.controller';
|
||||
@@ -43,6 +45,8 @@ export const controllers = [
|
||||
DownloadController,
|
||||
DuplicateController,
|
||||
FaceController,
|
||||
GroupController,
|
||||
GroupAdminController,
|
||||
JobController,
|
||||
LibraryController,
|
||||
MapController,
|
||||
|
||||
@@ -144,6 +144,17 @@ export type UserAdmin = User & {
|
||||
metadata: UserMetadataItem[];
|
||||
};
|
||||
|
||||
export type Group = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
};
|
||||
|
||||
export type GroupAdmin = Group & {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export type StorageAsset = {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
@@ -319,6 +330,7 @@ export const columns = {
|
||||
'shared_link.allowDownload',
|
||||
'shared_link.password',
|
||||
],
|
||||
groupAdmin: ['group.id', 'group.name', 'group.description', 'group.createdAt', 'group.updatedAt'],
|
||||
user: userColumns,
|
||||
userWithPrefix: userWithPrefixColumns,
|
||||
userAdmin: [
|
||||
|
||||
56
server/src/dtos/album-group.dto.ts
Normal file
56
server/src/dtos/album-group.dto.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, ValidateNested } from 'class-validator';
|
||||
import { Group } from 'src/database';
|
||||
import { GroupResponseDto, mapGroup } from 'src/dtos/group.dto';
|
||||
import { AlbumUserRole } from 'src/enum';
|
||||
import { ValidateEnum, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class AlbumGroupCreateAllDto {
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AlbumGroupDto)
|
||||
groups!: AlbumGroupDto[];
|
||||
}
|
||||
|
||||
export class AlbumGroupDeleteAllDto {
|
||||
@ValidateUUID({ each: true })
|
||||
groupIds!: string[];
|
||||
}
|
||||
|
||||
export class AlbumGroupDto {
|
||||
@ValidateUUID()
|
||||
groupId!: string;
|
||||
|
||||
@ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', optional: true })
|
||||
role?: AlbumUserRole;
|
||||
}
|
||||
|
||||
export class AlbumGroupUpdateDto {
|
||||
@ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole' })
|
||||
role!: AlbumUserRole;
|
||||
}
|
||||
|
||||
export class AlbumGroupResponseDto extends GroupResponseDto {
|
||||
metadata!: AlbumGroupMetadata;
|
||||
}
|
||||
|
||||
export class AlbumGroupMetadata {
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
}
|
||||
|
||||
type AlbumGroup = {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
group: Group;
|
||||
};
|
||||
|
||||
export const mapAlbumGroup = (albumGroup: AlbumGroup): AlbumGroupResponseDto => {
|
||||
return {
|
||||
...mapGroup(albumGroup.group),
|
||||
metadata: {
|
||||
createdAt: albumGroup.createdAt,
|
||||
updatedAt: albumGroup.updatedAt,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -3,9 +3,10 @@ import { Type } from 'class-transformer';
|
||||
import { ArrayNotEmpty, IsArray, IsString, ValidateNested } from 'class-validator';
|
||||
import _ from 'lodash';
|
||||
import { AlbumUser, AuthSharedLink, User } from 'src/database';
|
||||
import { AlbumGroupDto } from 'src/dtos/album-group.dto';
|
||||
import { AssetResponseDto, MapAsset, mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||
import { mapUser, UserResponseDto } from 'src/dtos/user.dto';
|
||||
import { AlbumUserRole, AssetOrder } from 'src/enum';
|
||||
import { Optional, ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation';
|
||||
|
||||
@@ -50,6 +51,12 @@ export class CreateAlbumDto {
|
||||
@Type(() => AlbumUserCreateDto)
|
||||
albumUsers?: AlbumUserCreateDto[];
|
||||
|
||||
@Optional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AlbumGroupDto)
|
||||
groups?: AlbumGroupDto[];
|
||||
|
||||
@ValidateUUID({ optional: true, each: true })
|
||||
assetIds?: string[];
|
||||
}
|
||||
|
||||
46
server/src/dtos/group-user.dto.ts
Normal file
46
server/src/dtos/group-user.dto.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ArrayNotEmpty } from 'class-validator';
|
||||
import { User } from 'src/database';
|
||||
import { mapUser, UserResponseDto } from 'src/dtos/user.dto';
|
||||
import { ValidateUUID } from 'src/validation';
|
||||
|
||||
export class GroupUserCreateAllDto {
|
||||
@ArrayNotEmpty()
|
||||
users!: GroupUserDto[];
|
||||
}
|
||||
|
||||
export class GroupUserDeleteAllDto {
|
||||
@ValidateUUID({ each: true })
|
||||
userIds!: string[];
|
||||
}
|
||||
|
||||
export class GroupUserDto {
|
||||
@ValidateUUID()
|
||||
userId!: string;
|
||||
|
||||
// TODO potentially add a role UserGroupRole field here
|
||||
}
|
||||
|
||||
export class GroupUserResponseDto extends UserResponseDto {
|
||||
metadata!: GroupUserMetadata;
|
||||
}
|
||||
|
||||
export class GroupUserMetadata {
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
}
|
||||
|
||||
type GroupUser = {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
user: User;
|
||||
};
|
||||
|
||||
export const mapGroupUser = (groupUser: GroupUser): GroupUserResponseDto => {
|
||||
return {
|
||||
...mapUser(groupUser.user),
|
||||
metadata: {
|
||||
createdAt: groupUser.createdAt,
|
||||
updatedAt: groupUser.updatedAt,
|
||||
},
|
||||
};
|
||||
};
|
||||
69
server/src/dtos/group.dto.ts
Normal file
69
server/src/dtos/group.dto.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
|
||||
import { Group, GroupAdmin } from 'src/database';
|
||||
import { GroupUserDto } from 'src/dtos/group-user.dto';
|
||||
import { Optional, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class GroupAdminSearchDto {
|
||||
@ValidateUUID({ optional: true })
|
||||
id?: string;
|
||||
|
||||
@ValidateUUID({ optional: true })
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export class GroupAdminCreateDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name!: string;
|
||||
|
||||
@Optional({ nullable: true, emptyToNull: true })
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
description?: string | null;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => GroupUserDto)
|
||||
@IsArray()
|
||||
users?: GroupUserDto[];
|
||||
}
|
||||
|
||||
export class GroupAdminUpdateDto {
|
||||
@Optional()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name?: string;
|
||||
|
||||
@Optional({ nullable: true, emptyToNull: true })
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export class GroupResponseDto {
|
||||
id!: string;
|
||||
name!: string;
|
||||
description!: string | null;
|
||||
}
|
||||
|
||||
export class GroupAdminResponseDto extends GroupResponseDto {
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
}
|
||||
|
||||
export const mapGroup = (group: Group | GroupAdmin) => {
|
||||
return {
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
description: group.description,
|
||||
};
|
||||
};
|
||||
|
||||
export const mapGroupAdmin = (group: GroupAdmin) => {
|
||||
return {
|
||||
...mapGroup(group),
|
||||
createdAt: group.createdAt,
|
||||
updatedAt: group.updatedAt,
|
||||
};
|
||||
};
|
||||
@@ -111,6 +111,11 @@ export enum Permission {
|
||||
AlbumUserUpdate = 'albumUser.update',
|
||||
AlbumUserDelete = 'albumUser.delete',
|
||||
|
||||
AlbumGroupCreate = 'albumGroup.create',
|
||||
AlbumGroupRead = 'albumGroup.read',
|
||||
AlbumGroupUpdate = 'albumGroup.update',
|
||||
AlbumGroupDelete = 'albumGroup.delete',
|
||||
|
||||
AuthChangePassword = 'auth.changePassword',
|
||||
|
||||
AuthDeviceDelete = 'authDevice.delete',
|
||||
@@ -125,6 +130,9 @@ export enum Permission {
|
||||
FaceUpdate = 'face.update',
|
||||
FaceDelete = 'face.delete',
|
||||
|
||||
GroupRead = 'group.read',
|
||||
GroupDelete = 'group.delete',
|
||||
|
||||
JobCreate = 'job.create',
|
||||
JobRead = 'job.read',
|
||||
|
||||
@@ -230,6 +238,16 @@ export enum Permission {
|
||||
UserProfileImageUpdate = 'userProfileImage.update',
|
||||
UserProfileImageDelete = 'userProfileImage.delete',
|
||||
|
||||
AdminGroupCreate = 'adminGroup.create',
|
||||
AdminGroupRead = 'adminGroup.read',
|
||||
AdminGroupUpdate = 'adminGroup.update',
|
||||
AdminGroupDelete = 'adminGroup.delete',
|
||||
|
||||
AdminGroupUserCreate = 'adminGroupUser.create',
|
||||
AdminGroupUserRead = 'adminGroupUser.read',
|
||||
AdminGroupUserUpdate = 'adminGroupUser.update',
|
||||
AdminGroupUserDelete = 'adminGroupUser.delete',
|
||||
|
||||
AdminUserCreate = 'adminUser.create',
|
||||
AdminUserRead = 'adminUser.read',
|
||||
AdminUserUpdate = 'adminUser.update',
|
||||
|
||||
87
server/src/queries/album.group.repository.sql
Normal file
87
server/src/queries/album.group.repository.sql
Normal file
@@ -0,0 +1,87 @@
|
||||
-- NOTE: This file is auto generated by ./sql-generator
|
||||
|
||||
-- AlbumGroupRepository.getAll
|
||||
select
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"group"."id",
|
||||
"group"."name",
|
||||
"group"."description"
|
||||
from
|
||||
"group"
|
||||
where
|
||||
"group"."id" = "album_group"."groupId"
|
||||
) as obj
|
||||
) as "group",
|
||||
"album_group"."createdAt",
|
||||
"album_group"."updatedAt"
|
||||
from
|
||||
"album_group"
|
||||
inner join "group" on "album_group"."groupId" = "group"."id"
|
||||
where
|
||||
"albumId" = $1
|
||||
order by
|
||||
"group"."name"
|
||||
|
||||
-- AlbumGroupRepository.createAll
|
||||
insert into
|
||||
"album_group" ("albumId", "groupId", "role")
|
||||
values
|
||||
($1, $2, $3)
|
||||
on conflict ("albumId", "groupId") do update
|
||||
set
|
||||
"role" = "excluded"."role"
|
||||
returning
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"description"
|
||||
from
|
||||
"group"
|
||||
where
|
||||
"album_group"."groupId" = "group"."id"
|
||||
) as obj
|
||||
) as "group"
|
||||
|
||||
-- AlbumGroupRepository.deleteAll
|
||||
delete from "album_group"
|
||||
where
|
||||
"albumId" = $1
|
||||
and "groupId" in ($2)
|
||||
|
||||
-- AlbumGroupRepository.update
|
||||
update "album_group"
|
||||
set
|
||||
"role" = $1
|
||||
where
|
||||
"albumId" = $2
|
||||
and "groupId" = $3
|
||||
returning
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"description"
|
||||
from
|
||||
"group"
|
||||
where
|
||||
"album_group"."groupId" = "group"."id"
|
||||
) as obj
|
||||
) as "group"
|
||||
@@ -170,10 +170,27 @@ where
|
||||
|
||||
-- AssetRepository.getFileSamples
|
||||
select
|
||||
"assetId",
|
||||
"path"
|
||||
"asset"."id",
|
||||
"asset"."originalPath",
|
||||
"asset"."sidecarPath",
|
||||
"asset"."encodedVideoPath",
|
||||
(
|
||||
select
|
||||
coalesce(json_agg(agg), '[]')
|
||||
from
|
||||
(
|
||||
select
|
||||
"path"
|
||||
from
|
||||
"asset_file"
|
||||
where
|
||||
"asset"."id" = "asset_file"."assetId"
|
||||
) as agg
|
||||
) as "files"
|
||||
from
|
||||
"asset_file"
|
||||
"asset"
|
||||
where
|
||||
"asset"."libraryId" is null
|
||||
limit
|
||||
3
|
||||
|
||||
|
||||
11
server/src/queries/group.repository.sql
Normal file
11
server/src/queries/group.repository.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- NOTE: This file is auto generated by ./sql-generator
|
||||
|
||||
-- GroupRepository.search
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"createdAt",
|
||||
"updatedAt"
|
||||
from
|
||||
"group"
|
||||
65
server/src/queries/group.user.repository.sql
Normal file
65
server/src/queries/group.user.repository.sql
Normal file
@@ -0,0 +1,65 @@
|
||||
-- NOTE: This file is auto generated by ./sql-generator
|
||||
|
||||
-- GroupUserRepository.getAll
|
||||
select
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"user2"."id",
|
||||
"user2"."name",
|
||||
"user2"."email",
|
||||
"user2"."avatarColor",
|
||||
"user2"."profileImagePath",
|
||||
"user2"."profileChangedAt"
|
||||
from
|
||||
"user" as "user2"
|
||||
where
|
||||
"user2"."id" = "group_user"."userId"
|
||||
) as obj
|
||||
) as "user",
|
||||
"group_user"."createdAt",
|
||||
"group_user"."updatedAt"
|
||||
from
|
||||
"group_user"
|
||||
inner join "user" on "group_user"."userId" = "user"."id"
|
||||
where
|
||||
"groupId" = $1
|
||||
order by
|
||||
"user"."name"
|
||||
|
||||
-- GroupUserRepository.createAll
|
||||
insert into
|
||||
"group_user" ("userId", "groupId")
|
||||
values
|
||||
($1, $2)
|
||||
on conflict do nothing
|
||||
returning
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"user2"."id",
|
||||
"user2"."name",
|
||||
"user2"."email",
|
||||
"user2"."avatarColor",
|
||||
"user2"."profileImagePath",
|
||||
"user2"."profileChangedAt"
|
||||
from
|
||||
"user" as "user2"
|
||||
where
|
||||
"group_user"."userId" = "user2"."id"
|
||||
) as obj
|
||||
) as "user"
|
||||
|
||||
-- GroupUserRepository.deleteAll
|
||||
delete from "group_user"
|
||||
where
|
||||
"groupId" = $1
|
||||
and "userId" in ($2)
|
||||
98
server/src/repositories/album-group.repository.ts
Normal file
98
server/src/repositories/album-group.repository.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Kysely, NotNull, Updateable } from 'kysely';
|
||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { Chunked, DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { AlbumGroupDto } from 'src/dtos/album-group.dto';
|
||||
import { AlbumUserRole } from 'src/enum';
|
||||
import { DB } from 'src/schema';
|
||||
import { AlbumGroupTable } from 'src/schema/tables/album-group.table';
|
||||
|
||||
type AlbumGroup = { albumId: string; groupId: string };
|
||||
|
||||
@Injectable()
|
||||
export class AlbumGroupRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getAll(albumId: string) {
|
||||
return this.db
|
||||
.selectFrom('album_group')
|
||||
.where('albumId', '=', albumId)
|
||||
.innerJoin('group', 'album_group.groupId', 'group.id')
|
||||
.orderBy('group.name')
|
||||
.select((eb) =>
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom('group')
|
||||
.select(['group.id', 'group.name', 'group.description'])
|
||||
.whereRef('group.id', '=', 'album_group.groupId'),
|
||||
).as('group'),
|
||||
)
|
||||
.$narrowType<{ group: NotNull }>()
|
||||
.select(['album_group.createdAt', 'album_group.updatedAt'])
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, [{ groupId: DummyValue.UUID, role: DummyValue.STRING }]] })
|
||||
@Chunked({ paramIndex: 1 })
|
||||
createAll(albumId: string, groups: AlbumGroupDto[]) {
|
||||
return this.db
|
||||
.insertInto('album_group')
|
||||
.values(groups.map(({ groupId, role }) => ({ albumId, groupId, role: role ?? AlbumUserRole.Editor })))
|
||||
.onConflict((oc) =>
|
||||
oc.columns(['albumId', 'groupId']).doUpdateSet((eb) => ({
|
||||
role: eb.ref('excluded.role'),
|
||||
})),
|
||||
)
|
||||
.returning(['createdAt', 'updatedAt'])
|
||||
.returning((eb) =>
|
||||
jsonObjectFrom(
|
||||
eb.selectFrom('group').whereRef('album_group.groupId', '=', 'group.id').select(['id', 'name', 'description']),
|
||||
).as('group'),
|
||||
)
|
||||
.$narrowType<{ group: NotNull }>()
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||
@Chunked({ paramIndex: 1 })
|
||||
deleteAll(albumId: string, groupIds: string[]) {
|
||||
if (groupIds.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.db.deleteFrom('album_group').where('albumId', '=', albumId).where('groupId', 'in', groupIds).execute();
|
||||
}
|
||||
|
||||
async exists({ albumId, groupId }: AlbumGroup) {
|
||||
const albumGroup = await this.db
|
||||
.selectFrom('album_group')
|
||||
.select(['albumId'])
|
||||
.where('albumId', '=', albumId)
|
||||
.where('groupId', '=', groupId)
|
||||
.execute();
|
||||
|
||||
return !!albumGroup;
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ albumId: DummyValue.UUID, groupId: DummyValue.UUID }, { role: DummyValue.STRING }] })
|
||||
update({ albumId, groupId }: AlbumGroup, dto: Updateable<AlbumGroupTable>) {
|
||||
return this.db
|
||||
.updateTable('album_group')
|
||||
.set(dto)
|
||||
.where('albumId', '=', albumId)
|
||||
.where('groupId', '=', groupId)
|
||||
.returning(['createdAt', 'updatedAt'])
|
||||
.returning((eb) =>
|
||||
jsonObjectFrom(
|
||||
eb.selectFrom('group').whereRef('album_group.groupId', '=', 'group.id').select(['id', 'name', 'description']),
|
||||
).as('group'),
|
||||
)
|
||||
.$narrowType<{ group: NotNull }>()
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
delete({ albumId, groupId }: AlbumGroup) {
|
||||
return this.db.deleteFrom('album_group').where('albumId', '=', albumId).where('groupId', '=', groupId).execute();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Insertable, Kysely, NotNull, Selectable, UpdateResult, Updateable, sql } from 'kysely';
|
||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||
import { isEmpty, isUndefined, omitBy } from 'lodash';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { Stack } from 'src/database';
|
||||
@@ -337,7 +338,20 @@ export class AssetRepository {
|
||||
|
||||
@GenerateSql()
|
||||
getFileSamples() {
|
||||
return this.db.selectFrom('asset_file').select(['assetId', 'path']).limit(sql.lit(3)).execute();
|
||||
return this.db
|
||||
.selectFrom('asset')
|
||||
.select((eb) => [
|
||||
'asset.id',
|
||||
'asset.originalPath',
|
||||
'asset.sidecarPath',
|
||||
'asset.encodedVideoPath',
|
||||
jsonArrayFrom(eb.selectFrom('asset_file').select('path').whereRef('asset.id', '=', 'asset_file.assetId')).as(
|
||||
'files',
|
||||
),
|
||||
])
|
||||
.where('asset.libraryId', 'is', null)
|
||||
.limit(sql.lit(3))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
|
||||
71
server/src/repositories/group-user.repository.ts
Normal file
71
server/src/repositories/group-user.repository.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Kysely, NotNull } from 'kysely';
|
||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { columns } from 'src/database';
|
||||
import { Chunked, DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { DB } from 'src/schema';
|
||||
|
||||
type GroupUser = { groupId: string; userId: string };
|
||||
|
||||
@Injectable()
|
||||
export class GroupUserRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getAll(groupId: string) {
|
||||
return this.db
|
||||
.selectFrom('group_user')
|
||||
.where('groupId', '=', groupId)
|
||||
.innerJoin('user', 'group_user.userId', 'user.id')
|
||||
.orderBy('user.name', 'asc')
|
||||
.select((eb) =>
|
||||
jsonObjectFrom(
|
||||
eb.selectFrom('user as user2').select(columns.userWithPrefix).whereRef('user2.id', '=', 'group_user.userId'),
|
||||
).as('user'),
|
||||
)
|
||||
.$narrowType<{ user: NotNull }>()
|
||||
.select(['group_user.createdAt', 'group_user.updatedAt'])
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||
@Chunked({ paramIndex: 1 })
|
||||
createAll(groupId: string, userIds: string[]) {
|
||||
return this.db
|
||||
.insertInto('group_user')
|
||||
.values(userIds.map((userId) => ({ userId, groupId })))
|
||||
.onConflict((oc) => oc.doNothing())
|
||||
.returning(['createdAt', 'updatedAt'])
|
||||
.returning((eb) =>
|
||||
jsonObjectFrom(
|
||||
eb.selectFrom('user as user2').whereRef('group_user.userId', '=', 'user2.id').select(columns.userWithPrefix),
|
||||
).as('user'),
|
||||
)
|
||||
.$narrowType<{ user: NotNull }>()
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||
@Chunked({ paramIndex: 1 })
|
||||
deleteAll(groupId: string, usersIds: string[]) {
|
||||
if (usersIds.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.db.deleteFrom('group_user').where('groupId', '=', groupId).where('userId', 'in', usersIds).execute();
|
||||
}
|
||||
|
||||
get({ groupId, userId }: GroupUser) {
|
||||
return this.db
|
||||
.selectFrom('group_user')
|
||||
.innerJoin('group', 'group.id', 'group_user.groupId')
|
||||
.select(['group.id', 'group.name', 'group.description'])
|
||||
.where('group_user.groupId', '=', groupId)
|
||||
.where('group_user.userId', '=', userId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
delete({ userId, groupId }: GroupUser) {
|
||||
return this.db.deleteFrom('group_user').where('userId', '=', userId).where('groupId', '=', groupId).execute();
|
||||
}
|
||||
}
|
||||
65
server/src/repositories/group.repository.ts
Normal file
65
server/src/repositories/group.repository.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Insertable, Kysely, Updateable } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { columns } from 'src/database';
|
||||
import { GenerateSql } from 'src/decorators';
|
||||
import { GroupUserDto } from 'src/dtos/group-user.dto';
|
||||
import { DB } from 'src/schema';
|
||||
import { GroupTable } from 'src/schema/tables/group.table';
|
||||
|
||||
@Injectable()
|
||||
export class GroupRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql()
|
||||
search(options: { id?: string; userId?: string } = {}) {
|
||||
const { id, userId } = options;
|
||||
return this.db
|
||||
.selectFrom('group')
|
||||
.select(['group.id', 'group.name', 'group.description', 'group.createdAt', 'group.updatedAt'])
|
||||
.$if(!!id, (eb) => eb.where('group.id', '=', id!))
|
||||
.$if(!!userId, (eb) =>
|
||||
eb.innerJoin('group_user', 'group_user.groupId', 'group.id').where('group_user.userId', '=', userId!),
|
||||
)
|
||||
.orderBy('group.name', 'asc')
|
||||
.execute();
|
||||
}
|
||||
|
||||
create(group: Insertable<GroupTable>, users?: GroupUserDto[]) {
|
||||
return this.db.transaction().execute(async (tx) => {
|
||||
const newGroup = await tx
|
||||
.insertInto('group')
|
||||
.values(group)
|
||||
.returning(columns.groupAdmin)
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
const groupId = newGroup.id;
|
||||
|
||||
if (users && users.length > 0) {
|
||||
await tx
|
||||
.insertInto('group_user')
|
||||
.values(users.map(({ userId }) => ({ groupId, userId })))
|
||||
.execute();
|
||||
}
|
||||
|
||||
return newGroup;
|
||||
});
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
return this.db.selectFrom('group').select(columns.groupAdmin).where('id', '=', id).executeTakeFirst();
|
||||
}
|
||||
|
||||
update(id: string, group: Updateable<GroupTable>) {
|
||||
return this.db
|
||||
.updateTable('group')
|
||||
.set(group)
|
||||
.where('id', '=', id)
|
||||
.returning(columns.groupAdmin)
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.db.deleteFrom('group').where('id', '=', id).executeTakeFirstOrThrow();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { AlbumGroupRepository } from 'src/repositories/album-group.repository';
|
||||
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
||||
@@ -14,6 +15,8 @@ import { DownloadRepository } from 'src/repositories/download.repository';
|
||||
import { DuplicateRepository } from 'src/repositories/duplicate.repository';
|
||||
import { EmailRepository } from 'src/repositories/email.repository';
|
||||
import { EventRepository } from 'src/repositories/event.repository';
|
||||
import { GroupUserRepository } from 'src/repositories/group-user.repository';
|
||||
import { GroupRepository } from 'src/repositories/group.repository';
|
||||
import { JobRepository } from 'src/repositories/job.repository';
|
||||
import { LibraryRepository } from 'src/repositories/library.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
@@ -48,6 +51,7 @@ export const repositories = [
|
||||
AccessRepository,
|
||||
ActivityRepository,
|
||||
AlbumRepository,
|
||||
AlbumGroupRepository,
|
||||
AlbumUserRepository,
|
||||
AuditRepository,
|
||||
ApiKeyRepository,
|
||||
@@ -61,6 +65,8 @@ export const repositories = [
|
||||
DuplicateRepository,
|
||||
EmailRepository,
|
||||
EventRepository,
|
||||
GroupRepository,
|
||||
GroupUserRepository,
|
||||
JobRepository,
|
||||
LibraryRepository,
|
||||
LoggingRepository,
|
||||
|
||||
@@ -165,6 +165,46 @@ export const album_user_delete_audit = registerFunction({
|
||||
END`,
|
||||
});
|
||||
|
||||
export const album_group_delete_audit = registerFunction({
|
||||
name: 'album_group_delete_audit',
|
||||
returnType: 'TRIGGER',
|
||||
language: 'PLPGSQL',
|
||||
body: `
|
||||
BEGIN
|
||||
INSERT INTO "album_audit" ("albumId", "userId")
|
||||
SELECT OLD."albumId", "group_user"."userId"
|
||||
FROM OLD INNER JOIN "group_user" ON "group_user"."groupId" = OLD."groupId";
|
||||
|
||||
IF pg_trigger_depth() = 1 THEN
|
||||
INSERT INTO album_group_audit ("albumId", "groupId")
|
||||
SELECT "albumId", "groupId"
|
||||
FROM OLD;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END`,
|
||||
});
|
||||
|
||||
export const group_user_delete_audit = registerFunction({
|
||||
name: 'group_user_delete_audit',
|
||||
returnType: 'TRIGGER',
|
||||
language: 'PLPGSQL',
|
||||
body: `
|
||||
BEGIN
|
||||
INSERT INTO group_audit ("groupId", "userId")
|
||||
SELECT "groupId", "userId"
|
||||
FROM OLD;
|
||||
|
||||
IF pg_trigger_depth() = 1 THEN
|
||||
INSERT INTO group_user_audit ("groupId", "userId")
|
||||
SELECT "groupId", "userId"
|
||||
FROM OLD;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END`,
|
||||
});
|
||||
|
||||
export const memory_delete_audit = registerFunction({
|
||||
name: 'memory_delete_audit',
|
||||
returnType: 'TRIGGER',
|
||||
|
||||
@@ -22,6 +22,8 @@ import { ActivityTable } from 'src/schema/tables/activity.table';
|
||||
import { AlbumAssetAuditTable } from 'src/schema/tables/album-asset-audit.table';
|
||||
import { AlbumAssetTable } from 'src/schema/tables/album-asset.table';
|
||||
import { AlbumAuditTable } from 'src/schema/tables/album-audit.table';
|
||||
import { AlbumGroupAuditTable } from 'src/schema/tables/album-group-audit.table';
|
||||
import { AlbumGroupTable } from 'src/schema/tables/album-group.table';
|
||||
import { AlbumUserAuditTable } from 'src/schema/tables/album-user-audit.table';
|
||||
import { AlbumUserTable } from 'src/schema/tables/album-user.table';
|
||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
@@ -36,6 +38,10 @@ import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { AuditTable } from 'src/schema/tables/audit.table';
|
||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
||||
import { GroupAuditTable } from 'src/schema/tables/group-audit.table';
|
||||
import { GroupUserAuditTable } from 'src/schema/tables/group-user-audit.table';
|
||||
import { GroupUserTable } from 'src/schema/tables/group-user.table';
|
||||
import { GroupTable } from 'src/schema/tables/group.table';
|
||||
import { LibraryTable } from 'src/schema/tables/library.table';
|
||||
import { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table';
|
||||
import { MemoryAssetTable } from 'src/schema/tables/memory-asset.table';
|
||||
@@ -71,12 +77,14 @@ import { Database, Extensions, Generated, Int8 } from 'src/sql-tools';
|
||||
export class ImmichDatabase {
|
||||
tables = [
|
||||
ActivityTable,
|
||||
AlbumAssetTable,
|
||||
AlbumAssetAuditTable,
|
||||
AlbumAuditTable,
|
||||
AlbumTable,
|
||||
AlbumGroupTable,
|
||||
AlbumGroupAuditTable,
|
||||
AlbumUserAuditTable,
|
||||
AlbumUserTable,
|
||||
AlbumTable,
|
||||
AlbumAssetTable,
|
||||
AlbumAssetAuditTable,
|
||||
ApiKeyTable,
|
||||
AssetAuditTable,
|
||||
AssetFaceTable,
|
||||
@@ -88,6 +96,10 @@ export class ImmichDatabase {
|
||||
AssetExifTable,
|
||||
FaceSearchTable,
|
||||
GeodataPlacesTable,
|
||||
GroupTable,
|
||||
GroupAuditTable,
|
||||
GroupUserTable,
|
||||
GroupUserAuditTable,
|
||||
LibraryTable,
|
||||
MemoryTable,
|
||||
MemoryAuditTable,
|
||||
@@ -154,6 +166,8 @@ export interface DB {
|
||||
album_audit: AlbumAuditTable;
|
||||
album_asset: AlbumAssetTable;
|
||||
album_asset_audit: AlbumAssetAuditTable;
|
||||
album_group: AlbumGroupTable;
|
||||
album_group_audit: AlbumGroupAuditTable;
|
||||
album_user: AlbumUserTable;
|
||||
album_user_audit: AlbumUserAuditTable;
|
||||
|
||||
@@ -173,6 +187,11 @@ export interface DB {
|
||||
|
||||
geodata_places: GeodataPlacesTable;
|
||||
|
||||
group: GroupTable;
|
||||
group_audit: GroupAuditTable;
|
||||
group_user: GroupUserTable;
|
||||
group_user_audit: GroupUserAuditTable;
|
||||
|
||||
library: LibraryTable;
|
||||
|
||||
memory: MemoryTable;
|
||||
|
||||
118
server/src/schema/migrations/1753908954798-AddUserGroups.ts
Normal file
118
server/src/schema/migrations/1753908954798-AddUserGroups.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE OR REPLACE FUNCTION group_delete_audit()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO group_audit ("groupId")
|
||||
SELECT "id"
|
||||
FROM OLD;
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;`.execute(db);
|
||||
await sql`CREATE OR REPLACE FUNCTION group_user_delete_audit()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO group_audit ("groupId", "userId")
|
||||
SELECT "groupId", "userId"
|
||||
FROM OLD;
|
||||
|
||||
IF pg_trigger_depth() = 1 THEN
|
||||
INSERT INTO group_user_audit ("groupId", "userId")
|
||||
SELECT "groupId", "userId"
|
||||
FROM OLD;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;`.execute(db);
|
||||
await sql`CREATE TABLE "group_audit" (
|
||||
"id" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"groupId" uuid NOT NULL,
|
||||
"deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(),
|
||||
CONSTRAINT "group_audit_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "group_audit_deletedAt_idx" ON "group_audit" ("deletedAt");`.execute(db);
|
||||
await sql`CREATE TABLE "group_user_audit" (
|
||||
"id" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"groupId" uuid NOT NULL,
|
||||
"userId" uuid NOT NULL,
|
||||
"deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(),
|
||||
CONSTRAINT "group_user_audit_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "group_user_audit_groupId_idx" ON "group_user_audit" ("groupId");`.execute(db);
|
||||
await sql`CREATE INDEX "group_user_audit_userId_idx" ON "group_user_audit" ("userId");`.execute(db);
|
||||
await sql`CREATE INDEX "group_user_audit_deletedAt_idx" ON "group_user_audit" ("deletedAt");`.execute(db);
|
||||
await sql`CREATE TABLE "group_user" (
|
||||
"groupId" uuid NOT NULL,
|
||||
"userId" uuid NOT NULL,
|
||||
"createId" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"createdAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
"updateId" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"updatedAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "group_user_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "album" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT "group_user_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT "group_user_pkey" PRIMARY KEY ("groupId", "userId")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "group_user_groupId_idx" ON "group_user" ("groupId");`.execute(db);
|
||||
await sql`CREATE INDEX "group_user_userId_idx" ON "group_user" ("userId");`.execute(db);
|
||||
await sql`CREATE INDEX "group_user_createId_idx" ON "group_user" ("createId");`.execute(db);
|
||||
await sql`CREATE INDEX "group_user_updateId_idx" ON "group_user" ("updateId");`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "group_user_delete_audit"
|
||||
AFTER DELETE ON "group_user"
|
||||
REFERENCING OLD TABLE AS "old"
|
||||
FOR EACH STATEMENT
|
||||
WHEN (pg_trigger_depth() <= 1)
|
||||
EXECUTE FUNCTION group_user_delete_audit();`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "group_user_updatedAt"
|
||||
BEFORE UPDATE ON "group_user"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION updated_at();`.execute(db);
|
||||
await sql`CREATE TABLE "group" (
|
||||
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"name" character varying NOT NULL,
|
||||
"description" character varying,
|
||||
"createdAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
"updateId" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
CONSTRAINT "group_name_uq" UNIQUE ("name"),
|
||||
CONSTRAINT "group_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "group_updatedAt_id_idx" ON "group" ("updatedAt", "id");`.execute(db);
|
||||
await sql`CREATE INDEX "group_updateId_idx" ON "group" ("updateId");`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "group_delete_audit"
|
||||
AFTER DELETE ON "group"
|
||||
REFERENCING OLD TABLE AS "old"
|
||||
FOR EACH STATEMENT
|
||||
WHEN (pg_trigger_depth() = 0)
|
||||
EXECUTE FUNCTION group_delete_audit();`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "group_updatedAt"
|
||||
BEFORE UPDATE ON "group"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION updated_at();`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_group_delete_audit', '{"type":"function","name":"group_delete_audit","sql":"CREATE OR REPLACE FUNCTION group_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO group_audit (\\"groupId\\")\\n SELECT \\"id\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_group_user_delete_audit', '{"type":"function","name":"group_user_delete_audit","sql":"CREATE OR REPLACE FUNCTION group_user_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO group_audit (\\"groupId\\", \\"userId\\")\\n SELECT \\"groupId\\", \\"userId\\"\\n FROM OLD;\\n\\n IF pg_trigger_depth() = 1 THEN\\n INSERT INTO group_user_audit (\\"groupId\\", \\"userId\\")\\n SELECT \\"groupId\\", \\"userId\\"\\n FROM OLD;\\n END IF;\\n\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_group_user_delete_audit', '{"type":"trigger","name":"group_user_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"group_user_delete_audit\\"\\n AFTER DELETE ON \\"group_user\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION group_user_delete_audit();"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_group_user_updatedAt', '{"type":"trigger","name":"group_user_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"group_user_updatedAt\\"\\n BEFORE UPDATE ON \\"group_user\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_group_delete_audit', '{"type":"trigger","name":"group_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"group_delete_audit\\"\\n AFTER DELETE ON \\"group\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION group_delete_audit();"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_group_updatedAt', '{"type":"trigger","name":"group_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"group_updatedAt\\"\\n BEFORE UPDATE ON \\"group\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`DROP TABLE "group_audit";`.execute(db);
|
||||
await sql`DROP TABLE "group_user_audit";`.execute(db);
|
||||
await sql`DROP TABLE "group_user";`.execute(db);
|
||||
await sql`DROP TABLE "group";`.execute(db);
|
||||
await sql`DROP FUNCTION group_delete_audit;`.execute(db);
|
||||
await sql`DROP FUNCTION group_user_delete_audit;`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_group_delete_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_group_user_delete_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_group_user_delete_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_group_user_updatedAt';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_group_delete_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_group_updatedAt';`.execute(db);
|
||||
}
|
||||
11
server/src/schema/migrations/1753973644248-FixStuff.ts
Normal file
11
server/src/schema/migrations/1753973644248-FixStuff.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`ALTER TABLE "group_user" DROP CONSTRAINT "group_user_groupId_fkey";`.execute(db);
|
||||
await sql`ALTER TABLE "group_user" ADD CONSTRAINT "group_user_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "group" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`ALTER TABLE "group_user" DROP CONSTRAINT "group_user_groupId_fkey";`.execute(db);
|
||||
await sql`ALTER TABLE "group_user" ADD CONSTRAINT "group_user_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "album" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
}
|
||||
33
server/src/schema/migrations/1753976066354-FixStuff2.ts
Normal file
33
server/src/schema/migrations/1753976066354-FixStuff2.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`DROP TRIGGER "group_delete_audit" ON "group";`.execute(db);
|
||||
await sql`ALTER TABLE "group_audit" ADD "userId" uuid NOT NULL;`.execute(db);
|
||||
await sql`DROP FUNCTION group_delete_audit;`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_group_delete_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_group_delete_audit';`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE OR REPLACE FUNCTION public.group_delete_audit()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
BEGIN
|
||||
INSERT INTO group_audit ("groupId")
|
||||
SELECT "id"
|
||||
FROM OLD;
|
||||
RETURN NULL;
|
||||
END
|
||||
$function$
|
||||
`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "group_delete_audit"
|
||||
AFTER DELETE ON "group"
|
||||
REFERENCING OLD TABLE AS "old"
|
||||
FOR EACH STATEMENT
|
||||
WHEN ((pg_trigger_depth() = 0))
|
||||
EXECUTE FUNCTION group_delete_audit();`.execute(db);
|
||||
await sql`ALTER TABLE "group_audit" DROP COLUMN "userId";`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_group_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION group_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO group_audit (\\"groupId\\")\\n SELECT \\"id\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;","name":"group_delete_audit","type":"function"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_group_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"group_delete_audit\\"\\n AFTER DELETE ON \\"group\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION group_delete_audit();","name":"group_delete_audit","type":"trigger"}'::jsonb);`.execute(db);
|
||||
}
|
||||
70
server/src/schema/migrations/1753995223470-FixStuff.ts
Normal file
70
server/src/schema/migrations/1753995223470-FixStuff.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE OR REPLACE FUNCTION album_group_delete_audit()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO "album_audit" ("albumId", "userId")
|
||||
SELECT OLD."albumId", "group_user"."userId"
|
||||
FROM OLD INNER JOIN "group_user" ON "group_user"."groupId" = OLD."groupId";
|
||||
|
||||
IF pg_trigger_depth() = 1 THEN
|
||||
INSERT INTO album_group_audit ("albumId", "groupId")
|
||||
SELECT "albumId", "groupId"
|
||||
FROM OLD;
|
||||
END IF;
|
||||
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;`.execute(db);
|
||||
await sql`CREATE TABLE "album_group_audit" (
|
||||
"id" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"albumId" uuid NOT NULL,
|
||||
"groupId" uuid NOT NULL,
|
||||
"deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(),
|
||||
CONSTRAINT "album_group_audit_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "album_group_audit_albumId_idx" ON "album_group_audit" ("albumId");`.execute(db);
|
||||
await sql`CREATE INDEX "album_group_audit_groupId_idx" ON "album_group_audit" ("groupId");`.execute(db);
|
||||
await sql`CREATE INDEX "album_group_audit_deletedAt_idx" ON "album_group_audit" ("deletedAt");`.execute(db);
|
||||
await sql`CREATE TABLE "album_group" (
|
||||
"albumId" uuid NOT NULL,
|
||||
"groupId" uuid NOT NULL,
|
||||
"role" character varying NOT NULL DEFAULT 'editor',
|
||||
"createId" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"createdAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
"updateId" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"updatedAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "album_group_albumId_fkey" FOREIGN KEY ("albumId") REFERENCES "album" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT "album_group_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "group" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT "album_group_pkey" PRIMARY KEY ("albumId", "groupId")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "album_group_albumId_idx" ON "album_group" ("albumId");`.execute(db);
|
||||
await sql`CREATE INDEX "album_group_groupId_idx" ON "album_group" ("groupId");`.execute(db);
|
||||
await sql`CREATE INDEX "album_group_createId_idx" ON "album_group" ("createId");`.execute(db);
|
||||
await sql`CREATE INDEX "album_group_updateId_idx" ON "album_group" ("updateId");`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "album_group_delete_audit"
|
||||
AFTER DELETE ON "album_group"
|
||||
REFERENCING OLD TABLE AS "old"
|
||||
FOR EACH STATEMENT
|
||||
WHEN (pg_trigger_depth() <= 1)
|
||||
EXECUTE FUNCTION album_group_delete_audit();`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "album_group_updatedAt"
|
||||
BEFORE UPDATE ON "album_group"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION updated_at();`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_group_delete_audit', '{"type":"function","name":"album_group_delete_audit","sql":"CREATE OR REPLACE FUNCTION album_group_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO \\"album_audit\\" (\\"albumId\\", \\"userId\\")\\n SELECT OLD.\\"albumId\\", \\"group_user\\".\\"userId\\"\\n FROM OLD INNER JOIN \\"group_user\\" ON \\"group_user\\".\\"groupId\\" = OLD.\\"groupId\\";\\n\\n IF pg_trigger_depth() = 1 THEN\\n INSERT INTO album_group_audit (\\"albumId\\", \\"groupId\\")\\n SELECT \\"albumId\\", \\"groupId\\"\\n FROM OLD;\\n END IF;\\n\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_group_delete_audit', '{"type":"trigger","name":"album_group_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"album_group_delete_audit\\"\\n AFTER DELETE ON \\"album_group\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION album_group_delete_audit();"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_group_updatedAt', '{"type":"trigger","name":"album_group_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"album_group_updatedAt\\"\\n BEFORE UPDATE ON \\"album_group\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`DROP TABLE "album_group_audit";`.execute(db);
|
||||
await sql`DROP TABLE "album_group";`.execute(db);
|
||||
await sql`DROP FUNCTION album_group_delete_audit;`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_album_group_delete_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_group_delete_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_group_updatedAt';`.execute(db);
|
||||
}
|
||||
17
server/src/schema/tables/album-group-audit.table.ts
Normal file
17
server/src/schema/tables/album-group-audit.table.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
||||
|
||||
@Table('album_group_audit')
|
||||
export class AlbumGroupAuditTable {
|
||||
@PrimaryGeneratedUuidV7Column()
|
||||
id!: Generated<string>;
|
||||
|
||||
@Column({ type: 'uuid', index: true })
|
||||
albumId!: string;
|
||||
|
||||
@Column({ type: 'uuid', index: true })
|
||||
groupId!: string;
|
||||
|
||||
@CreateDateColumn({ default: () => 'clock_timestamp()', index: true })
|
||||
deletedAt!: Generated<Timestamp>;
|
||||
}
|
||||
56
server/src/schema/tables/album-group.table.ts
Normal file
56
server/src/schema/tables/album-group.table.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||
import { AlbumUserRole } from 'src/enum';
|
||||
import { album_group_delete_audit } from 'src/schema/functions';
|
||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
import { GroupTable } from 'src/schema/tables/group.table';
|
||||
import {
|
||||
AfterDeleteTrigger,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
ForeignKeyColumn,
|
||||
Generated,
|
||||
Table,
|
||||
Timestamp,
|
||||
UpdateDateColumn,
|
||||
} from 'src/sql-tools';
|
||||
|
||||
@Table({ name: 'album_group' })
|
||||
@UpdatedAtTrigger('album_group_updatedAt')
|
||||
@AfterDeleteTrigger({
|
||||
scope: 'statement',
|
||||
function: album_group_delete_audit,
|
||||
referencingOldTableAs: 'old',
|
||||
when: 'pg_trigger_depth() <= 1',
|
||||
})
|
||||
export class AlbumGroupTable {
|
||||
@ForeignKeyColumn(() => AlbumTable, {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
nullable: false,
|
||||
primary: true,
|
||||
})
|
||||
albumId!: string;
|
||||
|
||||
@ForeignKeyColumn(() => GroupTable, {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
nullable: false,
|
||||
primary: true,
|
||||
})
|
||||
groupId!: string;
|
||||
|
||||
@Column({ type: 'character varying', default: AlbumUserRole.Editor })
|
||||
role!: Generated<AlbumUserRole>;
|
||||
|
||||
@CreateIdColumn({ index: true })
|
||||
createId!: Generated<string>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateIdColumn({ index: true })
|
||||
updateId!: Generated<string>;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
}
|
||||
17
server/src/schema/tables/group-audit.table.ts
Normal file
17
server/src/schema/tables/group-audit.table.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
||||
|
||||
@Table('group_audit')
|
||||
export class GroupAuditTable {
|
||||
@PrimaryGeneratedUuidV7Column()
|
||||
id!: Generated<string>;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
groupId!: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
userId!: string;
|
||||
|
||||
@CreateDateColumn({ default: () => 'clock_timestamp()', index: true })
|
||||
deletedAt!: Generated<Timestamp>;
|
||||
}
|
||||
17
server/src/schema/tables/group-user-audit.table.ts
Normal file
17
server/src/schema/tables/group-user-audit.table.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
||||
|
||||
@Table('group_user_audit')
|
||||
export class GroupUserAuditTable {
|
||||
@PrimaryGeneratedUuidV7Column()
|
||||
id!: Generated<string>;
|
||||
|
||||
@Column({ type: 'uuid', index: true })
|
||||
groupId!: string;
|
||||
|
||||
@Column({ type: 'uuid', index: true })
|
||||
userId!: string;
|
||||
|
||||
@CreateDateColumn({ default: () => 'clock_timestamp()', index: true })
|
||||
deletedAt!: Generated<Timestamp>;
|
||||
}
|
||||
51
server/src/schema/tables/group-user.table.ts
Normal file
51
server/src/schema/tables/group-user.table.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||
import { group_user_delete_audit } from 'src/schema/functions';
|
||||
import { GroupTable } from 'src/schema/tables/group.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
AfterDeleteTrigger,
|
||||
CreateDateColumn,
|
||||
ForeignKeyColumn,
|
||||
Generated,
|
||||
Table,
|
||||
Timestamp,
|
||||
UpdateDateColumn,
|
||||
} from 'src/sql-tools';
|
||||
|
||||
@Table({ name: 'group_user' })
|
||||
@UpdatedAtTrigger('group_user_updatedAt')
|
||||
@AfterDeleteTrigger({
|
||||
scope: 'statement',
|
||||
function: group_user_delete_audit,
|
||||
referencingOldTableAs: 'old',
|
||||
when: 'pg_trigger_depth() <= 1',
|
||||
})
|
||||
export class GroupUserTable {
|
||||
@ForeignKeyColumn(() => GroupTable, {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
nullable: false,
|
||||
primary: true,
|
||||
})
|
||||
groupId!: string;
|
||||
|
||||
@ForeignKeyColumn(() => UserTable, {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
nullable: false,
|
||||
primary: true,
|
||||
})
|
||||
userId!: string;
|
||||
|
||||
@CreateIdColumn({ index: true })
|
||||
createId!: Generated<string>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateIdColumn({ index: true })
|
||||
updateId!: Generated<string>;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
}
|
||||
34
server/src/schema/tables/group.table.ts
Normal file
34
server/src/schema/tables/group.table.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Generated,
|
||||
Index,
|
||||
PrimaryGeneratedColumn,
|
||||
Table,
|
||||
Timestamp,
|
||||
UpdateDateColumn,
|
||||
} from 'src/sql-tools';
|
||||
|
||||
@Table('group')
|
||||
@UpdatedAtTrigger('group_updatedAt')
|
||||
@Index({ columns: ['updatedAt', 'id'] })
|
||||
export class GroupTable {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: Generated<string>;
|
||||
|
||||
@Column({ unique: true })
|
||||
name!: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description!: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateIdColumn({ index: true })
|
||||
updateId!: Generated<string>;
|
||||
}
|
||||
@@ -1,4 +1,11 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
AlbumGroupCreateAllDto,
|
||||
AlbumGroupDeleteAllDto,
|
||||
AlbumGroupResponseDto,
|
||||
AlbumGroupUpdateDto,
|
||||
mapAlbumGroup,
|
||||
} from 'src/dtos/album-group.dto';
|
||||
import {
|
||||
AddUsersDto,
|
||||
AlbumInfoDto,
|
||||
@@ -204,6 +211,38 @@ export class AlbumService extends BaseService {
|
||||
return results;
|
||||
}
|
||||
|
||||
async getGroups(auth: AuthDto, id: string): Promise<AlbumGroupResponseDto[]> {
|
||||
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] });
|
||||
const albumGroups = await this.albumGroupRepository.getAll(id);
|
||||
return albumGroups.map((albumGroup) => mapAlbumGroup(albumGroup));
|
||||
}
|
||||
|
||||
async upsertGroups(auth: AuthDto, id: string, { groups }: AlbumGroupCreateAllDto): Promise<AlbumGroupResponseDto[]> {
|
||||
await this.requireAccess({ auth, permission: Permission.AlbumUpdate, ids: [id] });
|
||||
const albumGroups = await this.albumGroupRepository.createAll(id, groups);
|
||||
return albumGroups.map((albumGroup) => mapAlbumGroup(albumGroup));
|
||||
}
|
||||
|
||||
async removeGroups(auth: AuthDto, id: string, dto: AlbumGroupDeleteAllDto): Promise<void> {
|
||||
await this.requireAccess({ auth, permission: Permission.AlbumUpdate, ids: [id] });
|
||||
await this.albumGroupRepository.deleteAll(id, dto.groupIds);
|
||||
}
|
||||
|
||||
async updateGroup(
|
||||
auth: AuthDto,
|
||||
id: string,
|
||||
groupId: string,
|
||||
dto: AlbumGroupUpdateDto,
|
||||
): Promise<AlbumGroupResponseDto> {
|
||||
await this.requireAccess({ auth, permission: Permission.AlbumUpdate, ids: [id] });
|
||||
const exists = await this.albumGroupRepository.exists({ albumId: id, groupId });
|
||||
if (!exists) {
|
||||
throw new BadRequestException('Album group not found');
|
||||
}
|
||||
const albumGroup = await this.albumGroupRepository.update({ albumId: id, groupId }, { role: dto.role });
|
||||
return mapAlbumGroup(albumGroup);
|
||||
}
|
||||
|
||||
async addUsers(auth: AuthDto, id: string, { albumUsers }: AddUsersDto): Promise<AlbumResponseDto> {
|
||||
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { StorageCore } from 'src/cores/storage.core';
|
||||
import { UserAdmin } from 'src/database';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { AlbumGroupRepository } from 'src/repositories/album-group.repository';
|
||||
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
||||
@@ -21,6 +22,8 @@ import { DownloadRepository } from 'src/repositories/download.repository';
|
||||
import { DuplicateRepository } from 'src/repositories/duplicate.repository';
|
||||
import { EmailRepository } from 'src/repositories/email.repository';
|
||||
import { EventRepository } from 'src/repositories/event.repository';
|
||||
import { GroupUserRepository } from 'src/repositories/group-user.repository';
|
||||
import { GroupRepository } from 'src/repositories/group.repository';
|
||||
import { JobRepository } from 'src/repositories/job.repository';
|
||||
import { LibraryRepository } from 'src/repositories/library.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
@@ -111,6 +114,7 @@ export class BaseService {
|
||||
protected accessRepository: AccessRepository,
|
||||
protected activityRepository: ActivityRepository,
|
||||
protected albumRepository: AlbumRepository,
|
||||
protected albumGroupRepository: AlbumGroupRepository,
|
||||
protected albumUserRepository: AlbumUserRepository,
|
||||
protected apiKeyRepository: ApiKeyRepository,
|
||||
protected assetRepository: AssetRepository,
|
||||
@@ -124,6 +128,8 @@ export class BaseService {
|
||||
protected duplicateRepository: DuplicateRepository,
|
||||
protected emailRepository: EmailRepository,
|
||||
protected eventRepository: EventRepository,
|
||||
protected groupRepository: GroupRepository,
|
||||
protected groupUserRepository: GroupUserRepository,
|
||||
protected jobRepository: JobRepository,
|
||||
protected libraryRepository: LibraryRepository,
|
||||
protected machineLearningRepository: MachineLearningRepository,
|
||||
|
||||
@@ -86,7 +86,12 @@ export class CliService extends BaseService {
|
||||
}
|
||||
|
||||
for (const asset of assets) {
|
||||
paths.push(asset.path);
|
||||
paths.push(
|
||||
asset.originalPath,
|
||||
asset.sidecarPath,
|
||||
asset.encodedVideoPath,
|
||||
...asset.files.map((file) => file.path),
|
||||
);
|
||||
}
|
||||
|
||||
return paths.filter(Boolean) as string[];
|
||||
|
||||
95
server/src/services/group-admin.service.ts
Normal file
95
server/src/services/group-admin.service.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { PostgresError } from 'postgres';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import {
|
||||
GroupUserCreateAllDto,
|
||||
GroupUserDeleteAllDto,
|
||||
GroupUserResponseDto,
|
||||
mapGroupUser,
|
||||
} from 'src/dtos/group-user.dto';
|
||||
import {
|
||||
GroupAdminCreateDto,
|
||||
GroupAdminResponseDto,
|
||||
GroupAdminSearchDto,
|
||||
GroupAdminUpdateDto,
|
||||
mapGroupAdmin,
|
||||
} from 'src/dtos/group.dto';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
|
||||
@Injectable()
|
||||
export class GroupAdminService extends BaseService {
|
||||
async search(auth: AuthDto, dto: GroupAdminSearchDto): Promise<GroupAdminResponseDto[]> {
|
||||
const groups = await this.groupRepository.search(dto);
|
||||
return groups.map((group) => mapGroupAdmin(group));
|
||||
}
|
||||
|
||||
async create(auth: AuthDto, dto: GroupAdminCreateDto): Promise<GroupAdminResponseDto> {
|
||||
try {
|
||||
const { users, ...groupDto } = dto;
|
||||
const group = await this.groupRepository.create(groupDto, users);
|
||||
return mapGroupAdmin(group);
|
||||
} catch (error) {
|
||||
this.handleError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async get(auth: AuthDto, id: string): Promise<GroupAdminResponseDto> {
|
||||
const group = await this.findOrFail(id);
|
||||
return mapGroupAdmin(group);
|
||||
}
|
||||
|
||||
async update(auth: AuthDto, id: string, dto: GroupAdminUpdateDto): Promise<GroupAdminResponseDto> {
|
||||
await this.findOrFail(id);
|
||||
const updated = await this.groupRepository.update(id, { ...dto, updatedAt: new Date() });
|
||||
return mapGroupAdmin(updated);
|
||||
}
|
||||
|
||||
async delete(auth: AuthDto, id: string): Promise<void> {
|
||||
await this.findOrFail(id);
|
||||
await this.groupRepository.delete(id);
|
||||
}
|
||||
|
||||
async getUsers(auth: AuthDto, id: string): Promise<GroupUserResponseDto[]> {
|
||||
await this.findOrFail(id);
|
||||
const users = await this.groupUserRepository.getAll(id);
|
||||
return users.map((user) => mapGroupUser(user));
|
||||
}
|
||||
|
||||
async addUsers(auth: AuthDto, id: string, { users }: GroupUserCreateAllDto): Promise<GroupUserResponseDto[]> {
|
||||
await this.findOrFail(id);
|
||||
const userIds = users.map(({ userId }) => userId);
|
||||
const groupUsers = await this.groupUserRepository.createAll(id, userIds);
|
||||
return groupUsers.map((groupUser) => mapGroupUser(groupUser));
|
||||
}
|
||||
|
||||
async removeUsers(auth: AuthDto, id: string, dto: GroupUserDeleteAllDto): Promise<void> {
|
||||
await this.findOrFail(id);
|
||||
await this.groupUserRepository.deleteAll(id, dto.userIds);
|
||||
}
|
||||
|
||||
async removeUser(auth: AuthDto, id: string, userId: string): Promise<void> {
|
||||
await this.findOrFail(id);
|
||||
|
||||
const exists = await this.groupUserRepository.get({ groupId: id, userId });
|
||||
if (!exists) {
|
||||
throw new BadRequestException('Group does not include this user');
|
||||
}
|
||||
|
||||
await this.groupUserRepository.delete({ groupId: id, userId });
|
||||
}
|
||||
|
||||
private handleError(error: unknown): never {
|
||||
if ((error as PostgresError).constraint_name === 'group_name_uq') {
|
||||
throw new BadRequestException('Group with this name already exists');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
private async findOrFail(id: string) {
|
||||
const group = await this.groupRepository.get(id);
|
||||
if (!group) {
|
||||
throw new BadRequestException('Group not found');
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
38
server/src/services/group.service.ts
Normal file
38
server/src/services/group.service.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { GroupUserResponseDto, mapGroupUser } from 'src/dtos/group-user.dto';
|
||||
import { GroupResponseDto, mapGroup } from 'src/dtos/group.dto';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
|
||||
@Injectable()
|
||||
export class GroupService extends BaseService {
|
||||
async search(auth: AuthDto): Promise<GroupResponseDto[]> {
|
||||
const groups = await this.groupRepository.search({ userId: auth.user.id });
|
||||
return groups.map((group) => mapGroup(group));
|
||||
}
|
||||
|
||||
async get(auth: AuthDto, id: string): Promise<GroupResponseDto> {
|
||||
const group = await this.findOrFail({ userId: auth.user.id, groupId: id });
|
||||
return mapGroup(group);
|
||||
}
|
||||
|
||||
async delete(auth: AuthDto, id: string): Promise<void> {
|
||||
await this.findOrFail({ userId: auth.user.id, groupId: id });
|
||||
await this.groupUserRepository.delete({ userId: auth.user.id, groupId: id });
|
||||
}
|
||||
|
||||
async getUsers(auth: AuthDto, id: string): Promise<GroupUserResponseDto[]> {
|
||||
await this.findOrFail({ userId: auth.user.id, groupId: id });
|
||||
const users = await this.groupUserRepository.getAll(id);
|
||||
return users.map((user) => mapGroupUser(user));
|
||||
}
|
||||
|
||||
async findOrFail({ userId, groupId }: { userId: string; groupId: string }): Promise<GroupResponseDto> {
|
||||
const [group] = await this.groupUserRepository.get({ userId, groupId });
|
||||
if (!group) {
|
||||
throw new BadRequestException('Group not found');
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import { CliService } from 'src/services/cli.service';
|
||||
import { DatabaseService } from 'src/services/database.service';
|
||||
import { DownloadService } from 'src/services/download.service';
|
||||
import { DuplicateService } from 'src/services/duplicate.service';
|
||||
import { GroupAdminService } from 'src/services/group-admin.service';
|
||||
import { GroupService } from 'src/services/group.service';
|
||||
import { JobService } from 'src/services/job.service';
|
||||
import { LibraryService } from 'src/services/library.service';
|
||||
import { MapService } from 'src/services/map.service';
|
||||
@@ -54,6 +56,8 @@ export const services = [
|
||||
DatabaseService,
|
||||
DownloadService,
|
||||
DuplicateService,
|
||||
GroupAdminService,
|
||||
GroupService,
|
||||
JobService,
|
||||
LibraryService,
|
||||
MapService,
|
||||
|
||||
@@ -97,18 +97,18 @@ export class StorageService extends BaseService {
|
||||
const current = StorageCore.getMediaLocation();
|
||||
const samples = await this.assetRepository.getFileSamples();
|
||||
if (samples.length > 0) {
|
||||
const path = samples[0].path;
|
||||
const originalPath = samples[0].originalPath;
|
||||
const savedValue = await this.systemMetadataRepository.get(SystemMetadataKey.MediaLocation);
|
||||
let previous = savedValue?.location || '';
|
||||
|
||||
if (!previous) {
|
||||
previous = path.startsWith('upload/') ? 'upload' : '/usr/src/app/upload';
|
||||
previous = originalPath.startsWith('upload/') ? 'upload' : '/usr/src/app/upload';
|
||||
}
|
||||
|
||||
if (previous !== current) {
|
||||
this.logger.log(`Media location changed (from=${previous}, to=${current})`);
|
||||
|
||||
if (!path.startsWith(previous)) {
|
||||
if (!originalPath.startsWith(previous)) {
|
||||
throw new Error(
|
||||
'Detected an inconsistent media location. For more information, see https://immich.app/errors#inconsistent-media-location',
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user