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:
JiriUhlir
2026-07-31 17:00:37 +02:00
parent 46f2f0b07e
commit 7b045a9f20
100 changed files with 15409 additions and 35 deletions
+52
View File
@@ -0,0 +1,52 @@
import type { NextFunction, Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import { config } from '../config.js';
import { findUserById } from '../data/users.js';
import type { JwtPayload, User } from '../types.js';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Express {
interface Request {
user?: User;
}
}
}
/** Overi Bearer token a doplni req.user. Bez tokenu / s neplatnym tokenem vraci 401. */
export function requireAuth(req: Request, res: Response, next: NextFunction) {
const header = req.header('authorization') ?? '';
const [scheme, token] = header.split(' ');
if (scheme?.toLowerCase() !== 'bearer' || !token) {
return res.status(401).json({ error: 'unauthorized', message: 'Chybí přihlašovací token.' });
}
try {
const payload = jwt.verify(token, config.jwtSecret) as JwtPayload;
const user = findUserById(payload.sub);
if (!user) {
console.warn(`[auth] token s platnym podpisem, ale neexistujici uzivatel: ${payload.sub}`);
return res.status(401).json({ error: 'unauthorized', message: 'Uživatel neexistuje.' });
}
req.user = user;
return next();
} catch (err) {
console.warn('[auth] neplatny token:', err instanceof Error ? err.message : err);
return res.status(401).json({ error: 'unauthorized', message: 'Neplatný nebo expirovaný token.' });
}
}
/** Omezi pristup na konkretni role. Pouzivat vzdy AZ za requireAuth. */
export function requireRole(...roles: User['role'][]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
console.error('[auth] requireRole pouzito bez requireAuth');
return res.status(401).json({ error: 'unauthorized' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'forbidden', message: 'Nedostatečná oprávnění.' });
}
return next();
};
}