diff --git a/backend/migrations/001_initial_schema.sql b/backend/migrations/001_initial_schema.sql new file mode 100644 index 0000000..b3302fa --- /dev/null +++ b/backend/migrations/001_initial_schema.sql @@ -0,0 +1,599 @@ +-- Corrosion Platform — Initial Schema +-- All tables scoped by license_id for multi-tenant isolation + +-- Enable UUID extension +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +----------------------------------------------------------- +-- USERS & AUTH +----------------------------------------------------------- + +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + email VARCHAR(255) NOT NULL UNIQUE, + username VARCHAR(50) NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + totp_secret TEXT, + totp_enabled BOOLEAN NOT NULL DEFAULT false, + backup_codes TEXT[], + email_verified BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + last_login_at TIMESTAMPTZ +); + +CREATE INDEX idx_users_email ON users(email); + +----------------------------------------------------------- +-- LICENSES +----------------------------------------------------------- + +CREATE TABLE licenses ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_key VARCHAR(64) NOT NULL UNIQUE, + status VARCHAR(20) NOT NULL DEFAULT 'active' + CHECK (status IN ('active', 'suspended', 'expired', 'revoked')), + owner_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + server_name VARCHAR(100), + subdomain VARCHAR(63) UNIQUE, + custom_domain VARCHAR(255) UNIQUE, + modules_enabled TEXT[] DEFAULT '{}', + webstore_active BOOLEAN NOT NULL DEFAULT false, + webstore_subscription_id VARCHAR(100), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + expires_at TIMESTAMPTZ +); + +CREATE INDEX idx_licenses_key ON licenses(license_key); +CREATE INDEX idx_licenses_owner ON licenses(owner_user_id); +CREATE INDEX idx_licenses_subdomain ON licenses(subdomain); + +----------------------------------------------------------- +-- TEAM & RBAC +----------------------------------------------------------- + +CREATE TABLE roles ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID REFERENCES licenses(id) ON DELETE CASCADE, + role_name VARCHAR(50) NOT NULL, + is_system_default BOOLEAN NOT NULL DEFAULT false, + is_cloned_from UUID REFERENCES roles(id) ON DELETE SET NULL, + permissions JSONB NOT NULL DEFAULT '{}', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_roles_license ON roles(license_id); + +CREATE TABLE team_members ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + role_id UUID NOT NULL REFERENCES roles(id) ON DELETE RESTRICT, + invited_by UUID NOT NULL REFERENCES users(id), + accepted_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(license_id, user_id) +); + +CREATE INDEX idx_team_license ON team_members(license_id); +CREATE INDEX idx_team_user ON team_members(user_id); + +----------------------------------------------------------- +-- SERVER CONNECTION & CONFIG +----------------------------------------------------------- + +CREATE TABLE server_connections ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL UNIQUE REFERENCES licenses(id) ON DELETE CASCADE, + connection_type VARCHAR(20) NOT NULL + CHECK (connection_type IN ('amp', 'pterodactyl', 'bare_metal')), + panel_api_endpoint TEXT, + panel_api_key_encrypted TEXT, + panel_server_identifier VARCHAR(255), + companion_agent_token VARCHAR(128), + companion_last_seen TIMESTAMPTZ, + plugin_last_seen TIMESTAMPTZ, + server_ip VARCHAR(45), + server_port INTEGER, + game_port INTEGER, + connection_status VARCHAR(20) NOT NULL DEFAULT 'offline' + CHECK (connection_status IN ('connected', 'degraded', 'offline')), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_server_conn_license ON server_connections(license_id); + +CREATE TABLE server_config ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL UNIQUE REFERENCES licenses(id) ON DELETE CASCADE, + server_name VARCHAR(255) NOT NULL DEFAULT '', + max_players INTEGER, + world_size INTEGER, + current_seed INTEGER, + current_map_id UUID, + server_description TEXT, + server_url TEXT, + server_header_image TEXT, + tags TEXT[] DEFAULT '{}', + auto_restart_enabled BOOLEAN NOT NULL DEFAULT false, + auto_restart_cron VARCHAR(100), + auto_restart_timezone VARCHAR(50), + crash_recovery_enabled BOOLEAN NOT NULL DEFAULT true, + crash_recovery_max_attempts INTEGER NOT NULL DEFAULT 3, + crash_recovery_cooldown_minutes INTEGER NOT NULL DEFAULT 10, + force_wipe_eligible BOOLEAN NOT NULL DEFAULT true, + auto_update_on_force_wipe BOOLEAN NOT NULL DEFAULT true, + config_overrides JSONB DEFAULT '{}', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_server_config_license ON server_config(license_id); + +----------------------------------------------------------- +-- GAME ADMINS (in-game SteamID-based) +----------------------------------------------------------- + +CREATE TABLE game_admins ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + steam_id VARCHAR(20) NOT NULL, + display_name VARCHAR(100) NOT NULL DEFAULT '', + admin_level VARCHAR(20) NOT NULL DEFAULT 'admin' + CHECK (admin_level IN ('owner', 'admin', 'moderator')), + permissions JSONB DEFAULT '{}', + added_by UUID NOT NULL REFERENCES users(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(license_id, steam_id) +); + +CREATE INDEX idx_game_admins_license ON game_admins(license_id); + +----------------------------------------------------------- +-- WIPE PROFILES & SCHEDULES +----------------------------------------------------------- + +CREATE TABLE wipe_profiles ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + profile_name VARCHAR(100) NOT NULL, + description TEXT, + pre_wipe_config JSONB NOT NULL DEFAULT '{ + "enabled": true, + "backup_before_wipe": true, + "countdown_warnings": [30, 15, 5, 1], + "countdown_unit": "minutes", + "countdown_messages": {}, + "kick_players_before_wipe": true, + "kick_message": "Server is wiping. Be back shortly!", + "run_final_save": true, + "discord_pre_announce": true, + "pushbullet_notify": false, + "custom_commands_before": [] + }', + post_wipe_config JSONB NOT NULL DEFAULT '{ + "enabled": true, + "verify_server_started": true, + "verify_correct_map": true, + "verify_plugins_loaded": true, + "verify_player_slots_open": true, + "max_restart_attempts": 3, + "health_check_timeout_seconds": 120, + "discord_post_announce": true, + "pushbullet_notify": false, + "rollback_on_failure": true, + "post_wipe_commands": [] + }', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_wipe_profiles_license ON wipe_profiles(license_id); + +CREATE TABLE wipe_schedules ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + wipe_profile_id UUID NOT NULL REFERENCES wipe_profiles(id) ON DELETE CASCADE, + schedule_name VARCHAR(100) NOT NULL, + wipe_type VARCHAR(20) NOT NULL + CHECK (wipe_type IN ('map', 'blueprint', 'full')), + cron_expression VARCHAR(100) NOT NULL, + timezone VARCHAR(50) NOT NULL DEFAULT 'America/New_York', + wipe_blueprints BOOLEAN NOT NULL DEFAULT false, + is_active BOOLEAN NOT NULL DEFAULT true, + next_scheduled_run TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_wipe_schedules_license ON wipe_schedules(license_id); +CREATE INDEX idx_wipe_schedules_next_run ON wipe_schedules(next_scheduled_run) WHERE is_active = true; + +----------------------------------------------------------- +-- MAP LIBRARY & ROTATION +----------------------------------------------------------- + +CREATE TABLE map_library ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + filename VARCHAR(255) NOT NULL, + display_name VARCHAR(255) NOT NULL, + storage_path TEXT NOT NULL, + file_size_bytes BIGINT NOT NULL DEFAULT 0, + map_type VARCHAR(20) NOT NULL DEFAULT 'custom' + CHECK (map_type IN ('custom', 'procedural')), + seed INTEGER, + world_size INTEGER, + thumbnail_path TEXT, + checksum VARCHAR(64) NOT NULL, + uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_map_library_license ON map_library(license_id); + +-- FK for server_config.current_map_id (deferred because map_library created after server_config) +ALTER TABLE server_config + ADD CONSTRAINT fk_server_config_map + FOREIGN KEY (current_map_id) REFERENCES map_library(id) ON DELETE SET NULL; + +CREATE TABLE map_rotations ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + map_id UUID NOT NULL REFERENCES map_library(id) ON DELETE CASCADE, + rotation_order INTEGER NOT NULL DEFAULT 0, + is_active BOOLEAN NOT NULL DEFAULT true, + UNIQUE(license_id, rotation_order) +); + +CREATE INDEX idx_map_rotations_license ON map_rotations(license_id); + +----------------------------------------------------------- +-- PLUGIN REGISTRY +----------------------------------------------------------- + +CREATE TABLE plugin_registry ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + plugin_name VARCHAR(255) NOT NULL, + plugin_version VARCHAR(50), + source VARCHAR(20) NOT NULL DEFAULT 'manual' + CHECK (source IN ('umod', 'corrosion_module', 'manual')), + umod_slug VARCHAR(255), + is_installed BOOLEAN NOT NULL DEFAULT false, + is_loaded BOOLEAN NOT NULL DEFAULT false, + config_json JSONB, + data_path TEXT, + wipe_on_map BOOLEAN NOT NULL DEFAULT false, + wipe_on_bp BOOLEAN NOT NULL DEFAULT false, + wipe_on_full BOOLEAN NOT NULL DEFAULT false, + never_wipe BOOLEAN NOT NULL DEFAULT false, + installed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(license_id, plugin_name) +); + +CREATE INDEX idx_plugin_registry_license ON plugin_registry(license_id); + +----------------------------------------------------------- +-- WIPE HISTORY +----------------------------------------------------------- + +CREATE TABLE wipe_history ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + wipe_schedule_id UUID REFERENCES wipe_schedules(id) ON DELETE SET NULL, + wipe_profile_id UUID NOT NULL REFERENCES wipe_profiles(id), + wipe_type VARCHAR(20) NOT NULL, + trigger_type VARCHAR(20) NOT NULL + CHECK (trigger_type IN ('scheduled', 'manual', 'force_wipe')), + status VARCHAR(20) NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending', 'pre_wipe', 'wiping', 'post_wipe', 'success', 'failed', 'rolled_back')), + started_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ, + map_used VARCHAR(255), + plugins_wiped TEXT[], + plugins_preserved TEXT[], + backup_reference TEXT, + error_message TEXT, + execution_log JSONB DEFAULT '[]', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_wipe_history_license ON wipe_history(license_id); +CREATE INDEX idx_wipe_history_created ON wipe_history(created_at DESC); + +----------------------------------------------------------- +-- SCHEDULED TASKS (restarts, announcements, etc.) +----------------------------------------------------------- + +CREATE TABLE scheduled_tasks ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + task_type VARCHAR(30) NOT NULL + CHECK (task_type IN ('restart', 'announcement', 'command', 'plugin_reload')), + task_name VARCHAR(100) NOT NULL, + cron_expression VARCHAR(100) NOT NULL, + timezone VARCHAR(50) NOT NULL DEFAULT 'America/New_York', + task_config JSONB NOT NULL DEFAULT '{}', + is_active BOOLEAN NOT NULL DEFAULT true, + next_run TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_scheduled_tasks_license ON scheduled_tasks(license_id); + +----------------------------------------------------------- +-- NOTIFICATIONS +----------------------------------------------------------- + +CREATE TABLE notifications_config ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL UNIQUE REFERENCES licenses(id) ON DELETE CASCADE, + discord_webhook_url TEXT, + discord_enabled BOOLEAN NOT NULL DEFAULT false, + pushbullet_api_key TEXT, + pushbullet_enabled BOOLEAN NOT NULL DEFAULT false, + email_alerts_enabled BOOLEAN NOT NULL DEFAULT true, + notify_wipe_start BOOLEAN NOT NULL DEFAULT true, + notify_wipe_complete BOOLEAN NOT NULL DEFAULT true, + notify_wipe_failed BOOLEAN NOT NULL DEFAULT true, + notify_server_crash BOOLEAN NOT NULL DEFAULT true, + notify_server_offline BOOLEAN NOT NULL DEFAULT true, + notify_store_purchase BOOLEAN NOT NULL DEFAULT true, + notify_player_report BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +----------------------------------------------------------- +-- CHAT LOGS & PLAYER ACTIONS +----------------------------------------------------------- + +CREATE TABLE chat_logs ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + steam_id VARCHAR(20) NOT NULL, + player_name VARCHAR(100) NOT NULL, + channel VARCHAR(20) NOT NULL DEFAULT 'global' + CHECK (channel IN ('global', 'team', 'server')), + message TEXT NOT NULL, + flagged BOOLEAN NOT NULL DEFAULT false, + flagged_by UUID REFERENCES users(id), + flag_reason TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_chat_logs_license ON chat_logs(license_id); +CREATE INDEX idx_chat_logs_created ON chat_logs(created_at DESC); +CREATE INDEX idx_chat_logs_steam ON chat_logs(license_id, steam_id); + +CREATE TABLE player_actions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + steam_id VARCHAR(20) NOT NULL, + player_name VARCHAR(100) NOT NULL, + action_type VARCHAR(20) NOT NULL + CHECK (action_type IN ('kick', 'ban', 'unban', 'warn', 'note')), + reason TEXT, + duration_minutes INTEGER, + performed_by UUID NOT NULL REFERENCES users(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_player_actions_license ON player_actions(license_id); +CREATE INDEX idx_player_actions_steam ON player_actions(license_id, steam_id); + +----------------------------------------------------------- +-- SERVER STATS (time-series) +----------------------------------------------------------- + +CREATE TABLE server_stats ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + player_count INTEGER NOT NULL DEFAULT 0, + max_players INTEGER NOT NULL DEFAULT 0, + fps DOUBLE PRECISION NOT NULL DEFAULT 0, + entity_count INTEGER NOT NULL DEFAULT 0, + uptime_seconds INTEGER NOT NULL DEFAULT 0, + memory_usage_mb INTEGER NOT NULL DEFAULT 0, + recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_server_stats_license_time ON server_stats(license_id, recorded_at DESC); + +CREATE TABLE server_stats_hourly ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + hour TIMESTAMPTZ NOT NULL, + avg_players DOUBLE PRECISION NOT NULL DEFAULT 0, + max_players INTEGER NOT NULL DEFAULT 0, + avg_fps DOUBLE PRECISION NOT NULL DEFAULT 0, + min_fps DOUBLE PRECISION NOT NULL DEFAULT 0, + avg_entities INTEGER NOT NULL DEFAULT 0, + uptime_percentage DOUBLE PRECISION NOT NULL DEFAULT 0, + UNIQUE(license_id, hour) +); + +CREATE INDEX idx_stats_hourly_license ON server_stats_hourly(license_id, hour DESC); + +----------------------------------------------------------- +-- PUBLIC SITE CONFIG +----------------------------------------------------------- + +CREATE TABLE public_site_config ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL UNIQUE REFERENCES licenses(id) ON DELETE CASCADE, + site_enabled BOOLEAN NOT NULL DEFAULT true, + show_on_status_page BOOLEAN NOT NULL DEFAULT false, + steam_connect_url VARCHAR(255), + motd TEXT, + public_mods TEXT[] DEFAULT '{}', + header_image_url TEXT, + theme_color VARCHAR(7) DEFAULT '#ef4444', + custom_css TEXT, + discord_invite_url TEXT, + show_player_count BOOLEAN NOT NULL DEFAULT true, + show_wipe_schedule BOOLEAN NOT NULL DEFAULT true, + show_wipe_countdown BOOLEAN NOT NULL DEFAULT true, + show_mod_list BOOLEAN NOT NULL DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +----------------------------------------------------------- +-- WEBSTORE +----------------------------------------------------------- + +CREATE TABLE webstore_config ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL UNIQUE REFERENCES licenses(id) ON DELETE CASCADE, + is_active BOOLEAN NOT NULL DEFAULT false, + paypal_client_id TEXT, + paypal_secret TEXT, + paypal_mode VARCHAR(10) NOT NULL DEFAULT 'sandbox' + CHECK (paypal_mode IN ('sandbox', 'live')), + store_name VARCHAR(100), + store_description TEXT, + currency VARCHAR(3) NOT NULL DEFAULT 'USD', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE webstore_categories ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + category_name VARCHAR(100) NOT NULL, + display_order INTEGER NOT NULL DEFAULT 0, + is_active BOOLEAN NOT NULL DEFAULT true +); + +CREATE INDEX idx_webstore_cats_license ON webstore_categories(license_id); + +CREATE TABLE webstore_items ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + category_id UUID NOT NULL REFERENCES webstore_categories(id) ON DELETE CASCADE, + item_name VARCHAR(100) NOT NULL, + description TEXT, + price DECIMAL(10,2) NOT NULL, + image_url TEXT, + item_type VARCHAR(20) NOT NULL DEFAULT 'kit' + CHECK (item_type IN ('kit', 'rank', 'currency', 'custom_command')), + delivery_config JSONB NOT NULL DEFAULT '{"commands": []}', + is_active BOOLEAN NOT NULL DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_webstore_items_license ON webstore_items(license_id); +CREATE INDEX idx_webstore_items_category ON webstore_items(category_id); + +CREATE TABLE webstore_transactions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + item_id UUID NOT NULL REFERENCES webstore_items(id), + buyer_steam_id VARCHAR(20) NOT NULL, + buyer_name VARCHAR(100), + amount DECIMAL(10,2) NOT NULL, + currency VARCHAR(3) NOT NULL DEFAULT 'USD', + paypal_transaction_id VARCHAR(100), + status VARCHAR(20) NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending', 'paid', 'delivered', 'failed', 'refunded')), + delivered_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_webstore_txn_license ON webstore_transactions(license_id); +CREATE INDEX idx_webstore_txn_status ON webstore_transactions(license_id, status); + +----------------------------------------------------------- +-- MIGRATION EXPORTS +----------------------------------------------------------- + +CREATE TABLE migration_exports ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + export_type VARCHAR(20) NOT NULL DEFAULT 'full' + CHECK (export_type IN ('full', 'config_only', 'store_only')), + storage_path TEXT NOT NULL, + file_size_bytes BIGINT NOT NULL DEFAULT 0, + created_by UUID NOT NULL REFERENCES users(id), + expires_at TIMESTAMPTZ NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +----------------------------------------------------------- +-- PLATFORM CHANGELOG +----------------------------------------------------------- + +CREATE TABLE platform_changelog ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + version VARCHAR(20) NOT NULL, + title VARCHAR(200) NOT NULL, + body TEXT NOT NULL, + category VARCHAR(20) NOT NULL DEFAULT 'feature' + CHECK (category IN ('feature', 'bugfix', 'module', 'security')), + published_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +----------------------------------------------------------- +-- MODULE STORE +----------------------------------------------------------- + +CREATE TABLE module_store ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + module_slug VARCHAR(50) NOT NULL UNIQUE, + module_name VARCHAR(100) NOT NULL, + description TEXT, + long_description TEXT, + price DECIMAL(10,2) NOT NULL DEFAULT 0, + price_type VARCHAR(20) NOT NULL DEFAULT 'one_time' + CHECK (price_type IN ('one_time', 'monthly')), + monthly_price DECIMAL(10,2), + version VARCHAR(20) NOT NULL DEFAULT '1.0.0', + download_path TEXT, + thumbnail_url TEXT, + screenshots TEXT[] DEFAULT '{}', + category VARCHAR(50), + is_active BOOLEAN NOT NULL DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +----------------------------------------------------------- +-- DEFAULT SYSTEM ROLES +----------------------------------------------------------- + +INSERT INTO roles (id, license_id, role_name, is_system_default, permissions) VALUES +(uuid_generate_v4(), NULL, 'Owner', true, '{ + "server.*": true, + "wipe.*": true, + "plugins.*": true, + "players.*": true, + "team.*": true, + "billing.*": true, + "store.*": true, + "settings.*": true, + "public_site.*": true +}'), +(uuid_generate_v4(), NULL, 'Head Admin', true, '{ + "server.*": true, + "wipe.*": true, + "plugins.*": true, + "players.*": true, + "team.view": true, + "team.invite": true, + "store.*": true, + "settings.server": true, + "public_site.*": true +}'), +(uuid_generate_v4(), NULL, 'Moderator', true, '{ + "server.view": true, + "server.console": true, + "players.kick": true, + "players.ban": true, + "players.chat_log": true, + "wipe.view": true, + "store.view": true +}'), +(uuid_generate_v4(), NULL, 'Viewer', true, '{ + "server.view": true, + "players.view": true, + "wipe.view": true, + "store.view": true +}');