From 5a144e0ce48506feb23f8311a143a81b4a11b8d3 Mon Sep 17 00:00:00 2001 From: Joachim Hummel Date: Fri, 17 Apr 2026 08:57:37 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Migrationen=20f=C3=BCr=20campaigns-Tabe?= =?UTF-8?q?llen=20(PostgreSQL)=20und=20email=5Fevents=20(ClickHouse)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- migrations/ch/2026-04-17_email_events.sql | 11 ++++++ migrations/pg/2026-04-17_campaigns.sql | 42 +++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 migrations/ch/2026-04-17_email_events.sql create mode 100644 migrations/pg/2026-04-17_campaigns.sql diff --git a/migrations/ch/2026-04-17_email_events.sql b/migrations/ch/2026-04-17_email_events.sql new file mode 100644 index 0000000..35bc3d9 --- /dev/null +++ b/migrations/ch/2026-04-17_email_events.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS newsletter.email_events ( + event_type LowCardinality(String), + tenant_id String, + campaign_id UUID, + recipient_hash String, + timestamp DateTime64(3, 'UTC'), + metadata Map(String, String) +) +ENGINE = MergeTree() +PARTITION BY toYYYYMM(timestamp) +ORDER BY (tenant_id, campaign_id, event_type, timestamp); diff --git a/migrations/pg/2026-04-17_campaigns.sql b/migrations/pg/2026-04-17_campaigns.sql new file mode 100644 index 0000000..05054f9 --- /dev/null +++ b/migrations/pg/2026-04-17_campaigns.sql @@ -0,0 +1,42 @@ +-- Wird pro Tenant-Schema ausgeführt (SET search_path = tenant_, public vorher) + +CREATE TABLE IF NOT EXISTS campaigns ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + subject TEXT NOT NULL, + html_body TEXT NOT NULL, + plain_body TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'draft' + CHECK (status IN ('draft','scheduled','sending','sent','paused','cancelled')), + scheduled_at TIMESTAMPTZ, + cron_expression TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS campaign_recipients ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + campaign_id UUID NOT NULL REFERENCES campaigns(id) ON DELETE CASCADE, + list_id UUID, + segment_id UUID, + CONSTRAINT recipient_has_one CHECK ( + (list_id IS NOT NULL AND segment_id IS NULL) OR + (segment_id IS NOT NULL AND list_id IS NULL) + ) +); + +CREATE TABLE IF NOT EXISTS campaign_triggers ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + campaign_id UUID NOT NULL REFERENCES campaigns(id) ON DELETE CASCADE, + trigger_type TEXT NOT NULL CHECK (trigger_type IN ('cron', 'event')), + trigger_value TEXT NOT NULL +); + +CREATE OR REPLACE FUNCTION update_updated_at() +RETURNS TRIGGER AS $$ +BEGIN NEW.updated_at = now(); RETURN NEW; END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER campaigns_updated_at + BEFORE UPDATE ON campaigns + FOR EACH ROW EXECUTE FUNCTION update_updated_at();