103 lines
5.0 KiB
PL/PgSQL
103 lines
5.0 KiB
PL/PgSQL
-- Migration 000001: Initial schema
|
|
-- Tables: tenants, users, api_keys
|
|
-- Row Level Security enabled for logical multi-tenancy (physical isolation in V2)
|
|
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
|
|
-- ============================================================
|
|
-- TENANTS
|
|
-- ============================================================
|
|
CREATE TABLE tenants (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
name TEXT NOT NULL,
|
|
slug TEXT NOT NULL UNIQUE, -- URL-safe identifier, e.g. "acme-corp"
|
|
plan TEXT NOT NULL DEFAULT 'starter' CHECK (plan IN ('starter', 'business', 'enterprise')),
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_tenants_slug ON tenants(slug);
|
|
|
|
-- ============================================================
|
|
-- USERS
|
|
-- ============================================================
|
|
CREATE TABLE users (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
external_id TEXT NOT NULL, -- Keycloak subject (sub claim)
|
|
email TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('admin', 'manager', 'user', 'auditor')),
|
|
department TEXT, -- e.g. "finance", "legal", "engineering"
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE (tenant_id, external_id),
|
|
UNIQUE (tenant_id, email)
|
|
);
|
|
|
|
CREATE INDEX idx_users_tenant_id ON users(tenant_id);
|
|
CREATE INDEX idx_users_external_id ON users(external_id);
|
|
CREATE INDEX idx_users_email ON users(tenant_id, email);
|
|
|
|
-- ============================================================
|
|
-- API KEYS
|
|
-- Keys are stored as SHA-256 hashes — never in plaintext.
|
|
-- The first 8 chars are kept as prefix for identification (e.g. "sk-vyl_ab12cd34...").
|
|
-- ============================================================
|
|
CREATE TABLE api_keys (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL, -- Human-readable label, e.g. "Finance dept key"
|
|
key_hash TEXT NOT NULL UNIQUE, -- SHA-256(raw_key), hex-encoded
|
|
key_prefix TEXT NOT NULL, -- First 8 chars of raw key for display
|
|
provider TEXT NOT NULL CHECK (provider IN ('openai', 'anthropic', 'azure', 'mistral', 'ollama')),
|
|
scopes TEXT[] NOT NULL DEFAULT '{}', -- e.g. {'chat:completions', 'embeddings'}
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
expires_at TIMESTAMPTZ, -- NULL = no expiry
|
|
last_used_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_api_keys_tenant_id ON api_keys(tenant_id);
|
|
CREATE INDEX idx_api_keys_key_hash ON api_keys(key_hash);
|
|
|
|
-- ============================================================
|
|
-- ROW LEVEL SECURITY
|
|
-- App connects as role 'veylant_app' and sets app.tenant_id per session.
|
|
-- Policies are added here for completeness; the role is created in env setup.
|
|
-- ============================================================
|
|
ALTER TABLE tenants ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE api_keys ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Superuser bypasses RLS — dev connections use superuser, prod connections use veylant_app role.
|
|
-- Policies below apply only to veylant_app role (added in env-specific setup scripts).
|
|
|
|
-- ============================================================
|
|
-- updated_at auto-update trigger
|
|
-- ============================================================
|
|
CREATE OR REPLACE FUNCTION set_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER tenants_updated_at BEFORE UPDATE ON tenants FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
|
CREATE TRIGGER users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
|
CREATE TRIGGER api_keys_updated_at BEFORE UPDATE ON api_keys FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
|
|
|
-- ============================================================
|
|
-- Seed: default development tenant and admin user
|
|
-- (Development only — replaced by real onboarding in prod)
|
|
-- ============================================================
|
|
INSERT INTO tenants (id, name, slug, plan) VALUES
|
|
('00000000-0000-0000-0000-000000000001', 'Veylant Dev', 'veylant-dev', 'enterprise');
|
|
|
|
INSERT INTO users (tenant_id, external_id, email, role, department) VALUES
|
|
('00000000-0000-0000-0000-000000000001', 'dev-admin', 'admin@veylant.dev', 'admin', 'engineering');
|