MCP je standard, ale prihlaseni k nemu ne. Oficialni specifikace stoji na OAuth 2.1 a objevovani autorizacniho serveru pres .well-known. EasyWeb (Centaur) ma prihlaseni vlastni: POST /login s HTTP Basic vrati trojici tokenu a obnovuje se vlastnimi endpointy. Zadny OAuth, zadne .well-known, jina verze protokolu, zadne SSE ani hlavicka sezeni. Proto dve sluzby, ne jedna s prepinacem: firma pri zakladani konektoru vyplnuje neco jineho. U obecne ID a tajemstvi aplikace nebo hotovy token, u EasyWebu jmeno, heslo a nazev zarizeni. Slucovat to by znamenalo formular, kde je pulka poli vzdycky k nicemu, a hadani, ktera pulka to prave je. Obecna sluzba zustava plnohodnotna. Vlastni server je duvod pridat sluzbu, ne duvod zavrit dvere ostatnim. Pribylo: - src/mcp/dialect.ts - rozdily obou serveru na jednom miste: prihlaseni, verze protokolu, jestli se prijima SSE a jestli se posila Mcp-Session-Id. Rozesete po klientovi by u kazdeho dalsiho serveru pribyl dalsi if na jinem miste - sluzba MCP EasyWeb: adresa, jmeno, heslo, nazev a otisk zarizeni. Prihlasovaci adresy si portal odvodi sam, otisk doplni z ID konektoru - hotovy token u obecne sluzby. Rada verejnych serveru nic jineho nenabizi - objevovani pres WWW-Authenticate, coz specifikace ma jako povinnou cestu. Pouziva se az kdyz obvykla mista selzou, stoji to volani navic - zivotnost z tela tokenu: kdyz server expires_in ani datum neposle, cte se exp z JWT. Presne pripad EasyWebu - strankovani nastroju: nastroj s parametrem cursor dostane v builderu prepinac Nacist vsechny stranky. Kurzor je hodnota z odpovedi, takze v dobe stavby stromu ho nikdo nezna a nejde ho vyplnit dopredu. Krok pak vraci navic items, pages, pageCount a truncated. Strop je 20 stranek Opraveno: prihlaseni driv zkousela password grant a HTTP Basic proti hlavnimu endpointu. Prvni OAuth 2.1 zrusil, druhe neni nikde ve specifikaci a u EasyWebu by stejne neproslo - ten chce Basic na /login, ne na /mcp. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2421 lines
89 KiB
TypeScript
2421 lines
89 KiB
TypeScript
import { config } from './config.js';
|
|
|
|
/**
|
|
* Sprava zaznamu ma u kazde entity stejnou petici endpointu, protoze ji na
|
|
* serveru dela jedna fabrika (`routes/crud.ts`). Popisovat ji devetkrat rucne
|
|
* by znamenalo devet mist, ktere se casem rozejdou.
|
|
*/
|
|
function crudPaths(entity: {
|
|
/** Cast cesty, napr. `roles`. */
|
|
path: string;
|
|
/** Jak se o tom mluvi v popisu, napr. `roli`. */
|
|
label: string;
|
|
/** Pravo, ktere je na zapis potreba. */
|
|
permission: string;
|
|
}) {
|
|
const id = { name: 'id', in: 'path', required: true, schema: { type: 'string' } };
|
|
const body = {
|
|
required: true,
|
|
content: { 'application/json': { schema: { type: 'object' } } },
|
|
};
|
|
const record = {
|
|
description: 'Zaznam',
|
|
content: { 'application/json': { schema: { type: 'object' } } },
|
|
};
|
|
const denied = { '403': { description: `Chybi pravo ${entity.permission}` } };
|
|
const base = `/api/dashboard/settings/${entity.path}`;
|
|
|
|
return {
|
|
[base]: {
|
|
get: {
|
|
tags: ['Nastaveni'],
|
|
summary: `Seznam - ${entity.label}`,
|
|
description: 'Vraci jen zaznamy firem, do kterych volajici patri.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Seznam',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: { items: { type: 'array', items: { type: 'object' } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
...denied,
|
|
},
|
|
},
|
|
post: {
|
|
tags: ['Nastaveni'],
|
|
summary: `Vytvorit - ${entity.label}`,
|
|
security: [{ bearerAuth: [] }],
|
|
requestBody: body,
|
|
responses: { '201': record, '400': { description: 'Neplatny vstup' }, ...denied },
|
|
},
|
|
},
|
|
[`${base}/{id}`]: {
|
|
get: {
|
|
tags: ['Nastaveni'],
|
|
summary: `Detail - ${entity.label}`,
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [id],
|
|
responses: { '200': record, '404': { description: 'Neexistuje' }, ...denied },
|
|
},
|
|
patch: {
|
|
tags: ['Nastaveni'],
|
|
summary: `Upravit - ${entity.label}`,
|
|
description: 'Posilaji se jen menena pole. ID a cas vzniku se prepsat nedaji.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [id],
|
|
requestBody: body,
|
|
responses: {
|
|
'200': record,
|
|
'400': { description: 'Neplatny vstup' },
|
|
'404': { description: 'Neexistuje' },
|
|
...denied,
|
|
},
|
|
},
|
|
delete: {
|
|
tags: ['Nastaveni'],
|
|
summary: `Smazat - ${entity.label}`,
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [id],
|
|
responses: {
|
|
'204': { description: 'Smazano' },
|
|
'404': { description: 'Neexistuje' },
|
|
...denied,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
/** Entity, ktere se spravuji v Nastaveni. Jeden radek na entitu. */
|
|
const settingsEntities = [
|
|
{ path: 'tenants', label: 'firmy', permission: 'tenant.manage' },
|
|
{ path: 'users', label: 'uzivatele', permission: 'user.manage' },
|
|
{ path: 'roles', label: 'role a prava', permission: 'role.manage' },
|
|
{ path: 'people', label: 'resitele', permission: 'people.manage' },
|
|
{ path: 'groups', label: 'skupiny resitelu', permission: 'group.manage' },
|
|
{ path: 'ticket-types', label: 'typy ticketu', permission: 'ticketType.manage' },
|
|
{ path: 'actions', label: 'akce na ticketu', permission: 'action.manage' },
|
|
{ path: 'widgets', label: 'vlastni widgety', permission: 'widget.manage' },
|
|
{ path: 'features', label: 'zalozky firmy', permission: 'tenant.manage' },
|
|
];
|
|
|
|
/**
|
|
* OpenAPI popis API.
|
|
*
|
|
* `servers` MUSI obsahovat prefix reverse proxy, jinak Swagger "Try it out"
|
|
* vola endpointy na root domene a dostane 404 (viz AGENTS.md).
|
|
* Prefix se bere z ROOT_PATH, nikdy se nehardcoduje.
|
|
*/
|
|
export function buildOpenApiDocument() {
|
|
const server = config.rootPath === '' ? '/' : config.rootPath;
|
|
|
|
return {
|
|
openapi: '3.0.3',
|
|
info: {
|
|
title: 'Automia - portal a API',
|
|
version: '1.0.0',
|
|
description:
|
|
'Webova prezentace a klientsky portal. Automatizace, voiceboti, integrace, ' +
|
|
'tickety a incidenty. Aplikace bezi za reverse proxy AppFactory.',
|
|
},
|
|
servers: [{ url: server, description: 'Verejna adresa vcetne prefixu proxy' }],
|
|
tags: [
|
|
{ name: 'Provoz', description: 'Health a zakladni informace' },
|
|
{ name: 'Autentizace', description: 'Prihlaseni do portalu' },
|
|
{ name: 'Dashboard', description: 'Data klientskeho portalu' },
|
|
{ name: 'Tickety', description: 'Pozadavky, jejich resitele a log prubehu' },
|
|
{ name: 'Automatizace', description: 'Sprava automatizaci a stromu akci' },
|
|
{ name: 'Sluzby', description: 'Katalog toho, co umime napojit' },
|
|
{ name: 'Konektory', description: 'Napojeni firmy na sluzbu vcetne pristupovych udaju' },
|
|
{ name: 'Skripty', description: 'Vykonna cast sluzby: manifest, kod a zkusebni beh' },
|
|
{ name: 'Nastaveni', description: 'Firmy, lide, role a prava, typy ticketu, akce, widgety' },
|
|
{ name: 'Sprava platformy', description: 'Audit a prepnuti na jiny ucet' },
|
|
{ name: 'Webhook', description: 'Verejny prijem dat do automatizace' },
|
|
{ name: 'Kontakt', description: 'Poptavkovy formular z webu' },
|
|
],
|
|
components: {
|
|
securitySchemes: {
|
|
bearerAuth: {
|
|
type: 'http',
|
|
scheme: 'bearer',
|
|
bearerFormat: 'JWT',
|
|
description: 'Token z POST /api/auth/login. Vlozte samotny token bez slova Bearer.',
|
|
},
|
|
},
|
|
schemas: {
|
|
Error: {
|
|
type: 'object',
|
|
properties: {
|
|
error: { type: 'string', example: 'validation_error' },
|
|
message: { type: 'string', example: 'Zadejte platny e-mail.' },
|
|
},
|
|
},
|
|
User: {
|
|
type: 'object',
|
|
description:
|
|
'Uzivatel muze patrit do vic firem. Role je vzdy az uvnitr firmy, ' +
|
|
'pristup napric firmami je zvlast jako platformAdmin.',
|
|
properties: {
|
|
id: { type: 'string', example: 'usr_1' },
|
|
email: { type: 'string', example: 'admin@automia.cz' },
|
|
name: { type: 'string', example: 'Jiri Uhlir' },
|
|
platformAdmin: {
|
|
type: 'boolean',
|
|
description: 'Vidi napric vsemi firmami a muze mezi nimi prepinat.',
|
|
},
|
|
memberships: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
tenantId: { type: 'string', example: 'tnt_automia' },
|
|
role: { type: 'string', enum: ['admin', 'agent'] },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Access: {
|
|
type: 'object',
|
|
description: 'Co uzivatel smi. Klient podle toho kresli prepinac pohledu.',
|
|
properties: {
|
|
scopes: {
|
|
type: 'array',
|
|
items: { type: 'string', enum: ['all', 'tenant', 'mine'] },
|
|
},
|
|
tenants: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', example: 'tnt_automia' },
|
|
name: { type: 'string', example: 'Automia' },
|
|
},
|
|
},
|
|
},
|
|
defaultTenantId: { type: 'string', nullable: true },
|
|
canAssignOthers: {
|
|
type: 'boolean',
|
|
description: 'Smi prehazovat tickety mezi lidmi, ne jen brat na sebe.',
|
|
},
|
|
personId: { type: 'string', nullable: true },
|
|
},
|
|
},
|
|
Connector: {
|
|
type: 'object',
|
|
description:
|
|
'Napojeni firmy na jednu sluzbu. Hodnoty pristupovych udaju tady zamerne ' +
|
|
'nejsou a nikdy nebudou - secrets se z beznych endpointu nevraci.',
|
|
properties: {
|
|
id: { type: 'string', example: 'con_1a2b3c4d' },
|
|
tenantId: { type: 'string', example: 'tnt_automia' },
|
|
serviceId: { type: 'string', example: 'idoklad' },
|
|
name: { type: 'string', example: 'iDoklad Automia' },
|
|
baseUrl: { type: 'string', nullable: true },
|
|
enabled: { type: 'boolean' },
|
|
status: { type: 'string', enum: ['untested', 'ok', 'error'] },
|
|
lastCheckAt: { type: 'string', format: 'date-time', nullable: true },
|
|
lastError: { type: 'string', nullable: true },
|
|
checkCount: {
|
|
type: 'integer',
|
|
description:
|
|
'Kolik zaznamu o overeni je v historii. Samotna historie se cte pres ' +
|
|
'/api/dashboard/connectors/{id}/checks - v seznamu by to byla tela odpovedi navic.',
|
|
},
|
|
isDefault: {
|
|
type: 'boolean',
|
|
description: 'Krok stromu bez vybraneho konektoru pouzije tenhle.',
|
|
},
|
|
filled: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'ID poli, ktera jsou vyplnena. Hodnoty se nevraci.',
|
|
},
|
|
missing: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'ID povinnych poli, ktera chybi.',
|
|
},
|
|
config: {
|
|
type: 'object',
|
|
additionalProperties: { type: 'string' },
|
|
description: 'Necitliva nastaveni. Tajna pole tu nejsou vubec.',
|
|
},
|
|
ready: { type: 'boolean' },
|
|
},
|
|
},
|
|
ScriptField: {
|
|
type: 'object',
|
|
description:
|
|
'Parametr skriptu. Stejny tvar pro vstup i vystup - kontrola je pak ' +
|
|
'jedna funkce, ne dve skoro stejne.',
|
|
required: ['id', 'label', 'type', 'required'],
|
|
properties: {
|
|
id: {
|
|
type: 'string',
|
|
example: 'invoiceId',
|
|
description: 'Pouziva se v sablonach jako {{invoiceId}}.',
|
|
},
|
|
label: { type: 'string', example: 'ID faktury v iDokladu' },
|
|
type: { type: 'string', enum: ['string', 'number', 'boolean', 'date'] },
|
|
required: { type: 'boolean' },
|
|
hint: { type: 'string' },
|
|
options: {
|
|
type: 'array',
|
|
description: 'Vyber z hodnot. Jina hodnota neprojde kontrolou.',
|
|
items: {
|
|
type: 'object',
|
|
properties: { value: { type: 'string' }, label: { type: 'string' } },
|
|
},
|
|
},
|
|
pattern: { type: 'string', description: 'Jen u typu string.' },
|
|
multiline: { type: 'boolean', description: 'Jen u typu string.' },
|
|
default: { description: 'Dosadi se, kdyz hodnota chybi a parametr neni povinny.' },
|
|
},
|
|
},
|
|
ScriptManifest: {
|
|
type: 'object',
|
|
description: 'Co skript umi. Podle nej s nim umi pracovat strom automatizace.',
|
|
properties: {
|
|
id: {
|
|
type: 'string',
|
|
example: 'idoklad.get-issued-invoice',
|
|
description: 'Tvar sluzba.operace. Nazev souboru musi byt <id>.js.',
|
|
},
|
|
serviceId: { type: 'string', example: 'idoklad' },
|
|
serviceName: { type: 'string', example: 'iDoklad' },
|
|
operationId: { type: 'string', example: 'get-issued-invoice' },
|
|
name: { type: 'string', example: 'Získat vydanou fakturu' },
|
|
description: { type: 'string' },
|
|
inputs: { type: 'array', items: { $ref: '#/components/schemas/ScriptField' } },
|
|
outputs: { type: 'array', items: { $ref: '#/components/schemas/ScriptField' } },
|
|
timeoutMs: { type: 'integer', example: 15000 },
|
|
},
|
|
},
|
|
ScriptProblem: {
|
|
type: 'object',
|
|
description:
|
|
'Rozbity skript. Nesmi shodit ostatni ani tise zmizet, proto se vraci sem.',
|
|
properties: {
|
|
file: { type: 'string', example: 'idoklad.get-issued-invoice.js' },
|
|
scriptId: { type: 'string', nullable: true },
|
|
message: { type: 'string' },
|
|
issues: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: { field: { type: 'string' }, message: { type: 'string' } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ConnectionStatus: {
|
|
type: 'object',
|
|
description:
|
|
'Stav napojeni konektoru. Hodnoty pristupovych udaju se nevraci nikdy, ' +
|
|
'jen jmena promennych, ktere chybi.',
|
|
properties: {
|
|
connectorId: { type: 'string', example: 'idoklad' },
|
|
baseUrl: { type: 'string', example: 'https://services.csbot.cz/apps/idoklad' },
|
|
ready: { type: 'boolean' },
|
|
missing: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
example: ['IDOKLAD_CLIENT_SECRET'],
|
|
},
|
|
headers: { type: 'array', items: { type: 'string' }, example: ['X-ClientId'] },
|
|
},
|
|
},
|
|
ScriptRunResult: {
|
|
type: 'object',
|
|
description:
|
|
'Vysledek behu skriptu. `retryable` rika, jestli ma smysl zkusit to znovu - ' +
|
|
'timeout ano, spatny vstup ne.',
|
|
properties: {
|
|
ok: { type: 'boolean' },
|
|
scriptId: { type: 'string' },
|
|
outputs: {
|
|
type: 'object',
|
|
additionalProperties: true,
|
|
description: 'Prazdne, kdyz beh selhal.',
|
|
},
|
|
logs: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
at: { type: 'string', format: 'date-time' },
|
|
message: { type: 'string' },
|
|
detail: { type: 'string' },
|
|
},
|
|
},
|
|
},
|
|
durationMs: { type: 'integer' },
|
|
httpCalls: { type: 'integer' },
|
|
error: {
|
|
type: 'object',
|
|
nullable: true,
|
|
properties: {
|
|
kind: {
|
|
type: 'string',
|
|
enum: [
|
|
'not_found',
|
|
'config',
|
|
'validation',
|
|
'output',
|
|
'retryable',
|
|
'terminal',
|
|
'timeout',
|
|
'internal',
|
|
],
|
|
},
|
|
message: { type: 'string' },
|
|
retryable: { type: 'boolean' },
|
|
status: { type: 'integer' },
|
|
detail: { type: 'string' },
|
|
issues: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: { field: { type: 'string' }, message: { type: 'string' } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
LoginRequest: {
|
|
type: 'object',
|
|
required: ['email', 'password'],
|
|
properties: {
|
|
email: { type: 'string', format: 'email', example: 'admin@automia.cz' },
|
|
password: { type: 'string', format: 'password', example: 'demo1234' },
|
|
},
|
|
},
|
|
LoginResponse: {
|
|
type: 'object',
|
|
properties: {
|
|
token: { type: 'string' },
|
|
user: { $ref: '#/components/schemas/User' },
|
|
},
|
|
},
|
|
Person: {
|
|
type: 'object',
|
|
description: 'Resitel ticketu. Nemusi mit ucet v portalu, spojka je e-mail.',
|
|
properties: {
|
|
id: { type: 'string', example: 'ppl_vomacka' },
|
|
name: { type: 'string', example: 'Karel Vomacka' },
|
|
email: { type: 'string', format: 'email' },
|
|
role: { type: 'string', example: 'Servicedesk' },
|
|
capacity: {
|
|
type: 'integer',
|
|
description: 'Kolik nevyrizenych ticketu je pro nej jeste zdrava zatez.',
|
|
},
|
|
},
|
|
},
|
|
TicketCustomer: {
|
|
type: 'object',
|
|
properties: {
|
|
id: {
|
|
type: 'string',
|
|
nullable: true,
|
|
description: 'ID firmy v CRM. null = zakaznika se nepodarilo dohledat.',
|
|
example: 'crm_1042',
|
|
},
|
|
company: { type: 'string', example: 'Firma s.r.o.' },
|
|
contact: { type: 'string', example: 'Petra Klientova' },
|
|
reply: {
|
|
type: 'string',
|
|
description: 'Adresa nebo cislo, odkud pozadavek prisel a kam se odpovida.',
|
|
},
|
|
},
|
|
},
|
|
Ticket: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', example: 'TK-4821' },
|
|
subject: { type: 'string' },
|
|
body: {
|
|
type: 'string',
|
|
description:
|
|
'Cely text pozadavku. Prazdny retezec = krok "Zalozit ticket" obsah nenaplnil.',
|
|
},
|
|
sourceRef: {
|
|
type: 'string',
|
|
nullable: true,
|
|
description: 'Odkaz na zdrojovou zpravu u poskytovatele.',
|
|
example: 'wamid.HBgLNDIwNzc0OTAyMzMx',
|
|
},
|
|
channel: {
|
|
type: 'string',
|
|
enum: ['whatsapp', 'facebook', 'instagram', 'email', 'voice', 'form', 'portal'],
|
|
description: 'Odkud pozadavek prisel.',
|
|
},
|
|
customer: { $ref: '#/components/schemas/TicketCustomer' },
|
|
status: { type: 'string', enum: ['new', 'open', 'waiting', 'resolved'] },
|
|
priority: { type: 'string', enum: ['low', 'normal', 'high', 'critical'] },
|
|
assignee: {
|
|
type: 'object',
|
|
nullable: true,
|
|
description: 'Kdo ma ticket u sebe. null = ceka ve fronte.',
|
|
properties: {
|
|
id: { type: 'string', example: 'ppl_vomacka' },
|
|
name: { type: 'string', example: 'Karel Vomacka' },
|
|
},
|
|
},
|
|
automationId: {
|
|
type: 'string',
|
|
nullable: true,
|
|
description: 'Automatizace, ktera ticket zalozila. null = zalozeno rucne.',
|
|
},
|
|
createdAt: { type: 'string', format: 'date-time' },
|
|
updatedAt: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
TicketTraceEntry: {
|
|
type: 'object',
|
|
description:
|
|
'Jeden radek logu ticketu. Strom se sklada pres parentId - vetev podminky ' +
|
|
'visi na zaznamu te podminky.',
|
|
properties: {
|
|
id: { type: 'string', example: 'tr_12' },
|
|
parentId: {
|
|
type: 'string',
|
|
nullable: true,
|
|
description: 'null = zaznam v hlavni sekvenci.',
|
|
},
|
|
kind: { type: 'string', enum: ['trigger', 'action', 'condition', 'note'] },
|
|
connectorId: { type: 'string', nullable: true, example: 'raynet' },
|
|
operationId: { type: 'string', nullable: true, example: 'upsert-contact' },
|
|
label: { type: 'string' },
|
|
status: { type: 'string', enum: ['ok', 'error', 'skipped', 'info'] },
|
|
response: {
|
|
type: 'string',
|
|
nullable: true,
|
|
description: 'Co sluzba vratila. Kvuli tomuhle log existuje.',
|
|
},
|
|
durationMs: { type: 'integer', nullable: true },
|
|
at: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
TicketDetail: {
|
|
allOf: [
|
|
{ $ref: '#/components/schemas/Ticket' },
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
trace: {
|
|
type: 'array',
|
|
items: { $ref: '#/components/schemas/TicketTraceEntry' },
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
Workload: {
|
|
type: 'object',
|
|
description: 'Prehled nad firmou - kdo ma kolik ticketu u sebe.',
|
|
properties: {
|
|
rows: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
person: { $ref: '#/components/schemas/Person' },
|
|
open: { type: 'integer', description: 'Nevyresene tickety.' },
|
|
total: { type: 'integer' },
|
|
critical: { type: 'integer' },
|
|
oldestOpenAt: { type: 'string', format: 'date-time', nullable: true },
|
|
overloaded: { type: 'boolean' },
|
|
},
|
|
},
|
|
},
|
|
unassigned: { type: 'integer', description: 'Nevyresene tickety bez resitele.' },
|
|
openTotal: { type: 'integer' },
|
|
},
|
|
},
|
|
Incident: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', example: 'INC-231' },
|
|
title: { type: 'string' },
|
|
service: { type: 'string' },
|
|
severity: { type: 'string', enum: ['sev1', 'sev2', 'sev3'] },
|
|
status: {
|
|
type: 'string',
|
|
enum: ['investigating', 'identified', 'monitoring', 'resolved'],
|
|
},
|
|
startedAt: { type: 'string', format: 'date-time' },
|
|
resolvedAt: { type: 'string', format: 'date-time', nullable: true },
|
|
},
|
|
},
|
|
TriggerField: {
|
|
type: 'object',
|
|
required: ['id', 'name', 'type', 'required'],
|
|
properties: {
|
|
id: { type: 'string', example: 'f_42' },
|
|
name: {
|
|
type: 'string',
|
|
example: 'score',
|
|
description: 'Klic v prichozich datech, pismena, cislice a podtrzitko.',
|
|
},
|
|
type: { type: 'string', enum: ['string', 'number', 'boolean', 'date'] },
|
|
required: { type: 'boolean' },
|
|
},
|
|
},
|
|
FlowStep: {
|
|
type: 'object',
|
|
description:
|
|
'Krok stromu. Bud akce nad konektorem, nebo podminka se dvema vetvemi.',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
kind: { type: 'string', enum: ['action', 'condition'] },
|
|
connectorId: { type: 'string', example: 'email' },
|
|
operationId: { type: 'string', example: 'send' },
|
|
inputs: {
|
|
type: 'object',
|
|
additionalProperties: { type: 'string' },
|
|
description:
|
|
'Nastaveni akce. Klic je ID pole z katalogu, hodnota je sablona - ' +
|
|
'{{nazev}} se nahradi parametrem spoustece. Neznamy klic vraci 400.',
|
|
example: { subject: 'Reklamace od {{profileName}}', body: '{{text}}' },
|
|
},
|
|
fieldId: { type: 'string', example: 'f_42' },
|
|
operator: {
|
|
type: 'string',
|
|
enum: [
|
|
'eq',
|
|
'neq',
|
|
'gt',
|
|
'gte',
|
|
'lt',
|
|
'lte',
|
|
'contains',
|
|
'startsWith',
|
|
'isEmpty',
|
|
'isNotEmpty',
|
|
'isTrue',
|
|
'isFalse',
|
|
],
|
|
},
|
|
value: { type: 'string', example: '15' },
|
|
yes: { type: 'array', items: { $ref: '#/components/schemas/FlowStep' } },
|
|
no: { type: 'array', items: { $ref: '#/components/schemas/FlowStep' } },
|
|
},
|
|
},
|
|
AutomationFlow: {
|
|
type: 'object',
|
|
properties: {
|
|
trigger: {
|
|
type: 'object',
|
|
nullable: true,
|
|
properties: {
|
|
connectorId: { type: 'string', example: 'webhook' },
|
|
operationId: { type: 'string', example: 'received' },
|
|
fields: {
|
|
type: 'array',
|
|
items: { $ref: '#/components/schemas/TriggerField' },
|
|
},
|
|
webhookToken: {
|
|
type: 'string',
|
|
readOnly: true,
|
|
description: 'Generuje vyhradne server, hodnota od klienta se ignoruje.',
|
|
},
|
|
},
|
|
},
|
|
steps: { type: 'array', items: { $ref: '#/components/schemas/FlowStep' } },
|
|
},
|
|
},
|
|
Automation: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', example: 'AUT-01' },
|
|
name: { type: 'string' },
|
|
kind: { type: 'string', enum: ['workflow', 'voicebot', 'integrace', 'report'] },
|
|
enabled: { type: 'boolean' },
|
|
runsToday: { type: 'integer' },
|
|
runsYesterday: { type: 'integer' },
|
|
runsTotal: { type: 'integer' },
|
|
successRate: { type: 'number' },
|
|
avgDurationMs: { type: 'integer' },
|
|
lastRunAt: { type: 'string', format: 'date-time' },
|
|
stepCount: { type: 'integer' },
|
|
configured: { type: 'boolean' },
|
|
issues: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'Co chybi k zapnuti. Prazdne pole znamena hotovo.',
|
|
},
|
|
},
|
|
},
|
|
AutomationDetail: {
|
|
allOf: [
|
|
{ $ref: '#/components/schemas/Automation' },
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
flow: { $ref: '#/components/schemas/AutomationFlow' },
|
|
createdAt: { type: 'string', format: 'date-time' },
|
|
updatedAt: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
paths: {
|
|
// Petice endpointu za kazdou entitu v Nastaveni, viz `crudPaths` vyse.
|
|
...settingsEntities.reduce(
|
|
(all, entity) => ({ ...all, ...crudPaths(entity) }),
|
|
{} as Record<string, unknown>,
|
|
),
|
|
'/health': {
|
|
get: {
|
|
tags: ['Provoz'],
|
|
summary: 'Health check',
|
|
description: 'Vraci 200, pokud je aplikace schopna prijimat provoz.',
|
|
responses: {
|
|
'200': {
|
|
description: 'Aplikace bezi',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
status: { type: 'string', example: 'ok' },
|
|
uptimeSec: { type: 'integer', example: 42 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/auth/login': {
|
|
post: {
|
|
tags: ['Autentizace'],
|
|
summary: 'Prihlaseni',
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/LoginRequest' } },
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Token a udaje uzivatele',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/LoginResponse' } },
|
|
},
|
|
},
|
|
'401': {
|
|
description: 'Nespravny e-mail nebo heslo',
|
|
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/auth/me': {
|
|
get: {
|
|
tags: ['Autentizace'],
|
|
summary: 'Prihlaseny uzivatel',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Udaje uzivatele',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: { user: { $ref: '#/components/schemas/User' } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'401': { description: 'Chybi nebo neplatny token' },
|
|
},
|
|
},
|
|
},
|
|
'/api/auth/logout': {
|
|
post: {
|
|
tags: ['Autentizace'],
|
|
summary: 'Odhlaseni',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: { '204': { description: 'Odhlaseno' } },
|
|
},
|
|
},
|
|
'/api/dashboard/summary': {
|
|
get: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Souhrn pro prehled',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: { '200': { description: 'Souhrnne metriky a casova rada' } },
|
|
},
|
|
},
|
|
'/api/dashboard/widgets': {
|
|
get: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Katalog widgetu prehledu',
|
|
description: 'Co jde polozit na dashboard vcetne povolenych sirek.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: { '200': { description: 'Widgety' } },
|
|
},
|
|
},
|
|
'/api/dashboard/layout': {
|
|
get: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Rozlozeni dashboardu',
|
|
description:
|
|
'Uklada se pro dvojici uzivatel a firma. `custom: false` znamena, ' +
|
|
'ze uzivatel kouka na vychozi rozlozeni.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{
|
|
name: 'tenantId',
|
|
in: 'query',
|
|
schema: { type: 'string' },
|
|
description: 'Firma. Bez ni se pouzije prvni, do ktere uzivatel patri.',
|
|
},
|
|
],
|
|
responses: {
|
|
'200': { description: 'Rozlozeni' },
|
|
'403': { description: 'Ucet nepatri do zadne firmy' },
|
|
'404': { description: 'Firma neexistuje, nebo do ni uzivatel nepatri' },
|
|
},
|
|
},
|
|
put: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Ulozit rozlozeni',
|
|
description: 'Overuje se proti katalogu. Neznamy widget nebo sirka vraci 400.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'tenantId', in: 'query', schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['items'],
|
|
properties: {
|
|
items: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
required: ['id', 'widgetId', 'size'],
|
|
properties: {
|
|
id: { type: 'string', example: 'w1' },
|
|
widgetId: { type: 'string', example: 'stat.openTickets' },
|
|
size: { type: 'string', enum: ['third', 'half', 'full'] },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': { description: 'Ulozeno' },
|
|
'400': { description: 'Neplatne rozlozeni' },
|
|
},
|
|
},
|
|
delete: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Vratit na vychozi rozlozeni',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'tenantId', in: 'query', schema: { type: 'string' } }],
|
|
responses: { '200': { description: 'Vychozi rozlozeni' } },
|
|
},
|
|
},
|
|
'/api/dashboard/access': {
|
|
get: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Co uzivatel smi videt',
|
|
description:
|
|
'Povolene pohledy, firmy k prepinani a prava. Klient si to nesmi dovozovat sam.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Opravneni',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/Access' } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/people': {
|
|
get: {
|
|
tags: ['Tickety'],
|
|
summary: 'Seznam resitelu',
|
|
description:
|
|
'Lide, na ktere jde ticket priradit. `meId` je resitel odpovidajici ' +
|
|
'prihlasenemu uzivateli, nebo null, pokud zadny neni.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Resitele',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
items: { type: 'array', items: { $ref: '#/components/schemas/Person' } },
|
|
meId: { type: 'string', nullable: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets': {
|
|
get: {
|
|
tags: ['Tickety'],
|
|
summary: 'Seznam ticketu',
|
|
description: 'Neznama hodnota filtru se ignoruje a zaloguje, seznam se nezuzi.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{
|
|
name: 'scope',
|
|
in: 'query',
|
|
schema: { type: 'string', enum: ['all', 'tenant', 'mine'] },
|
|
description:
|
|
'`all` napric firmami (jen platformni admin), `tenant` cela firma, ' +
|
|
'`mine` jen moje tickety. Nepovoleny pohled vraci 403, nikdy se tise nezuzi.',
|
|
},
|
|
{
|
|
name: 'tenantId',
|
|
in: 'query',
|
|
schema: { type: 'string' },
|
|
description: 'Firma u pohledu `tenant` a `mine`. Bez clenstvi vraci 404.',
|
|
example: 'tnt_automia',
|
|
},
|
|
{
|
|
name: 'assignee',
|
|
in: 'query',
|
|
schema: { type: 'string' },
|
|
description:
|
|
'ID resitele nebo `unassigned` pro frontu. U pohledu `mine` se ignoruje.',
|
|
},
|
|
{
|
|
name: 'status',
|
|
in: 'query',
|
|
schema: { type: 'string', enum: ['new', 'open', 'waiting', 'resolved'] },
|
|
},
|
|
{
|
|
name: 'channel',
|
|
in: 'query',
|
|
schema: { type: 'string', enum: ['whatsapp', 'email', 'voice', 'form', 'portal'] },
|
|
},
|
|
],
|
|
responses: {
|
|
'200': {
|
|
description: 'Tickety',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
items: { type: 'array', items: { $ref: '#/components/schemas/Ticket' } },
|
|
meId: { type: 'string', nullable: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets/workload': {
|
|
get: {
|
|
tags: ['Tickety'],
|
|
summary: 'Kdo co ma u sebe',
|
|
description: 'Prehled zateze pres cely tym vcetne poctu ticketu ve fronte.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Vytizeni resitelu',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/Workload' } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets/{id}': {
|
|
get: {
|
|
tags: ['Tickety'],
|
|
summary: 'Detail ticketu vcetne logu',
|
|
description: 'Log obsahuje i to, co ktera volana sluzba vratila.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{
|
|
name: 'id',
|
|
in: 'path',
|
|
required: true,
|
|
schema: { type: 'string' },
|
|
example: 'TK-4821',
|
|
},
|
|
],
|
|
responses: {
|
|
'200': {
|
|
description: 'Detail',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/TicketDetail' } },
|
|
},
|
|
},
|
|
'404': { description: 'Neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets/{id}/assign': {
|
|
post: {
|
|
tags: ['Tickety'],
|
|
summary: 'Priradit resitele',
|
|
description: 'Poslete null pro vraceni ticketu do fronty.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['assigneeId'],
|
|
properties: {
|
|
assigneeId: { type: 'string', nullable: true, example: 'ppl_vomacka' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Prirazeno',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/Ticket' } },
|
|
},
|
|
},
|
|
'400': { description: 'Chybi assigneeId' },
|
|
'404': { description: 'Ticket nebo resitel neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets/{id}/status': {
|
|
post: {
|
|
tags: ['Tickety'],
|
|
summary: 'Zmenit stav ticketu',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['status'],
|
|
properties: {
|
|
status: { type: 'string', enum: ['new', 'open', 'waiting', 'resolved'] },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Zmeneno',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/Ticket' } },
|
|
},
|
|
},
|
|
'400': { description: 'Neplatny stav' },
|
|
'404': { description: 'Neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets/{id}/comment': {
|
|
post: {
|
|
tags: ['Tickety'],
|
|
summary: 'Pridat komentar',
|
|
description: 'Komentar je dalsi radek logu, aby bylo vse na jedne casove ose.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['text'],
|
|
properties: { text: { type: 'string', minLength: 2 } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Zapsano',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/Ticket' } },
|
|
},
|
|
},
|
|
'400': { description: 'Prazdny komentar' },
|
|
'404': { description: 'Neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets/{id}/type': {
|
|
post: {
|
|
tags: ['Tickety'],
|
|
summary: 'Nastavit typ ticketu',
|
|
description:
|
|
'Typ rozhoduje, ktera vlastni pole ticket ma a ktere akce se na nem ukazou. ' +
|
|
'Pri zmene typu se hodnoty poli **nemazou**, jen prestanou byt videt.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['typeId'],
|
|
properties: {
|
|
typeId: { type: 'string', nullable: true, example: 'tt_order' },
|
|
fields: {
|
|
type: 'object',
|
|
description: 'Hodnoty vlastnich poli. Klic je klic pole z typu ticketu.',
|
|
additionalProperties: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Ulozeno',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/Ticket' } },
|
|
},
|
|
},
|
|
'403': { description: 'Chybi pravo ticket.type.change' },
|
|
'404': { description: 'Ticket nebo typ neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets/{id}/tags': {
|
|
post: {
|
|
tags: ['Tickety'],
|
|
summary: 'Nastavit tagy',
|
|
description: 'Tagy se prepisuji cele. Prirustkova zmena by u vic lidi naraz kolidovala.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['tags'],
|
|
properties: {
|
|
tags: { type: 'array', maxItems: 20, items: { type: 'string', maxLength: 40 } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Ulozeno',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/Ticket' } },
|
|
},
|
|
},
|
|
'403': { description: 'Chybi pravo ticket.tag' },
|
|
'404': { description: 'Neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets/{id}/group': {
|
|
post: {
|
|
tags: ['Tickety'],
|
|
summary: 'Prehodit na skupinu resitelu',
|
|
description:
|
|
'Prirazeni konkretnimu cloveku se **zrusi**. Kdyby zustalo, ticket by byl ' +
|
|
've fronte skupiny i u cloveka a nikdo by nevedel, kdo to resi.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['groupId'],
|
|
properties: { groupId: { type: 'string', nullable: true } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Ulozeno',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/Ticket' } },
|
|
},
|
|
},
|
|
'403': { description: 'Chybi pravo ticket.assign.group' },
|
|
'404': { description: 'Neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets/{id}/actions': {
|
|
get: {
|
|
tags: ['Tickety'],
|
|
summary: 'Akce dostupne k ticketu',
|
|
description:
|
|
'Vraci **jen akce, ktere v teto situaci opravdu jdou spustit**: sedi typ nebo ' +
|
|
'tag, projdou podminky a volajici na ne ma pravo. Klient nefiltruje nic.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Akce',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
items: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
label: { type: 'string', example: 'Odeslat do iDokladu' },
|
|
icon: { type: 'string' },
|
|
style: { type: 'string', enum: ['primary', 'default', 'danger'] },
|
|
confirm: { type: 'string', nullable: true },
|
|
form: { type: 'array', items: { type: 'object' } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'404': { description: 'Ticket neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/tickets/{id}/actions/{actionId}': {
|
|
post: {
|
|
tags: ['Tickety'],
|
|
summary: 'Spustit akci',
|
|
description:
|
|
'Vraci 200 **i kdyz akce selhala** - selhani akce neni chyba API. Cely prubeh ' +
|
|
'vcetne toho, co sluzba vratila, se zapise do logu ticketu.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{ name: 'id', in: 'path', required: true, schema: { type: 'string' } },
|
|
{ name: 'actionId', in: 'path', required: true, schema: { type: 'string' } },
|
|
],
|
|
requestBody: {
|
|
required: false,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
form: {
|
|
type: 'object',
|
|
description: 'Hodnoty poli, ktera si akce vyzada.',
|
|
additionalProperties: { type: 'string' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Akce probehla nebo selhala, viz ok',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
ok: { type: 'boolean' },
|
|
summary: { type: 'string' },
|
|
detail: {
|
|
type: 'string',
|
|
nullable: true,
|
|
description: 'Cele chybove hlaseni. Nikdy se nezkracuje.',
|
|
},
|
|
durationMs: { type: 'integer' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'403': { description: 'Chybi pravo na tuto akci' },
|
|
'404': { description: 'Ticket nebo akce neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/widget-data': {
|
|
post: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Data vlastnich widgetu',
|
|
description:
|
|
'Jeden request na cely prehled. Deset dlazdic nesmi znamenat deset dotazu.',
|
|
security: [{ bearerAuth: [] }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['widgetIds'],
|
|
properties: { widgetIds: { type: 'array', items: { type: 'string' } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Data po widgetech',
|
|
content: { 'application/json': { schema: { type: 'object' } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/widget-data/options': {
|
|
get: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Co jde ve vlastnim widgetu nastavit',
|
|
description: 'Zdroje dat, mozna seskupeni a sirky. Aby to klient nemel v kodu.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Moznosti',
|
|
content: { 'application/json': { schema: { type: 'object' } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/settings/catalog': {
|
|
get: {
|
|
tags: ['Nastaveni'],
|
|
summary: 'Katalog prav a modulu',
|
|
description:
|
|
'Seznam vsech prav a zalozek. Formular role tak nema seznam prav v kodu klienta.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Katalog',
|
|
content: { 'application/json': { schema: { type: 'object' } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/settings/users/{id}/password': {
|
|
patch: {
|
|
tags: ['Nastaveni'],
|
|
summary: 'Zmenit heslo',
|
|
description:
|
|
'Svoje heslo si zmeni kazdy, cizi jen spravce platformy. Hash se nikdy nevraci.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['password'],
|
|
properties: { password: { type: 'string', minLength: 8 } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'204': { description: 'Zmeneno' },
|
|
'400': { description: 'Kratke heslo' },
|
|
'403': { description: 'Cizi heslo bez prava' },
|
|
},
|
|
},
|
|
},
|
|
'/api/admin/impersonate': {
|
|
post: {
|
|
tags: ['Sprava platformy'],
|
|
summary: 'Prepnout se na jiny ucet',
|
|
description:
|
|
'Vraci novy token s narokem `act`. Bez `writes` projde **jen GET**, cokoliv ' +
|
|
'jineho vrati 403. Prepnuti i jeho ukonceni je v auditu.',
|
|
security: [{ bearerAuth: [] }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['userId'],
|
|
properties: {
|
|
userId: { type: 'string' },
|
|
allowWrites: {
|
|
type: 'boolean',
|
|
default: false,
|
|
description: 'true = i zapis. Musi se zapnout vedome.',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Token na cizi ucet',
|
|
content: { 'application/json': { schema: { type: 'object' } } },
|
|
},
|
|
'403': { description: 'Neni spravce platformy' },
|
|
'404': { description: 'Ucet neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/admin/impersonate/stop': {
|
|
post: {
|
|
tags: ['Sprava platformy'],
|
|
summary: 'Ukoncit prepnuti',
|
|
description:
|
|
'Jen zaznam do auditu. Svuj puvodni token si drzi klient, server o nem nevi.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: { '204': { description: 'Zapsano' } },
|
|
},
|
|
},
|
|
'/api/admin/impersonate/candidates': {
|
|
get: {
|
|
tags: ['Sprava platformy'],
|
|
summary: 'Koho lze prepnout',
|
|
description: 'Spravci platformy se nenabizeji.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Ucty',
|
|
content: { 'application/json': { schema: { type: 'object' } } },
|
|
},
|
|
'403': { description: 'Neni spravce platformy' },
|
|
},
|
|
},
|
|
},
|
|
'/api/admin/audit': {
|
|
get: {
|
|
tags: ['Sprava platformy'],
|
|
summary: 'Audit',
|
|
description:
|
|
'Kdo co udelal, vcetne odepreni a vcetne toho, kdo se za koho vydaval. ' +
|
|
'Nejnovejsi nahore.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{ name: 'action', in: 'query', schema: { type: 'string' } },
|
|
{ name: 'result', in: 'query', schema: { type: 'string', enum: ['ok', 'denied'] } },
|
|
{ name: 'limit', in: 'query', schema: { type: 'integer', maximum: 500, default: 200 } },
|
|
],
|
|
responses: {
|
|
'200': {
|
|
description: 'Zaznamy',
|
|
content: { 'application/json': { schema: { type: 'object' } } },
|
|
},
|
|
'403': { description: 'Neni spravce platformy' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/incidents': {
|
|
get: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Seznam incidentu',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Incidenty',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
items: { type: 'array', items: { $ref: '#/components/schemas/Incident' } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/stream': {
|
|
get: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Zivy stream zmen (SSE)',
|
|
description:
|
|
'Server-Sent Events. Drzi otevrene spojeni a posila udalosti, jakmile nastanou. ' +
|
|
'Swagger UI streamovanou odpoved nezobrazi rozumne, testujte prohlizecem nebo curl.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: { '200': { description: 'Proud udalosti text/event-stream' } },
|
|
},
|
|
},
|
|
'/health/ready': {
|
|
get: {
|
|
tags: ['Provoz'],
|
|
summary: 'Readiness vcetne databaze',
|
|
description:
|
|
'Vraci 503, kdyz je databaze nastavena a nedostupna. `/health` na databazi ' +
|
|
'zamerne nezavisi - kratky vypadek DB by jinak vedl k restartovani containeru.',
|
|
responses: {
|
|
'200': { description: 'Aplikace je pripravena' },
|
|
'503': { description: 'Databaze je nastavena, ale nedostupna' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/storage': {
|
|
get: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Kam se uklada',
|
|
description:
|
|
'mode postgres nebo memory. `ephemeral: true` znamena, ze restart procesu ' +
|
|
'data smaze. Portal to musi umet rict nahlas.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Rezim uloziste',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
mode: { type: 'string', enum: ['postgres', 'memory'] },
|
|
reason: { type: 'string', nullable: true },
|
|
ephemeral: { type: 'boolean' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/services': {
|
|
get: {
|
|
tags: ['Sluzby'],
|
|
summary: 'Katalog sluzeb pro builder',
|
|
description:
|
|
'Vraci jen sluzby, ktere uzivatel vidi. Neviditelna sluzba v odpovedi neni ' +
|
|
'vubec, ne se stavem "nemate pravo". Operace, ktere obsluhuje skript, nesou ' +
|
|
'implementation: script a maji skutecne inputs a outputFields.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: { '200': { description: 'Sluzby, kategorie a operatory podminek' } },
|
|
},
|
|
},
|
|
'/api/dashboard/connectors/services': {
|
|
get: {
|
|
tags: ['Sluzby'],
|
|
summary: 'Katalog sluzeb ocima firmy',
|
|
description:
|
|
'Jako /services, navic connectorCount, tedy kolik konektoru na sluzbu firma ma. ' +
|
|
'Podle toho se rozlisi napojeno od muzete si napojit. Neni to vlastnost sluzby, ' +
|
|
'ale te firmy.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'tenantId', in: 'query', schema: { type: 'string' } }],
|
|
responses: { '200': { description: 'Sluzby vcetne pouziti ve firme' } },
|
|
},
|
|
},
|
|
'/api/dashboard/connectors': {
|
|
get: {
|
|
tags: ['Konektory'],
|
|
summary: 'Konektory firmy',
|
|
description:
|
|
'Hodnoty pristupovych udaju se NIKDY nevraci, jen filled (co je vyplnene) ' +
|
|
'a missing (ktera povinna pole chybi).',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{ name: 'tenantId', in: 'query', schema: { type: 'string' } },
|
|
{ name: 'serviceId', in: 'query', schema: { type: 'string' } },
|
|
],
|
|
responses: {
|
|
'200': {
|
|
description: 'Konektory firmy',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
items: {
|
|
type: 'array',
|
|
items: { $ref: '#/components/schemas/Connector' },
|
|
},
|
|
tenantId: { type: 'string' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
post: {
|
|
tags: ['Konektory'],
|
|
summary: 'Zalozit konektor',
|
|
description:
|
|
'Pristupove udaje se posilaji ve values s klici podle Service.credentials. ' +
|
|
'Nevyplnene povinne pole neni chyba, konektor se ulozi a jen nepujde pouzit.',
|
|
security: [{ bearerAuth: [] }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['serviceId', 'name'],
|
|
properties: {
|
|
serviceId: { type: 'string', example: 'idoklad' },
|
|
name: { type: 'string', example: 'iDoklad Celo' },
|
|
baseUrl: { type: 'string', nullable: true },
|
|
values: {
|
|
type: 'object',
|
|
additionalProperties: { type: 'string' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'201': {
|
|
description: 'Zalozeno',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/Connector' } },
|
|
},
|
|
},
|
|
'400': { description: 'Obecna sluzba konektor nepotrebuje, nebo nezname pole' },
|
|
'404': { description: 'Sluzba neexistuje nebo ji uzivatel nevidi' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/connectors/{id}': {
|
|
get: {
|
|
tags: ['Konektory'],
|
|
summary: 'Detail konektoru',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
responses: { '200': { description: 'Konektor' }, '404': { description: 'Neexistuje' } },
|
|
},
|
|
patch: {
|
|
tags: ['Konektory'],
|
|
summary: 'Upravit konektor',
|
|
description:
|
|
'Ve values staci poslat jen to, co se meni. PRAZDNY RETEZEC hodnotu smaze, ' +
|
|
'chybejici klic ji nechava - diky tomu jde ulozit formular, ktery tajne hodnoty ' +
|
|
'neposila. Zmena udaju vzdy zrusi predchozi overeni.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string' },
|
|
baseUrl: { type: 'string', nullable: true },
|
|
values: { type: 'object', additionalProperties: { type: 'string' } },
|
|
enabled: { type: 'boolean' },
|
|
isDefault: { type: 'boolean', enum: [true] },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: { '200': { description: 'Upraveno' }, '404': { description: 'Neexistuje' } },
|
|
},
|
|
delete: {
|
|
tags: ['Konektory'],
|
|
summary: 'Smazat konektor',
|
|
description:
|
|
'Kdyz zmizel vychozi konektor, prevezme to prvni zbyly - jinak by kroky bez ' +
|
|
'vybraneho konektoru prestaly fungovat.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
responses: { '204': { description: 'Smazano' }, '404': { description: 'Neexistuje' } },
|
|
},
|
|
},
|
|
'/api/whoami': {
|
|
get: {
|
|
tags: ['Systém'],
|
|
summary: 'Jak nas vidi ten, kdo nam vola',
|
|
description:
|
|
'Vraci volajicimu jeho vlastni adresu tak, jak dorazila k serveru. Zni to ' +
|
|
'zbytecne, ale je to jediny zpusob, jak zmerit, s jakou zdrojovou adresou ' +
|
|
'doruci reverse proxy volani, ktere vyslo z naseho containeru. Bez prihlaseni ' +
|
|
'zamerne - volajici dostane svoji vlastni adresu, nic navic.',
|
|
responses: {
|
|
'200': {
|
|
description: 'Adresa volajiciho',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
ip: { type: 'string', nullable: true },
|
|
forwardedFor: { type: 'string', nullable: true },
|
|
remoteAddress: { type: 'string', nullable: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/connectors/egress-ip': {
|
|
get: {
|
|
tags: ['Konektory'],
|
|
summary: 'Odchozi IP adresa portalu',
|
|
description:
|
|
'Adresa, kterou vidi volana sluzba, tedy ta, ktera musi byt na jejim seznamu ' +
|
|
'povolenych IP. Z containeru videt neni, zjistuje se echo sluzbou podle ' +
|
|
'EGRESS_IP_URL a vysledek se drzi v pameti po EGRESS_IP_TTL_MS. Neni to ' +
|
|
'tajemstvi: kazda volana sluzba tuhle adresu stejne vidi.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Odchozi adresa, nebo duvod, proc se nezjistila',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
ip: { type: 'string', nullable: true },
|
|
source: { type: 'string' },
|
|
checkedAt: { type: 'string', format: 'date-time' },
|
|
error: { type: 'string' },
|
|
viaProxy: {
|
|
type: 'object',
|
|
nullable: true,
|
|
description:
|
|
'Jak nas vidi nase vlastni reverse proxy. Zmeri se volanim na ' +
|
|
'vlastni verejnou adresu (PUBLIC_ORIGIN + ROOT_PATH + /whoami), ' +
|
|
'ktere se otoci zpatky na tentyz stroj. Byva jina nez ta verejna ' +
|
|
'a prave ji porovnava seznam povolenych IP u sluzeb za toutez proxy.',
|
|
properties: {
|
|
ip: { type: 'string', nullable: true },
|
|
forwardedFor: { type: 'string', nullable: true },
|
|
remoteAddress: {
|
|
type: 'string',
|
|
nullable: true,
|
|
description:
|
|
'Sama proxy, ne volajici - k nam uz to jde od ni. Je to tu na to, ' +
|
|
'aby bylo poznat, ze se volani opravdu tocilo pres ni.',
|
|
},
|
|
suggestedRange: {
|
|
type: 'string',
|
|
nullable: true,
|
|
description:
|
|
'CIDR rozsah, ktery tu adresu pokryje cely (napr. 172.16.0.0/12 ' +
|
|
'nebo 127.0.0.0/8). Do seznamu povolenych patri on, ne jedna ' +
|
|
'adresa: docker prideluje z bloku a pri prekresleni site se cisla ' +
|
|
'meni. null = adresa je verejna, zadny blok se nenabizi.',
|
|
},
|
|
url: { type: 'string' },
|
|
error: { type: 'string' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/connectors/{id}/test': {
|
|
post: {
|
|
tags: ['Konektory'],
|
|
summary: 'Overit napojeni',
|
|
description:
|
|
'Zavola verifyPath sluzby, coz je zamerne cteci volani vyzadujici autorizaci. ' +
|
|
'Kdyz ho sluzba nema, overi se jen /health a odpoved to v checked rekne - aby ' +
|
|
'si nikdo nemyslel, ze jsou overene i pristupove udaje. Neuspesne overeni neni ' +
|
|
'chyba API, vraci se 200 s ok: false.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Vysledek overeni',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
ok: { type: 'boolean' },
|
|
checked: { type: 'string' },
|
|
status: { type: 'integer' },
|
|
message: { type: 'string' },
|
|
baseUrl: {
|
|
type: 'string',
|
|
description:
|
|
'Kam konektor miri. Vraci se i pri uspechu - zaklad adresy je ' +
|
|
'z konfigurace a konektor ho smi prepsat, takze bez nej nerika ' +
|
|
'kod odpovedi nic o tom, jestli se to trefilo na spravny stroj.',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'404': { description: 'Konektor neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/connectors/{id}/mcp/tools': {
|
|
post: {
|
|
tags: ['Konektory'],
|
|
summary: 'Nacist nastroje MCP serveru',
|
|
description:
|
|
'Zepta se MCP serveru na tools/list a ulozi vysledek ke konektoru. Plati pro obe ' +
|
|
'sluzby MCP (obecnou i EasyWeb) - jsou to jedine sluzby, u kterych seznam operaci ' +
|
|
'neurcuje katalog, ale az sam server - teprve tim ' +
|
|
'vzniknou kroky, ktere jde davat do automatizaci, vcetne toho, jake promenne ' +
|
|
'prijimaji a jake vraceji. Zaroven to je overeni konektoru, proto se zapisuje do ' +
|
|
'historie: kdyz server odpovi seznamem, adresa i token sedi. Cteci volani, nic ' +
|
|
'nemeni. Prazdny vysledek se ulozi (server uz nastroje nenabizi), chyba nemeni nic ' +
|
|
'- vypadek serveru nesmi vymazat kroky z hotovych automatizaci. Neuspech neni ' +
|
|
'chyba API, vraci se 200 s ok: false.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Vysledek nacteni',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
ok: { type: 'boolean' },
|
|
checked: { type: 'string' },
|
|
message: { type: 'string' },
|
|
status: { type: 'integer' },
|
|
detail: { type: 'string' },
|
|
server: { type: 'string', description: 'Jak se server predstavil.' },
|
|
protocolVersion: { type: 'string' },
|
|
tools: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string' },
|
|
label: { type: 'string' },
|
|
description: { type: 'string' },
|
|
inputs: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'Nazvy parametru, povinne s hvezdickou na konci.',
|
|
},
|
|
outputs: { type: 'array', items: { type: 'string' } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'400': { description: 'Sluzba neni MCP server' },
|
|
'404': { description: 'Konektor neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/connectors/{id}/checks': {
|
|
get: {
|
|
tags: ['Konektory'],
|
|
summary: 'Historie overeni konektoru',
|
|
description:
|
|
'Poslednich pet overeni, nejnovejsi prvni. U neuspechu nese zaznam cele telo ' +
|
|
'odpovedi sluzby v poli detail - prave tam sluzba pise, co ji vadilo, a bez ' +
|
|
'toho se neda rozlisit spatny udaj od zakazane IP adresy. Texty jsou uz ' +
|
|
'zredigovane, pristupovy udaj v nich neni. Historie je zvlast a ne v seznamu ' +
|
|
'konektoru proto, ze telo odpovedi byva o rady velikosti vetsi nez zbytek radku.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Zaznamy o overeni',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
limit: { type: 'integer' },
|
|
items: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
at: { type: 'string', format: 'date-time' },
|
|
ok: { type: 'boolean' },
|
|
checked: { type: 'string' },
|
|
status: { type: 'integer', nullable: true },
|
|
message: { type: 'string' },
|
|
detail: { type: 'string', nullable: true },
|
|
request: {
|
|
type: 'object',
|
|
nullable: true,
|
|
description:
|
|
'url je cela adresa vcetne serveru, bez query - v query muze byt tajemstvi.',
|
|
properties: {
|
|
method: { type: 'string' },
|
|
path: { type: 'string' },
|
|
url: { type: 'string' },
|
|
},
|
|
},
|
|
responseHeaders: {
|
|
type: 'object',
|
|
nullable: true,
|
|
additionalProperties: { type: 'string' },
|
|
description:
|
|
'Vybrane hlavicky odpovedi (server, via, content-type, ' +
|
|
'www-authenticate, retry-after, x-request-id, date). Rikaji, kdo ' +
|
|
'odpoved vydal - aplikace, nebo proxy pred ni. U kodu bez tela ' +
|
|
'je to jedina stopa, ktera zbyde. Allowlist, ne vsechno: ' +
|
|
'Set-Cookie a podobne do zaznamu nepatri.',
|
|
},
|
|
egressIp: {
|
|
type: 'string',
|
|
nullable: true,
|
|
description:
|
|
'Odchozi IP adresa portalu ve chvili volani. Vyplnena jen ' +
|
|
'u odmitnuteho pristupu (401, 403) - tam je to prvni otazka, ' +
|
|
'jinde nema co rict.',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'404': { description: 'Konektor neexistuje' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/scripts': {
|
|
get: {
|
|
tags: ['Skripty'],
|
|
summary: 'Seznam skriptu konektoru',
|
|
description:
|
|
'Manifesty vsech nactenych skriptu, rozbite skripty v `problems` ' +
|
|
'a stav napojeni v `connections`. Pristupove udaje se nikdy nevraci, ' +
|
|
'jen jmena chybejicich environment variables.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Skripty, problemy a stav napojeni',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
items: {
|
|
type: 'array',
|
|
items: { $ref: '#/components/schemas/ScriptManifest' },
|
|
},
|
|
problems: {
|
|
type: 'array',
|
|
items: { $ref: '#/components/schemas/ScriptProblem' },
|
|
},
|
|
connections: {
|
|
type: 'array',
|
|
items: { $ref: '#/components/schemas/ConnectionStatus' },
|
|
},
|
|
directory: { type: 'string', example: '/app/scripts' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/scripts/reload': {
|
|
post: {
|
|
tags: ['Skripty'],
|
|
summary: 'Znovu nacist skripty ze slozky',
|
|
description:
|
|
'Skripty se nacitaji samy podle casu zmeny souboru. Tenhle endpoint ' +
|
|
'to jen vynuti hned, bez cekani.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': { description: 'Skripty po nacteni' },
|
|
'403': { description: 'Jen spravce platformy' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/scripts/{id}': {
|
|
get: {
|
|
tags: ['Skripty'],
|
|
summary: 'Manifest a kod skriptu',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{
|
|
name: 'id',
|
|
in: 'path',
|
|
required: true,
|
|
schema: { type: 'string' },
|
|
example: 'idoklad.get-issued-invoice',
|
|
},
|
|
],
|
|
responses: {
|
|
'200': {
|
|
description: 'Kod se vraci vzdy. `manifest` je null, kdyz je skript rozbity.',
|
|
},
|
|
'400': { description: 'Neplatne ID skriptu' },
|
|
'404': { description: 'Skript neexistuje' },
|
|
},
|
|
},
|
|
put: {
|
|
tags: ['Skripty'],
|
|
summary: 'Ulozit kod skriptu',
|
|
description:
|
|
'Nejdriv se kod nacte a overi, az pak prepise soubor. Rozbita uprava ' +
|
|
'se neulozi a puvodni skript dal funguje.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{
|
|
name: 'id',
|
|
in: 'path',
|
|
required: true,
|
|
schema: { type: 'string' },
|
|
example: 'idoklad.get-issued-invoice',
|
|
},
|
|
],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['code'],
|
|
properties: {
|
|
code: {
|
|
type: 'string',
|
|
description: 'Cely obsah souboru vcetne exportu manifest a run.',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': { description: 'Ulozeno, vraci se overeny manifest' },
|
|
'400': {
|
|
description: 'Kod nebo manifest neprosel, v `issues` je co opravit',
|
|
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
|
|
},
|
|
'403': { description: 'Jen spravce platformy' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/scripts/{id}/test': {
|
|
post: {
|
|
tags: ['Skripty'],
|
|
summary: 'Zkusebni spusteni skriptu',
|
|
description:
|
|
'POZOR: vola opravdovou sluzbu. Vystavena faktura opravdu vznikne. ' +
|
|
'Chyba skriptu neni chyba API, vraci se 200 s popisem v `error`.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{
|
|
name: 'id',
|
|
in: 'path',
|
|
required: true,
|
|
schema: { type: 'string' },
|
|
example: 'idoklad.get-issued-invoice',
|
|
},
|
|
],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
inputs: {
|
|
type: 'object',
|
|
additionalProperties: true,
|
|
example: { invoiceId: 12345 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Vysledek behu',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/ScriptRunResult' } },
|
|
},
|
|
},
|
|
'400': { description: 'Neplatne ID nebo vstupy' },
|
|
'403': { description: 'Jen spravce platformy' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/automations': {
|
|
get: {
|
|
tags: ['Automatizace'],
|
|
summary: 'Seznam automatizaci',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Automatizace',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
items: { type: 'array', items: { $ref: '#/components/schemas/Automation' } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
post: {
|
|
tags: ['Automatizace'],
|
|
summary: 'Zalozit automatizaci',
|
|
security: [{ bearerAuth: [] }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['name'],
|
|
properties: { name: { type: 'string', minLength: 3 } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'201': {
|
|
description: 'Vytvoreno',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/AutomationDetail' } },
|
|
},
|
|
},
|
|
'400': { description: 'Neplatny nazev' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/automations/{id}': {
|
|
parameters: [
|
|
{ name: 'id', in: 'path', required: true, schema: { type: 'string' }, example: 'AUT-01' },
|
|
],
|
|
get: {
|
|
tags: ['Automatizace'],
|
|
summary: 'Detail vcetne stromu akci',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Detail',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/AutomationDetail' } },
|
|
},
|
|
},
|
|
'404': { description: 'Neexistuje' },
|
|
},
|
|
},
|
|
put: {
|
|
tags: ['Automatizace'],
|
|
summary: 'Ulozit automatizaci',
|
|
description:
|
|
'Validuje strom proti katalogu konektoru. Nedokoncenou automatizaci server ' +
|
|
'nezapne ani pri enabled=true, duvody vraci v poli issues.',
|
|
security: [{ bearerAuth: [] }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string', minLength: 3 },
|
|
enabled: { type: 'boolean' },
|
|
flow: { $ref: '#/components/schemas/AutomationFlow' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'200': {
|
|
description: 'Ulozeno',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/AutomationDetail' } },
|
|
},
|
|
},
|
|
'400': { description: 'Neplatny strom' },
|
|
'404': { description: 'Neexistuje' },
|
|
},
|
|
},
|
|
delete: {
|
|
tags: ['Automatizace'],
|
|
summary: 'Smazat automatizaci',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: { '204': { description: 'Smazano' }, '404': { description: 'Neexistuje' } },
|
|
},
|
|
},
|
|
'/api/dashboard/automations/{id}/webhook/regenerate': {
|
|
post: {
|
|
tags: ['Automatizace'],
|
|
summary: 'Nova adresa webhooku',
|
|
description: 'Stara adresa okamzite prestane fungovat.',
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Novy token',
|
|
content: {
|
|
'application/json': { schema: { $ref: '#/components/schemas/AutomationDetail' } },
|
|
},
|
|
},
|
|
'404': { description: 'Neexistuje nebo spoustecem neni webhook' },
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/notifications': {
|
|
get: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Upozorneni prihlaseneho',
|
|
description:
|
|
'Cislo u zalozky Tickety a hlasky o pridelene praci. Upozorneni jsou ulozena, ' +
|
|
'takze je najde i ten, kdo mel portal zavreny.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Upozorneni',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
items: { type: 'array', items: { type: 'object' } },
|
|
unread: { type: 'integer' },
|
|
mine: { type: 'integer', description: 'Kolik ticketu ma volajici u sebe.' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/api/dashboard/notifications/read': {
|
|
post: {
|
|
tags: ['Dashboard'],
|
|
summary: 'Oznacit upozorneni jako prectena',
|
|
security: [{ bearerAuth: [] }],
|
|
requestBody: {
|
|
required: false,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
ids: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'Bez seznamu se oznaci vsechna.',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: { '200': { description: 'Oznaceno' } },
|
|
},
|
|
},
|
|
'/api/dashboard/runs': {
|
|
get: {
|
|
tags: ['Automatizace'],
|
|
summary: 'Stav fronty behu',
|
|
description:
|
|
'Kdyz neco nefunguje, tohle je prvni misto, kam se clovek podiva: ceka fronta, ' +
|
|
'nebo uz to nekolikrat selhalo? U kazdeho behu je cele chybove hlaseni.',
|
|
security: [{ bearerAuth: [] }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Fronta a posledni behy',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
stats: {
|
|
type: 'object',
|
|
properties: {
|
|
pending: { type: 'integer' },
|
|
running: { type: 'integer' },
|
|
done: { type: 'integer' },
|
|
failed: { type: 'integer' },
|
|
oldestPendingAt: { type: 'string', nullable: true },
|
|
},
|
|
},
|
|
items: { type: 'array', items: { type: 'object' } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/webhook/ticket/{token}': {
|
|
post: {
|
|
tags: ['Webhook'],
|
|
summary: 'Prijem udalosti do ticketu',
|
|
description:
|
|
'VEREJNY endpoint, autorizuje token firmy v adrese. Se stejnym externalId se ' +
|
|
'udalost navesi na existujici ticket, jinak vznikne novy. externalId je ' +
|
|
'unikatni v ramci firmy.',
|
|
parameters: [{ name: 'token', in: 'path', required: true, schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
externalId: { oneOf: [{ type: 'string' }, { type: 'number' }] },
|
|
source: { type: 'string', example: 'eshop' },
|
|
event: { type: 'string', example: 'order.created' },
|
|
subject: { type: 'string' },
|
|
typeId: { type: 'string' },
|
|
tags: { type: 'array', items: { type: 'string' } },
|
|
fields: { type: 'object', additionalProperties: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'201': { description: 'Ticket vznikl' },
|
|
'200': { description: 'Udalost se navesila na existujici ticket' },
|
|
'400': { description: 'Neplatna data' },
|
|
'404': { description: 'Neznamy token' },
|
|
},
|
|
},
|
|
},
|
|
'/webhook/{token}': {
|
|
post: {
|
|
tags: ['Webhook'],
|
|
summary: 'Prijem dat do automatizace',
|
|
description:
|
|
'VEREJNY endpoint. **Odpovi hned** (202) a strom vykona worker na pozadi - ' +
|
|
'cizi sluzba muze odpovidat pomalu a odesilateli by vyprsel timeout. ' +
|
|
'Telo se kontroluje proti kontraktu spoustece, vcetne vnorenych cest.',
|
|
parameters: [{ name: 'token', in: 'path', required: true, schema: { type: 'string' } }],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: { type: 'object', additionalProperties: true },
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'202': {
|
|
description: 'Prijato, zpracuje se na pozadi',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
accepted: { type: 'boolean' },
|
|
automationId: { type: 'string' },
|
|
runId: { type: 'string', nullable: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'400': { description: 'Telo neodpovida kontraktu spoustece' },
|
|
'404': { description: 'Neznamy token' },
|
|
'409': { description: 'Automatizace je pozastavena' },
|
|
},
|
|
},
|
|
get: {
|
|
tags: ['Webhook'],
|
|
summary: 'Napoveda: co se v tele ceka',
|
|
description: 'Vraci metodu, seznam parametru vcetne cest a ukazku tela.',
|
|
parameters: [{ name: 'token', in: 'path', required: true, schema: { type: 'string' } }],
|
|
responses: {
|
|
'200': { description: 'Kontrakt' },
|
|
'404': { description: 'Neznamy token' },
|
|
},
|
|
},
|
|
},
|
|
'/api/contact': {
|
|
post: {
|
|
tags: ['Kontakt'],
|
|
summary: 'Odeslat poptavku z webu',
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['name', 'email', 'topic', 'message'],
|
|
properties: {
|
|
name: { type: 'string', minLength: 2 },
|
|
email: { type: 'string', format: 'email' },
|
|
company: { type: 'string' },
|
|
phone: { type: 'string' },
|
|
topic: {
|
|
type: 'string',
|
|
enum: [
|
|
'automatizace',
|
|
'voicebot',
|
|
'integrace',
|
|
'dashboard',
|
|
'podpora',
|
|
'jine',
|
|
],
|
|
},
|
|
message: { type: 'string', minLength: 10 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
'202': { description: 'Prijato' },
|
|
'400': { description: 'Neplatny vstup' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|