Template System
The template system generates HTML documents for GDPR processing activities (Art. 30 declarations and Art. 13 information clauses). It uses Handlebars for compilation, a 3-tier fallback for template selection, and a structured context object built from translated activity data.
Source files
| File | Role |
|---|---|
src/lib/text/renderTemplate.ts | Core Handlebars compilation |
src/services/templateService.ts | CRUD + reloadTemplateRO (3-tier lookup) + compilePreviewRO |
src/lib/html/generateActivityDeclaration.ts | Builds context + renders Art. 30 HTML |
src/lib/html/generateActivityInformation.ts | Builds context + renders Art. 13 HTML |
src/app/[locale]/[id]/route.ts | HTTP route returning the rendered HTML |
templates/activity-declaration/*.hbs | Default Art. 30 templates (one per locale) |
templates/activity-information/*.hbs | Default Art. 13 templates (one per locale) |
templates/ropa-frontpage/*.hbs | Default ROPA front page templates |
templates/ropa-summary-page/*.hbs | Default ROPA table-of-contents templates |
Compilation
Entry point: renderTemplate(templateContent, data) in src/lib/text/renderTemplate.ts.
import Handlebars from "handlebars/dist/handlebars";
const renderTemplate = (
templateContent: string,
data: Record<string, unknown> = {}
): string => {
const template = Handlebars.compile(templateContent);
return template({ generatedDate: new Date().toISOString().split("T")[0], ...data });
};
- Accepts a raw Handlebars string, not a file path.
- Automatically injects
generatedDate(ISO date,YYYY-MM-DD) unless already present indata.
Built-in helpers registered globally:
| Helper | Signature | Purpose |
|---|---|---|
formatDate | (date, format) | Locale-aware date formatting |
json | (value) | Serialize value to JSON string |
eq | (a, b) | Strict equality check |
ne | (a, b) | Strict inequality check |
gt | (a, b) | Greater-than comparison |
lt | (a, b) | Less-than comparison |
Template types
type | Generator function | Default template directory |
|---|---|---|
activityDeclaration | generateActivityDeclaration | templates/activity-declaration/ |
activityInformation | generateActivityInformation | templates/activity-information/ |
ropaFrontPage | (org-level only) | templates/ropa-frontpage/ |
ropaTOC | (org-level only) | templates/ropa-summary-page/ |
Default files are named {type}-{locale}.hbs, e.g. activity-declaration-en.hbs.
Template lookup
reloadTemplateRO in templateService.ts resolves the correct template using a 3-tier fallback:
1. Activity-specific template
Ropa.ous[N].activities[M].templateId → Template document
2. Org-level fallback
Organization.templates[] where activityId=0 AND type matches → Template document
3. Default file
templates/{type}/{type}-{locale}.hbs (bundled with the app)
The first tier that resolves wins. The resulting Handlebars string is passed directly to renderTemplate.
Context object
Built by generateActivityDeclaration / generateActivityInformation. Key fields:
| Field | Type | Notes |
|---|---|---|
activityName | string | |
purposeShort | string | |
purposeLong | string | |
organization.shortName | string | |
organization.organizationName | string | organizationNameLong from partners[0] |
organization.logo | string | null | Base64 data URL (JPEG/PNG); null if no logo set |
ou.name | string | Name of the parent OU |
isActive | boolean | |
lastUpdated | string | Locale-formatted date string (e.g. "June 25, 2026") |
legalbasis | string[] | Translated from attribute.legalbasis.* namespace |
legalbasisLong | string | |
legalbasisSpecial | string[] | Translated |
dataCategories | string[] | Translated |
datasubjectCategories | string | |
dataOrigin | string | |
activityCategories | string[] | Translated |
profiling | string | "Yes" or "No" (translated via attribute.true/false) |
communications | string[] | |
communicationsLong | string | |
transfers | string | "Yes" or "No" (translated) |
transfersLong | string | |
securityLevel | string | Translated |
securityMeasuresLong | string | |
timeLimit | string | |
contracts | object[] | Contracts linked to this activity |
controllers | { name, address }[] | Resolved from Activity.controllers[] via Organization.partners[] |
processors | { name, address }[] | Resolved from Activity.processors[] via Organization.partners[] |
generatedDate | string | Auto-injected by renderTemplate; ISO date YYYY-MM-DD |
Enum translation: Multi-value fields (legalbasis, dataCategories, etc.) are stored as English keys in MongoDB (e.g. "a", "b"). The generator translates each key using getTranslations({ locale, namespace: "attribute" }) before passing it to the template.
HTML route
GET /{locale}/{activityId}?type=activity-declaration|activity-information
Source: src/app/[locale]/[id]/route.ts
typedefaults toactivity-informationwhen omitted.- Returns
Content-Type: text/html; charset=utf-8. - Protected by Turnstile CAPTCHA verification (same flow as the PDF route).
- The PDF route (
[id]/pdf/route.ts) calls the same generator functions and feeds the resulting HTML into Playwright — the HTML and PDF routes share the same rendering pipeline.