mirror of
https://github.com/immich-app/immich.git
synced 2026-06-06 06:05:21 -04:00
feat: workflows & plugins (#26727)
feat: plugins chore: better types feat: plugins
This commit is contained in:
@@ -60,7 +60,8 @@ import { PartnerAuditTable } from 'src/schema/tables/partner-audit.table';
|
||||
import { PartnerTable } from 'src/schema/tables/partner.table';
|
||||
import { PersonAuditTable } from 'src/schema/tables/person-audit.table';
|
||||
import { PersonTable } from 'src/schema/tables/person.table';
|
||||
import { PluginActionTable, PluginFilterTable, PluginTable } from 'src/schema/tables/plugin.table';
|
||||
import { PluginMethodTable } from 'src/schema/tables/plugin-method.table';
|
||||
import { PluginTable } from 'src/schema/tables/plugin.table';
|
||||
import { SessionTable } from 'src/schema/tables/session.table';
|
||||
import { SharedLinkAssetTable } from 'src/schema/tables/shared-link-asset.table';
|
||||
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
||||
@@ -82,7 +83,8 @@ import {
|
||||
VideoStreamSessionTable,
|
||||
VideoStreamVariantTable,
|
||||
} from 'src/schema/tables/video-stream.table';
|
||||
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';
|
||||
|
||||
@Extensions(['uuid-ossp', 'unaccent', 'cube', 'earthdistance', 'pg_trgm', 'plpgsql'])
|
||||
@Database({ name: 'immich' })
|
||||
@@ -143,11 +145,9 @@ export class ImmichDatabase {
|
||||
VideoStreamVariantTable,
|
||||
VideoStreamSegmentTable,
|
||||
PluginTable,
|
||||
PluginFilterTable,
|
||||
PluginActionTable,
|
||||
PluginMethodTable,
|
||||
WorkflowTable,
|
||||
WorkflowFilterTable,
|
||||
WorkflowActionTable,
|
||||
WorkflowStepTable,
|
||||
];
|
||||
|
||||
functions = [
|
||||
@@ -264,10 +264,8 @@ export interface DB {
|
||||
video_stream_segment: VideoStreamSegmentTable;
|
||||
|
||||
plugin: PluginTable;
|
||||
plugin_filter: PluginFilterTable;
|
||||
plugin_action: PluginActionTable;
|
||||
plugin_method: PluginMethodTable;
|
||||
|
||||
workflow: WorkflowTable;
|
||||
workflow_filter: WorkflowFilterTable;
|
||||
workflow_action: WorkflowActionTable;
|
||||
workflow_step: WorkflowStepTable;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`ALTER TABLE "workflow" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`ALTER TABLE "workflow" DROP COLUMN "updateId";`.execute(db);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
// take #2...
|
||||
await sql`DROP TABLE "workflow_action";`.execute(db);
|
||||
await sql`DROP TABLE "workflow_filter";`.execute(db);
|
||||
await sql`DROP TABLE "workflow";`.execute(db);
|
||||
await sql`DROP TABLE "plugin_action";`.execute(db);
|
||||
await sql`DROP TABLE "plugin_filter";`.execute(db);
|
||||
await sql`DROP TABLE "plugin";`.execute(db);
|
||||
|
||||
await sql`CREATE TABLE "plugin" (
|
||||
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"enabled" boolean NOT NULL DEFAULT true,
|
||||
"name" character varying NOT NULL,
|
||||
"version" character varying NOT NULL,
|
||||
"title" character varying NOT NULL,
|
||||
"description" character varying NOT NULL,
|
||||
"author" character varying NOT NULL,
|
||||
"wasmBytes" bytea NOT NULL,
|
||||
"createdAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "plugin_name_version_uq" UNIQUE ("name", "version"),
|
||||
CONSTRAINT "plugin_name_uq" UNIQUE ("name"),
|
||||
CONSTRAINT "plugin_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "plugin_name_idx" ON "plugin" ("name");`.execute(db);
|
||||
await sql`CREATE TABLE "plugin_method" (
|
||||
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"pluginId" uuid NOT NULL,
|
||||
"name" character varying NOT NULL,
|
||||
"title" character varying NOT NULL,
|
||||
"description" character varying NOT NULL,
|
||||
"types" character varying[] NOT NULL,
|
||||
"hostFunctions" boolean NOT NULL DEFAULT false,
|
||||
"uiHints" character varying[] NOT NULL DEFAULT '{}',
|
||||
"schema" jsonb,
|
||||
CONSTRAINT "plugin_method_pluginId_fkey" FOREIGN KEY ("pluginId") REFERENCES "plugin" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT "plugin_method_pluginId_name_uq" UNIQUE ("pluginId", "name"),
|
||||
CONSTRAINT "plugin_method_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "plugin_method_pluginId_idx" ON "plugin_method" ("pluginId");`.execute(db);
|
||||
await sql`CREATE TABLE "workflow" (
|
||||
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"ownerId" uuid NOT NULL,
|
||||
"trigger" character varying NOT NULL,
|
||||
"name" character varying,
|
||||
"description" character varying,
|
||||
"createdAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
"updateId" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"enabled" boolean NOT NULL DEFAULT true,
|
||||
CONSTRAINT "workflow_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "user" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT "workflow_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "workflow_ownerId_idx" ON "workflow" ("ownerId");`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "workflow_updatedAt"
|
||||
BEFORE UPDATE ON "workflow"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION updated_at();`.execute(db);
|
||||
await sql`CREATE TABLE "workflow_step" (
|
||||
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"enabled" boolean NOT NULL DEFAULT true,
|
||||
"workflowId" uuid NOT NULL,
|
||||
"pluginMethodId" uuid NOT NULL,
|
||||
"config" jsonb,
|
||||
"order" integer NOT NULL,
|
||||
CONSTRAINT "workflow_step_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT "workflow_step_pluginMethodId_fkey" FOREIGN KEY ("pluginMethodId") REFERENCES "plugin_method" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT "workflow_step_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "workflow_step_workflowId_idx" ON "workflow_step" ("workflowId");`.execute(db);
|
||||
await sql`CREATE INDEX "workflow_step_pluginMethodId_idx" ON "workflow_step" ("pluginMethodId");`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_workflow_updatedAt', '{"type":"trigger","name":"workflow_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"workflow_updatedAt\\"\\n BEFORE UPDATE ON \\"workflow\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_plugin_filter_supportedContexts_idx';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_plugin_action_supportedContexts_idx';`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(): Promise<void> {
|
||||
// not supported
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export class AssetExifTable {
|
||||
tags!: string[] | null;
|
||||
|
||||
@UpdateDateColumn({ default: () => 'clock_timestamp()' })
|
||||
updatedAt!: Generated<Date>;
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateIdColumn({ index: true })
|
||||
updateId!: Generated<string>;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table, Unique } from '@immich/sql-tools';
|
||||
import { JsonSchemaDto } from 'src/dtos/json-schema.dto';
|
||||
import { WorkflowType } from 'src/enum';
|
||||
import { PluginTable } from 'src/schema/tables/plugin.table';
|
||||
|
||||
@Unique({ columns: ['pluginId', 'name'] })
|
||||
@Table('plugin_method')
|
||||
export class PluginMethodTable {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: Generated<string>;
|
||||
|
||||
@ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
pluginId!: string;
|
||||
|
||||
@Column()
|
||||
name!: string;
|
||||
|
||||
@Column()
|
||||
title!: string;
|
||||
|
||||
@Column()
|
||||
description!: string;
|
||||
|
||||
@Column({ type: 'character varying', array: true })
|
||||
types!: Generated<WorkflowType[]>;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
hostFunctions!: Generated<boolean>;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
schema!: JsonSchemaDto | null;
|
||||
|
||||
@Column({ type: 'character varying', default: [], array: true })
|
||||
uiHints!: Generated<string[]>;
|
||||
}
|
||||
@@ -1,25 +1,29 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
ForeignKeyColumn,
|
||||
Generated,
|
||||
Index,
|
||||
PrimaryGeneratedColumn,
|
||||
Table,
|
||||
Timestamp,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from '@immich/sql-tools';
|
||||
import { PluginContext } from 'src/enum';
|
||||
import type { JSONSchema } from 'src/types/plugin-schema.types';
|
||||
|
||||
@Unique({ columns: ['name', 'version'] })
|
||||
@Table('plugin')
|
||||
export class PluginTable {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: Generated<string>;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
enabled!: Generated<boolean>;
|
||||
|
||||
@Column({ index: true, unique: true })
|
||||
name!: string;
|
||||
|
||||
@Column()
|
||||
version!: string;
|
||||
|
||||
@Column()
|
||||
title!: string;
|
||||
|
||||
@@ -29,11 +33,8 @@ export class PluginTable {
|
||||
@Column()
|
||||
author!: string;
|
||||
|
||||
@Column()
|
||||
version!: string;
|
||||
|
||||
@Column()
|
||||
wasmPath!: string;
|
||||
@Column({ type: 'bytea' })
|
||||
wasmBytes!: Buffer;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Generated<Timestamp>;
|
||||
@@ -41,55 +42,3 @@ export class PluginTable {
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
}
|
||||
|
||||
@Index({ columns: ['supportedContexts'], using: 'gin' })
|
||||
@Table('plugin_filter')
|
||||
export class PluginFilterTable {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: Generated<string>;
|
||||
|
||||
@ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
@Column({ index: true })
|
||||
pluginId!: string;
|
||||
|
||||
@Column({ index: true, unique: true })
|
||||
methodName!: string;
|
||||
|
||||
@Column()
|
||||
title!: string;
|
||||
|
||||
@Column()
|
||||
description!: string;
|
||||
|
||||
@Column({ type: 'character varying', array: true })
|
||||
supportedContexts!: Generated<PluginContext[]>;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
schema!: JSONSchema | null;
|
||||
}
|
||||
|
||||
@Index({ columns: ['supportedContexts'], using: 'gin' })
|
||||
@Table('plugin_action')
|
||||
export class PluginActionTable {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: Generated<string>;
|
||||
|
||||
@ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
@Column({ index: true })
|
||||
pluginId!: string;
|
||||
|
||||
@Column({ index: true, unique: true })
|
||||
methodName!: string;
|
||||
|
||||
@Column()
|
||||
title!: string;
|
||||
|
||||
@Column()
|
||||
description!: string;
|
||||
|
||||
@Column({ type: 'character varying', array: true })
|
||||
supportedContexts!: Generated<PluginContext[]>;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
schema!: JSONSchema | null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { WorkflowStepConfig } from '@immich/plugin-sdk';
|
||||
import { Column, ForeignKeyColumn, PrimaryGeneratedColumn, Table } from '@immich/sql-tools';
|
||||
import { Generated } from 'kysely';
|
||||
import { PluginMethodTable } from 'src/schema/tables/plugin-method.table';
|
||||
import { WorkflowTable } from 'src/schema/tables/workflow.table';
|
||||
|
||||
@Table('workflow_step')
|
||||
export class WorkflowStepTable {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: Generated<string>;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
enabled!: boolean;
|
||||
|
||||
@ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
workflowId!: string;
|
||||
|
||||
@ForeignKeyColumn(() => PluginMethodTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
pluginMethodId!: string;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
config!: WorkflowStepConfig | null;
|
||||
|
||||
@Column({ type: 'integer' })
|
||||
order!: number;
|
||||
}
|
||||
@@ -3,17 +3,17 @@ import {
|
||||
CreateDateColumn,
|
||||
ForeignKeyColumn,
|
||||
Generated,
|
||||
Index,
|
||||
PrimaryGeneratedColumn,
|
||||
Table,
|
||||
Timestamp,
|
||||
UpdateDateColumn,
|
||||
} from '@immich/sql-tools';
|
||||
import { PluginTriggerType } from 'src/enum';
|
||||
import { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table';
|
||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||
import { WorkflowTrigger } from 'src/enum';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import type { ActionConfig, FilterConfig } from 'src/types/plugin-schema.types';
|
||||
|
||||
@Table('workflow')
|
||||
@UpdatedAtTrigger('workflow_updatedAt')
|
||||
export class WorkflowTable {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: Generated<string>;
|
||||
@@ -22,57 +22,23 @@ export class WorkflowTable {
|
||||
ownerId!: string;
|
||||
|
||||
@Column()
|
||||
triggerType!: PluginTriggerType;
|
||||
trigger!: WorkflowTrigger;
|
||||
|
||||
@Column({ nullable: true })
|
||||
name!: string | null;
|
||||
|
||||
@Column()
|
||||
description!: string;
|
||||
@Column({ nullable: true })
|
||||
description!: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateIdColumn()
|
||||
updateId!: Generated<string>;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
enabled!: boolean;
|
||||
}
|
||||
|
||||
@Index({ columns: ['workflowId', 'order'] })
|
||||
@Index({ columns: ['pluginFilterId'] })
|
||||
@Table('workflow_filter')
|
||||
export class WorkflowFilterTable {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: Generated<string>;
|
||||
|
||||
@ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
workflowId!: Generated<string>;
|
||||
|
||||
@ForeignKeyColumn(() => PluginFilterTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
pluginFilterId!: string;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
filterConfig!: FilterConfig | null;
|
||||
|
||||
@Column({ type: 'integer' })
|
||||
order!: number;
|
||||
}
|
||||
|
||||
@Index({ columns: ['workflowId', 'order'] })
|
||||
@Index({ columns: ['pluginActionId'] })
|
||||
@Table('workflow_action')
|
||||
export class WorkflowActionTable {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: Generated<string>;
|
||||
|
||||
@ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
workflowId!: Generated<string>;
|
||||
|
||||
@ForeignKeyColumn(() => PluginActionTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
pluginActionId!: string;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
actionConfig!: ActionConfig | null;
|
||||
|
||||
@Column({ type: 'integer' })
|
||||
order!: number;
|
||||
enabled!: Generated<boolean>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user