Tmavý režim
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+bodyTemplatepro 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
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
}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.
userCanDisable
Pokud userCanDisable = false, uživatel nemůže typ vypnout ve svých preferencích. Vhodné pro kritické systémové notifikace.
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ý (PUT ho needituje), aby se vazba nikdy nerozpadla.
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)
}keyje slug ([a-z0-9_-]), unikátní v rámci(app, type), a je neměnný (rename se nepodporuje — vytvoř novou).variablespatří šabloně a jsou společné pro všechny její jazykové verze.
Správa šablon
| Metoda | Cesta | Účel |
|---|---|---|
| GET | /admin/notifications/types/:appCode/:type/templates | Šablony pod typem (vč. seznamu locale) |
| POST | /admin/notifications/types/:appCode/:type/templates | Vytvoř šablonu { key, name, description?, variables? } |
| PUT | /admin/notifications/templates/:id | Uprav šablonu (name, description, variables) |
| DELETE | /admin/notifications/templates/:id | Smaž š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ínka | Chyba (4xx, details.code) |
|---|---|
type musí existovat jako NotificationType dané aplikace | UNKNOWN_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í
| Metoda | Cesta | Účel |
|---|---|---|
| GET | /admin/notifications/templates/:id/locales | Jazykové verze šablony |
| PUT (upsert) | /admin/notifications/templates/:id/locales/:locale | Vytvoř/uprav verzi { subjectTemplate, bodyTemplate } |
| DELETE | /admin/notifications/templates/:id/locales/:locale | Smaž jednu jazykovou verzi |
- Upsert podle
(templateId, locale)— když verze neexistuje, vytvoří (201); jinak aktualizuje (200). Žádné „already exists". localemusí být povolený jazyk aplikace (Application.supportedLanguages; když aplikace nemá omezení, musí existovat v registru jazyků) → jinak400UNSUPPORTED_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:
- verze v
localez request body (override), jinak vUserProfile.locale(default"cs"), - jinak po řetězu
Language.fallback(např.hr → en → cs), - 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):
| Stav | Chování |
|---|---|
template vyplněné, existuje | použije se šablona (app, type, template) |
template vyplněné, neexistuje | 400 UNKNOWN_TEMPLATE, details.available = [klíče] |
template chybí, typ má 1 šablonu | použije se ta jediná (pokrývá migrovaná data) |
template chybí, typ má víc šablon | 400 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 MISSING_TEMPLATE_VARIABLES, details.missing = […]. Systémové (appName, 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, 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.
| Metoda | Cesta | Tělo |
|---|---|---|
| GET | /admin/languages | → Language[] |
| 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. DELETE se odmítne, pokud je jazyk fallbackem jiného nebo je v Application.supportedLanguages.
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) |
| 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) skey+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/pushEnabledauserCanDisablena 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. Placeholderyjsou 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.