import { config } from './config.js'; /** * 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: 'Simulace', description: 'Vyvolani provoznich udalosti pro nahled' }, { 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 }, }, }, 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' }, 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: { '/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/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' } }, }, }, '/api/dashboard/connectors': { get: { tags: ['Automatizace'], summary: 'Katalog konektoru', security: [{ bearerAuth: [] }], responses: { '200': { description: 'Konektory, kategorie a operatory podminek' } }, }, }, '/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/simulate': { post: { tags: ['Simulace'], summary: 'Vyvolat provozni udalost', description: 'Meni skutecna data, aby bylo videt, jak dashboard reaguje zive. ' + 'Nevyplnena pole server doplni ukazkovou hodnotou.', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['action'], properties: { action: { type: 'string', enum: [ 'ticket.created', 'ticket.resolved', 'incident.started', 'incident.resolved', 'automation.run', ], }, subject: { type: 'string' }, body: { type: 'string', description: 'Cely text pozadavku.' }, channel: { type: 'string', enum: ['whatsapp', 'facebook', 'instagram', 'email', 'voice', 'form', 'portal'], description: 'Odkud pozadavek prisel. Podle toho se poskladá i log ticketu.', }, contact: { type: 'string' }, knownCustomer: { type: 'boolean', description: 'false = CRM firmu nedohleda, ticket zustane bez zakaznika i bez resitele.', }, priority: { type: 'string', enum: ['low', 'normal', 'high', 'critical'] }, ticketId: { type: 'string' }, title: { type: 'string' }, service: { type: 'string' }, severity: { type: 'string', enum: ['sev1', 'sev2', 'sev3'] }, incidentId: { type: 'string' }, automationId: { type: 'string' }, ok: { type: 'boolean' }, }, }, }, }, }, responses: { '200': { description: 'Udalost provedena' }, '201': { description: 'Zaznam vytvoren' }, '409': { description: 'Neni co provest' }, }, }, }, '/webhook/{token}': { post: { tags: ['Webhook'], summary: 'Prijem dat do automatizace', description: 'Verejny endpoint bez prihlaseni. Autorizuje neuhodnutelny token v adrese. ' + 'Telo se overuje proti parametrum deklarovanym u spoustece.', parameters: [ { name: 'token', in: 'path', required: true, schema: { type: 'string' }, description: '32znakovy token vygenerovany serverem.', }, ], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', additionalProperties: true }, example: { customer: 'Nordis', score: 18, comment: 'vse ok' }, }, }, }, responses: { '202': { description: 'Prijato' }, '400': { description: 'Chybi parametr nebo nesedi typ' }, '404': { description: 'Neznamy token' }, '409': { description: 'Automatizace je pozastavena' }, }, }, }, '/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' }, }, }, }, }, }; }