-- Fractafrag Database Bootstrap -- Runs on first container start via docker-entrypoint-initdb.d -- Enable required extensions CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "vector"; CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- for text search -- ════════════════════════════════════════════════════════════ -- USERS -- ════════════════════════════════════════════════════════════ CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), username TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', -- user, moderator, admin trust_tier TEXT NOT NULL DEFAULT 'standard', -- standard, creator, trusted_api is_system BOOLEAN NOT NULL DEFAULT FALSE, -- platform system account (fractafrag) stripe_customer_id TEXT, subscription_tier TEXT DEFAULT 'free', -- free, pro, studio ai_credits_remaining INTEGER DEFAULT 0, taste_vector vector(512), -- pgvector: learned taste embedding -- Creator economy stubs (Section 11f — deferred, schema only) is_verified_creator BOOLEAN DEFAULT FALSE, verified_creator_at TIMESTAMPTZ, stripe_connect_account_id TEXT, -- Timestamps created_at TIMESTAMPTZ DEFAULT NOW(), last_active_at TIMESTAMPTZ ); -- ════════════════════════════════════════════════════════════ -- SHADERS -- ════════════════════════════════════════════════════════════ CREATE TABLE shaders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), author_id UUID REFERENCES users(id) ON DELETE SET NULL, title TEXT NOT NULL, description TEXT, glsl_code TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'published', -- draft, published, archived is_public BOOLEAN DEFAULT TRUE, is_ai_generated BOOLEAN DEFAULT FALSE, is_system BOOLEAN DEFAULT FALSE, -- generated by fractafrag platform ai_provider TEXT, -- anthropic, openai, ollama, null system_label TEXT, -- e.g. 'fractafrag-curated', 'fractafrag-generated' thumbnail_url TEXT, preview_url TEXT, render_status TEXT DEFAULT 'pending', -- pending, rendering, ready, failed style_vector vector(512), -- pgvector: visual style embedding style_metadata JSONB, -- { chaos_level, color_temp, motion_type, ... } tags TEXT[], shader_type TEXT DEFAULT '2d', -- 2d, 3d, audio-reactive forked_from UUID REFERENCES shaders(id) ON DELETE SET NULL, current_version INTEGER NOT NULL DEFAULT 1, -- current version number view_count INTEGER DEFAULT 0, score FLOAT DEFAULT 0, -- cached hot score for feed ranking -- Creator economy stubs (Section 11f) access_tier TEXT DEFAULT 'open', source_unlock_price_cents INTEGER, commercial_license_price_cents INTEGER, verified_creator_shader BOOLEAN DEFAULT FALSE, -- Timestamps created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ════════════════════════════════════════════════════════════ -- SHADER VERSIONS — immutable snapshots of each edit -- ════════════════════════════════════════════════════════════ CREATE TABLE shader_versions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), shader_id UUID NOT NULL REFERENCES shaders(id) ON DELETE CASCADE, version_number INTEGER NOT NULL, glsl_code TEXT NOT NULL, title TEXT NOT NULL, description TEXT, tags TEXT[], style_metadata JSONB, change_note TEXT, -- optional: "fixed the color bleeding", "added mouse interaction" thumbnail_url TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE (shader_id, version_number) ); -- ════════════════════════════════════════════════════════════ -- VOTES -- ════════════════════════════════════════════════════════════ CREATE TABLE votes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, shader_id UUID REFERENCES shaders(id) ON DELETE CASCADE, value SMALLINT NOT NULL CHECK (value IN (-1, 1)), created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE (user_id, shader_id) ); -- ════════════════════════════════════════════════════════════ -- ENGAGEMENT EVENTS (dwell time, replays, shares) -- ════════════════════════════════════════════════════════════ CREATE TABLE engagement_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE SET NULL, session_id TEXT, shader_id UUID REFERENCES shaders(id) ON DELETE CASCADE, event_type TEXT NOT NULL, dwell_secs FLOAT, metadata JSONB, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ════════════════════════════════════════════════════════════ -- DESIRES / BOUNTIES -- ════════════════════════════════════════════════════════════ CREATE TABLE desires ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), author_id UUID REFERENCES users(id) ON DELETE SET NULL, prompt_text TEXT NOT NULL, prompt_embedding vector(512), style_hints JSONB, tip_amount_cents INTEGER DEFAULT 0, status TEXT DEFAULT 'open', heat_score FLOAT DEFAULT 1, fulfilled_by_shader UUID REFERENCES shaders(id) ON DELETE SET NULL, fulfilled_at TIMESTAMPTZ, expires_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE desire_clusters ( cluster_id UUID, desire_id UUID REFERENCES desires(id) ON DELETE CASCADE, similarity FLOAT, PRIMARY KEY (cluster_id, desire_id) ); -- ════════════════════════════════════════════════════════════ -- BOUNTY TIPS -- ════════════════════════════════════════════════════════════ CREATE TABLE bounty_tips ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), desire_id UUID REFERENCES desires(id) ON DELETE CASCADE, tipper_id UUID REFERENCES users(id) ON DELETE SET NULL, amount_cents INTEGER NOT NULL, stripe_payment_intent_id TEXT, status TEXT DEFAULT 'held', created_at TIMESTAMPTZ DEFAULT NOW() ); -- ════════════════════════════════════════════════════════════ -- CREATOR PAYOUTS -- ════════════════════════════════════════════════════════════ CREATE TABLE creator_payouts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), creator_id UUID REFERENCES users(id) ON DELETE SET NULL, desire_id UUID REFERENCES desires(id) ON DELETE SET NULL, gross_amount_cents INTEGER, platform_fee_cents INTEGER, net_amount_cents INTEGER, stripe_transfer_id TEXT, status TEXT DEFAULT 'pending', created_at TIMESTAMPTZ DEFAULT NOW() ); -- ════════════════════════════════════════════════════════════ -- API KEYS (for MCP clients) -- ════════════════════════════════════════════════════════════ CREATE TABLE api_keys ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, key_hash TEXT UNIQUE NOT NULL, key_prefix TEXT NOT NULL, name TEXT, trust_tier TEXT DEFAULT 'probation', submissions_approved INTEGER DEFAULT 0, rate_limit_per_hour INTEGER DEFAULT 10, last_used_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), revoked_at TIMESTAMPTZ ); -- ════════════════════════════════════════════════════════════ -- AI GENERATION LOG -- ════════════════════════════════════════════════════════════ CREATE TABLE generation_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE SET NULL, shader_id UUID REFERENCES shaders(id) ON DELETE SET NULL, provider TEXT NOT NULL, prompt_text TEXT, tokens_used INTEGER, cost_cents INTEGER, success BOOLEAN, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ════════════════════════════════════════════════════════════ -- COMMENTS (schema in place, feature deferred to post-M5) -- ════════════════════════════════════════════════════════════ CREATE TABLE comments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), shader_id UUID REFERENCES shaders(id) ON DELETE CASCADE, author_id UUID REFERENCES users(id) ON DELETE SET NULL, body TEXT NOT NULL, parent_id UUID REFERENCES comments(id) ON DELETE CASCADE, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ════════════════════════════════════════════════════════════ -- CREATOR ECONOMY STUBS (Section 11f — dormant until activated) -- ════════════════════════════════════════════════════════════ CREATE TABLE source_unlocks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), shader_id UUID REFERENCES shaders(id) ON DELETE CASCADE, buyer_id UUID REFERENCES users(id) ON DELETE SET NULL, license_type TEXT NOT NULL, amount_cents INTEGER NOT NULL, platform_fee_cents INTEGER NOT NULL, stripe_payment_intent_id TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE creator_engagement_snapshots ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), creator_id UUID REFERENCES users(id) ON DELETE CASCADE, month DATE NOT NULL, total_score FLOAT NOT NULL, pool_share FLOAT, payout_cents INTEGER, paid_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ════════════════════════════════════════════════════════════ -- INDEXES -- ════════════════════════════════════════════════════════════ -- Feed performance CREATE INDEX idx_shaders_score ON shaders(score DESC) WHERE is_public = TRUE AND status = 'published'; CREATE INDEX idx_shaders_created ON shaders(created_at DESC) WHERE is_public = TRUE AND status = 'published'; CREATE INDEX idx_shaders_tags ON shaders USING GIN(tags); CREATE INDEX idx_shaders_render_status ON shaders(render_status) WHERE render_status != 'ready'; CREATE INDEX idx_shaders_status ON shaders(status); CREATE INDEX idx_shaders_author_status ON shaders(author_id, status, updated_at DESC); CREATE INDEX idx_shaders_system ON shaders(is_system) WHERE is_system = TRUE; -- Versioning CREATE INDEX idx_shader_versions_shader ON shader_versions(shader_id, version_number DESC); -- Recommendation (pgvector HNSW — works on empty tables) CREATE INDEX idx_shaders_style_vector ON shaders USING hnsw (style_vector vector_cosine_ops) WITH (m = 16, ef_construction = 64); CREATE INDEX idx_users_taste_vector ON users USING hnsw (taste_vector vector_cosine_ops) WITH (m = 16, ef_construction = 64); CREATE INDEX idx_desires_embedding ON desires USING hnsw (prompt_embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); -- Engagement CREATE INDEX idx_engagement_user ON engagement_events(user_id, created_at DESC); CREATE INDEX idx_engagement_shader ON engagement_events(shader_id, event_type); CREATE INDEX idx_engagement_session ON engagement_events(session_id, created_at DESC) WHERE session_id IS NOT NULL; -- Desires / bounties CREATE INDEX idx_desires_status ON desires(status, heat_score DESC); CREATE INDEX idx_desires_author ON desires(author_id); -- API keys CREATE INDEX idx_api_keys_user ON api_keys(user_id) WHERE revoked_at IS NULL; CREATE INDEX idx_api_keys_prefix ON api_keys(key_prefix); -- Votes CREATE INDEX idx_votes_shader ON votes(shader_id); CREATE INDEX idx_votes_user ON votes(user_id); -- Comments CREATE INDEX idx_comments_shader ON comments(shader_id, created_at); CREATE INDEX idx_comments_parent ON comments(parent_id); -- Text search CREATE INDEX idx_shaders_title_trgm ON shaders USING GIN(title gin_trgm_ops); CREATE INDEX idx_desires_prompt_trgm ON desires USING GIN(prompt_text gin_trgm_ops); -- ════════════════════════════════════════════════════════════ -- SYSTEM ACCOUNT: The "fractafrag" platform user -- All system-generated/curated shaders are authored by this account -- ════════════════════════════════════════════════════════════ INSERT INTO users (id, username, email, password_hash, role, trust_tier, is_system, subscription_tier, is_verified_creator) VALUES ( '00000000-0000-0000-0000-000000000001', 'fractafrag', 'system@fractafrag.local', '$2b$12$000000000000000000000000000000000000000000000000000000', -- not a valid login 'admin', 'trusted_api', TRUE, 'studio', TRUE );