Nalezeno na bezicim serveru: TK-4946 mel 177 udalosti a 620 radku logu, pritom se skoro nic nestalo. Zmereno proti fronte: ve stejnem okne vzniklo presne tolik behu, kolik prislo udalosti (22 a 22), kazdy s jednim pokusem. Fronta nenasobi nic, odesilatel poslal 177 POSTu. Nase vina byla, ze to z historie neslo poznat. - Data udalosti se ukladaji. Kdyz krok nema vlastni, ulozi se to, cim beh zacal - u webhooku prijate telo. Prazdna udalost je horsi nez zadna. - Shodna udalost se pocita (repeats, lastAt), nezaklada dalsi radek. Ticket se pritom nemeni, takze duplikat nerozblika dashboard ani nespusti automatizaci na zmenu ticketu. Zahodit ji nejde, jinak by nikdo nezjistil, ze proti nam neco tluce. - Zmeny se radi pod udalost, ktera je zpusobila, a u udalosti stoji jmeno automatizace. Log se cte jako "prislo tohle -> zmenilo to tohle". - Poznamka o stavu jen kdyz se stav zmenil. "z in-progress na in-progress" u kazde zpravy byl zdroj tech 620 radku. - runsToday konecne znamena dnes: behy po dnech, k tomu vcera a celkem. Dosud to byl citac od zalozeni automatizace, jen se jmenoval "dnes". Vedle toho prace, o kterou slo predtim: - Prevzeti ticketu ze skupiny (POST /tickets/:id/claim) a krok Predat skupine s prepinacem automatickeho prideleni nejvolnejsimu. - Pozvanky do firmy: odkaz s nahodnym kodem, heslo si nastavi pozvany. - Resitele, skupiny a pozvanky presunuty z Nastaveni do zalozky Lide, cleny skupiny se vybiraji klikanim. - Ctyri AI znaky, ktere zbyvaly v kodu, pryc. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
15 KiB
04 - API
Interaktivni dokumentace je na /apps/<app-id>/docs. Tenhle soubor popisuje to,
co ze Swaggeru neni videt.
Endpointy
Verejne:
| Metoda | Cesta | Popis |
|---|---|---|
| GET | /health |
liveness, nezavisi na databazi |
| GET | /health/ready |
readiness, 503 pri nedostupne databazi |
| GET | /docs |
Swagger UI |
| GET | /openapi.json |
OpenAPI definice |
| POST | /api/auth/login |
prihlaseni, vraci JWT |
| POST | /api/contact |
poptavka z webu |
| POST | /webhook/:token |
prijem dat do automatizace |
| POST | /webhook/ticket/:token |
prijem udalosti do ticketu |
| GET | /webhook/ticket/:token |
napoveda k prijmu |
Vyzaduji Authorization: Bearer <token>:
| Metoda | Cesta |
|---|---|
| GET | /api/auth/me |
| POST | /api/auth/logout |
| GET | /api/dashboard/access |
| GET | /api/dashboard/widgets |
| GET | /api/dashboard/layout |
| PUT | /api/dashboard/layout |
| DELETE | /api/dashboard/layout |
| GET | /api/dashboard/summary |
| GET | /api/dashboard/people |
| GET | /api/dashboard/people/:id |
| GET | /api/dashboard/intake |
| POST | /api/dashboard/intake/regenerate |
| GET | /api/dashboard/settings/actions/:id/scope |
| GET | /api/dashboard/notifications |
| POST | /api/dashboard/notifications/read |
| GET | /api/dashboard/runs |
| GET | /api/dashboard/tickets |
| GET | /api/dashboard/tickets/workload |
| GET | /api/dashboard/tickets/:id |
| POST | /api/dashboard/tickets/:id/assign |
| POST | /api/dashboard/tickets/:id/status |
| POST | /api/dashboard/tickets/:id/comment |
| POST | /api/dashboard/tickets/:id/type |
| POST | /api/dashboard/tickets/:id/tags |
| POST | /api/dashboard/tickets/:id/group |
| POST | /api/dashboard/tickets/:id/claim |
| GET | /api/dashboard/tickets/:id/actions |
| POST | /api/dashboard/tickets/:id/actions/:actionId |
| GET | /api/dashboard/invites |
| POST | /api/dashboard/invites |
| DELETE | /api/dashboard/invites/:id |
| GET | /api/invites/:kod |
| POST | /api/invites/:kod/accept |
| GET | /api/dashboard/incidents |
| GET | /api/dashboard/storage |
| GET | /api/dashboard/services |
| GET | /api/dashboard/connectors/services |
| GET | /api/dashboard/connectors |
| POST | /api/dashboard/connectors |
| GET | /api/dashboard/connectors/:id |
| PATCH | /api/dashboard/connectors/:id |
| DELETE | /api/dashboard/connectors/:id |
| POST | /api/dashboard/connectors/:id/test |
| GET | /api/dashboard/scripts |
| GET | /api/dashboard/scripts/:id |
| PUT | /api/dashboard/scripts/:id |
| POST | /api/dashboard/scripts/:id/test |
| POST | /api/dashboard/scripts/reload |
| GET | /api/dashboard/stream |
| GET | /api/dashboard/automations |
| POST | /api/dashboard/automations |
| GET | /api/dashboard/automations/:id |
| PUT | /api/dashboard/automations/:id |
| DELETE | /api/dashboard/automations/:id |
| POST | /api/dashboard/automations/:id/webhook/regenerate |
| POST | /api/dashboard/widget-data |
| GET | /api/dashboard/widget-data/options |
| GET | /api/dashboard/settings/catalog |
| GET | /api/dashboard/settings/features |
| GET | /api/dashboard/settings/people-overview |
| GET | /api/dashboard/settings/users-overview |
| PATCH | /api/dashboard/settings/users/:id/password |
| POST | /api/admin/impersonate |
| POST | /api/admin/impersonate/stop |
| GET | /api/admin/impersonate/candidates |
| GET | /api/admin/audit |
Sprava zaznamu ma u kazde entity stejnou petici (seznam, detail, vytvoreni,
uprava, mazani) na /api/dashboard/settings/<entita>, protoze ji dela jedna
fabrika (src/routes/crud.ts):
tenants, users, roles, people, groups, ticket-types, actions,
widgets, features.
Format chyb
Jednotny pro cele API:
{ "error": "validation_error", "message": "Zadejte platny e-mail." }
| HTTP | error |
Kdy |
|---|---|---|
| 400 | validation_error |
vstup neprosel schematem |
| 401 | unauthorized |
chybi nebo neplatny token |
| 401 | invalid_credentials |
spatny e-mail nebo heslo |
| 403 | forbidden |
nedostatecna role |
| 404 | not_found |
zaznam nebo endpoint neexistuje |
| 409 | ruzne | operace nedava v danem stavu smysl |
| 500 | internal_error |
neodchycena chyba, detail jen mimo produkci |
message je vzdy cesky a je urcena k zobrazeni uzivateli.
Autentizace
Hesla se hashuji bcryptem, plaintext se nikde neuklada. Login vraci JWT
podepsany JWT_SECRET s platnosti JWT_EXPIRES_IN.
Spatne heslo i neexistujici e-mail vraci stejnou odpoved, aby se neprozradilo, ktere ucty existuji. Pokus se loguje bez hesla.
Token si drzi klient v localStorage. Pro produkci je cilovy stav httpOnly
cookie se Secure a SameSite plus CSRF token.
Zivy stream
GET /api/dashboard/stream je Server-Sent Events. Po pripojeni posle potvrzeni
a poslednich par udalosti, pak uz jen nove. Kazdych 25 sekund jde komentarovy
radek, aby spojeni neuspalo proxy.
Typy udalosti: ticket.created, ticket.updated, ticket.assigned,
ticket.resolved, incident.started, incident.updated, incident.resolved,
automation.created, automation.updated, automation.deleted,
automation.run, webhook.received.
Klient se pripojuje pres fetch s hlavickou Authorization, ne pres EventSource.
Duvod je v 03-architektura-a-mapa-kodu.md.
Webhook
Verejny endpoint bez prihlaseni. Autorizuje neuhodnutelny token v adrese,
32 znaku z randomBytes(24) v base64url.
Token generuje vyhradne server. Hodnota webhookToken poslana klientem se
ignoruje, jinak by si sel nastavit predvidatelnou adresu.
| Situace | Odpoved |
|---|---|
| vse v poradku | 202 |
| neznamy token | 404 |
| automatizace je pozastavena | 409 |
| chybi povinny parametr, spatny typ | 400 |
Parametry navic se neodmitaji, jen loguji. Odesilatele bezne posilaji i vlastni data a odmitat je by rozbijelo integrace.
curl -X POST https://services.csbot.cz/apps/<app-id>/webhook/<token> \
-H "Content-Type: application/json" \
-d '{"customer":"Nordis","score":18}'
Prototyp pozadavek prijme, zvaliduje a zapocita do metrik, ale strom akci nevykona - runtime neexistuje.
Firmy a pohledy
Prava popisuje 07-firmy-a-prava.md, tady jen API.
Endpointy dashboardu berou scope (all, tenant, mine) a tenantId.
GET /api/dashboard/access rekne, co uzivatel smi, aby to klient nedovozoval.
Pozadavek na pohled nebo firmu bez opravneni vraci 403 nebo 404, nikdy tise zuzeny vysledek. Uzivatel nesmi koukat na cizi cisla v domneni, ze jsou spravna.
Tickety
Popis modelu je v 06-tickety.md, tady jen to, co se tyka API.
GET /api/dashboard/tickets bere navic filtry assignee, status, channel.
U assignee je zvlastni hodnota unassigned pro frontu bez resitele.
U pohledu mine se assignee ignoruje, pohled je silnejsi. Neznama hodnota
filtru se zaloguje a ignoruje - je lepsi ukazat vic ticketu nez prazdny seznam
bez vysvetleni.
Odpoved nese vedle items jeste meId. Klient podle nej pozna, ktere tickety
jsou jeho, a jestli ma vubec smysl nabizet filtr "moje".
Filtrovani dela server, ne klient. Seznam a prehled vytizeni tak nikdy neukazuji jina cisla. Vyhledavaci pole v portalu je jina vec - to jen dohledava v uz nactenem seznamu.
GET /api/dashboard/tickets/:id vraci navic trace, tedy log prubehu vcetne
toho, co ktera volana sluzba vratila.
POST /api/dashboard/tickets/:id/assign s telem {"assigneeId": null} vrati
ticket do fronty. Neznamy resitel vraci 404, ne tiche odpojeni.
POST /api/dashboard/tickets/:id/claim je prevzeti prace, ne prehozeni:
volajici si bere ticket sam a telo je prazdne. Smi to u ticketu bez resitele
a u ticketu ve skupine, ve ktere je. Kdyz uz ticket nekdo resi, vraci 409, resp.
403 u cizi skupiny - vzit nekomu rozdelanou praci je jine rozhodnuti a chce to
pravo ticket.assign.others. Kdo neni vedeny jako resitel, dostane 400.
Pozvanky do firmy
/api/invites/:kod je verejne, protoze kdo prijde za odkazem, jeste ucet
mit nemusi. Autorizuje kod v adrese, proto je nahodny a dlouhy - stejne jako
u webhooku. Odpoved je zamerne uzka: nazev firmy, jestli pozvanka plati, a kdyz
uz adresu zname, tak ji, at ji clovek nemusi psat. Nic o tom, kdo ve firme je.
POST /api/invites/:kod/accept s telem {"name", "email", "password"}:
| Situace | Co se stane |
|---|---|
| ucet neexistuje | zalozi se a pripoji k firme |
| ucet existuje, heslo sedi | jen se pripoji k firme |
| ucet existuje, heslo nesedi | 401 |
| pozvanka je na jinou adresu | 403 |
| pozvanka uz byla pouzita nebo vyprsela | 409 s konkretnim duvodem |
Overeni hesla u existujiciho uctu neni formalita: bez nej by kdokoliv s odkazem pripojil cizi adresu ke sve firme a videl by jeji data.
Sprava pozvanek (/api/dashboard/invites) chce pravo user.manage. Seznam
vraci u kazde pozvanky celou adresu vcetne prefixu proxy, aby slo rovnou
kopirovat - relativni cesta se do zpravy vlepit neda.
Akce na ticketu
Popis modelu je v 09-navrh-rozsireni.md.
GET /api/dashboard/tickets/:id/actions vraci jen akce, ktere v teto situaci
opravdu jdou spustit: sedi typ nebo tag, projdou podminky a volajici na ne ma
pravo. Klient si nefiltruje nic - jinak by se to pocitalo na dvou mistech
a jednou se to rozejde.
POST /api/dashboard/tickets/:id/actions/:actionId vraci 200 i kdyz akce
selhala. Selhani akce neni chyba API. V odpovedi je ok, summary, detail
s celym chybovym hlasenim a durationMs. Cely prubeh se zapise do logu ticketu.
Vestavene akce (type, tags, group, assign, status, comment) jsou
zvlast: meni ticket sam, ne cizi sluzbu, a kazda ma vlastni pravo.
Prijem udalosti do ticketu
Popis modelu je v 18-ticketovaci-system.md.
POST /webhook/ticket/:token je verejny, autorizuje token firmy v adrese.
Vraci 201 kdyz ticket vznikl, 200 kdyz se udalost navesila na existujici:
{ "ok": true, "created": false, "ticketId": "TK-4822", "externalId": "3", "eventId": "tev_2" }
externalId je unikatni v ramci firmy. Token urcuje firmu, takze dve firmy
mohou obe poslat objednavku cislo 3 a nedojde ke smichani. Cislo i retezec jsou
tentyz klic.
Neznamy typeId se zahodi a zaloguje, ticket vznikne bez typu. Odmitnout celou
udalost kvuli jednomu poli by znamenalo ztratu dat.
Fronta behu
Popis je v 20-fronta-a-runtime.md.
POST /webhook/:token vraci 202, ne 200: data jsme prevzali a strom se
vykona na pozadi. Vysledek se hleda v GET /api/dashboard/runs nebo v logu
ticketu. Cekat na cizi sluzbu v requestu nejde - za jeji rychlost nerucime
a odesilateli by vyprsel timeout.
GET /webhook/:token vraci kontrakt: co se v tele ceka, na jakych cestach
a ukazku. Bez toho by musel ten, kdo webhook zapojuje, hadat.
GET /api/dashboard/runs ma u kazdeho behu cele chybove hlaseni, pocet pokusu
a kdy se to zkusi znovu.
Prava a navigace
GET /api/dashboard/access vraci permissions (efektivni prava po slouceni
roli), nav (zalozky, ktere ma volajici videt) a platformAdmin. Klient podle
toho kresli, ale nic si nedovozuje - kdo co smi, rozhoduje server u kazdeho
requestu znovu.
GET /api/dashboard/settings/catalog vraci katalog prav a modulu, aby formular
role nemel seznam prav napsany v kodu klienta.
Prepnuti na jiny ucet
POST /api/admin/impersonate vraci novy token s narokem act (kdo se za koho
vydava) a writes. Bez writes middleware odmitne cokoliv jineho nez GET
s 403. Kazde prepnuti i ukonceni je v auditu vcetne toho, kdo to byl doopravdy.
Svuj puvodni token si klient odklada do sessionStorage, server o nem nic nevi.
Sluzby a konektory
Popis modelu je v 12-sluzby-a-konektory.md, tady jen API.
Sluzba je to, co umime. Konektor je napojeni jedne firmy vcetne jejich pristupovych udaju.
Hodnoty pristupovych udaju se nikdy nevraci, jen filled a missing.
V PATCH staci poslat jen to, co se meni: prazdny retezec hodnotu smaze,
chybejici klic ji nechava.
Sluzba, kterou uzivatel nevidi, se nevraci vubec, ne se stavem 403.
POST /connectors/:id/test vraci 200 i pri neuspechu. checked rika, co se
vlastne overilo - u sluzby bez verifyPath jen dostupnost, ne udaje.
Skripty konektoru
Popis modelu je v 11-skripty-konektoru.md, tady jen API.
Cteni smi kazdy prihlaseny, protoze builder potrebuje vedet, co skript umi. Uprava, zkusebni spusteni a vynucene nacteni smi jen spravce platformy - uprava skriptu meni chovani vseho, co ho pouziva.
GET /api/dashboard/scripts vraci vedle manifestu i problems s rozbitymi
skripty a connections se stavem napojeni. Hodnoty pristupovych udaju se
nevraci nikdy, jen jmena chybejicich environment variables.
PUT /api/dashboard/scripts/:id kod nejdriv nacte a overi a az pak prepise
soubor. Rozbita uprava vraci 400 s issues a puvodni skript dal funguje.
POST /api/dashboard/scripts/:id/test vola opravdovou sluzbu. Chyba skriptu
neni chyba API, vraci se 200 a popis v error vcetne toho, jestli ma smysl
zkusit to znovu.
Pri pridani endpointu
Soucasne aktualizovat src/openapi.ts a tenhle soubor. Swagger musi odpovidat
skutecnemu chovani aplikace, jinak je horsi nez zadny.