-- 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 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, is_public BOOLEAN DEFAULT TRUE, is_ai_generated BOOLEAN DEFAULT FALSE, ai_provider TEXT, -- anthropic, openai, ollama, null 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, 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', -- open, source_locked, commercial 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() ); -- ════════════════════════════════════════════════════════════ -- 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, -- null for anonymous session_id TEXT, -- anonymous session token shader_id UUID REFERENCES shaders(id) ON DELETE CASCADE, event_type TEXT NOT NULL, -- dwell, replay, share, generate_similar 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), -- embedded for similarity grouping style_hints JSONB, -- { chaos_level, color_temp, etc } tip_amount_cents INTEGER DEFAULT 0, status TEXT DEFAULT 'open', -- open, in_progress, fulfilled, expired heat_score FLOAT DEFAULT 1, -- updated as similar desires accumulate fulfilled_by_shader UUID REFERENCES shaders(id) ON DELETE SET NULL, fulfilled_at TIMESTAMPTZ, expires_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); -- Similar desire grouping (many-to-many) 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', -- held, released, refunded 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, -- 10% net_amount_cents INTEGER, -- 90% stripe_transfer_id TEXT, status TEXT DEFAULT 'pending', -- pending, processing, completed, failed 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, -- bcrypt hash of the actual key key_prefix TEXT NOT NULL, -- first 8 chars for display (ff_key_XXXXXXXX) name TEXT, -- user-given label trust_tier TEXT DEFAULT 'probation', -- probation, trusted, premium 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, -- platform cost for credit-based generations 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, -- personal, commercial 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; CREATE INDEX idx_shaders_created ON shaders(created_at DESC) WHERE is_public = TRUE; CREATE INDEX idx_shaders_tags ON shaders USING GIN(tags); CREATE INDEX idx_shaders_render_status ON shaders(render_status) WHERE render_status != 'ready'; -- Recommendation (pgvector ANN — ivfflat, will rebuild after data exists) -- NOTE: ivfflat indexes require data in the table to build properly. -- Run these AFTER seeding initial data: -- CREATE INDEX idx_shaders_style_vector ON shaders -- USING ivfflat (style_vector vector_cosine_ops) WITH (lists = 100); -- CREATE INDEX idx_users_taste_vector ON users -- USING ivfflat (taste_vector vector_cosine_ops) WITH (lists = 50); -- CREATE INDEX idx_desires_embedding ON desires -- USING ivfflat (prompt_embedding vector_cosine_ops) WITH (lists = 50); -- For now, use HNSW (works on empty tables, better perf at small scale) 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);