docs: Anleitungen fuer Benutzer und Entwickler, Quellcode-Referenz, Glossar
Die bisherige Dokumentation beschrieb die Architektur, half aber niemandem, der die Anwendung zum ersten Mal oeffnet. Dazu jetzt vier Kapitel: - 07 Anleitung fuer Benutzer: was die Anwendung tut, jeder Bereich Schritt fuer Schritt, warum der Signalton nur im Dashboard klingelt, was Loeschen eines Suchagenten mitnimmt - 08 Anleitung fuer Entwickler: einrichten, pruefen, Feld/Menuepunkt/Reiter/ Endpunkt hinzufuegen, dazu eine Tabelle der Fallen, die real Zeit gekostet haben (Routen-Reihenfolge, FormData, Leerzeichen in Pfaden, Zeitstempel koennen nur vorziehen, Kaskaden beim Loeschen) - 09 Quellcode-Referenz: jede Datei mit ihren Exporten und den Zusagen, auf die sich der Rest verlaesst - 10 Glossar: Agent, Match, Lead, Opportunity und die Statuswerte der Datenbank Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e58b94f007
commit
4d90f14d6b
@@ -0,0 +1,195 @@
|
||||
# 08 Anleitung fuer Entwickler
|
||||
|
||||
Fuer jemanden, der das Projekt zum ersten Mal oeffnet. Was man braucht, wie man es
|
||||
startet, wo man was aendert - und welche Fallen es gibt.
|
||||
|
||||
## In zwei Minuten zum Bild
|
||||
|
||||
* **Ein** Vite-Frontend (React 19, TypeScript, Tailwind 4), **ein** Express-Backend,
|
||||
**zwei** MariaDB-Datenbanken.
|
||||
* Kein Router im Frontend: `App.tsx` haelt die Ansicht in einem `useState`. Die
|
||||
App liegt hinter einer Anmeldung, tiefe Links braucht sie nicht.
|
||||
* Kein ORM im CRM-Teil - dort schreibt das Backend SQL mit `mysql2`. Der
|
||||
Scanner-Teil benutzt Sequelize, weil er 1:1 aus einer anderen Anwendung
|
||||
uebernommen wurde.
|
||||
* Alles auf Deutsch: Bezeichner, Kommentare, Meldungen. Kommentare erklaeren das
|
||||
**Warum**, nicht das Was.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
| | Version |
|
||||
| --- | --- |
|
||||
| Node | 20 oder neuer (Server laeuft 20.20) |
|
||||
| MariaDB | 10.11 oder neuer, erreichbar mit Benutzer und Passwort |
|
||||
| Browser | Chrome/Edge fuer WebGPU beim Diktat (sonst laeuft es auf der CPU) |
|
||||
|
||||
## Lokal starten
|
||||
|
||||
```bash
|
||||
git clone http://192.168.1.199:3000/sascha/perinco-CRM.git
|
||||
cd perinco-CRM
|
||||
|
||||
npm install # Frontend
|
||||
cd server && npm install # Backend
|
||||
cp .env.example .env # Werte eintragen, mindestens DB_* und JWT_SECRET
|
||||
npm start # Backend auf :5006
|
||||
cd .. && npm run dev # Frontend auf :3100, /api wird geproxied
|
||||
```
|
||||
|
||||
Was dabei von selbst passiert:
|
||||
|
||||
* `perinco_CRM` muss **existieren**; die Tabellen legt `server/src/schema.sql`
|
||||
beim Start an (idempotent).
|
||||
* `perinco_scanner` legt das Backend **komplett selbst** an - Datenbank, Tabellen,
|
||||
Portale, Standardeinstellungen.
|
||||
* Fehlt `ADMIN_PASSWORD`, wird beim ersten Start ein Zufallspasswort erzeugt und
|
||||
**einmalig ins Log geschrieben**. Dort nachsehen, sonst kommt man nicht rein.
|
||||
|
||||
Ohne MariaDB startet das Backend nicht. Das Frontend laesst sich trotzdem
|
||||
ansehen - siehe *Oberflaeche pruefen ohne Datenbank* weiter unten.
|
||||
|
||||
## Pruefen
|
||||
|
||||
```bash
|
||||
npm run lint # = tsc --noEmit, das ist die Wahrheit
|
||||
npm run build # muss ohne Fehler durchlaufen
|
||||
```
|
||||
|
||||
Es gibt **keine automatischen Tests**. Wer etwas aendert, prueft mit `tsc`, einem
|
||||
Build und einem Blick in die Oberflaeche.
|
||||
|
||||
## Aufbau
|
||||
|
||||
```
|
||||
src/ Frontend
|
||||
App.tsx Ansicht, zentrale Daten, Layout
|
||||
types.ts Typen, Konstanten, Rechenhilfen (Volumen, Farben)
|
||||
lib/ API-Zugriff und Kontexte
|
||||
components/ je Bereich ein Ordner
|
||||
server/src/ Backend
|
||||
server.js Express-App, Reihenfolge der Routen
|
||||
db.js Verbindungspool, Schema-Init, Admin-Seed
|
||||
routes/ ein Router je Ressource
|
||||
middleware/auth.js JWT pruefen, Adminrechte
|
||||
scanner/ portierter Projekt Scanner (Sequelize, Playwright, OpenAI)
|
||||
docs/ diese Dokumentation
|
||||
deploy/ Apache-Vhosts, systemd-Unit, Deployskript
|
||||
```
|
||||
|
||||
## Wie eine Anfrage laeuft
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant K as Komponente
|
||||
participant A as lib/api.ts
|
||||
participant E as Express
|
||||
participant M as middleware/auth
|
||||
participant R as routes/*.js
|
||||
participant D as MariaDB
|
||||
|
||||
K->>A: leadsApi.update(7, { status: 'Gewonnen' })
|
||||
A->>A: Token anhaengen, Content-Type setzen
|
||||
A->>E: PATCH /api/leads/7
|
||||
E->>M: requireAuth
|
||||
M->>D: Benutzer laden, aktiv?
|
||||
M->>R: req.user gesetzt
|
||||
R->>R: Eingaben pruefen und normalisieren
|
||||
R->>D: UPDATE ... ; SELECT (angereichert)
|
||||
R-->>A: 200 JSON
|
||||
A-->>K: typisiertes Objekt
|
||||
```
|
||||
|
||||
Jede Route liefert den **vollstaendigen, angereicherten Datensatz** zurueck (mit
|
||||
`created_by_name`, `owner_name`, Ansprechpartnern, Teilnehmern). Das Frontend muss
|
||||
danach nichts nachladen.
|
||||
|
||||
## Etwas hinzufuegen
|
||||
|
||||
### Neues Feld an einem Kontakt
|
||||
|
||||
1. `server/src/schema.sql`: Spalte mit `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`
|
||||
ergaenzen - das Statement muss beim naechsten Start wiederholbar sein.
|
||||
2. `server/src/routes/leads.js`: Feld in die Listen `TEXT_FIELDS` bzw. in
|
||||
`INSERT`/`UPDATE` und in den `SELECT` aufnehmen.
|
||||
3. `src/types.ts`: Feld ins `Lead`-Interface.
|
||||
4. `src/components/leads/LeadModal.tsx`: Eingabefeld, `inputClass` benutzen.
|
||||
5. Wenn es in Kachel oder Liste sichtbar sein soll: `LeadGrid.tsx`, `LeadList.tsx`.
|
||||
|
||||
### Neuer Menuepunkt
|
||||
|
||||
1. `src/types.ts`: Wert in `ViewState` ergaenzen.
|
||||
2. `src/components/Sidebar.tsx`: Eintrag in `CATEGORY_ITEMS` (Name + SVG-Pfad).
|
||||
3. `src/App.tsx`: Komponente rendern und einen Untertitel setzen.
|
||||
|
||||
Der Name in `ViewState` ist gleichzeitig die Beschriftung und der Titel in der
|
||||
Kopfzeile. Wer eine abweichende Beschriftung braucht, setzt `label` am `MenuItem`
|
||||
(so macht es der Unterpunkt *Dashboard*, der auf die Ansicht `Scanner` zeigt).
|
||||
|
||||
### Neuer Reiter in den Einstellungen
|
||||
|
||||
1. `src/types.ts`: Wert in `SettingsTab`; nur fuer Admins zusaetzlich in
|
||||
`ADMIN_SETTINGS_TABS`.
|
||||
2. `src/components/settings/Settings.tsx`: Eintrag in `TABS` und Rendering.
|
||||
|
||||
Ein Sprung dorthin aus einer beliebigen Tiefe:
|
||||
`goTo('Einstellungen', { tab: 'whisper' })`.
|
||||
|
||||
### Neuer Endpunkt
|
||||
|
||||
1. Router in `server/src/routes/` anlegen, `router.use(requireAuth)` nicht
|
||||
vergessen, `requireAdmin` wo noetig.
|
||||
2. In `server/src/server.js` einhaengen - **vor** dem `/api`-404-Fallback.
|
||||
3. In `src/lib/api.ts` eine typisierte Funktion ergaenzen.
|
||||
|
||||
## Fallen
|
||||
|
||||
Diese Punkte haben real Zeit gekostet:
|
||||
|
||||
| Falle | Was passiert |
|
||||
| --- | --- |
|
||||
| **Reihenfolge in `server.js`** | Der 404-Fallback `app.use('/api', ...)` faengt alles, was danach registriert wird. Neue Router davor einhaengen. |
|
||||
| **`Content-Type` bei FormData** | `apiRequest` setzt JSON nur, wenn der Body **keine** `FormData` ist - sonst fehlt die multipart-Grenze und der Upload scheitert. |
|
||||
| **Dateien in neuen Tabs** | Ein `<a href>` schickt kein Bearer-Token. Deshalb holt `openAgentCv()` das PDF per `fetch` und oeffnet einen Blob. |
|
||||
| **Leerzeichen in Pfaden** | Der alte Scanner liegt unter `/var/www/Projekt Scanner/…`. In der `.env` muss der Wert gequotet sein, sonst schneidet systemd ihn am Leerzeichen ab. |
|
||||
| **`.env` ist gleichzeitig `EnvironmentFile`** | Also kein `export`, keine Shell-Ausdruecke, Werte mit Leerzeichen in Anfuehrungszeichen. |
|
||||
| **Zeitstempel koennen nur vorziehen** | Ein „vorgealterter" `lastRunTime` verzoegert keinen Cronlauf. Wer verzoegern will, braucht eine eigene Frist. |
|
||||
| **Sperrfristen bei jedem Start neu** | Frueher schob jedes Deployment den faelligen Scan um 30 Minuten - er fand nie statt. Deshalb liegt `last_scan_at` in der Datenbank. |
|
||||
| **`sequelize.sync({ alter: true })`** | Legt bei jedem Start Unique-Indizes neu an und sprengt irgendwann MySQLs Grenze von 64 Keys. Nur `sync()` benutzen. |
|
||||
| **Kaskaden beim Loeschen** | Einen Suchagenten loeschen loescht alle seine Treffer mit. Inaktiv setzen ist meist gemeint. |
|
||||
| **Doppelte Scanner-Laeufe** | Scheduler und *Jetzt scannen* teilen eine Sperre. Ohne sie zahlt man jede KI-Bewertung zweimal. |
|
||||
|
||||
## Oberflaeche pruefen ohne Datenbank
|
||||
|
||||
Fuer Bildschirmfotos und Klickwege genuegt ein Stub: ein kleiner `node:http`-Server,
|
||||
der `dist/` ausliefert und auf `/api/...` erfundene Daten zurueckgibt. Token vorab
|
||||
setzen, dann ist die Anmeldung uebersprungen:
|
||||
|
||||
```js
|
||||
await page.goto(BASE);
|
||||
await page.evaluate(() => localStorage.setItem('perinco_crm_token', 'stub-token'));
|
||||
await page.goto(BASE);
|
||||
```
|
||||
|
||||
Playwright startet dabei am besten das **installierte Chrome**
|
||||
(`chromium.launch({ channel: 'chrome' })`) - dann braucht es keinen
|
||||
Browser-Download.
|
||||
|
||||
## Konventionen
|
||||
|
||||
* **Deutsch** in Bezeichnern, Kommentaren, Meldungen. Fachbegriffe bleiben
|
||||
englisch (`Match`, `Opportunity`, `Lead`).
|
||||
* **Kommentare erklaeren das Warum.** Was der Code tut, steht im Code.
|
||||
* **Kein `dark:` im Markup.** Nur die Skalen `ink-*`, `brand-*`, `canvas`,
|
||||
`surface` benutzen, den Rest macht `index.css`.
|
||||
* **Gemeinsame Klassen** statt kopierter Utility-Ketten: `inputClass`,
|
||||
`labelClass` aus `components/leads/leadUi.tsx`, dazu `cardClass`,
|
||||
`primaryButtonClass` aus `components/scanner/scannerUi.tsx`.
|
||||
* **Meldungen als Toast**, Loeschabfragen ueber `ConfirmDialog` - nie `alert()`
|
||||
oder `confirm()`.
|
||||
* **Rechte immer doppelt**: Oberflaeche blendet aus, Backend antwortet mit 403.
|
||||
|
||||
## Deployment
|
||||
|
||||
Siehe [06 Betrieb](06-betrieb.md). Kurzform: lokal bauen, `deploy/deploy.ps1`
|
||||
laedt `dist/` und `server/src` hoch und startet den Dienst neu. Auf dem Server
|
||||
wird **nicht** gebaut - dort sind RAM und Swap knapp.
|
||||
Reference in New Issue
Block a user