Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Tran 942c53d9fe feat: add RC fastlane lanes and wire build-mobile workflow 2026-05-13 15:26:18 -05:00
163 changed files with 7562 additions and 6373 deletions
@@ -16,7 +16,7 @@ services:
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
- /etc/localtime:/etc/localtime:ro
- pnpm_store_server:/buildcache/pnpm-store
- ../packages/plugin-core:/build/plugins/immich-plugin-core
- ../packages/plugins:/build/corePlugin
immich-web:
env_file: !reset []
immich-machine-learning:
+8 -4
View File
@@ -7,7 +7,7 @@ on:
required: false
type: string
environment:
description: 'Target environment'
description: 'Target environment (development, rc, or production)'
required: true
default: 'development'
type: string
@@ -116,6 +116,7 @@ jobs:
~/.gradle/wrapper
~/.android/sdk
mobile/android/.gradle
mobile/.dart_tool
key: build-mobile-gradle-${{ runner.os }}-main
- name: Setup Android SDK
@@ -188,6 +189,7 @@ jobs:
~/.gradle/wrapper
~/.android/sdk
mobile/android/.gradle
mobile/.dart_tool
key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }}
build-sign-ios:
@@ -296,10 +298,12 @@ jobs:
run: |
# Only upload to TestFlight on main branch
if [[ "$GITHUB_REF" == "refs/heads/main" ]]; then
if [[ "$ENVIRONMENT" == "development" ]]; then
bundle exec fastlane gha_testflight_dev
else
if [[ "$ENVIRONMENT" == "rc" ]]; then
bundle exec fastlane gha_testflight_rc
elif [[ "$ENVIRONMENT" == "production" ]]; then
bundle exec fastlane gha_release_prod
elif [[ "$ENVIRONMENT" == "development" ]]; then
bundle exec fastlane gha_testflight_dev
fi
else
# Build only, no TestFlight upload for non-main branches
+14 -10
View File
@@ -62,6 +62,9 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: ./server
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
@@ -81,7 +84,7 @@ jobs:
github_token: ${{ steps.token.outputs.token }}
- name: Run ci-unit
run: mise run //server:ci-unit
run: mise run ci-unit
cli-unit-tests:
name: Unit Test CLI
@@ -377,7 +380,7 @@ jobs:
cache-dependency-path: '**/pnpm-lock.yaml'
- name: Setup packages
run: pnpm --filter @immich/sdk --filter @immich/cli install --frozen-lockfile && pnpm --filter @immich/sdk --filter @immich/cli build
run: pnpm --filter "@immich/*" install --frozen-lockfile && pnpm --filter "@immich/*" build
- name: Run setup web
run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync
@@ -675,6 +678,7 @@ jobs:
- name: Install server dependencies
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich install --frozen-lockfile
- name: Run API generation
run: mise //:open-api
working-directory: open-api
@@ -713,6 +717,9 @@ jobs:
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
ports:
- 5432:5432
defaults:
run:
working-directory: ./server
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
@@ -734,21 +741,18 @@ jobs:
- name: Install server dependencies
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
- name: Build plugins
run: mise //:plugins
- name: Build the app
run: mise //server:build
run: pnpm build
- name: Run existing migrations
run: pnpm --filter immich migrations:run
run: pnpm migrations:run
- name: Test npm run schema:reset command works
run: pnpm --filter immich schema:reset
run: pnpm schema:reset
- name: Generate new migrations
continue-on-error: true
run: pnpm --filter migrations:generate src/TestMigration
run: pnpm migrations:generate src/TestMigration
- name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
@@ -764,7 +768,7 @@ jobs:
run: |
echo "ERROR: Generated migration files not up to date!"
echo "Changed files: ${CHANGED_FILES}"
cat ./server/src/*-TestMigration.ts
cat ./src/*-TestMigration.ts
exit 1
- name: Run SQL generation
+1 -1
View File
@@ -74,7 +74,7 @@ services:
- ${UPLOAD_LOCATION}/photos:/data
- /etc/localtime:/etc/localtime:ro
- pnpm_store_server:/buildcache/pnpm-store
- ../packages/plugin-core:/build/plugins/immich-plugin-core
- ../packages/plugins:/build/corePlugin
env_file:
- .env
environment:
+1 -1
View File
@@ -18,7 +18,7 @@ make e2e
Before you can run the tests, you need to run the following commands _once_:
- `pnpm install`
- `pnpm --filter @immich/sdk --filter @immich/cli build`
- `pnpm --filter "@immich/*" build`
- `mise //:open-api`
Once the test environment is running, the e2e tests can be run via:
+2 -66
View File
@@ -52,7 +52,7 @@ Scroll to the bottom of the "**Details**" section and find the `IP Address` list
## Step 4 - Configure Firewall Settings
Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS to allow communication between the Immich containers.
Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS.
Open "**Control Panel**" on your Synology NAS, and select "**Security**". Navigate to "**Firewall**"
@@ -74,7 +74,6 @@ Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instr
<details>
<summary>Updating Immich using Container Manager</summary>
Check the post installation and upgrade instructions at the links above before proceeding with this section.
## Step 1. Backup
@@ -111,7 +110,7 @@ Go to **Project**, select **Action** then **Build**. This will download, unpack,
## Step 5. Update firewall rule
Without a fixed subnet, the default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address.
The default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address.
Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address.
![Container IP](../../static/img/synology-container-ip.png)
@@ -124,67 +123,4 @@ In this example, the IP addresses mismatch and the firewall rule needs to be edi
![Edit IP](../../static/img/synology-fw-ipedit.png)
To prevent future firewall issues, you may set a fixed subnet. [See Set Fixed Subnet](#set-fixed-subnet) for instructions.
</details>
<details id="set-fixed-subnet">
<summary>Set Fixed Subnet</summary>
Docker by default assigns dynamic subnets to bridge networks which can change when rebuilding containers and can cause firewall rules to break. To avoid this, define a fixed subnet in your `docker-compose.yml`:
## Step 1. Determine current subnet
Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address.
![Container IP](../../static/img/synology-container-ip.png)
## Step 2. Add network configuration
Add the following network configuration at the end of your `docker-compose.yml` file:
```yaml
networks:
immich-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.1
```
If your docker container is running on a different subnet then update accordingly.
## Step 3. Add network to each service
Add the network to each service (immich-server, immich-machine-learning, redis, database):
```yaml
services:
immich-server:
# other config options
networks:
- immich-network
immich-machine-learning:
# other config options
networks:
- immich-network
redis:
# other config options
networks:
- immich-network
database:
# other config options
networks:
- immich-network
```
Save your changes. Synology will ask if you want to save changes only or rebuild containers. Select rebuild containers.
## Step 4. Update Firewall Rules, if necessary
If your firewall rules were not already set for this subnet, the firewall rules will need to be updated. See [Step 4 - Configure Firewall Settings](#step-4---configure-firewall-settings).
</details>
+7 -13
View File
@@ -22,12 +22,13 @@
"add_birthday": "Add a birthday",
"add_endpoint": "Add endpoint",
"add_exclusion_pattern": "Add exclusion pattern",
"add_filter": "Add filter",
"add_filter_description": "Click to add a filter condition",
"add_location": "Add location",
"add_more_users": "Add more users",
"add_partner": "Add partner",
"add_path": "Add path",
"add_photos": "Add photos",
"add_step": "Add step",
"add_tag": "Add tag",
"add_to": "Add to…",
"add_to_album": "Add to album",
@@ -41,6 +42,7 @@
"add_to_shared_album": "Add to shared album",
"add_upload_to_stack": "Add upload to stack",
"add_url": "Add URL",
"add_workflow_step": "Add workflow step",
"added_to_archive": "Added to archive",
"added_to_favorites": "Added to favorites",
"added_to_favorites_count": "Added {count, number} to favorites",
@@ -731,7 +733,6 @@
"cannot_update_the_description": "Cannot update the description",
"cast": "Cast",
"cast_description": "Configure available cast destinations",
"change": "Change",
"change_date": "Change date",
"change_description": "Change description",
"change_display_order": "Change display order",
@@ -760,7 +761,6 @@
"check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.",
"check_logs": "Check Logs",
"checksum": "Checksum",
"choose": "Choose",
"choose_matching_people_to_merge": "Choose matching people to merge",
"city": "City",
"cleanup_confirm_description": "Immich found {count} assets (created before {date}) safely backed up to the server. Remove the local copies from this device?",
@@ -809,7 +809,6 @@
"comments_are_disabled": "Comments are disabled",
"common_create_new_album": "Create new album",
"completed": "Completed",
"configuration": "Configuration",
"confirm": "Confirm",
"confirm_admin_password": "Confirm Admin Password",
"confirm_delete_face": "Are you sure you want to delete {name} face from the asset?",
@@ -1629,6 +1628,7 @@
"next": "Next",
"next_memory": "Next memory",
"no": "No",
"no_actions_added": "No actions added yet",
"no_albums_found": "No albums found",
"no_albums_message": "Create an album to organize your photos and videos",
"no_albums_with_name_yet": "It looks like you do not have any albums with this name yet.",
@@ -1645,6 +1645,7 @@
"no_exif_info_available": "No exif info available",
"no_explore_results_message": "Upload more photos to explore your collection.",
"no_favorites_message": "Add favorites to quickly find your best pictures and videos",
"no_filters_added": "No filters added yet",
"no_libraries_message": "Create an external library to view your photos and videos",
"no_local_assets_found": "No local assets found with this checksum",
"no_location_set": "No location set",
@@ -1657,7 +1658,6 @@
"no_results": "No results",
"no_results_description": "Try a synonym or more general keyword",
"no_shared_albums_message": "Create an album to share photos and videos with people in your network",
"no_steps": "No steps added yet",
"no_uploads_in_progress": "No uploads in progress",
"none": "None",
"not_allowed": "Not allowed",
@@ -1794,8 +1794,6 @@
"play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.",
"play_transcoded_video": "Play transcoded video",
"please_auth_to_access": "Please authenticate to access",
"plugin_method_filter_type": "Filter",
"plugin_method_filter_type_description": "This method can filter events and conditionally prevent subsequent steps from running",
"port": "Port",
"preferences_settings_subtitle": "Manage the app's preferences",
"preferences_settings_title": "Preferences",
@@ -2238,10 +2236,6 @@
"start_date_before_end_date": "Start date must be before end date",
"state": "State",
"status": "Status",
"step_delete": "Delete step",
"step_delete_confirm": "Are you sure you want to delete this step?",
"step_details": "Step details",
"steps": "Steps",
"stop_casting": "Stop casting",
"stop_motion_photo": "Stop Motion Photo",
"stop_photo_sharing": "Stop sharing your photos?",
@@ -2335,7 +2329,7 @@
"trash_page_title": "Trash ({count})",
"trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.",
"trigger": "Trigger",
"trigger_asset_uploaded": "Asset Upload",
"trigger_asset_uploaded": "Asset Uploaded",
"trigger_asset_uploaded_description": "Triggered when a new asset is uploaded",
"trigger_description": "An event that kicks off the workflow",
"trigger_person_recognized": "Person Recognized",
@@ -2375,6 +2369,7 @@
"unsupported_field_type": "Unsupported field type",
"unsupported_file_type": "File {file} can't be uploaded because its file type {type} is not supported.",
"untagged": "Untagged",
"untitled_workflow": "Untitled workflow",
"up_next": "Up next",
"update_location_action_prompt": "Update the location of {count} selected assets with:",
"updated_at": "Updated",
@@ -2466,7 +2461,6 @@
"welcome_to_immich": "Welcome to Immich",
"width": "Width",
"wifi_name": "Wi-Fi Name",
"workflow": "Workflow",
"workflow_delete_prompt": "Are you sure you want to delete this workflow?",
"workflow_deleted": "Workflow deleted",
"workflow_description": "Workflow description",
+1 -12
View File
@@ -2,7 +2,7 @@ experimental_monorepo_root = true
[monorepo]
config_roots = [
"packages/plugin-core",
"packages/plugins",
"server",
"packages/cli",
"deployment",
@@ -22,9 +22,6 @@ terragrunt = "1.0.3"
opentofu = "1.11.6"
java = "21.0.2"
"npm:oazapfts" = "7.5.0"
"github:extism/cli" = "1.6.3"
"github:webassembly/binaryen" = "version_124"
"github:extism/js-pdk" = "1.6.0"
[tools."github:CQLabs/homebrew-dcm"]
version = "1.37.0"
@@ -44,12 +41,6 @@ macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz"
experimental = true
pin = true
[tasks.plugins]
run = [
"pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core install --frozen-lockfile",
"pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core build"
]
[tasks.open-api-typescript]
run = [
"oazapfts --optimistic --argumentStyle=object --useEnumType --allSchemas open-api/immich-openapi-specs.json packages/sdk/src/fetch-client.ts",
@@ -64,8 +55,6 @@ run = "bash ./bin/generate-dart-sdk.sh"
[tasks.open-api]
env = { SHARP_IGNORE_GLOBAL_LIBVIPS = true }
run = [
{ task = "//:plugins" },
{ task = "//server:build" },
{ task = "//server:install" },
{ task = "//server:build" },
{ task = "//server:sync-open-api" },
+2 -2
View File
@@ -16,8 +16,8 @@
default_platform(:android)
platform :android do
desc "Build Android and Release Testing"
lane :beta do
desc "Build and Release Android RC to Open Testing"
lane :rc do
gradle(
task: 'bundle',
build_type: 'Release',
+31
View File
@@ -169,6 +169,37 @@ end
)
end
desc "iOS RC Build to public TestFlight"
lane :gha_testflight_rc do
api_key = get_api_key
sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true)
main_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true)
share_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true)
widget_profile_name = lane_context[SharedValues::SIGH_NAME]
configure_code_signing(
base_bundle_id: DEV_BUNDLE_ID,
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
)
build_and_upload(
api_key: api_key,
base_bundle_id: DEV_BUNDLE_ID,
version_number: get_version_from_pubspec.split('-').first,
distribute_external: true,
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
)
end
desc "iOS Release to TestFlight"
lane :gha_release_prod do
api_key = get_api_key
@@ -11,7 +11,6 @@ class RemoteAsset extends BaseAsset {
final String ownerId;
final String? stackId;
final DateTime? uploadedAt;
final DateTime? deletedAt;
const RemoteAsset({
required this.id,
@@ -32,7 +31,6 @@ class RemoteAsset extends BaseAsset {
super.livePhotoVideoId,
this.stackId,
required super.isEdited,
this.deletedAt,
}) : localAssetId = localId;
@override
@@ -50,8 +48,6 @@ class RemoteAsset extends BaseAsset {
@override
bool get isEditable => isImage && !isMotionPhoto && !isAnimatedImage;
bool get isTrashed => deletedAt != null;
@override
String toString() {
return '''Asset {
@@ -90,8 +86,7 @@ class RemoteAsset extends BaseAsset {
thumbHash == other.thumbHash &&
visibility == other.visibility &&
stackId == other.stackId &&
uploadedAt == other.uploadedAt &&
deletedAt == other.deletedAt;
uploadedAt == other.uploadedAt;
}
@override
@@ -103,8 +98,7 @@ class RemoteAsset extends BaseAsset {
thumbHash.hashCode ^
visibility.hashCode ^
stackId.hashCode ^
uploadedAt.hashCode ^
deletedAt.hashCode;
uploadedAt.hashCode;
RemoteAsset copyWith({
String? id,
@@ -125,7 +119,6 @@ class RemoteAsset extends BaseAsset {
String? livePhotoVideoId,
String? stackId,
bool? isEdited,
DateTime? deletedAt,
}) {
return RemoteAsset(
id: id ?? this.id,
@@ -146,7 +139,6 @@ class RemoteAsset extends BaseAsset {
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
stackId: stackId ?? this.stackId,
isEdited: isEdited ?? this.isEdited,
deletedAt: deletedAt ?? this.deletedAt,
);
}
}
@@ -164,7 +156,6 @@ class RemoteAssetExif extends RemoteAsset {
required super.createdAt,
required super.updatedAt,
super.uploadedAt,
super.deletedAt,
super.width,
super.height,
super.durationMs,
@@ -202,7 +193,6 @@ class RemoteAssetExif extends RemoteAsset {
DateTime? createdAt,
DateTime? updatedAt,
DateTime? uploadedAt,
DateTime? deletedAt,
int? width,
int? height,
int? durationMs,
@@ -224,7 +214,6 @@ class RemoteAssetExif extends RemoteAsset {
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
uploadedAt: uploadedAt ?? this.uploadedAt,
deletedAt: deletedAt ?? this.deletedAt,
width: width ?? this.width,
height: height ?? this.height,
durationMs: durationMs ?? this.durationMs,
+11
View File
@@ -4,15 +4,25 @@ import 'package:immich_mobile/domain/models/user.model.dart';
/// Defines the data type for each value
enum StoreKey<T> {
version<int>._(0),
assetETag<String>._(1),
currentUser<UserDto>._(2),
deviceIdHash<int>._(3),
deviceId<String>._(4),
backupFailedSince<DateTime>._(5),
backupRequireWifi<bool>._(6),
backupRequireCharging<bool>._(7),
backupTriggerDelay<int>._(8),
serverUrl<String>._(10),
accessToken<String>._(11),
serverEndpoint<String>._(12),
autoBackup<bool>._(13),
backgroundBackup<bool>._(14),
sslClientCertData<String>._(15),
sslClientPasswd<String>._(16),
uploadErrorNotificationGracePeriod<int>._(106),
selectedAlbumSortOrder<int>._(113),
advancedTroubleshooting<bool>._(114),
selfSignedCert<bool>._(120),
selectedAlbumSortReverse<bool>._(123),
enableHapticFeedback<bool>._(126),
customHeaders<String>._(127),
@@ -28,6 +38,7 @@ enum StoreKey<T> {
// Read-only Mode settings
readonlyModeEnabled<bool>._(138),
albumGridView<bool>._(140),
loadOriginal<bool>._(101),
// Experimental stuff
enableBackup<bool>._(1003),
@@ -74,6 +74,5 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
localId: localId,
stackId: stackId,
isEdited: isEdited,
deletedAt: deletedAt,
);
}
@@ -18,15 +18,8 @@ class DeletePermanentActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
final bool useShortLabel;
const DeletePermanentActionButton({
super.key,
required this.source,
this.iconOnly = false,
this.menuItem = false,
this.useShortLabel = false,
});
const DeletePermanentActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
@@ -71,7 +64,7 @@ class DeletePermanentActionButton extends ConsumerWidget {
return BaseActionButton(
maxWidth: 110.0,
iconData: Icons.delete_forever,
label: useShortLabel ? "delete".t(context: context) : "delete_permanently".t(context: context),
label: "delete_permanently".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
@@ -1,55 +0,0 @@
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/domain/models/events.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.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 RestoreActionButton extends ConsumerWidget {
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const RestoreActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result = await ref.read(actionProvider.notifier).restoreTrash(source);
ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'assets_restored_count'.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,
);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.history_rounded,
label: 'restore'.t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
maxWidth: 100.0,
);
}
}
@@ -2,19 +2,15 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/add_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_image_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/restore_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/routes.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
@@ -37,31 +33,23 @@ class ViewerBottomBar extends ConsumerWidget {
final showingDetails = ref.watch(assetViewerProvider.select((s) => s.showingDetails));
final isInLockedView = ref.watch(inLockedViewProvider);
final serverInfo = ref.watch(serverInfoProvider);
final isInTrash = ref.read(timelineServiceProvider).origin == TimelineOrigin.trash;
final originalTheme = context.themeData;
final actions = <Widget>[
if (isInTrash && isOwner && asset.hasRemote)
const RestoreActionButton(source: ActionSource.viewer)
else
const ShareActionButton(source: ActionSource.viewer),
const ShareActionButton(source: ActionSource.viewer),
if (!isInLockedView) ...[
if (!isInTrash) ...[
if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer),
// edit sync was added in 2.6.0
if (asset.isEditable && serverInfo.serverVersion >= const SemVer(major: 2, minor: 6, patch: 0))
const EditImageActionButton(),
if (asset.hasRemote) AddActionButton(originalTheme: originalTheme),
],
if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer),
// edit sync was added in 2.6.0
if (asset.isEditable && serverInfo.serverVersion >= const SemVer(major: 2, minor: 6, patch: 0))
const EditImageActionButton(),
if (asset.hasRemote) AddActionButton(originalTheme: originalTheme),
if (isOwner) ...[
if (asset.isLocalOnly)
const DeleteLocalActionButton(source: ActionSource.viewer)
else if (asset.isTrashed)
const DeletePermanentActionButton(source: ActionSource.viewer, useShortLabel: true)
else
const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true),
asset.isLocalOnly
? const DeleteLocalActionButton(source: ActionSource.viewer)
: const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true),
],
],
];
+3 -1
View File
@@ -11,11 +11,12 @@ import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/auth.service.dart';
import 'package:immich_mobile/services/background_upload.service.dart';
import 'package:immich_mobile/services/foreground_upload.service.dart';
import 'package:immich_mobile/services/secure_storage.service.dart';
import 'package:immich_mobile/services/background_upload.service.dart';
import 'package:immich_mobile/services/widget.service.dart';
import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/hash.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
@@ -143,6 +144,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
// Due to the flow of the code, this will always happen on first login
user = serverUser;
await Store.put(StoreKey.deviceId, deviceId);
await Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
}
} on ApiException catch (error, stackTrace) {
if (error.code == 401) {
@@ -2,9 +2,15 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
enum AppSettingsEnum<T> {
uploadErrorNotificationGracePeriod<int>(
StoreKey.uploadErrorNotificationGracePeriod,
"uploadErrorNotificationGracePeriod",
2,
),
selectedAlbumSortOrder<int>(StoreKey.selectedAlbumSortOrder, "selectedAlbumSortOrder", 2),
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
manageLocalMediaAndroid<bool>(StoreKey.manageLocalMediaAndroid, null, false),
allowSelfSignedSSLCert<bool>(StoreKey.selfSignedCert, null, false),
selectedAlbumSortReverse<bool>(StoreKey.selectedAlbumSortReverse, null, true),
enableHapticFeedback<bool>(StoreKey.enableHapticFeedback, null, true),
syncAlbums<bool>(StoreKey.syncAlbums, null, false),
+1
View File
@@ -123,6 +123,7 @@ class AuthService {
_authRepository.clearLocalData(),
Store.delete(StoreKey.currentUser),
Store.delete(StoreKey.accessToken),
Store.delete(StoreKey.assetETag),
Store.delete(StoreKey.autoEndpointSwitching),
Store.delete(StoreKey.preferredWifiName),
Store.delete(StoreKey.localEndpoint),
+4 -19
View File
@@ -21,7 +21,6 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_f
import 'package:immich_mobile/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/restore_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/set_profile_picture_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
@@ -82,7 +81,6 @@ enum ActionButtonType {
moveToLockFolder,
removeFromLockFolder,
removeFromAlbum,
restoreTrash,
trash,
deleteLocal,
deletePermanent,
@@ -114,17 +112,12 @@ enum ActionButtonType {
context.isOwner && //
!context.isInLockedView && //
context.asset.hasRemote && //
context.isTrashEnabled && //
context.timelineOrigin != TimelineOrigin.trash,
ActionButtonType.restoreTrash =>
context.isOwner && //
!context.isInLockedView && //
context.asset.hasRemote && //
context.timelineOrigin == TimelineOrigin.trash,
context.isTrashEnabled,
ActionButtonType.deletePermanent =>
context.isOwner && //
context.asset.hasRemote && //
(!context.isTrashEnabled || context.timelineOrigin == TimelineOrigin.trash || context.isInLockedView),
context.asset.hasRemote && //
!context.isTrashEnabled ||
context.isInLockedView,
ActionButtonType.delete =>
context.isOwner && //
!context.isInLockedView && //
@@ -208,11 +201,6 @@ enum ActionButtonType {
),
ActionButtonType.download => DownloadActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.trash => TrashActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.restoreTrash => RestoreActionButton(
source: context.source,
iconOnly: iconOnly,
menuItem: menuItem,
),
ActionButtonType.deletePermanent => DeletePermanentActionButton(
source: context.source,
iconOnly: iconOnly,
@@ -304,7 +292,6 @@ enum ActionButtonType {
ActionButtonType.moveToLockFolder => 10,
ActionButtonType.deleteLocal => 10,
ActionButtonType.delete => 10,
ActionButtonType.restoreTrash => 10,
// 90: advancedInfo
ActionButtonType.advancedInfo => 90,
// 1: others
@@ -322,8 +309,6 @@ class ActionButtonBuilder {
ActionButtonType.delete,
ActionButtonType.archive,
ActionButtonType.unarchive,
ActionButtonType.restoreTrash,
ActionButtonType.deletePermanent,
};
static List<Widget> build(ActionButtonContext context) {
@@ -3,7 +3,10 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/notification_permission.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
import 'package:permission_handler/permission_handler.dart';
@@ -13,6 +16,9 @@ class NotificationSetting extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final permissionService = ref.watch(notificationPermissionProvider);
final sliderValue = useAppSettingsState(AppSettingsEnum.uploadErrorNotificationGracePeriod);
final hasPermission = permissionService == PermissionStatus.granted;
openAppNotificationSettings(BuildContext ctx) {
@@ -35,6 +41,8 @@ class NotificationSetting extends HookConsumerWidget {
);
}
final String formattedValue = _formatSliderValue(sliderValue.value.toDouble());
final notificationSettings = [
if (!hasPermission)
SettingsButtonListTile(
@@ -49,8 +57,32 @@ class NotificationSetting extends HookConsumerWidget {
}
}),
),
SettingsSliderListTile(
enabled: hasPermission,
valueNotifier: sliderValue,
text: 'setting_notifications_notify_failures_grace_period'.tr(namedArgs: {'duration': formattedValue}),
maxValue: 5.0,
noDivisons: 5,
label: formattedValue,
),
];
return SettingsSubPageScaffold(settings: notificationSettings);
}
}
String _formatSliderValue(double v) {
if (v == 0.0) {
return 'setting_notifications_notify_immediately'.tr();
} else if (v == 1.0) {
return 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '30'});
} else if (v == 2.0) {
return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '2'});
} else if (v == 3.0) {
return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '8'});
} else if (v == 4.0) {
return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '24'});
} else {
return 'setting_notifications_notify_never'.tr();
}
}
+16 -12
View File
@@ -205,8 +205,8 @@ Class | Method | HTTP request | Description
*PeopleApi* | [**updatePeople**](doc//PeopleApi.md#updatepeople) | **PUT** /people | Update people
*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person
*PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin
*PluginsApi* | [**searchPluginMethods**](doc//PluginsApi.md#searchpluginmethods) | **GET** /plugins/methods | Retrieve plugin methods
*PluginsApi* | [**searchPlugins**](doc//PluginsApi.md#searchplugins) | **GET** /plugins | List all plugins
*PluginsApi* | [**getPluginTriggers**](doc//PluginsApi.md#getplugintriggers) | **GET** /plugins/triggers | List all plugin triggers
*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins
*QueuesApi* | [**emptyQueue**](doc//QueuesApi.md#emptyqueue) | **DELETE** /queues/{name}/jobs | Empty a queue
*QueuesApi* | [**getQueue**](doc//QueuesApi.md#getqueue) | **GET** /queues/{name} | Retrieve a queue
*QueuesApi* | [**getQueueJobs**](doc//QueuesApi.md#getqueuejobs) | **GET** /queues/{name}/jobs | Retrieve queue jobs
@@ -314,9 +314,7 @@ Class | Method | HTTP request | Description
*WorkflowsApi* | [**createWorkflow**](doc//WorkflowsApi.md#createworkflow) | **POST** /workflows | Create a workflow
*WorkflowsApi* | [**deleteWorkflow**](doc//WorkflowsApi.md#deleteworkflow) | **DELETE** /workflows/{id} | Delete a workflow
*WorkflowsApi* | [**getWorkflow**](doc//WorkflowsApi.md#getworkflow) | **GET** /workflows/{id} | Retrieve a workflow
*WorkflowsApi* | [**getWorkflowForShare**](doc//WorkflowsApi.md#getworkflowforshare) | **GET** /workflows/{id}/share | Retrieve a workflow
*WorkflowsApi* | [**getWorkflowTriggers**](doc//WorkflowsApi.md#getworkflowtriggers) | **GET** /workflows/triggers | List all workflow triggers
*WorkflowsApi* | [**searchWorkflows**](doc//WorkflowsApi.md#searchworkflows) | **GET** /workflows | List all workflows
*WorkflowsApi* | [**getWorkflows**](doc//WorkflowsApi.md#getworkflows) | **GET** /workflows | List all workflows
*WorkflowsApi* | [**updateWorkflow**](doc//WorkflowsApi.md#updateworkflow) | **PUT** /workflows/{id} | Update a workflow
@@ -489,8 +487,16 @@ Class | Method | HTTP request | Description
- [PinCodeResetDto](doc//PinCodeResetDto.md)
- [PinCodeSetupDto](doc//PinCodeSetupDto.md)
- [PlacesResponseDto](doc//PlacesResponseDto.md)
- [PluginMethodResponseDto](doc//PluginMethodResponseDto.md)
- [PluginActionResponseDto](doc//PluginActionResponseDto.md)
- [PluginContextType](doc//PluginContextType.md)
- [PluginFilterResponseDto](doc//PluginFilterResponseDto.md)
- [PluginJsonSchema](doc//PluginJsonSchema.md)
- [PluginJsonSchemaProperty](doc//PluginJsonSchemaProperty.md)
- [PluginJsonSchemaPropertyAdditionalProperties](doc//PluginJsonSchemaPropertyAdditionalProperties.md)
- [PluginJsonSchemaType](doc//PluginJsonSchemaType.md)
- [PluginResponseDto](doc//PluginResponseDto.md)
- [PluginTriggerResponseDto](doc//PluginTriggerResponseDto.md)
- [PluginTriggerType](doc//PluginTriggerType.md)
- [PurchaseResponse](doc//PurchaseResponse.md)
- [PurchaseUpdate](doc//PurchaseUpdate.md)
- [QueueCommand](doc//QueueCommand.md)
@@ -663,14 +669,12 @@ Class | Method | HTTP request | Description
- [VersionCheckStateResponseDto](doc//VersionCheckStateResponseDto.md)
- [VideoCodec](doc//VideoCodec.md)
- [VideoContainer](doc//VideoContainer.md)
- [WorkflowActionItemDto](doc//WorkflowActionItemDto.md)
- [WorkflowActionResponseDto](doc//WorkflowActionResponseDto.md)
- [WorkflowCreateDto](doc//WorkflowCreateDto.md)
- [WorkflowFilterItemDto](doc//WorkflowFilterItemDto.md)
- [WorkflowFilterResponseDto](doc//WorkflowFilterResponseDto.md)
- [WorkflowResponseDto](doc//WorkflowResponseDto.md)
- [WorkflowShareResponseDto](doc//WorkflowShareResponseDto.md)
- [WorkflowShareStepDto](doc//WorkflowShareStepDto.md)
- [WorkflowStepDto](doc//WorkflowStepDto.md)
- [WorkflowTrigger](doc//WorkflowTrigger.md)
- [WorkflowTriggerResponseDto](doc//WorkflowTriggerResponseDto.md)
- [WorkflowType](doc//WorkflowType.md)
- [WorkflowUpdateDto](doc//WorkflowUpdateDto.md)
+13 -7
View File
@@ -235,8 +235,16 @@ part 'model/pin_code_change_dto.dart';
part 'model/pin_code_reset_dto.dart';
part 'model/pin_code_setup_dto.dart';
part 'model/places_response_dto.dart';
part 'model/plugin_method_response_dto.dart';
part 'model/plugin_action_response_dto.dart';
part 'model/plugin_context_type.dart';
part 'model/plugin_filter_response_dto.dart';
part 'model/plugin_json_schema.dart';
part 'model/plugin_json_schema_property.dart';
part 'model/plugin_json_schema_property_additional_properties.dart';
part 'model/plugin_json_schema_type.dart';
part 'model/plugin_response_dto.dart';
part 'model/plugin_trigger_response_dto.dart';
part 'model/plugin_trigger_type.dart';
part 'model/purchase_response.dart';
part 'model/purchase_update.dart';
part 'model/queue_command.dart';
@@ -409,14 +417,12 @@ part 'model/validate_library_response_dto.dart';
part 'model/version_check_state_response_dto.dart';
part 'model/video_codec.dart';
part 'model/video_container.dart';
part 'model/workflow_action_item_dto.dart';
part 'model/workflow_action_response_dto.dart';
part 'model/workflow_create_dto.dart';
part 'model/workflow_filter_item_dto.dart';
part 'model/workflow_filter_response_dto.dart';
part 'model/workflow_response_dto.dart';
part 'model/workflow_share_response_dto.dart';
part 'model/workflow_share_step_dto.dart';
part 'model/workflow_step_dto.dart';
part 'model/workflow_trigger.dart';
part 'model/workflow_trigger_response_dto.dart';
part 'model/workflow_type.dart';
part 'model/workflow_update_dto.dart';
+13 -144
View File
@@ -73,40 +73,14 @@ class PluginsApi {
return null;
}
/// Retrieve plugin methods
/// List all plugin triggers
///
/// Retrieve a list of plugin methods
/// Retrieve a list of all available plugin triggers.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] description:
///
/// * [bool] enabled:
/// Whether the plugin method is enabled
///
/// * [String] id:
/// Plugin method ID
///
/// * [String] name:
///
/// * [String] pluginName:
/// Plugin name
///
/// * [String] pluginVersion:
/// Plugin version
///
/// * [String] title:
///
/// * [WorkflowTrigger] trigger:
/// Workflow trigger
///
/// * [WorkflowType] type:
/// Workflow types
Future<Response> searchPluginMethodsWithHttpInfo({ String? description, bool? enabled, String? id, String? name, String? pluginName, String? pluginVersion, String? title, WorkflowTrigger? trigger, WorkflowType? type, }) async {
Future<Response> getPluginTriggersWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/plugins/methods';
final apiPath = r'/plugins/triggers';
// ignore: prefer_final_locals
Object? postBody;
@@ -115,34 +89,6 @@ class PluginsApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (description != null) {
queryParams.addAll(_queryParams('', 'description', description));
}
if (enabled != null) {
queryParams.addAll(_queryParams('', 'enabled', enabled));
}
if (id != null) {
queryParams.addAll(_queryParams('', 'id', id));
}
if (name != null) {
queryParams.addAll(_queryParams('', 'name', name));
}
if (pluginName != null) {
queryParams.addAll(_queryParams('', 'pluginName', pluginName));
}
if (pluginVersion != null) {
queryParams.addAll(_queryParams('', 'pluginVersion', pluginVersion));
}
if (title != null) {
queryParams.addAll(_queryParams('', 'title', title));
}
if (trigger != null) {
queryParams.addAll(_queryParams('', 'trigger', trigger));
}
if (type != null) {
queryParams.addAll(_queryParams('', 'type', type));
}
const contentTypes = <String>[];
@@ -157,37 +103,11 @@ class PluginsApi {
);
}
/// Retrieve plugin methods
/// List all plugin triggers
///
/// Retrieve a list of plugin methods
///
/// Parameters:
///
/// * [String] description:
///
/// * [bool] enabled:
/// Whether the plugin method is enabled
///
/// * [String] id:
/// Plugin method ID
///
/// * [String] name:
///
/// * [String] pluginName:
/// Plugin name
///
/// * [String] pluginVersion:
/// Plugin version
///
/// * [String] title:
///
/// * [WorkflowTrigger] trigger:
/// Workflow trigger
///
/// * [WorkflowType] type:
/// Workflow types
Future<List<PluginMethodResponseDto>?> searchPluginMethods({ String? description, bool? enabled, String? id, String? name, String? pluginName, String? pluginVersion, String? title, WorkflowTrigger? trigger, WorkflowType? type, }) async {
final response = await searchPluginMethodsWithHttpInfo( description: description, enabled: enabled, id: id, name: name, pluginName: pluginName, pluginVersion: pluginVersion, title: title, trigger: trigger, type: type, );
/// Retrieve a list of all available plugin triggers.
Future<List<PluginTriggerResponseDto>?> getPluginTriggers() async {
final response = await getPluginTriggersWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -196,8 +116,8 @@ class PluginsApi {
// 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<PluginMethodResponseDto>') as List)
.cast<PluginMethodResponseDto>()
return (await apiClient.deserializeAsync(responseBody, 'List<PluginTriggerResponseDto>') as List)
.cast<PluginTriggerResponseDto>()
.toList(growable: false);
}
@@ -209,23 +129,7 @@ class PluginsApi {
/// Retrieve a list of plugins available to the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] description:
///
/// * [bool] enabled:
/// Whether the plugin is enabled
///
/// * [String] id:
/// Plugin ID
///
/// * [String] name:
///
/// * [String] title:
///
/// * [String] version:
Future<Response> searchPluginsWithHttpInfo({ String? description, bool? enabled, String? id, String? name, String? title, String? version, }) async {
Future<Response> getPluginsWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/plugins';
@@ -236,25 +140,6 @@ class PluginsApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (description != null) {
queryParams.addAll(_queryParams('', 'description', description));
}
if (enabled != null) {
queryParams.addAll(_queryParams('', 'enabled', enabled));
}
if (id != null) {
queryParams.addAll(_queryParams('', 'id', id));
}
if (name != null) {
queryParams.addAll(_queryParams('', 'name', name));
}
if (title != null) {
queryParams.addAll(_queryParams('', 'title', title));
}
if (version != null) {
queryParams.addAll(_queryParams('', 'version', version));
}
const contentTypes = <String>[];
@@ -272,24 +157,8 @@ class PluginsApi {
/// List all plugins
///
/// Retrieve a list of plugins available to the authenticated user.
///
/// Parameters:
///
/// * [String] description:
///
/// * [bool] enabled:
/// Whether the plugin is enabled
///
/// * [String] id:
/// Plugin ID
///
/// * [String] name:
///
/// * [String] title:
///
/// * [String] version:
Future<List<PluginResponseDto>?> searchPlugins({ String? description, bool? enabled, String? id, String? name, String? title, String? version, }) async {
final response = await searchPluginsWithHttpInfo( description: description, enabled: enabled, id: id, name: name, title: title, version: version, );
Future<List<PluginResponseDto>?> getPlugins() async {
final response = await getPluginsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
+3 -161
View File
@@ -178,137 +178,12 @@ class WorkflowsApi {
return null;
}
/// Retrieve a workflow
///
/// Retrieve a workflow details without ids, default values, etc.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
Future<Response> getWorkflowForShareWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final apiPath = r'/workflows/{id}/share'
.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,
);
}
/// Retrieve a workflow
///
/// Retrieve a workflow details without ids, default values, etc.
///
/// Parameters:
///
/// * [String] id (required):
Future<WorkflowShareResponseDto?> getWorkflowForShare(String id,) async {
final response = await getWorkflowForShareWithHttpInfo(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), 'WorkflowShareResponseDto',) as WorkflowShareResponseDto;
}
return null;
}
/// List all workflow triggers
///
/// Retrieve a list of all available workflow triggers.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getWorkflowTriggersWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/workflows/triggers';
// 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,
);
}
/// List all workflow triggers
///
/// Retrieve a list of all available workflow triggers.
Future<List<WorkflowTriggerResponseDto>?> getWorkflowTriggers() async {
final response = await getWorkflowTriggersWithHttpInfo();
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<WorkflowTriggerResponseDto>') as List)
.cast<WorkflowTriggerResponseDto>()
.toList(growable: false);
}
return null;
}
/// List all workflows
///
/// Retrieve a list of workflows available to the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] description:
/// Workflow description
///
/// * [bool] enabled:
/// Workflow enabled
///
/// * [String] id:
/// Workflow ID
///
/// * [String] name:
/// Workflow name
///
/// * [WorkflowTrigger] trigger:
/// Workflow trigger type
Future<Response> searchWorkflowsWithHttpInfo({ String? description, bool? enabled, String? id, String? name, WorkflowTrigger? trigger, }) async {
Future<Response> getWorkflowsWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/workflows';
@@ -319,22 +194,6 @@ class WorkflowsApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (description != null) {
queryParams.addAll(_queryParams('', 'description', description));
}
if (enabled != null) {
queryParams.addAll(_queryParams('', 'enabled', enabled));
}
if (id != null) {
queryParams.addAll(_queryParams('', 'id', id));
}
if (name != null) {
queryParams.addAll(_queryParams('', 'name', name));
}
if (trigger != null) {
queryParams.addAll(_queryParams('', 'trigger', trigger));
}
const contentTypes = <String>[];
@@ -352,25 +211,8 @@ class WorkflowsApi {
/// List all workflows
///
/// Retrieve a list of workflows available to the authenticated user.
///
/// Parameters:
///
/// * [String] description:
/// Workflow description
///
/// * [bool] enabled:
/// Workflow enabled
///
/// * [String] id:
/// Workflow ID
///
/// * [String] name:
/// Workflow name
///
/// * [WorkflowTrigger] trigger:
/// Workflow trigger type
Future<List<WorkflowResponseDto>?> searchWorkflows({ String? description, bool? enabled, String? id, String? name, WorkflowTrigger? trigger, }) async {
final response = await searchWorkflowsWithHttpInfo( description: description, enabled: enabled, id: id, name: name, trigger: trigger, );
Future<List<WorkflowResponseDto>?> getWorkflows() async {
final response = await getWorkflowsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
+26 -14
View File
@@ -516,10 +516,26 @@ class ApiClient {
return PinCodeSetupDto.fromJson(value);
case 'PlacesResponseDto':
return PlacesResponseDto.fromJson(value);
case 'PluginMethodResponseDto':
return PluginMethodResponseDto.fromJson(value);
case 'PluginActionResponseDto':
return PluginActionResponseDto.fromJson(value);
case 'PluginContextType':
return PluginContextTypeTypeTransformer().decode(value);
case 'PluginFilterResponseDto':
return PluginFilterResponseDto.fromJson(value);
case 'PluginJsonSchema':
return PluginJsonSchema.fromJson(value);
case 'PluginJsonSchemaProperty':
return PluginJsonSchemaProperty.fromJson(value);
case 'PluginJsonSchemaPropertyAdditionalProperties':
return PluginJsonSchemaPropertyAdditionalProperties.fromJson(value);
case 'PluginJsonSchemaType':
return PluginJsonSchemaTypeTypeTransformer().decode(value);
case 'PluginResponseDto':
return PluginResponseDto.fromJson(value);
case 'PluginTriggerResponseDto':
return PluginTriggerResponseDto.fromJson(value);
case 'PluginTriggerType':
return PluginTriggerTypeTypeTransformer().decode(value);
case 'PurchaseResponse':
return PurchaseResponse.fromJson(value);
case 'PurchaseUpdate':
@@ -864,22 +880,18 @@ class ApiClient {
return VideoCodecTypeTransformer().decode(value);
case 'VideoContainer':
return VideoContainerTypeTransformer().decode(value);
case 'WorkflowActionItemDto':
return WorkflowActionItemDto.fromJson(value);
case 'WorkflowActionResponseDto':
return WorkflowActionResponseDto.fromJson(value);
case 'WorkflowCreateDto':
return WorkflowCreateDto.fromJson(value);
case 'WorkflowFilterItemDto':
return WorkflowFilterItemDto.fromJson(value);
case 'WorkflowFilterResponseDto':
return WorkflowFilterResponseDto.fromJson(value);
case 'WorkflowResponseDto':
return WorkflowResponseDto.fromJson(value);
case 'WorkflowShareResponseDto':
return WorkflowShareResponseDto.fromJson(value);
case 'WorkflowShareStepDto':
return WorkflowShareStepDto.fromJson(value);
case 'WorkflowStepDto':
return WorkflowStepDto.fromJson(value);
case 'WorkflowTrigger':
return WorkflowTriggerTypeTransformer().decode(value);
case 'WorkflowTriggerResponseDto':
return WorkflowTriggerResponseDto.fromJson(value);
case 'WorkflowType':
return WorkflowTypeTypeTransformer().decode(value);
case 'WorkflowUpdateDto':
return WorkflowUpdateDto.fromJson(value);
default:
+9 -6
View File
@@ -142,6 +142,15 @@ String parameterToString(dynamic value) {
if (value is Permission) {
return PermissionTypeTransformer().encode(value).toString();
}
if (value is PluginContextType) {
return PluginContextTypeTypeTransformer().encode(value).toString();
}
if (value is PluginJsonSchemaType) {
return PluginJsonSchemaTypeTypeTransformer().encode(value).toString();
}
if (value is PluginTriggerType) {
return PluginTriggerTypeTypeTransformer().encode(value).toString();
}
if (value is QueueCommand) {
return QueueCommandTypeTransformer().encode(value).toString();
}
@@ -199,12 +208,6 @@ String parameterToString(dynamic value) {
if (value is VideoContainer) {
return VideoContainerTypeTransformer().encode(value).toString();
}
if (value is WorkflowTrigger) {
return WorkflowTriggerTypeTransformer().encode(value).toString();
}
if (value is WorkflowType) {
return WorkflowTypeTypeTransformer().encode(value).toString();
}
return value.toString();
}
+3 -3
View File
@@ -77,7 +77,7 @@ class JobName {
static const versionCheck = JobName._(r'VersionCheck');
static const ocrQueueAll = JobName._(r'OcrQueueAll');
static const ocr = JobName._(r'Ocr');
static const workflowAssetCreate = JobName._(r'WorkflowAssetCreate');
static const workflowRun = JobName._(r'WorkflowRun');
/// List of all possible values in this [enum][JobName].
static const values = <JobName>[
@@ -135,7 +135,7 @@ class JobName {
versionCheck,
ocrQueueAll,
ocr,
workflowAssetCreate,
workflowRun,
];
static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value);
@@ -228,7 +228,7 @@ class JobNameTypeTransformer {
case r'VersionCheck': return JobName.versionCheck;
case r'OcrQueueAll': return JobName.ocrQueueAll;
case r'Ocr': return JobName.ocr;
case r'WorkflowAssetCreate': return JobName.workflowAssetCreate;
case r'WorkflowRun': return JobName.workflowRun;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
+158
View File
@@ -0,0 +1,158 @@
//
// 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 PluginActionResponseDto {
/// Returns a new [PluginActionResponseDto] instance.
PluginActionResponseDto({
required this.description,
required this.id,
required this.methodName,
required this.pluginId,
required this.schema,
this.supportedContexts = const [],
required this.title,
});
/// Action description
String description;
/// Action ID
String id;
/// Method name
String methodName;
/// Plugin ID
String pluginId;
/// Action schema
PluginJsonSchema? schema;
/// Supported contexts
List<PluginContextType> supportedContexts;
/// Action title
String title;
@override
bool operator ==(Object other) => identical(this, other) || other is PluginActionResponseDto &&
other.description == description &&
other.id == id &&
other.methodName == methodName &&
other.pluginId == pluginId &&
other.schema == schema &&
_deepEquality.equals(other.supportedContexts, supportedContexts) &&
other.title == title;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(description.hashCode) +
(id.hashCode) +
(methodName.hashCode) +
(pluginId.hashCode) +
(schema == null ? 0 : schema!.hashCode) +
(supportedContexts.hashCode) +
(title.hashCode);
@override
String toString() => 'PluginActionResponseDto[description=$description, id=$id, methodName=$methodName, pluginId=$pluginId, schema=$schema, supportedContexts=$supportedContexts, title=$title]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'description'] = this.description;
json[r'id'] = this.id;
json[r'methodName'] = this.methodName;
json[r'pluginId'] = this.pluginId;
if (this.schema != null) {
json[r'schema'] = this.schema;
} else {
// json[r'schema'] = null;
}
json[r'supportedContexts'] = this.supportedContexts;
json[r'title'] = this.title;
return json;
}
/// Returns a new [PluginActionResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static PluginActionResponseDto? fromJson(dynamic value) {
upgradeDto(value, "PluginActionResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return PluginActionResponseDto(
description: mapValueOfType<String>(json, r'description')!,
id: mapValueOfType<String>(json, r'id')!,
methodName: mapValueOfType<String>(json, r'methodName')!,
pluginId: mapValueOfType<String>(json, r'pluginId')!,
schema: PluginJsonSchema.fromJson(json[r'schema']),
supportedContexts: PluginContextType.listFromJson(json[r'supportedContexts']),
title: mapValueOfType<String>(json, r'title')!,
);
}
return null;
}
static List<PluginActionResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginActionResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PluginActionResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, PluginActionResponseDto> mapFromJson(dynamic json) {
final map = <String, PluginActionResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = PluginActionResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of PluginActionResponseDto-objects as value to a dart map
static Map<String, List<PluginActionResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<PluginActionResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = PluginActionResponseDto.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',
'methodName',
'pluginId',
'schema',
'supportedContexts',
'title',
};
}
+88
View File
@@ -0,0 +1,88 @@
//
// 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;
/// Plugin context
class PluginContextType {
/// Instantiate a new enum with the provided [value].
const PluginContextType._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const asset = PluginContextType._(r'asset');
static const album = PluginContextType._(r'album');
static const person = PluginContextType._(r'person');
/// List of all possible values in this [enum][PluginContextType].
static const values = <PluginContextType>[
asset,
album,
person,
];
static PluginContextType? fromJson(dynamic value) => PluginContextTypeTypeTransformer().decode(value);
static List<PluginContextType> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginContextType>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PluginContextType.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [PluginContextType] to String,
/// and [decode] dynamic data back to [PluginContextType].
class PluginContextTypeTypeTransformer {
factory PluginContextTypeTypeTransformer() => _instance ??= const PluginContextTypeTypeTransformer._();
const PluginContextTypeTypeTransformer._();
String encode(PluginContextType data) => data.value;
/// Decodes a [dynamic value][data] to a PluginContextType.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
PluginContextType? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'asset': return PluginContextType.asset;
case r'album': return PluginContextType.album;
case r'person': return PluginContextType.person;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [PluginContextTypeTypeTransformer] instance.
static PluginContextTypeTypeTransformer? _instance;
}
+158
View File
@@ -0,0 +1,158 @@
//
// 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 PluginFilterResponseDto {
/// Returns a new [PluginFilterResponseDto] instance.
PluginFilterResponseDto({
required this.description,
required this.id,
required this.methodName,
required this.pluginId,
required this.schema,
this.supportedContexts = const [],
required this.title,
});
/// Filter description
String description;
/// Filter ID
String id;
/// Method name
String methodName;
/// Plugin ID
String pluginId;
/// Filter schema
PluginJsonSchema? schema;
/// Supported contexts
List<PluginContextType> supportedContexts;
/// Filter title
String title;
@override
bool operator ==(Object other) => identical(this, other) || other is PluginFilterResponseDto &&
other.description == description &&
other.id == id &&
other.methodName == methodName &&
other.pluginId == pluginId &&
other.schema == schema &&
_deepEquality.equals(other.supportedContexts, supportedContexts) &&
other.title == title;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(description.hashCode) +
(id.hashCode) +
(methodName.hashCode) +
(pluginId.hashCode) +
(schema == null ? 0 : schema!.hashCode) +
(supportedContexts.hashCode) +
(title.hashCode);
@override
String toString() => 'PluginFilterResponseDto[description=$description, id=$id, methodName=$methodName, pluginId=$pluginId, schema=$schema, supportedContexts=$supportedContexts, title=$title]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'description'] = this.description;
json[r'id'] = this.id;
json[r'methodName'] = this.methodName;
json[r'pluginId'] = this.pluginId;
if (this.schema != null) {
json[r'schema'] = this.schema;
} else {
// json[r'schema'] = null;
}
json[r'supportedContexts'] = this.supportedContexts;
json[r'title'] = this.title;
return json;
}
/// Returns a new [PluginFilterResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static PluginFilterResponseDto? fromJson(dynamic value) {
upgradeDto(value, "PluginFilterResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return PluginFilterResponseDto(
description: mapValueOfType<String>(json, r'description')!,
id: mapValueOfType<String>(json, r'id')!,
methodName: mapValueOfType<String>(json, r'methodName')!,
pluginId: mapValueOfType<String>(json, r'pluginId')!,
schema: PluginJsonSchema.fromJson(json[r'schema']),
supportedContexts: PluginContextType.listFromJson(json[r'supportedContexts']),
title: mapValueOfType<String>(json, r'title')!,
);
}
return null;
}
static List<PluginFilterResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginFilterResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PluginFilterResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, PluginFilterResponseDto> mapFromJson(dynamic json) {
final map = <String, PluginFilterResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = PluginFilterResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of PluginFilterResponseDto-objects as value to a dart map
static Map<String, List<PluginFilterResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<PluginFilterResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = PluginFilterResponseDto.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',
'methodName',
'pluginId',
'schema',
'supportedContexts',
'title',
};
}
+158
View File
@@ -0,0 +1,158 @@
//
// 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 PluginJsonSchema {
/// Returns a new [PluginJsonSchema] instance.
PluginJsonSchema({
this.additionalProperties,
this.description,
this.properties = const {},
this.required_ = const [],
this.type,
});
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? additionalProperties;
///
/// 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? description;
Map<String, PluginJsonSchemaProperty> properties;
List<String> required_;
///
/// 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.
///
PluginJsonSchemaType? type;
@override
bool operator ==(Object other) => identical(this, other) || other is PluginJsonSchema &&
other.additionalProperties == additionalProperties &&
other.description == description &&
_deepEquality.equals(other.properties, properties) &&
_deepEquality.equals(other.required_, required_) &&
other.type == type;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(additionalProperties == null ? 0 : additionalProperties!.hashCode) +
(description == null ? 0 : description!.hashCode) +
(properties.hashCode) +
(required_.hashCode) +
(type == null ? 0 : type!.hashCode);
@override
String toString() => 'PluginJsonSchema[additionalProperties=$additionalProperties, description=$description, properties=$properties, required_=$required_, type=$type]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.additionalProperties != null) {
json[r'additionalProperties'] = this.additionalProperties;
} else {
// json[r'additionalProperties'] = null;
}
if (this.description != null) {
json[r'description'] = this.description;
} else {
// json[r'description'] = null;
}
json[r'properties'] = this.properties;
json[r'required'] = this.required_;
if (this.type != null) {
json[r'type'] = this.type;
} else {
// json[r'type'] = null;
}
return json;
}
/// Returns a new [PluginJsonSchema] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static PluginJsonSchema? fromJson(dynamic value) {
upgradeDto(value, "PluginJsonSchema");
if (value is Map) {
final json = value.cast<String, dynamic>();
return PluginJsonSchema(
additionalProperties: mapValueOfType<bool>(json, r'additionalProperties'),
description: mapValueOfType<String>(json, r'description'),
properties: PluginJsonSchemaProperty.mapFromJson(json[r'properties']),
required_: json[r'required'] is Iterable
? (json[r'required'] as Iterable).cast<String>().toList(growable: false)
: const [],
type: PluginJsonSchemaType.fromJson(json[r'type']),
);
}
return null;
}
static List<PluginJsonSchema> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginJsonSchema>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PluginJsonSchema.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, PluginJsonSchema> mapFromJson(dynamic json) {
final map = <String, PluginJsonSchema>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = PluginJsonSchema.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of PluginJsonSchema-objects as value to a dart map
static Map<String, List<PluginJsonSchema>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<PluginJsonSchema>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = PluginJsonSchema.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}
+195
View File
@@ -0,0 +1,195 @@
//
// 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 PluginJsonSchemaProperty {
/// Returns a new [PluginJsonSchemaProperty] instance.
PluginJsonSchemaProperty({
this.additionalProperties,
this.default_,
this.description,
this.enum_ = const [],
this.items,
this.properties = const {},
this.required_ = const [],
this.type,
});
///
/// 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.
///
PluginJsonSchemaPropertyAdditionalProperties? additionalProperties;
Object? default_;
///
/// 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? description;
List<String> enum_;
///
/// 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.
///
PluginJsonSchemaProperty? items;
Map<String, PluginJsonSchemaProperty> properties;
List<String> required_;
///
/// 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.
///
PluginJsonSchemaType? type;
@override
bool operator ==(Object other) => identical(this, other) || other is PluginJsonSchemaProperty &&
other.additionalProperties == additionalProperties &&
other.default_ == default_ &&
other.description == description &&
_deepEquality.equals(other.enum_, enum_) &&
other.items == items &&
_deepEquality.equals(other.properties, properties) &&
_deepEquality.equals(other.required_, required_) &&
other.type == type;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(additionalProperties == null ? 0 : additionalProperties!.hashCode) +
(default_ == null ? 0 : default_!.hashCode) +
(description == null ? 0 : description!.hashCode) +
(enum_.hashCode) +
(items == null ? 0 : items!.hashCode) +
(properties.hashCode) +
(required_.hashCode) +
(type == null ? 0 : type!.hashCode);
@override
String toString() => 'PluginJsonSchemaProperty[additionalProperties=$additionalProperties, default_=$default_, description=$description, enum_=$enum_, items=$items, properties=$properties, required_=$required_, type=$type]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.additionalProperties != null) {
json[r'additionalProperties'] = this.additionalProperties;
} else {
// json[r'additionalProperties'] = null;
}
if (this.default_ != null) {
json[r'default'] = this.default_;
} else {
// json[r'default'] = null;
}
if (this.description != null) {
json[r'description'] = this.description;
} else {
// json[r'description'] = null;
}
json[r'enum'] = this.enum_;
if (this.items != null) {
json[r'items'] = this.items;
} else {
// json[r'items'] = null;
}
json[r'properties'] = this.properties;
json[r'required'] = this.required_;
if (this.type != null) {
json[r'type'] = this.type;
} else {
// json[r'type'] = null;
}
return json;
}
/// Returns a new [PluginJsonSchemaProperty] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static PluginJsonSchemaProperty? fromJson(dynamic value) {
upgradeDto(value, "PluginJsonSchemaProperty");
if (value is Map) {
final json = value.cast<String, dynamic>();
return PluginJsonSchemaProperty(
additionalProperties: PluginJsonSchemaPropertyAdditionalProperties.fromJson(json[r'additionalProperties']),
default_: mapValueOfType<Object>(json, r'default'),
description: mapValueOfType<String>(json, r'description'),
enum_: json[r'enum'] is Iterable
? (json[r'enum'] as Iterable).cast<String>().toList(growable: false)
: const [],
items: PluginJsonSchemaProperty.fromJson(json[r'items']),
properties: PluginJsonSchemaProperty.mapFromJson(json[r'properties']),
required_: json[r'required'] is Iterable
? (json[r'required'] as Iterable).cast<String>().toList(growable: false)
: const [],
type: PluginJsonSchemaType.fromJson(json[r'type']),
);
}
return null;
}
static List<PluginJsonSchemaProperty> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginJsonSchemaProperty>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PluginJsonSchemaProperty.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, PluginJsonSchemaProperty> mapFromJson(dynamic json) {
final map = <String, PluginJsonSchemaProperty>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = PluginJsonSchemaProperty.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of PluginJsonSchemaProperty-objects as value to a dart map
static Map<String, List<PluginJsonSchemaProperty>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<PluginJsonSchemaProperty>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = PluginJsonSchemaProperty.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}
@@ -0,0 +1,195 @@
//
// 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 PluginJsonSchemaPropertyAdditionalProperties {
/// Returns a new [PluginJsonSchemaPropertyAdditionalProperties] instance.
PluginJsonSchemaPropertyAdditionalProperties({
this.additionalProperties,
this.default_,
this.description,
this.enum_ = const [],
this.items,
this.properties = const {},
this.required_ = const [],
this.type,
});
///
/// 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.
///
PluginJsonSchemaPropertyAdditionalProperties? additionalProperties;
Object? default_;
///
/// 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? description;
List<String> enum_;
///
/// 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.
///
PluginJsonSchemaProperty? items;
Map<String, PluginJsonSchemaProperty> properties;
List<String> required_;
///
/// 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.
///
PluginJsonSchemaType? type;
@override
bool operator ==(Object other) => identical(this, other) || other is PluginJsonSchemaPropertyAdditionalProperties &&
other.additionalProperties == additionalProperties &&
other.default_ == default_ &&
other.description == description &&
_deepEquality.equals(other.enum_, enum_) &&
other.items == items &&
_deepEquality.equals(other.properties, properties) &&
_deepEquality.equals(other.required_, required_) &&
other.type == type;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(additionalProperties == null ? 0 : additionalProperties!.hashCode) +
(default_ == null ? 0 : default_!.hashCode) +
(description == null ? 0 : description!.hashCode) +
(enum_.hashCode) +
(items == null ? 0 : items!.hashCode) +
(properties.hashCode) +
(required_.hashCode) +
(type == null ? 0 : type!.hashCode);
@override
String toString() => 'PluginJsonSchemaPropertyAdditionalProperties[additionalProperties=$additionalProperties, default_=$default_, description=$description, enum_=$enum_, items=$items, properties=$properties, required_=$required_, type=$type]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.additionalProperties != null) {
json[r'additionalProperties'] = this.additionalProperties;
} else {
// json[r'additionalProperties'] = null;
}
if (this.default_ != null) {
json[r'default'] = this.default_;
} else {
// json[r'default'] = null;
}
if (this.description != null) {
json[r'description'] = this.description;
} else {
// json[r'description'] = null;
}
json[r'enum'] = this.enum_;
if (this.items != null) {
json[r'items'] = this.items;
} else {
// json[r'items'] = null;
}
json[r'properties'] = this.properties;
json[r'required'] = this.required_;
if (this.type != null) {
json[r'type'] = this.type;
} else {
// json[r'type'] = null;
}
return json;
}
/// Returns a new [PluginJsonSchemaPropertyAdditionalProperties] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static PluginJsonSchemaPropertyAdditionalProperties? fromJson(dynamic value) {
upgradeDto(value, "PluginJsonSchemaPropertyAdditionalProperties");
if (value is Map) {
final json = value.cast<String, dynamic>();
return PluginJsonSchemaPropertyAdditionalProperties(
additionalProperties: PluginJsonSchemaPropertyAdditionalProperties.fromJson(json[r'additionalProperties']),
default_: mapValueOfType<Object>(json, r'default'),
description: mapValueOfType<String>(json, r'description'),
enum_: json[r'enum'] is Iterable
? (json[r'enum'] as Iterable).cast<String>().toList(growable: false)
: const [],
items: PluginJsonSchemaProperty.fromJson(json[r'items']),
properties: PluginJsonSchemaProperty.mapFromJson(json[r'properties']),
required_: json[r'required'] is Iterable
? (json[r'required'] as Iterable).cast<String>().toList(growable: false)
: const [],
type: PluginJsonSchemaType.fromJson(json[r'type']),
);
}
return null;
}
static List<PluginJsonSchemaPropertyAdditionalProperties> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginJsonSchemaPropertyAdditionalProperties>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PluginJsonSchemaPropertyAdditionalProperties.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, PluginJsonSchemaPropertyAdditionalProperties> mapFromJson(dynamic json) {
final map = <String, PluginJsonSchemaPropertyAdditionalProperties>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = PluginJsonSchemaPropertyAdditionalProperties.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of PluginJsonSchemaPropertyAdditionalProperties-objects as value to a dart map
static Map<String, List<PluginJsonSchemaPropertyAdditionalProperties>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<PluginJsonSchemaPropertyAdditionalProperties>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = PluginJsonSchemaPropertyAdditionalProperties.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}
+100
View File
@@ -0,0 +1,100 @@
//
// 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 PluginJsonSchemaType {
/// Instantiate a new enum with the provided [value].
const PluginJsonSchemaType._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const string = PluginJsonSchemaType._(r'string');
static const number = PluginJsonSchemaType._(r'number');
static const integer = PluginJsonSchemaType._(r'integer');
static const boolean = PluginJsonSchemaType._(r'boolean');
static const object = PluginJsonSchemaType._(r'object');
static const array = PluginJsonSchemaType._(r'array');
static const null_ = PluginJsonSchemaType._(r'null');
/// List of all possible values in this [enum][PluginJsonSchemaType].
static const values = <PluginJsonSchemaType>[
string,
number,
integer,
boolean,
object,
array,
null_,
];
static PluginJsonSchemaType? fromJson(dynamic value) => PluginJsonSchemaTypeTypeTransformer().decode(value);
static List<PluginJsonSchemaType> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginJsonSchemaType>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PluginJsonSchemaType.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [PluginJsonSchemaType] to String,
/// and [decode] dynamic data back to [PluginJsonSchemaType].
class PluginJsonSchemaTypeTypeTransformer {
factory PluginJsonSchemaTypeTypeTransformer() => _instance ??= const PluginJsonSchemaTypeTypeTransformer._();
const PluginJsonSchemaTypeTypeTransformer._();
String encode(PluginJsonSchemaType data) => data.value;
/// Decodes a [dynamic value][data] to a PluginJsonSchemaType.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
PluginJsonSchemaType? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'string': return PluginJsonSchemaType.string;
case r'number': return PluginJsonSchemaType.number;
case r'integer': return PluginJsonSchemaType.integer;
case r'boolean': return PluginJsonSchemaType.boolean;
case r'object': return PluginJsonSchemaType.object;
case r'array': return PluginJsonSchemaType.array;
case r'null': return PluginJsonSchemaType.null_;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [PluginJsonSchemaTypeTypeTransformer] instance.
static PluginJsonSchemaTypeTypeTransformer? _instance;
}
-172
View File
@@ -1,172 +0,0 @@
//
// 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 PluginMethodResponseDto {
/// Returns a new [PluginMethodResponseDto] instance.
PluginMethodResponseDto({
required this.description,
required this.hostFunctions,
required this.key,
required this.name,
this.schema,
required this.title,
this.types = const [],
this.uiHints = const [],
});
/// Description
String description;
bool hostFunctions;
/// Key
String key;
/// Name
String name;
///
/// 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.
///
Object? schema;
/// Title
String title;
/// Workflow types
List<WorkflowType> types;
/// Ui hints
List<String> uiHints;
@override
bool operator ==(Object other) => identical(this, other) || other is PluginMethodResponseDto &&
other.description == description &&
other.hostFunctions == hostFunctions &&
other.key == key &&
other.name == name &&
other.schema == schema &&
other.title == title &&
_deepEquality.equals(other.types, types) &&
_deepEquality.equals(other.uiHints, uiHints);
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(description.hashCode) +
(hostFunctions.hashCode) +
(key.hashCode) +
(name.hashCode) +
(schema == null ? 0 : schema!.hashCode) +
(title.hashCode) +
(types.hashCode) +
(uiHints.hashCode);
@override
String toString() => 'PluginMethodResponseDto[description=$description, hostFunctions=$hostFunctions, key=$key, name=$name, schema=$schema, title=$title, types=$types, uiHints=$uiHints]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'description'] = this.description;
json[r'hostFunctions'] = this.hostFunctions;
json[r'key'] = this.key;
json[r'name'] = this.name;
if (this.schema != null) {
json[r'schema'] = this.schema;
} else {
// json[r'schema'] = null;
}
json[r'title'] = this.title;
json[r'types'] = this.types;
json[r'uiHints'] = this.uiHints;
return json;
}
/// Returns a new [PluginMethodResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static PluginMethodResponseDto? fromJson(dynamic value) {
upgradeDto(value, "PluginMethodResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return PluginMethodResponseDto(
description: mapValueOfType<String>(json, r'description')!,
hostFunctions: mapValueOfType<bool>(json, r'hostFunctions')!,
key: mapValueOfType<String>(json, r'key')!,
name: mapValueOfType<String>(json, r'name')!,
schema: mapValueOfType<Object>(json, r'schema'),
title: mapValueOfType<String>(json, r'title')!,
types: WorkflowType.listFromJson(json[r'types']),
uiHints: json[r'uiHints'] is Iterable
? (json[r'uiHints'] as Iterable).cast<String>().toList(growable: false)
: const [],
);
}
return null;
}
static List<PluginMethodResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginMethodResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PluginMethodResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, PluginMethodResponseDto> mapFromJson(dynamic json) {
final map = <String, PluginMethodResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = PluginMethodResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of PluginMethodResponseDto-objects as value to a dart map
static Map<String, List<PluginMethodResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<PluginMethodResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = PluginMethodResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'description',
'hostFunctions',
'key',
'name',
'title',
'types',
'uiHints',
};
}
+19 -10
View File
@@ -13,17 +13,21 @@ part of openapi.api;
class PluginResponseDto {
/// Returns a new [PluginResponseDto] instance.
PluginResponseDto({
this.actions = const [],
required this.author,
required this.createdAt,
required this.description,
this.filters = const [],
required this.id,
this.methods = const [],
required this.name,
required this.title,
required this.updatedAt,
required this.version,
});
/// Plugin actions
List<PluginActionResponseDto> actions;
/// Plugin author
String author;
@@ -33,12 +37,12 @@ class PluginResponseDto {
/// Plugin description
String description;
/// Plugin filters
List<PluginFilterResponseDto> filters;
/// Plugin ID
String id;
/// Plugin methods
List<PluginMethodResponseDto> methods;
/// Plugin name
String name;
@@ -53,11 +57,12 @@ class PluginResponseDto {
@override
bool operator ==(Object other) => identical(this, other) || other is PluginResponseDto &&
_deepEquality.equals(other.actions, actions) &&
other.author == author &&
other.createdAt == createdAt &&
other.description == description &&
_deepEquality.equals(other.filters, filters) &&
other.id == id &&
_deepEquality.equals(other.methods, methods) &&
other.name == name &&
other.title == title &&
other.updatedAt == updatedAt &&
@@ -66,26 +71,28 @@ class PluginResponseDto {
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(actions.hashCode) +
(author.hashCode) +
(createdAt.hashCode) +
(description.hashCode) +
(filters.hashCode) +
(id.hashCode) +
(methods.hashCode) +
(name.hashCode) +
(title.hashCode) +
(updatedAt.hashCode) +
(version.hashCode);
@override
String toString() => 'PluginResponseDto[author=$author, createdAt=$createdAt, description=$description, id=$id, methods=$methods, name=$name, title=$title, updatedAt=$updatedAt, version=$version]';
String toString() => 'PluginResponseDto[actions=$actions, author=$author, createdAt=$createdAt, description=$description, filters=$filters, id=$id, name=$name, title=$title, updatedAt=$updatedAt, version=$version]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'actions'] = this.actions;
json[r'author'] = this.author;
json[r'createdAt'] = this.createdAt;
json[r'description'] = this.description;
json[r'filters'] = this.filters;
json[r'id'] = this.id;
json[r'methods'] = this.methods;
json[r'name'] = this.name;
json[r'title'] = this.title;
json[r'updatedAt'] = this.updatedAt;
@@ -102,11 +109,12 @@ class PluginResponseDto {
final json = value.cast<String, dynamic>();
return PluginResponseDto(
actions: PluginActionResponseDto.listFromJson(json[r'actions']),
author: mapValueOfType<String>(json, r'author')!,
createdAt: mapValueOfType<String>(json, r'createdAt')!,
description: mapValueOfType<String>(json, r'description')!,
filters: PluginFilterResponseDto.listFromJson(json[r'filters']),
id: mapValueOfType<String>(json, r'id')!,
methods: PluginMethodResponseDto.listFromJson(json[r'methods']),
name: mapValueOfType<String>(json, r'name')!,
title: mapValueOfType<String>(json, r'title')!,
updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
@@ -158,11 +166,12 @@ class PluginResponseDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'actions',
'author',
'createdAt',
'description',
'filters',
'id',
'methods',
'name',
'title',
'updatedAt',
+107
View 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 PluginTriggerResponseDto {
/// Returns a new [PluginTriggerResponseDto] instance.
PluginTriggerResponseDto({
required this.contextType,
required this.type,
});
PluginContextType contextType;
PluginTriggerType type;
@override
bool operator ==(Object other) => identical(this, other) || other is PluginTriggerResponseDto &&
other.contextType == contextType &&
other.type == type;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(contextType.hashCode) +
(type.hashCode);
@override
String toString() => 'PluginTriggerResponseDto[contextType=$contextType, type=$type]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'contextType'] = this.contextType;
json[r'type'] = this.type;
return json;
}
/// Returns a new [PluginTriggerResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static PluginTriggerResponseDto? fromJson(dynamic value) {
upgradeDto(value, "PluginTriggerResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return PluginTriggerResponseDto(
contextType: PluginContextType.fromJson(json[r'contextType'])!,
type: PluginTriggerType.fromJson(json[r'type'])!,
);
}
return null;
}
static List<PluginTriggerResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginTriggerResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PluginTriggerResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, PluginTriggerResponseDto> mapFromJson(dynamic json) {
final map = <String, PluginTriggerResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = PluginTriggerResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of PluginTriggerResponseDto-objects as value to a dart map
static Map<String, List<PluginTriggerResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<PluginTriggerResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = PluginTriggerResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'contextType',
'type',
};
}
@@ -11,9 +11,9 @@
part of openapi.api;
/// Plugin trigger type
class WorkflowTrigger {
class PluginTriggerType {
/// Instantiate a new enum with the provided [value].
const WorkflowTrigger._(this.value);
const PluginTriggerType._(this.value);
/// The underlying value of this enum member.
final String value;
@@ -23,22 +23,22 @@ class WorkflowTrigger {
String toJson() => value;
static const assetCreate = WorkflowTrigger._(r'AssetCreate');
static const personRecognized = WorkflowTrigger._(r'PersonRecognized');
static const assetCreate = PluginTriggerType._(r'AssetCreate');
static const personRecognized = PluginTriggerType._(r'PersonRecognized');
/// List of all possible values in this [enum][WorkflowTrigger].
static const values = <WorkflowTrigger>[
/// List of all possible values in this [enum][PluginTriggerType].
static const values = <PluginTriggerType>[
assetCreate,
personRecognized,
];
static WorkflowTrigger? fromJson(dynamic value) => WorkflowTriggerTypeTransformer().decode(value);
static PluginTriggerType? fromJson(dynamic value) => PluginTriggerTypeTypeTransformer().decode(value);
static List<WorkflowTrigger> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowTrigger>[];
static List<PluginTriggerType> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginTriggerType>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WorkflowTrigger.fromJson(row);
final value = PluginTriggerType.fromJson(row);
if (value != null) {
result.add(value);
}
@@ -48,16 +48,16 @@ class WorkflowTrigger {
}
}
/// Transformation class that can [encode] an instance of [WorkflowTrigger] to String,
/// and [decode] dynamic data back to [WorkflowTrigger].
class WorkflowTriggerTypeTransformer {
factory WorkflowTriggerTypeTransformer() => _instance ??= const WorkflowTriggerTypeTransformer._();
/// Transformation class that can [encode] an instance of [PluginTriggerType] to String,
/// and [decode] dynamic data back to [PluginTriggerType].
class PluginTriggerTypeTypeTransformer {
factory PluginTriggerTypeTypeTransformer() => _instance ??= const PluginTriggerTypeTypeTransformer._();
const WorkflowTriggerTypeTransformer._();
const PluginTriggerTypeTypeTransformer._();
String encode(WorkflowTrigger data) => data.value;
String encode(PluginTriggerType data) => data.value;
/// Decodes a [dynamic value][data] to a WorkflowTrigger.
/// Decodes a [dynamic value][data] to a PluginTriggerType.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -65,11 +65,11 @@ class WorkflowTriggerTypeTransformer {
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
WorkflowTrigger? decode(dynamic data, {bool allowNull = true}) {
PluginTriggerType? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'AssetCreate': return WorkflowTrigger.assetCreate;
case r'PersonRecognized': return WorkflowTrigger.personRecognized;
case r'AssetCreate': return PluginTriggerType.assetCreate;
case r'PersonRecognized': return PluginTriggerType.personRecognized;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
@@ -79,7 +79,7 @@ class WorkflowTriggerTypeTransformer {
return null;
}
/// Singleton [WorkflowTriggerTypeTransformer] instance.
static WorkflowTriggerTypeTransformer? _instance;
/// Singleton [PluginTriggerTypeTypeTransformer] instance.
static PluginTriggerTypeTypeTransformer? _instance;
}
+107
View 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 WorkflowActionItemDto {
/// Returns a new [WorkflowActionItemDto] instance.
WorkflowActionItemDto({
this.actionConfig = const {},
required this.pluginActionId,
});
Map<String, Object> actionConfig;
/// Plugin action ID
String pluginActionId;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowActionItemDto &&
_deepEquality.equals(other.actionConfig, actionConfig) &&
other.pluginActionId == pluginActionId;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(actionConfig.hashCode) +
(pluginActionId.hashCode);
@override
String toString() => 'WorkflowActionItemDto[actionConfig=$actionConfig, pluginActionId=$pluginActionId]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'actionConfig'] = this.actionConfig;
json[r'pluginActionId'] = this.pluginActionId;
return json;
}
/// Returns a new [WorkflowActionItemDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static WorkflowActionItemDto? fromJson(dynamic value) {
upgradeDto(value, "WorkflowActionItemDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return WorkflowActionItemDto(
actionConfig: mapCastOfType<String, Object>(json, r'actionConfig') ?? const {},
pluginActionId: mapValueOfType<String>(json, r'pluginActionId')!,
);
}
return null;
}
static List<WorkflowActionItemDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowActionItemDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WorkflowActionItemDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, WorkflowActionItemDto> mapFromJson(dynamic json) {
final map = <String, WorkflowActionItemDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = WorkflowActionItemDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of WorkflowActionItemDto-objects as value to a dart map
static Map<String, List<WorkflowActionItemDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<WorkflowActionItemDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = WorkflowActionItemDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'pluginActionId',
};
}
+142
View File
@@ -0,0 +1,142 @@
//
// 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 WorkflowActionResponseDto {
/// Returns a new [WorkflowActionResponseDto] instance.
WorkflowActionResponseDto({
required this.actionConfig,
required this.id,
required this.order,
required this.pluginActionId,
required this.workflowId,
});
Map<String, Object>? actionConfig;
/// Action ID
String id;
/// Action order
///
/// Minimum value: -9007199254740991
/// Maximum value: 9007199254740991
int order;
/// Plugin action ID
String pluginActionId;
/// Workflow ID
String workflowId;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowActionResponseDto &&
_deepEquality.equals(other.actionConfig, actionConfig) &&
other.id == id &&
other.order == order &&
other.pluginActionId == pluginActionId &&
other.workflowId == workflowId;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(actionConfig == null ? 0 : actionConfig!.hashCode) +
(id.hashCode) +
(order.hashCode) +
(pluginActionId.hashCode) +
(workflowId.hashCode);
@override
String toString() => 'WorkflowActionResponseDto[actionConfig=$actionConfig, id=$id, order=$order, pluginActionId=$pluginActionId, workflowId=$workflowId]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.actionConfig != null) {
json[r'actionConfig'] = this.actionConfig;
} else {
// json[r'actionConfig'] = null;
}
json[r'id'] = this.id;
json[r'order'] = this.order;
json[r'pluginActionId'] = this.pluginActionId;
json[r'workflowId'] = this.workflowId;
return json;
}
/// Returns a new [WorkflowActionResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static WorkflowActionResponseDto? fromJson(dynamic value) {
upgradeDto(value, "WorkflowActionResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return WorkflowActionResponseDto(
actionConfig: mapCastOfType<String, Object>(json, r'actionConfig'),
id: mapValueOfType<String>(json, r'id')!,
order: mapValueOfType<int>(json, r'order')!,
pluginActionId: mapValueOfType<String>(json, r'pluginActionId')!,
workflowId: mapValueOfType<String>(json, r'workflowId')!,
);
}
return null;
}
static List<WorkflowActionResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowActionResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WorkflowActionResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, WorkflowActionResponseDto> mapFromJson(dynamic json) {
final map = <String, WorkflowActionResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = WorkflowActionResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of WorkflowActionResponseDto-objects as value to a dart map
static Map<String, List<WorkflowActionResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<WorkflowActionResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = WorkflowActionResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'actionConfig',
'id',
'order',
'pluginActionId',
'workflowId',
};
}
+37 -23
View File
@@ -13,14 +13,24 @@ part of openapi.api;
class WorkflowCreateDto {
/// Returns a new [WorkflowCreateDto] instance.
WorkflowCreateDto({
this.actions = const [],
this.description,
this.enabled,
this.name,
this.steps = const [],
required this.trigger,
this.filters = const [],
required this.name,
required this.triggerType,
});
/// Workflow actions
List<WorkflowActionItemDto> actions;
/// Workflow 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? description;
/// Workflow enabled
@@ -32,35 +42,39 @@ class WorkflowCreateDto {
///
bool? enabled;
/// Workflow filters
List<WorkflowFilterItemDto> filters;
/// Workflow name
String? name;
String name;
List<WorkflowStepDto> steps;
WorkflowTrigger trigger;
PluginTriggerType triggerType;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowCreateDto &&
_deepEquality.equals(other.actions, actions) &&
other.description == description &&
other.enabled == enabled &&
_deepEquality.equals(other.filters, filters) &&
other.name == name &&
_deepEquality.equals(other.steps, steps) &&
other.trigger == trigger;
other.triggerType == triggerType;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(actions.hashCode) +
(description == null ? 0 : description!.hashCode) +
(enabled == null ? 0 : enabled!.hashCode) +
(name == null ? 0 : name!.hashCode) +
(steps.hashCode) +
(trigger.hashCode);
(filters.hashCode) +
(name.hashCode) +
(triggerType.hashCode);
@override
String toString() => 'WorkflowCreateDto[description=$description, enabled=$enabled, name=$name, steps=$steps, trigger=$trigger]';
String toString() => 'WorkflowCreateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'actions'] = this.actions;
if (this.description != null) {
json[r'description'] = this.description;
} else {
@@ -71,13 +85,9 @@ class WorkflowCreateDto {
} else {
// json[r'enabled'] = null;
}
if (this.name != null) {
json[r'filters'] = this.filters;
json[r'name'] = this.name;
} else {
// json[r'name'] = null;
}
json[r'steps'] = this.steps;
json[r'trigger'] = this.trigger;
json[r'triggerType'] = this.triggerType;
return json;
}
@@ -90,11 +100,12 @@ class WorkflowCreateDto {
final json = value.cast<String, dynamic>();
return WorkflowCreateDto(
actions: WorkflowActionItemDto.listFromJson(json[r'actions']),
description: mapValueOfType<String>(json, r'description'),
enabled: mapValueOfType<bool>(json, r'enabled'),
name: mapValueOfType<String>(json, r'name'),
steps: WorkflowStepDto.listFromJson(json[r'steps']),
trigger: WorkflowTrigger.fromJson(json[r'trigger'])!,
filters: WorkflowFilterItemDto.listFromJson(json[r'filters']),
name: mapValueOfType<String>(json, r'name')!,
triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!,
);
}
return null;
@@ -142,7 +153,10 @@ class WorkflowCreateDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'trigger',
'actions',
'filters',
'name',
'triggerType',
};
}
+107
View 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 WorkflowFilterItemDto {
/// Returns a new [WorkflowFilterItemDto] instance.
WorkflowFilterItemDto({
this.filterConfig = const {},
required this.pluginFilterId,
});
Map<String, Object> filterConfig;
/// Plugin filter ID
String pluginFilterId;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowFilterItemDto &&
_deepEquality.equals(other.filterConfig, filterConfig) &&
other.pluginFilterId == pluginFilterId;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(filterConfig.hashCode) +
(pluginFilterId.hashCode);
@override
String toString() => 'WorkflowFilterItemDto[filterConfig=$filterConfig, pluginFilterId=$pluginFilterId]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'filterConfig'] = this.filterConfig;
json[r'pluginFilterId'] = this.pluginFilterId;
return json;
}
/// Returns a new [WorkflowFilterItemDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static WorkflowFilterItemDto? fromJson(dynamic value) {
upgradeDto(value, "WorkflowFilterItemDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return WorkflowFilterItemDto(
filterConfig: mapCastOfType<String, Object>(json, r'filterConfig') ?? const {},
pluginFilterId: mapValueOfType<String>(json, r'pluginFilterId')!,
);
}
return null;
}
static List<WorkflowFilterItemDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowFilterItemDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WorkflowFilterItemDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, WorkflowFilterItemDto> mapFromJson(dynamic json) {
final map = <String, WorkflowFilterItemDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = WorkflowFilterItemDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of WorkflowFilterItemDto-objects as value to a dart map
static Map<String, List<WorkflowFilterItemDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<WorkflowFilterItemDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = WorkflowFilterItemDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'pluginFilterId',
};
}
+142
View File
@@ -0,0 +1,142 @@
//
// 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 WorkflowFilterResponseDto {
/// Returns a new [WorkflowFilterResponseDto] instance.
WorkflowFilterResponseDto({
required this.filterConfig,
required this.id,
required this.order,
required this.pluginFilterId,
required this.workflowId,
});
Map<String, Object>? filterConfig;
/// Filter ID
String id;
/// Filter order
///
/// Minimum value: -9007199254740991
/// Maximum value: 9007199254740991
int order;
/// Plugin filter ID
String pluginFilterId;
/// Workflow ID
String workflowId;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowFilterResponseDto &&
_deepEquality.equals(other.filterConfig, filterConfig) &&
other.id == id &&
other.order == order &&
other.pluginFilterId == pluginFilterId &&
other.workflowId == workflowId;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(filterConfig == null ? 0 : filterConfig!.hashCode) +
(id.hashCode) +
(order.hashCode) +
(pluginFilterId.hashCode) +
(workflowId.hashCode);
@override
String toString() => 'WorkflowFilterResponseDto[filterConfig=$filterConfig, id=$id, order=$order, pluginFilterId=$pluginFilterId, workflowId=$workflowId]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.filterConfig != null) {
json[r'filterConfig'] = this.filterConfig;
} else {
// json[r'filterConfig'] = null;
}
json[r'id'] = this.id;
json[r'order'] = this.order;
json[r'pluginFilterId'] = this.pluginFilterId;
json[r'workflowId'] = this.workflowId;
return json;
}
/// Returns a new [WorkflowFilterResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static WorkflowFilterResponseDto? fromJson(dynamic value) {
upgradeDto(value, "WorkflowFilterResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return WorkflowFilterResponseDto(
filterConfig: mapCastOfType<String, Object>(json, r'filterConfig'),
id: mapValueOfType<String>(json, r'id')!,
order: mapValueOfType<int>(json, r'order')!,
pluginFilterId: mapValueOfType<String>(json, r'pluginFilterId')!,
workflowId: mapValueOfType<String>(json, r'workflowId')!,
);
}
return null;
}
static List<WorkflowFilterResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowFilterResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WorkflowFilterResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, WorkflowFilterResponseDto> mapFromJson(dynamic json) {
final map = <String, WorkflowFilterResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = WorkflowFilterResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of WorkflowFilterResponseDto-objects as value to a dart map
static Map<String, List<WorkflowFilterResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<WorkflowFilterResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = WorkflowFilterResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'filterConfig',
'id',
'order',
'pluginFilterId',
'workflowId',
};
}
+37 -32
View File
@@ -13,83 +13,86 @@ part of openapi.api;
class WorkflowResponseDto {
/// Returns a new [WorkflowResponseDto] instance.
WorkflowResponseDto({
this.actions = const [],
required this.createdAt,
required this.description,
required this.enabled,
this.filters = const [],
required this.id,
required this.name,
this.steps = const [],
required this.trigger,
required this.updatedAt,
required this.ownerId,
required this.triggerType,
});
/// Workflow actions
List<WorkflowActionResponseDto> actions;
/// Creation date
String createdAt;
/// Workflow description
String? description;
String description;
/// Workflow enabled
bool enabled;
/// Workflow filters
List<WorkflowFilterResponseDto> filters;
/// Workflow ID
String id;
/// Workflow name
String? name;
/// Workflow steps
List<WorkflowStepDto> steps;
/// Owner user ID
String ownerId;
WorkflowTrigger trigger;
/// Update date
String updatedAt;
PluginTriggerType triggerType;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowResponseDto &&
_deepEquality.equals(other.actions, actions) &&
other.createdAt == createdAt &&
other.description == description &&
other.enabled == enabled &&
_deepEquality.equals(other.filters, filters) &&
other.id == id &&
other.name == name &&
_deepEquality.equals(other.steps, steps) &&
other.trigger == trigger &&
other.updatedAt == updatedAt;
other.ownerId == ownerId &&
other.triggerType == triggerType;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(actions.hashCode) +
(createdAt.hashCode) +
(description == null ? 0 : description!.hashCode) +
(description.hashCode) +
(enabled.hashCode) +
(filters.hashCode) +
(id.hashCode) +
(name == null ? 0 : name!.hashCode) +
(steps.hashCode) +
(trigger.hashCode) +
(updatedAt.hashCode);
(ownerId.hashCode) +
(triggerType.hashCode);
@override
String toString() => 'WorkflowResponseDto[createdAt=$createdAt, description=$description, enabled=$enabled, id=$id, name=$name, steps=$steps, trigger=$trigger, updatedAt=$updatedAt]';
String toString() => 'WorkflowResponseDto[actions=$actions, createdAt=$createdAt, description=$description, enabled=$enabled, filters=$filters, id=$id, name=$name, ownerId=$ownerId, triggerType=$triggerType]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'actions'] = this.actions;
json[r'createdAt'] = this.createdAt;
if (this.description != null) {
json[r'description'] = this.description;
} else {
// json[r'description'] = null;
}
json[r'enabled'] = this.enabled;
json[r'filters'] = this.filters;
json[r'id'] = this.id;
if (this.name != null) {
json[r'name'] = this.name;
} else {
// json[r'name'] = null;
}
json[r'steps'] = this.steps;
json[r'trigger'] = this.trigger;
json[r'updatedAt'] = this.updatedAt;
json[r'ownerId'] = this.ownerId;
json[r'triggerType'] = this.triggerType;
return json;
}
@@ -102,14 +105,15 @@ class WorkflowResponseDto {
final json = value.cast<String, dynamic>();
return WorkflowResponseDto(
actions: WorkflowActionResponseDto.listFromJson(json[r'actions']),
createdAt: mapValueOfType<String>(json, r'createdAt')!,
description: mapValueOfType<String>(json, r'description'),
description: mapValueOfType<String>(json, r'description')!,
enabled: mapValueOfType<bool>(json, r'enabled')!,
filters: WorkflowFilterResponseDto.listFromJson(json[r'filters']),
id: mapValueOfType<String>(json, r'id')!,
name: mapValueOfType<String>(json, r'name'),
steps: WorkflowStepDto.listFromJson(json[r'steps']),
trigger: WorkflowTrigger.fromJson(json[r'trigger'])!,
updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
ownerId: mapValueOfType<String>(json, r'ownerId')!,
triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!,
);
}
return null;
@@ -157,14 +161,15 @@ class WorkflowResponseDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'actions',
'createdAt',
'description',
'enabled',
'filters',
'id',
'name',
'steps',
'trigger',
'updatedAt',
'ownerId',
'triggerType',
};
}
-134
View File
@@ -1,134 +0,0 @@
//
// 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 WorkflowShareResponseDto {
/// Returns a new [WorkflowShareResponseDto] instance.
WorkflowShareResponseDto({
required this.description,
required this.name,
this.steps = const [],
required this.trigger,
});
/// Workflow description
String? description;
/// Workflow name
String? name;
/// Workflow steps
List<WorkflowShareStepDto> steps;
WorkflowTrigger trigger;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowShareResponseDto &&
other.description == description &&
other.name == name &&
_deepEquality.equals(other.steps, steps) &&
other.trigger == trigger;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(description == null ? 0 : description!.hashCode) +
(name == null ? 0 : name!.hashCode) +
(steps.hashCode) +
(trigger.hashCode);
@override
String toString() => 'WorkflowShareResponseDto[description=$description, name=$name, steps=$steps, trigger=$trigger]';
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;
}
json[r'steps'] = this.steps;
json[r'trigger'] = this.trigger;
return json;
}
/// Returns a new [WorkflowShareResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static WorkflowShareResponseDto? fromJson(dynamic value) {
upgradeDto(value, "WorkflowShareResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return WorkflowShareResponseDto(
description: mapValueOfType<String>(json, r'description'),
name: mapValueOfType<String>(json, r'name'),
steps: WorkflowShareStepDto.listFromJson(json[r'steps']),
trigger: WorkflowTrigger.fromJson(json[r'trigger'])!,
);
}
return null;
}
static List<WorkflowShareResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowShareResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WorkflowShareResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, WorkflowShareResponseDto> mapFromJson(dynamic json) {
final map = <String, WorkflowShareResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = WorkflowShareResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of WorkflowShareResponseDto-objects as value to a dart map
static Map<String, List<WorkflowShareResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<WorkflowShareResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = WorkflowShareResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'description',
'name',
'steps',
'trigger',
};
}
-131
View File
@@ -1,131 +0,0 @@
//
// 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 WorkflowShareStepDto {
/// Returns a new [WorkflowShareStepDto] instance.
WorkflowShareStepDto({
this.config = const {},
this.enabled,
required this.method,
});
/// Step configuration
Map<String, Object>? config;
/// Step is enabled
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? enabled;
/// Step plugin method
String method;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowShareStepDto &&
_deepEquality.equals(other.config, config) &&
other.enabled == enabled &&
other.method == method;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(config == null ? 0 : config!.hashCode) +
(enabled == null ? 0 : enabled!.hashCode) +
(method.hashCode);
@override
String toString() => 'WorkflowShareStepDto[config=$config, enabled=$enabled, method=$method]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.config != null) {
json[r'config'] = this.config;
} else {
// json[r'config'] = null;
}
if (this.enabled != null) {
json[r'enabled'] = this.enabled;
} else {
// json[r'enabled'] = null;
}
json[r'method'] = this.method;
return json;
}
/// Returns a new [WorkflowShareStepDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static WorkflowShareStepDto? fromJson(dynamic value) {
upgradeDto(value, "WorkflowShareStepDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return WorkflowShareStepDto(
config: mapCastOfType<String, Object>(json, r'config'),
enabled: mapValueOfType<bool>(json, r'enabled'),
method: mapValueOfType<String>(json, r'method')!,
);
}
return null;
}
static List<WorkflowShareStepDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowShareStepDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WorkflowShareStepDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, WorkflowShareStepDto> mapFromJson(dynamic json) {
final map = <String, WorkflowShareStepDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = WorkflowShareStepDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of WorkflowShareStepDto-objects as value to a dart map
static Map<String, List<WorkflowShareStepDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<WorkflowShareStepDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = WorkflowShareStepDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'config',
'method',
};
}
-131
View File
@@ -1,131 +0,0 @@
//
// 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 WorkflowStepDto {
/// Returns a new [WorkflowStepDto] instance.
WorkflowStepDto({
this.config = const {},
this.enabled,
required this.method,
});
/// Step configuration
Map<String, Object>? config;
/// Step is enabled
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? enabled;
/// Step plugin method
String method;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowStepDto &&
_deepEquality.equals(other.config, config) &&
other.enabled == enabled &&
other.method == method;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(config == null ? 0 : config!.hashCode) +
(enabled == null ? 0 : enabled!.hashCode) +
(method.hashCode);
@override
String toString() => 'WorkflowStepDto[config=$config, enabled=$enabled, method=$method]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.config != null) {
json[r'config'] = this.config;
} else {
// json[r'config'] = null;
}
if (this.enabled != null) {
json[r'enabled'] = this.enabled;
} else {
// json[r'enabled'] = null;
}
json[r'method'] = this.method;
return json;
}
/// Returns a new [WorkflowStepDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static WorkflowStepDto? fromJson(dynamic value) {
upgradeDto(value, "WorkflowStepDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return WorkflowStepDto(
config: mapCastOfType<String, Object>(json, r'config'),
enabled: mapValueOfType<bool>(json, r'enabled'),
method: mapValueOfType<String>(json, r'method')!,
);
}
return null;
}
static List<WorkflowStepDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowStepDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WorkflowStepDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, WorkflowStepDto> mapFromJson(dynamic json) {
final map = <String, WorkflowStepDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = WorkflowStepDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of WorkflowStepDto-objects as value to a dart map
static Map<String, List<WorkflowStepDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<WorkflowStepDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = WorkflowStepDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'config',
'method',
};
}
@@ -1,108 +0,0 @@
//
// 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 WorkflowTriggerResponseDto {
/// Returns a new [WorkflowTriggerResponseDto] instance.
WorkflowTriggerResponseDto({
required this.trigger,
this.types = const [],
});
WorkflowTrigger trigger;
/// Workflow types
List<WorkflowType> types;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowTriggerResponseDto &&
other.trigger == trigger &&
_deepEquality.equals(other.types, types);
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(trigger.hashCode) +
(types.hashCode);
@override
String toString() => 'WorkflowTriggerResponseDto[trigger=$trigger, types=$types]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'trigger'] = this.trigger;
json[r'types'] = this.types;
return json;
}
/// Returns a new [WorkflowTriggerResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static WorkflowTriggerResponseDto? fromJson(dynamic value) {
upgradeDto(value, "WorkflowTriggerResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return WorkflowTriggerResponseDto(
trigger: WorkflowTrigger.fromJson(json[r'trigger'])!,
types: WorkflowType.listFromJson(json[r'types']),
);
}
return null;
}
static List<WorkflowTriggerResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowTriggerResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WorkflowTriggerResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, WorkflowTriggerResponseDto> mapFromJson(dynamic json) {
final map = <String, WorkflowTriggerResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = WorkflowTriggerResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of WorkflowTriggerResponseDto-objects as value to a dart map
static Map<String, List<WorkflowTriggerResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<WorkflowTriggerResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = WorkflowTriggerResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'trigger',
'types',
};
}
-85
View File
@@ -1,85 +0,0 @@
//
// 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;
/// Workflow type
class WorkflowType {
/// Instantiate a new enum with the provided [value].
const WorkflowType._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const assetV1 = WorkflowType._(r'AssetV1');
static const assetPersonV1 = WorkflowType._(r'AssetPersonV1');
/// List of all possible values in this [enum][WorkflowType].
static const values = <WorkflowType>[
assetV1,
assetPersonV1,
];
static WorkflowType? fromJson(dynamic value) => WorkflowTypeTypeTransformer().decode(value);
static List<WorkflowType> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowType>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WorkflowType.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [WorkflowType] to String,
/// and [decode] dynamic data back to [WorkflowType].
class WorkflowTypeTypeTransformer {
factory WorkflowTypeTypeTransformer() => _instance ??= const WorkflowTypeTypeTransformer._();
const WorkflowTypeTypeTransformer._();
String encode(WorkflowType data) => data.value;
/// Decodes a [dynamic value][data] to a WorkflowType.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
WorkflowType? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'AssetV1': return WorkflowType.assetV1;
case r'AssetPersonV1': return WorkflowType.assetPersonV1;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [WorkflowTypeTypeTransformer] instance.
static WorkflowTypeTypeTransformer? _instance;
}
+38 -17
View File
@@ -13,14 +13,24 @@ part of openapi.api;
class WorkflowUpdateDto {
/// Returns a new [WorkflowUpdateDto] instance.
WorkflowUpdateDto({
this.actions = const [],
this.description,
this.enabled,
this.filters = const [],
this.name,
this.steps = const [],
this.trigger,
this.triggerType,
});
/// Workflow actions
List<WorkflowActionItemDto> actions;
/// Workflow 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? description;
/// Workflow enabled
@@ -32,10 +42,17 @@ class WorkflowUpdateDto {
///
bool? enabled;
/// Workflow name
String? name;
/// Workflow filters
List<WorkflowFilterItemDto> filters;
List<WorkflowStepDto> steps;
/// Workflow name
///
/// 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;
///
/// Please note: This property should have been non-nullable! Since the specification file
@@ -43,30 +60,33 @@ class WorkflowUpdateDto {
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
WorkflowTrigger? trigger;
PluginTriggerType? triggerType;
@override
bool operator ==(Object other) => identical(this, other) || other is WorkflowUpdateDto &&
_deepEquality.equals(other.actions, actions) &&
other.description == description &&
other.enabled == enabled &&
_deepEquality.equals(other.filters, filters) &&
other.name == name &&
_deepEquality.equals(other.steps, steps) &&
other.trigger == trigger;
other.triggerType == triggerType;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(actions.hashCode) +
(description == null ? 0 : description!.hashCode) +
(enabled == null ? 0 : enabled!.hashCode) +
(filters.hashCode) +
(name == null ? 0 : name!.hashCode) +
(steps.hashCode) +
(trigger == null ? 0 : trigger!.hashCode);
(triggerType == null ? 0 : triggerType!.hashCode);
@override
String toString() => 'WorkflowUpdateDto[description=$description, enabled=$enabled, name=$name, steps=$steps, trigger=$trigger]';
String toString() => 'WorkflowUpdateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'actions'] = this.actions;
if (this.description != null) {
json[r'description'] = this.description;
} else {
@@ -77,16 +97,16 @@ class WorkflowUpdateDto {
} else {
// json[r'enabled'] = null;
}
json[r'filters'] = this.filters;
if (this.name != null) {
json[r'name'] = this.name;
} else {
// json[r'name'] = null;
}
json[r'steps'] = this.steps;
if (this.trigger != null) {
json[r'trigger'] = this.trigger;
if (this.triggerType != null) {
json[r'triggerType'] = this.triggerType;
} else {
// json[r'trigger'] = null;
// json[r'triggerType'] = null;
}
return json;
}
@@ -100,11 +120,12 @@ class WorkflowUpdateDto {
final json = value.cast<String, dynamic>();
return WorkflowUpdateDto(
actions: WorkflowActionItemDto.listFromJson(json[r'actions']),
description: mapValueOfType<String>(json, r'description'),
enabled: mapValueOfType<bool>(json, r'enabled'),
filters: WorkflowFilterItemDto.listFromJson(json[r'filters']),
name: mapValueOfType<String>(json, r'name'),
steps: WorkflowStepDto.listFromJson(json[r'steps']),
trigger: WorkflowTrigger.fromJson(json[r'trigger']),
triggerType: PluginTriggerType.fromJson(json[r'triggerType']),
);
}
return null;
@@ -9,8 +9,9 @@ import 'package:mocktail/mocktail.dart';
import '../../infrastructure/repository.mock.dart';
const _kAccessToken = '#ThisIsAToken';
const _kEnableBackup = false;
const _kBackgroundBackup = false;
const _kVersion = 2;
final _kBackupFailedSince = DateTime.utc(2023);
void main() {
late StoreService sut;
@@ -23,13 +24,15 @@ void main() {
// For generics, we need to provide fallback to each concrete type to avoid runtime errors
registerFallbackValue(StoreKey.accessToken);
registerFallbackValue(StoreKey.backupTriggerDelay);
registerFallbackValue(StoreKey.enableBackup);
registerFallbackValue(StoreKey.backgroundBackup);
registerFallbackValue(StoreKey.backupFailedSince);
when(() => mockDriftStoreRepo.getAll()).thenAnswer(
(_) async => [
const StoreDto(StoreKey.accessToken, _kAccessToken),
const StoreDto(StoreKey.enableBackup, _kEnableBackup),
const StoreDto(StoreKey.backgroundBackup, _kBackgroundBackup),
const StoreDto(StoreKey.version, _kVersion),
StoreDto(StoreKey.backupFailedSince, _kBackupFailedSince),
],
);
when(() => mockDriftStoreRepo.watchAll()).thenAnswer((_) => controller.stream);
@@ -46,8 +49,9 @@ void main() {
test('Populates the internal cache on init', () {
verify(() => mockDriftStoreRepo.getAll()).called(1);
expect(sut.tryGet(StoreKey.accessToken), _kAccessToken);
expect(sut.tryGet(StoreKey.enableBackup), _kEnableBackup);
expect(sut.tryGet(StoreKey.backgroundBackup), _kBackgroundBackup);
expect(sut.tryGet(StoreKey.version), _kVersion);
expect(sut.tryGet(StoreKey.backupFailedSince), _kBackupFailedSince);
// Other keys should be null
expect(sut.tryGet(StoreKey.currentUser), isNull);
});
@@ -147,8 +151,9 @@ void main() {
await sut.clear();
verify(() => mockDriftStoreRepo.deleteAll()).called(1);
expect(sut.tryGet(StoreKey.accessToken), isNull);
expect(sut.tryGet(StoreKey.enableBackup), isNull);
expect(sut.tryGet(StoreKey.backgroundBackup), isNull);
expect(sut.tryGet(StoreKey.version), isNull);
expect(sut.tryGet(StoreKey.backupFailedSince), isNull);
});
});
}
@@ -12,8 +12,9 @@ import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'
import '../../fixtures/user.stub.dart';
const _kTestAccessToken = "#TestToken";
final _kTestBackupFailed = DateTime(2025, 2, 20, 11, 45);
const _kTestVersion = 10;
const _kTestBackupRequireCharging = false;
const _kTestBackupRequireWifi = false;
final _kTestUser = UserStub.admin;
Future<void> _populateStore(Drift db) async {
@@ -21,8 +22,16 @@ Future<void> _populateStore(Drift db) async {
batch.insert(
db.storeEntity,
StoreEntityCompanion(
id: Value(StoreKey.backupRequireCharging.id),
intValue: const Value(_kTestBackupRequireCharging ? 1 : 0),
id: Value(StoreKey.backupRequireWifi.id),
intValue: const Value(_kTestBackupRequireWifi ? 1 : 0),
stringValue: const Value(null),
),
);
batch.insert(
db.storeEntity,
StoreEntityCompanion(
id: Value(StoreKey.backupFailedSince.id),
intValue: Value(_kTestBackupFailed.millisecondsSinceEpoch),
stringValue: const Value(null),
),
);
@@ -75,12 +84,20 @@ void main() {
expect(accessToken, _kTestAccessToken);
});
test('converts datetime', () async {
DateTime? backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince);
expect(backupFailedSince, isNull);
await sut.upsert(StoreKey.backupFailedSince, _kTestBackupFailed);
backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince);
expect(backupFailedSince, _kTestBackupFailed);
});
test('converts bool', () async {
bool? backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging);
expect(backupRequireCharging, isNull);
await sut.upsert(StoreKey.backupRequireCharging, _kTestBackupRequireCharging);
backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging);
expect(backupRequireCharging, _kTestBackupRequireCharging);
bool? backupRequireWifi = await sut.tryGet(StoreKey.backupRequireWifi);
expect(backupRequireWifi, isNull);
await sut.upsert(StoreKey.backupRequireWifi, _kTestBackupRequireWifi);
backupRequireWifi = await sut.tryGet(StoreKey.backupRequireWifi);
expect(backupRequireWifi, _kTestBackupRequireWifi);
});
test('converts user', () async {
@@ -98,11 +115,11 @@ void main() {
});
test('delete()', () async {
bool? backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging);
expect(backupRequireCharging, isFalse);
await sut.delete(StoreKey.backupRequireCharging);
backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging);
expect(backupRequireCharging, isNull);
bool? backupRequireWifi = await sut.tryGet(StoreKey.backupRequireWifi);
expect(backupRequireWifi, isFalse);
await sut.delete(StoreKey.backupRequireWifi);
backupRequireWifi = await sut.tryGet(StoreKey.backupRequireWifi);
expect(backupRequireWifi, isNull);
});
test('deleteAll()', () async {
@@ -147,12 +164,14 @@ void main() {
emitsInOrder([
[
const StoreDto<Object>(StoreKey.version, _kTestVersion),
const StoreDto<Object>(StoreKey.backupRequireCharging, _kTestBackupRequireCharging),
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
const StoreDto<Object>(StoreKey.backupRequireWifi, _kTestBackupRequireWifi),
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
],
[
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
const StoreDto<Object>(StoreKey.backupRequireCharging, _kTestBackupRequireCharging),
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
const StoreDto<Object>(StoreKey.backupRequireWifi, _kTestBackupRequireWifi),
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
],
]),
@@ -3,7 +3,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/utils/action_button.utils.dart';
LocalAsset createLocalAsset({
@@ -38,7 +37,6 @@ RemoteAsset createRemoteAsset({
DateTime? updatedAt,
DateTime? uploadedAt,
bool isFavorite = false,
DateTime? deletedAt,
}) {
return RemoteAsset(
id: 'remote-id',
@@ -52,7 +50,6 @@ RemoteAsset createRemoteAsset({
uploadedAt: uploadedAt ?? DateTime.now(),
isFavorite: isFavorite,
isEdited: false,
deletedAt: deletedAt,
);
}
@@ -461,62 +458,6 @@ void main() {
expect(ActionButtonType.trash.shouldShow(context), isFalse);
});
test('should not show when asset is already trashed', () {
final remoteAsset = createRemoteAsset(deletedAt: DateTime(2024));
final context = ActionButtonContext(
asset: remoteAsset,
isOwner: true,
isArchived: false,
isTrashEnabled: true,
isInLockedView: false,
currentAlbum: null,
advancedTroubleshooting: false,
isStacked: false,
source: ActionSource.viewer,
timelineOrigin: TimelineOrigin.trash,
);
expect(ActionButtonType.trash.shouldShow(context), isFalse);
});
});
group('restoreTrash button', () {
test('should show when owner, not locked, has remote, and is in trash timeline', () {
final remoteAsset = createRemoteAsset();
final context = ActionButtonContext(
asset: remoteAsset,
isOwner: true,
isArchived: false,
isTrashEnabled: true,
isInLockedView: false,
currentAlbum: null,
advancedTroubleshooting: false,
isStacked: false,
source: ActionSource.timeline,
timelineOrigin: TimelineOrigin.trash,
);
expect(ActionButtonType.restoreTrash.shouldShow(context), isTrue);
});
test('should not show when not in trash timeline', () {
final remoteAsset = createRemoteAsset();
final context = ActionButtonContext(
asset: remoteAsset,
isOwner: true,
isArchived: false,
isTrashEnabled: false,
isInLockedView: false,
currentAlbum: null,
advancedTroubleshooting: false,
isStacked: false,
source: ActionSource.timeline,
timelineOrigin: TimelineOrigin.main,
);
expect(ActionButtonType.restoreTrash.shouldShow(context), isFalse);
});
});
group('deletePermanent button', () {
@@ -553,24 +494,6 @@ void main() {
expect(ActionButtonType.deletePermanent.shouldShow(context), isFalse);
});
test('should show when asset is trashed even with trash enabled', () {
final remoteAsset = createRemoteAsset(deletedAt: DateTime(2024));
final context = ActionButtonContext(
asset: remoteAsset,
isOwner: true,
isArchived: false,
isTrashEnabled: true,
isInLockedView: false,
currentAlbum: null,
advancedTroubleshooting: false,
isStacked: false,
source: ActionSource.viewer,
timelineOrigin: TimelineOrigin.trash,
);
expect(ActionButtonType.deletePermanent.shouldShow(context), isTrue);
});
});
group('delete button', () {
File diff suppressed because it is too large Load Diff
-2
View File
@@ -1,2 +0,0 @@
/dist
/node_modules
-6
View File
@@ -1,6 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"semi": true
}
-11
View File
@@ -1,11 +0,0 @@
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.ts'],
outdir: 'dist',
bundle: true,
sourcemap: false,
minify: false, // might want to use true for production build
format: 'cjs', // needs to be CJS for now
target: ['es2020'], // don't go over es2020 because quickjs doesn't support it
});
-258
View File
@@ -1,258 +0,0 @@
{
"name": "immich-plugin-core",
"version": "2.0.1",
"title": "Immich Core Plugin",
"description": "Core workflow capabilities for Immich",
"author": "Immich Team",
"wasmPath": "dist/plugin.wasm",
"methods": [
{
"name": "assetFileFilter",
"title": "Filter by filename",
"description": "Filter assets by filename pattern using text matching or regular expressions",
"types": ["AssetV1"],
"schema": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"title": "Filename pattern",
"description": "Text or regex pattern to match against filename"
},
"matchType": {
"type": "string",
"title": "Match type",
"enum": ["contains", "startsWith", "exact", "regex"],
"default": "contains",
"description": "Type of pattern matching to perform"
},
"caseSensitive": {
"type": "boolean",
"default": false,
"title": "Case sensitive",
"description": "Whether matching should be case-sensitive"
}
},
"required": ["pattern"]
},
"uiHints": ["filter"]
},
{
"name": "filterFileType",
"title": "Filter by file type",
"description": "Filter assets by file type",
"types": ["AssetV1"],
"schema": {
"type": "object",
"properties": {
"fileTypes": {
"title": "File types",
"description": "Allowed file types",
"type": "string",
"array": true,
"enum": ["image", "video"]
}
},
"required": ["fileTypes"]
},
"uiHints": ["filter"]
},
{
"name": "filterPerson",
"title": "Filter by person",
"description": "Filter by detected person",
"types": ["AssetV1"],
"schema": {
"properties": {
"personIds": {
"type": "string",
"array": true,
"title": "Person IDs",
"description": "List of person to match",
"uiHint": "personI"
},
"matchAny": {
"type": "boolean",
"title": "Match any",
"default": true,
"description": "Match any name (true) or require all names (false)"
}
},
"required": ["personIds"]
},
"uiHints": ["filter"]
},
{
"name": "assetArchive",
"title": "Archive asset",
"description": "Change asset visibility to archive",
"types": ["AssetV1"],
"schema": {
"properties": {
"inverse": {
"title": "Inverse",
"description": "When true will unarchive any archived assets",
"type": "boolean"
}
}
}
},
{
"name": "assetLock",
"title": "Move to locked folder",
"description": "Change visibility to locked",
"types": ["AssetV1"]
},
{
"name": "assetTimeline",
"title": "Move to timeline",
"description": "Change visibility to timeline",
"types": ["AssetV1"]
},
{
"name": "assetVisibility",
"title": "Update visibility",
"description": "Change visibility to selected option",
"types": ["AssetV1"],
"schema": {
"properties": {
"visibility": {
"title": "Visibility",
"description": "Asset visibility",
"type": "string",
"enum": ["archive", "timeline", "locked"]
}
},
"required": ["visibility"]
}
},
{
"name": "assetFavorite",
"title": "Favorite",
"description": "Favorite an asset",
"types": ["AssetV1"],
"schema": {
"type": "object",
"properties": {
"inverse": {
"type": "boolean",
"title": "Inverse",
"description": "Unfavorite by default, set to true to favorite instead",
"default": false
}
}
}
},
{
"name": "assetAddToAlbums",
"title": "Add to Album(s)",
"description": "Add asset to selected albums",
"types": ["AssetV1"],
"hostFunctions": true,
"schema": {
"type": "object",
"properties": {
"albumIds": {
"type": "string",
"title": "Album IDs",
"array": true,
"description": "Target album IDs",
"uiHint": "albumId"
}
},
"required": ["albumIds"]
}
},
{
"name": "noop1",
"title": "DEV: Nested properties",
"description": "Example configuration with nested properties",
"types": ["AssetV1"],
"schema": {
"type": "object",
"properties": {
"number1": {
"type": "number",
"title": "Number 1",
"description": "Basic number"
},
"number2": {
"type": "number",
"title": "Number 2",
"array": true,
"description": "List of numbers"
},
"string1": {
"type": "string",
"title": "String 1",
"description": "Basic string"
},
"string2": {
"type": "string",
"title": "String 2",
"array": true,
"description": "List of strings"
},
"string3": {
"type": "string",
"title": "String 3",
"enum": ["choice-1", "choice-2"],
"description": "Select from a list"
},
"nested": {
"type": "object",
"title": "Nested",
"description": "Nested properties for nesting",
"properties": {
"nested1": {
"type": "string",
"title": "Nested 1",
"description": "Nested string"
},
"nested2": {
"type": "number",
"title": "Nested 2",
"description": "Nested number"
},
"nested3": {
"type": "object",
"title": "Nested 3",
"description": "Nested again",
"properties": {
"nested4": {
"type": "boolean",
"title": "Nested 4",
"description": "Nested, nested boolean"
}
}
}
}
}
}
}
},
{
"name": "noop2",
"title": "DEV: Album pickers",
"description": "Example configuration with album pickers",
"types": ["AssetV1"],
"schema": {
"properties": {
"albumId": {
"type": "string",
"title": "Album ID",
"description": "Target album ID",
"uiHint": "albumId"
},
"albumIds": {
"type": "string",
"title": "Album IDs",
"description": "Target album IDs",
"array": true,
"uiHint": "albumId"
}
}
}
}
]
}
-6
View File
@@ -1,6 +0,0 @@
[tasks.install]
run = "pnpm install --frozen-lockfile"
[tasks.build]
depends = ["install"]
run = "pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core build"
-19
View File
@@ -1,19 +0,0 @@
// copy from
// import '@immich/plugin-sdk/host-functions';
declare module 'extism:host' {
interface user {
albumAddAssets(ptr: PTR): I64;
addAssetsToAlbums(ptr: PTR): I64;
}
}
declare module 'main' {
export function assetFileFilter(): I32;
export function assetFavorite(): I32;
export function assetVisibility(): I32;
export function assetArchive(): I32;
export function assetLock(): I32;
export function assetTimeline(): I32;
export function assetTrash(): I32;
export function assetAddToAlbums(): I32;
}
-111
View File
@@ -1,111 +0,0 @@
import { AssetStatus, AssetVisibility, WorkflowType, wrapper } from '@immich/plugin-sdk';
type AssetFileFilterConfig = {
pattern: string;
matchType?: 'contains' | 'exact' | 'regex' | 'startsWith';
caseSensitive?: boolean;
};
export const assetFileFilter = () => {
return wrapper<WorkflowType.AssetV1, AssetFileFilterConfig>(({ data, config }) => {
const { pattern, matchType = 'contains', caseSensitive = false } = config;
const { asset } = data;
const fileName = asset.originalFileName || '';
const searchName = caseSensitive ? fileName : fileName.toLowerCase();
const searchPattern = caseSensitive ? pattern : pattern.toLowerCase();
switch (matchType) {
case 'contains': {
return { workflow: { continue: searchName.includes(searchPattern) } };
}
case 'exact': {
return { workflow: { continue: searchName === searchPattern } };
}
case 'startsWith': {
return { workflow: { continue: searchName.startsWith(searchPattern) } };
}
case 'regex': {
const flags = caseSensitive ? '' : 'i';
const regex = new RegExp(searchPattern, flags);
return { workflow: { continue: regex.test(fileName) } };
}
default: {
return {};
}
}
});
};
export const assetFavorite = () => {
return wrapper<WorkflowType.AssetV1, { inverse?: boolean }>(({ config, data }) => {
const target = config.inverse ? false : true;
if (target !== data.asset.isFavorite) {
return {
changes: {
asset: { isFavorite: target },
},
};
}
});
};
export const assetVisibility = () => {
return wrapper<WorkflowType.AssetV1, { visibility: AssetVisibility }>(({ config }) => ({
changes: { asset: { visibility: config.visibility } },
}));
};
export const assetArchive = () => {
return wrapper<WorkflowType.AssetV1, { inverse?: boolean }>(({ config, data }) => {
if (!config.inverse && data.asset.visibility !== AssetVisibility.Archive) {
return { changes: { asset: { visibility: AssetVisibility.Archive } } };
}
if (config.inverse && data.asset.visibility === AssetVisibility.Archive) {
return { changes: { asset: { visibility: AssetVisibility.Timeline } } };
}
return {};
});
};
export const assetLock = () => {
return wrapper<WorkflowType.AssetV1, { inverse?: boolean }>(({ config, data }) => {
if (!config.inverse && data.asset.visibility !== AssetVisibility.Locked) {
return { changes: { asset: { visibility: AssetVisibility.Locked } } };
}
if (config.inverse && data.asset.visibility === AssetVisibility.Locked) {
return { changes: { asset: { visibility: AssetVisibility.Timeline } } };
}
return {};
});
};
export const assetTrash = () => {
return wrapper<WorkflowType.AssetV1, { inverse?: boolean }>(({ config, data }) => ({
changes: {
asset: config.inverse
? { deletedAt: null, status: AssetStatus.Active }
: { deletedAt: new Date(), status: AssetStatus.Trashed },
},
}));
};
export const assetAddToAlbums = () => {
return wrapper<WorkflowType.AssetV1, { albumIds: string[] }>(({ config, data, functions }) => {
if (config.albumIds.length === 1) {
functions.albumAddAssets(config.albumIds[0], [data.asset.id]);
return {};
}
functions.addAssetsToAlbums({ albumIds: config.albumIds, assetIds: [data.asset.id] });
return {};
});
};
-2
View File
@@ -1,2 +0,0 @@
/dist
/node_modules
-11
View File
@@ -1,11 +0,0 @@
import esbuild from 'esbuild';
esbuild.build({
entryPoints: ['src/index.ts'],
outdir: 'dist',
bundle: true,
sourcemap: false,
minify: false,
format: 'esm',
target: ['es2020'],
});
-38
View File
@@ -1,38 +0,0 @@
{
"name": "@immich/plugin-sdk",
"version": "0.0.0",
"description": "",
"main": "index.js",
"type": "module",
"exports": {
"./host-functions": {
"import": "./dist/host-functions.js",
"types": "./dist/host-functions.d.ts"
},
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "node esbuild.js && tsc --emitDeclarationOnly && tsc-alias"
},
"files": [
"dist"
],
"keywords": [],
"author": "",
"license": "GNU Affero General Public License version 3",
"packageManager": "pnpm@10.30.3",
"devDependencies": {
"@extism/js-pdk": "^1.1.1",
"@types/node": "^24.11.0",
"esbuild": "^0.27.3",
"tsc-alias": "^1.8.16",
"typescript": "^5.9.3"
},
"peerDependencies": {
"@extism/js-pdk": "^1.1.1"
}
}
-33
View File
@@ -1,33 +0,0 @@
export enum WorkflowTrigger {
AssetCreate = 'AssetCreate',
PersonRecognized = 'PersonRecognized',
}
export enum WorkflowType {
AssetV1 = 'AssetV1',
AssetPersonV1 = 'AssetPersonV1',
}
export enum AssetType {
Image = 'IMAGE',
Video = 'VIDEO',
Audio = 'AUDIO',
Other = 'OTHER',
}
export enum AssetStatus {
Active = 'active',
Trashed = 'trashed',
Deleted = 'deleted',
}
export enum AssetVisibility {
Archive = 'archive',
Timeline = 'timeline',
/**
* Video part of the LivePhotos and MotionPhotos
*/
Hidden = 'hidden',
Locked = 'locked',
}
-51
View File
@@ -1,51 +0,0 @@
declare module 'extism:host' {
interface user {
albumAddAssets(ptr: PTR): I64;
addAssetsToAlbums(ptr: PTR): I64;
}
}
const host = Host.getFunctions();
type HostFunctionName = keyof typeof host;
type HostFunctionSuccessResult<T> = { success: true; response: T };
type HostFunctionErrorResult = {
success: false;
status: number;
message: string;
};
type HostFunctionResult<T> =
| HostFunctionSuccessResult<T>
| HostFunctionErrorResult;
const call = <T, R>(name: HostFunctionName, authToken: string, args: T) => {
const pointer1 = Memory.fromString(JSON.stringify({ authToken, args }));
const fn = host[name];
const handler = Memory.find(fn(pointer1.offset));
try {
const result = JSON.parse(handler.readString()) as HostFunctionResult<R>;
if (result.success) {
return result.response;
}
throw new Error(
`Failed to call host function "${String(name)}", received ${result.status} - ${JSON.stringify(result.message)}`,
);
} finally {
handler.free();
pointer1.free();
}
};
type AlbumsToAssets = {
assetIds: string[];
albumIds: string[];
};
export const hostFunctions = (authToken: string) => ({
albumAddAssets: (albumId: string, assetIds: string[]) =>
call('albumAddAssets', authToken, [albumId, { ids: assetIds }]),
addAssetsToAlbums: ({ assetIds, albumIds }: AlbumsToAssets) =>
call('addAssetsToAlbums', authToken, [{ albumIds, assetIds }]),
});
-4
View File
@@ -1,4 +0,0 @@
export * from 'src/enum.js';
export * from 'src/host-functions.js';
export * from 'src/sdk.js';
export * from 'src/types.js';
-43
View File
@@ -1,43 +0,0 @@
import type { WorkflowType } from 'src/enum.js';
import { hostFunctions } from 'src/host-functions.js';
import type {
ConfigValue,
WorkflowEventPayload,
WorkflowResponse,
} from 'src/types.js';
export const wrapper = <
T extends WorkflowType = WorkflowType,
TConfig extends ConfigValue = ConfigValue,
>(
fn: (
payload: WorkflowEventPayload<T, TConfig> & {
functions: ReturnType<typeof hostFunctions>;
},
) => WorkflowResponse<T> | undefined,
) => {
const input = Host.inputString();
try {
const event = JSON.parse(input) as WorkflowEventPayload<T, TConfig>;
// const debug = event.workflow.debug ?? false;
console.debug(
`Inputs: trigger=${event.trigger}, event=${event.type}, config=${JSON.stringify(event.config)}`,
);
const response =
fn({ ...event, functions: hostFunctions(event.workflow.authToken) }) ??
{};
console.debug(
`Outputs: workflow=${JSON.stringify(response.workflow)}, changes=${JSON.stringify(response.changes)}, data=${JSON.stringify(response.data)}`,
);
const output = JSON.stringify(response);
Host.outputString(output);
} catch (error: Error | any) {
console.error(`Unhandled plugin exception: ${error.message || error}`);
throw error;
}
};
-129
View File
@@ -1,129 +0,0 @@
import type {
AssetStatus,
AssetType,
AssetVisibility,
WorkflowTrigger,
WorkflowType,
} from 'src/enum.js';
type DeepPartial<T> = T extends Date
? T
: T extends Record<string, unknown>
? { [K in keyof T]?: DeepPartial<T[K]> }
: T extends Array<infer R>
? DeepPartial<R>[]
: T;
export type WorkflowEventMap = {
[WorkflowType.AssetV1]: AssetV1;
[WorkflowType.AssetPersonV1]: AssetPersonV1;
};
export type WorkflowEventData<T extends WorkflowType> = WorkflowEventMap[T];
export type WorkflowEventPayload<
T extends WorkflowType = WorkflowType,
TConfig = WorkflowStepConfig,
> = {
trigger: WorkflowTrigger;
type: T;
data: WorkflowEventData<T>;
config: TConfig;
workflow: {
id: string;
authToken: string;
stepId: string;
debug?: boolean;
};
};
export type WorkflowChanges<T extends WorkflowType = WorkflowType> =
DeepPartial<WorkflowEventData<T>>;
export type WorkflowResponse<T extends WorkflowType = WorkflowType> = {
workflow?: {
/** stop the workflow */
continue?: boolean;
};
changes?: WorkflowChanges<T>;
/** data to be passed to the next workflow step */
data?: Record<string, unknown>;
};
export type WorkflowStepConfig = {
[key: string]: ConfigValue;
};
export type ConfigValue =
| string
| number
| boolean
| null
| ConfigValue[]
| { [key: string]: ConfigValue };
export type AssetV1 = {
asset: {
id: string;
ownerId: string;
type: AssetType;
originalPath: string;
fileCreatedAt: Date;
fileModifiedAt: Date;
isFavorite: boolean;
checksum: Buffer; // sha1 checksum
livePhotoVideoId: string | null;
updatedAt: Date;
createdAt: Date;
originalFileName: string;
isOffline: boolean;
libraryId: string | null;
isExternal: boolean;
deletedAt: Date | null;
localDateTime: Date;
stackId: string | null;
duplicateId: string | null;
status: AssetStatus;
visibility: AssetVisibility;
isEdited: boolean;
exifInfo: {
make: string | null;
model: string | null;
exifImageWidth: number | null;
exifImageHeight: number | null;
fileSizeInByte: number | null;
orientation: string | null;
dateTimeOriginal: Date | null;
modifyDate: Date | null;
lensModel: string | null;
fNumber: number | null;
focalLength: number | null;
iso: number | null;
latitude: number | null;
longitude: number | null;
city: string | null;
state: string | null;
country: string | null;
description: string | null;
fps: number | null;
exposureTime: string | null;
livePhotoCID: string | null;
timeZone: string | null;
projectionType: string | null;
profileDescription: string | null;
colorspace: string | null;
bitsPerSample: number | null;
autoStackId: string | null;
rating: number | null;
tags: string[] | null;
updatedAt: Date | null;
} | null;
};
};
export type AssetPersonV1 = AssetV1 & {
person: {
id: string;
name: string;
};
};
-26
View File
@@ -1,26 +0,0 @@
{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"esModuleInterop": true,
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"lib": ["esnext"],
"module": "nodenext",
"moduleDetection": "force",
"noUncheckedIndexedAccess": true,
"noUncheckedSideEffectImports": true,
"outDir": "./dist",
"paths": {
"src/*": ["./src/*"]
},
"removeComments": true,
"rootDir": "./src",
"skipLibCheck": true,
"sourceMap": false,
"strict": true,
"target": "esnext",
"types": ["node", "@extism/js-pdk"],
"verbatimModuleSyntax": true
}
}
+2
View File
@@ -0,0 +1,2 @@
node_modules
dist
+26
View File
@@ -0,0 +1,26 @@
Copyright 2024, The Extism Authors.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+12
View File
@@ -0,0 +1,12 @@
const esbuild = require('esbuild');
esbuild
.build({
entryPoints: ['src/index.ts'],
outdir: 'dist',
bundle: true,
sourcemap: true,
minify: false, // might want to use true for production build
format: 'cjs', // needs to be CJS for now
target: ['es2020'] // don't go over es2020 because quickjs doesn't support it
})
+159
View File
@@ -0,0 +1,159 @@
{
"name": "immich-core",
"version": "2.0.1",
"title": "Immich Core",
"description": "Core workflow capabilities for Immich",
"author": "Immich Team",
"wasm": {
"path": "dist/plugin.wasm"
},
"filters": [
{
"methodName": "filterFileName",
"title": "Filter by filename",
"description": "Filter assets by filename pattern using text matching or regular expressions",
"supportedContexts": [
"asset"
],
"schema": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"title": "Filename pattern",
"description": "Text or regex pattern to match against filename"
},
"matchType": {
"type": "string",
"title": "Match type",
"enum": [
"contains",
"regex",
"exact"
],
"default": "contains",
"description": "Type of pattern matching to perform"
},
"caseSensitive": {
"type": "boolean",
"default": false,
"description": "Whether matching should be case-sensitive"
}
},
"required": [
"pattern"
]
}
},
{
"methodName": "filterFileType",
"title": "Filter by file type",
"description": "Filter assets by file type",
"supportedContexts": [
"asset"
],
"schema": {
"type": "object",
"properties": {
"fileTypes": {
"type": "array",
"title": "File types",
"items": {
"type": "string",
"enum": [
"image",
"video"
]
},
"description": "Allowed file types"
}
},
"required": [
"fileTypes"
]
}
},
{
"methodName": "filterPerson",
"title": "Filter by person",
"description": "Filter by detected person",
"supportedContexts": [
"person"
],
"schema": {
"type": "object",
"properties": {
"personIds": {
"type": "array",
"title": "Person IDs",
"items": {
"type": "string"
},
"description": "List of person to match",
"subType": "people-picker"
},
"matchAny": {
"type": "boolean",
"default": true,
"description": "Match any name (true) or require all names (false)"
}
},
"required": [
"personIds"
]
}
}
],
"actions": [
{
"methodName": "actionArchive",
"title": "Archive",
"description": "Move the asset to archive",
"supportedContexts": [
"asset"
],
"schema": {}
},
{
"methodName": "actionFavorite",
"title": "Favorite",
"description": "Mark the asset as favorite or unfavorite",
"supportedContexts": [
"asset"
],
"schema": {
"type": "object",
"properties": {
"favorite": {
"type": "boolean",
"default": true,
"description": "Set favorite (true) or unfavorite (false)"
}
}
}
},
{
"methodName": "actionAddToAlbum",
"title": "Add to Album",
"description": "Add the item to a specified album",
"supportedContexts": [
"asset",
"person"
],
"schema": {
"type": "object",
"properties": {
"albumId": {
"type": "string",
"title": "Album ID",
"description": "Target album ID",
"subType": "album-picker"
}
},
"required": [
"albumId"
]
}
}
]
}
+11
View File
@@ -0,0 +1,11 @@
[tools]
"github:extism/cli" = "1.6.3"
"github:webassembly/binaryen" = "version_124"
"github:extism/js-pdk" = "1.6.0"
[tasks.install]
run = "pnpm install --frozen-lockfile"
[tasks.build]
depends = ["install"]
run = "pnpm run build"
+533
View File
@@ -0,0 +1,533 @@
{
"name": "plugins",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "plugins",
"version": "1.0.0",
"license": "AGPL-3.0",
"devDependencies": {
"@extism/js-pdk": "^1.0.1",
"esbuild": "^0.28.0",
"typescript": "^6.0.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
"integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
"integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
"integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
"integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
"integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
"integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
"integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
"integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
"integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
"integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
"integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
"integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
"integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
"integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
"integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
"integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
"integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
"integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
"integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
"integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
"integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
"integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
"integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
"integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
"integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
"integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@extism/js-pdk": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.1.1.tgz",
"integrity": "sha512-VZLn/dX0ttA1uKk2PZeR/FL3N+nA1S5Vc7E5gdjkR60LuUIwCZT9cYON245V4HowHlBA7YOegh0TLjkx+wNbrA==",
"dev": true,
"license": "BSD-Clause-3",
"dependencies": {
"urlpattern-polyfill": "^8.0.2"
}
},
"node_modules/esbuild": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
"integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.28.0",
"@esbuild/android-arm": "0.28.0",
"@esbuild/android-arm64": "0.28.0",
"@esbuild/android-x64": "0.28.0",
"@esbuild/darwin-arm64": "0.28.0",
"@esbuild/darwin-x64": "0.28.0",
"@esbuild/freebsd-arm64": "0.28.0",
"@esbuild/freebsd-x64": "0.28.0",
"@esbuild/linux-arm": "0.28.0",
"@esbuild/linux-arm64": "0.28.0",
"@esbuild/linux-ia32": "0.28.0",
"@esbuild/linux-loong64": "0.28.0",
"@esbuild/linux-mips64el": "0.28.0",
"@esbuild/linux-ppc64": "0.28.0",
"@esbuild/linux-riscv64": "0.28.0",
"@esbuild/linux-s390x": "0.28.0",
"@esbuild/linux-x64": "0.28.0",
"@esbuild/netbsd-arm64": "0.28.0",
"@esbuild/netbsd-x64": "0.28.0",
"@esbuild/openbsd-arm64": "0.28.0",
"@esbuild/openbsd-x64": "0.28.0",
"@esbuild/openharmony-arm64": "0.28.0",
"@esbuild/sunos-x64": "0.28.0",
"@esbuild/win32-arm64": "0.28.0",
"@esbuild/win32-ia32": "0.28.0",
"@esbuild/win32-x64": "0.28.0"
}
},
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/urlpattern-polyfill": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz",
"integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==",
"dev": true,
"license": "MIT"
}
}
}
@@ -1,5 +1,5 @@
{
"name": "@immich/plugin-core",
"name": "plugins",
"version": "1.0.0",
"description": "",
"main": "src/index.ts",
@@ -13,7 +13,6 @@
"license": "AGPL-3.0",
"devDependencies": {
"@extism/js-pdk": "^1.0.1",
"@immich/plugin-sdk": "workspace:*",
"esbuild": "^0.28.0",
"typescript": "^6.0.0"
}
+12
View File
@@ -0,0 +1,12 @@
declare module 'main' {
export function filterFileName(): I32;
export function actionAddToAlbum(): I32;
export function actionArchive(): I32;
}
declare module 'extism:host' {
interface user {
updateAsset(ptr: PTR): I32;
addAssetToAlbum(ptr: PTR): I32;
}
}
+71
View File
@@ -0,0 +1,71 @@
const { updateAsset, addAssetToAlbum } = Host.getFunctions();
function parseInput() {
return JSON.parse(Host.inputString());
}
function returnOutput(output: any) {
Host.outputString(JSON.stringify(output));
return 0;
}
export function filterFileName() {
const input = parseInput();
const { data, config } = input;
const { pattern, matchType = 'contains', caseSensitive = false } = config;
const fileName = data.asset.originalFileName || data.asset.fileName || '';
const searchName = caseSensitive ? fileName : fileName.toLowerCase();
const searchPattern = caseSensitive ? pattern : pattern.toLowerCase();
let passed = false;
if (matchType === 'exact') {
passed = searchName === searchPattern;
} else if (matchType === 'regex') {
const flags = caseSensitive ? '' : 'i';
const regex = new RegExp(searchPattern, flags);
passed = regex.test(fileName);
} else {
// contains
passed = searchName.includes(searchPattern);
}
return returnOutput({ passed });
}
export function actionAddToAlbum() {
const input = parseInput();
const { authToken, config, data } = input;
const { albumId } = config;
const ptr = Memory.fromString(
JSON.stringify({
authToken,
assetId: data.asset.id,
albumId: albumId,
}),
);
addAssetToAlbum(ptr.offset);
ptr.free();
return returnOutput({ success: true });
}
export function actionArchive() {
const input = parseInput();
const { authToken, data } = input;
const ptr = Memory.fromString(
JSON.stringify({
authToken,
id: data.asset.id,
visibility: 'archive',
}),
);
updateAsset(ptr.offset);
ptr.free();
return returnOutput({ success: true });
}
@@ -1,24 +1,20 @@
{
"compilerOptions": {
"allowJs": true, // Allow JavaScript files to be compiled
"declaration": true,
"emitDeclarationOnly": true,
"esModuleInterop": true, // Enables compatibility with Babel-style module imports
"lib": ["es2020"], // Specify a list of library files to be included in the compilation
"module": "nodenext", // Specify module code generation
"moduleResolution": "nodenext",
"noEmit": true, // Do not emit outputs (no .js or .d.ts files)
"outDir": "./dist",
"rootDir": "./src",
"skipLibCheck": true, // Skip type checking of declaration files
"strict": true, // Enable all strict type-checking options
"target": "es2020", // Specify ECMAScript target version
"types": ["./src/index.d.ts", "./node_modules/@extism/js-pdk"] // Specify a list of type definition files to be included in the compilation
"module": "commonjs", // Specify module code generation
"lib": ["es2020"], // Specify a list of library files to be included in the compilation
"types": ["./src/index.d.ts", "./node_modules/@extism/js-pdk"], // Specify a list of type definition files to be included in the compilation
"strict": true, // Enable all strict type-checking options
"esModuleInterop": true, // Enables compatibility with Babel-style module imports
"skipLibCheck": true, // Skip type checking of declaration files
"allowJs": true, // Allow JavaScript files to be compiled
"rootDir": "./src",
"noEmit": true // Do not emit outputs (no .js or .d.ts files)
},
"exclude": [
"node_modules" // Exclude the node_modules directory
],
"include": [
"src/**/*.ts" // Include all TypeScript files in src directory
],
"exclude": [
"node_modules" // Exclude the node_modules directory
]
}
+141 -150
View File
@@ -1478,33 +1478,72 @@ export type PersonStatisticsResponseDto = {
/** Number of assets */
assets: number;
};
export type PluginMethodResponseDto = {
/** Description */
export type PluginJsonSchemaProperty = {
additionalProperties?: boolean | PluginJsonSchemaProperty;
"default"?: any;
description?: string;
"enum"?: string[];
items?: PluginJsonSchemaProperty;
properties?: {
[key: string]: PluginJsonSchemaProperty;
};
required?: string[];
"type"?: PluginJsonSchemaType;
};
export type PluginJsonSchema = {
additionalProperties?: boolean;
description?: string;
properties?: {
[key: string]: PluginJsonSchemaProperty;
};
required?: string[];
"type"?: PluginJsonSchemaType;
};
export type PluginActionResponseDto = {
/** Action description */
description: string;
hostFunctions: boolean;
/** Key */
key: string;
/** Name */
name: string;
schema?: {};
/** Title */
/** Action ID */
id: string;
/** Method name */
methodName: string;
/** Plugin ID */
pluginId: string;
/** Action schema */
schema: (PluginJsonSchema) | null;
/** Supported contexts */
supportedContexts: PluginContextType[];
/** Action title */
title: string;
};
export type PluginFilterResponseDto = {
/** Filter description */
description: string;
/** Filter ID */
id: string;
/** Method name */
methodName: string;
/** Plugin ID */
pluginId: string;
/** Filter schema */
schema: (PluginJsonSchema) | null;
/** Supported contexts */
supportedContexts: PluginContextType[];
/** Filter title */
title: string;
/** Workflow types */
types: WorkflowType[];
/** Ui hints */
uiHints: string[];
};
export type PluginResponseDto = {
/** Plugin actions */
actions: PluginActionResponseDto[];
/** Plugin author */
author: string;
/** Creation date */
createdAt: string;
/** Plugin description */
description: string;
/** Plugin filters */
filters: PluginFilterResponseDto[];
/** Plugin ID */
id: string;
/** Plugin methods */
methods: PluginMethodResponseDto[];
/** Plugin name */
name: string;
/** Plugin title */
@@ -1514,6 +1553,10 @@ export type PluginResponseDto = {
/** Plugin version */
version: string;
};
export type PluginTriggerResponseDto = {
contextType: PluginContextType;
"type": PluginTriggerType;
};
export type QueueResponseDto = {
/** Whether the queue is paused */
isPaused: boolean;
@@ -2667,81 +2710,89 @@ export type CreateProfileImageResponseDto = {
/** User ID */
userId: string;
};
export type WorkflowStepDto = {
/** Step configuration */
config: {
[key: string]: any;
} | null;
/** Step is enabled */
enabled?: boolean;
/** Step plugin method */
method: string;
export type PluginConfigValue = any;
export type WorkflowActionConfig = {
[key: string]: PluginConfigValue;
};
export type WorkflowActionResponseDto = {
actionConfig: (WorkflowActionConfig) | null;
/** Action ID */
id: string;
/** Action order */
order: number;
/** Plugin action ID */
pluginActionId: string;
/** Workflow ID */
workflowId: string;
};
export type WorkflowFilterConfig = {
[key: string]: PluginConfigValue;
};
export type WorkflowFilterResponseDto = {
filterConfig: (WorkflowFilterConfig) | null;
/** Filter ID */
id: string;
/** Filter order */
order: number;
/** Plugin filter ID */
pluginFilterId: string;
/** Workflow ID */
workflowId: string;
};
export type WorkflowResponseDto = {
/** Workflow actions */
actions: WorkflowActionResponseDto[];
/** Creation date */
createdAt: string;
/** Workflow description */
description: string | null;
description: string;
/** Workflow enabled */
enabled: boolean;
/** Workflow filters */
filters: WorkflowFilterResponseDto[];
/** Workflow ID */
id: string;
/** Workflow name */
name: string | null;
/** Workflow steps */
steps: WorkflowStepDto[];
/** Workflow trigger type */
trigger: WorkflowTrigger;
/** Update date */
updatedAt: string;
/** Owner user ID */
ownerId: string;
triggerType: PluginTriggerType;
};
export type WorkflowActionItemDto = {
actionConfig?: WorkflowActionConfig;
/** Plugin action ID */
pluginActionId: string;
};
export type WorkflowFilterItemDto = {
filterConfig?: WorkflowFilterConfig;
/** Plugin filter ID */
pluginFilterId: string;
};
export type WorkflowCreateDto = {
/** Workflow actions */
actions: WorkflowActionItemDto[];
/** Workflow description */
description?: string | null;
description?: string;
/** Workflow enabled */
enabled?: boolean;
/** Workflow filters */
filters: WorkflowFilterItemDto[];
/** Workflow name */
name?: string | null;
steps?: WorkflowStepDto[];
/** Workflow trigger type */
trigger: WorkflowTrigger;
};
export type WorkflowTriggerResponseDto = {
/** Trigger type */
trigger: WorkflowTrigger;
/** Workflow types */
types: WorkflowType[];
name: string;
triggerType: PluginTriggerType;
};
export type WorkflowUpdateDto = {
/** Workflow actions */
actions?: WorkflowActionItemDto[];
/** Workflow description */
description?: string | null;
description?: string;
/** Workflow enabled */
enabled?: boolean;
/** Workflow filters */
filters?: WorkflowFilterItemDto[];
/** Workflow name */
name?: string | null;
steps?: WorkflowStepDto[];
/** Workflow trigger type */
trigger?: WorkflowTrigger;
};
export type WorkflowShareStepDto = {
/** Step configuration */
config: {
[key: string]: any;
} | null;
/** Step is enabled */
enabled?: boolean;
/** Step plugin method */
method: string;
};
export type WorkflowShareResponseDto = {
/** Workflow description */
description: string | null;
/** Workflow name */
name: string | null;
/** Workflow steps */
steps: WorkflowShareStepDto[];
/** Workflow trigger type */
trigger: WorkflowTrigger;
name?: string;
triggerType?: PluginTriggerType;
};
export type LicenseResponseDto = UserLicense;
export type SyncAckV1 = {};
@@ -5189,56 +5240,22 @@ export function getPersonThumbnail({ id }: {
/**
* List all plugins
*/
export function searchPlugins({ description, enabled, id, name, title, version }: {
description?: string;
enabled?: boolean;
id?: string;
name?: string;
title?: string;
version?: string;
}, opts?: Oazapfts.RequestOpts) {
export function getPlugins(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PluginResponseDto[];
}>(`/plugins${QS.query(QS.explode({
description,
enabled,
id,
name,
title,
version
}))}`, {
}>("/plugins", {
...opts
}));
}
/**
* Retrieve plugin methods
* List all plugin triggers
*/
export function searchPluginMethods({ description, enabled, id, name, pluginName, pluginVersion, title, trigger, $type }: {
description?: string;
enabled?: boolean;
id?: string;
name?: string;
pluginName?: string;
pluginVersion?: string;
title?: string;
trigger?: WorkflowTrigger;
$type?: WorkflowType;
}, opts?: Oazapfts.RequestOpts) {
export function getPluginTriggers(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PluginMethodResponseDto[];
}>(`/plugins/methods${QS.query(QS.explode({
description,
enabled,
id,
name,
pluginName,
pluginVersion,
title,
trigger,
"type": $type
}))}`, {
data: PluginTriggerResponseDto[];
}>("/plugins/triggers", {
...opts
}));
}
@@ -6614,23 +6631,11 @@ export function getUniqueOriginalPaths(opts?: Oazapfts.RequestOpts) {
/**
* List all workflows
*/
export function searchWorkflows({ description, enabled, id, name, trigger }: {
description?: string;
enabled?: boolean;
id?: string;
name?: string;
trigger?: WorkflowTrigger;
}, opts?: Oazapfts.RequestOpts) {
export function getWorkflows(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: WorkflowResponseDto[];
}>(`/workflows${QS.query(QS.explode({
description,
enabled,
id,
name,
trigger
}))}`, {
}>("/workflows", {
...opts
}));
}
@@ -6649,17 +6654,6 @@ export function createWorkflow({ workflowCreateDto }: {
body: workflowCreateDto
})));
}
/**
* List all workflow triggers
*/
export function getWorkflowTriggers(opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: WorkflowTriggerResponseDto[];
}>("/workflows/triggers", {
...opts
}));
}
/**
* Delete a workflow
*/
@@ -6700,19 +6694,6 @@ export function updateWorkflow({ id, workflowUpdateDto }: {
body: workflowUpdateDto
})));
}
/**
* Retrieve a workflow
*/
export function getWorkflowForShare({ id }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: WorkflowShareResponseDto;
}>(`/workflows/${encodeURIComponent(id)}/share`, {
...opts
}));
}
export enum ReactionLevel {
Album = "album",
Asset = "asset"
@@ -7036,11 +7017,21 @@ export enum PartnerDirection {
SharedBy = "shared-by",
SharedWith = "shared-with"
}
export enum WorkflowType {
AssetV1 = "AssetV1",
AssetPersonV1 = "AssetPersonV1"
export enum PluginJsonSchemaType {
String = "string",
Number = "number",
Integer = "integer",
Boolean = "boolean",
Object = "object",
Array = "array",
Null = "null"
}
export enum WorkflowTrigger {
export enum PluginContextType {
Asset = "asset",
Album = "album",
Person = "person"
}
export enum PluginTriggerType {
AssetCreate = "AssetCreate",
PersonRecognized = "PersonRecognized"
}
@@ -7107,7 +7098,7 @@ export enum JobName {
VersionCheck = "VersionCheck",
OcrQueueAll = "OcrQueueAll",
Ocr = "Ocr",
WorkflowAssetCreate = "WorkflowAssetCreate"
WorkflowRun = "WorkflowRun"
}
export enum SearchSuggestionType {
Country = "country",
+1 -75
View File
@@ -312,14 +312,11 @@ importers:
specifier: ^4.20.6
version: 4.21.0
packages/plugin-core:
packages/plugins:
devDependencies:
'@extism/js-pdk':
specifier: ^1.0.1
version: 1.1.1
'@immich/plugin-sdk':
specifier: workspace:*
version: link:../plugin-sdk
esbuild:
specifier: ^0.28.0
version: 0.28.0
@@ -327,24 +324,6 @@ importers:
specifier: ^6.0.0
version: 6.0.3
packages/plugin-sdk:
devDependencies:
'@extism/js-pdk':
specifier: ^1.1.1
version: 1.1.1
'@types/node':
specifier: ^24.11.0
version: 24.12.2
esbuild:
specifier: ^0.27.3
version: 0.27.4
tsc-alias:
specifier: ^1.8.16
version: 1.8.16
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/sdk:
dependencies:
'@oazapfts/runtime':
@@ -363,9 +342,6 @@ importers:
'@extism/extism':
specifier: 2.0.0-rc13
version: 2.0.0-rc13
'@immich/plugin-sdk':
specifier: workspace:*
version: link:../packages/plugin-sdk
'@immich/sql-tools':
specifier: ^0.5.1
version: 0.5.2
@@ -477,9 +453,6 @@ importers:
fluent-ffmpeg:
specifier: ^2.1.2
version: 2.1.3
generic-pool:
specifier: ^3.9.0
version: 3.9.0
geo-tz:
specifier: ^8.0.0
version: 8.1.6
@@ -6473,10 +6446,6 @@ packages:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
commander@9.5.0:
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
engines: {node: ^12.20.0 || >=14}
comment-json@5.0.0:
resolution: {integrity: sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==}
engines: {node: '>= 6'}
@@ -7848,10 +7817,6 @@ packages:
engines: {node: '>=10'}
deprecated: This package is no longer supported.
generic-pool@3.9.0:
resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
engines: {node: '>= 4'}
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
@@ -9497,10 +9462,6 @@ packages:
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
engines: {node: ^18.17.0 || >=20.5.0}
mylas@2.1.14:
resolution: {integrity: sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==}
engines: {node: '>=16.0.0'}
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
@@ -10039,10 +10000,6 @@ packages:
engines: {node: '>=18'}
hasBin: true
plimit-lit@1.6.1:
resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==}
engines: {node: '>=12'}
pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
@@ -10685,10 +10642,6 @@ packages:
resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==}
engines: {node: '>=0.6'}
queue-lit@1.5.2:
resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==}
engines: {node: '>=12'}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -11915,11 +11868,6 @@ packages:
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
tsc-alias@1.8.16:
resolution: {integrity: sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==}
engines: {node: '>=16.20.2'}
hasBin: true
tsconfck@3.1.6:
resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==}
engines: {node: ^18 || >=20}
@@ -19050,8 +18998,6 @@ snapshots:
commander@8.3.0: {}
commander@9.5.0: {}
comment-json@5.0.0:
dependencies:
array-timsort: 1.0.3
@@ -20697,8 +20643,6 @@ snapshots:
strip-ansi: 6.0.1
wide-align: 1.1.5
generic-pool@3.9.0: {}
gensync@1.0.0-beta.2: {}
geo-coordinates-parser@1.7.4: {}
@@ -22740,8 +22684,6 @@ snapshots:
mute-stream@2.0.0: {}
mylas@2.1.14: {}
mz@2.7.0:
dependencies:
any-promise: 1.3.0
@@ -23299,10 +23241,6 @@ snapshots:
optionalDependencies:
fsevents: 2.3.2
plimit-lit@1.6.1:
dependencies:
queue-lit: 1.5.2
pluralize@8.0.0: {}
pmtiles@3.2.1:
@@ -23987,8 +23925,6 @@ snapshots:
dependencies:
side-channel: 1.1.0
queue-lit@1.5.2: {}
queue-microtask@1.2.3: {}
quick-lru@5.1.1: {}
@@ -25582,16 +25518,6 @@ snapshots:
ts-interface-checker@0.1.13: {}
tsc-alias@1.8.16:
dependencies:
chokidar: 3.6.0
commander: 9.5.0
get-tsconfig: 4.13.0
globby: 11.1.0
mylas: 2.1.14
normalize-path: 3.0.0
plimit-lit: 1.6.1
tsconfck@3.1.6(typescript@6.0.3):
optionalDependencies:
typescript: 6.0.3
-1
View File
@@ -7,7 +7,6 @@ packages:
- plugins
- web
- .github
- packages/*
ignoredBuiltDependencies:
- '@nestjs/core'
- '@parcel/watcher'
+10 -15
View File
@@ -13,13 +13,12 @@ FROM builder AS server
WORKDIR /usr/src/app
COPY ./server ./server/
COPY ./packages/plugin-sdk ./packages/plugin-sdk/
RUN --mount=type=cache,id=pnpm-server,target=/buildcache/pnpm-store \
--mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \
--mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
--mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \
SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich --filter @immich/plugin-sdk --frozen-lockfile build && \
SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile build && \
SHARP_FORCE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile --prod --no-optional deploy /output/server-pruned
FROM builder AS web
@@ -57,26 +56,22 @@ ARG TARGETPLATFORM
COPY --from=ghcr.io/jdx/mise:2026.3.12@sha256:0210678cbf58413806531a27adb2c7daf1c37238e56e8f7ea381d73521571775 /usr/local/bin/mise /usr/local/bin/mise
WORKDIR /app
COPY ./mise.toml ./mise.toml
COPY ./packages/plugin-core/mise.toml ./packages/plugin-core/
ENV MISE_TRUSTED_CONFIG_PATHS=/app/mise.toml
WORKDIR /usr/src/app
COPY ./packages/plugins/mise.toml ./packages/plugins/
ENV MISE_TRUSTED_CONFIG_PATHS=/usr/src/app/packages/plugins/mise.toml
ENV MISE_DATA_DIR=/buildcache/mise
ENV MISE_DISABLE_TOOLS=flutter
RUN --mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \
mise install
COPY ./packages/plugin-core ./packages/plugin-core/
COPY ./packages/plugin-sdk ./packages/plugin-sdk/
mise install --cd packages/plugins
COPY ./packages/plugins ./packages/plugins/
# Build plugins
RUN --mount=type=cache,id=pnpm-packages,target=/buildcache/pnpm-store \
RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \
--mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \
--mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
--mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \
--mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \
mise //:plugins
cd packages/plugins && mise run build
FROM ghcr.io/immich-app/base-server-prod:202605051129@sha256:50f7ffe4ed31e360c90c4905bd5f6658f2a121297544e3fe9368e338b3f76bcd
@@ -88,8 +83,8 @@ ENV NODE_ENV=production \
COPY --from=server /output/server-pruned ./server
COPY --from=web /usr/src/app/web/build /build/www
COPY --from=cli /output/cli-pruned ./cli
COPY --from=plugins /app/packages/plugin-core/dist /build/plugins/immich-core-plugin/dist
COPY --from=plugins /app/packages/plugin-core/manifest.json /build/plugins/immich-core-plugin/manifest.json
COPY --from=plugins /usr/src/app/packages/plugins/dist /build/corePlugin/dist
COPY --from=plugins /usr/src/app/packages/plugins/manifest.json /build/corePlugin/manifest.json
RUN ln -s ../../cli/bin/immich server/bin/immich
COPY LICENSE /licenses/LICENSE.txt
COPY LICENSE /LICENSE
-2
View File
@@ -58,7 +58,6 @@ run = "email dev -p 3050 --dir src/emails"
[tasks.ci-unit]
run = [
{ task = ":install" },
{ task = "//:plugins" },
{ task = ":format" },
{ task = ":lint" },
{ task = ":check" },
@@ -68,7 +67,6 @@ run = [
[tasks.ci-medium]
run = [
{ task = ":install" },
{ task = "//packages/plugin-core:build" },
{ task = ":test-medium --run" },
]
-2
View File
@@ -37,7 +37,6 @@
},
"dependencies": {
"@extism/extism": "2.0.0-rc13",
"@immich/plugin-sdk": "workspace:*",
"@immich/sql-tools": "^0.5.1",
"@nestjs/bullmq": "^11.0.1",
"@nestjs/common": "^11.0.4",
@@ -75,7 +74,6 @@
"express": "^5.1.0",
"fast-glob": "^3.3.2",
"fluent-ffmpeg": "^2.1.2",
"generic-pool": "^3.9.0",
"geo-tz": "^8.0.0",
"handlebars": "^4.7.8",
"helmet": "^8.1.0",
@@ -1,56 +0,0 @@
import { PluginController } from 'src/controllers/plugin.controller';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { PluginService } from 'src/services/plugin.service';
import request from 'supertest';
import { errorDto } from 'test/medium/responses';
import { factory } from 'test/small.factory';
import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils';
describe(PluginController.name, () => {
let ctx: ControllerContext;
const service = mockBaseService(PluginService);
beforeAll(async () => {
ctx = await controllerSetup(PluginController, [
{ provide: PluginService, useValue: service },
{ provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) },
]);
return () => ctx.close();
});
beforeEach(() => {
service.resetAllMocks();
ctx.reset();
});
describe('GET /plugins', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/plugins');
expect(ctx.authenticate).toHaveBeenCalled();
});
it(`should require id to be a uuid`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.get(`/plugins`)
.query({ id: 'invalid' })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }]));
});
});
describe('GET /plugins/:id', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get(`/plugins/${factory.uuid()}`);
expect(ctx.authenticate).toHaveBeenCalled();
});
it(`should require id to be a uuid`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.get(`/plugins/invalid`)
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }]));
});
});
});
+17 -22
View File
@@ -1,12 +1,7 @@
import { Controller, Get, Param, Query } from '@nestjs/common';
import { Controller, Get, Param } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import {
PluginMethodResponseDto,
PluginMethodSearchDto,
PluginResponseDto,
PluginSearchDto,
} from 'src/dtos/plugin.dto';
import { PluginResponseDto, PluginTriggerResponseDto } from 'src/dtos/plugin.dto';
import { Permission } from 'src/enum';
import { Authenticated } from 'src/middleware/auth.guard';
import { PluginService } from 'src/services/plugin.service';
@@ -17,26 +12,26 @@ import { UUIDParamDto } from 'src/validation';
export class PluginController {
constructor(private service: PluginService) {}
@Get('triggers')
@Authenticated({ permission: Permission.PluginRead })
@Endpoint({
summary: 'List all plugin triggers',
description: 'Retrieve a list of all available plugin triggers.',
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
})
getPluginTriggers(): PluginTriggerResponseDto[] {
return this.service.getTriggers();
}
@Get()
@Authenticated({ permission: Permission.PluginRead })
@Endpoint({
summary: 'List all plugins',
description: 'Retrieve a list of plugins available to the authenticated user.',
history: HistoryBuilder.v3(),
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
})
searchPlugins(@Query() dto: PluginSearchDto): Promise<PluginResponseDto[]> {
return this.service.search(dto);
}
@Get('methods')
@Authenticated({ permission: Permission.PluginRead })
@Endpoint({
summary: 'Retrieve plugin methods',
description: 'Retrieve a list of plugin methods',
history: HistoryBuilder.v3(),
})
searchPluginMethods(@Query() dto: PluginMethodSearchDto): Promise<PluginMethodResponseDto[]> {
return this.service.searchMethods(dto);
getPlugins(): Promise<PluginResponseDto[]> {
return this.service.getAll();
}
@Get(':id')
@@ -44,7 +39,7 @@ export class PluginController {
@Endpoint({
summary: 'Retrieve a plugin',
description: 'Retrieve information about a specific plugin by its ID.',
history: HistoryBuilder.v3(),
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
})
getPlugin(@Param() { id }: UUIDParamDto): Promise<PluginResponseDto> {
return this.service.get(id);
@@ -1,113 +0,0 @@
import { WorkflowController } from 'src/controllers/workflow.controller';
import { WorkflowTrigger } from 'src/enum';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { WorkflowService } from 'src/services/workflow.service';
import request from 'supertest';
import { errorDto } from 'test/medium/responses';
import { factory } from 'test/small.factory';
import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils';
describe(WorkflowController.name, () => {
let ctx: ControllerContext;
const service = mockBaseService(WorkflowService);
beforeAll(async () => {
ctx = await controllerSetup(WorkflowController, [
{ provide: WorkflowService, useValue: service },
{ provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) },
]);
return () => ctx.close();
});
beforeEach(() => {
service.resetAllMocks();
ctx.reset();
});
describe('POST /workflows', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).post('/workflows').send({});
expect(ctx.authenticate).toHaveBeenCalled();
});
it(`should require a valid trigger`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.post(`/workflows`)
.send({ trigger: 'invalid' })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([
{ path: ['trigger'], message: expect.stringContaining('Invalid option: expected one of') },
]),
);
});
it(`should require a valid enabled value`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.post(`/workflows`)
.send({ enabled: 'invalid' })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([{ path: ['enabled'], message: 'Invalid input: expected boolean, received string' }]),
);
});
it(`should not require a name`, async () => {
const { status } = await request(ctx.getHttpServer())
.post(`/workflows`)
.send({ trigger: WorkflowTrigger.AssetCreate })
.set('Authorization', `Bearer token`);
expect(status).toBe(201);
expect(service.create).toHaveBeenCalled();
});
});
describe('GET /workflows', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get('/workflows');
expect(ctx.authenticate).toHaveBeenCalled();
});
it(`should require id to be a uuid`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.get(`/workflows`)
.query({ id: 'invalid' })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }]));
});
});
describe('GET /workflows/:id', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).get(`/workflows/${factory.uuid()}`);
expect(ctx.authenticate).toHaveBeenCalled();
});
it(`should require id to be a uuid`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.get(`/workflows/invalid`)
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }]));
});
});
describe('PUT /workflows/:id', () => {
it('should be an authenticated route', async () => {
await request(ctx.getHttpServer()).put(`/workflows/${factory.uuid()}`).send({});
expect(ctx.authenticate).toHaveBeenCalled();
});
it(`should require id to be a uuid`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.put(`/workflows/invalid`)
.set('Authorization', `Bearer token`)
.send({});
expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }]));
});
});
});
+9 -38
View File
@@ -1,15 +1,8 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import {
WorkflowCreateDto,
WorkflowResponseDto,
WorkflowSearchDto,
WorkflowShareResponseDto,
WorkflowTriggerResponseDto,
WorkflowUpdateDto,
} from 'src/dtos/workflow.dto';
import { WorkflowCreateDto, WorkflowResponseDto, WorkflowUpdateDto } from 'src/dtos/workflow.dto';
import { Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { WorkflowService } from 'src/services/workflow.service';
@@ -25,7 +18,7 @@ export class WorkflowController {
@Endpoint({
summary: 'Create a workflow',
description: 'Create a new workflow, the workflow can also be created with empty filters and actions.',
history: HistoryBuilder.v3(),
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
})
createWorkflow(@Auth() auth: AuthDto, @Body() dto: WorkflowCreateDto): Promise<WorkflowResponseDto> {
return this.service.create(auth, dto);
@@ -36,21 +29,10 @@ export class WorkflowController {
@Endpoint({
summary: 'List all workflows',
description: 'Retrieve a list of workflows available to the authenticated user.',
history: HistoryBuilder.v3(),
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
})
searchWorkflows(@Auth() auth: AuthDto, @Query() dto: WorkflowSearchDto): Promise<WorkflowResponseDto[]> {
return this.service.search(auth, dto);
}
@Get('triggers')
@Authenticated({ permission: false })
@Endpoint({
summary: 'List all workflow triggers',
description: 'Retrieve a list of all available workflow triggers.',
history: HistoryBuilder.v3(),
})
getWorkflowTriggers(): WorkflowTriggerResponseDto[] {
return this.service.getTriggers();
getWorkflows(@Auth() auth: AuthDto): Promise<WorkflowResponseDto[]> {
return this.service.getAll(auth);
}
@Get(':id')
@@ -58,30 +40,19 @@ export class WorkflowController {
@Endpoint({
summary: 'Retrieve a workflow',
description: 'Retrieve information about a specific workflow by its ID.',
history: HistoryBuilder.v3(),
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
})
getWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<WorkflowResponseDto> {
return this.service.get(auth, id);
}
@Get(':id/share')
@Authenticated({ permission: Permission.WorkflowRead })
@Endpoint({
summary: 'Retrieve a workflow',
description: 'Retrieve a workflow details without ids, default values, etc.',
history: HistoryBuilder.v3(),
})
getWorkflowForShare(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<WorkflowShareResponseDto> {
return this.service.share(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.WorkflowUpdate })
@Endpoint({
summary: 'Update a workflow',
description:
'Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc.',
history: HistoryBuilder.v3(),
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
})
updateWorkflow(
@Auth() auth: AuthDto,
@@ -97,7 +68,7 @@ export class WorkflowController {
@Endpoint({
summary: 'Delete a workflow',
description: 'Delete a workflow by its ID.',
history: HistoryBuilder.v3(),
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
})
deleteWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
+52 -37
View File
@@ -8,6 +8,8 @@ import {
ChecksumAlgorithm,
MemoryType,
Permission,
PluginContext,
PluginTriggerType,
SharedLinkType,
SourceType,
UserAvatarColor,
@@ -16,8 +18,10 @@ import {
import { AlbumTable } from 'src/schema/tables/album.table';
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
import { AssetTable } from 'src/schema/tables/asset.table';
import { PluginTable } from 'src/schema/tables/plugin.table';
import { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table';
import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table';
import { UserMetadataItem } from 'src/types';
import type { ActionConfig, FilterConfig, JSONSchema } from 'src/types/plugin-schema.types';
export type AuthUser = {
id: string;
@@ -272,7 +276,42 @@ export type AssetFace = {
isVisible: boolean;
};
export type Plugin = Selectable<PluginTable>;
export type PluginFilter = Selectable<PluginFilterTable> & {
methodName: string;
title: string;
description: string;
supportedContexts: PluginContext[];
schema: JSONSchema | null;
};
export type PluginAction = Selectable<PluginActionTable> & {
methodName: string;
title: string;
description: string;
supportedContexts: PluginContext[];
schema: JSONSchema | null;
};
export type Workflow = Selectable<WorkflowTable> & {
triggerType: PluginTriggerType;
name: string | null;
description: string;
enabled: boolean;
};
export type WorkflowFilter = Selectable<WorkflowFilterTable> & {
workflowId: string;
pluginFilterId: string;
filterConfig: FilterConfig | null;
order: number;
};
export type WorkflowAction = Selectable<WorkflowActionTable> & {
workflowId: string;
pluginActionId: string;
actionConfig: ActionConfig | null;
order: number;
};
const userColumns = ['id', 'name', 'email', 'avatarColor', 'profileImagePath', 'profileChangedAt'] as const;
const userWithPrefixColumns = [
@@ -304,32 +343,6 @@ export const columns = {
'asset.height',
'asset.isEdited',
],
workflowAssetV1: [
'asset.id',
'asset.ownerId',
'asset.stackId',
'asset.livePhotoVideoId',
'asset.libraryId',
'asset.duplicateId',
'asset.createdAt',
'asset.updatedAt',
'asset.deletedAt',
'asset.fileCreatedAt',
'asset.fileModifiedAt',
'asset.localDateTime',
'asset.type',
'asset.status',
'asset.visibility',
'asset.duration',
'asset.checksum',
'asset.originalPath',
'asset.originalFileName',
'asset.isOffline',
'asset.isFavorite',
'asset.isExternal',
'asset.isEdited',
'asset.isFavorite',
],
assetFiles: ['asset_file.id', 'asset_file.path', 'asset_file.type', 'asset_file.isEdited'],
assetFilesForThumbnail: [
'asset_file.id',
@@ -361,15 +374,6 @@ export const columns = {
tag: ['tag.id', 'tag.value', 'tag.createdAt', 'tag.updatedAt', 'tag.color', 'tag.parentId'],
apiKey: ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'],
notification: ['id', 'createdAt', 'level', 'type', 'title', 'description', 'data', 'readAt'],
pluginMethod: [
'plugin_method.name',
'plugin_method.title',
'plugin_method.description',
'plugin_method.types',
'plugin_method.schema',
'plugin_method.hostFunctions',
'plugin_method.uiHints',
],
syncAsset: [
'asset.id',
'asset.ownerId',
@@ -483,6 +487,17 @@ export const columns = {
'asset_exif.tags',
'asset_exif.timeZone',
],
plugin: [
'plugin.id as id',
'plugin.name as name',
'plugin.title as title',
'plugin.description as description',
'plugin.author as author',
'plugin.version as version',
'plugin.wasmPath as wasmPath',
'plugin.createdAt as createdAt',
'plugin.updatedAt as updatedAt',
],
} as const;
export type LockableProperty = (typeof lockableProperties)[number];
-4
View File
@@ -200,10 +200,6 @@ export class HistoryBuilder {
private hasDeprecated = false;
private items: HistoryEntry[] = [];
static v3() {
return new HistoryBuilder().added('v3.0.0');
}
added(version: string, description?: string) {
return this.push({ version, state: 'Added', description });
}
-32
View File
@@ -1,32 +0,0 @@
import { createZodDto } from 'nestjs-zod';
import z from 'zod';
export const JsonSchemaTypeSchema = z
.enum(['string', 'number', 'integer', 'boolean', 'object'])
.meta({ id: 'JsonSchemaType' });
const JsonSchemaPropertySchema = z
.object({
type: JsonSchemaTypeSchema.optional().default('object').describe('Type'),
title: z.string().describe('Title'),
description: z.string().describe('Description'),
default: z.any().optional().describe('Default value'),
enum: z.array(z.string()).optional().describe('Valid choices for enum types'),
array: z.boolean().optional().describe('Type is an array type'),
required: z.array(z.string()).optional().describe('A list of required properties'),
uiHint: z.string().optional(),
get properties() {
return z.record(z.string(), JsonSchemaPropertySchema).optional();
},
})
.meta({ id: 'JsonSchemaPropertyDto' });
export const JsonSchemaSchema = z
.object({
...JsonSchemaPropertySchema.shape,
title: z.string().optional().describe('Title'),
description: z.string().optional().describe('Description'),
})
.meta({ id: 'JsonSchemaDto' });
export class JsonSchemaDto extends createZodDto(JsonSchemaSchema) {}
+32 -21
View File
@@ -1,29 +1,39 @@
import { createZodDto } from 'nestjs-zod';
import { JsonSchemaSchema } from 'src/dtos/json-schema.dto';
import { WorkflowTypeSchema } from 'src/enum';
import { PluginContextSchema } from 'src/enum';
import { JSONSchemaSchema } from 'src/types/plugin-schema.types';
import z from 'zod';
const pluginNameRegex = /^[a-z0-9-]+[a-z0-9]$/;
const semverRegex =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
export const PluginManifestMethodSchemaSchema = JsonSchemaSchema.nullable()
.optional()
.transform((value) => (value && Object.keys(value).length === 0 ? null : value));
const PluginManifestMethodSchema = z
const PluginManifestWasmSchema = z
.object({
name: z.string().min(1).describe('Method name'),
title: z.string().min(1).describe('Method title'),
description: z.string().min(1).describe('Method description'),
types: z.array(WorkflowTypeSchema).min(1).describe('Workflow type'),
hostFunctions: z.boolean().optional().default(false).describe('Method uses host functions'),
schema: PluginManifestMethodSchemaSchema.describe('Schema'),
uiHints: z.array(z.string()).optional().describe('Ui hints, for example "filter"'),
path: z.string().describe('WASM file path'),
})
.meta({ id: 'PluginManifestMethodDto' });
.meta({ id: 'PluginManifestWasmDto' });
const PluginManifestSchema = z
const PluginManifestFilterSchema = z
.object({
methodName: z.string().describe('Filter method name'),
title: z.string().describe('Filter title'),
description: z.string().describe('Filter description'),
supportedContexts: z.array(PluginContextSchema).min(1).describe('Supported contexts'),
schema: JSONSchemaSchema.optional(),
})
.meta({ id: 'PluginManifestFilterDto' });
const PluginManifestActionSchema = z
.object({
methodName: z.string().describe('Action method name'),
title: z.string().describe('Action title'),
description: z.string().describe('Action description'),
supportedContexts: z.array(PluginContextSchema).min(1).describe('Supported contexts'),
schema: JSONSchemaSchema.optional(),
})
.meta({ id: 'PluginManifestActionDto' });
export const PluginManifestSchema = z
.object({
name: z
.string()
@@ -34,11 +44,12 @@ const PluginManifestSchema = z
)
.describe('Plugin name (lowercase, numbers, hyphens only)'),
version: z.string().regex(semverRegex).describe('Plugin version (semver)'),
title: z.string().min(1).describe('Plugin title'),
description: z.string().min(1).describe('Plugin description'),
wasmPath: z.string().min(1).describe('WASM file path'),
author: z.string().min(1).describe('Plugin author'),
methods: z.array(PluginManifestMethodSchema).optional().default([]).describe('Plugin methods'),
title: z.string().describe('Plugin title'),
description: z.string().describe('Plugin description'),
author: z.string().describe('Plugin author'),
wasm: PluginManifestWasmSchema,
filters: z.array(PluginManifestFilterSchema).optional().describe('Plugin filters'),
actions: z.array(PluginManifestActionSchema).optional().describe('Plugin actions'),
})
.meta({ id: 'PluginManifestDto' });
+38 -68
View File
@@ -1,33 +1,39 @@
import { createZodDto } from 'nestjs-zod';
import { JsonSchemaDto } from 'src/dtos/json-schema.dto';
import { WorkflowTriggerSchema, WorkflowType, WorkflowTypeSchema } from 'src/enum';
import { asMethodString } from 'src/utils/workflow';
import { PluginAction, PluginFilter } from 'src/database';
import { PluginContextSchema, PluginTriggerTypeSchema } from 'src/enum';
import { JSONSchemaSchema } from 'src/types/plugin-schema.types';
import z from 'zod';
const PluginSearchSchema = z
const PluginTriggerResponseSchema = z
.object({
id: z.uuidv4().optional().describe('Plugin ID'),
enabled: z.boolean().optional().describe('Whether the plugin is enabled'),
name: z.string().optional(),
version: z.string().optional(),
title: z.string().optional(),
description: z.string().optional(),
type: PluginTriggerTypeSchema,
contextType: PluginContextSchema,
})
.meta({ id: 'PluginSearchDto' });
.meta({ id: 'PluginTriggerResponseDto' });
const PluginMethodResponseSchema = z
const PluginFilterResponseSchema = z
.object({
key: z.string().describe('Key'),
name: z.string().describe('Name'),
title: z.string().describe('Title'),
description: z.string().describe('Description'),
types: z.array(WorkflowTypeSchema).describe('Workflow types'),
uiHints: z.array(z.string()).describe('Ui hints'),
// TODO fix this
schema: z.object().optional(),
hostFunctions: z.boolean(),
id: z.string().describe('Filter ID'),
pluginId: z.string().describe('Plugin ID'),
methodName: z.string().describe('Method name'),
title: z.string().describe('Filter title'),
description: z.string().describe('Filter description'),
supportedContexts: z.array(PluginContextSchema).describe('Supported contexts'),
schema: JSONSchemaSchema.nullable().describe('Filter schema'),
})
.meta({ id: 'PluginMethodResponseDto' });
.meta({ id: 'PluginFilterResponseDto' });
const PluginActionResponseSchema = z
.object({
id: z.string().describe('Action ID'),
pluginId: z.string().describe('Plugin ID'),
methodName: z.string().describe('Method name'),
title: z.string().describe('Action title'),
description: z.string().describe('Action description'),
supportedContexts: z.array(PluginContextSchema).describe('Supported contexts'),
schema: JSONSchemaSchema.nullable().describe('Action schema'),
})
.meta({ id: 'PluginActionResponseDto' });
const PluginResponseSchema = z
.object({
@@ -39,53 +45,29 @@ const PluginResponseSchema = z
version: z.string().describe('Plugin version'),
createdAt: z.string().describe('Creation date'),
updatedAt: z.string().describe('Last update date'),
methods: z.array(PluginMethodResponseSchema).describe('Plugin methods'),
filters: z.array(PluginFilterResponseSchema).describe('Plugin filters'),
actions: z.array(PluginActionResponseSchema).describe('Plugin actions'),
})
.meta({ id: 'PluginResponseDto' });
const PluginMethodSearchSchema = z
.object({
id: z.uuidv4().optional().describe('Plugin method ID'),
enabled: z.boolean().optional().describe('Whether the plugin method is enabled'),
name: z.string().optional(),
title: z.string().optional(),
description: z.string().optional(),
type: WorkflowTypeSchema.optional().describe('Workflow types'),
trigger: WorkflowTriggerSchema.optional().describe('Workflow trigger'),
pluginName: z.string().optional().describe('Plugin name'),
pluginVersion: z.string().optional().describe('Plugin version'),
})
.meta({ id: 'PluginMethodSearchDto' });
export class PluginSearchDto extends createZodDto(PluginSearchSchema) {}
export class PluginTriggerResponseDto extends createZodDto(PluginTriggerResponseSchema) {}
export class PluginResponseDto extends createZodDto(PluginResponseSchema) {}
export class PluginMethodSearchDto extends createZodDto(PluginMethodSearchSchema) {}
export class PluginMethodResponseDto extends createZodDto(PluginMethodResponseSchema) {}
type Plugin = {
type MapPlugin = {
id: string;
name: string;
title: string;
description: string;
author: string;
version: string;
wasmPath: string;
createdAt: Date;
updatedAt: Date;
methods: PluginMethod[];
filters: PluginFilter[];
actions: PluginAction[];
};
type PluginMethod = {
pluginName: string;
name: string;
title: string;
description: string;
types: WorkflowType[];
schema: JsonSchemaDto | null;
hostFunctions: boolean;
uiHints: string[];
};
export function mapPlugin(plugin: Plugin): PluginResponseDto {
export function mapPlugin(plugin: MapPlugin): PluginResponseDto {
return {
id: plugin.id,
name: plugin.name,
@@ -95,19 +77,7 @@ export function mapPlugin(plugin: Plugin): PluginResponseDto {
version: plugin.version,
createdAt: plugin.createdAt.toISOString(),
updatedAt: plugin.updatedAt.toISOString(),
methods: plugin.methods.map((method) => mapMethod(method)),
filters: plugin.filters,
actions: plugin.actions,
};
}
export const mapMethod = (method: PluginMethod): PluginMethodResponseDto => {
return {
key: asMethodString({ pluginName: method.pluginName, methodName: method.name }),
name: method.name,
title: method.title,
hostFunctions: method.hostFunctions,
uiHints: method.uiHints,
description: method.description,
types: method.types,
schema: method.schema as any,
};
};
+62 -96
View File
@@ -1,135 +1,101 @@
import type { WorkflowStepConfig } from '@immich/plugin-sdk';
import { createZodDto } from 'nestjs-zod';
import { WorkflowTrigger, WorkflowTriggerSchema, WorkflowTypeSchema } from 'src/enum';
import type { WorkflowAction, WorkflowFilter } from 'src/database';
import { PluginTriggerTypeSchema } from 'src/enum';
import { ActionConfigSchema, FilterConfigSchema } from 'src/types/plugin-schema.types';
import z from 'zod';
const WorkflowTriggerResponseSchema = z
const WorkflowFilterItemSchema = z
.object({
trigger: WorkflowTriggerSchema.describe('Trigger type'),
types: z.array(WorkflowTypeSchema).describe('Workflow types'),
pluginFilterId: z.uuidv4().describe('Plugin filter ID'),
filterConfig: FilterConfigSchema.optional(),
})
.meta({ id: 'WorkflowTriggerResponseDto' });
.meta({ id: 'WorkflowFilterItemDto' });
const WorkflowSearchSchema = z
const WorkflowActionItemSchema = z
.object({
id: z.uuidv4().optional().describe('Workflow ID'),
trigger: WorkflowTriggerSchema.optional().describe('Workflow trigger type'),
name: z.string().optional().describe('Workflow name'),
description: z.string().optional().describe('Workflow description'),
enabled: z.boolean().optional().describe('Workflow enabled'),
pluginActionId: z.uuidv4().describe('Plugin action ID'),
actionConfig: ActionConfigSchema.optional(),
})
.meta({ id: 'WorkflowSearchDto' });
const WorkflowStepSchema = z
.object({
method: z.string().describe('Step plugin method'),
config: z.record(z.string(), z.unknown()).nullable().describe('Step configuration'),
enabled: z.boolean().optional().describe('Step is enabled'),
})
.meta({ id: 'WorkflowStepDto' });
const WorkflowShareStepSchema = z
.object({
method: z.string().describe('Step plugin method'),
config: z.record(z.string(), z.unknown()).nullable().describe('Step configuration'),
enabled: z.boolean().optional().describe('Step is enabled'),
})
.meta({ id: 'WorkflowShareStepDto' });
.meta({ id: 'WorkflowActionItemDto' });
const WorkflowCreateSchema = z
.object({
trigger: WorkflowTriggerSchema.describe('Workflow trigger type'),
name: z.string().nullable().optional().describe('Workflow name'),
description: z.string().nullable().optional().describe('Workflow description'),
triggerType: PluginTriggerTypeSchema,
name: z.string().describe('Workflow name'),
description: z.string().optional().describe('Workflow description'),
enabled: z.boolean().optional().describe('Workflow enabled'),
steps: z.array(WorkflowStepSchema).optional(),
filters: z.array(WorkflowFilterItemSchema).describe('Workflow filters'),
actions: z.array(WorkflowActionItemSchema).describe('Workflow actions'),
})
.meta({ id: 'WorkflowCreateDto' });
const WorkflowUpdateSchema = z
.object({
trigger: WorkflowTriggerSchema.optional().describe('Workflow trigger type'),
name: z.string().nullable().optional().describe('Workflow name'),
description: z.string().nullable().optional().describe('Workflow description'),
triggerType: PluginTriggerTypeSchema.optional(),
name: z.string().optional().describe('Workflow name'),
description: z.string().optional().describe('Workflow description'),
enabled: z.boolean().optional().describe('Workflow enabled'),
steps: z.array(WorkflowStepSchema).optional(),
filters: z.array(WorkflowFilterItemSchema).optional().describe('Workflow filters'),
actions: z.array(WorkflowActionItemSchema).optional().describe('Workflow actions'),
})
.meta({ id: 'WorkflowUpdateDto' });
const WorkflowFilterResponseSchema = z
.object({
id: z.string().describe('Filter ID'),
workflowId: z.string().describe('Workflow ID'),
pluginFilterId: z.string().describe('Plugin filter ID'),
filterConfig: FilterConfigSchema.nullable(),
order: z.int().describe('Filter order'),
})
.meta({ id: 'WorkflowFilterResponseDto' });
const WorkflowActionResponseSchema = z
.object({
id: z.string().describe('Action ID'),
workflowId: z.string().describe('Workflow ID'),
pluginActionId: z.string().describe('Plugin action ID'),
actionConfig: ActionConfigSchema.nullable(),
order: z.int().describe('Action order'),
})
.meta({ id: 'WorkflowActionResponseDto' });
const WorkflowResponseSchema = z
.object({
id: z.string().describe('Workflow ID'),
trigger: WorkflowTriggerSchema.describe('Workflow trigger type'),
ownerId: z.string().describe('Owner user ID'),
triggerType: PluginTriggerTypeSchema,
name: z.string().nullable().describe('Workflow name'),
description: z.string().nullable().describe('Workflow description'),
description: z.string().describe('Workflow description'),
createdAt: z.string().describe('Creation date'),
updatedAt: z.string().describe('Update date'),
enabled: z.boolean().describe('Workflow enabled'),
steps: z.array(WorkflowStepSchema).describe('Workflow steps'),
filters: z.array(WorkflowFilterResponseSchema).describe('Workflow filters'),
actions: z.array(WorkflowActionResponseSchema).describe('Workflow actions'),
})
.meta({ id: 'WorkflowResponseDto' });
const WorkflowShareResponseSchema = z
.object({
trigger: WorkflowTriggerSchema.describe('Workflow trigger type'),
name: z.string().nullable().describe('Workflow name'),
description: z.string().nullable().describe('Workflow description'),
steps: z.array(WorkflowShareStepSchema).describe('Workflow steps'),
})
.meta({ id: 'WorkflowShareResponseDto' });
export class WorkflowTriggerResponseDto extends createZodDto(WorkflowTriggerResponseSchema) {}
export class WorkflowSearchDto extends createZodDto(WorkflowSearchSchema) {}
export class WorkflowCreateDto extends createZodDto(WorkflowCreateSchema) {}
export class WorkflowUpdateDto extends createZodDto(WorkflowUpdateSchema) {}
export class WorkflowResponseDto extends createZodDto(WorkflowResponseSchema) {}
export class WorkflowShareResponseDto extends createZodDto(WorkflowShareResponseSchema) {}
class WorkflowFilterResponseDto extends createZodDto(WorkflowFilterResponseSchema) {}
class WorkflowActionResponseDto extends createZodDto(WorkflowActionResponseSchema) {}
type Workflow = {
id: string;
createdAt: Date;
updatedAt: Date;
trigger: WorkflowTrigger;
name: string | null;
description: string | null;
enabled: boolean;
};
type WorkflowStep = {
enabled: boolean;
methodName: string;
config: WorkflowStepConfig | null;
pluginName: string;
};
export const mapWorkflow = (workflow: Workflow & { steps: WorkflowStep[] }): WorkflowResponseDto => {
export function mapWorkflowFilter(filter: WorkflowFilter): WorkflowFilterResponseDto {
return {
id: workflow.id,
enabled: workflow.enabled,
trigger: workflow.trigger,
name: workflow.name,
description: workflow.description,
createdAt: workflow.createdAt.toISOString(),
updatedAt: workflow.updatedAt.toISOString(),
steps: workflow.steps.map((step) => ({
method: `${step.pluginName}#${step.methodName}`,
// TODO fix this
config: step.config as any,
enabled: step.enabled,
})),
id: filter.id,
workflowId: filter.workflowId,
pluginFilterId: filter.pluginFilterId,
filterConfig: filter.filterConfig,
order: filter.order,
};
};
}
export const mapWorkflowShare = (workflow: Workflow & { steps: WorkflowStep[] }): WorkflowShareResponseDto => {
export function mapWorkflowAction(action: WorkflowAction): WorkflowActionResponseDto {
return {
trigger: workflow.trigger,
name: workflow.name,
description: workflow.description,
steps: workflow.steps.map((step) => ({
method: `${step.pluginName}#${step.methodName}`,
// TODO fix this
config: step.config as any,
enabled: step.enabled ? undefined : false,
})),
id: action.id,
workflowId: action.workflowId,
pluginActionId: action.pluginActionId,
actionConfig: action.actionConfig,
order: action.order,
};
};
}
+6 -17
View File
@@ -749,11 +749,8 @@ export enum BootstrapEventPriority {
StorageService = -195,
// Other services may need to queue jobs on bootstrap.
JobService = -190,
// Initialize config after other bootstrap services, stop other services from using config on bootstrap
// Initialise config after other bootstrap services, stop other services from using config on bootstrap
SystemConfig = 100,
PluginSync = 190,
// Load plugins into memory after sync
PluginLoad = 200,
}
export enum QueueName {
@@ -866,7 +863,7 @@ export enum JobName {
Ocr = 'Ocr',
// Workflow
WorkflowAssetCreate = 'WorkflowAssetCreate',
WorkflowRun = 'WorkflowRun',
}
export const JobNameSchema = z.enum(JobName).describe('Job name').meta({ id: 'JobName' });
@@ -912,7 +909,6 @@ export enum DatabaseLock {
CLIPDimSize = 512,
Library = 1337,
NightlyJobs = 600,
PluginImport = 666,
MediaLocation = 700,
GetSystemConfig = 69,
BackupDatabase = 42,
@@ -1164,19 +1160,12 @@ export enum PluginContext {
export const PluginContextSchema = z.enum(PluginContext).describe('Plugin context').meta({ id: 'PluginContextType' });
export enum WorkflowTrigger {
export enum PluginTriggerType {
AssetCreate = 'AssetCreate',
PersonRecognized = 'PersonRecognized',
}
export const WorkflowTriggerSchema = z
.enum(WorkflowTrigger)
export const PluginTriggerTypeSchema = z
.enum(PluginTriggerType)
.describe('Plugin trigger type')
.meta({ id: 'WorkflowTrigger' });
export enum WorkflowType {
AssetV1 = 'AssetV1',
AssetPersonV1 = 'AssetPersonV1',
}
export const WorkflowTypeSchema = z.enum(WorkflowType).describe('Workflow type').meta({ id: 'WorkflowType' });
.meta({ id: 'PluginTriggerType' });
+17
View File
@@ -0,0 +1,17 @@
import { PluginContext, PluginTriggerType } from 'src/enum';
export type PluginTrigger = {
type: PluginTriggerType;
contextType: PluginContext;
};
export const pluginTriggers: PluginTrigger[] = [
{
type: PluginTriggerType.AssetCreate,
contextType: PluginContext.Asset,
},
{
type: PluginTriggerType.PersonRecognized,
contextType: PluginContext.Person,
},
];
+127 -127
View File
@@ -1,159 +1,159 @@
-- NOTE: This file is auto generated by ./sql-generator
-- PluginRepository.getForLoad
-- PluginRepository.getPlugin
select
"plugin"."id",
"plugin"."name",
"plugin"."version",
"plugin"."wasmBytes",
"plugin"."id" as "id",
"plugin"."name" as "name",
"plugin"."title" as "title",
"plugin"."description" as "description",
"plugin"."author" as "author",
"plugin"."version" as "version",
"plugin"."wasmPath" as "wasmPath",
"plugin"."createdAt" as "createdAt",
"plugin"."updatedAt" as "updatedAt",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"plugin_method"."name",
"plugin_method"."hostFunctions"
*
from
"plugin_method"
"plugin_filter"
where
"plugin_method"."pluginId" = "plugin"."id"
"plugin_filter"."pluginId" = "plugin"."id"
) as agg
) as "methods"
from
"plugin"
where
"enabled" = $1
-- PluginRepository.search
select
"plugin"."id",
"plugin"."name",
"plugin"."title",
"plugin"."description",
"plugin"."author",
"plugin"."version",
"plugin"."createdAt",
"plugin"."updatedAt",
) as "filters",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"plugin_method"."name",
"plugin_method"."title",
"plugin_method"."description",
"plugin_method"."types",
"plugin_method"."schema",
"plugin_method"."hostFunctions",
"plugin_method"."uiHints",
"plugin"."name" as "pluginName"
*
from
"plugin_method"
"plugin_action"
where
"plugin_method"."pluginId" = "plugin"."id"
"plugin_action"."pluginId" = "plugin"."id"
) as agg
) as "methods"
from
"plugin"
order by
"plugin"."name"
-- PluginRepository.getByName
select
"plugin"."id",
"plugin"."name",
"plugin"."title",
"plugin"."description",
"plugin"."author",
"plugin"."version",
"plugin"."createdAt",
"plugin"."updatedAt",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"plugin_method"."name",
"plugin_method"."title",
"plugin_method"."description",
"plugin_method"."types",
"plugin_method"."schema",
"plugin_method"."hostFunctions",
"plugin_method"."uiHints",
"plugin"."name" as "pluginName"
from
"plugin_method"
where
"plugin_method"."pluginId" = "plugin"."id"
) as agg
) as "methods"
from
"plugin"
where
"plugin"."name" = $1
-- PluginRepository.get
select
"plugin"."id",
"plugin"."name",
"plugin"."title",
"plugin"."description",
"plugin"."author",
"plugin"."version",
"plugin"."createdAt",
"plugin"."updatedAt",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"plugin_method"."name",
"plugin_method"."title",
"plugin_method"."description",
"plugin_method"."types",
"plugin_method"."schema",
"plugin_method"."hostFunctions",
"plugin_method"."uiHints",
"plugin"."name" as "pluginName"
from
"plugin_method"
where
"plugin_method"."pluginId" = "plugin"."id"
) as agg
) as "methods"
) as "actions"
from
"plugin"
where
"plugin"."id" = $1
-- PluginRepository.getForValidation
-- PluginRepository.getPluginByName
select
"plugin_method"."id",
"plugin_method"."name",
"plugin"."name" as "pluginName",
"plugin_method"."types"
"plugin"."id" as "id",
"plugin"."name" as "name",
"plugin"."title" as "title",
"plugin"."description" as "description",
"plugin"."author" as "author",
"plugin"."version" as "version",
"plugin"."wasmPath" as "wasmPath",
"plugin"."createdAt" as "createdAt",
"plugin"."updatedAt" as "updatedAt",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
*
from
"plugin_filter"
where
"plugin_filter"."pluginId" = "plugin"."id"
) as agg
) as "filters",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
*
from
"plugin_action"
where
"plugin_action"."pluginId" = "plugin"."id"
) as agg
) as "actions"
from
"plugin_method"
inner join "plugin" on "plugin_method"."pluginId" = "plugin"."id"
"plugin"
where
"plugin"."name" = $1
-- PluginRepository.searchMethods
-- PluginRepository.getAllPlugins
select
"plugin"."name" as "pluginName",
"plugin_method"."pluginId",
"plugin_method"."id",
"plugin_method"."name",
"plugin_method"."title",
"plugin_method"."description",
"plugin_method"."types",
"plugin_method"."schema",
"plugin_method"."hostFunctions",
"plugin_method"."uiHints"
"plugin"."id" as "id",
"plugin"."name" as "name",
"plugin"."title" as "title",
"plugin"."description" as "description",
"plugin"."author" as "author",
"plugin"."version" as "version",
"plugin"."wasmPath" as "wasmPath",
"plugin"."createdAt" as "createdAt",
"plugin"."updatedAt" as "updatedAt",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
*
from
"plugin_filter"
where
"plugin_filter"."pluginId" = "plugin"."id"
) as agg
) as "filters",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
*
from
"plugin_action"
where
"plugin_action"."pluginId" = "plugin"."id"
) as agg
) as "actions"
from
"plugin_method"
inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId"
"plugin"
order by
"plugin_method"."name"
"plugin"."name"
-- PluginRepository.getFilter
select
*
from
"plugin_filter"
where
"id" = $1
-- PluginRepository.getFiltersByPlugin
select
*
from
"plugin_filter"
where
"pluginId" = $1
-- PluginRepository.getAction
select
*
from
"plugin_action"
where
"id" = $1
-- PluginRepository.getActionsByPlugin
select
*
from
"plugin_action"
where
"pluginId" = $1

Some files were not shown because too many files have changed in this diff Show More