feat(postfach): crm@perinco.de lesend anbinden, Kontakte aus Mails
Spiegelt das Vertriebspostfach ueber Microsoft Graph ins CRM. Aus jeder Mail entstehen Kontakt-Vorschlaege, und jede Mail laesst sich an Opportunities und Kontakte haengen. Uebernommen ist die Idee aus dem Altsystem KeepTrack, nicht die Umsetzung: - Delta Query statt "$filter=isRead eq false" mit anschliessendem PATCH. Damit genuegt die Anwendungsberechtigung Mail.Read, es braucht keinen Schreibzugriff auf das Postfach, und eine vorher in Outlook geoeffnete Mail geht nicht verloren. Der Fortschritt liegt als delta_link in der Datenbank und ueberlebt Neustarts; ein Lauf ist gedeckelt und fortsetzbar. - Tenant- und Client-ID kommen aus der .env, nicht aus dem Browser. Im Altsystem konnte jeder Angemeldete das Server-Secret gegen einen fremden Tenant verwenden. - Kontaktdaten werden aus den Graph-Feldern und der Signatur gelesen, ohne Sprachmodell. Mailinhalte verlassen den Server nicht. - Ein Vorschlag ohne E-Mail-Adresse wird nicht mit einer erfundenen Adresse angelegt, sondern bleibt zur Durchsicht liegen. Ist die Adresse schon als Kontakt vorhanden, werden nur leere Felder ergaenzt. - Mails haengen als eigene Verknuepfung an Opportunity und Kontakt, nicht als Text im Notizfeld - loesbar und mehrfach moeglich. - Fremdes Mail-HTML wird serverseitig entschaerft und in einem iframe mit sandbox="" gezeigt. Externe Bilder sind per CSP gesperrt, damit das Oeffnen dem Absender nichts meldet. - Der Verbindungstest prueft Anmeldung und tatsaechlichen Postfachzugriff und benennt den wahrscheinlichen Grund. Der alte Test holte nur ein Token. Fehlen die MS_*-Werte, startet das CRM normal; die Einstellungen nennen die fehlende Variable. Neue Tabellen: crm_mail_messages, crm_mail_links, crm_mail_contact_candidates, crm_mail_sync_state, crm_settings. Uebernommene Kontakte tragen source = 'Mail'; die Kontaktliste hat dafuer einen Herkunftsfilter. Nicht enthalten: Senden, Antworten, Weiterleiten, Anhaenge und die automatische Zuordnung zu Opportunities. npm test im Verzeichnis server prueft Kontaktextraktion und Graph-Client (47 Pruefungen, ohne Framework und ohne Datenbank). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3647201111
commit
6fd713497d
@@ -53,10 +53,18 @@ ansehen - siehe *Oberflaeche pruefen ohne Datenbank* weiter unten.
|
||||
```bash
|
||||
npm run lint # = tsc --noEmit, das ist die Wahrheit
|
||||
npm run build # muss ohne Fehler durchlaufen
|
||||
cd server && npm test # Pruefungen der Postfachanbindung
|
||||
```
|
||||
|
||||
Es gibt **keine automatischen Tests**. Wer etwas aendert, prueft mit `tsc`, einem
|
||||
Build und einem Blick in die Oberflaeche.
|
||||
Automatische Tests gibt es nur fuer **einen** Bereich: `server/test/mail.test.mjs`
|
||||
prueft die Kontaktextraktion aus Mails und den Graph-Client. Beides laeuft
|
||||
unbeaufsichtigt im Hintergrund und laesst sich von Hand kaum nachstellen - ein
|
||||
falsch erkannter Name landet still in der Kontaktliste, und ein 429 vom echten
|
||||
Dienst kommt genau dann, wenn niemand hinsieht. Kein Test-Framework, reines Node,
|
||||
keine Datenbank.
|
||||
|
||||
Fuer alles andere gilt: `tsc`, ein Build und ein Blick in die Oberflaeche - notfalls
|
||||
mit dem Stub aus [Oberflaeche pruefen ohne Datenbank](#oberflaeche-pruefen-ohne-datenbank).
|
||||
|
||||
## Aufbau
|
||||
|
||||
@@ -69,8 +77,10 @@ src/ Frontend
|
||||
server/src/ Backend
|
||||
server.js Express-App, Reihenfolge der Routen
|
||||
db.js Verbindungspool, Schema-Init, Admin-Seed
|
||||
settings.js crm_settings lesen und schreiben (ohne Geheimnisse)
|
||||
routes/ ein Router je Ressource
|
||||
middleware/auth.js JWT pruefen, Adminrechte
|
||||
mail/ Postfach crm@perinco.de ueber Microsoft Graph
|
||||
scanner/ portierter Projekt Scanner (Sequelize, Playwright, OpenAI)
|
||||
docs/ diese Dokumentation
|
||||
deploy/ Apache-Vhosts, systemd-Unit, Deployskript
|
||||
@@ -141,6 +151,22 @@ Ein Sprung dorthin aus einer beliebigen Tiefe:
|
||||
2. In `server/src/server.js` einhaengen - **vor** dem `/api`-404-Fallback.
|
||||
3. In `src/lib/api.ts` eine typisierte Funktion ergaenzen.
|
||||
|
||||
### Neue Einstellung im CRM
|
||||
|
||||
Nur fuer Werte, die im Betrieb verstellbar sein sollen. **Zugangsdaten gehoeren
|
||||
nicht dazu** - die stehen in `server/.env` und werden beim Start gelesen, damit ein
|
||||
Datenbank-Dump keine Geheimnisse enthaelt.
|
||||
|
||||
1. Schluessel samt Vorgabe in das Vorgabenobjekt des Bereichs eintragen (Muster:
|
||||
`MAIL_SETTING_DEFAULTS` in `server/src/mail/config.js`). Es ist gleichzeitig die
|
||||
Positivliste des Speicher-Endpunkts.
|
||||
2. Lesen mit `getSettings(defaults)` aus `server/src/settings.js`, schreiben mit
|
||||
`setSetting()`. Zahlen und Schalter mit `asInt`/`asBool` wandeln.
|
||||
3. Feld im passenden Einstellungsreiter ergaenzen.
|
||||
|
||||
Die Tabelle `crm_settings` legt `schema.sql` an; ein neuer Schluessel braucht keine
|
||||
Migration.
|
||||
|
||||
## Fallen
|
||||
|
||||
Diese Punkte haben real Zeit gekostet:
|
||||
@@ -157,6 +183,10 @@ Diese Punkte haben real Zeit gekostet:
|
||||
| **`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. |
|
||||
| **`allow-same-origin` bei `srcDoc`** | Ein so eingebettetes Dokument erbt die Origin der Anwendung und kann `localStorage` lesen - also das Anmeldetoken. Fremdes HTML gehoert in `sandbox=""` ohne jedes Flag. Der Preis: die Inhaltshoehe ist nicht auslesbar, deshalb hat der Mailrahmen eine feste Hoehe. |
|
||||
| **`/me` unter App-only** | Es gibt keinen Benutzer im Token, also auch kein `/me` - jeder Graph-Aufruf muss `/users/{postfach}/…` lauten. Genau daran ist das Altsystem gescheitert, still, weil die Antwort nicht geprueft wurde. |
|
||||
| **UTC neben Wandzeit** | `crm_mail_messages.received_at` steht in UTC (so liefert Graph es), `crm_events.starts_at` in lokaler Wandzeit. Beim Lesen im Frontend `parseMailDate()` benutzen, sonst ist die Anzeige um Stunden verschoben. |
|
||||
| **Aufzaehlungen in der Datenbank sind deutsch** | `crm_mail_links.origin` (`manuell`/`automatisch`), Vorschlagsstatus (`offen`, `uebernommen`, `verworfen`, `dublette`). Wer englische Werte schickt, bekommt einen Datenbankfehler statt einer Validierungsmeldung. |
|
||||
|
||||
## Oberflaeche pruefen ohne Datenbank
|
||||
|
||||
|
||||
Reference in New Issue
Block a user