# 01 Architektur
## Bausteine
Drei Teile, keiner davon spricht direkt mit einem anderen ausser ueber HTTP bzw.
SQL:
| Teil | Technik | Wo |
| --- | --- | --- |
| Frontend | React 19, TypeScript, Tailwind 4, Vite | statisches Build in `/var/www/perinco_CRM/dist` |
| Backend | Express 4, mysql2 (CRM), Sequelize (Scanner) | `/var/www/perinco_CRM/server`, `127.0.0.1:5006` |
| Datenbank | MariaDB 10.11 | `perinco_CRM` und `perinco_scanner` |
Der Browser spricht **nie** direkt mit MariaDB - deshalb ueberhaupt ein Backend.
Das Backend bindet ausschliesslich auf `127.0.0.1` und ist von aussen nur ueber
den Apache-Reverse-Proxy erreichbar.
## Topologie
```mermaid
flowchart LR
U(("Browser")) -->|"HTTPS"| A["Apache :443
pcrm.perinco.de"]
A -->|"/"| D["dist/
SPA, FallbackResource"]
A -->|"/api"| E["Express :5006"]
E --> C[("perinco_CRM")]
E --> S[("perinco_scanner")]
U2(("Browser")) -->|"HTTPS"| A2["Apache :443
scanner.perinco.de"]
A2 -->|"/api, /uploads, /health"| E2["alte Scanner-App :5005"]
E2 --> S2[("project_scanner")]
classDef alt fill:#f7f7f7,stroke:#9a9a9a,stroke-dasharray: 4 3
class U2,A2,E2,S2 alt
```
Der Scanner im CRM haengt unter `/api/scanner` und braucht damit **keine eigene
Apache-Regel**: alles laeuft ueber den bestehenden `/api`-Proxy.
## Anfrageweg mit Anmeldung
```mermaid
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 crm_users WHERE username
M-->>E: Zeile mit bcrypt-Hash
E->>E: bcrypt.compare
E-->>B: { token (JWT, 12 h), user }
Note over B: Token im localStorage
B->>A: GET /api/scanner/matches
Authorization: Bearer ...
A->>E: weiterleiten
E->>E: requireAuth: JWT pruefen
E->>M: SELECT crm_users WHERE id (ist der noch aktiv?)
E->>M: SELECT matches JOIN agents, projects
M-->>E: Treffer
E-->>B: JSON
```
Bei `401` loescht das Frontend das Token und springt zurueck auf den
Anmeldeschirm (`setUnauthorizedHandler` in `src/lib/api.ts`). Das Token wird bei
jeder Anfrage neu gegen die Datenbank geprueft - ein deaktivierter Benutzer ist
sofort draussen, ohne auf den Ablauf des Tokens zu warten.
## Rollen
```mermaid
flowchart TD
L["Anmeldung"] --> R{"Rolle"}
R -->|admin| A["Opportunities, Kalender, Kontakte, Scanner
Einstellungen: Nutzerverwaltung, Mein Konto, Scanner, Whisper"]
R -->|user| U["Opportunities, Kalender, Kontakte, Scanner
Einstellungen: Mein Konto, Whisper"]
```
Die Rollenpruefung sitzt **doppelt**: das Menue blendet aus, was nicht erlaubt
ist, und das Backend weist Zugriffe zusaetzlich mit `403` ab (`requireAdmin`).
Ausnahme ist `GET /api/users/directory` - die Benutzerliste brauchen alle Rollen
fuer Netzwerkdiagramm, Zustaendigkeiten, @-Mentions, Teilnehmerauswahl und
Kalenderfarben. Passwort-Hashes liefert der Endpunkt nie.
Ein Sicherheitsnetz im Backend verhindert, dass der letzte aktive Administrator
degradiert, deaktiviert oder geloescht wird.
## Belegte Ports auf dem Server
Der Rechner hostet weitere Dienste (n8n, Docker, ~15 Apache-Vhosts). Vor jeder
Portbelegung `ss -tlnp` pruefen. Relevant hier:
| Port | Dienst |
| --- | --- |
| 5006 | perinco CRM Backend (nur `127.0.0.1`) |
| 5005 | alte Scanner-App (`0.0.0.0`) |
| 3000 | weitere Vite-App |
| 5678 | n8n |