AeThex-OS/supabase/migrations/003_gameforge_system.sql
MrPiglr b3c308b2c8 Add functional marketplace modules, bottom nav bar, root terminal, arcade games
- ModuleManager: Central tracking for installed marketplace modules
- DataAnalyzerWidget: Real-time CPU/RAM/Battery/Storage widget (unlocked by Data Analyzer module)
- BottomNavBar: Navigation bar for Projects/Chat/Marketplace/Settings
- RootShell: Real root command execution utility
- TerminalActivity: Full root shell with neofetch, sysinfo, real Linux commands
- Terminal Pro module: Adds aliases (ll, la, h), command history
- ArcadeActivity + SnakeGame: Pixel Arcade module unlocks retro games
- fade_in/fade_out animations for smooth transitions
2026-02-18 22:03:50 -07:00

337 lines
14 KiB
PL/PgSQL

-- Migration 003: GameForge Studio Management System
-- Complete project lifecycle tracking for the GameForge game development studio
-- Ported from aethex-forge
-- ============================================================================
-- GAMEFORGE PROJECTS
-- ============================================================================
CREATE TABLE IF NOT EXISTS gameforge_projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(300) NOT NULL,
description TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'planning' CHECK (status IN ('planning', 'in_development', 'qa', 'released', 'hiatus', 'cancelled')),
lead_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE SET NULL,
platform VARCHAR(50) NOT NULL CHECK (platform IN ('Unity', 'Unreal', 'Godot', 'Custom', 'WebGL')),
genre TEXT[] NOT NULL DEFAULT '{}',
target_release_date TIMESTAMP WITH TIME ZONE,
actual_release_date TIMESTAMP WITH TIME ZONE,
budget NUMERIC(12, 2),
current_spend NUMERIC(12, 2) NOT NULL DEFAULT 0,
team_size INTEGER DEFAULT 0,
repository_url VARCHAR(500),
documentation_url VARCHAR(500),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_gf_projects_status ON gameforge_projects(status);
CREATE INDEX IF NOT EXISTS idx_gf_projects_lead ON gameforge_projects(lead_id);
CREATE INDEX IF NOT EXISTS idx_gf_projects_platform ON gameforge_projects(platform);
CREATE INDEX IF NOT EXISTS idx_gf_projects_created ON gameforge_projects(created_at DESC);
-- ============================================================================
-- GAMEFORGE TEAM MEMBERS
-- ============================================================================
CREATE TABLE IF NOT EXISTS gameforge_team_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
role VARCHAR(50) NOT NULL CHECK (role IN ('engineer', 'designer', 'artist', 'producer', 'qa', 'sound_designer', 'writer', 'manager')),
position VARCHAR(200),
contract_type VARCHAR(50) NOT NULL DEFAULT 'employee' CHECK (contract_type IN ('employee', 'contractor', 'consultant', 'intern')),
hourly_rate NUMERIC(8, 2),
project_ids UUID[] NOT NULL DEFAULT '{}',
skills TEXT[] DEFAULT '{}',
bio TEXT,
joined_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
left_date TIMESTAMP WITH TIME ZONE,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id)
);
CREATE INDEX IF NOT EXISTS idx_gf_team_user ON gameforge_team_members(user_id);
CREATE INDEX IF NOT EXISTS idx_gf_team_role ON gameforge_team_members(role);
CREATE INDEX IF NOT EXISTS idx_gf_team_active ON gameforge_team_members(is_active);
-- ============================================================================
-- GAMEFORGE BUILDS
-- ============================================================================
CREATE TABLE IF NOT EXISTS gameforge_builds (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES gameforge_projects(id) ON DELETE CASCADE,
version VARCHAR(50) NOT NULL,
build_type VARCHAR(50) NOT NULL CHECK (build_type IN ('alpha', 'beta', 'release_candidate', 'final')),
release_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
download_url VARCHAR(1000),
changelog TEXT,
file_size BIGINT,
target_platforms TEXT[] NOT NULL DEFAULT '{}',
download_count INTEGER NOT NULL DEFAULT 0,
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(project_id, version)
);
CREATE INDEX IF NOT EXISTS idx_gf_builds_project ON gameforge_builds(project_id);
CREATE INDEX IF NOT EXISTS idx_gf_builds_release ON gameforge_builds(release_date DESC);
CREATE INDEX IF NOT EXISTS idx_gf_builds_type ON gameforge_builds(build_type);
-- ============================================================================
-- GAMEFORGE SPRINTS
-- ============================================================================
CREATE TABLE IF NOT EXISTS gameforge_sprints (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES gameforge_projects(id) ON DELETE CASCADE,
sprint_number INTEGER NOT NULL,
title VARCHAR(200) NOT NULL,
description TEXT,
phase VARCHAR(50) NOT NULL DEFAULT 'planning' CHECK (phase IN ('planning', 'active', 'completed', 'cancelled')),
status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'active', 'on_hold', 'completed')),
goal TEXT,
start_date TIMESTAMP WITH TIME ZONE,
end_date TIMESTAMP WITH TIME ZONE,
planned_velocity INTEGER,
actual_velocity INTEGER,
created_by UUID NOT NULL REFERENCES auth.users(id) ON DELETE SET NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(project_id, sprint_number)
);
CREATE INDEX IF NOT EXISTS idx_gf_sprints_project ON gameforge_sprints(project_id);
CREATE INDEX IF NOT EXISTS idx_gf_sprints_phase ON gameforge_sprints(phase);
CREATE INDEX IF NOT EXISTS idx_gf_sprints_status ON gameforge_sprints(status);
-- ============================================================================
-- GAMEFORGE SPRINT MEMBERS
-- ============================================================================
CREATE TABLE IF NOT EXISTS gameforge_sprint_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sprint_id UUID NOT NULL REFERENCES gameforge_sprints(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
role VARCHAR(50) NOT NULL DEFAULT 'contributor' CHECK (role IN ('lead', 'contributor', 'reviewer')),
joined_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(sprint_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_gf_sprint_members_sprint ON gameforge_sprint_members(sprint_id);
CREATE INDEX IF NOT EXISTS idx_gf_sprint_members_user ON gameforge_sprint_members(user_id);
-- ============================================================================
-- GAMEFORGE TASKS
-- ============================================================================
CREATE TABLE IF NOT EXISTS gameforge_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sprint_id UUID REFERENCES gameforge_sprints(id) ON DELETE SET NULL,
project_id UUID NOT NULL REFERENCES gameforge_projects(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
description TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'todo' CHECK (status IN ('todo', 'in_progress', 'in_review', 'done', 'blocked')),
priority VARCHAR(50) NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'critical')),
estimated_hours NUMERIC(6, 1),
actual_hours NUMERIC(6, 1),
assigned_to UUID REFERENCES auth.users(id) ON DELETE SET NULL,
created_by UUID NOT NULL REFERENCES auth.users(id) ON DELETE SET NULL,
due_date TIMESTAMP WITH TIME ZONE,
completed_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_gf_tasks_sprint ON gameforge_tasks(sprint_id);
CREATE INDEX IF NOT EXISTS idx_gf_tasks_project ON gameforge_tasks(project_id);
CREATE INDEX IF NOT EXISTS idx_gf_tasks_assigned ON gameforge_tasks(assigned_to);
CREATE INDEX IF NOT EXISTS idx_gf_tasks_status ON gameforge_tasks(status);
CREATE INDEX IF NOT EXISTS idx_gf_tasks_priority ON gameforge_tasks(priority);
-- ============================================================================
-- GAMEFORGE METRICS
-- ============================================================================
CREATE TABLE IF NOT EXISTS gameforge_metrics (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES gameforge_projects(id) ON DELETE CASCADE,
metric_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
metric_type VARCHAR(50) NOT NULL CHECK (metric_type IN ('monthly', 'sprint', 'milestone')),
velocity INTEGER,
hours_logged INTEGER,
team_size_avg INTEGER,
bugs_found INTEGER DEFAULT 0,
bugs_fixed INTEGER DEFAULT 0,
build_count INTEGER DEFAULT 0,
days_from_planned_to_release INTEGER,
on_schedule BOOLEAN,
budget_allocated NUMERIC(12, 2),
budget_spent NUMERIC(12, 2),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_gf_metrics_project ON gameforge_metrics(project_id);
CREATE INDEX IF NOT EXISTS idx_gf_metrics_date ON gameforge_metrics(metric_date DESC);
CREATE INDEX IF NOT EXISTS idx_gf_metrics_type ON gameforge_metrics(metric_type);
-- ============================================================================
-- ENABLE ROW LEVEL SECURITY
-- ============================================================================
ALTER TABLE gameforge_projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE gameforge_team_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE gameforge_builds ENABLE ROW LEVEL SECURITY;
ALTER TABLE gameforge_sprints ENABLE ROW LEVEL SECURITY;
ALTER TABLE gameforge_sprint_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE gameforge_tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE gameforge_metrics ENABLE ROW LEVEL SECURITY;
-- ============================================================================
-- RLS POLICIES
-- ============================================================================
-- Projects: Readable by all authenticated users
CREATE POLICY "gf_projects_select" ON gameforge_projects
FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "gf_projects_insert" ON gameforge_projects
FOR INSERT WITH CHECK (auth.uid() = lead_id);
CREATE POLICY "gf_projects_update" ON gameforge_projects
FOR UPDATE USING (auth.uid() = lead_id);
-- Team Members: Readable by all authenticated users
CREATE POLICY "gf_team_select" ON gameforge_team_members
FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "gf_team_insert" ON gameforge_team_members
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "gf_team_update" ON gameforge_team_members
FOR UPDATE USING (auth.uid() = user_id);
-- Builds: Readable by all, writable by project leads
CREATE POLICY "gf_builds_select" ON gameforge_builds
FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "gf_builds_insert" ON gameforge_builds
FOR INSERT WITH CHECK (
EXISTS(SELECT 1 FROM gameforge_projects WHERE id = project_id AND lead_id = auth.uid())
);
CREATE POLICY "gf_builds_update" ON gameforge_builds
FOR UPDATE USING (
EXISTS(SELECT 1 FROM gameforge_projects WHERE id = project_id AND lead_id = auth.uid())
);
-- Sprints: Readable by authenticated, writable by project leads
CREATE POLICY "gf_sprints_select" ON gameforge_sprints
FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "gf_sprints_insert" ON gameforge_sprints
FOR INSERT WITH CHECK (
EXISTS(SELECT 1 FROM gameforge_projects WHERE id = project_id AND lead_id = auth.uid())
);
CREATE POLICY "gf_sprints_update" ON gameforge_sprints
FOR UPDATE USING (
EXISTS(SELECT 1 FROM gameforge_projects WHERE id = project_id AND lead_id = auth.uid())
);
-- Sprint Members
CREATE POLICY "gf_sprint_members_select" ON gameforge_sprint_members
FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "gf_sprint_members_insert" ON gameforge_sprint_members
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "gf_sprint_members_delete" ON gameforge_sprint_members
FOR DELETE USING (auth.uid() = user_id);
-- Tasks: Readable by authenticated, updatable by assigned user or project lead
CREATE POLICY "gf_tasks_select" ON gameforge_tasks
FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "gf_tasks_insert" ON gameforge_tasks
FOR INSERT WITH CHECK (auth.uid() = created_by);
CREATE POLICY "gf_tasks_update" ON gameforge_tasks
FOR UPDATE USING (
auth.uid() = assigned_to
OR EXISTS(SELECT 1 FROM gameforge_projects WHERE id = project_id AND lead_id = auth.uid())
);
-- Metrics: Readable by all authenticated
CREATE POLICY "gf_metrics_select" ON gameforge_metrics
FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "gf_metrics_insert" ON gameforge_metrics
FOR INSERT WITH CHECK (
EXISTS(SELECT 1 FROM gameforge_projects WHERE id = project_id AND lead_id = auth.uid())
);
-- ============================================================================
-- TRIGGERS FOR updated_at
-- ============================================================================
CREATE OR REPLACE FUNCTION update_gameforge_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_gf_projects_updated ON gameforge_projects;
CREATE TRIGGER trigger_gf_projects_updated
BEFORE UPDATE ON gameforge_projects
FOR EACH ROW EXECUTE FUNCTION update_gameforge_updated_at();
DROP TRIGGER IF EXISTS trigger_gf_team_updated ON gameforge_team_members;
CREATE TRIGGER trigger_gf_team_updated
BEFORE UPDATE ON gameforge_team_members
FOR EACH ROW EXECUTE FUNCTION update_gameforge_updated_at();
DROP TRIGGER IF EXISTS trigger_gf_builds_updated ON gameforge_builds;
CREATE TRIGGER trigger_gf_builds_updated
BEFORE UPDATE ON gameforge_builds
FOR EACH ROW EXECUTE FUNCTION update_gameforge_updated_at();
DROP TRIGGER IF EXISTS trigger_gf_sprints_updated ON gameforge_sprints;
CREATE TRIGGER trigger_gf_sprints_updated
BEFORE UPDATE ON gameforge_sprints
FOR EACH ROW EXECUTE FUNCTION update_gameforge_updated_at();
DROP TRIGGER IF EXISTS trigger_gf_tasks_updated ON gameforge_tasks;
CREATE TRIGGER trigger_gf_tasks_updated
BEFORE UPDATE ON gameforge_tasks
FOR EACH ROW EXECUTE FUNCTION update_gameforge_updated_at();
-- ============================================================================
-- TRIGGER TO UPDATE PROJECT TEAM SIZE
-- ============================================================================
CREATE OR REPLACE FUNCTION update_project_team_size()
RETURNS TRIGGER AS $$
BEGIN
-- Update team_size for all affected projects
UPDATE gameforge_projects
SET team_size = (
SELECT COUNT(*) FROM gameforge_team_members
WHERE is_active = true AND project_ids @> ARRAY[gameforge_projects.id]
)
WHERE id = ANY(COALESCE(NEW.project_ids, OLD.project_ids));
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_update_team_size ON gameforge_team_members;
CREATE TRIGGER trigger_update_team_size
AFTER INSERT OR UPDATE OR DELETE ON gameforge_team_members
FOR EACH ROW EXECUTE FUNCTION update_project_team_size();