28 KiB
NSCT-Frontend — Admin Handbook
1. Übersicht
Das NSCT-Frontend ist eine Single-Page-Application (SPA), die als statische Assets gebaut und über einen Caddy Reverse-Proxy ausgeliefert wird. Die App ist in SvelteKit + TailwindCSS + TypeScript implementiert und bietet eine Dark/Light-Oberfläche zur Interaktion mit dem NSCT-Backend.
Architektur
Browser → Caddy (HTTPS/Reverse-Proxy) → Web-Container (Statische Assets) → NSCT-Backend (API)
- Build: SvelteKit mit
@sveltejs/adapter-static→ alle Assets landen inbuild/ - Runtime: Node 22 Alpine, portiert auf
3000 - Proxy: Caddy übernimmt TLS, Security Headers, Compression
- Auth: API-Key-basiert, persistiert im
localStoragedes Browsers - UI-Pattern: Svelte 5 Stores (
writable), TailwindCSS utility-first, Svelte 5 Runes
Features
| Feature | Beschreibung |
|---|---|
| Dark/Light Theme | Klassen-basiertes Theming über dark-Class auf <html> |
| API-Key-Auth | Login mit API-Key, persistent im localStorage |
| Share-Links | Token-basierte freischaltbare Research-Detailansichten |
| Sidebar-Historie | Liste letzter Researches mit Polling-Status |
| Markdown-Reports | Vollständige Berichte via svelte-markdown |
| Error-Banner | Zentrales Error-System (ContradictionBanner, ErrorBanner) |
| Polling | Automatisches Refetching mit konfigurierbarem Intervall |
Deployment
Multi-Stage Docker Build (Node 22 Alpine) → statische Assets → Caddy Reverse-Proxy.
2. Schnellstart
Repository klonen
git clone https://git.frerkc.de/opencode/NSCT-FrontEnd.git
cd NSCT-FrontEnd
Umgebungsvariablen konfigurieren
# .env kopieren und anpassen
cp .env.example .env
Minimale .env-Konfiguration:
# Erreichbarer Host:Port des Backend-Rechners (kein Docker-Netzwerk nötig)
NSCT_API_UPSTREAM=backend.example.com:8080
# Optional: Domain für HTTPS-Zugriff über Caddy
CADDY_DOMAIN=nsct.example.com
# Standard-Theme: dark | light
DEFAULT_THEME=dark
Stack starten
docker compose up -d
Health-Check
curl http://localhost:3000/health
Erwartete Antwort: HTTP 200 mit JSON-Payload.
Browser öffnen
https://nsct.example.com
3. Caddy + HTTPS Konfiguration
Caddyfile-Struktur
Die Caddyfile (Basis: Caddyfile) definiert zwei Block-Typen:
- HTTPS-Block (
:443) — Hauptblock mit Security Headers, Compression, Reverse-Proxy - HTTP-Fallback (
:80) — Kein HTTPS, Basis-Header (wenn keine Domain gesetzt)
# Block 1: HTTPS (immer aktiv)
:443 {
reverse_proxy web:3000
header { ... } # Security Headers
encode gzip zstd # Compression
log { format json } # Structured JSON Logs
}
# Block 2: HTTP (ohne HTTPS)
:80 {
reverse_proxy web:3000
header { ... }
encode gzip zstd
}
Let's Encrypt (automatisch)
CADDY_DOMAIN=nsct.example.com
Caddy erstellt automatisch ein TLS-Zertifikat über ACME/Let's Encrypt. Der Container muss port 443 erreichen können und die DNS-Auflösung für nsct.example.com muss auf den Server zeigen.
Zertifikate werden persistiert im Volume caddy_data gespeichert (Pfad: /data/caddy/certificates).
HTTP-Fallback (ohne Domain)
Ohne CADDY_DOMAIN läuft Caddy nur auf Port 80 (:80 Block). Keine TLS, kein Redirect.
Security Headers
| Header | Wert | Zweck |
|---|---|---|
X-Content-Type-Options |
nosniff |
Verhindert MIME-Type-Sniffing |
X-Frame-Options |
DENY |
Keine Einbettung in iframes |
X-XSS-Protection |
1; mode=block |
XSS-Protection (legacy) |
Content-Security-Policy |
Default-Self + inline-src | Resource-Whitelisting |
Referrer-Policy |
no-referrer-when-downgrade |
Referrer-Kontrolle |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
Feature-Blocking |
X-Permitted-Cross-Domain-Policies |
none |
Cross-Domain-Restriction |
X-DNS-Prefetch-Control |
off |
DNS-Prefetch deaktivieren |
Compression
encode gzip zstd
gzip 1 # Minimaler Kompressionslevel
Rate-Limiting (optional)
NSCT_RATE_LIMIT=100/second
Wenn gesetzt, aktiviert Caddy ein Rate-Limiting. Konfiguration erfolgt via Caddy-Rate-limit-Plugin (nicht im Standard-Image enthalten). Für Produktionsbetrieb empfohlen: dediziertes Rate-Limiting über einen separaten Reverse-Proxy (z.B. Traefik/HAProxy).
DNS-Konfiguration
Für externen Zugriff muss die Domain auf den öffentlichen Server-IP-Adresse verweisen:
nsct.example.com A 203.0.113.1
Port 80 und 443 müssen im Firewall-Regelwerk freigeschaltet sein.
4. Environment-Variablen
Web-Container (web service)
| Variable | Beschreibung | Default | Beispiel |
|---|---|---|---|
PORT |
Port des statischen Webservers | 3000 |
(fest im Caddyfile) |
Caddy-Container (caddy service)
| Variable | Beschreibung | Default | Beispiel |
|---|---|---|---|
CADDY_DOMAIN |
Domain für HTTPS / Zertifikat | leer | nsct.example.com |
NSCT_RATE_LIMIT |
Rate-Limiting (optional) | leer | 100/second |
NSCT_API_UPSTREAM |
Host:Port des Backend-Rechners | host.docker.internal:8080 |
backend.example.com:8080 |
Konfiguration am Beispiel
Lokale Entwicklung (HTTP only)
NSCT_API_UPSTREAM=host.docker.internal:8080
CADDY_DOMAIN=
DEFAULT_THEME=dark
Produktionsumgebung (HTTPS)
NSCT_API_UPSTREAM=backend.example.com:8080
CADDY_DOMAIN=nsct.example.com
DEFAULT_THEME=dark
Light-Mode (z.B. für bestimmte Nutzergruppen)
NSCT_API_UPSTREAM=backend.example.com:8080
CADDY_DOMAIN=nsct.example.com
DEFAULT_THEME=light
5. Docker Compose Konfiguration
Services
web — NSCT-Frontend
services:
web:
build:
context: .
dockerfile: Dockerfile
container_name: nsct-web
restart: unless-stopped
environment:
- NSCT_API_BASE_URL=http://nsct-api:8080
- DEFAULT_THEME=dark
ports:
- "3000:3000" # Direktzugriff (intern)
networks:
- nsct-network
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 128M
cpus: '0.25'
Erklärung:
build.context:./— Dockerfile und Quellcode im selben Verzeichniscontainer_name: Festgelegt fürdocker compose exec/logsrestart: unless-stopped: Startet automatisch nach Crash/Rebootports: 3000:3000: Exponiert für Debugging/Health-Check (Caddy sollte im Produktivbetrieb den einzigen入口 haben)healthcheck:wgetprüft/health-Endpoint
caddy — Reverse-Proxy
caddy:
image: caddy:2.8-alpine
container_name: nsct-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
environment:
- CADDY_DOMAIN=${CADDY_DOMAIN:-}
- NSCT_RATE_LIMIT=${NSCT_RATE_LIMIT:-}
networks:
- nsct-network
depends_on:
- web
deploy:
resources:
limits:
memory: 256M
cpus: '0.5'
reservations:
memory: 64M
cpus: '0.1'
Erklärung:
image: Offizielles Caddy 2.8 Alpine-Imageports: 80:80, 443:443: Öffentliche Portsvolumes: Caddyfile-Mapping, TLS-Zertifikate (caddy_data), Konfiguration (caddy_config)depends_on: Web-Container muss vor Caddy starten
Volumes
| Volume | Pfad im Container | Zweck |
|---|---|---|
caddy_data |
/data |
TLS-Zertifikate (Let's Encrypt) |
caddy_config |
/config |
Caddy-Konfiguration (ACME account) |
Volumes sind persistent — Zertifikate überleben Container-Neustarts.
Networks
networks:
nsct-network:
driver: bridge
Bridge-Netzwerk für inter-Container-Kommunikation. Container erreichen sich über ihren Namen als hostname:
- Web-Container erreicht Backend über
nsct-api:8080 - Caddy erreicht Web-Container über
web:3000
Resource Limits
| Service | Memory Limit | CPU Limit | Memory Reservation | CPU Reservation |
|---|---|---|---|---|
web |
512 MB | 1.0 Core | 128 MB | 0.25 Core |
caddy |
256 MB | 0.5 Core | 64 MB | 0.1 Core |
6. Build-Prozess
Multi-Stage Docker Build
# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json ./
COPY package-lock.json ./
RUN npm ci
COPY . .
ENV NODE_ENV=production
RUN npm run build
# Stage 2: Runtime
FROM node:22-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/build ./build
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
RUN addgroup -S nsct && adduser -S nsct -G nsct
USER nsct
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 CMD wget -qO- http://localhost:3000/health || exit 1
EXPOSE 3000
ENV HOST=0.0.0.0
ENV PORT=3000
CMD ["node", "build"]
Stage 1 — Builder:
- Node 22 Alpine als Build-Umgebung
npm cifür reproduzierbare Dependencies (lock-file-basiert)npm run buildcompiliert SvelteKit → statische Assets inbuild/
Stage 2 — Runtime:
- Node 22 Alpine (lean, ~70 MB)
- Nur
build/undnode_moduleskopiert (kein Quellcode) - Non-root-User
nsct(Sicherheit) - Health-Check integriert
- Startet SvelteKit mit
node build
Vite Konfiguration
export default defineConfig({
plugins: [sveltekit()],
server: {
port: 3000,
strictPort: true,
host: '0.0.0.0',
proxy: {
'/api': {
target: 'http://nsct-api:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
- Port: 3000, strikte Bindung (
strictPort: true) - Dev-Proxy:
/api/*wird anhttp://nsct-api:8080geroutet - Production: Keine Proxy-Config — API-Calls gehen direkt an
NSCT_API_BASE_URL
TailwindCSS Konfiguration
export default {
content: ['./src/**/*.{svelte,js,ts,html}'],
darkMode: 'class',
theme: {
extend: {
colors: {
'nsct-dark-bg': '#0f0f23',
'nsct-dark-surface': '#1a1a2e',
'nsct-dark-border': '#2d2d44',
'nsct-dark-text': '#e0e0e0',
'nsct-light-bg': '#ffffff',
'nsct-light-surface': '#f8f9fa',
'nsct-light-text': '#1a1a1a',
'nsct-accent': '#6366f1',
'nsct-success': '#22c55e',
'nsct-warning': '#f59e0b',
'nsct-error': '#ef4444'
}
}
}
};
- Dark Mode: Klassen-basiert (
class="dark"auf<html>) - Custom Farben:
nsct-*-Präfix für konsistentes Theming
SvelteKit Konfiguration
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: undefined,
precompress: false,
strict: true
}),
alias: {
$components: 'src/components',
$lib: 'src/lib',
$assets: 'src/assets'
}
}
};
- Adapter:
static— alle Routes werden als HTML-Dateien gebaut - Output:
build/(Pages + Assets) - Alias:
$lib/,$components/,$assets/für Importe
Dependencies
| Paket | Version | Rolle |
|---|---|---|
svelte |
^5.0.0 | Framework |
@sveltejs/kit |
^2.0.0 | Router / SSR/SSG |
@sveltejs/adapter-static |
^3.0.0 | Static-Output-Adapter |
@sveltejs/vite-plugin-svelte |
^4.0.0 | Vite-Integration |
vite |
^6.0.0 | Build-Tool |
tailwindcss |
^4.0.0 | CSS-Framework |
postcss + autoprefixer |
^8.4.0 | CSS-Pipeline |
typescript |
^5.0.0 | Typisierung |
svelte-markdown |
^0.5.0 | Markdown-Rendring für Reports |
Bauen
# Lokal bauen (für Tests)
npm run build
# Ausgabe: ./build/ (index.html, _app/, assets/)
# Dev-Server starten
npm run dev
# → http://localhost:3000
# Typ-Checks
npm run check
# → svelte-check über tsconfig
7. Security
Security Headers (Caddy)
| Header | Wert | Bedeutung |
|---|---|---|
X-Content-Type-Options |
nosniff |
Verhindert MIME-Type-Sniffing durch Browser |
X-Frame-Options |
DENY |
Kein Embedding in Frames/iframes |
X-XSS-Protection |
1; mode=block |
Builtin XSS-Protection aktivieren |
Content-Security-Policy |
Default-self + inline-src | Ressourcen-Whitelist |
Referrer-Policy |
no-referrer-when-downgrade |
Referrer nur bei upgrades senden |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
Sensible Browser-Features blockieren |
CSP-Details:
default-src 'self'
script-src 'self' 'unsafe-inline' 'unsafe-eval'
style-src 'self' 'unsafe-inline'
img-src 'self' data: blob:
font-src 'self' data:
connect-src 'self' https://*
frame-src 'none'
object-src 'none'
base-uri 'self'
form-action 'self'
Rate-Limiting
Optional über Umgebungsvariable NSCT_RATE_LIMIT. Im Caddyfile wird geprüft, ob die Variable gesetzt ist:
# .env
NSCT_RATE_LIMIT=100/second
Hinweis: Das offizielle caddy:2.8-alpine-Image enthält kein Rate-Limiting-Plugin. Für Produktions-Rate-Limiting empfiehlt sich ein vorgeschalteter Reverse-Proxy (Traefik, HAProxy, Nginx).
CORS
Das Frontend stellt keine Cross-Origin-Anfragen. Alle API-Calls gehen an den Same-Origin-Pfad /api; Caddy leitet ihn an NSCT_API_UPSTREAM weiter. Frontend und Backend können daher auf getrennten Rechnern laufen, ohne dass der Browser CORS zum Backend benötigt.
API-Key-Speicherung
| Eigenschaft | Wert |
|---|---|
| Speicherort | localStorage (Key: nsct-api-key) |
| Nicht gespeichert in | Cookies, Sessions, Server |
| Persistenz | Bleibt nach Browser-Neustart erhalten |
| Entfernen | logout() → removeApiKey() |
// auth.ts — Schlüssel-Management
const API_KEY_STORAGE = 'nsct-api-key';
localStorage.getItem('nsct-api-key'); // Lesen
localStorage.setItem('nsct-api-key', key); // Schreiben
localStorage.removeItem('nsct-api-key'); // Entfernen
Sicherheitsbewertung: API-Key im localStorage ist für SPA-Architekturen üblich, bietet aber keine Protection vor XSS. Für höheres Security-Niveau wäre httpOnly-Cookie-Auth bevorzugt. Für interne Tools mit kontrollierter Umgebung ist localStorage akzeptabel.
8. Troubleshooting
Login schlägt fehl
Symptome: API-Key wird akzeptiert, aber keine Research-Daten geladen.
Ursachen und Lösungen:
| Ursache | Diagnose | Lösung |
|---|---|---|
Falsche NSCT_API_BASE_URL |
curl $NSCT_API_BASE_URL/health → fail |
.env anpassen, docker compose up -d |
| CORS-Probleme (Backend) | Browser-Console: CORS error | Backend CORS-Headers prüfen |
| API-Key falsch/ungültig | 401/403 vom Backend | API-Key validieren, neu generieren |
| Backend nicht erreichbar | docker compose logs web → connection refused |
Backend-Container prüfen: docker ps |
# Diagnose-Skript
docker compose logs web | grep -i error
curl -v http://localhost:3000/health
docker compose exec web wget -qO- http://nsct-api:8080/health || echo "Backend unreachable"
Share-Links funktionieren nicht
Symptome: Share-Token-URL (z.B. /share/<token>) zeigt 404 oder leere Seite.
Mögliche Ursachen:
| Ursache | Diagnose | Lösung |
|---|---|---|
| Token abgelaufen | Backend gibt 404/410 zurück | Neues Share-Link generieren |
| Backend nicht erreichbar | curl auf Backend fail |
Backend-Container prüfen |
| Caddy-Proxy blockiert | Caddy logs zeigen 404 | Caddyfile auf /share/-Route prüfen |
Falsche NSCT_API_BASE_URL |
Frontend kann Share-Endpoint nicht reachen | .env überprüfen |
# Share-Token auf Gültigkeit prüfen
docker compose logs web | grep share
Markdown-Reports werden nicht angezeigt
Symptome: ReportTab zeigt leeren Bereich statt Report-Inhalt.
| Ursache | Diagnose | Lösung |
|---|---|---|
svelte-markdown nicht importiert |
Console error: Cannot find module |
svelte-markdown in package.json prüfen |
| Report-Feld leer | Backend gibt report: null zurück |
Backend-Daten prüfen |
| Markdown-Syntax invalid | Reports mit ### Headern |
Client-side Markdown-Renderer prüfen |
# svelte-markdown prüfen
docker compose exec web ls node_modules/svelte-markdown/package.json
Polling stoppt nicht
Symptome: Fetch-Intervall läuft weiter, auch wenn Research COMPLETED oder FAILED.
Ursache: Das Polling-System prüft nicht auf End-Status (COMPLETED, FAILED).
Lösung: Prüfen Sie die Polling-Logik in src/lib/stores.ts und den entsprechenden Komponenten. Das Polling muss auf pollingActive-Store reagieren und bei COMPLETED/FAILED-Status den pollingActive-Store auf false setzen.
// Korrektur: Polling bei End-Status stoppen
if (status === 'COMPLETED' || status === 'FAILED') {
pollingActive.set(false);
}
Theme nicht persistent
Symptome: Theme springt nach Seiten-Reload zurück zu Standard.
| Ursache | Diagnose | Lösung |
|---|---|---|
| localStorage blockiert | Browser-Extension blockiert LS | Extension deaktivieren, testen |
class="dark" nicht gesetzt |
app.html hat keine dark-class |
app.html Zeile 2: class="dark" prüfen |
| Theme-Store initialisiert falsch | DEFAULT_THEME ignored |
.env → DEFAULT_THEME prüfen |
# Theme-Store initial prüfen
docker compose exec web node -e "console.log(require('fs').readFileSync('/dev/stdin','utf8'), process.env.DEFAULT_THEME)"
Error-Banner verstehen
Das Error-System besteht aus drei Komponenten:
| Komponente | Datei | Zweck |
|---|---|---|
ErrorBanner |
src/components/ErrorBanner.svelte |
Zeigt API/Network-Fehler an |
ErrorBannerStore |
src/components/ErrorBannerStore.ts |
Zentrales Error-State |
ContradictionBanner |
src/components/ContradictionBanner.svelte |
Zeigt Zielkonflikte in Research an |
Error-Flow:
- API-Fehler wird gefangen →
ErrorBannerStoreschreiben ErrorBanner.sveltesubscribt auf Store → rendert Banner- Nutzer schließt Banner → Error wird zurückgesetzt
Fehlerkategorien:
| Error-Typ | Beispiel | Lösung |
|---|---|---|
| Network Error | TypeError: Failed to fetch |
Netzwerk/Backend prüfen |
| API Error | API Error 500: Internal Server Error |
Backend-Logs prüfen |
| Auth Error | API Error 401: Unauthorized |
API-Key neu eingeben |
| Share Error | Token nicht gefunden | Link prüfen/erneut generieren |
9. Monitoring
Container-Logs
# Alle Logs (follow)
docker compose logs -f
# Nur Web-Container
docker compose logs -f web
# Nur Caddy-Container
docker compose logs -f caddy
# Logs mit Filter
docker compose logs web | grep -i error
docker compose logs caddy | grep -i "request"
Health-Check
# Web-Container health
curl -s http://localhost:3000/health
# Caddy health (über Proxy)
curl -s -o /dev/null -w "%{http_code}" https://nsct.example.com/health
# Docker health-Check-Status
docker inspect --format='{{.State.Health.Status}}' nsct-web
docker inspect --format='{{.State.Health.Status}}' nsct-caddy
Browser-Entwicklerwerkzeuge
| Tab | Inhalt |
|---|---|
| Network | API-Calls inspizieren: URL, Status, Payload, Timing |
| Console | JS-Errors, Warnings, Store-Updates |
| Application | localStorage-Inhalt prüfen (nsct-api-key) |
| Performance | Loading-Zeiten, Render-Performance |
Wichtige Network-Filter:
XHR/Fetch: Alle API-Calls (GET, POST, DELETE)WS/EventSource: Falls WebSocket-Polling verwendet wird- Status-Codes: 200 (OK), 401 (Auth), 404 (Not Found), 500+ (Server Error)
Prometheus/Metriken (falls aktiv)
Falls der NSCT-Backend oder Caddy Metriken exponieren:
# Caddy Metriken (Standard: :2019)
curl -s http://localhost:2019/metrics | head -20
# Web-Container Metriken
curl -s http://localhost:3000/metrics
Caddy 2.8 mit caddymetrics oder built-in prometheus-Directive.
10. Deployment-Checkliste
Pre-Deploy
.env-Datei überprüft:NSCT_API_BASE_URL,CADDY_DOMAIN,DEFAULT_THEME- DNS-Eintrag für
CADDY_DOMAINzeigt auf Server-IP - Ports 80 und 443 im Firewall/Security-Group freigeschaltet
- Caddyfile auf Korrektheit geprüft (Syntax:
docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile) - Backend-Container erreichbar:
docker compose exec web wget -qO- http://nsct-api:8080/health - Git-Status sauber:
git status→ kein uncommitted Code
Deploy
# Änderungen committen
git add .
git commit -m "Prepare deployment: <description>"
git push origin main
# Stack deployen
docker compose up -d
docker compose up -d --build # Bei Code-Änderungen
Post-Deploy
- Health-Check erfolgreich:
curl http://localhost:3000/health→ 200 - Caddy erreichbar:
curl -s -o /dev/null -w "%{http_code}" https://nsct.example.com→ 200 - Browser-Test: App lädt, Theme korrekt, Login funktioniert
- API-Key-Login test: Login mit gültigem Key → Dashboard sichtbar
- Share-Link test: Neue Research starten → Share-Token generieren → Link im Browser öffnen
- Polling-Test: Langsame Research starten → Polling-Updates in der Sidebar verfolgen
- Error-Test: Falschen API-Key eingeben → Error-Banner erscheint
- Logs prüfen:
docker compose logs --tail=50→ keine Errors - Docker-Health-Status:
docker inspect --format='{{.State.Health.Status}}' nsct-web→healthy
Rollback
# Current Image-ID merken
docker compose ps --format json | jq -r '.Image'
# Stack stoppen
docker compose down
# Alte Image-Version in docker-compose.yml eintragen (Tag fixen)
# docker-compose.yml → web.build → tag auf feste Version setzen
# Stack starten
docker compose up -d
Best Practice: Immer feste Image-Tags verwenden (image: nsct-web:1.2.3) statt latest.
11. Architektur-Diagramm
Hochlevel-Architektur
┌──────────────┐ HTTPS ┌──────────────┐ HTTP ┌──────────────┐
│ Browser │ ────────────────► │ Caddy │ ──────────────────► │ Web │
│ (Client) │ Port 80/443 │ (Reverse- │ Port 3000 │ Container │
│ │ │ Proxy) │ │ (Node 22) │
│ - UI/HTML │ │ │ │ :3000 Static│
│ - JS/Bundle │ │ - TLS/HTTPS │ │ Assets │
│ - localStorage│ │ - Security │ │ │
│ (API-Key) │ │ Headers │ │ │
└──────────────┘ │ - Gzip/Zstd │ └──────┬───────┘
│ - Rate-Limit* │ │
└──────────────┘ │
┌────────▼───────┐
│ NSCT Backend │
│ (API Server) │
│ :8080 │
│ │
│ - Research API │
│ - Auth │
│ - PostgreSQL │
└────────────────┘
Datenfluss
1. API-Key Eingabe (Login)
→ localStorage.setItem('nsct-api-key', key)
2. API-Anfrage (z.B. Research erstellen)
→ localStorage.getItem('nsct-api-key')
→ POST ${NSCT_API_BASE_URL}/research
→ Header: X-API-Key: <key>
→ Body: { "query": "..." }
3. Antwort vom Backend
→ 200 OK: { id: "...", status: "RUNNING" }
→ Store: researchData.set(response)
→ Polling: pollingActive.set(true) → pollingInterval (5s)
4. Polling bis COMPLETED/FAILED
→ GET ${NSCT_API_BASE_URL}/research/<id>
→ X-API-Key: <key>
→ Status prüfen: COMPLETED → pollingActive.set(false)
5. Share-Link generieren
→ Share-Token vom Backend erhalten
→ URL: /share/<token>
→ Öffentlicher Zugang (kein API-Key nötig)
Kompletter Request-Flow (mit Caddy)
Browser Caddy (HTTPS) Web Container
───────── ─────────────── ─────────────
GET /share/abc123 ──────────► :443 reverse_proxy web:3000 ──────► serve static
200 OK: share.html
+ JS Bundle
|
Browser ◄─────────────────────────── ◄──────────────────────────── HTML + JS
200 OK 200 OK |
|
(JS lädt Share-Daten) |
|
GET /api/share/abc123 ──────────────────────────────────────────────► POST/GET NSCT-API
X-API-Key: (none for share) |
|
◄────────────────────────────────────────────────────────────────── JSON Response
200 OK
Storage-Layer
┌─────────────────────────────────────────────────────────────────────┐
│ PostgreSQL │
│ ┌───────────────┐ ┌───────────────┐ ┌──────────────────────────┐ │
│ │ researches │ │ sources │ │ claims / evidence │ │
│ │ - id (PK) │ │ - id (PK) │ │ - id (PK) │ │
│ │ - query │ │ - url │ │ - research_id (FK) │ │
│ │ - status │ │ - title │ │ - claim text │ │
│ │ - created_at │ │ - domain │ │ - evidence │ │
│ │ - completed_at│ └───────────────┘ └──────────────────────────┘ │
│ └───────────────┘ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ users │ │ shares │ │
│ │ - api_key │ │ - token │ │
│ │ - username │ │ - research_id │ │
│ └───────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────────────┘