feat(templates): T6 controller + DTOs + module wiring

POST /v1/templates/send (render→queue) and /preview (render only). OPA-gated in
the sender: iios.template.send, and iios.template.send.inline for ad-hoc HTML
(arbitrary markup to a customer is riskier than a reviewed stored template).
Scope resolved from the caller's principal. TemplateModule registered in AppModule.

Verified over HTTP against a local boot: preview + send of the seeded
payment.receipt (SENT via sandbox, provenance persisted), XSS name escaped,
idempotent replay → 1 row, unknown key 404, bad channel/no-source/no-auth 400.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 12:39:04 +05:30
parent 37407cb0af
commit b44f795ba4
6 changed files with 135 additions and 3 deletions
@@ -0,0 +1,21 @@
import { Module } from '@nestjs/common';
import { AdaptersModule } from '../adapters/adapters.module';
import { TemplateRepository } from './template.repository';
import { TemplateService } from './template.service';
import { TemplatedSender } from './templated-sender';
import { TemplateSeeder } from './template.seeder';
import { TemplateController } from './template.controller';
/**
* Reusable message-template module: render a stored/inline template → hand it to the existing
* outbound pipeline (via AdaptersModule's OutboundService). Content lives here; delivery stays in
* the outbound/capability layers. SessionVerifier, ActorResolver (IdentityModule) and PLATFORM_PORTS
* (PlatformModule) are global.
*/
@Module({
imports: [AdaptersModule],
controllers: [TemplateController],
providers: [TemplateRepository, TemplateService, TemplatedSender, TemplateSeeder],
exports: [TemplateService, TemplatedSender],
})
export class TemplateModule {}