diff --git a/crates/db/migrations/20260317195000_profession_specific_profiles.up.sql b/crates/db/migrations/20260317195000_profession_specific_profiles.up.sql index dd45ab9..837965e 100644 --- a/crates/db/migrations/20260317195000_profession_specific_profiles.up.sql +++ b/crates/db/migrations/20260317195000_profession_specific_profiles.up.sql @@ -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; diff --git a/crates/db/migrations/20260318233000_tracecoin_ledger_immutable.down.sql b/crates/db/migrations/20260318233000_tracecoin_ledger_immutable.down.sql index a56bbe9..d3587e9 100644 --- a/crates/db/migrations/20260318233000_tracecoin_ledger_immutable.down.sql +++ b/crates/db/migrations/20260318233000_tracecoin_ledger_immutable.down.sql @@ -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; diff --git a/crates/db/migrations/20260318233000_tracecoin_ledger_immutable.up.sql b/crates/db/migrations/20260318233000_tracecoin_ledger_immutable.up.sql index f557cf9..12d4aad 100644 --- a/crates/db/migrations/20260318233000_tracecoin_ledger_immutable.up.sql +++ b/crates/db/migrations/20260318233000_tracecoin_ledger_immutable.up.sql @@ -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 $$ diff --git a/crates/db/migrations/20260425000000_ai_usage.up.sql b/crates/db/migrations/20260425000000_ai_usage.up.sql index 2c59be5..cc54921 100644 --- a/crates/db/migrations/20260425000000_ai_usage.up.sql +++ b/crates/db/migrations/20260425000000_ai_usage.up.sql @@ -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; diff --git a/crates/db/migrations/20260721030000_create_lead_requests.down.sql b/crates/db/migrations/20260721030000_create_lead_requests.down.sql index d6eba2c..19b57c5 100644 --- a/crates/db/migrations/20260721030000_create_lead_requests.down.sql +++ b/crates/db/migrations/20260721030000_create_lead_requests.down.sql @@ -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. diff --git a/crates/db/migrations/20260721030000_create_lead_requests.up.sql b/crates/db/migrations/20260721030000_create_lead_requests.up.sql index 448a363..39b27f0 100644 --- a/crates/db/migrations/20260721030000_create_lead_requests.up.sql +++ b/crates/db/migrations/20260721030000_create_lead_requests.up.sql @@ -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.