-- Phase 2: Game server lifecycle and node enrollment -- New enums DO $$ BEGIN CREATE TYPE "ServerEdition" AS ENUM ('JAVA', 'BEDROCK'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN CREATE TYPE "SoftwareFamily" AS ENUM ('VANILLA', 'PAPER', 'PURPUR', 'FABRIC', 'FORGE', 'NEOFORGE', 'QUILT', 'BEDROCK_DEDICATED'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN CREATE TYPE "AllocationStatus" AS ENUM ('ACTIVE', 'RELEASED'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; -- Extend GameNodeStatus ALTER TYPE "GameNodeStatus" ADD VALUE IF NOT EXISTS 'UNREACHABLE'; -- Game servers: add columns ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "edition" "ServerEdition" NOT NULL DEFAULT 'JAVA'; ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "softwareFamily" "SoftwareFamily" NOT NULL DEFAULT 'VANILLA'; ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "minecraftVersion" TEXT NOT NULL DEFAULT '1.21.1'; ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "eulaAccepted" BOOLEAN NOT NULL DEFAULT false; ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "hostPort" INTEGER; ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "containerId" TEXT; ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "dataPath" TEXT; ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "ramMb" INTEGER NOT NULL DEFAULT 1024; -- Game nodes: enrollment + heartbeat ALTER TABLE "game_nodes" ADD COLUMN IF NOT EXISTS "lastHeartbeatAt" TIMESTAMPTZ(3); ALTER TABLE "game_nodes" ADD COLUMN IF NOT EXISTS "agentVersion" TEXT; ALTER TABLE "game_nodes" ADD COLUMN IF NOT EXISTS "enrollmentTokenHash" TEXT; ALTER TABLE "game_nodes" ADD COLUMN IF NOT EXISTS "enrolledAt" TIMESTAMPTZ(3); -- State transitions: audit fields ALTER TABLE "game_server_state_transitions" ADD COLUMN IF NOT EXISTS "correlationId" TEXT; ALTER TABLE "game_server_state_transitions" ADD COLUMN IF NOT EXISTS "actorId" TEXT; ALTER TABLE "game_server_state_transitions" ADD COLUMN IF NOT EXISTS "errorCode" TEXT; ALTER TABLE "game_server_state_transitions" ADD COLUMN IF NOT EXISTS "errorMessage" TEXT; -- Allocations CREATE TABLE IF NOT EXISTS "game_server_allocations" ( "id" TEXT NOT NULL, "serverId" TEXT NOT NULL, "nodeId" TEXT NOT NULL, "hostPort" INTEGER NOT NULL, "ramMbReserved" INTEGER NOT NULL, "status" "AllocationStatus" NOT NULL DEFAULT 'ACTIVE', "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMPTZ(3) NOT NULL, CONSTRAINT "game_server_allocations_pkey" PRIMARY KEY ("id") ); CREATE UNIQUE INDEX IF NOT EXISTS "game_server_allocations_serverId_key" ON "game_server_allocations"("serverId"); CREATE UNIQUE INDEX IF NOT EXISTS "game_server_allocations_nodeId_hostPort_key" ON "game_server_allocations"("nodeId", "hostPort"); CREATE INDEX IF NOT EXISTS "game_server_allocations_nodeId_idx" ON "game_server_allocations"("nodeId"); DO $$ BEGIN ALTER TABLE "game_server_allocations" ADD CONSTRAINT "game_server_allocations_serverId_fkey" FOREIGN KEY ("serverId") REFERENCES "game_servers"("id") ON DELETE CASCADE ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN ALTER TABLE "game_server_allocations" ADD CONSTRAINT "game_server_allocations_nodeId_fkey" FOREIGN KEY ("nodeId") REFERENCES "game_nodes"("id") ON DELETE RESTRICT ON UPDATE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$; -- Free plan seed INSERT INTO "plans" ("id", "name", "slug", "description", "isFree", "isActive", "maxRamMb", "maxCpuCores", "maxStorageMb", "createdAt", "updatedAt") SELECT gen_random_uuid()::text, 'Free', 'free', 'Free tier with limited resources', true, true, 2048, 1, 5120, NOW(), NOW() WHERE NOT EXISTS (SELECT 1 FROM "plans" WHERE "slug" = 'free');