Skip to content

Typy, šablony a jazykové verze

Notifikace mají 3 úrovně:

NotificationType   (SKUPINA / kategorie;  unikát: app + code)
        │  1 : N

NotificationTemplate  (POJMENOVANÁ šablona;  unikát: app + type + key)
        │  1 : N

TemplateLocale     (jazyková verze;  unikát: templateId + locale)

Příklad:

Typ "error"  (skupina — drží kanály a předvolby uživatele)
 ├─ Šablona "disk_full"   (key + name + variables)
 │    ├─ cs (subject/body)   ├─ en   └─ de
 └─ Šablona "auth_failed"
      ├─ cs   └─ en
  • Typ = skupina/kategorie. Drží uživatelské předvolby a kanály (emailEnabled, pushEnabled, userCanDisable, defaultEmail, defaultPush). Uživatel se odhlašuje z typu (= ze všech jeho šablon).
  • Šablona = pojmenovaná jednotka obsahu pod typem (key + name + description + variables[]). Variables patří šabloně (společné pro její jazyky).
  • Jazyková verze = subjectTemplate + bodyTemplate pro daný locale.

Jednotka, kterou aplikace odešle a kterou renderujeme = (app, type, template, locale).


1. Notifikační typy (skupina)

Typ definuje kategorii — co se oznamuje, jakými kanály a zda to může uživatel vypnout.

typescript
{
  id: number;
  applicationCode: string;
  code: string;              // unikátní kód v rámci aplikace (např. 'error')
  name: string;              // zobrazovaný název (pro UI preferencí)
  description: string | null;

  emailEnabled: boolean;     // email kanál povolen pro tento typ
  emailFromName: string | null; // volitelné jméno v hlavičce From
  pushEnabled: boolean;      // push kanál povolen pro tento typ
  userCanDisable: boolean;   // může uživatel typ (celou skupinu) vypnout?

  defaultEmail: boolean;     // výchozí stav email pro nové uživatele
  defaultPush: boolean;      // výchozí stav push pro nové uživatele
  selfServiceEnabled: boolean;
  system: boolean;           // read-only ochrana systémového typu
}

Předvolby a kanály jsou na typu, ne na šabloně. Uživatel se odhlašuje z celé skupiny (typu), ne z jednotlivých šablon.

emailFromName přepisuje pouze zobrazované jméno odesílatele a podporuje jediný token . Adresa schránky zůstává převzatá z SMTP konfigurace. Například Auth pro organizaci Atrea a schránku report@amotion.cloud odešle zprávu jako Auth Atrea <report@amotion.cloud>.

Display name se vybírá v pořadí typ → brand → globální SMTP. Token je povolen jen v display-name části; mailbox je vždy statický a parser odmítá CR/LF, neznámé tokeny i token uvnitř e-mailové adresy.

Skutečný výsledek lze bezpečně zobrazit ještě před uložením:

http
POST /api/admin/notifications/sender-preview
Authorization: Bearer <panel-token>
Content-Type: application/json

{
  "appCode": "auth",
  "typeCode": "auth_zitadel",
  "brand": "atrea",
  "emailFromName": "Auth {{orgName}}"
}

Endpoint používá stejný resolver jako ostré odesílání a vrací pouze from, displayName, address, orgName a bezpečný popis source. SMTP heslo ani jiný secret se nenačítá do odpovědi. Vynechané emailFromName náhled počítá z uložené hodnoty; explicitní null ukáže fallback na brand/globální SMTP.

userCanDisable

Pokud userCanDisable = false, uživatel nemůže typ vypnout ve svých preferencích. Vhodné pro kritické systémové notifikace.

Systémový typ

system=true nastavuje pouze backendový seed. Takový typ nelze smazat ani mu změnit code/system; backend vždy vynutí e-mailový, povinný a service-to-service režim. Jeho obsah, překlady a emailFromName zůstávají editovatelné. Seedované šablony nelze smazat a poslední obsahovou locale nelze odstranit.

Správa typů

bash
POST   /api/admin/notifications/types/:appCode          # vytvoření
PUT    /api/admin/notifications/types/detail/:id        # aktualizace
DELETE /api/admin/notifications/types/detail/:id        # smazání (kaskádní)

Kaskádní smazání

DELETE /admin/notifications/types/detail/:id smaže typ i všechny jeho šablony i jejich jazykové verze (v transakci). code typu je neměnný: full-entity PUT smí zopakovat jeho současnou hodnotu jako idempotentní no-op, ale skutečná změna je odmítnuta.


2. Pojmenované šablony (mezivrstva)

Šablona je pojmenovaná jednotka obsahu pod typem.

typescript
{
  id: number;
  type: string;              // kód nadřazeného typu, např. 'error'
  key: string;               // slug unikátní v rámci (app, type), např. 'disk_full'
  name: string;              // zobrazovaný název
  description: string | null;
  variables: TemplateVariable[];  // kontrakt pro `data` (společné pro všechny jazyky)
}
  • key je slug ([a-z0-9_-]), unikátní v rámci (app, type), a je neměnný (rename se nepodporuje — vytvoř novou).
  • variables patří šabloně a jsou společné pro všechny její jazykové verze.

Správa šablon

MetodaCestaÚčel
GET/admin/notifications/types/:appCode/:type/templatesŠablony pod typem (vč. seznamu locale)
POST/admin/notifications/types/:appCode/:type/templatesVytvoř šablonu { key, name, description?, variables? }
PUT/admin/notifications/templates/:idUprav šablonu (name, description, variables)
DELETE/admin/notifications/templates/:idSmaž šablonu i s jazykovými verzemi (kaskáda)

GET vrací:

json
{
  "type": { "id": 3, "code": "error", "name": "Chyby", "...": "..." },
  "templates": [
    { "id": 10, "type": "error", "key": "disk_full", "name": "Disk je plný",
      "description": null, "variables": [ ... ], "locales": ["cs", "en"] }
  ]
}

Validace při zápisu

PodmínkaChyba (4xx, details.code)
type musí existovat jako NotificationType dané aplikaceUNKNOWN_NOTIFICATION_TYPE
key musí být validní slug a unikátní v (app, type)BAD_REQUEST

3. Jazykové verze (subject/body)

Každá šablona má 0..N jazykových verzí — jednu per locale.

typescript
{
  id: number;
  templateId: number;
  locale: string;            // "cs", "en", "de", ...
  subjectTemplate: string;   // Mustache šablona předmětu
  bodyTemplate: string;      // Mustache HTML šablona těla
}

Správa jazykových verzí

MetodaCestaÚčel
GET/admin/notifications/templates/:id/localesJazykové verze šablony
PUT (upsert)/admin/notifications/templates/:id/locales/:localeVytvoř/uprav verzi { subjectTemplate, bodyTemplate }
DELETE/admin/notifications/templates/:id/locales/:localeSmaž jednu jazykovou verzi
  • Upsert podle (templateId, locale) — když verze neexistuje, vytvoří (201); jinak aktualizuje (200). Žádné „already exists".
  • locale musí být povolený jazyk aplikace (Application.supportedLanguages; když aplikace nemá omezení, musí existovat v registru jazyků) → jinak 400 UNSUPPORTED_LOCALE. Normalizuje se na lowercase.

Rozlišení jazyka (fallback řetěz) — v rámci šablony

Při odeslání se vybírá jazyková verze zvolené šablony:

  1. verze v locale z request body (override), jinak v UserProfile.locale (default "en"),
  2. jinak po řetězu Language.fallback (např. hr → en),
  3. koncový safety-net: libovolná existující jazyková verze té šablony.

Řetěz se spravuje globálně v registru jazyků.


Odeslání: pole type + template

Aplikace cílí šablonu přes type (skupina) a template (key):

jsonc
POST /api/internal/notifications
{
  "recipientEmail": "petr@example.com",
  "appCode": "ui-template",
  "type": "error",          // SKUPINA
  "template": "disk_full",  // KONKRÉTNÍ ŠABLONA
  "locale": "cs",           // volitelné
  "data": { "path": "/var" }
}

Pravidla pro template (zpětná kompatibilita):

StavChování
template vyplněné, existujepoužije se šablona (app, type, template)
template vyplněné, neexistuje400 UNKNOWN_TEMPLATE, details.available = [klíče]
template chybí, typ má 1 šablonupoužije se ta jediná (pokrývá migrovaná data)
template chybí, typ má víc šablon400 TEMPLATE_REQUIRED, details.available = [klíče]
type === "generic"speciál — vestavěná šablona, bez DB

Validace data (per šablona)

required parametry se berou z variables zvolené šablony. Chybí-li → 400, code = "MISSING_TEMPLATE_VARIABLES", details.missing = […]. Systémové (appName, orgName, userName, userEmail) doplní backend (neposílat v data).


Parametry šablony (variables)

Šablona deklaruje, jaké parametry vyžaduje. Je to kontrakt, který odesílající aplikace musí splnit v poli data, a zároveň zdroj, ze kterého panel staví nápovědu a náhled.

typescript
{
  key: string;          // povinné, [A-Za-z0-9_.]; používá se jako {{key}}
  label?: string;       // popisek pro admina
  description?: string; // nápověda
  required?: boolean;   // default true
  example?: string;     // ukázková hodnota pro náhled
}

Parametry jsou jazykově nezávislé → patří šabloně a jsou shodné napříč všemi jejími jazykovými verzemi.

Systémové proměnné (appName, orgName, userName, userEmail) do variables nepatří — backend je doplňuje automaticky (viz Systémové proměnné).


Registr jazyků

Kurátovaný globální seznam jazyků platformy. Admin jazyky zakládá, pojmenovává a nastavuje jim fallback.

MetodaCestaTělo
GET/admin/languagesLanguage[]
POST/admin/languages{ code, name, fallback? }
PUT/admin/languages/{code}{ name?, fallback? } (code neměnný)
DELETE/admin/languages/{code}

Model: { code: string (lowercase, PK), name: string, fallback: string | null }. fallback = null = kořen. V řetězu nesmí vzniknout cyklus a registr musí mít alespoň jeden kořen. DELETE se odmítne, pokud je jazyk fallbackem jiného, je v Application.supportedLanguages nebo jde o poslední kořen.


Mustache šablony

Šablony používají jednoduchou Mustache syntaxi: vykreslí hodnotu.

Subject:

Nová objednávka č. {{orderNumber}} od {{customerName}}

Body (HTML):

html
<h1 style="color: {{brandPrimaryColor}}">Nová objednávka!</h1>
<p>Přijali jsme objednávku č. <strong>{{orderNumber}}</strong></p>
<p><a href="{{orderUrl}}" style="color: {{brandLinkColor}}">Zobrazit objednávku</a></p>
<hr>
<small>{{brandFooterText}}</small>

Automaticky dostupné proměnné (brand context)

ProměnnáObsah
Hlavní barva brandu (hex)
Tmavší varianta hlavní barvy
Barva pozadí
Barva textu
Barva odkazů
URL loga brandu
Text v patičce
Název brandu
Odesílací email

Brand se načte dle UserProfile.brand příjemce. Pokud brand neexistuje, použije se výchozí brand.

Systémové proměnné

Backend doplňuje do každé šablony tyto proměnné. Odesílající aplikace je nemá posílat v data — jakákoliv hodnota poslaná pod těmito klíči je přepsána:

ProměnnáObsah
Název aplikace (z Application.name)
Název organizace/brandu příjemce (z Brand.name)
Jméno příjemce (UserProfile.displayName, fallback na e-mail)
E-mail příjemce

Tyto proměnné se do variables neuvádějí.


Preview šablony

bash
POST /api/admin/notifications/templates/preview
{
  "subjectTemplate": "Objednávka č. {{orderNumber}}",
  "bodyTemplate": "<h1>Přijata!</h1><p>{{orderNumber}} — {{totalAmount}} Kč</p>",
  "data": { "orderNumber": "TEST-001", "totalAmount": "5000" },
  "brand": "atrea"
}

Preview renderuje libovolný subject/body — není vázaný na uloženou šablonu.


Checklist pro nový notifikační typ

  • [ ] Vytvořit NotificationType (POST /admin/notifications/types/:appCode)
  • [ ] Vytvořit pojmenovanou šablonu (POST /admin/notifications/types/:appCode/:type/templates) s key + name + variables
  • [ ] Přidat jazykové verze (PUT /admin/notifications/templates/:id/locales/:locale) — ručně nebo přes Lexicon auto-překlad
  • [ ] Ověřit fallback řetěz v /admin/languages
  • [ ] Otestovat preview s testovacími daty
  • [ ] Ověřit, že aplikace posílá type + template + správná data
  • [ ] Zkontrolovat emailEnabled / pushEnabled a userCanDisable na typu

Auto-překlad přes Lexicon

Šablony lze automaticky dopřeložit přes Lexicon API (DeepL pod kapotou):

  • GET /admin/notifications/lexicon/languages — seznam podporovaných cílových jazyků.
  • POST /admin/notifications/lexicon/translate-template — přeloží subjectTemplate + bodyTemplate. Placeholdery jsou před překladem maskované a po překladu obnovené.

Auto-překlad šablonu neukládá — vrátí přeložený text ke kontrole. Konfigurace: LEXICON_API_URL / LEXICON_API_KEY.

Atrea User API — interní dokumentace