scaffold: Initial database migration — 24-table multi-tenant schema
Full PostgreSQL schema with license_id tenant scoping on every table, 4 default RBAC roles (Owner, Head Admin, Moderator, Viewer), JSONB configs for wipe profiles, webstore, and notification channels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
599
backend/migrations/001_initial_schema.sql
Normal file
599
backend/migrations/001_initial_schema.sql
Normal file
@@ -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
|
||||
}');
|
||||
Reference in New Issue
Block a user