mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 01:46:46 +08:00
* MUL-6350 refactor(plugins): reset the plugin system to Action / Hook / Resource foundations The V1 plugin system shipped 14 tables, an append-only revision log per relationship, a capability-snapshot compiler, a per-task execution manifest pinned by an INSERT trigger on agent_task_queue, release signing, trust tiers, and a bundled catalog — and never left its feature flag. It holds no production data, so this removes it outright instead of migrating it forward. What replaces it, per the design in MUL-6338: - A plugin relates to Multica in exactly three ways. Action (plugin calls Multica), Hook (Multica calls plugin), Resource (a static contribution with no call at all). "Who triggers" and "what capability is called" are orthogonal: a hook is declared once and lists which triggers may invoke it. - We never execute third-party code. A plugin runs only in a sandboxed iframe in the user's browser or on the author's own server. - Distribution is "install by URL, admin reads the scopes". No signature, no trust tier, no publisher verification — the scope list IS the trust decision. This change is the foundation, with no user-visible surface yet: - Decouples remote MCP first. RemoteMCPConnection/RemoteMCPTool/DigestBytes move into pkg/remotemcp, where they belonged; the protocol layer and daemon broker survive intact and dormant, ready to be rewired for the agent trigger. - Drops all 14 plugin_* tables, their triggers, the manifest-composition function, and agent_task_queue.plugin_execution_manifest_id. Adds three: plugin_installation (one row per workspace+plugin, holding the manifest snapshot the admin consented to), plugin_storage, plugin_secret. - Defines the manifest schema in full, once — surfaces, hooks with their triggers and events, resources, and a closed scope list. Parsing stays strict (DisallowUnknownFields, no trailing JSON). Contribution kinds this build does not ship yet fail installation loudly rather than installing half-working. - Two-step install: preview parses and returns the scopes without writing, then install requires consent matching the manifest exactly. Manifest fetches reuse the remote MCP SSRF guard; MULTICA_PLUGIN_DIR adds a local source for self-hosting and plugin authors. - Storage quotas are enforced on write and never by eviction. Secrets live in a separate encrypted table with no read path that returns a value. Co-authored-by: multica-agent <[email protected]> * MUL-6350 fix(migrate): register the plugin index builds for invalid-index cleanup Every migration that builds an index CONCURRENTLY must be registered in concurrentIndexCleanups, or an interrupted build leaves a permanently INVALID index that the `IF NOT EXISTS` retry records as success. TestEveryConcurrentUp\ BuildHasCleanup caught all three new plugin index migrations. Co-authored-by: multica-agent <[email protected]> * MUL-6350 fix(plugins): address review — byte quotas, version bound, secret pruning, script entry From the review on #7144, plus one contract change the surface design forced. - Storage quotas were accounted in characters and enforced in bytes. The usage query and the size_bytes it reports now use octet_length, and so do the plugin_storage column checks. A UTF-8 value could otherwise spend up to a quarter of the budget it appeared to. - manifest version had no length bound while plugin_installation.version caps at 64, so a legal-but-long semver failed at INSERT as an opaque install error instead of a parse error naming the field. - Upgrading pruned config values the new manifest dropped but left the matching plugin_secret rows. Ciphertext nothing can reach is exactly the residue pruneConfig exists to prevent; upgrade now prunes both in one transaction. - SetConfig wrote secrets and config across two tables with no transaction, so a failed config write could leave a saved form half-applied. - A duplicate first install mapped a unique-index violation to 502; it is 409. - net: is now an exact host match. It was a suffix match in the hook transport check while the same scope becomes an exact-host CSP connect-src in the surface runtime — one scope string cannot mean two things across the two places the consent screen claims to describe. - surfaces[].entry must be a .js/.mjs script, not an HTML document. The host generates the surface's document so it can attach the CSP derived from net:; a plugin-authored document would carry its own server's policy and net: would be a claim rather than a control. Tests: the three storage limits get a canonical unit matrix plus DB-backed coverage of the byte counting and the exclude-the-candidate-key replacement semantics; upgrade-time secret pruning is pinned end to end (verified failing without the fix); and the admin-only routes now have a real middleware-level 403 check, which every direct-handler test bypassed. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]>
587 lines
17 KiB
Go
587 lines
17 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: plugin.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const createPluginInstallation = `-- name: CreatePluginInstallation :one
|
|
INSERT INTO plugin_installation (
|
|
workspace_id, plugin_key, source_url, version, manifest, granted_scopes, installed_by
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id, workspace_id, plugin_key, source_url, version, manifest, granted_scopes, config, enabled, installed_by, created_at, updated_at
|
|
`
|
|
|
|
type CreatePluginInstallationParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
PluginKey string `json:"plugin_key"`
|
|
SourceUrl string `json:"source_url"`
|
|
Version string `json:"version"`
|
|
Manifest []byte `json:"manifest"`
|
|
GrantedScopes []byte `json:"granted_scopes"`
|
|
InstalledBy pgtype.UUID `json:"installed_by"`
|
|
}
|
|
|
|
func (q *Queries) CreatePluginInstallation(ctx context.Context, arg CreatePluginInstallationParams) (PluginInstallation, error) {
|
|
row := q.db.QueryRow(ctx, createPluginInstallation,
|
|
arg.WorkspaceID,
|
|
arg.PluginKey,
|
|
arg.SourceUrl,
|
|
arg.Version,
|
|
arg.Manifest,
|
|
arg.GrantedScopes,
|
|
arg.InstalledBy,
|
|
)
|
|
var i PluginInstallation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PluginKey,
|
|
&i.SourceUrl,
|
|
&i.Version,
|
|
&i.Manifest,
|
|
&i.GrantedScopes,
|
|
&i.Config,
|
|
&i.Enabled,
|
|
&i.InstalledBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const deletePluginInstallation = `-- name: DeletePluginInstallation :exec
|
|
DELETE FROM plugin_installation WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeletePluginInstallation(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, deletePluginInstallation, id)
|
|
return err
|
|
}
|
|
|
|
const deletePluginSecret = `-- name: DeletePluginSecret :execrows
|
|
DELETE FROM plugin_secret WHERE installation_id = $1 AND key = $2
|
|
`
|
|
|
|
type DeletePluginSecretParams struct {
|
|
InstallationID pgtype.UUID `json:"installation_id"`
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
func (q *Queries) DeletePluginSecret(ctx context.Context, arg DeletePluginSecretParams) (int64, error) {
|
|
result, err := q.db.Exec(ctx, deletePluginSecret, arg.InstallationID, arg.Key)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected(), nil
|
|
}
|
|
|
|
const deletePluginSecretsByInstallation = `-- name: DeletePluginSecretsByInstallation :exec
|
|
DELETE FROM plugin_secret WHERE installation_id = $1
|
|
`
|
|
|
|
func (q *Queries) DeletePluginSecretsByInstallation(ctx context.Context, installationID pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, deletePluginSecretsByInstallation, installationID)
|
|
return err
|
|
}
|
|
|
|
const deletePluginStorageByInstallation = `-- name: DeletePluginStorageByInstallation :exec
|
|
DELETE FROM plugin_storage WHERE installation_id = $1
|
|
`
|
|
|
|
func (q *Queries) DeletePluginStorageByInstallation(ctx context.Context, installationID pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, deletePluginStorageByInstallation, installationID)
|
|
return err
|
|
}
|
|
|
|
const deletePluginStorageValue = `-- name: DeletePluginStorageValue :execrows
|
|
DELETE FROM plugin_storage
|
|
WHERE installation_id = $1 AND scope_type = $2 AND scope_id = $3 AND key = $4
|
|
`
|
|
|
|
type DeletePluginStorageValueParams struct {
|
|
InstallationID pgtype.UUID `json:"installation_id"`
|
|
ScopeType string `json:"scope_type"`
|
|
ScopeID pgtype.UUID `json:"scope_id"`
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
func (q *Queries) DeletePluginStorageValue(ctx context.Context, arg DeletePluginStorageValueParams) (int64, error) {
|
|
result, err := q.db.Exec(ctx, deletePluginStorageValue,
|
|
arg.InstallationID,
|
|
arg.ScopeType,
|
|
arg.ScopeID,
|
|
arg.Key,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected(), nil
|
|
}
|
|
|
|
const getPluginInstallation = `-- name: GetPluginInstallation :one
|
|
SELECT id, workspace_id, plugin_key, source_url, version, manifest, granted_scopes, config, enabled, installed_by, created_at, updated_at FROM plugin_installation WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetPluginInstallation(ctx context.Context, id pgtype.UUID) (PluginInstallation, error) {
|
|
row := q.db.QueryRow(ctx, getPluginInstallation, id)
|
|
var i PluginInstallation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PluginKey,
|
|
&i.SourceUrl,
|
|
&i.Version,
|
|
&i.Manifest,
|
|
&i.GrantedScopes,
|
|
&i.Config,
|
|
&i.Enabled,
|
|
&i.InstalledBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getPluginSecret = `-- name: GetPluginSecret :one
|
|
SELECT id, installation_id, key, ciphertext, created_at, updated_at FROM plugin_secret
|
|
WHERE installation_id = $1 AND key = $2
|
|
`
|
|
|
|
type GetPluginSecretParams struct {
|
|
InstallationID pgtype.UUID `json:"installation_id"`
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
func (q *Queries) GetPluginSecret(ctx context.Context, arg GetPluginSecretParams) (PluginSecret, error) {
|
|
row := q.db.QueryRow(ctx, getPluginSecret, arg.InstallationID, arg.Key)
|
|
var i PluginSecret
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.InstallationID,
|
|
&i.Key,
|
|
&i.Ciphertext,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getPluginStorageUsage = `-- name: GetPluginStorageUsage :one
|
|
SELECT COUNT(*)::bigint AS key_count,
|
|
COALESCE(SUM(octet_length(value)), 0)::bigint AS total_bytes
|
|
FROM plugin_storage
|
|
WHERE installation_id = $1 AND scope_type = $2 AND scope_id = $3 AND key <> $4
|
|
`
|
|
|
|
type GetPluginStorageUsageParams struct {
|
|
InstallationID pgtype.UUID `json:"installation_id"`
|
|
ScopeType string `json:"scope_type"`
|
|
ScopeID pgtype.UUID `json:"scope_id"`
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
type GetPluginStorageUsageRow struct {
|
|
KeyCount int64 `json:"key_count"`
|
|
TotalBytes int64 `json:"total_bytes"`
|
|
}
|
|
|
|
// Quota accounting for one (installation, scope) pair. The candidate key is
|
|
// excluded so overwriting an existing key is measured as a replacement rather
|
|
// than an addition. octet_length, not char_length: the service compares these
|
|
// against byte budgets, and a UTF-8 character is up to 4 bytes.
|
|
func (q *Queries) GetPluginStorageUsage(ctx context.Context, arg GetPluginStorageUsageParams) (GetPluginStorageUsageRow, error) {
|
|
row := q.db.QueryRow(ctx, getPluginStorageUsage,
|
|
arg.InstallationID,
|
|
arg.ScopeType,
|
|
arg.ScopeID,
|
|
arg.Key,
|
|
)
|
|
var i GetPluginStorageUsageRow
|
|
err := row.Scan(&i.KeyCount, &i.TotalBytes)
|
|
return i, err
|
|
}
|
|
|
|
const getPluginStorageValue = `-- name: GetPluginStorageValue :one
|
|
SELECT id, installation_id, scope_type, scope_id, key, value, created_at, updated_at FROM plugin_storage
|
|
WHERE installation_id = $1 AND scope_type = $2 AND scope_id = $3 AND key = $4
|
|
`
|
|
|
|
type GetPluginStorageValueParams struct {
|
|
InstallationID pgtype.UUID `json:"installation_id"`
|
|
ScopeType string `json:"scope_type"`
|
|
ScopeID pgtype.UUID `json:"scope_id"`
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
func (q *Queries) GetPluginStorageValue(ctx context.Context, arg GetPluginStorageValueParams) (PluginStorage, error) {
|
|
row := q.db.QueryRow(ctx, getPluginStorageValue,
|
|
arg.InstallationID,
|
|
arg.ScopeType,
|
|
arg.ScopeID,
|
|
arg.Key,
|
|
)
|
|
var i PluginStorage
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.InstallationID,
|
|
&i.ScopeType,
|
|
&i.ScopeID,
|
|
&i.Key,
|
|
&i.Value,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getWorkspacePluginInstallation = `-- name: GetWorkspacePluginInstallation :one
|
|
SELECT id, workspace_id, plugin_key, source_url, version, manifest, granted_scopes, config, enabled, installed_by, created_at, updated_at FROM plugin_installation
|
|
WHERE workspace_id = $1 AND id = $2
|
|
`
|
|
|
|
type GetWorkspacePluginInstallationParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
ID pgtype.UUID `json:"id"`
|
|
}
|
|
|
|
func (q *Queries) GetWorkspacePluginInstallation(ctx context.Context, arg GetWorkspacePluginInstallationParams) (PluginInstallation, error) {
|
|
row := q.db.QueryRow(ctx, getWorkspacePluginInstallation, arg.WorkspaceID, arg.ID)
|
|
var i PluginInstallation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PluginKey,
|
|
&i.SourceUrl,
|
|
&i.Version,
|
|
&i.Manifest,
|
|
&i.GrantedScopes,
|
|
&i.Config,
|
|
&i.Enabled,
|
|
&i.InstalledBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getWorkspacePluginInstallationByKey = `-- name: GetWorkspacePluginInstallationByKey :one
|
|
SELECT id, workspace_id, plugin_key, source_url, version, manifest, granted_scopes, config, enabled, installed_by, created_at, updated_at FROM plugin_installation
|
|
WHERE workspace_id = $1 AND plugin_key = $2
|
|
`
|
|
|
|
type GetWorkspacePluginInstallationByKeyParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
PluginKey string `json:"plugin_key"`
|
|
}
|
|
|
|
func (q *Queries) GetWorkspacePluginInstallationByKey(ctx context.Context, arg GetWorkspacePluginInstallationByKeyParams) (PluginInstallation, error) {
|
|
row := q.db.QueryRow(ctx, getWorkspacePluginInstallationByKey, arg.WorkspaceID, arg.PluginKey)
|
|
var i PluginInstallation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PluginKey,
|
|
&i.SourceUrl,
|
|
&i.Version,
|
|
&i.Manifest,
|
|
&i.GrantedScopes,
|
|
&i.Config,
|
|
&i.Enabled,
|
|
&i.InstalledBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listPluginSecretKeys = `-- name: ListPluginSecretKeys :many
|
|
SELECT key, updated_at FROM plugin_secret
|
|
WHERE installation_id = $1
|
|
ORDER BY key ASC
|
|
`
|
|
|
|
type ListPluginSecretKeysRow struct {
|
|
Key string `json:"key"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
}
|
|
|
|
// Deliberately never selects ciphertext: the presence of a secret is readable,
|
|
// its value is not.
|
|
func (q *Queries) ListPluginSecretKeys(ctx context.Context, installationID pgtype.UUID) ([]ListPluginSecretKeysRow, error) {
|
|
rows, err := q.db.Query(ctx, listPluginSecretKeys, installationID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListPluginSecretKeysRow{}
|
|
for rows.Next() {
|
|
var i ListPluginSecretKeysRow
|
|
if err := rows.Scan(&i.Key, &i.UpdatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listPluginStorageKeys = `-- name: ListPluginStorageKeys :many
|
|
SELECT key, octet_length(value)::bigint AS size_bytes, updated_at
|
|
FROM plugin_storage
|
|
WHERE installation_id = $1 AND scope_type = $2 AND scope_id = $3
|
|
ORDER BY key ASC
|
|
`
|
|
|
|
type ListPluginStorageKeysParams struct {
|
|
InstallationID pgtype.UUID `json:"installation_id"`
|
|
ScopeType string `json:"scope_type"`
|
|
ScopeID pgtype.UUID `json:"scope_id"`
|
|
}
|
|
|
|
type ListPluginStorageKeysRow struct {
|
|
Key string `json:"key"`
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
}
|
|
|
|
func (q *Queries) ListPluginStorageKeys(ctx context.Context, arg ListPluginStorageKeysParams) ([]ListPluginStorageKeysRow, error) {
|
|
rows, err := q.db.Query(ctx, listPluginStorageKeys, arg.InstallationID, arg.ScopeType, arg.ScopeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListPluginStorageKeysRow{}
|
|
for rows.Next() {
|
|
var i ListPluginStorageKeysRow
|
|
if err := rows.Scan(&i.Key, &i.SizeBytes, &i.UpdatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listWorkspacePluginInstallations = `-- name: ListWorkspacePluginInstallations :many
|
|
SELECT id, workspace_id, plugin_key, source_url, version, manifest, granted_scopes, config, enabled, installed_by, created_at, updated_at FROM plugin_installation
|
|
WHERE workspace_id = $1
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
func (q *Queries) ListWorkspacePluginInstallations(ctx context.Context, workspaceID pgtype.UUID) ([]PluginInstallation, error) {
|
|
rows, err := q.db.Query(ctx, listWorkspacePluginInstallations, workspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []PluginInstallation{}
|
|
for rows.Next() {
|
|
var i PluginInstallation
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PluginKey,
|
|
&i.SourceUrl,
|
|
&i.Version,
|
|
&i.Manifest,
|
|
&i.GrantedScopes,
|
|
&i.Config,
|
|
&i.Enabled,
|
|
&i.InstalledBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const setPluginInstallationEnabled = `-- name: SetPluginInstallationEnabled :one
|
|
UPDATE plugin_installation
|
|
SET enabled = $2,
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
RETURNING id, workspace_id, plugin_key, source_url, version, manifest, granted_scopes, config, enabled, installed_by, created_at, updated_at
|
|
`
|
|
|
|
type SetPluginInstallationEnabledParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
func (q *Queries) SetPluginInstallationEnabled(ctx context.Context, arg SetPluginInstallationEnabledParams) (PluginInstallation, error) {
|
|
row := q.db.QueryRow(ctx, setPluginInstallationEnabled, arg.ID, arg.Enabled)
|
|
var i PluginInstallation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PluginKey,
|
|
&i.SourceUrl,
|
|
&i.Version,
|
|
&i.Manifest,
|
|
&i.GrantedScopes,
|
|
&i.Config,
|
|
&i.Enabled,
|
|
&i.InstalledBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updatePluginInstallationConfig = `-- name: UpdatePluginInstallationConfig :one
|
|
UPDATE plugin_installation
|
|
SET config = $2,
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
RETURNING id, workspace_id, plugin_key, source_url, version, manifest, granted_scopes, config, enabled, installed_by, created_at, updated_at
|
|
`
|
|
|
|
type UpdatePluginInstallationConfigParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
Config []byte `json:"config"`
|
|
}
|
|
|
|
func (q *Queries) UpdatePluginInstallationConfig(ctx context.Context, arg UpdatePluginInstallationConfigParams) (PluginInstallation, error) {
|
|
row := q.db.QueryRow(ctx, updatePluginInstallationConfig, arg.ID, arg.Config)
|
|
var i PluginInstallation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PluginKey,
|
|
&i.SourceUrl,
|
|
&i.Version,
|
|
&i.Manifest,
|
|
&i.GrantedScopes,
|
|
&i.Config,
|
|
&i.Enabled,
|
|
&i.InstalledBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updatePluginInstallationManifest = `-- name: UpdatePluginInstallationManifest :one
|
|
UPDATE plugin_installation
|
|
SET source_url = $2,
|
|
version = $3,
|
|
manifest = $4,
|
|
granted_scopes = $5,
|
|
config = $6,
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
RETURNING id, workspace_id, plugin_key, source_url, version, manifest, granted_scopes, config, enabled, installed_by, created_at, updated_at
|
|
`
|
|
|
|
type UpdatePluginInstallationManifestParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
SourceUrl string `json:"source_url"`
|
|
Version string `json:"version"`
|
|
Manifest []byte `json:"manifest"`
|
|
GrantedScopes []byte `json:"granted_scopes"`
|
|
Config []byte `json:"config"`
|
|
}
|
|
|
|
// Upgrade path: the re-consented manifest snapshot replaces the old one in
|
|
// place. Config values survive on purpose; fields the new manifest dropped are
|
|
// pruned by the service before this runs.
|
|
func (q *Queries) UpdatePluginInstallationManifest(ctx context.Context, arg UpdatePluginInstallationManifestParams) (PluginInstallation, error) {
|
|
row := q.db.QueryRow(ctx, updatePluginInstallationManifest,
|
|
arg.ID,
|
|
arg.SourceUrl,
|
|
arg.Version,
|
|
arg.Manifest,
|
|
arg.GrantedScopes,
|
|
arg.Config,
|
|
)
|
|
var i PluginInstallation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PluginKey,
|
|
&i.SourceUrl,
|
|
&i.Version,
|
|
&i.Manifest,
|
|
&i.GrantedScopes,
|
|
&i.Config,
|
|
&i.Enabled,
|
|
&i.InstalledBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const upsertPluginSecret = `-- name: UpsertPluginSecret :exec
|
|
INSERT INTO plugin_secret (installation_id, key, ciphertext)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (installation_id, key)
|
|
DO UPDATE SET ciphertext = EXCLUDED.ciphertext, updated_at = now()
|
|
`
|
|
|
|
type UpsertPluginSecretParams struct {
|
|
InstallationID pgtype.UUID `json:"installation_id"`
|
|
Key string `json:"key"`
|
|
Ciphertext []byte `json:"ciphertext"`
|
|
}
|
|
|
|
func (q *Queries) UpsertPluginSecret(ctx context.Context, arg UpsertPluginSecretParams) error {
|
|
_, err := q.db.Exec(ctx, upsertPluginSecret, arg.InstallationID, arg.Key, arg.Ciphertext)
|
|
return err
|
|
}
|
|
|
|
const upsertPluginStorageValue = `-- name: UpsertPluginStorageValue :one
|
|
INSERT INTO plugin_storage (installation_id, scope_type, scope_id, key, value)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
ON CONFLICT (installation_id, scope_type, scope_id, key)
|
|
DO UPDATE SET value = EXCLUDED.value, updated_at = now()
|
|
RETURNING id, installation_id, scope_type, scope_id, key, value, created_at, updated_at
|
|
`
|
|
|
|
type UpsertPluginStorageValueParams struct {
|
|
InstallationID pgtype.UUID `json:"installation_id"`
|
|
ScopeType string `json:"scope_type"`
|
|
ScopeID pgtype.UUID `json:"scope_id"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
func (q *Queries) UpsertPluginStorageValue(ctx context.Context, arg UpsertPluginStorageValueParams) (PluginStorage, error) {
|
|
row := q.db.QueryRow(ctx, upsertPluginStorageValue,
|
|
arg.InstallationID,
|
|
arg.ScopeType,
|
|
arg.ScopeID,
|
|
arg.Key,
|
|
arg.Value,
|
|
)
|
|
var i PluginStorage
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.InstallationID,
|
|
&i.ScopeType,
|
|
&i.ScopeID,
|
|
&i.Key,
|
|
&i.Value,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|