Files
ICON-CRM/docs/01-architektur.md
T

5.2 KiB
Raw Blame History

01 Architektur

Bausteine

Drei Teile; keiner spricht direkt mit einem anderen außer über HTTP bzw. SQL:

Teil Technik Wo
Frontend React 19, TypeScript, Tailwind 4, Vite 6 statisches Build in /var/www/icon_CRM/dist
Backend Express 4, mysql2, JWT, bcrypt /var/www/icon_CRM/server, 127.0.0.1:5007
Datenbank MariaDB icon_CRM

Der Browser spricht nie direkt mit MariaDB – deshalb überhaupt ein Backend. Es bindet ausschließlich auf 127.0.0.1 und ist von außen nur über den Apache-Reverse-Proxy erreichbar.

Das ersetzt den vorherigen Aufbau: dort lag der Supabase-Anon-Key im Browser-Bundle und hatte vollen Lesezugriff auf alle Tabellen, icon_users samt Passwort-Hashes eingeschlossen.

Topologie

flowchart LR
    U(("Browser")) -->|"HTTPS"| A["Apache :443<br/>icrm + crm.icon-experts.de"]
    A -->|"/"| D["dist/<br/>SPA, FallbackResource"]
    A -->|"/api"| E["Express :5007"]
    E --> C[("icon_CRM")]

    W(("Besucher")) -->|"HTTPS"| B["Apache :443<br/>icon-experts.de"]
    B -->|"/"| WD["icon_website/dist"]
    B -->|"/api/public"| E

Beide Vhosts liegen auf demselben Rechner. Die Website reicht nur /api/public durch – Anmeldung, Kontakte und Leads sind von dort aus unerreichbar. Der Umweg über den eigenen Vhost ist Absicht: so bleibt der Aufruf gleicher Herkunft und die Content-Security-Policy der Website kommt weiter mit connect-src 'self' aus.

Ausgehende Verbindungen

Das Backend spricht von sich aus mit drei fremden Diensten. Alle sind optional – fehlen die Zugangsdaten oder ist das Ziel nicht erreichbar, läuft die Anwendung ohne den jeweiligen Teil weiter.

Ziel Wofür Ausgelöst von
api.openai.com CV-Auslesung, Schreibhilfe, erweiterte Suche Benutzer, jeweils auf Klick
pcrm.perinco.de Kontakte des perinco CRM lesen Schalter „perinco" in den Kontakten
linkedin.com, media.licdn.com Profilfoto zu einem Kontakt holen Knopf im Kontaktformular

Das Diktat taucht in dieser Liste nicht auf: es lädt sein Modell direkt aus dem Browser vom Hugging-Face-CDN, das Backend ist daran nicht beteiligt (siehe 04 Diktat).

Anfrageweg mit Anmeldung

sequenceDiagram
    actor B as Browser
    participant A as Apache
    participant E as Express
    participant M as MariaDB

    B->>A: POST /api/auth/login
    A->>E: weiterleiten
    E->>M: SELECT icon_users WHERE username OR email
    M-->>E: Zeile mit Hash
    E->>E: bcrypt.compare (SHA-256 des Altbestands wird dabei umgeschrieben)
    E-->>B: { token (JWT, 12 h), user }
    Note over B: Token im localStorage

    B->>A: GET /api/contacts<br/>Authorization: Bearer ...
    A->>E: weiterleiten
    E->>E: requireAuth: JWT pruefen
    E->>M: SELECT icon_users WHERE id (noch aktiv?)
    E->>M: SELECT contacts JOIN companies
    M-->>E: Zeilen
    E-->>B: JSON

Bei 401 löscht das Frontend das Token und springt auf den Anmeldeschirm (setUnauthorizedHandler in src/lib/api.ts). Das Token wird bei jeder Anfrage neu gegen die Datenbank geprüft – ein deaktivierter Benutzer ist sofort draußen, ohne auf den Ablauf des Tokens zu warten.

Passwörter aus dem Altbestand: im alten CRM lagen sie als ungesalzenes SHA-256. Diese Hashes sind an ihrer Form erkennbar (64 Hexziffern) und werden beim ersten erfolgreichen Login auf bcrypt umgeschrieben. Neu vergeben wird ausschließlich bcrypt.

Rollen

flowchart TD
    L["Anmeldung"] --> R{"Rolle"}
    R -->|admin| A["Dashboard, Leads, Kontakte, Unternehmen, Ausschreibungen,<br/>Laufende Projekte, Historie<br/>Einstellungen: Nutzerverwaltung, Stammdaten, KI, Mein Konto, Diktat"]
    R -->|user| U["Dashboard, Leads, Kontakte, Unternehmen, Ausschreibungen,<br/>Laufende Projekte, Historie<br/>Einstellungen: Mein Konto, Diktat"]

Die Prüfung sitzt doppelt: das Menü blendet aus, was nicht erlaubt ist, und das Backend weist zusätzlich mit 403 ab (requireAdmin). Ausnahme ist GET /api/users/directory – die Benutzerliste brauchen alle Rollen für Erwähnungen, Zuständigkeiten und die Farben in der Historie. Passwort-Hashes liefert der Endpunkt nie.

Ein Sicherheitsnetz im Backend verhindert, dass der letzte aktive Administrator degradiert, deaktiviert oder gelöscht wird.

Der Reiter Diktat ist bewusst für jede Rolle sichtbar: jeder richtet die Spracherkennung auf seinem eigenen Gerät ein, mit seinem eigenen Download.

Belegte Ports

Port Dienst
5007 ICON CRM Backend (nur 127.0.0.1)
3101 Vite-Dev-Server (nur lokal beim Entwickeln)
80/443 Apache: icrm.icon-experts.de (+ Alias crm.icon-experts.de), icon-experts.de, www.icon-experts.de

Was wo liegt

/var/www/icon_CRM/dist         SPA (Build)
/var/www/icon_CRM/server       Backend samt .env (chmod 600)
/var/www/icon_CRM/uploads      hochgeladene Lebenslaeufe
/var/www/icon_website/dist     ICON Website
/etc/systemd/system/icon-crm-backend.service
/etc/apache2/sites-available/icrm.icon-experts.de*.conf
/etc/apache2/sites-available/icon-experts.de*.conf