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:
@@ -0,0 +1,42 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { IiosMessageTemplate, IiosTemplateChannel } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
export class TemplateNotFoundError extends NotFoundException {
|
||||
constructor(key: string, channel: string, locale: string) {
|
||||
super(`no active template for key="${key}" channel=${channel} locale=${locale}`);
|
||||
this.name = 'TemplateNotFoundError';
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TemplateRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
/**
|
||||
* Resolve the template to use: the highest active version for (key, channel, locale), preferring
|
||||
* a row owned by `scopeId` (a tenant override) and falling back to the global default (scopeId
|
||||
* NULL). Throws if neither exists.
|
||||
*/
|
||||
async resolve(
|
||||
key: string,
|
||||
channel: IiosTemplateChannel,
|
||||
locale = 'en',
|
||||
scopeId?: string,
|
||||
): Promise<IiosMessageTemplate> {
|
||||
if (scopeId) {
|
||||
const scoped = await this.highest({ key, channel, locale, scopeId });
|
||||
if (scoped) return scoped;
|
||||
}
|
||||
const global = await this.highest({ key, channel, locale, scopeId: null });
|
||||
if (global) return global;
|
||||
throw new TemplateNotFoundError(key, channel, locale);
|
||||
}
|
||||
|
||||
private highest(where: { key: string; channel: IiosTemplateChannel; locale: string; scopeId: string | null }) {
|
||||
return this.prisma.iiosMessageTemplate.findFirst({
|
||||
where: { ...where, active: true },
|
||||
orderBy: { version: 'desc' },
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user