Nahrazeni sablony kompletnim webem a klientskym portalem
Web a portal Automia v jednom containeru. Express obsluhuje API i zbuildovanou React aplikaci z dist/public. Obsah: - verejny web: homepage, sluzby, o nas, kontakt, 404 - prihlaseni pres JWT, demo ucty - portal: prehled s grafem, tickety, incidenty, automatizace, konektory - builder automatizaci: strom akci, vetveni podminkou - katalog 25 konektoru v 8 kategoriich - webhook s registrovanou adresou, token generuje server - zivy dashboard pres SSE vcetne simulace provozu - Swagger UI na /docs a OpenAPI na /openapi.json Soulad s AGENTS.md: - ROOT_PATH z prostredi, prefix proxy nikde nehardcodovan - mount na koren i na prefix, funguje s handle_path i bez nej - base tag a window.__BASE_PATH__ vkladane do index.html za behu - OpenAPI servers obsahuje prefix, Try it out vola spravnou adresu - povinne /health a /docs, port 3000, naslouchani na 0.0.0.0 - secrets jen z environment variables, nikdy v logu Dokumentace ve slozce documentation/.
This commit is contained in:
+600
@@ -0,0 +1,600 @@
|
||||
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: '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',
|
||||
properties: {
|
||||
id: { type: 'string', example: 'usr_1' },
|
||||
email: { type: 'string', example: 'admin@automia.cz' },
|
||||
name: { type: 'string', example: 'Jiri Uhlir' },
|
||||
role: { type: 'string', enum: ['admin', 'client'] },
|
||||
company: { type: 'string', example: 'Automia' },
|
||||
},
|
||||
},
|
||||
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' },
|
||||
},
|
||||
},
|
||||
Ticket: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', example: 'TK-4821' },
|
||||
subject: { type: 'string' },
|
||||
requester: { type: 'string' },
|
||||
status: { type: 'string', enum: ['new', 'open', 'waiting', 'resolved'] },
|
||||
priority: { type: 'string', enum: ['low', 'normal', 'high', 'critical'] },
|
||||
assignee: { type: 'string', nullable: true },
|
||||
createdAt: { type: 'string', format: 'date-time' },
|
||||
updatedAt: { type: 'string', format: 'date-time' },
|
||||
},
|
||||
},
|
||||
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' },
|
||||
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/tickets': {
|
||||
get: {
|
||||
tags: ['Dashboard'],
|
||||
summary: 'Seznam ticketu',
|
||||
security: [{ bearerAuth: [] }],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Tickety',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
items: { type: 'array', items: { $ref: '#/components/schemas/Ticket' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/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' },
|
||||
requester: { type: 'string' },
|
||||
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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user