feat(templates): T2 IiosMessageTemplate table + resolve()
Schema + migration for the template source table, and TemplateRepository.resolve: highest active version for (key,channel,locale), scoped override preferred over the global (scopeId NULL) default. Migration adds a PARTIAL unique index WHERE scopeId IS NULL so two platform defaults for the same key can't coexist (Postgres treats NULL as distinct under a plain UNIQUE — the scoped constraint alone wouldn't catch it). 6 tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
-- Reusable message templates (email/SMS/in-app). DB is runtime truth; platform defaults seeded
|
||||
-- from repo files on boot. Versions are immutable (a change = a new higher version).
|
||||
CREATE TYPE "IiosTemplateChannel" AS ENUM ('EMAIL', 'SMS', 'INTERNAL');
|
||||
|
||||
CREATE TABLE "IiosMessageTemplate" (
|
||||
"id" TEXT NOT NULL,
|
||||
"scopeId" TEXT,
|
||||
"key" TEXT NOT NULL,
|
||||
"channel" "IiosTemplateChannel" NOT NULL,
|
||||
"locale" TEXT NOT NULL DEFAULT 'en',
|
||||
"version" INTEGER NOT NULL DEFAULT 1,
|
||||
"subject" TEXT,
|
||||
"bodyHtml" TEXT,
|
||||
"bodyText" TEXT,
|
||||
"variables" JSONB,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "IiosMessageTemplate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "IiosMessageTemplate"
|
||||
ADD CONSTRAINT "IiosMessageTemplate_scopeId_fkey"
|
||||
FOREIGN KEY ("scopeId") REFERENCES "IiosScope"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Uniqueness for SCOPED rows (scopeId NOT NULL).
|
||||
CREATE UNIQUE INDEX "IiosMessageTemplate_scopeId_key_channel_locale_version_key"
|
||||
ON "IiosMessageTemplate" ("scopeId", "key", "channel", "locale", "version");
|
||||
|
||||
-- Uniqueness for GLOBAL rows (scopeId IS NULL). Postgres treats NULL as distinct under a normal
|
||||
-- UNIQUE, so the constraint above does NOT stop two platform defaults for the same key — this
|
||||
-- partial index does. (Same footgun handled in the inbox idempotency migration.)
|
||||
CREATE UNIQUE INDEX "IiosMessageTemplate_global_key"
|
||||
ON "IiosMessageTemplate" ("key", "channel", "locale", "version")
|
||||
WHERE "scopeId" IS NULL;
|
||||
|
||||
-- Resolution lookup: by key/channel/locale among active rows.
|
||||
CREATE INDEX "IiosMessageTemplate_key_channel_locale_active_idx"
|
||||
ON "IiosMessageTemplate" ("key", "channel", "locale", "active");
|
||||
Reference in New Issue
Block a user