From 8c5e761f92450eb98d571f0b2d82d5e18e5abb03 Mon Sep 17 00:00:00 2001 From: trexfr-ops Date: Thu, 20 Aug 2026 14:32:28 +0000 Subject: [PATCH 1/2] fix(db): move clan_members table definition before data migrations (closes #82) --- api/schema.sql | 66 ++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/api/schema.sql b/api/schema.sql index c5103206..df95100e 100644 --- a/api/schema.sql +++ b/api/schema.sql @@ -1,3 +1,5 @@ +\set ON_ERROR_STOP on + CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE IF NOT EXISTS accounts ( @@ -91,6 +93,39 @@ CREATE TABLE IF NOT EXISTS clans ( updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); +ALTER TABLE characters + ADD COLUMN IF NOT EXISTS clan_id UUID REFERENCES clans(id) ON DELETE SET NULL; + +CREATE TABLE IF NOT EXISTS clan_members ( + clan_id UUID NOT NULL REFERENCES clans(id) ON DELETE CASCADE, + character_id UUID NOT NULL REFERENCES characters(id) ON DELETE CASCADE, + role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('leader', 'co_leader', 'member')), + joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (clan_id, character_id), + UNIQUE (character_id) +); + +ALTER TABLE clan_members + DROP CONSTRAINT IF EXISTS clan_members_role_check; + +ALTER TABLE clan_members + ADD CONSTRAINT clan_members_role_check + CHECK (role IN ('leader', 'co_leader', 'member')); + +CREATE TABLE IF NOT EXISTS clan_requests ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + clan_id UUID NOT NULL REFERENCES clans(id) ON DELETE CASCADE, + character_id UUID NOT NULL REFERENCES characters(id) ON DELETE CASCADE, + message TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE (character_id) +); + +CREATE INDEX IF NOT EXISTS idx_clans_leader_character_id ON clans(leader_character_id); +CREATE INDEX IF NOT EXISTS idx_characters_clan_id ON characters(clan_id); +CREATE INDEX IF NOT EXISTS idx_clan_members_clan_id ON clan_members(clan_id); +CREATE INDEX IF NOT EXISTS idx_clan_requests_clan_id ON clan_requests(clan_id); + ALTER TABLE clans DROP CONSTRAINT IF EXISTS clans_alignment_check; @@ -141,38 +176,7 @@ ALTER TABLE clans ADD CONSTRAINT clans_alignment_check CHECK (alignment IN ('citizen', 'criminal')); -ALTER TABLE characters - ADD COLUMN IF NOT EXISTS clan_id UUID REFERENCES clans(id) ON DELETE SET NULL; - -CREATE TABLE IF NOT EXISTS clan_members ( - clan_id UUID NOT NULL REFERENCES clans(id) ON DELETE CASCADE, - character_id UUID NOT NULL REFERENCES characters(id) ON DELETE CASCADE, - role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('leader', 'co_leader', 'member')), - joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (clan_id, character_id), - UNIQUE (character_id) -); - -ALTER TABLE clan_members - DROP CONSTRAINT IF EXISTS clan_members_role_check; - -ALTER TABLE clan_members - ADD CONSTRAINT clan_members_role_check - CHECK (role IN ('leader', 'co_leader', 'member')); - -CREATE TABLE IF NOT EXISTS clan_requests ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - clan_id UUID NOT NULL REFERENCES clans(id) ON DELETE CASCADE, - character_id UUID NOT NULL REFERENCES characters(id) ON DELETE CASCADE, - message TEXT NOT NULL DEFAULT '', - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - UNIQUE (character_id) -); -CREATE INDEX IF NOT EXISTS idx_clans_leader_character_id ON clans(leader_character_id); -CREATE INDEX IF NOT EXISTS idx_characters_clan_id ON characters(clan_id); -CREATE INDEX IF NOT EXISTS idx_clan_members_clan_id ON clan_members(clan_id); -CREATE INDEX IF NOT EXISTS idx_clan_requests_clan_id ON clan_requests(clan_id); ALTER TABLE characters ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ; From 83c03aa6439f6616f115a26b6dbb2ab4ae0b3e84 Mon Sep 17 00:00:00 2001 From: trexfr-ops Date: Thu, 20 Aug 2026 14:36:25 +0000 Subject: [PATCH 2/2] fix(db): remove psql meta-command and wrap migrate.ts in atomic transaction --- api/schema.sql | 2 -- api/src/migrate.ts | 34 ++++++++++++++++++++++------------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/api/schema.sql b/api/schema.sql index df95100e..5cfba6e6 100644 --- a/api/schema.sql +++ b/api/schema.sql @@ -1,5 +1,3 @@ -\set ON_ERROR_STOP on - CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE IF NOT EXISTS accounts ( diff --git a/api/src/migrate.ts b/api/src/migrate.ts index 3e9425ea..ab0b7a66 100644 --- a/api/src/migrate.ts +++ b/api/src/migrate.ts @@ -6,18 +6,28 @@ async function migrate(): Promise { const schemaPath = path.resolve(__dirname, "..", "schema.sql"); const schemaSql = fs.readFileSync(schemaPath, "utf8"); - await pool.query(schemaSql); - await pool.query(` - CREATE INDEX IF NOT EXISTS idx_market_listings_active_price_created - ON market_listings(price ASC, created_at ASC) - WHERE status = 'active' - `); - await pool.query(` - CREATE INDEX IF NOT EXISTS idx_market_listings_seller_active_created - ON market_listings(seller_character_id, created_at DESC) - WHERE status = 'active' - `); - console.log("Database schema applied successfully"); + const client = await pool.connect(); + try { + await client.query("BEGIN"); + await client.query(schemaSql); + await client.query(` + CREATE INDEX IF NOT EXISTS idx_market_listings_active_price_created + ON market_listings(price ASC, created_at ASC) + WHERE status = 'active' + `); + await client.query(` + CREATE INDEX IF NOT EXISTS idx_market_listings_seller_active_created + ON market_listings(seller_character_id, created_at DESC) + WHERE status = 'active' + `); + await client.query("COMMIT"); + console.log("Database schema applied successfully"); + } catch (error) { + await client.query("ROLLBACK"); + throw error; + } finally { + client.release(); + } } void migrate()