feat(templates): T5 boot seeder + default seeds

TemplateSeeder (OnModuleInit) seeds repo file defaults as global (scopeId NULL)
templates if absent. Idempotent per (key,channel,locale,version): re-boot inserts
nothing; a bumped version adds a new row and keeps the old for audit. Seeds:
welcome (email), payment.receipt (email+sms), onboarding.reminder (email) —
placeholder copy for marketing to replace. 3 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 12:33:12 +05:30
parent 10f3545a25
commit 37407cb0af
3 changed files with 170 additions and 0 deletions
@@ -0,0 +1,49 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { TEMPLATE_SEEDS, type TemplateSeed } from './seeds';
/**
* Seeds platform-default templates (scopeId NULL) from the repo files on boot — DB is runtime truth,
* files are the version-controlled source. Idempotent: a seed is inserted only if its exact
* (key, channel, locale, version) default is absent, so re-boots never duplicate and a bumped
* version adds a new row while leaving the old one for audit/replay.
*/
@Injectable()
export class TemplateSeeder implements OnModuleInit {
private readonly log = new Logger(TemplateSeeder.name);
constructor(private readonly prisma: PrismaService) {}
async onModuleInit(): Promise<void> {
const inserted = await this.seed(TEMPLATE_SEEDS);
if (inserted > 0) this.log.log(`seeded ${inserted} default template(s)`);
}
/** Returns how many rows were newly inserted. */
async seed(seeds: TemplateSeed[]): Promise<number> {
let inserted = 0;
for (const s of seeds) {
const exists = await this.prisma.iiosMessageTemplate.findFirst({
where: { scopeId: null, key: s.key, channel: s.channel, locale: s.locale, version: s.version },
select: { id: true },
});
if (exists) continue;
await this.prisma.iiosMessageTemplate.create({
data: {
scopeId: null,
key: s.key,
channel: s.channel,
locale: s.locale,
version: s.version,
subject: s.subject,
bodyHtml: s.bodyHtml,
bodyText: s.bodyText,
variables: s.variables as Prisma.InputJsonValue,
},
});
inserted++;
}
return inserted;
}
}