Files
LWLT-AI/control-plane/migrations/001_initial.sql
2026-07-13 19:57:46 +08:00

162 lines
5.1 KiB
SQL

CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE IF NOT EXISTS schema_migrations (
version text PRIMARY KEY,
applied_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS organizations (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
slug text NOT NULL UNIQUE,
name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id uuid NOT NULL REFERENCES organizations(id),
username text NOT NULL,
password_hash text NOT NULL,
role text NOT NULL DEFAULT 'admin' CHECK (role = 'admin'),
is_active boolean NOT NULL DEFAULT true,
failed_login_count integer NOT NULL DEFAULT 0,
locked_until timestamptz,
last_login_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (organization_id, username)
);
CREATE TABLE IF NOT EXISTS sessions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash bytea NOT NULL UNIQUE,
csrf_token_hash bytea NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
last_seen_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz NOT NULL,
idle_expires_at timestamptz NOT NULL,
revoked_at timestamptz,
ip_address inet,
user_agent text
);
CREATE INDEX IF NOT EXISTS sessions_active_idx
ON sessions (token_hash, expires_at, idle_expires_at)
WHERE revoked_at IS NULL;
CREATE TABLE IF NOT EXISTS tasks (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id uuid NOT NULL REFERENCES organizations(id),
task_id text NOT NULL,
original_text_ciphertext text,
operation_ciphertext text,
parse_response_ciphertext text,
execution_result_ciphertext text,
operation jsonb,
parse_response jsonb,
summary jsonb,
status text NOT NULL,
stage text NOT NULL,
message text,
error text,
handoff_status text,
execution_result jsonb,
created_by uuid REFERENCES users(id),
confirmed_by uuid REFERENCES users(id),
confirmed_at timestamptz,
idempotency_key text,
lease_owner text,
lease_expires_at timestamptz,
last_event_id bigint,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (organization_id, task_id),
UNIQUE (organization_id, idempotency_key)
);
CREATE INDEX IF NOT EXISTS tasks_queue_idx
ON tasks (organization_id, status, lease_expires_at, created_at);
CREATE TABLE IF NOT EXISTS task_events (
id bigserial PRIMARY KEY,
organization_id uuid NOT NULL REFERENCES organizations(id),
task_id uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
status text NOT NULL,
stage text NOT NULL,
message text,
payload jsonb,
actor_user_id uuid REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS task_events_task_idx ON task_events (task_id, id);
CREATE TABLE IF NOT EXISTS task_attempts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id uuid NOT NULL REFERENCES organizations(id),
task_id uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
attempt_no integer NOT NULL,
phase text NOT NULL,
status text NOT NULL,
request_hash text,
response_hash text,
error_code text,
details jsonb,
started_at timestamptz NOT NULL DEFAULT now(),
finished_at timestamptz,
UNIQUE (task_id, attempt_no, phase)
);
CREATE TABLE IF NOT EXISTS idempotency_keys (
id bigserial PRIMARY KEY,
organization_id uuid NOT NULL REFERENCES organizations(id),
scope text NOT NULL,
idempotency_key text NOT NULL,
request_hash text NOT NULL,
task_id uuid REFERENCES tasks(id) ON DELETE CASCADE,
created_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz,
UNIQUE (organization_id, scope, idempotency_key)
);
CREATE TABLE IF NOT EXISTS audit_events (
id bigserial PRIMARY KEY,
organization_id uuid NOT NULL REFERENCES organizations(id),
actor_user_id uuid REFERENCES users(id),
event_type text NOT NULL,
entity_type text NOT NULL,
entity_id text,
request_id text,
metadata jsonb,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS audit_events_created_idx ON audit_events (organization_id, created_at DESC);
CREATE TABLE IF NOT EXISTS browser_connections (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id uuid NOT NULL REFERENCES organizations(id),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
connection_id text NOT NULL,
extension_version text,
status text NOT NULL DEFAULT 'connected',
last_seen_at timestamptz NOT NULL DEFAULT now(),
metadata jsonb,
UNIQUE (organization_id, connection_id)
);
CREATE TABLE IF NOT EXISTS outbox_events (
id bigserial PRIMARY KEY,
organization_id uuid NOT NULL REFERENCES organizations(id),
topic text NOT NULL,
aggregate_type text NOT NULL,
aggregate_id text NOT NULL,
payload jsonb NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
published_at timestamptz
);
CREATE INDEX IF NOT EXISTS outbox_pending_idx ON outbox_events (organization_id, created_at) WHERE published_at IS NULL;