fix: three more already-deployed migrations unconditionally ALTERed tables that were never created
Following the reference_numbers fix, audited every active (non .skip) migration for the same failure shape — ALTER/CREATE TRIGGER against lead_requests, tracecoin_wallets, tracecoin_ledger, or job_applications without ever creating them — since this codebase has a repeated pattern of table-creation migrations getting silently disabled (renamed .skip) after the code that depends on them was already written. Found three more, ALL already pushed to origin, meaning they've likely been breaking the migration chain since the date each was deployed: - 20260317195000_profession_specific_profiles.up.sql (Mar 17) — ALTERs lead_requests twice. Earliest failure point found for the lead_requests chain. Now self-creates a minimal lead_requests table first (no FK to `leads`, which isn't created until June 10 — well after this migration). - 20260318233000_tracecoin_ledger_immutable.up.sql (Mar 18) — creates immutability triggers ON tracecoin_ledger, assuming it exists. Now self-creates tracecoin_wallets/tracecoin_ledger first, matching the column names (transaction_type/reference_type) the Rust code actually uses — the only schema that ever existed for these tables (in a disabled .skip migration) used stale names (type/reason). - 20260425000000_ai_usage.up.sql (Apr 25) — ALTERs job_applications inside a BEGIN/COMMIT block, so this failure was also rolling back company_ai_usage/job_seeker_ai_usage creation in the same file. Now self-creates job_applications first. Each later migration that also touches these tables (this session's customer/job-seeker/lead_requests fixes) now uses ADD COLUMN IF NOT EXISTS instead of assuming its own CREATE TABLE ran, so the schema converges to the same end state regardless of which migration actually created the table first. Every touched migration uses IF NOT EXISTS / idempotent guards throughout, so this is safe to apply whether or not any of these tables already exist in the real database.
This commit is contained in:
parent
70e63c6aff
commit
94a8a1096a
6 changed files with 106 additions and 46 deletions
|
|
@ -200,7 +200,24 @@ ALTER TABLE services
|
|||
ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
ADD COLUMN IF NOT EXISTS profession_key VARCHAR(50);
|
||||
|
||||
-- Lead requests: use user_id instead of professional_id foreign key
|
||||
-- Lead requests: use user_id instead of professional_id foreign key.
|
||||
-- lead_requests itself was never created by any active migration until now
|
||||
-- (only in later-disabled .skip files) — self-creating it here (no FK to
|
||||
-- `leads`, since that table isn't created until 20260610003321, well after
|
||||
-- this migration) makes the ALTER below safe regardless of history.
|
||||
CREATE TABLE IF NOT EXISTS lead_requests (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
lead_id UUID,
|
||||
user_role_profile_id UUID,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||
tracecoins_reserved INTEGER NOT NULL DEFAULT 25,
|
||||
remarks TEXT,
|
||||
expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '7 days'),
|
||||
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
resolved_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
ALTER TABLE lead_requests
|
||||
ADD COLUMN IF NOT EXISTS professional_user_id UUID REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
DROP TRIGGER IF EXISTS trg_prevent_tracecoin_ledger_update ON tracecoin_ledger;
|
||||
DROP TRIGGER IF EXISTS trg_prevent_tracecoin_ledger_delete ON tracecoin_ledger;
|
||||
DROP FUNCTION IF EXISTS prevent_tracecoin_ledger_mutation();
|
||||
DROP TABLE IF EXISTS tracecoin_ledger;
|
||||
DROP TABLE IF EXISTS tracecoin_wallets;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,39 @@
|
|||
-- Enforce immutable tracecoin ledger: no UPDATE/DELETE allowed.
|
||||
--
|
||||
-- This migration assumed tracecoin_wallets/tracecoin_ledger already existed
|
||||
-- (they were meant to come from an earlier migration that was later disabled
|
||||
-- via .skip, with no active replacement) — so `CREATE TRIGGER ... ON
|
||||
-- tracecoin_ledger` below has been failing with "relation tracecoin_ledger
|
||||
-- does not exist" on any environment where those tables were never created,
|
||||
-- which blocks every migration after this one in the chain. Self-creating
|
||||
-- both tables here (idempotent, matching the schema
|
||||
-- crates/db/src/models/tracecoin_wallet.rs actually reads/writes) makes this
|
||||
-- migration correct on its own regardless of what ran before it.
|
||||
CREATE TABLE IF NOT EXISTS tracecoin_wallets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
|
||||
balance INTEGER NOT NULL DEFAULT 0,
|
||||
reserved INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tracecoin_ledger (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
wallet_id UUID NOT NULL REFERENCES tracecoin_wallets(id),
|
||||
transaction_type VARCHAR(20) NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
reference_type VARCHAR(50),
|
||||
reference_id UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Defensive fallback in case tracecoin_ledger already existed under the
|
||||
-- older, stale column names (`type`/`reason`) from the disabled migration.
|
||||
ALTER TABLE tracecoin_ledger ADD COLUMN IF NOT EXISTS transaction_type VARCHAR(20);
|
||||
ALTER TABLE tracecoin_ledger ADD COLUMN IF NOT EXISTS reference_type VARCHAR(50);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tracecoin_ledger_wallet_id ON tracecoin_ledger(wallet_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tracecoin_ledger_reference_id ON tracecoin_ledger(reference_id);
|
||||
|
||||
CREATE OR REPLACE FUNCTION prevent_tracecoin_ledger_mutation()
|
||||
RETURNS trigger AS $$
|
||||
|
|
|
|||
|
|
@ -29,6 +29,24 @@ CREATE TABLE IF NOT EXISTS job_seeker_ai_usage (
|
|||
CREATE INDEX IF NOT EXISTS idx_company_ai_usage_company_date ON company_ai_usage(company_id, usage_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_job_seeker_ai_usage_seeker_date ON job_seeker_ai_usage(job_seeker_id, usage_date);
|
||||
|
||||
-- job_applications was never created by any active migration until now
|
||||
-- (only referenced, never defined — see crates/db/src/models/application.rs)
|
||||
-- so this ALTER always failed and — since the whole file runs in one
|
||||
-- transaction — rolled back company_ai_usage/job_seeker_ai_usage above too.
|
||||
-- Self-create a minimal version; the later migration that fully owns this
|
||||
-- table (20260721000000_fix_job_seeker_profile_and_applications.up.sql)
|
||||
-- converges the rest of the columns via ADD COLUMN IF NOT EXISTS.
|
||||
CREATE TABLE IF NOT EXISTS job_applications (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
job_id UUID NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
||||
applicant_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
cover_note TEXT,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'APPLIED',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(job_id, applicant_user_id)
|
||||
);
|
||||
|
||||
-- Add applied_via_ai flag to job_applications for AI auto-apply tracking
|
||||
ALTER TABLE job_applications ADD COLUMN IF NOT EXISTS applied_via_ai BOOLEAN DEFAULT false;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
DROP TABLE IF EXISTS lead_requests;
|
||||
DROP FUNCTION IF EXISTS set_lead_request_reference_number();
|
||||
DROP SEQUENCE IF EXISTS lead_request_number_seq;
|
||||
-- tracecoin_wallets/tracecoin_ledger intentionally left in place — an
|
||||
-- already-deployed migration (20260318233000_tracecoin_ledger_immutable)
|
||||
-- has triggers depending on tracecoin_ledger, and other data may reference
|
||||
-- these tables by now.
|
||||
-- tracecoin_wallets/tracecoin_ledger are owned by
|
||||
-- 20260318233000_tracecoin_ledger_immutable now — nothing to tear down here.
|
||||
|
|
|
|||
|
|
@ -6,23 +6,44 @@
|
|||
-- versions didn't match the columns the current Rust code actually reads/writes
|
||||
-- (professional_user_id, customer_user_id, remarks, requested_at, resolved_at) —
|
||||
-- so this flow has never worked. Schema below matches the live Rust code exactly.
|
||||
-- 20260317195000_profession_specific_profiles.up.sql (already deployed,
|
||||
-- earlier in the chain) now self-creates a minimal version of this table —
|
||||
-- CREATE TABLE IF NOT EXISTS here is a no-op in that case, so every column
|
||||
-- is also added via ADD COLUMN IF NOT EXISTS below to converge regardless
|
||||
-- of which migration actually created the table.
|
||||
CREATE TABLE IF NOT EXISTS lead_requests (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
lead_id UUID REFERENCES leads(id) ON DELETE CASCADE,
|
||||
lead_id UUID,
|
||||
user_role_profile_id UUID,
|
||||
professional_user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
customer_user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||
tracecoins_reserved INTEGER NOT NULL DEFAULT 25,
|
||||
remarks TEXT,
|
||||
expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '7 days'),
|
||||
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
resolved_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(lead_id, professional_user_id)
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
ALTER TABLE lead_requests
|
||||
ADD COLUMN IF NOT EXISTS lead_id UUID,
|
||||
ADD COLUMN IF NOT EXISTS user_role_profile_id UUID,
|
||||
ADD COLUMN IF NOT EXISTS professional_user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
ADD COLUMN IF NOT EXISTS customer_user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||
ADD COLUMN IF NOT EXISTS tracecoins_reserved INTEGER NOT NULL DEFAULT 25,
|
||||
ADD COLUMN IF NOT EXISTS remarks TEXT,
|
||||
ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '7 days'),
|
||||
ADD COLUMN IF NOT EXISTS requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS resolved_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'lead_requests_lead_id_professional_user_id_key') THEN
|
||||
ALTER TABLE lead_requests ADD CONSTRAINT lead_requests_lead_id_professional_user_id_key UNIQUE (lead_id, professional_user_id);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_lead_requests_lead_id ON lead_requests(lead_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_lead_requests_user_role_profile_id ON lead_requests(user_role_profile_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_lead_requests_professional_user_id ON lead_requests(professional_user_id);
|
||||
|
|
@ -65,38 +86,7 @@ BEGIN
|
|||
END IF;
|
||||
END $$;
|
||||
|
||||
-- tracecoin_wallets/tracecoin_ledger back the Tracecoin reservation that
|
||||
-- happens when a lead request is sent (TracecoinWalletRepository). Like
|
||||
-- lead_requests, they were only ever defined in disabled (.skip) migrations
|
||||
-- — and even those used stale column names (`type`/`reason` instead of the
|
||||
-- `transaction_type`/`reference_type` the current Rust code writes). An
|
||||
-- earlier active migration (20260318233000_tracecoin_ledger_immutable,
|
||||
-- already deployed) creates triggers ON tracecoin_ledger assuming it already
|
||||
-- exists; CREATE TABLE IF NOT EXISTS here is a safe no-op if that migration
|
||||
-- already succeeded against a table created some other way, and fills the
|
||||
-- gap if it didn't.
|
||||
CREATE TABLE IF NOT EXISTS tracecoin_wallets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
|
||||
balance INTEGER NOT NULL DEFAULT 0,
|
||||
reserved INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tracecoin_ledger (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
wallet_id UUID NOT NULL REFERENCES tracecoin_wallets(id),
|
||||
transaction_type VARCHAR(20) NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
reference_type VARCHAR(50),
|
||||
reference_id UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- In case tracecoin_ledger already existed under the older, stale column
|
||||
-- names from the disabled migration's design.
|
||||
ALTER TABLE tracecoin_ledger ADD COLUMN IF NOT EXISTS transaction_type VARCHAR(20);
|
||||
ALTER TABLE tracecoin_ledger ADD COLUMN IF NOT EXISTS reference_type VARCHAR(50);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tracecoin_ledger_wallet_id ON tracecoin_ledger(wallet_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tracecoin_ledger_reference_id ON tracecoin_ledger(reference_id);
|
||||
-- tracecoin_wallets/tracecoin_ledger (needed for the Tracecoin reservation
|
||||
-- that happens when a lead request is sent) are now created earlier in the
|
||||
-- chain by 20260318233000_tracecoin_ledger_immutable.up.sql — nothing left
|
||||
-- to do here.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue