Konektory do Postgresu, pristupove udaje sifrovane

Pristupove udaje konektoru se ukladaji do databaze a prezijou restart. Popis
v documentation/14-databaze.md.

Databaze je volitelna a rezimy jsou oddelene:
- postgres kdyz je DATABASE_URL i SECRETS_KEY
- memory jinak, tedy pri nasazenem mockupu a lokalnim vyvoji bez DB

Rozhodnuti je jen na jednom miste (src/data/connectorStore.ts). Nikde jinde se
nezjistuje, jestli databaze je - kdyby se to rozlezlo po kodu, jedno misto by se
zapomnelo a chovalo by se pak jinak nez zbytek.

Chybejici databaze nesmi shodit start: container, ktery nenastartuje, je pro
AppFactory nefunkcni sluzba. Misto toho se do logu napise proc a portal to ukaze
na strance Konektory. Stejne tak kdyz migrace selzou - psat do rozbiteho
schematu je horsi nez neukladat.

Databaze potrebuje oboji. Bez SECRETS_KEY by se udaje ukladaly v plaintextu
a to je horsi nez ztratit je pri restartu: tabulku vidi kazda zaloha a kazdy
dump pri ladeni.

Pridano:
- pool v src/db/pool.ts vcetne transakci a dbFor(tenantId) jako sev pro budouci
  oddelenou databazi jednoho klienta
- migrace ze src/db/migrations/*.sql pod pg_advisory_lock, jinak je pri rolling
  deployi pusti vsechny instance naraz. Jeden soubor je jedna transakce
- sifrovani AES-256-GCM s nahodnym IV a verzi klice. Nerozsifrovatelna hodnota
  nepada, chova se jako nevyplnena a zaloguje se - jeden rozbity konektor nesmi
  shodit seznam ostatnich
- /health/ready s pingem do DB. /health na databazi zamerne nezavisi, kratky
  vypadek by jinak vedl k restartovani containeru
- GET /api/dashboard/storage a hlaska v portalu o tom, ze data jsou jen v pameti
- jediny vychozi konektor na firmu a sluzbu hlida castecny unikatni index, ne jen
  kod. Dva soubezne zapisy by jinak udelaly dva vychozi

Zmeneno: cteni i zapis konektoru je asynchronni, vcetne validace stromu.

Overeno proti Postgresu 16 v kontejneru: migrace, sifrovani v tabulce, preziti
restartu, rozsifrovani spravnym klicem, degradace pri spatnem klici, PATCH bez
tajneho pole, prepnuti a smazani vychoziho konektoru, pametovy rezim bez
DATABASE_URL. Kontejner po overeni smazan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-08-12 15:08:25 +02:00
co-authored by Claude Opus 5
parent 3279dd7dac
commit 78e7f99d60
22 changed files with 1761 additions and 298 deletions
+40
View File
@@ -987,6 +987,46 @@ export function buildOpenApiDocument() {
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'],