Konektory dostaly vykonnou cast. Jeden skript je jeden soubor, ktery nese manifest (vstupni a vystupni parametry) i kod. Diky manifestu s nim umi pracovat strom automatizace, aniz by o kodu cokoliv vedel. Soubory jsou zamerne obycejny JavaScript, ne TypeScript. TypeScript by se musel prelozit a to je presne to otaceni, ktere tady nema byt. Registr sleduje cas zmeny souboru, takze uprava v portalu, rucni uprava souboru i novy soubor ve slozce funguji stejne a bez restartu. Pridano: - scripts/ se skripty konektoru, nazev souboru je zaroven ID operace - kontrola vstupu i vystupu proti manifestu, jedna funkce pro obe strany. Chybejici povinny vystup je chyba skriptu, ne uzivatele - jinak by strom veril parametru, ktery nikdy nedosel - ctx predavany skriptu: http nad adresou napojeni, util, log, config, idempotencyKey, fail a retry. Skript nedostane pristupove udaje - rozliseni opakovatelne a koncove chyby. Runner nikdy nevyhodi vyjimku, vzdy vraci vysledek vcetne retryable - redakce tajnych hodnot pred zapisem do logu. Cizi API rado vraci prijaty token v chybove zprave a log ticketu vidi klient - napojeni z environment variables vcetne iDokladu - sest ukazkovych skriptu pro iDoklad proti skutecnemu API sluzby services.csbot.cz/apps/idoklad, kazdy na jiny vzor - stranka /dashboard/skripty: seznam, manifest, editor, zkusebni spusteni. Formular testu se sklada z manifestu, nepise se pro kazdy skript - endpointy /api/dashboard/scripts vcetne Swaggeru Zmeneno: - katalog konektoru uz neni jen staticky seznam. Akce ze skriptu se domeruji prekryvem v src/data/connectors.ts, takze se naraz objevi ve validaci stromu, ve vypoctu scope i v sablonach. Pri stejnem ID vyhrava skript - ConnectorOperation ma implementation a scriptId - ApiError na klientovi nese cele telo odpovedi a umi z nej vytahnout issues - Dockerfile kopiruje scripts/ do vysledneho image Ukladani nemuze rozbit fungujici skript: kod se nejdriv zapise do docasneho souboru, ten se nacte a overi, a az pak prepise puvodni. K tomu tri dokumenty navrhu dalsich kroku: 09 datove modely a prava, 10 runtime a rozpocet na 150 klientu, 11 popis skriptu konektoru. Overeno: npm run typecheck prochazi na serveru i webu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
108 lines
4.2 KiB
TypeScript
108 lines
4.2 KiB
TypeScript
/**
|
|
* Konfigurace z environment variables.
|
|
* AppFactory je predava containeru, viz AGENTS.md, sekce Variables a secrets.
|
|
* Zadna hodnota se nehardcoduje a zadny secret se neloguje.
|
|
*
|
|
* Pravidlo: chybejici konfigurace nesmi shodit start aplikace. Container,
|
|
* ktery nenastartuje, je pro AppFactory nefunkcni sluzba.
|
|
*/
|
|
|
|
import { randomBytes } from 'node:crypto';
|
|
import path from 'node:path';
|
|
|
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
|
|
function positiveNumber(value: string | undefined, fallback: number): number {
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
}
|
|
|
|
/**
|
|
* Tajny klic pro podpis tokenu.
|
|
*
|
|
* Kdyz promenna chybi, NEPADAME a ani nepouzijeme klic zapsany v kodu.
|
|
* Aplikace musi nastartovat a odpovidat na /health, jinak ji AppFactory
|
|
* vyhodnoti jako nefunkcni. Misto toho se vygeneruje nahodny klic pro
|
|
* beh procesu a do logu jde hlasite varovani.
|
|
*
|
|
* Dusledek: po restartu containeru prestanou platit vydane tokeny
|
|
* a uzivatele se musi prihlasit znovu. Proto se JWT_SECRET ma nastavit
|
|
* jako promenna aplikace v AppFactory.
|
|
*/
|
|
function resolveJwtSecret(): string {
|
|
const value = process.env.JWT_SECRET;
|
|
if (value && value.trim().length > 0) return value;
|
|
|
|
const generated = randomBytes(32).toString('base64url');
|
|
console.warn(
|
|
'[config] JWT_SECRET neni nastavena. Pouzivam nahodny klic platny jen do restartu ' +
|
|
'containeru. Nastavte JWT_SECRET jako promennou aplikace v AppFactory.',
|
|
);
|
|
return generated;
|
|
}
|
|
|
|
/**
|
|
* Prefix verejne adresy, napr. "/apps/csbot-prototype".
|
|
* Caddy ho pred predanim do containeru odstranuje (handle_path), ale prohlizec
|
|
* ho vidi - proto se z nej sklada base pro SPA, odkazy, Swagger i webhooky.
|
|
*/
|
|
function normalizeRootPath(value: string | undefined): string {
|
|
const trimmed = (value ?? '').trim();
|
|
if (trimmed.length === 0 || trimmed === '/') return '';
|
|
const withSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
return withSlash.replace(/\/+$/, '');
|
|
}
|
|
|
|
export const config = {
|
|
isProduction,
|
|
/** Port urcuje AppFactory sablona, vychozi 3000. Nemenit bez upravy metadat. */
|
|
port: Number(process.env.PORT ?? 3000),
|
|
rootPath: normalizeRootPath(process.env.ROOT_PATH),
|
|
jwtSecret: resolveJwtSecret(),
|
|
jwtExpiresIn: process.env.JWT_EXPIRES_IN ?? '8h',
|
|
/**
|
|
* Povolene originy pro CORS. V nasazeni bezi web i API na stejne domene,
|
|
* takze se CORS neuplatni. Je tu kvuli lokalnimu vyvoji s Vite dev serverem.
|
|
*/
|
|
corsOrigins: (process.env.CORS_ORIGIN ?? 'http://localhost:5173,http://localhost:4173')
|
|
.split(',')
|
|
.map((o) => o.trim())
|
|
.filter(Boolean),
|
|
/**
|
|
* Verejna adresa bez prefixu, napr. "https://services.csbot.cz".
|
|
* Sklada se z ni absolutni URL webhooku. Kdyz neni vyplnena, pouzije se
|
|
* relativni tvar - nikdy se nehardcoduje produkcni domena.
|
|
*/
|
|
publicOrigin: (process.env.PUBLIC_ORIGIN ?? '').trim().replace(/\/+$/, ''),
|
|
|
|
// ------------------------------------------------------- skripty konektoru
|
|
|
|
/**
|
|
* Adresar se skripty konektoru. Relativne k adresari, ze ktereho aplikace
|
|
* bezi, aby to fungovalo v containeru (`/app/scripts`) i lokalne.
|
|
*/
|
|
scriptsDir: path.resolve(process.env.SCRIPTS_DIR ?? path.join(process.cwd(), 'scripts')),
|
|
/**
|
|
* Zaklad adres napojenych sluzeb, napr. "https://services.csbot.cz/apps".
|
|
* Konkretni konektor lze presmerovat pres `<KONEKTOR>_BASE_URL`.
|
|
* Nikdy se nehardcoduje do logiky, viz AGENTS.md.
|
|
*/
|
|
servicesBaseUrl: (process.env.SERVICES_BASE_URL ?? 'https://services.csbot.cz/apps')
|
|
.trim()
|
|
.replace(/\/+$/, ''),
|
|
/** Strop na jeden beh skriptu, kdyz si ho manifest neurci sam. */
|
|
scriptTimeoutMs: positiveNumber(process.env.SCRIPT_TIMEOUT_MS, 15_000),
|
|
/** Vetsi odpoved cizi sluzby se zahodi, misto aby snedla pamet procesu. */
|
|
scriptMaxResponseBytes: positiveNumber(process.env.SCRIPT_MAX_RESPONSE_BYTES, 1_000_000),
|
|
/**
|
|
* Povoli skriptum volat na localhost a do privatnich rozsahu IP.
|
|
* Jen pro lokalni vyvoj, v nasazeni musi zustat vypnute.
|
|
*/
|
|
allowPrivateTargets: process.env.ALLOW_PRIVATE_TARGETS === 'true',
|
|
};
|
|
|
|
/** Zaklad verejne adresy aplikace vcetne prefixu proxy. */
|
|
export function publicBaseUrl(): string {
|
|
return `${config.publicOrigin}${config.rootPath}`;
|
|
}
|