Files
Containr/app/backend/migrations_goose/20261103000000_outbound_webhooks.sql
Tomas Dvorak f499678964 feat: outbound webhooks — signed event delivery + delivery log
Every audited action now emits a "resource.action" event (bare actions
get resource-qualified; already-qualified actions like service.clone
pass through). Webhooks subscribe per-user with exact names, resource.*
wildcards, or *.

- schema: outbound_webhooks + webhook_deliveries (sqlc + goose)
- dispatcher: 4 workers, 512-queue, 3 attempts with backoff, 10s timeout;
  deliveries record status/http-code/body(4KiB)/duration
- signing: X-Containr-Signature sha256=<hmac> over raw body;
  X-Containr-Event + X-Containr-Delivery headers; custom headers per hook
- api: CRUD + /deliveries + /test (ping) under authed group
- fix: SetAuditDB never called — LogAudit was a silent no-op at every
  call site; now wired at boot
- coverage: service create/delete audited (previously uninstrumented)
- cli: containr webhooks list|create|update|delete|deliveries|test
- mcp: 6 tools, delete requires confirm
- frontend: /settings/webhooks page — list/create/expand-deliveries/
  test/disable/delete, secret-shown-once banner; nav + account menu
- openapi + regenerated types; CLI.md + docs snapshot; roadmap ticks
2026-10-09 09:44:08 +02:00

37 lines
1.3 KiB
SQL

-- +goose Up
-- Outbound webhooks: user-owned HTTP endpoints that receive signed
-- platform events (resource.action), with a delivery log.
CREATE TABLE IF NOT EXISTS outbound_webhooks (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name varchar(255) NOT NULL,
url varchar(500) NOT NULL,
secret text NOT NULL DEFAULT '',
events jsonb NOT NULL DEFAULT '[]'::jsonb,
headers jsonb,
enabled boolean NOT NULL DEFAULT true,
created_at timestamptz DEFAULT now(),
updated_at timestamptz DEFAULT now()
);
CREATE TABLE IF NOT EXISTS webhook_deliveries (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
webhook_id uuid NOT NULL REFERENCES outbound_webhooks(id) ON DELETE CASCADE,
event varchar(255) NOT NULL,
payload jsonb,
status varchar(50) NOT NULL DEFAULT 'pending',
response_status integer,
response_body text,
attempts integer NOT NULL DEFAULT 0,
duration_ms integer,
created_at timestamptz DEFAULT now(),
delivered_at timestamptz
);
CREATE INDEX IF NOT EXISTS idx_outbound_webhooks_user ON outbound_webhooks(user_id);
CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_webhook ON webhook_deliveries(webhook_id, created_at DESC);
-- +goose Down
DROP TABLE IF EXISTS webhook_deliveries;
DROP TABLE IF EXISTS outbound_webhooks;