-- 0092_email_templates_and_campaigns.sql -- -- Creates the `email_templates` and `campaigns` tables referenced by -- `db/schema/marketing.ts` (Drizzle). These tables were defined in the -- schema but never migrated, so the "Create Template" UI on the -- communications page errored with `relation "email_templates" does -- not exist`. -- -- A parallel set of tables (`communication_templates`, -- `communication_campaigns`) already exists from 0001_init.sql — those -- are a different schema used by the Harvest Reach module and are -- untouched here. This migration only adds the marketing-schema tables -- the `upsertTemplate` / `createCampaign` actions need. -- -- Conventions (mirror 0001_init.sql): -- - gen_random_uuid() for PKs -- - TIMESTAMPTZ for timestamps -- - TEXT + CHECK for the campaigns.status enum -- - CREATE TABLE IF NOT EXISTS for re-runnability -- - set_updated_at() trigger for updated_at maintenance BEGIN; -- ============================================================================ -- email_templates -- ============================================================================ CREATE TABLE IF NOT EXISTS email_templates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE, name TEXT NOT NULL, subject TEXT NOT NULL, body_html TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_trigger t JOIN pg_class c ON t.tgrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE t.tgname = 'email_templates_set_updated_at' AND n.nspname = current_schema() ) THEN CREATE TRIGGER email_templates_set_updated_at BEFORE UPDATE ON email_templates FOR EACH ROW EXECUTE FUNCTION set_updated_at(); END IF; END $$; CREATE INDEX IF NOT EXISTS email_templates_brand_idx ON email_templates (brand_id); -- ============================================================================ -- campaigns -- ============================================================================ CREATE TABLE IF NOT EXISTS campaigns ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE, template_id UUID REFERENCES email_templates(id) ON DELETE SET NULL, name TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'scheduled', 'sending', 'sent', 'canceled')), scheduled_for TIMESTAMPTZ, sent_at TIMESTAMPTZ, recipient_count INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_trigger t JOIN pg_class c ON t.tgrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE t.tgname = 'campaigns_set_updated_at' AND n.nspname = current_schema() ) THEN CREATE TRIGGER campaigns_set_updated_at BEFORE UPDATE ON campaigns FOR EACH ROW EXECUTE FUNCTION set_updated_at(); END IF; END $$; CREATE INDEX IF NOT EXISTS campaigns_brand_idx ON campaigns (brand_id); CREATE INDEX IF NOT EXISTS campaigns_status_idx ON campaigns (brand_id, status); COMMIT;