feat(plugin): scope secret-ref config by company

This commit is contained in:
Paperclip Bot 2026-06-03 06:31:01 +00:00 committed by Alkim Ake Gozen
parent 62863126a3
commit db0ef46900
19 changed files with 587 additions and 102 deletions

View file

@ -0,0 +1,11 @@
ALTER TABLE "plugin_config" ADD COLUMN IF NOT EXISTS "company_id" uuid;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'plugin_config_company_id_companies_id_fk') THEN
ALTER TABLE "plugin_config" ADD CONSTRAINT "plugin_config_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
DROP INDEX IF EXISTS "plugin_config_plugin_id_idx";--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "plugin_config_plugin_id_idx" ON "plugin_config" USING btree ("plugin_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "plugin_config_company_id_idx" ON "plugin_config" USING btree ("company_id");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "plugin_config_legacy_plugin_id_uq" ON "plugin_config" USING btree ("plugin_id") WHERE "plugin_config"."company_id" is null;--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "plugin_config_company_plugin_uq" ON "plugin_config" USING btree ("plugin_id","company_id") WHERE "plugin_config"."company_id" is not null;

View file

@ -659,6 +659,13 @@
"when": 1780040470886,
"tag": "0093_giant_green_goblin",
"breakpoints": true
},
{
"idx": 94,
"version": "7",
"when": 1780444800000,
"tag": "0094_plugin_config_company_scope",
"breakpoints": true
}
]
}
}

View file

@ -1,10 +1,13 @@
import { pgTable, uuid, text, timestamp, jsonb, uniqueIndex } from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm";
import { pgTable, uuid, text, timestamp, jsonb, uniqueIndex, index } from "drizzle-orm/pg-core";
import { companies } from "./companies.js";
import { plugins } from "./plugins.js";
/**
* `plugin_config` table stores operator-provided instance configuration
* for each plugin (one row per plugin, enforced by a unique index on
* `plugin_id`).
* `plugin_config` table stores operator-provided plugin configuration.
*
* New configuration is company-scoped. Legacy rows may still have a null
* `company_id` so existing installs keep working until re-saved.
*
* The `config_json` column holds the values that the operator enters in the
* plugin settings UI. These values are validated at runtime against the
@ -19,12 +22,21 @@ export const pluginConfig = pgTable(
pluginId: uuid("plugin_id")
.notNull()
.references(() => plugins.id, { onDelete: "cascade" }),
companyId: uuid("company_id")
.references(() => companies.id, { onDelete: "cascade" }),
configJson: jsonb("config_json").$type<Record<string, unknown>>().notNull().default({}),
lastError: text("last_error"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
pluginIdIdx: uniqueIndex("plugin_config_plugin_id_idx").on(table.pluginId),
pluginIdIdx: index("plugin_config_plugin_id_idx").on(table.pluginId),
companyIdIdx: index("plugin_config_company_id_idx").on(table.companyId),
legacyPluginIdUq: uniqueIndex("plugin_config_legacy_plugin_id_uq")
.on(table.pluginId)
.where(sql`${table.companyId} is null`),
companyPluginUq: uniqueIndex("plugin_config_company_plugin_uq")
.on(table.pluginId, table.companyId)
.where(sql`${table.companyId} is not null`),
}),
);