Compare commits

..

1 Commits

Author SHA1 Message Date
Jason Rasmussen d7c5406b8b feat: plugins
chore: better types

feat: plugins
2026-05-15 19:51:41 -04:00
157 changed files with 6146 additions and 7538 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/plugins:/build/corePlugin
- ../packages/plugin-core:/build/plugins/immich-plugin-core
immich-web:
env_file: !reset []
immich-machine-learning:
+10 -14
View File
@@ -62,9 +62,6 @@ 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
@@ -84,7 +81,7 @@ jobs:
github_token: ${{ steps.token.outputs.token }}
- name: Run ci-unit
run: mise run ci-unit
run: mise run //server:ci-unit
cli-unit-tests:
name: Unit Test CLI
@@ -380,7 +377,7 @@ jobs:
cache-dependency-path: '**/pnpm-lock.yaml'
- name: Setup packages
run: pnpm --filter "@immich/*" install --frozen-lockfile && pnpm --filter "@immich/*" build
run: pnpm --filter @immich/sdk --filter @immich/cli install --frozen-lockfile && pnpm --filter @immich/sdk --filter @immich/cli build
- name: Run setup web
run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync
@@ -678,7 +675,6 @@ 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
@@ -717,9 +713,6 @@ 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
@@ -741,18 +734,21 @@ 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: pnpm build
run: mise //server:build
- name: Run existing migrations
run: pnpm migrations:run
run: pnpm --filter immich migrations:run
- name: Test npm run schema:reset command works
run: pnpm schema:reset
run: pnpm --filter immich schema:reset
- name: Generate new migrations
continue-on-error: true
run: pnpm migrations:generate src/TestMigration
run: pnpm --filter migrations:generate src/TestMigration
- name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
@@ -768,7 +764,7 @@ jobs:
run: |
echo "ERROR: Generated migration files not up to date!"
echo "Changed files: ${CHANGED_FILES}"
cat ./src/*-TestMigration.ts
cat ./server/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/plugins:/build/corePlugin
- ../packages/plugin-core:/build/plugins/immich-plugin-core
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/*" build`
- `pnpm --filter @immich/sdk --filter @immich/cli build`
- `mise //:open-api`
Once the test environment is running, the e2e tests can be run via:
@@ -2,7 +2,7 @@ import { LoginResponseDto, ManualJobName } from '@immich/sdk';
import { errorDto } from 'src/responses';
import { app, utils } from 'src/utils';
import request from 'supertest';
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
describe('/admin/database-backups', () => {
let cookie: string | undefined;
@@ -13,9 +13,6 @@ describe('/admin/database-backups', () => {
admin = await utils.adminSetup({
onboarding: false,
});
});
beforeEach(async () => {
await utils.resetBackups(admin.accessToken);
});
-2
View File
@@ -568,8 +568,6 @@ export const utils = {
name: ManualJobName.BackupDatabase,
});
await utils.waitForQueueFinish(accessToken, 'backupDatabase');
return utils.poll(
() => request(app).get('/admin/database-backups').set('Authorization', `Bearer ${accessToken}`),
({ status, body }) => status === 200 && body.backups.length === 1,
+13 -7
View File
@@ -22,13 +22,12 @@
"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",
@@ -42,7 +41,6 @@
"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",
@@ -733,6 +731,7 @@
"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",
@@ -761,6 +760,7 @@
"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,6 +809,7 @@
"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?",
@@ -1628,7 +1629,6 @@
"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,7 +1645,6 @@
"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",
@@ -1658,6 +1657,7 @@
"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,6 +1794,8 @@
"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",
@@ -2236,6 +2238,10 @@
"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?",
@@ -2329,7 +2335,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 Uploaded",
"trigger_asset_uploaded": "Asset Upload",
"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",
@@ -2369,7 +2375,6 @@
"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",
@@ -2461,6 +2466,7 @@
"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",
+12 -1
View File
@@ -2,7 +2,7 @@ experimental_monorepo_root = true
[monorepo]
config_roots = [
"packages/plugins",
"packages/plugin-core",
"server",
"packages/cli",
"deployment",
@@ -22,6 +22,9 @@ 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"
@@ -41,6 +44,12 @@ 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",
@@ -55,6 +64,8 @@ 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" },
@@ -1,8 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift_sqlite_async/drift_sqlite_async.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart';
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
@@ -32,10 +31,6 @@ import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart'
import 'package:immich_mobile/infrastructure/repositories/db.repository.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:sqlite3/sqlite3.dart';
import 'package:sqlite_async/sqlite_async.dart';
@DriftDatabase(
tables: [
@@ -65,9 +60,8 @@ import 'package:sqlite_async/sqlite_async.dart';
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
)
class Drift extends $Drift {
Drift(super.executor);
Drift.sqlite(SqliteConnection db) : super(SqliteAsyncDriftConnection(db));
Drift([QueryExecutor? executor])
: super(executor ?? driftDatabase(name: 'immich', native: const DriftNativeOptions(shareAcrossIsolates: true)));
Future<void> reset() async {
// https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94
@@ -311,18 +305,3 @@ class DriftDatabaseRepository {
Future<T> transaction<T>(Future<T> Function() callback) => _db.transaction(callback);
}
Future<SqliteConnection> openSqliteConnection({required String name}) async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, '$name.sqlite'));
return SqliteDatabase(path: file.path);
}
Future<void> configureSqliteCache() async {
// Make sqlite3 pick a more suitable location for temporary files - the
// one from the system may be inaccessible due to sand-boxing.
final cacheBase = (await getTemporaryDirectory()).path;
// We can't access /tmp on Android, which sqlite3 would try by default.
// Explicitly tell it about the correct temporary directory.
sqlite3.tempDirectory = cacheBase;
}
@@ -1,14 +1,14 @@
import 'package:drift/drift.dart';
import 'package:drift_sqlite_async/drift_sqlite_async.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.drift.dart';
import 'package:sqlite_async/sqlite_async.dart';
@DriftDatabase(tables: [LogMessageEntity])
class DriftLogger extends $DriftLogger {
DriftLogger.fromExecutor(super.executor);
DriftLogger.sqlite(SqliteConnection db) : super(SqliteAsyncDriftConnection(db));
DriftLogger([QueryExecutor? executor])
: super(
executor ?? driftDatabase(name: 'immich_logs', native: const DriftNativeOptions(shareAcrossIsolates: true)),
);
@override
int get schemaVersion => 1;
@@ -19,8 +19,7 @@ class DriftLogger extends $DriftLogger {
await customStatement('PRAGMA foreign_keys = ON');
await customStatement('PRAGMA synchronous = NORMAL');
await customStatement('PRAGMA journal_mode = WAL');
await customStatement('PRAGMA busy_timeout = 30000'); // 30s
await customStatement('PRAGMA cache_size = -32000'); // 32MB
await customStatement('PRAGMA busy_timeout = 500');
await customStatement('PRAGMA temp_store = MEMORY');
},
);
@@ -35,11 +35,10 @@ class BaseActionButton extends ConsumerWidget {
final miniWidth = minWidth ?? (context.isMobile ? context.width / 4.5 : 75.0);
final iconTheme = IconTheme.of(context);
final iconSize = iconTheme.size ?? 24.0;
final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color;
final textColor = context.themeData.textTheme.labelLarge?.color;
if (iconOnly) {
final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color;
return IconButton(
onPressed: onPressed,
icon: Icon(iconData, size: iconSize, color: iconColor),
@@ -47,18 +46,17 @@ class BaseActionButton extends ConsumerWidget {
}
if (menuItem) {
final iconColor = this.iconColor;
final theme = context.themeData;
final effectiveIconColor = iconColor ?? theme.iconTheme.color ?? theme.colorScheme.onSurfaceVariant;
return MenuItemButton(
style: MenuItemButton.styleFrom(alignment: Alignment.centerLeft, padding: const EdgeInsets.all(16)),
leadingIcon: Icon(iconData, color: iconColor),
leadingIcon: Icon(iconData, color: effectiveIconColor),
onPressed: onPressed,
child: Text(label, style: TextStyle(fontSize: 16, color: iconColor)),
child: Text(label, style: theme.textTheme.labelLarge?.copyWith(fontSize: 16, color: iconColor)),
);
}
final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color;
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxWidth),
child: MaterialButton(
@@ -50,7 +50,7 @@ class ViewerKebabMenu extends ConsumerWidget {
timelineOrigin: timelineOrigin,
);
final menuChildren = ActionButtonBuilder.buildViewerKebabMenu(actionContext, context, ref);
final menuChildren = ActionButtonBuilder.buildViewerKebabMenu(actionContext, context);
return MenuAnchor(
consumeOutsideTap: true,
+2 -2
View File
@@ -330,7 +330,7 @@ class ActionButtonBuilder {
return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList();
}
static List<Widget> buildViewerKebabMenu(ActionButtonContext context, BuildContext buildContext, WidgetRef ref) {
static List<Widget> buildViewerKebabMenu(ActionButtonContext context, BuildContext buildContext) {
final visibleButtons = defaultViewerKebabMenuOrder
.where((type) => !defaultViewerBottomBarButtons.contains(type) && type.shouldShow(context))
.toList();
@@ -346,7 +346,7 @@ class ActionButtonBuilder {
if (lastGroup != null && type.kebabMenuGroup != lastGroup) {
result.add(const Divider(height: 1));
}
result.add(type.buildButton(context, buildContext, false, true).build(buildContext, ref));
result.add(type.buildButton(context, buildContext, false, true));
lastGroup = type.kebabMenuGroup;
}
+2 -3
View File
@@ -43,9 +43,8 @@ void configureFileDownloaderNotifications() {
abstract final class Bootstrap {
static Future<(Drift, DriftLogger)> initDomain({bool listenStoreUpdates = true, bool shouldBufferLogs = true}) async {
await configureSqliteCache();
final drift = Drift.sqlite(await openSqliteConnection(name: 'immich'));
final logDb = DriftLogger.sqlite(await openSqliteConnection(name: 'immich_logs'));
final drift = Drift();
final logDb = DriftLogger();
final DriftStoreRepository storeRepo = DriftStoreRepository(drift);
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
+12 -16
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* | [**getPluginTriggers**](doc//PluginsApi.md#getplugintriggers) | **GET** /plugins/triggers | List all plugin triggers
*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins
*PluginsApi* | [**searchPluginMethods**](doc//PluginsApi.md#searchpluginmethods) | **GET** /plugins/methods | Retrieve plugin methods
*PluginsApi* | [**searchPlugins**](doc//PluginsApi.md#searchplugins) | **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,7 +314,9 @@ 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* | [**getWorkflows**](doc//WorkflowsApi.md#getworkflows) | **GET** /workflows | List all workflows
*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* | [**updateWorkflow**](doc//WorkflowsApi.md#updateworkflow) | **PUT** /workflows/{id} | Update a workflow
@@ -487,16 +489,8 @@ Class | Method | HTTP request | Description
- [PinCodeResetDto](doc//PinCodeResetDto.md)
- [PinCodeSetupDto](doc//PinCodeSetupDto.md)
- [PlacesResponseDto](doc//PlacesResponseDto.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)
- [PluginMethodResponseDto](doc//PluginMethodResponseDto.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)
@@ -669,12 +663,14 @@ 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)
+7 -13
View File
@@ -235,16 +235,8 @@ 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_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_method_response_dto.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';
@@ -417,12 +409,14 @@ 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';
+144 -13
View File
@@ -73,14 +73,40 @@ class PluginsApi {
return null;
}
/// List all plugin triggers
/// Retrieve plugin methods
///
/// Retrieve a list of all available plugin triggers.
/// Retrieve a list of plugin methods
///
/// Note: This method returns the HTTP [Response].
Future<Response> getPluginTriggersWithHttpInfo() async {
///
/// 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 {
// ignore: prefer_const_declarations
final apiPath = r'/plugins/triggers';
final apiPath = r'/plugins/methods';
// ignore: prefer_final_locals
Object? postBody;
@@ -89,6 +115,34 @@ 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>[];
@@ -103,11 +157,37 @@ class PluginsApi {
);
}
/// List all plugin triggers
/// Retrieve plugin methods
///
/// Retrieve a list of all available plugin triggers.
Future<List<PluginTriggerResponseDto>?> getPluginTriggers() async {
final response = await getPluginTriggersWithHttpInfo();
/// 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, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -116,8 +196,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<PluginTriggerResponseDto>') as List)
.cast<PluginTriggerResponseDto>()
return (await apiClient.deserializeAsync(responseBody, 'List<PluginMethodResponseDto>') as List)
.cast<PluginMethodResponseDto>()
.toList(growable: false);
}
@@ -129,7 +209,23 @@ class PluginsApi {
/// Retrieve a list of plugins available to the authenticated user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getPluginsWithHttpInfo() async {
///
/// 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 {
// ignore: prefer_const_declarations
final apiPath = r'/plugins';
@@ -140,6 +236,25 @@ 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>[];
@@ -157,8 +272,24 @@ class PluginsApi {
/// List all plugins
///
/// Retrieve a list of plugins available to the authenticated user.
Future<List<PluginResponseDto>?> getPlugins() async {
final response = await getPluginsWithHttpInfo();
///
/// 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, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
+164 -6
View File
@@ -178,14 +178,19 @@ class WorkflowsApi {
return null;
}
/// List all workflows
/// Retrieve a workflow
///
/// Retrieve a list of workflows available to the authenticated user.
/// Retrieve a workflow details without ids, default values, etc.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getWorkflowsWithHttpInfo() async {
///
/// Parameters:
///
/// * [String] id (required):
Future<Response> getWorkflowForShareWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final apiPath = r'/workflows';
final apiPath = r'/workflows/{id}/share'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
@@ -208,11 +213,164 @@ class WorkflowsApi {
);
}
/// 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.
Future<List<WorkflowResponseDto>?> getWorkflows() async {
final response = await getWorkflowsWithHttpInfo();
///
/// 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 {
// ignore: prefer_const_declarations
final apiPath = r'/workflows';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
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>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// 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, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
+14 -26
View File
@@ -516,26 +516,10 @@ class ApiClient {
return PinCodeSetupDto.fromJson(value);
case 'PlacesResponseDto':
return PlacesResponseDto.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 'PluginMethodResponseDto':
return PluginMethodResponseDto.fromJson(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':
@@ -880,18 +864,22 @@ 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:
+6 -9
View File
@@ -142,15 +142,6 @@ 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();
}
@@ -208,6 +199,12 @@ 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 workflowRun = JobName._(r'WorkflowRun');
static const workflowAssetCreate = JobName._(r'WorkflowAssetCreate');
/// List of all possible values in this [enum][JobName].
static const values = <JobName>[
@@ -135,7 +135,7 @@ class JobName {
versionCheck,
ocrQueueAll,
ocr,
workflowRun,
workflowAssetCreate,
];
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'WorkflowRun': return JobName.workflowRun;
case r'WorkflowAssetCreate': return JobName.workflowAssetCreate;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
-158
View File
@@ -1,158 +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 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
@@ -1,88 +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;
/// 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
@@ -1,158 +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 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
@@ -1,158 +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 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
@@ -1,195 +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 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>{
};
}
@@ -1,195 +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 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
@@ -1,100 +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 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
@@ -0,0 +1,172 @@
//
// 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',
};
}
+10 -19
View File
@@ -13,21 +13,17 @@ 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;
@@ -37,12 +33,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;
@@ -57,12 +53,11 @@ 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 &&
@@ -71,28 +66,26 @@ 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[actions=$actions, author=$author, createdAt=$createdAt, description=$description, filters=$filters, id=$id, name=$name, title=$title, updatedAt=$updatedAt, version=$version]';
String toString() => 'PluginResponseDto[author=$author, createdAt=$createdAt, description=$description, id=$id, methods=$methods, 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;
@@ -109,12 +102,11 @@ 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')!,
@@ -166,12 +158,11 @@ 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
@@ -1,107 +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 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',
};
}
-107
View File
@@ -1,107 +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 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
@@ -1,142 +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 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',
};
}
+23 -37
View File
@@ -13,24 +13,14 @@ part of openapi.api;
class WorkflowCreateDto {
/// Returns a new [WorkflowCreateDto] instance.
WorkflowCreateDto({
this.actions = const [],
this.description,
this.enabled,
this.filters = const [],
required this.name,
required this.triggerType,
this.name,
this.steps = const [],
required this.trigger,
});
/// 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
@@ -42,39 +32,35 @@ class WorkflowCreateDto {
///
bool? enabled;
/// Workflow filters
List<WorkflowFilterItemDto> filters;
/// Workflow name
String name;
String? name;
PluginTriggerType triggerType;
List<WorkflowStepDto> steps;
WorkflowTrigger trigger;
@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 &&
other.triggerType == triggerType;
_deepEquality.equals(other.steps, steps) &&
other.trigger == trigger;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(actions.hashCode) +
(description == null ? 0 : description!.hashCode) +
(enabled == null ? 0 : enabled!.hashCode) +
(filters.hashCode) +
(name.hashCode) +
(triggerType.hashCode);
(name == null ? 0 : name!.hashCode) +
(steps.hashCode) +
(trigger.hashCode);
@override
String toString() => 'WorkflowCreateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]';
String toString() => 'WorkflowCreateDto[description=$description, enabled=$enabled, name=$name, steps=$steps, trigger=$trigger]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'actions'] = this.actions;
if (this.description != null) {
json[r'description'] = this.description;
} else {
@@ -85,9 +71,13 @@ class WorkflowCreateDto {
} else {
// json[r'enabled'] = null;
}
json[r'filters'] = this.filters;
if (this.name != null) {
json[r'name'] = this.name;
json[r'triggerType'] = this.triggerType;
} else {
// json[r'name'] = null;
}
json[r'steps'] = this.steps;
json[r'trigger'] = this.trigger;
return json;
}
@@ -100,12 +90,11 @@ 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'),
filters: WorkflowFilterItemDto.listFromJson(json[r'filters']),
name: mapValueOfType<String>(json, r'name')!,
triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!,
name: mapValueOfType<String>(json, r'name'),
steps: WorkflowStepDto.listFromJson(json[r'steps']),
trigger: WorkflowTrigger.fromJson(json[r'trigger'])!,
);
}
return null;
@@ -153,10 +142,7 @@ class WorkflowCreateDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'actions',
'filters',
'name',
'triggerType',
'trigger',
};
}
-107
View File
@@ -1,107 +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 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
@@ -1,142 +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 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',
};
}
+32 -37
View File
@@ -13,86 +13,83 @@ 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,
required this.ownerId,
required this.triggerType,
this.steps = const [],
required this.trigger,
required this.updatedAt,
});
/// 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;
/// Owner user ID
String ownerId;
/// Workflow steps
List<WorkflowStepDto> steps;
PluginTriggerType triggerType;
WorkflowTrigger trigger;
/// Update date
String updatedAt;
@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 &&
other.ownerId == ownerId &&
other.triggerType == triggerType;
_deepEquality.equals(other.steps, steps) &&
other.trigger == trigger &&
other.updatedAt == updatedAt;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(actions.hashCode) +
(createdAt.hashCode) +
(description.hashCode) +
(description == null ? 0 : description!.hashCode) +
(enabled.hashCode) +
(filters.hashCode) +
(id.hashCode) +
(name == null ? 0 : name!.hashCode) +
(ownerId.hashCode) +
(triggerType.hashCode);
(steps.hashCode) +
(trigger.hashCode) +
(updatedAt.hashCode);
@override
String toString() => 'WorkflowResponseDto[actions=$actions, createdAt=$createdAt, description=$description, enabled=$enabled, filters=$filters, id=$id, name=$name, ownerId=$ownerId, triggerType=$triggerType]';
String toString() => 'WorkflowResponseDto[createdAt=$createdAt, description=$description, enabled=$enabled, id=$id, name=$name, steps=$steps, trigger=$trigger, updatedAt=$updatedAt]';
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'ownerId'] = this.ownerId;
json[r'triggerType'] = this.triggerType;
json[r'steps'] = this.steps;
json[r'trigger'] = this.trigger;
json[r'updatedAt'] = this.updatedAt;
return json;
}
@@ -105,15 +102,14 @@ 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'),
ownerId: mapValueOfType<String>(json, r'ownerId')!,
triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!,
steps: WorkflowStepDto.listFromJson(json[r'steps']),
trigger: WorkflowTrigger.fromJson(json[r'trigger'])!,
updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
);
}
return null;
@@ -161,15 +157,14 @@ 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',
'ownerId',
'triggerType',
'steps',
'trigger',
'updatedAt',
};
}
+134
View File
@@ -0,0 +1,134 @@
//
// 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
@@ -0,0 +1,131 @@
//
// 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
@@ -0,0 +1,131 @@
//
// 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',
};
}
@@ -11,9 +11,9 @@
part of openapi.api;
/// Plugin trigger type
class PluginTriggerType {
class WorkflowTrigger {
/// Instantiate a new enum with the provided [value].
const PluginTriggerType._(this.value);
const WorkflowTrigger._(this.value);
/// The underlying value of this enum member.
final String value;
@@ -23,22 +23,22 @@ class PluginTriggerType {
String toJson() => value;
static const assetCreate = PluginTriggerType._(r'AssetCreate');
static const personRecognized = PluginTriggerType._(r'PersonRecognized');
static const assetCreate = WorkflowTrigger._(r'AssetCreate');
static const personRecognized = WorkflowTrigger._(r'PersonRecognized');
/// List of all possible values in this [enum][PluginTriggerType].
static const values = <PluginTriggerType>[
/// List of all possible values in this [enum][WorkflowTrigger].
static const values = <WorkflowTrigger>[
assetCreate,
personRecognized,
];
static PluginTriggerType? fromJson(dynamic value) => PluginTriggerTypeTypeTransformer().decode(value);
static WorkflowTrigger? fromJson(dynamic value) => WorkflowTriggerTypeTransformer().decode(value);
static List<PluginTriggerType> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PluginTriggerType>[];
static List<WorkflowTrigger> listFromJson(dynamic json, {bool growable = false,}) {
final result = <WorkflowTrigger>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PluginTriggerType.fromJson(row);
final value = WorkflowTrigger.fromJson(row);
if (value != null) {
result.add(value);
}
@@ -48,16 +48,16 @@ class PluginTriggerType {
}
}
/// 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._();
/// 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._();
const PluginTriggerTypeTypeTransformer._();
const WorkflowTriggerTypeTransformer._();
String encode(PluginTriggerType data) => data.value;
String encode(WorkflowTrigger data) => data.value;
/// Decodes a [dynamic value][data] to a PluginTriggerType.
/// Decodes a [dynamic value][data] to a WorkflowTrigger.
///
/// 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 PluginTriggerTypeTypeTransformer {
///
/// 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.
PluginTriggerType? decode(dynamic data, {bool allowNull = true}) {
WorkflowTrigger? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'AssetCreate': return PluginTriggerType.assetCreate;
case r'PersonRecognized': return PluginTriggerType.personRecognized;
case r'AssetCreate': return WorkflowTrigger.assetCreate;
case r'PersonRecognized': return WorkflowTrigger.personRecognized;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
@@ -79,7 +79,7 @@ class PluginTriggerTypeTypeTransformer {
return null;
}
/// Singleton [PluginTriggerTypeTypeTransformer] instance.
static PluginTriggerTypeTypeTransformer? _instance;
/// Singleton [WorkflowTriggerTypeTransformer] instance.
static WorkflowTriggerTypeTransformer? _instance;
}
@@ -0,0 +1,108 @@
//
// 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
@@ -0,0 +1,85 @@
//
// 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;
}
+16 -37
View File
@@ -13,24 +13,14 @@ 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.triggerType,
this.steps = const [],
this.trigger,
});
/// 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
@@ -42,51 +32,41 @@ class WorkflowUpdateDto {
///
bool? enabled;
/// Workflow filters
List<WorkflowFilterItemDto> filters;
/// 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;
List<WorkflowStepDto> steps;
///
/// 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.
///
PluginTriggerType? triggerType;
WorkflowTrigger? trigger;
@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 &&
other.triggerType == triggerType;
_deepEquality.equals(other.steps, steps) &&
other.trigger == trigger;
@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) +
(triggerType == null ? 0 : triggerType!.hashCode);
(steps.hashCode) +
(trigger == null ? 0 : trigger!.hashCode);
@override
String toString() => 'WorkflowUpdateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]';
String toString() => 'WorkflowUpdateDto[description=$description, enabled=$enabled, name=$name, steps=$steps, trigger=$trigger]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'actions'] = this.actions;
if (this.description != null) {
json[r'description'] = this.description;
} else {
@@ -97,16 +77,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;
}
if (this.triggerType != null) {
json[r'triggerType'] = this.triggerType;
json[r'steps'] = this.steps;
if (this.trigger != null) {
json[r'trigger'] = this.trigger;
} else {
// json[r'triggerType'] = null;
// json[r'trigger'] = null;
}
return json;
}
@@ -120,12 +100,11 @@ 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'),
triggerType: PluginTriggerType.fromJson(json[r'triggerType']),
steps: WorkflowStepDto.listFromJson(json[r'steps']),
trigger: WorkflowTrigger.fromJson(json[r'trigger']),
);
}
return null;
+16 -24
View File
@@ -370,11 +370,11 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.32.1"
drift_sqlite_async:
drift_flutter:
dependency: "direct main"
description:
name: drift_sqlite_async
sha256: "1b6e99562fc5d35fe5e3696741720a8aca47f4c3eee35d4b9b94be819f53a6f6"
name: drift_flutter
sha256: "887fdec622174dc7eaefd0048403e34ee07cc18626ac8a7544cc3b8a4a172166"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
@@ -1619,38 +1619,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.2"
sqlcipher_flutter_libs:
dependency: transitive
description:
name: sqlcipher_flutter_libs
sha256: "38d62d659d2fb8739bf25a42c9a350d1fdd6c29a5a61f13a946778ec75d27929"
url: "https://pub.dev"
source: hosted
version: "0.7.0+eol"
sqlite3:
dependency: "direct main"
dependency: transitive
description:
name: sqlite3
sha256: "56da3e13ed7d28a66f930aa2b2b29db6736a233f08283326e96321dd812030f5"
url: "https://pub.dev"
source: hosted
version: "3.3.1"
sqlite3_connection_pool:
sqlite3_flutter_libs:
dependency: transitive
description:
name: sqlite3_connection_pool
sha256: "90b25972c7699d84da97df1c5919804275560b4ab8a158bbec890434b9718f65"
name: sqlite3_flutter_libs
sha256: "3ed7553eee7bb368f8950f58ba29f634e06e813c029aff6a0d60862b96de8454"
url: "https://pub.dev"
source: hosted
version: "0.2.4"
sqlite3_web:
dependency: transitive
description:
name: sqlite3_web
sha256: d876398a9f2cbf115d93fc34901f8fa129b58b13b5fa9377156ed3a9a05695e3
url: "https://pub.dev"
source: hosted
version: "0.7.1"
sqlite_async:
dependency: "direct main"
description:
name: sqlite_async
sha256: "4c243c5386eba3a7102f98999388a7e0a7f2632e4e06dafb3b4f5a44170a26f6"
url: "https://pub.dev"
source: hosted
version: "0.14.1"
version: "0.6.0+eol"
sqlparser:
dependency: transitive
description:
+1 -3
View File
@@ -19,7 +19,7 @@ dependencies:
crypto: ^3.0.7
device_info_plus: ^12.4.0
drift: ^2.32.1
drift_sqlite_async: 0.3.0
drift_flutter: ^0.3.0
dynamic_color: ^1.8.1
easy_localization: ^3.0.8
ffi: ^2.2.0
@@ -66,8 +66,6 @@ dependencies:
share_plus: ^10.1.4
sliver_tools: ^0.2.12
stream_transform: ^2.1.1
sqlite3: ^3.3.1
sqlite_async: 0.14.1
thumbhash: 0.1.0+1
timezone: ^0.9.4
url_launcher: ^6.3.2
@@ -131,7 +131,7 @@ void main() {
durationMs: 0,
orientation: 0,
isFavorite: false,
playbackStyle: PlatformAssetPlaybackStyle.image,
playbackStyle: PlatformAssetPlaybackStyle.image
);
final assetsToRestore = [LocalAssetStub.image1];
@@ -215,7 +215,7 @@ void main() {
isFavorite: false,
createdAt: 1700000000,
updatedAt: 1732000000,
playbackStyle: PlatformAssetPlaybackStyle.image,
playbackStyle: PlatformAssetPlaybackStyle.image
);
final localAsset = platformAsset.toLocalAsset();
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
/dist
/node_modules
+6
View File
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"semi": true
}
+11
View File
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,258 @@
{
"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
@@ -0,0 +1,6 @@
[tasks.install]
run = "pnpm install --frozen-lockfile"
[tasks.build]
depends = ["install"]
run = "pnpm --filter @immich/plugin-sdk --filter @immich/plugin-core build"
@@ -1,5 +1,5 @@
{
"name": "plugins",
"name": "@immich/plugin-core",
"version": "1.0.0",
"description": "",
"main": "src/index.ts",
@@ -13,6 +13,7 @@
"license": "AGPL-3.0",
"devDependencies": {
"@extism/js-pdk": "^1.0.1",
"@immich/plugin-sdk": "workspace:*",
"esbuild": "^0.28.0",
"typescript": "^6.0.0"
}
+19
View File
@@ -0,0 +1,19 @@
// 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
@@ -0,0 +1,111 @@
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 {};
});
};
@@ -1,20 +1,24 @@
{
"compilerOptions": {
"target": "es2020", // Specify ECMAScript target version
"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
"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",
"noEmit": true // Do not emit outputs (no .js or .d.ts files)
"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
},
"include": [
"src/**/*.ts" // Include all TypeScript files in src directory
],
"exclude": [
"node_modules" // Exclude the node_modules directory
],
"include": [
"src/**/*.ts" // Include all TypeScript files in src directory
]
}
+2
View File
@@ -0,0 +1,2 @@
/dist
/node_modules
+11
View File
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,38 @@
{
"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
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,51 @@
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
@@ -0,0 +1,4 @@
export * from 'src/enum.js';
export * from 'src/host-functions.js';
export * from 'src/sdk.js';
export * from 'src/types.js';
+43
View File
@@ -0,0 +1,43 @@
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
@@ -0,0 +1,129 @@
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
@@ -0,0 +1,26 @@
{
"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
@@ -1,2 +0,0 @@
node_modules
dist
-26
View File
@@ -1,26 +0,0 @@
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
@@ -1,12 +0,0 @@
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
@@ -1,159 +0,0 @@
{
"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
@@ -1,11 +0,0 @@
[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
@@ -1,533 +0,0 @@
{
"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"
}
}
}
-12
View File
@@ -1,12 +0,0 @@
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
@@ -1,71 +0,0 @@
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 });
}
+150 -141
View File
@@ -1478,72 +1478,33 @@ export type PersonStatisticsResponseDto = {
/** Number of assets */
assets: number;
};
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 */
export type PluginMethodResponseDto = {
/** Description */
description: string;
/** 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 */
hostFunctions: boolean;
/** Key */
key: string;
/** Name */
name: string;
schema?: {};
/** 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 */
@@ -1553,10 +1514,6 @@ export type PluginResponseDto = {
/** Plugin version */
version: string;
};
export type PluginTriggerResponseDto = {
contextType: PluginContextType;
"type": PluginTriggerType;
};
export type QueueResponseDto = {
/** Whether the queue is paused */
isPaused: boolean;
@@ -2710,89 +2667,81 @@ export type CreateProfileImageResponseDto = {
/** User ID */
userId: 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 WorkflowStepDto = {
/** Step configuration */
config: {
[key: string]: any;
} | null;
/** Step is enabled */
enabled?: boolean;
/** Step plugin method */
method: string;
};
export type WorkflowResponseDto = {
/** Workflow actions */
actions: WorkflowActionResponseDto[];
/** Creation date */
createdAt: string;
/** Workflow description */
description: string;
description: string | null;
/** Workflow enabled */
enabled: boolean;
/** Workflow filters */
filters: WorkflowFilterResponseDto[];
/** Workflow ID */
id: string;
/** Workflow name */
name: string | null;
/** 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;
/** Workflow steps */
steps: WorkflowStepDto[];
/** Workflow trigger type */
trigger: WorkflowTrigger;
/** Update date */
updatedAt: string;
};
export type WorkflowCreateDto = {
/** Workflow actions */
actions: WorkflowActionItemDto[];
/** Workflow description */
description?: string;
description?: string | null;
/** Workflow enabled */
enabled?: boolean;
/** Workflow filters */
filters: WorkflowFilterItemDto[];
/** Workflow name */
name: string;
triggerType: PluginTriggerType;
name?: string | null;
steps?: WorkflowStepDto[];
/** Workflow trigger type */
trigger: WorkflowTrigger;
};
export type WorkflowTriggerResponseDto = {
/** Trigger type */
trigger: WorkflowTrigger;
/** Workflow types */
types: WorkflowType[];
};
export type WorkflowUpdateDto = {
/** Workflow actions */
actions?: WorkflowActionItemDto[];
/** Workflow description */
description?: string;
description?: string | null;
/** Workflow enabled */
enabled?: boolean;
/** Workflow filters */
filters?: WorkflowFilterItemDto[];
/** Workflow name */
name?: string;
triggerType?: PluginTriggerType;
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;
};
export type LicenseResponseDto = UserLicense;
export type SyncAckV1 = {};
@@ -5240,22 +5189,56 @@ export function getPersonThumbnail({ id }: {
/**
* List all plugins
*/
export function getPlugins(opts?: Oazapfts.RequestOpts) {
export function searchPlugins({ description, enabled, id, name, title, version }: {
description?: string;
enabled?: boolean;
id?: string;
name?: string;
title?: string;
version?: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PluginResponseDto[];
}>("/plugins", {
}>(`/plugins${QS.query(QS.explode({
description,
enabled,
id,
name,
title,
version
}))}`, {
...opts
}));
}
/**
* List all plugin triggers
* Retrieve plugin methods
*/
export function getPluginTriggers(opts?: Oazapfts.RequestOpts) {
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) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: PluginTriggerResponseDto[];
}>("/plugins/triggers", {
data: PluginMethodResponseDto[];
}>(`/plugins/methods${QS.query(QS.explode({
description,
enabled,
id,
name,
pluginName,
pluginVersion,
title,
trigger,
"type": $type
}))}`, {
...opts
}));
}
@@ -6631,11 +6614,23 @@ export function getUniqueOriginalPaths(opts?: Oazapfts.RequestOpts) {
/**
* List all workflows
*/
export function getWorkflows(opts?: Oazapfts.RequestOpts) {
export function searchWorkflows({ description, enabled, id, name, trigger }: {
description?: string;
enabled?: boolean;
id?: string;
name?: string;
trigger?: WorkflowTrigger;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: WorkflowResponseDto[];
}>("/workflows", {
}>(`/workflows${QS.query(QS.explode({
description,
enabled,
id,
name,
trigger
}))}`, {
...opts
}));
}
@@ -6654,6 +6649,17 @@ 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
*/
@@ -6694,6 +6700,19 @@ 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"
@@ -7017,21 +7036,11 @@ export enum PartnerDirection {
SharedBy = "shared-by",
SharedWith = "shared-with"
}
export enum PluginJsonSchemaType {
String = "string",
Number = "number",
Integer = "integer",
Boolean = "boolean",
Object = "object",
Array = "array",
Null = "null"
export enum WorkflowType {
AssetV1 = "AssetV1",
AssetPersonV1 = "AssetPersonV1"
}
export enum PluginContextType {
Asset = "asset",
Album = "album",
Person = "person"
}
export enum PluginTriggerType {
export enum WorkflowTrigger {
AssetCreate = "AssetCreate",
PersonRecognized = "PersonRecognized"
}
@@ -7098,7 +7107,7 @@ export enum JobName {
VersionCheck = "VersionCheck",
OcrQueueAll = "OcrQueueAll",
Ocr = "Ocr",
WorkflowRun = "WorkflowRun"
WorkflowAssetCreate = "WorkflowAssetCreate"
}
export enum SearchSuggestionType {
Country = "country",
+75 -1
View File
@@ -312,11 +312,14 @@ importers:
specifier: ^4.20.6
version: 4.21.0
packages/plugins:
packages/plugin-core:
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
@@ -324,6 +327,24 @@ 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':
@@ -342,6 +363,9 @@ 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
@@ -453,6 +477,9 @@ 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
@@ -6446,6 +6473,10 @@ 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'}
@@ -7817,6 +7848,10 @@ 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'}
@@ -9462,6 +9497,10 @@ 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==}
@@ -10000,6 +10039,10 @@ 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'}
@@ -10642,6 +10685,10 @@ 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==}
@@ -11868,6 +11915,11 @@ 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}
@@ -18998,6 +19050,8 @@ snapshots:
commander@8.3.0: {}
commander@9.5.0: {}
comment-json@5.0.0:
dependencies:
array-timsort: 1.0.3
@@ -20643,6 +20697,8 @@ 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: {}
@@ -22684,6 +22740,8 @@ snapshots:
mute-stream@2.0.0: {}
mylas@2.1.14: {}
mz@2.7.0:
dependencies:
any-promise: 1.3.0
@@ -23241,6 +23299,10 @@ 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:
@@ -23925,6 +23987,8 @@ snapshots:
dependencies:
side-channel: 1.1.0
queue-lit@1.5.2: {}
queue-microtask@1.2.3: {}
quick-lru@5.1.1: {}
@@ -25518,6 +25582,16 @@ 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,6 +7,7 @@ packages:
- plugins
- web
- .github
- packages/*
ignoredBuiltDependencies:
- '@nestjs/core'
- '@parcel/watcher'
+15 -10
View File
@@ -13,12 +13,13 @@ 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 --frozen-lockfile build && \
SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich --filter @immich/plugin-sdk --frozen-lockfile build && \
SHARP_FORCE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile --prod --no-optional deploy /output/server-pruned
FROM builder AS web
@@ -56,22 +57,26 @@ ARG TARGETPLATFORM
COPY --from=ghcr.io/jdx/mise:2026.3.12@sha256:0210678cbf58413806531a27adb2c7daf1c37238e56e8f7ea381d73521571775 /usr/local/bin/mise /usr/local/bin/mise
WORKDIR /usr/src/app
COPY ./packages/plugins/mise.toml ./packages/plugins/
ENV MISE_TRUSTED_CONFIG_PATHS=/usr/src/app/packages/plugins/mise.toml
WORKDIR /app
COPY ./mise.toml ./mise.toml
COPY ./packages/plugin-core/mise.toml ./packages/plugin-core/
ENV MISE_TRUSTED_CONFIG_PATHS=/app/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 --cd packages/plugins
mise install
COPY ./packages/plugin-core ./packages/plugin-core/
COPY ./packages/plugin-sdk ./packages/plugin-sdk/
COPY ./packages/plugins ./packages/plugins/
# Build plugins
RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \
RUN --mount=type=cache,id=pnpm-packages,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 \
cd packages/plugins && mise run build
mise //:plugins
FROM ghcr.io/immich-app/base-server-prod:202605051129@sha256:50f7ffe4ed31e360c90c4905bd5f6658f2a121297544e3fe9368e338b3f76bcd
@@ -83,8 +88,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 /usr/src/app/packages/plugins/dist /build/corePlugin/dist
COPY --from=plugins /usr/src/app/packages/plugins/manifest.json /build/corePlugin/manifest.json
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
RUN ln -s ../../cli/bin/immich server/bin/immich
COPY LICENSE /licenses/LICENSE.txt
COPY LICENSE /LICENSE
+2
View File
@@ -58,6 +58,7 @@ run = "email dev -p 3050 --dir src/emails"
[tasks.ci-unit]
run = [
{ task = ":install" },
{ task = "//:plugins" },
{ task = ":format" },
{ task = ":lint" },
{ task = ":check" },
@@ -67,6 +68,7 @@ run = [
[tasks.ci-medium]
run = [
{ task = ":install" },
{ task = "//packages/plugin-core:build" },
{ task = ":test-medium --run" },
]
+2
View File
@@ -37,6 +37,7 @@
},
"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",
@@ -74,6 +75,7 @@
"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",
@@ -0,0 +1,56 @@
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' }]));
});
});
});
+22 -17
View File
@@ -1,7 +1,12 @@
import { Controller, Get, Param } from '@nestjs/common';
import { Controller, Get, Param, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { PluginResponseDto, PluginTriggerResponseDto } from 'src/dtos/plugin.dto';
import {
PluginMethodResponseDto,
PluginMethodSearchDto,
PluginResponseDto,
PluginSearchDto,
} from 'src/dtos/plugin.dto';
import { Permission } from 'src/enum';
import { Authenticated } from 'src/middleware/auth.guard';
import { PluginService } from 'src/services/plugin.service';
@@ -12,26 +17,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: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
history: HistoryBuilder.v3(),
})
getPlugins(): Promise<PluginResponseDto[]> {
return this.service.getAll();
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);
}
@Get(':id')
@@ -39,7 +44,7 @@ export class PluginController {
@Endpoint({
summary: 'Retrieve a plugin',
description: 'Retrieve information about a specific plugin by its ID.',
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
history: HistoryBuilder.v3(),
})
getPlugin(@Param() { id }: UUIDParamDto): Promise<PluginResponseDto> {
return this.service.get(id);
@@ -0,0 +1,113 @@
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' }]));
});
});
});
+38 -9
View File
@@ -1,8 +1,15 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common';
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import { WorkflowCreateDto, WorkflowResponseDto, WorkflowUpdateDto } from 'src/dtos/workflow.dto';
import {
WorkflowCreateDto,
WorkflowResponseDto,
WorkflowSearchDto,
WorkflowShareResponseDto,
WorkflowTriggerResponseDto,
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';
@@ -18,7 +25,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: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
history: HistoryBuilder.v3(),
})
createWorkflow(@Auth() auth: AuthDto, @Body() dto: WorkflowCreateDto): Promise<WorkflowResponseDto> {
return this.service.create(auth, dto);
@@ -29,10 +36,21 @@ export class WorkflowController {
@Endpoint({
summary: 'List all workflows',
description: 'Retrieve a list of workflows available to the authenticated user.',
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
history: HistoryBuilder.v3(),
})
getWorkflows(@Auth() auth: AuthDto): Promise<WorkflowResponseDto[]> {
return this.service.getAll(auth);
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();
}
@Get(':id')
@@ -40,19 +58,30 @@ export class WorkflowController {
@Endpoint({
summary: 'Retrieve a workflow',
description: 'Retrieve information about a specific workflow by its ID.',
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
history: HistoryBuilder.v3(),
})
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: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
history: HistoryBuilder.v3(),
})
updateWorkflow(
@Auth() auth: AuthDto,
@@ -68,7 +97,7 @@ export class WorkflowController {
@Endpoint({
summary: 'Delete a workflow',
description: 'Delete a workflow by its ID.',
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
history: HistoryBuilder.v3(),
})
deleteWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
+37 -52
View File
@@ -8,8 +8,6 @@ import {
ChecksumAlgorithm,
MemoryType,
Permission,
PluginContext,
PluginTriggerType,
SharedLinkType,
SourceType,
UserAvatarColor,
@@ -18,10 +16,8 @@ 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 { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table';
import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table';
import { PluginTable } from 'src/schema/tables/plugin.table';
import { UserMetadataItem } from 'src/types';
import type { ActionConfig, FilterConfig, JSONSchema } from 'src/types/plugin-schema.types';
export type AuthUser = {
id: string;
@@ -276,42 +272,7 @@ export type AssetFace = {
isVisible: boolean;
};
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;
};
export type Plugin = Selectable<PluginTable>;
const userColumns = ['id', 'name', 'email', 'avatarColor', 'profileImagePath', 'profileChangedAt'] as const;
const userWithPrefixColumns = [
@@ -343,6 +304,32 @@ 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',
@@ -374,6 +361,15 @@ 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',
@@ -487,17 +483,6 @@ 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,6 +200,10 @@ 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
@@ -0,0 +1,32 @@
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) {}
+20 -31
View File
@@ -1,39 +1,29 @@
import { createZodDto } from 'nestjs-zod';
import { PluginContextSchema } from 'src/enum';
import { JSONSchemaSchema } from 'src/types/plugin-schema.types';
import { JsonSchemaSchema } from 'src/dtos/json-schema.dto';
import { WorkflowTypeSchema } from 'src/enum';
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-]+)*))?$/;
const PluginManifestWasmSchema = z
.object({
path: z.string().describe('WASM file path'),
})
.meta({ id: 'PluginManifestWasmDto' });
export const PluginManifestMethodSchemaSchema = JsonSchemaSchema.nullable()
.optional()
.transform((value) => (value && Object.keys(value).length === 0 ? null : value));
const PluginManifestFilterSchema = z
const PluginManifestMethodSchema = 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(),
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"'),
})
.meta({ id: 'PluginManifestFilterDto' });
.meta({ id: 'PluginManifestMethodDto' });
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
const PluginManifestSchema = z
.object({
name: z
.string()
@@ -44,12 +34,11 @@ export const PluginManifestSchema = z
)
.describe('Plugin name (lowercase, numbers, hyphens only)'),
version: z.string().regex(semverRegex).describe('Plugin version (semver)'),
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'),
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'),
})
.meta({ id: 'PluginManifestDto' });
+69 -39
View File
@@ -1,39 +1,33 @@
import { createZodDto } from 'nestjs-zod';
import { PluginAction, PluginFilter } from 'src/database';
import { PluginContextSchema, PluginTriggerTypeSchema } from 'src/enum';
import { JSONSchemaSchema } from 'src/types/plugin-schema.types';
import { JsonSchemaDto } from 'src/dtos/json-schema.dto';
import { WorkflowTriggerSchema, WorkflowType, WorkflowTypeSchema } from 'src/enum';
import { asMethodString } from 'src/utils/workflow';
import z from 'zod';
const PluginTriggerResponseSchema = z
const PluginSearchSchema = z
.object({
type: PluginTriggerTypeSchema,
contextType: PluginContextSchema,
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(),
})
.meta({ id: 'PluginTriggerResponseDto' });
.meta({ id: 'PluginSearchDto' });
const PluginFilterResponseSchema = z
const PluginMethodResponseSchema = z
.object({
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'),
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(),
})
.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' });
.meta({ id: 'PluginMethodResponseDto' });
const PluginResponseSchema = z
.object({
@@ -45,29 +39,53 @@ const PluginResponseSchema = z
version: z.string().describe('Plugin version'),
createdAt: z.string().describe('Creation date'),
updatedAt: z.string().describe('Last update date'),
filters: z.array(PluginFilterResponseSchema).describe('Plugin filters'),
actions: z.array(PluginActionResponseSchema).describe('Plugin actions'),
methods: z.array(PluginMethodResponseSchema).describe('Plugin methods'),
})
.meta({ id: 'PluginResponseDto' });
export class PluginTriggerResponseDto extends createZodDto(PluginTriggerResponseSchema) {}
export class PluginResponseDto extends createZodDto(PluginResponseSchema) {}
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' });
type MapPlugin = {
export class PluginSearchDto extends createZodDto(PluginSearchSchema) {}
export class PluginResponseDto extends createZodDto(PluginResponseSchema) {}
export class PluginMethodSearchDto extends createZodDto(PluginMethodSearchSchema) {}
export class PluginMethodResponseDto extends createZodDto(PluginMethodResponseSchema) {}
type Plugin = {
id: string;
name: string;
title: string;
description: string;
author: string;
version: string;
wasmPath: string;
createdAt: Date;
updatedAt: Date;
filters: PluginFilter[];
actions: PluginAction[];
methods: PluginMethod[];
};
export function mapPlugin(plugin: MapPlugin): PluginResponseDto {
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 {
return {
id: plugin.id,
name: plugin.name,
@@ -77,7 +95,19 @@ export function mapPlugin(plugin: MapPlugin): PluginResponseDto {
version: plugin.version,
createdAt: plugin.createdAt.toISOString(),
updatedAt: plugin.updatedAt.toISOString(),
filters: plugin.filters,
actions: plugin.actions,
methods: plugin.methods.map((method) => mapMethod(method)),
};
}
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,
};
};
+98 -64
View File
@@ -1,101 +1,135 @@
import type { WorkflowStepConfig } from '@immich/plugin-sdk';
import { createZodDto } from 'nestjs-zod';
import type { WorkflowAction, WorkflowFilter } from 'src/database';
import { PluginTriggerTypeSchema } from 'src/enum';
import { ActionConfigSchema, FilterConfigSchema } from 'src/types/plugin-schema.types';
import { WorkflowTrigger, WorkflowTriggerSchema, WorkflowTypeSchema } from 'src/enum';
import z from 'zod';
const WorkflowFilterItemSchema = z
const WorkflowTriggerResponseSchema = z
.object({
pluginFilterId: z.uuidv4().describe('Plugin filter ID'),
filterConfig: FilterConfigSchema.optional(),
trigger: WorkflowTriggerSchema.describe('Trigger type'),
types: z.array(WorkflowTypeSchema).describe('Workflow types'),
})
.meta({ id: 'WorkflowFilterItemDto' });
.meta({ id: 'WorkflowTriggerResponseDto' });
const WorkflowActionItemSchema = z
const WorkflowSearchSchema = z
.object({
pluginActionId: z.uuidv4().describe('Plugin action ID'),
actionConfig: ActionConfigSchema.optional(),
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'),
})
.meta({ id: 'WorkflowActionItemDto' });
.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' });
const WorkflowCreateSchema = z
.object({
triggerType: PluginTriggerTypeSchema,
name: z.string().describe('Workflow name'),
description: z.string().optional().describe('Workflow description'),
trigger: WorkflowTriggerSchema.describe('Workflow trigger type'),
name: z.string().nullable().optional().describe('Workflow name'),
description: z.string().nullable().optional().describe('Workflow description'),
enabled: z.boolean().optional().describe('Workflow enabled'),
filters: z.array(WorkflowFilterItemSchema).describe('Workflow filters'),
actions: z.array(WorkflowActionItemSchema).describe('Workflow actions'),
steps: z.array(WorkflowStepSchema).optional(),
})
.meta({ id: 'WorkflowCreateDto' });
const WorkflowUpdateSchema = z
.object({
triggerType: PluginTriggerTypeSchema.optional(),
name: z.string().optional().describe('Workflow name'),
description: z.string().optional().describe('Workflow description'),
trigger: WorkflowTriggerSchema.optional().describe('Workflow trigger type'),
name: z.string().nullable().optional().describe('Workflow name'),
description: z.string().nullable().optional().describe('Workflow description'),
enabled: z.boolean().optional().describe('Workflow enabled'),
filters: z.array(WorkflowFilterItemSchema).optional().describe('Workflow filters'),
actions: z.array(WorkflowActionItemSchema).optional().describe('Workflow actions'),
steps: z.array(WorkflowStepSchema).optional(),
})
.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'),
ownerId: z.string().describe('Owner user ID'),
triggerType: PluginTriggerTypeSchema,
trigger: WorkflowTriggerSchema.describe('Workflow trigger type'),
name: z.string().nullable().describe('Workflow name'),
description: z.string().describe('Workflow description'),
description: z.string().nullable().describe('Workflow description'),
createdAt: z.string().describe('Creation date'),
updatedAt: z.string().describe('Update date'),
enabled: z.boolean().describe('Workflow enabled'),
filters: z.array(WorkflowFilterResponseSchema).describe('Workflow filters'),
actions: z.array(WorkflowActionResponseSchema).describe('Workflow actions'),
steps: z.array(WorkflowStepSchema).describe('Workflow steps'),
})
.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) {}
class WorkflowFilterResponseDto extends createZodDto(WorkflowFilterResponseSchema) {}
class WorkflowActionResponseDto extends createZodDto(WorkflowActionResponseSchema) {}
export class WorkflowShareResponseDto extends createZodDto(WorkflowShareResponseSchema) {}
export function mapWorkflowFilter(filter: WorkflowFilter): WorkflowFilterResponseDto {
return {
id: filter.id,
workflowId: filter.workflowId,
pluginFilterId: filter.pluginFilterId,
filterConfig: filter.filterConfig,
order: filter.order,
};
}
type Workflow = {
id: string;
createdAt: Date;
updatedAt: Date;
trigger: WorkflowTrigger;
name: string | null;
description: string | null;
enabled: boolean;
};
export function mapWorkflowAction(action: WorkflowAction): WorkflowActionResponseDto {
type WorkflowStep = {
enabled: boolean;
methodName: string;
config: WorkflowStepConfig | null;
pluginName: string;
};
export const mapWorkflow = (workflow: Workflow & { steps: WorkflowStep[] }): WorkflowResponseDto => {
return {
id: action.id,
workflowId: action.workflowId,
pluginActionId: action.pluginActionId,
actionConfig: action.actionConfig,
order: action.order,
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,
})),
};
}
};
export const mapWorkflowShare = (workflow: Workflow & { steps: WorkflowStep[] }): WorkflowShareResponseDto => {
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,
})),
};
};
+17 -6
View File
@@ -749,8 +749,11 @@ export enum BootstrapEventPriority {
StorageService = -195,
// Other services may need to queue jobs on bootstrap.
JobService = -190,
// Initialise config after other bootstrap services, stop other services from using config on bootstrap
// Initialize 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 {
@@ -863,7 +866,7 @@ export enum JobName {
Ocr = 'Ocr',
// Workflow
WorkflowRun = 'WorkflowRun',
WorkflowAssetCreate = 'WorkflowAssetCreate',
}
export const JobNameSchema = z.enum(JobName).describe('Job name').meta({ id: 'JobName' });
@@ -909,6 +912,7 @@ export enum DatabaseLock {
CLIPDimSize = 512,
Library = 1337,
NightlyJobs = 600,
PluginImport = 666,
MediaLocation = 700,
GetSystemConfig = 69,
BackupDatabase = 42,
@@ -1160,12 +1164,19 @@ export enum PluginContext {
export const PluginContextSchema = z.enum(PluginContext).describe('Plugin context').meta({ id: 'PluginContextType' });
export enum PluginTriggerType {
export enum WorkflowTrigger {
AssetCreate = 'AssetCreate',
PersonRecognized = 'PersonRecognized',
}
export const PluginTriggerTypeSchema = z
.enum(PluginTriggerType)
export const WorkflowTriggerSchema = z
.enum(WorkflowTrigger)
.describe('Plugin trigger type')
.meta({ id: 'PluginTriggerType' });
.meta({ id: 'WorkflowTrigger' });
export enum WorkflowType {
AssetV1 = 'AssetV1',
AssetPersonV1 = 'AssetPersonV1',
}
export const WorkflowTypeSchema = z.enum(WorkflowType).describe('Workflow type').meta({ id: 'WorkflowType' });
-17
View File
@@ -1,17 +0,0 @@
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,
},
];
+115 -115
View File
@@ -1,159 +1,159 @@
-- NOTE: This file is auto generated by ./sql-generator
-- PluginRepository.getPlugin
-- PluginRepository.getForLoad
select
"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",
"plugin"."id",
"plugin"."name",
"plugin"."version",
"plugin"."wasmBytes",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
*
"plugin_method"."name",
"plugin_method"."hostFunctions"
from
"plugin_filter"
"plugin_method"
where
"plugin_filter"."pluginId" = "plugin"."id"
"plugin_method"."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"
) as "methods"
from
"plugin"
where
"plugin"."id" = $1
"enabled" = $1
-- PluginRepository.getPluginByName
-- PluginRepository.search
select
"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",
"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_filter"
"plugin_method"
where
"plugin_filter"."pluginId" = "plugin"."id"
"plugin_method"."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"
where
"plugin"."name" = $1
-- PluginRepository.getAllPlugins
select
"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"
) as "methods"
from
"plugin"
order by
"plugin"."name"
-- PluginRepository.getFilter
-- 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_filter"
"plugin"
where
"id" = $1
"plugin"."name" = $1
-- PluginRepository.getFiltersByPlugin
-- 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"
from
"plugin_filter"
"plugin"
where
"pluginId" = $1
"plugin"."id" = $1
-- PluginRepository.getAction
-- PluginRepository.getForValidation
select
*
"plugin_method"."id",
"plugin_method"."name",
"plugin"."name" as "pluginName",
"plugin_method"."types"
from
"plugin_action"
where
"id" = $1
"plugin_method"
inner join "plugin" on "plugin_method"."pluginId" = "plugin"."id"
-- PluginRepository.getActionsByPlugin
-- PluginRepository.searchMethods
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"
from
"plugin_action"
where
"pluginId" = $1
"plugin_method"
inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId"
order by
"plugin_method"."name"
+84 -53
View File
@@ -1,70 +1,101 @@
-- NOTE: This file is auto generated by ./sql-generator
-- WorkflowRepository.getWorkflow
-- WorkflowRepository.search
select
*
"workflow"."id",
"workflow"."name",
"workflow"."description",
"workflow"."trigger",
"workflow"."enabled",
"workflow"."createdAt",
"workflow"."updatedAt",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"plugin"."name" as "pluginName",
"plugin_method"."name" as "methodName",
"workflow_step"."config",
"workflow_step"."enabled"
from
"workflow_step"
inner join "plugin_method" on "plugin_method"."id" = "workflow_step"."pluginMethodId"
inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId"
where
"workflow"."id" = "workflow_step"."workflowId"
) as agg
) as "steps"
from
"workflow"
order by
"createdAt" desc
-- WorkflowRepository.get
select
"workflow"."id",
"workflow"."name",
"workflow"."description",
"workflow"."trigger",
"workflow"."enabled",
"workflow"."createdAt",
"workflow"."updatedAt",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"plugin"."name" as "pluginName",
"plugin_method"."name" as "methodName",
"workflow_step"."config",
"workflow_step"."enabled"
from
"workflow_step"
inner join "plugin_method" on "plugin_method"."id" = "workflow_step"."pluginMethodId"
inner join "plugin" on "plugin"."id" = "plugin_method"."pluginId"
where
"workflow"."id" = "workflow_step"."workflowId"
) as agg
) as "steps"
from
"workflow"
where
"id" = $1
order by
"createdAt" desc
-- WorkflowRepository.getWorkflowsByOwner
-- WorkflowRepository.getForWorkflowRun
select
*
"workflow"."id",
"workflow"."name",
"workflow"."trigger",
(
select
coalesce(json_agg(agg), '[]')
from
(
select
"workflow_step"."id",
"workflow_step"."config",
"plugin_method"."pluginId" as "pluginId",
"plugin_method"."name" as "methodName",
"plugin_method"."types" as "types",
"plugin_method"."hostFunctions"
from
"workflow_step"
inner join "plugin_method" on "plugin_method"."id" = "workflow_step"."pluginMethodId"
where
"workflow_step"."workflowId" = "workflow"."id"
and "workflow_step"."enabled" = $1
) as agg
) as "steps"
from
"workflow"
where
"ownerId" = $1
order by
"createdAt" desc
-- WorkflowRepository.getWorkflowsByTrigger
select
*
from
"workflow"
where
"triggerType" = $1
and "enabled" = $2
-- WorkflowRepository.getWorkflowByOwnerAndTrigger
select
*
from
"workflow"
where
"ownerId" = $1
and "triggerType" = $2
"id" = $2
and "enabled" = $3
-- WorkflowRepository.deleteWorkflow
-- WorkflowRepository.delete
delete from "workflow"
where
"id" = $1
-- WorkflowRepository.getFilters
select
*
from
"workflow_filter"
where
"workflowId" = $1
order by
"order" asc
-- WorkflowRepository.deleteFiltersByWorkflow
delete from "workflow_filter"
where
"workflowId" = $1
-- WorkflowRepository.getActions
select
*
from
"workflow_action"
where
"workflowId" = $1
order by
"order" asc
+1 -1
View File
@@ -346,7 +346,7 @@ const getEnv = (): EnvData => {
root: folders.web,
indexHtml: join(folders.web, 'index.html'),
},
corePlugin: join(buildFolder, 'corePlugin'),
corePlugin: join(buildFolder, 'plugins', 'immich-plugin-core'),
},
setup: {
+4 -7
View File
@@ -171,8 +171,8 @@ export class JobRepository {
options: this.getJobOptions(item) || undefined,
} as JobItem & { data: any; options: JobsOptions | undefined };
if (job.options?.jobId || job.options?.deduplication) {
// need to use add() instead of addBulk() for jobId/deduplication to take effect
if (job.options?.jobId) {
// need to use add() instead of addBulk() for jobId deduplication
promises.push(this.getQueue(queueName).add(item.name, item.data, job.options));
} else {
itemsByQueue[queueName] = itemsByQueue[queueName] || [];
@@ -230,13 +230,10 @@ export class JobRepository {
return { priority: 1 };
}
case JobName.FacialRecognitionQueueAll: {
return { deduplication: { id: JobName.FacialRecognitionQueueAll } };
return { jobId: JobName.FacialRecognitionQueueAll };
}
case JobName.VersionCheck: {
return { deduplication: { id: JobName.VersionCheck } };
}
case JobName.DatabaseBackup: {
return { deduplication: { id: JobName.DatabaseBackup } };
return { jobId: JobName.VersionCheck };
}
default: {
return null;
@@ -119,6 +119,10 @@ export class LoggingRepository {
logLevels = level ? LOG_LEVELS.slice(LOG_LEVELS.indexOf(level)) : [];
}
getLogLevel(): LogLevel {
return logLevels[0] || LogLevel.Fatal;
}
verbose(message: string, ...details: LogDetails) {
this.handleMessage(LogLevel.Verbose, message, details);
}
+223 -145
View File
@@ -1,176 +1,254 @@
import { CallContext, Plugin as ExtismPlugin, newPlugin } from '@extism/extism';
import { Injectable } from '@nestjs/common';
import { Kysely } from 'kysely';
import { createPool, Pool } from 'generic-pool';
import { Insertable, Kysely } from 'kysely';
import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { readdir } from 'node:fs/promises';
import { columns } from 'src/database';
import { DummyValue, GenerateSql } from 'src/decorators';
import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto';
import { PluginMethodSearchDto, PluginSearchDto } from 'src/dtos/plugin.dto';
import { LogLevel, WorkflowType } from 'src/enum';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { DB } from 'src/schema';
import { PluginMethodTable } from 'src/schema/tables/plugin-method.table';
import { PluginTable } from 'src/schema/tables/plugin.table';
type PluginMethod = { pluginKey: string; methodName: string };
type PluginLoad = { key: string; label: string; wasmBytes: Buffer };
export type PluginHostFunction = (callContext: CallContext, input: bigint) => Promise<bigint> | bigint;
export type PluginLoadOptions = {
runInWorker?: boolean;
functions?: Record<string, PluginHostFunction>;
};
export type PluginMethodSearchResponse = {
id: string;
name: string;
pluginName: string;
types: WorkflowType[];
};
const levels = {
[LogLevel.Verbose]: 'trace',
[LogLevel.Debug]: 'debug',
[LogLevel.Log]: 'info',
[LogLevel.Warn]: 'warn',
[LogLevel.Error]: 'error',
[LogLevel.Fatal]: 'error',
} as const;
const asExtismLogLevel = (logLevel: LogLevel) => levels[logLevel] || 'info';
@Injectable()
export class PluginRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
private pluginMap: Map<string, { label: string; pool: Pool<ExtismPlugin> }> = new Map();
/**
* Loads a plugin from a validated manifest file in a transaction.
* This ensures all plugin, filter, and action operations are atomic.
* @param manifest The validated plugin manifest
* @param basePath The base directory path where the plugin is located
*/
async loadPlugin(manifest: PluginManifestDto, basePath: string) {
return this.db.transaction().execute(async (tx) => {
// Upsert the plugin
const plugin = await tx
.insertInto('plugin')
.values({
name: manifest.name,
title: manifest.title,
description: manifest.description,
author: manifest.author,
version: manifest.version,
wasmPath: `${basePath}/${manifest.wasm.path}`,
})
.onConflict((oc) =>
oc.column('name').doUpdateSet({
title: manifest.title,
description: manifest.description,
author: manifest.author,
version: manifest.version,
wasmPath: `${basePath}/${manifest.wasm.path}`,
}),
)
.returningAll()
.executeTakeFirstOrThrow();
const filters = manifest.filters
? await tx
.insertInto('plugin_filter')
.values(
manifest.filters.map((filter) => ({
pluginId: plugin.id,
methodName: filter.methodName,
title: filter.title,
description: filter.description,
supportedContexts: filter.supportedContexts,
schema: filter.schema,
})),
)
.onConflict((oc) =>
oc.column('methodName').doUpdateSet((eb) => ({
pluginId: eb.ref('excluded.pluginId'),
title: eb.ref('excluded.title'),
description: eb.ref('excluded.description'),
supportedContexts: eb.ref('excluded.supportedContexts'),
schema: eb.ref('excluded.schema'),
})),
)
.returningAll()
.execute()
: [];
const actions = manifest.actions
? await tx
.insertInto('plugin_action')
.values(
manifest.actions.map((action) => ({
pluginId: plugin.id,
methodName: action.methodName,
title: action.title,
description: action.description,
supportedContexts: action.supportedContexts,
schema: action.schema,
})),
)
.onConflict((oc) =>
oc.column('methodName').doUpdateSet((eb) => ({
pluginId: eb.ref('excluded.pluginId'),
title: eb.ref('excluded.title'),
description: eb.ref('excluded.description'),
supportedContexts: eb.ref('excluded.supportedContexts'),
schema: eb.ref('excluded.schema'),
})),
)
.returningAll()
.execute()
: [];
return { plugin, filters, actions };
});
}
async readDirectory(path: string) {
return readdir(path, { withFileTypes: true });
}
@GenerateSql({ params: [DummyValue.UUID] })
getPlugin(id: string) {
return this.db
.selectFrom('plugin')
.select((eb) => [
...columns.plugin,
jsonArrayFrom(
eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'),
).as('filters'),
jsonArrayFrom(
eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'),
).as('actions'),
])
.where('plugin.id', '=', id)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.STRING] })
getPluginByName(name: string) {
return this.db
.selectFrom('plugin')
.select((eb) => [
...columns.plugin,
jsonArrayFrom(
eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'),
).as('filters'),
jsonArrayFrom(
eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'),
).as('actions'),
])
.where('plugin.name', '=', name)
.executeTakeFirst();
constructor(
@InjectKysely() private db: Kysely<DB>,
private logger: LoggingRepository,
) {
this.logger.setContext(PluginRepository.name);
}
@GenerateSql()
getAllPlugins() {
getForLoad() {
return this.db
.selectFrom('plugin')
.select((eb) => [
...columns.plugin,
'plugin.id',
'plugin.name',
'plugin.version',
'plugin.wasmBytes',
jsonArrayFrom(
eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'),
).as('filters'),
jsonArrayFrom(
eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'),
).as('actions'),
eb
.selectFrom('plugin_method')
.whereRef('plugin_method.pluginId', '=', 'plugin.id')
.select(['plugin_method.name', 'plugin_method.hostFunctions']),
).as('methods'),
])
.where('enabled', '=', true)
.execute();
}
private queryBuilder() {
return this.db.selectFrom('plugin').select((eb) => [
'plugin.id',
'plugin.name',
'plugin.title',
'plugin.description',
'plugin.author',
'plugin.version',
'plugin.createdAt',
'plugin.updatedAt',
jsonArrayFrom(
eb
.selectFrom('plugin_method')
.select([...columns.pluginMethod, 'plugin.name as pluginName'])
.whereRef('plugin_method.pluginId', '=', 'plugin.id'),
).as('methods'),
]);
}
@GenerateSql()
search(dto: PluginSearchDto = {}) {
return this.queryBuilder()
.$if(!!dto.id, (qb) => qb.where('plugin.id', '=', dto.id!))
.$if(!!dto.name, (qb) => qb.where('plugin.name', '=', dto.name!))
.$if(!!dto.title, (qb) => qb.where('plugin.title', '=', dto.title!))
.$if(!!dto.description, (qb) => qb.where('plugin.description', '=', dto.description!))
.$if(!!dto.version, (qb) => qb.where('plugin.version', '=', dto.version!))
.orderBy('plugin.name')
.execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
getFilter(id: string) {
return this.db.selectFrom('plugin_filter').selectAll().where('id', '=', id).executeTakeFirst();
@GenerateSql({ params: [DummyValue.STRING] })
getByName(name: string) {
return this.queryBuilder().where('plugin.name', '=', name).executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID] })
getFiltersByPlugin(pluginId: string) {
return this.db.selectFrom('plugin_filter').selectAll().where('pluginId', '=', pluginId).execute();
get(id: string) {
return this.queryBuilder().where('plugin.id', '=', id).executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID] })
getAction(id: string) {
return this.db.selectFrom('plugin_action').selectAll().where('id', '=', id).executeTakeFirst();
@GenerateSql()
getForValidation(): Promise<PluginMethodSearchResponse[]> {
return this.db
.selectFrom('plugin_method')
.innerJoin('plugin', 'plugin_method.pluginId', 'plugin.id')
.select(['plugin_method.id', 'plugin_method.name', 'plugin.name as pluginName', 'plugin_method.types'])
.execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
getActionsByPlugin(pluginId: string) {
return this.db.selectFrom('plugin_action').selectAll().where('pluginId', '=', pluginId).execute();
@GenerateSql()
searchMethods(dto: PluginMethodSearchDto = {}) {
return this.db
.selectFrom('plugin_method')
.innerJoin('plugin', 'plugin.id', 'plugin_method.pluginId')
.select(['plugin.name as pluginName', 'plugin_method.pluginId', 'plugin_method.id', ...columns.pluginMethod])
.$if(!!dto.id, (qb) => qb.where('plugin_method.id', '=', dto.id!))
.$if(!!dto.name, (qb) => qb.where('plugin_method.name', '=', dto.name!))
.$if(!!dto.title, (qb) => qb.where('plugin_method.title', '=', dto.title!))
.$if(!!dto.type, (qb) => qb.where('plugin_method.types', '@>', [dto.type!]))
.$if(!!dto.description, (qb) => qb.where('plugin_method.description', '=', dto.description!))
.$if(!!dto.pluginVersion, (qb) => qb.where('plugin.version', '=', dto.pluginVersion!))
.$if(!!dto.pluginName, (qb) => qb.where('plugin.name', '=', dto.pluginName!))
.orderBy('plugin_method.name')
.execute();
}
async upsert(dto: Insertable<PluginTable>, initialMethods: Omit<Insertable<PluginMethodTable>, 'pluginId'>[]) {
return this.db.transaction().execute(async (tx) => {
// Upsert the plugin
const plugin = await tx
.insertInto('plugin')
.values(dto)
.onConflict((oc) =>
oc.columns(['name', 'version']).doUpdateSet((eb) => ({
title: eb.ref('excluded.title'),
description: eb.ref('excluded.description'),
author: eb.ref('excluded.author'),
version: eb.ref('excluded.version'),
wasmBytes: eb.ref('excluded.wasmBytes'),
})),
)
.returning(['id', 'name'])
.executeTakeFirstOrThrow();
// prune methods that no longer exist
if (initialMethods.length > 0) {
await tx
.deleteFrom('plugin_method')
.where('plugin_method.pluginId', '=', plugin.id)
.where(
'name',
'not in',
initialMethods.map((method) => method.name),
)
.execute();
}
const methods =
initialMethods.length > 0
? await tx
.insertInto('plugin_method')
.values(initialMethods.map((method) => ({ ...method, pluginId: plugin.id })))
.onConflict((oc) =>
oc.columns(['pluginId', 'name']).doUpdateSet(({ ref }) => ({
pluginId: ref('excluded.pluginId'),
name: ref('excluded.name'),
title: ref('excluded.title'),
description: ref('excluded.description'),
types: ref('excluded.types'),
hostFunctions: ref('excluded.hostFunctions'),
uiHints: ref('excluded.uiHints'),
schema: ref('excluded.schema'),
})),
)
.returningAll()
.execute()
: [];
return { ...plugin, methods };
});
}
async load({ key, label, wasmBytes }: PluginLoad, { runInWorker, functions }: PluginLoadOptions) {
const data = new Uint8Array(wasmBytes.buffer, wasmBytes.byteOffset, wasmBytes.byteLength);
const logger = LoggingRepository.create(`Plugin:${label}`);
const pool = createPool<ExtismPlugin>(
{
create: () =>
newPlugin(
{ wasm: [{ data }] },
{
useWasi: true,
runInWorker,
functions: {
'extism:host/user': functions ?? {},
},
logger: {
trace: (message) => logger.verbose(message),
info: (message) => logger.log(message),
debug: (message) => logger.debug(message),
warn: (message) => logger.warn(message),
error: (message) => logger.error(message),
} as Console,
logLevel: asExtismLogLevel(logger.getLogLevel()),
},
),
destroy: (plugin) => plugin.close(),
},
{ min: 1, max: 5 },
);
try {
await pool.ready();
this.pluginMap.set(key, { pool, label });
} catch (error: Error | any) {
throw new Error(`Unable to instantiate plugin: ${key}`, { cause: error });
}
}
async callMethod<T>({ pluginKey, methodName }: PluginMethod, input: unknown) {
const item = this.pluginMap.get(pluginKey);
if (!item) {
throw new Error(`No loaded plugin found for ${pluginKey}`);
}
const { pool, label } = item;
try {
const plugin = await pool.acquire();
try {
const result = await plugin.call(methodName, JSON.stringify(input));
return (result ? result.json() : result) as T;
} finally {
await pool.release(plugin);
}
} catch (error: Error | any) {
throw new Error(`Plugin method call failed: ${label}#${methodName}`, { cause: error });
}
}
}
+28 -9
View File
@@ -2,7 +2,15 @@ import { Injectable } from '@nestjs/common';
import archiver from 'archiver';
import chokidar, { ChokidarOptions } from 'chokidar';
import { escapePath, glob, globStream } from 'fast-glob';
import { constants, createReadStream, createWriteStream, existsSync, mkdirSync, ReadOptionsWithBuffer } from 'node:fs';
import {
constants,
createReadStream,
createWriteStream,
Dirent,
existsSync,
mkdirSync,
ReadOptionsWithBuffer,
} from 'node:fs';
import fs from 'node:fs/promises';
import path from 'node:path';
import { PassThrough, Readable, Writable } from 'node:stream';
@@ -50,6 +58,10 @@ export class StorageRepository {
return fs.readdir(folder);
}
readdirWithTypes(folder: string): Promise<Dirent[]> {
return fs.readdir(folder, { withFileTypes: true });
}
copyFile(source: string, target: string) {
return fs.copyFile(source, target);
}
@@ -117,17 +129,24 @@ export class StorageRepository {
}
async readFile(filepath: string, options?: ReadOptionsWithBuffer<Buffer>): Promise<Buffer> {
const file = await fs.open(filepath);
try {
const { buffer } = await file.read(options);
return buffer as Buffer;
} finally {
await file.close();
// read a slice
if (options) {
const file = await fs.open(filepath);
try {
const { buffer } = await file.read(options);
return buffer as Buffer;
} finally {
await file.close();
}
}
// read everything
return fs.readFile(filepath);
}
async readTextFile(filepath: string): Promise<string> {
return fs.readFile(filepath, 'utf8');
async readJsonFile<T>(filepath: string): Promise<T> {
const file = await fs.readFile(filepath, 'utf8');
return JSON.parse(file) as T;
}
async checkFileExists(filepath: string, mode = constants.F_OK): Promise<boolean> {
+149 -118
View File
@@ -1,149 +1,180 @@
import { Injectable } from '@nestjs/common';
import { Insertable, Kysely, Updateable } from 'kysely';
import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DummyValue, GenerateSql } from 'src/decorators';
import { PluginTriggerType } from 'src/enum';
import { WorkflowSearchDto } from 'src/dtos/workflow.dto';
import { DB } from 'src/schema';
import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table';
import { WorkflowStepTable } from 'src/schema/tables/workflow-step.table';
import { WorkflowTable } from 'src/schema/tables/workflow.table';
export type WorkflowStepUpsert = Omit<Insertable<WorkflowStepTable>, 'workflowId' | 'order'>;
@Injectable()
export class WorkflowRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
private queryBuilder(db?: Kysely<DB>) {
return (db ?? this.db)
.selectFrom('workflow')
.select([
'workflow.id',
'workflow.name',
'workflow.description',
'workflow.trigger',
'workflow.enabled',
'workflow.createdAt',
'workflow.updatedAt',
])
.select((eb) => [
jsonArrayFrom(
eb
.selectFrom('workflow_step')
.innerJoin('plugin_method', 'plugin_method.id', 'workflow_step.pluginMethodId')
.innerJoin('plugin', 'plugin.id', 'plugin_method.pluginId')
.whereRef('workflow.id', '=', 'workflow_step.workflowId')
.select([
'plugin.name as pluginName',
'plugin_method.name as methodName',
'workflow_step.config',
'workflow_step.enabled',
]),
).as('steps'),
]);
}
@GenerateSql({ params: [DummyValue.UUID] })
getWorkflow(id: string) {
search(dto: WorkflowSearchDto & { ownerId?: string }) {
return this.queryBuilder()
.$if(!!dto.ownerId, (qb) => qb.where('ownerId', '=', dto.ownerId!))
.$if(!!dto.trigger, (qb) => qb.where('trigger', '=', dto.trigger!))
.$if(dto.enabled !== undefined, (qb) => qb.where('enabled', '=', dto.enabled!))
.orderBy('createdAt', 'desc')
.execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
get(id: string) {
return this.queryBuilder().where('id', '=', id).executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID] })
getForWorkflowRun(id: string) {
return this.db
.selectFrom('workflow')
.selectAll()
.select(['workflow.id', 'workflow.name', 'workflow.trigger'])
.select((eb) => [
jsonArrayFrom(
eb
.selectFrom('workflow_step')
.innerJoin('plugin_method', 'plugin_method.id', 'workflow_step.pluginMethodId')
.whereRef('workflow_step.workflowId', '=', 'workflow.id')
.where('workflow_step.enabled', '=', true)
.select([
'workflow_step.id',
'workflow_step.config',
'plugin_method.pluginId as pluginId',
'plugin_method.name as methodName',
'plugin_method.types as types',
'plugin_method.hostFunctions',
]),
).as('steps'),
])
.where('id', '=', id)
.orderBy('createdAt', 'desc')
.where('enabled', '=', true)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID] })
getWorkflowsByOwner(ownerId: string) {
return this.db
.selectFrom('workflow')
.selectAll()
.where('ownerId', '=', ownerId)
.orderBy('createdAt', 'desc')
.execute();
}
@GenerateSql({ params: [PluginTriggerType.AssetCreate] })
getWorkflowsByTrigger(type: PluginTriggerType) {
return this.db
.selectFrom('workflow')
.selectAll()
.where('triggerType', '=', type)
.where('enabled', '=', true)
.execute();
}
@GenerateSql({ params: [DummyValue.UUID, PluginTriggerType.AssetCreate] })
getWorkflowByOwnerAndTrigger(ownerId: string, type: PluginTriggerType) {
return this.db
.selectFrom('workflow')
.selectAll()
.where('ownerId', '=', ownerId)
.where('triggerType', '=', type)
.where('enabled', '=', true)
.execute();
}
async createWorkflow(
workflow: Insertable<WorkflowTable>,
filters: Insertable<WorkflowFilterTable>[],
actions: Insertable<WorkflowActionTable>[],
) {
return await this.db.transaction().execute(async (tx) => {
const createdWorkflow = await tx.insertInto('workflow').values(workflow).returningAll().executeTakeFirstOrThrow();
if (filters.length > 0) {
const newFilters = filters.map((filter) => ({
...filter,
workflowId: createdWorkflow.id,
}));
await tx.insertInto('workflow_filter').values(newFilters).execute();
}
if (actions.length > 0) {
const newActions = actions.map((action) => ({
...action,
workflowId: createdWorkflow.id,
}));
await tx.insertInto('workflow_action').values(newActions).execute();
}
return createdWorkflow;
create(dto: Insertable<WorkflowTable>, steps?: WorkflowStepUpsert[]) {
return this.db.transaction().execute(async (tx) => {
const { id } = await tx.insertInto('workflow').values(dto).returning(['id']).executeTakeFirstOrThrow();
return this.replaceAndReturn(tx, id, steps);
});
}
async updateWorkflow(
id: string,
workflow: Updateable<WorkflowTable>,
filters: Insertable<WorkflowFilterTable>[] | undefined,
actions: Insertable<WorkflowActionTable>[] | undefined,
) {
return await this.db.transaction().execute(async (trx) => {
if (Object.keys(workflow).length > 0) {
await trx.updateTable('workflow').set(workflow).where('id', '=', id).execute();
update(id: string, dto: Updateable<WorkflowTable>, steps?: WorkflowStepUpsert[]) {
return this.db.transaction().execute(async (tx) => {
if (Object.values(dto).some((prop) => prop !== undefined)) {
await tx.updateTable('workflow').set(dto).where('id', '=', id).executeTakeFirstOrThrow();
}
if (filters !== undefined) {
await trx.deleteFrom('workflow_filter').where('workflowId', '=', id).execute();
if (filters.length > 0) {
const filtersWithWorkflowId = filters.map((filter) => ({
...filter,
workflowId: id,
}));
await trx.insertInto('workflow_filter').values(filtersWithWorkflowId).execute();
}
}
if (actions !== undefined) {
await trx.deleteFrom('workflow_action').where('workflowId', '=', id).execute();
if (actions.length > 0) {
const actionsWithWorkflowId = actions.map((action) => ({
...action,
workflowId: id,
}));
await trx.insertInto('workflow_action').values(actionsWithWorkflowId).execute();
}
}
return await trx.selectFrom('workflow').selectAll().where('id', '=', id).executeTakeFirstOrThrow();
return this.replaceAndReturn(tx, id, steps);
});
}
private async replaceAndReturn(tx: Kysely<DB>, workflowId: string, steps?: WorkflowStepUpsert[]) {
if (steps) {
await tx.deleteFrom('workflow_step').where('workflowId', '=', workflowId).execute();
if (steps.length > 0) {
await tx
.insertInto('workflow_step')
.values(
steps.map((step, i) => ({
workflowId,
enabled: step.enabled ?? true,
pluginMethodId: step.pluginMethodId,
config: step.config,
order: i,
})),
)
.returningAll()
.execute();
}
}
return this.queryBuilder(tx).where('id', '=', workflowId).executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID] })
async deleteWorkflow(id: string) {
async delete(id: string) {
await this.db.deleteFrom('workflow').where('id', '=', id).execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
getFilters(workflowId: string) {
getForAssetV1(assetId: string) {
return this.db
.selectFrom('workflow_filter')
.selectAll()
.where('workflowId', '=', workflowId)
.orderBy('order', 'asc')
.execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
async deleteFiltersByWorkflow(workflowId: string) {
await this.db.deleteFrom('workflow_filter').where('workflowId', '=', workflowId).execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
getActions(workflowId: string) {
return this.db
.selectFrom('workflow_action')
.selectAll()
.where('workflowId', '=', workflowId)
.orderBy('order', 'asc')
.execute();
.selectFrom('asset')
.leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id')
.select((eb) => [
...columns.workflowAssetV1,
jsonObjectFrom(
eb
.selectFrom('asset_exif')
.select([
'asset_exif.make',
'asset_exif.model',
'asset_exif.orientation',
'asset_exif.dateTimeOriginal',
'asset_exif.modifyDate',
'asset_exif.exifImageWidth',
'asset_exif.exifImageHeight',
'asset_exif.fileSizeInByte',
'asset_exif.lensModel',
'asset_exif.fNumber',
'asset_exif.focalLength',
'asset_exif.iso',
'asset_exif.latitude',
'asset_exif.longitude',
'asset_exif.city',
'asset_exif.state',
'asset_exif.country',
'asset_exif.description',
'asset_exif.fps',
'asset_exif.exposureTime',
'asset_exif.livePhotoCID',
'asset_exif.timeZone',
'asset_exif.projectionType',
'asset_exif.profileDescription',
'asset_exif.colorspace',
'asset_exif.bitsPerSample',
'asset_exif.autoStackId',
'asset_exif.rating',
'asset_exif.tags',
'asset_exif.updatedAt',
])
.whereRef('asset_exif.assetId', '=', 'asset.id'),
).as('exifInfo'),
])
.where('id', '=', assetId)
.executeTakeFirstOrThrow();
}
}

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