-- YakPanel 2026 core schema -- PostgreSQL 15+ compatible CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "citext"; -- Shared timestamps helper convention: -- created_at timestamptz not null default now() -- updated_at timestamptz not null default now() CREATE TABLE tenants ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), name varchar(150) NOT NULL, slug citext NOT NULL UNIQUE, status varchar(32) NOT NULL DEFAULT 'active', plan_code varchar(64) NOT NULL DEFAULT 'starter', settings jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE users ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), email citext NOT NULL UNIQUE, password_hash text NOT NULL, display_name varchar(120) NOT NULL, is_platform_admin boolean NOT NULL DEFAULT false, last_login_at timestamptz NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE tenant_users ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, status varchar(32) NOT NULL DEFAULT 'active', joined_at timestamptz NOT NULL DEFAULT now(), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (tenant_id, user_id) ); CREATE TABLE roles ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, name varchar(64) NOT NULL, description text NOT NULL DEFAULT '', is_system boolean NOT NULL DEFAULT false, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (tenant_id, name) ); CREATE TABLE permissions ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), code varchar(100) NOT NULL UNIQUE, description text NOT NULL DEFAULT '', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE role_permissions ( role_id uuid NOT NULL REFERENCES roles(id) ON DELETE CASCADE, permission_id uuid NOT NULL REFERENCES permissions(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (role_id, permission_id) ); CREATE TABLE user_roles ( tenant_user_id uuid NOT NULL REFERENCES tenant_users(id) ON DELETE CASCADE, role_id uuid NOT NULL REFERENCES roles(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (tenant_user_id, role_id) ); CREATE TABLE scopes ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, role_id uuid NULL REFERENCES roles(id) ON DELETE CASCADE, resource_type varchar(64) NOT NULL, resource_id uuid NULL, action varchar(64) NOT NULL, effect varchar(8) NOT NULL DEFAULT 'allow', conditions jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE servers ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, name varchar(120) NOT NULL, host varchar(255) NOT NULL, port integer NOT NULL DEFAULT 443, os_type varchar(32) NOT NULL, status varchar(32) NOT NULL DEFAULT 'provisioning', region varchar(64) NOT NULL DEFAULT 'global', labels jsonb NOT NULL DEFAULT '[]'::jsonb, heartbeat_at timestamptz NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (tenant_id, name) ); CREATE TABLE server_agents ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, server_id uuid NOT NULL REFERENCES servers(id) ON DELETE CASCADE, agent_uid varchar(128) NOT NULL UNIQUE, agent_version varchar(32) NOT NULL, capabilities jsonb NOT NULL DEFAULT '{}'::jsonb, cert_fingerprint varchar(255) NOT NULL, last_seen_at timestamptz NULL, status varchar(32) NOT NULL DEFAULT 'offline', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE jobs ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, server_id uuid NULL REFERENCES servers(id) ON DELETE SET NULL, requested_by_user_id uuid NULL REFERENCES users(id) ON DELETE SET NULL, type varchar(64) NOT NULL, status varchar(32) NOT NULL DEFAULT 'queued', priority smallint NOT NULL DEFAULT 5, idempotency_key varchar(120) NOT NULL, payload jsonb NOT NULL DEFAULT '{}'::jsonb, result jsonb NULL, started_at timestamptz NULL, finished_at timestamptz NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (tenant_id, idempotency_key) ); CREATE TABLE job_steps ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), job_id uuid NOT NULL REFERENCES jobs(id) ON DELETE CASCADE, step_order integer NOT NULL, name varchar(80) NOT NULL, status varchar(32) NOT NULL DEFAULT 'pending', input jsonb NOT NULL DEFAULT '{}'::jsonb, output jsonb NULL, error text NULL, started_at timestamptz NULL, finished_at timestamptz NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (job_id, step_order) ); CREATE TABLE job_events ( id bigserial PRIMARY KEY, tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, job_id uuid NOT NULL REFERENCES jobs(id) ON DELETE CASCADE, event_type varchar(64) NOT NULL, source varchar(32) NOT NULL, payload jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX idx_job_events_tenant_job_created ON job_events (tenant_id, job_id, created_at DESC); CREATE TABLE audit_logs ( id bigserial PRIMARY KEY, tenant_id uuid NULL REFERENCES tenants(id) ON DELETE SET NULL, actor_user_id uuid NULL REFERENCES users(id) ON DELETE SET NULL, actor_type varchar(32) NOT NULL, action varchar(80) NOT NULL, resource_type varchar(64) NOT NULL, resource_id uuid NULL, request_id varchar(64) NULL, ip inet NULL, user_agent text NULL, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX idx_audit_logs_tenant_created ON audit_logs (tenant_id, created_at DESC); CREATE INDEX idx_scopes_tenant_action ON scopes (tenant_id, action); CREATE INDEX idx_servers_tenant_status ON servers (tenant_id, status); CREATE INDEX idx_jobs_tenant_status ON jobs (tenant_id, status);