CREATE TABLE users ( id UUID PRIMARY KEY, email TEXT NOT NULL UNIQUE, display_name TEXT, password_hash TEXT, disabled BOOL NOT NULL DEFAULT false, super_admin BOOL NOT NULL DEFAULT false, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL ); CREATE TABLE roles ( id UUID PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT, created_at TIMESTAMPTZ NOT NULL ); CREATE TABLE role_permissions ( role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE, permission TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL, PRIMARY KEY (role_id, permission) ); CREATE TABLE memberships ( id UUID PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, scope_type TEXT NOT NULL, scope_id TEXT NOT NULL, role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL, UNIQUE (user_id, scope_type, scope_id) ); CREATE TABLE sessions ( id UUID PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL, last_seen_at TIMESTAMPTZ NOT NULL, user_agent TEXT, ip TEXT ); CREATE INDEX sessions_user_id_idx ON sessions (user_id); CREATE INDEX sessions_expires_at_idx ON sessions (expires_at); CREATE TABLE control_planes ( id UUID PRIMARY KEY, name TEXT NOT NULL UNIQUE, base_url TEXT NOT NULL, admin_token TEXT, region TEXT, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL ); CREATE TABLE networks ( id UUID PRIMARY KEY, control_plane_id UUID NOT NULL REFERENCES control_planes(id) ON DELETE CASCADE, network_id TEXT NOT NULL, name TEXT NOT NULL, dns_domain TEXT, overlay_v4 TEXT, overlay_v6 TEXT, requires_approval BOOL NOT NULL DEFAULT false, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, UNIQUE (control_plane_id, network_id) ); CREATE INDEX networks_control_plane_idx ON networks (control_plane_id); CREATE TABLE audit_log ( id UUID PRIMARY KEY, actor_user_id UUID REFERENCES users(id) ON DELETE SET NULL, action TEXT NOT NULL, target_type TEXT, target_id TEXT, metadata JSONB, created_at TIMESTAMPTZ NOT NULL ); CREATE INDEX audit_log_created_at_idx ON audit_log (created_at); CREATE TABLE user_providers ( id UUID PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, provider TEXT NOT NULL, subject TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL, UNIQUE (provider, subject), UNIQUE (user_id, provider) ); CREATE TABLE oidc_states ( id UUID PRIMARY KEY, provider_id TEXT NOT NULL, state TEXT NOT NULL, nonce TEXT NOT NULL, verifier TEXT NOT NULL, redirect TEXT, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL, UNIQUE (provider_id, state) ); CREATE INDEX oidc_states_expires_at_idx ON oidc_states (expires_at); INSERT INTO roles (id, name, description, created_at) VALUES ('00000000-0000-0000-0000-000000000001', 'owner', 'Full access', now()), ('00000000-0000-0000-0000-000000000002', 'admin', 'Network admin access', now()), ('00000000-0000-0000-0000-000000000003', 'viewer', 'Read-only access', now()), ('00000000-0000-0000-0000-000000000004', 'member', 'Authenticated user without console access', now()), ('00000000-0000-0000-0000-000000000005', 'joiner', 'Can mint user-bound join tokens (typically network-scoped)', now()); INSERT INTO role_permissions (role_id, permission, created_at) VALUES ('00000000-0000-0000-0000-000000000001', 'control_planes:read', now()), ('00000000-0000-0000-0000-000000000001', 'control_planes:write', now()), ('00000000-0000-0000-0000-000000000001', 'networks:read', now()), ('00000000-0000-0000-0000-000000000001', 'networks:write', now()), ('00000000-0000-0000-0000-000000000001', 'nodes:read', now()), ('00000000-0000-0000-0000-000000000001', 'nodes:write', now()), ('00000000-0000-0000-0000-000000000001', 'tokens:write', now()), ('00000000-0000-0000-0000-000000000001', 'join_tokens:create', now()), ('00000000-0000-0000-0000-000000000001', 'acl:read', now()), ('00000000-0000-0000-0000-000000000001', 'acl:write', now()), ('00000000-0000-0000-0000-000000000001', 'key_policy:read', now()), ('00000000-0000-0000-0000-000000000001', 'key_policy:write', now()), ('00000000-0000-0000-0000-000000000001', 'audit:read', now()), ('00000000-0000-0000-0000-000000000001', 'users:read', now()), ('00000000-0000-0000-0000-000000000001', 'users:write', now()), ('00000000-0000-0000-0000-000000000001', 'roles:read', now()), ('00000000-0000-0000-0000-000000000001', 'roles:write', now()), ('00000000-0000-0000-0000-000000000001', 'console:access', now()), ('00000000-0000-0000-0000-000000000002', 'control_planes:read', now()), ('00000000-0000-0000-0000-000000000002', 'networks:read', now()), ('00000000-0000-0000-0000-000000000002', 'networks:write', now()), ('00000000-0000-0000-0000-000000000002', 'nodes:read', now()), ('00000000-0000-0000-0000-000000000002', 'nodes:write', now()), ('00000000-0000-0000-0000-000000000002', 'tokens:write', now()), ('00000000-0000-0000-0000-000000000002', 'join_tokens:create', now()), ('00000000-0000-0000-0000-000000000002', 'acl:read', now()), ('00000000-0000-0000-0000-000000000002', 'acl:write', now()), ('00000000-0000-0000-0000-000000000002', 'key_policy:read', now()), ('00000000-0000-0000-0000-000000000002', 'key_policy:write', now()), ('00000000-0000-0000-0000-000000000002', 'audit:read', now()), ('00000000-0000-0000-0000-000000000002', 'users:read', now()), ('00000000-0000-0000-0000-000000000002', 'roles:read', now()), ('00000000-0000-0000-0000-000000000002', 'console:access', now()), ('00000000-0000-0000-0000-000000000003', 'control_planes:read', now()), ('00000000-0000-0000-0000-000000000003', 'networks:read', now()), ('00000000-0000-0000-0000-000000000003', 'nodes:read', now()), ('00000000-0000-0000-0000-000000000003', 'audit:read', now()), ('00000000-0000-0000-0000-000000000003', 'roles:read', now()), ('00000000-0000-0000-0000-000000000003', 'console:access', now()), ('00000000-0000-0000-0000-000000000005', 'join_tokens:create', now());