feat: Projekt Scanner integrieren, Whisper in die Einstellungen

Der eigenstaendige Projekt Scanner zieht komplett ins CRM ein und laeuft
parallel zur bestehenden App weiter - jede geteilte Ressource ist verdoppelt
statt geteilt, damit die alte Installation unberuehrt bleibt:

- eigene Datenbank perinco_scanner, legt sich beim Start selbst an
- eigener SCANNER_ENCRYPTION_KEY, eigenes Upload-Verzeichnis
- eigener Scheduler mit Zeitversatz, damit nicht beide Instanzen gleichzeitig
  mit denselben Portal-Cookies scrapen (Rate-Limits, CAPTCHA, doppelter RAM)
- Sperre gegen ueberlappende Laeufe: Scheduler und "Jetzt scannen" teilen sie,
  ein zweiter Aufruf bekommt 409 statt doppelter OpenAI-Kosten

Backend unter /api/scanner hinter der CRM-Anmeldung; die HTTP-Basic-Auth des
Originals entfaellt. CV-PDFs kommen als Stream mit Token statt aus einem
statischen Verzeichnis. Bestandsdaten holt "npm run scanner:import" aus der
alten Datenbank, ohne sie zu veraendern.

Oberflaeche in Tailwind und CI-Farben neu gebaut: Menuepunkt Scanner mit
aufklappbaren Unterpunkten, Einstellungen als eigener Reiter.

Whisper hat keinen eigenen Menuepunkt mehr, sondern ist ein Reiter in den
Einstellungen. Damit diese Geraeteeinstellung jeder Rolle offensteht, sind die
Einstellungen jetzt fuer alle sichtbar - welche Reiter erscheinen, entscheidet
die Rolle.

Ausserdem: kein festes Admin-Passwort mehr im Quelltext, playwright exakt auf
1.60.0 gepinnt (nutzt den vorhandenen Chromium-Build des Servers), technische
Dokumentation mit Funktionszeichnungen unter docs/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sascha Wolf
2026-08-06 15:08:59 +02:00
co-authored by Claude Opus 5
commit 82d1b7eab4
106 changed files with 24133 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
#!/bin/bash
# ---------------------------------------------------------------------------
# Einmalige Einrichtung des portierten Projekt Scanners im CRM-Backend.
#
# Ergaenzt /var/www/perinco_CRM/server/.env um die SCANNER_*- und LEGACY_*-
# Variablen. Idempotent: vorhandene Werte werden nicht ueberschrieben, der
# Verschluesselungsschluessel wird nur erzeugt, wenn noch keiner drinsteht.
#
# Geheimnisse werden nie ausgegeben - nur, ob ein Wert gesetzt ist.
#
# Aufruf auf dem Server:
# bash /tmp/scanner-setup.sh
# ---------------------------------------------------------------------------
set -euo pipefail
CRM_ENV=/var/www/perinco_CRM/server/.env
# Achtung: der Pfad der laufenden Scanner-App enthaelt ein Leerzeichen, deshalb
# ueberall in Anfuehrungszeichen. Die Unit heisst project-scanner-backend und
# hoert auf Port 5005.
LEGACY_DIR="/var/www/Projekt Scanner/backend"
LEGACY_ENV="$LEGACY_DIR/.env"
if [ ! -f "$CRM_ENV" ]; then
echo "FEHLER: $CRM_ENV fehlt."
exit 1
fi
cp -a "$CRM_ENV" "$CRM_ENV.bak.$(date +%Y%m%d%H%M%S)"
# Wert nur setzen, wenn der Schluessel fehlt oder leer ist.
#
# Werte mit Leerzeichen werden gequotet: systemd liest diese Datei als
# EnvironmentFile und trennt dort an Leerzeichen - ein ungequoteter Pfad wie
# "/var/www/Projekt Scanner/backend/uploads" kaeme abgeschnitten an. dotenv
# entfernt die Quotes beim Einlesen wieder.
upsert() {
local key="$1" value="$2"
if grep -qE "^${key}=.+" "$CRM_ENV"; then
echo " $key: bleibt unveraendert"
return
fi
# Leere Zeile des Schluessels entfernen, dann neu anhaengen.
sed -i "/^${key}=/d" "$CRM_ENV"
case "$value" in
*[[:space:]]*) printf '%s="%s"\n' "$key" "$value" >> "$CRM_ENV" ;;
*) printf '%s=%s\n' "$key" "$value" >> "$CRM_ENV" ;;
esac
echo " $key: gesetzt"
}
# Aus der alten .env lesen, ohne sie in die eigene Umgebung zu uebernehmen.
legacy_value() {
local key="$1"
[ -f "$LEGACY_ENV" ] || return 0
sed -n "s/^${key}=//p" "$LEGACY_ENV" | tail -n1 | tr -d '\r'
}
echo "== Scanner-Konfiguration in $CRM_ENV =="
# Kopfzeile nur beim ersten Lauf, sonst sammelt sich bei jedem Aufruf eine an.
if ! grep -q '^# --- Projekt Scanner' "$CRM_ENV"; then
printf '\n# --- Projekt Scanner (eingerichtet durch deploy/scanner-setup.sh) ---\n' >> "$CRM_ENV"
fi
upsert SCANNER_DB_NAME perinco_scanner
upsert SCANNER_UPLOAD_DIR /var/www/perinco_CRM/server/uploads
upsert SCANNER_SCHEDULER true
upsert SCANNER_SCHEDULER_OFFSET 30
if grep -qE '^SCANNER_ENCRYPTION_KEY=.+' "$CRM_ENV"; then
echo " SCANNER_ENCRYPTION_KEY: bleibt unveraendert"
else
sed -i '/^SCANNER_ENCRYPTION_KEY=/d' "$CRM_ENV"
printf 'SCANNER_ENCRYPTION_KEY=%s\n' "$(openssl rand -hex 32)" >> "$CRM_ENV"
echo " SCANNER_ENCRYPTION_KEY: neu erzeugt (64 Hex-Zeichen)"
fi
echo "== Uebernahme aus der alten App =="
if [ ! -f "$LEGACY_ENV" ]; then
echo " $LEGACY_ENV nicht gefunden - LEGACY_* bleibt leer, Import faellt aus."
else
LEGACY_DB_NAME_VALUE="$(legacy_value DB_NAME)"
LEGACY_DB_USER_VALUE="$(legacy_value DB_USER)"
LEGACY_DB_PASSWORD_VALUE="$(legacy_value DB_PASSWORD)"
LEGACY_DB_HOST_VALUE="$(legacy_value DB_HOST)"
LEGACY_DB_PORT_VALUE="$(legacy_value DB_PORT)"
LEGACY_KEY_VALUE="$(legacy_value ENCRYPTION_KEY)"
upsert LEGACY_DB_NAME "${LEGACY_DB_NAME_VALUE:-project_scanner}"
upsert LEGACY_DB_HOST "${LEGACY_DB_HOST_VALUE:-localhost}"
upsert LEGACY_DB_PORT "${LEGACY_DB_PORT_VALUE:-3306}"
upsert LEGACY_DB_USER "${LEGACY_DB_USER_VALUE:-root}"
upsert LEGACY_DB_PASSWORD "${LEGACY_DB_PASSWORD_VALUE:-}"
upsert LEGACY_UPLOAD_DIR "$LEGACY_DIR/uploads"
if [ -n "$LEGACY_KEY_VALUE" ]; then
upsert LEGACY_ENCRYPTION_KEY "$LEGACY_KEY_VALUE"
else
echo " LEGACY_ENCRYPTION_KEY: in der alten .env nicht gefunden - Portal-Passwoerter werden nicht uebernommen"
fi
fi
chmod 600 "$CRM_ENV"
echo "== Ergebnis (Werte maskiert) =="
grep -E '^(SCANNER|LEGACY)_' "$CRM_ENV" | sed -E 's/=(.+)/= <gesetzt>/' | sort -u
echo "Fertig."