Update README and add detailed documentation for Advoware integration and calendar sync

This commit is contained in:
bsiggel
2026-03-01 23:03:01 +00:00
parent 52356e634e
commit 0b8da01b71
4 changed files with 1076 additions and 15 deletions

View File

@@ -0,0 +1,268 @@
# Advoware Calendar Sync - Event-Driven Design
Dieser Abschnitt implementiert die bidirektionale Synchronisation zwischen Advoware-Terminen und Google Calendar. Das System nutzt einen event-driven Ansatz mit **Motia III v1.0-RC**, der auf direkten API-Calls basiert, mit Redis für Locking und Deduplikation. Es stellt sicher, dass Termine konsistent gehalten werden, mit Fokus auf Robustheit, Fehlerbehandlung und korrekte Handhabung von mehrtägigen Terminen.
## Übersicht
Das System synchronisiert Termine zwischen:
- **Advoware**: Zentrale Terminverwaltung mit detaillierten Informationen.
- **Google Calendar**: Benutzerfreundliche Kalenderansicht für jeden Mitarbeiter.
## Architektur
### Event-Driven Design
- **Direkte API-Synchronisation**: Kein zentraler Hub; Sync läuft direkt zwischen APIs.
- **Redis Locking**: Per-Employee Locking verhindert Race-Conditions.
- **Event Emission**: Cron → All-Step → Employee-Step für skalierbare Verarbeitung.
- **Fehlerresistenz**: Einzelne Fehler stoppen nicht den gesamten Sync.
- **Logging**: Alle Logs erscheinen in der iii Console via `ctx.logger`.
### Sync-Phasen
1. **Cron-Step**: Automatische Auslösung alle 15 Minuten.
2. **All-Step**: Fetcht alle Mitarbeiter und emittiert Events pro Employee.
3. **Employee-Step**: Synchronisiert Termine für einen einzelnen Mitarbeiter.
### Datenmapping und Standardisierung
Beide Systeme werden auf gemeinsames Format normalisiert (Berlin TZ):
```python
{
'start': datetime, # Berlin TZ
'end': datetime,
'text': str,
'notiz': str,
'ort': str,
'dauertermin': int, # 0/1
'turnus': int, # 0/1
'turnusArt': int,
'recurrence': str # RRULE oder None
}
```
#### Advoware → Standard
- Start: `datum` + `uhrzeitVon` (Fallback 09:00), oder `datum` als datetime.
- End: `datumBis` + `uhrzeitBis` (Fallback 10:00), oder `datum` + 1h.
- All-Day: `dauertermin=1` oder Dauer >1 Tag.
- Recurring: `turnus`/`turnusArt` (vereinfacht, keine RRULE).
#### Google → Standard
- Start/End: `dateTime` oder `date` (All-Day).
- All-Day: `dauertermin=1` wenn All-Day oder Dauer >1 Tag.
- Recurring: RRULE aus `recurrence`.
#### Standard → Advoware
- POST/PUT: `datum`/`uhrzeitBis`/`datumBis` aus start/end.
- Defaults: `vorbereitungsDauer='00:00:00'`, `sb`/`anwalt`=employee_kuerzel.
#### Standard → Google
- All-Day: `date` statt `dateTime`, end +1 Tag.
- Recurring: RRULE aus `recurrence`.
## Funktionalität
### Automatische Kalender-Erstellung
- Für jeden Advoware-Mitarbeiter wird ein Google Calendar mit dem Namen `AW-{Kuerzel}` erstellt.
- Beispiel: Mitarbeiter mit Kürzel "SB" → Calendar "AW-SB".
- Kalender wird mit dem Haupt-Google-Account (`lehmannundpartner@gmail.com`) als Owner geteilt.
### Sync-Details
#### Cron-Step (calendar_sync_cron_step.py)
- Läuft alle 15 Minuten und emittiert "calendar_sync_all".
- **Trigger**: `cron("0 */15 * * * *")` (6-field: Sekunde Minute Stunde Tag Monat Wochentag)
#### All-Step (calendar_sync_all_step.py)
- Fetcht alle Mitarbeiter aus Advoware.
- Filtert Debug-Liste (falls konfiguriert).
- Setzt Redis-Lock pro Employee.
- Emittiert "calendar_sync" Event pro Employee.
- **Trigger**: `queue('calendar_sync_all')`
#### Employee-Step (calendar_sync_event_step.py)
- Fetcht Advoware-Termine für den Employee.
- Fetcht Google-Events für den Employee.
- Synchronisiert: Neue erstellen, Updates anwenden, Deletes handhaben.
- Verwendet Locking, um parallele Syncs zu verhindern.
- **Trigger**: `queue('calendar_sync')`
#### API-Step (calendar_sync_api_step.py)
- Manueller Trigger für einzelnen Employee oder "ALL".
- Bei "ALL": Emittiert "calendar_sync_all".
- Bei Employee: Setzt Lock und emittiert "calendar_sync".
- **Trigger**: `http('POST', '/advoware/calendar/sync')`
## API-Schwächen und Fixes
### Advoware API
- **Mehrtägige Termine**: `datumBis` wird korrekt für Enddatum verwendet; '00:00:00' als '23:59:59' interpretiert.
- **Zeitformate**: Robuste Parsing mit Fallbacks.
- **Keine 24h-Limit**: Termine können länger als 24h sein; Google Calendar unterstützt das.
### Google Calendar API
- **Zeitbereiche**: Akzeptiert Events >24h ohne Probleme.
- **Rate Limits**: Backoff-Retry implementiert.
## Step-Konfiguration (Motia III)
### calendar_sync_cron_step.py
```python
config = {
'name': 'Calendar Sync Cron Job',
'flows': ['advoware'],
'triggers': [
cron("0 */15 * * * *") # Alle 15 Minuten (6-field format)
],
'enqueues': ['calendar_sync_all']
}
```
### calendar_sync_all_step.py
```python
config = {
'name': 'Calendar Sync All Step',
'flows': ['advoware'],
'triggers': [
queue('calendar_sync_all')
],
'enqueues': ['calendar_sync']
}
```
### calendar_sync_event_step.py
```python
config = {
'name': 'Calendar Sync Event Step',
'flows': ['advoware'],
'triggers': [
queue('calendar_sync')
],
'enqueues': []
}
```
### calendar_sync_api_step.py
```python
config = {
'name': 'Calendar Sync API Trigger',
'flows': ['advoware'],
'triggers': [
http('POST', '/advoware/calendar/sync')
],
'enqueues': ['calendar_sync', 'calendar_sync_all']
}
```
## Setup
### Umgebungsvariablen
```env
# Google Calendar
GOOGLE_CALENDAR_SERVICE_ACCOUNT_PATH=service-account.json
# Advoware API
ADVOWARE_API_BASE_URL=https://www2.advo-net.net:90/
ADVOWARE_PRODUCT_ID=64
ADVOWARE_APP_ID=your_app_id
ADVOWARE_API_KEY=your_api_key
ADVOWARE_KANZLEI=your_kanzlei
ADVOWARE_DATABASE=your_database
ADVOWARE_USER=your_user
ADVOWARE_ROLE=2
ADVOWARE_PASSWORD=your_password
ADVOWARE_TOKEN_LIFETIME_MINUTES=55
ADVOWARE_API_TIMEOUT_SECONDS=30
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB_CALENDAR_SYNC=1
REDIS_TIMEOUT_SECONDS=5
# Debug
CALENDAR_SYNC_DEBUG_EMPLOYEES=PB,AI # Optional, filter employees
```
## Verwendung
### Manueller Sync
```bash
# Sync für einen bestimmten Mitarbeiter
curl -X POST "http://localhost:3111/advoware/calendar/sync" \
-H "Content-Type: application/json" \
-d '{"kuerzel": "PB"}'
# Sync für alle Mitarbeiter
curl -X POST "http://localhost:3111/advoware/calendar/sync" \
-H "Content-Type: application/json" \
-d '{"kuerzel": "ALL"}'
```
### Automatischer Sync
Cron-Step läuft automatisch alle 15 Minuten.
## Fehlerbehandlung und Logging
- **Locking**: Redis NX/EX verhindert parallele Syncs.
- **Logging**: `ctx.logger` für iii Console-Sichtbarkeit.
- **API-Fehler**: Retry mit Backoff.
- **Parsing-Fehler**: Robuste Fallbacks.
## Sicherheit
- Service Account für Google Calendar API.
- HMAC-512 Authentifizierung für Advoware API.
- Redis für Concurrency-Control.
## Bekannte Probleme
- **Recurring-Events**: Begrenzte Unterstützung für komplexe Wiederholungen.
- **Performance**: Bei vielen Terminen Paginierung beachten.
- **Timezone-Handling**: Alle Operationen in Europe/Berlin TZ.
## Datenfluss
```
Cron (alle 15min)
→ calendar_sync_cron_step
→ ctx.enqueue(topic: "calendar_sync_all")
→ calendar_sync_all_step
→ Fetch Employees from Advoware
→ For each Employee:
→ Set Redis Lock (key: calendar_sync:employee:{kuerzel})
→ ctx.enqueue(topic: "calendar_sync", data: {kuerzel, ...})
→ calendar_sync_event_step
→ Fetch Advoware Termine (frNr, datum, text, etc.)
→ Fetch Google Calendar Events
→ 4-Phase Sync:
1. New from Advoware → Google
2. New from Google → Advoware
3. Process Deletes
4. Process Updates
→ Clear Redis Lock
```
## Weitere Dokumentation
- **Individual Step Docs**: Siehe `docs/` Ordner in diesem Verzeichnis
- **Architecture Overview**: [../../docs/ARCHITECTURE.md](../../docs/ARCHITECTURE.md)
- **Google Setup Guide**: [../../docs/GOOGLE_SETUP.md](../../docs/GOOGLE_SETUP.md)
- **Troubleshooting**: [../../docs/TROUBLESHOOTING.md](../../docs/TROUBLESHOOTING.md)
## Migration Notes
Dieses System wurde von **Motia v0.17** nach **Motia III v1.0-RC** migriert:
### Wichtige Änderungen:
-`type: 'event'``triggers: [queue('topic')]`
-`type: 'cron'``triggers: [cron('expression')]` (6-field format)
-`type: 'api'``triggers: [http('METHOD', 'path')]`
-`context.emit()``ctx.enqueue()`
-`emits: [...]``enqueues: [...]`
- ✅ Relative Imports → Absolute Imports mit `sys.path.insert()`
- ✅ Motia Workbench → iii Console
### Kompatibilität:
- ✅ Alle 4 Steps vollständig migriert
- ✅ Google Calendar API Integration unverändert
- ✅ Advoware API Integration unverändert
- ✅ Redis Locking-Mechanismus unverändert
- ✅ Datenbank-Schema kompatibel

View File

@@ -0,0 +1,314 @@
# Advoware API Proxy Steps
Dieser Ordner enthält die API-Proxy-Steps für die Advoware-Integration. Jeder Step implementiert eine HTTP-Methode als universellen Proxy zur Advoware-API mit **Motia III v1.0-RC**.
## Übersicht
Die Proxy-Steps fungieren als transparente Schnittstelle zwischen Clients und der Advoware-API. Sie handhaben Authentifizierung, Fehlerbehandlung und Logging automatisch.
## Steps
### 1. GET Proxy (`advoware_api_proxy_get_step.py`)
**Zweck:** Universeller Proxy für GET-Requests an die Advoware-API.
**Konfiguration:**
```python
config = {
'name': 'Advoware Proxy GET',
'flows': ['advoware'],
'triggers': [
http('GET', '/advoware/proxy')
],
'enqueues': []
}
```
**Funktionalität:**
- Extrahiert den Ziel-Endpoint aus Query-Parametern (`endpoint`)
- Übergibt alle anderen Query-Parameter als API-Parameter
- Gibt das Ergebnis als JSON zurück
**Beispiel Request:**
```bash
GET /advoware/proxy?endpoint=employees&limit=10&offset=0
```
**Response:**
```json
{
"result": {
"data": [...],
"total": 100
}
}
```
### 2. POST Proxy (`advoware_api_proxy_post_step.py`)
**Zweck:** Universeller Proxy für POST-Requests an die Advoware-API.
**Konfiguration:**
```python
config = {
'name': 'Advoware Proxy POST',
'flows': ['advoware'],
'triggers': [
http('POST', '/advoware/proxy')
],
'enqueues': []
}
```
**Funktionalität:**
- Extrahiert den Ziel-Endpoint aus Query-Parametern (`endpoint`)
- Verwendet den Request-Body als JSON-Daten für die API
- Erstellt neue Ressourcen in Advoware
**Beispiel Request:**
```bash
POST /advoware/proxy?endpoint=employees
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}
```
### 3. PUT Proxy (`advoware_api_proxy_put_step.py`)
**Zweck:** Universeller Proxy für PUT-Requests an die Advoware-API.
**Konfiguration:**
```python
config = {
'name': 'Advoware Proxy PUT',
'flows': ['advoware'],
'triggers': [
http('PUT', '/advoware/proxy')
],
'enqueues': []
}
```
**Funktionalität:**
- Extrahiert den Ziel-Endpoint aus Query-Parametern (`endpoint`)
- Verwendet den Request-Body als JSON-Daten für Updates
- Aktualisiert bestehende Ressourcen in Advoware
**Beispiel Request:**
```bash
PUT /advoware/proxy?endpoint=employees/123
Content-Type: application/json
{
"name": "John Smith",
"email": "johnsmith@example.com"
}
```
### 4. DELETE Proxy (`advoware_api_proxy_delete_step.py`)
**Zweck:** Universeller Proxy für DELETE-Requests an die Advoware-API.
**Konfiguration:**
```python
config = {
'name': 'Advoware Proxy DELETE',
'flows': ['advoware'],
'triggers': [
http('DELETE', '/advoware/proxy')
],
'enqueues': []
}
```
**Funktionalität:**
- Extrahiert den Ziel-Endpoint aus Query-Parametern (`endpoint`)
- Löscht Ressourcen in Advoware
**Beispiel Request:**
```bash
DELETE /advoware/proxy?endpoint=employees/123
```
## Gemeinsame Features
### Authentifizierung
Alle Steps verwenden den `AdvowareService` für automatische Token-Verwaltung und Authentifizierung:
- HMAC-512 basierte Signatur
- Token-Caching in Redis (55 Minuten Lifetime)
- Automatischer Token-Refresh bei 401-Errors
### Fehlerbehandling
- **400 Bad Request:** Fehlender `endpoint` Parameter
- **500 Internal Server Error:** API-Fehler oder Exceptions
- **401 Unauthorized:** Automatischer Token-Refresh und Retry
### Logging
Detaillierte Logs via `ctx.logger` für:
- Eingehende Requests
- API-Calls an Advoware
- Fehler und Exceptions
- Token-Management
Alle Logs sind in der **iii Console** sichtbar.
### Sicherheit
- Keine direkte Weitergabe sensibler Daten
- Authentifizierung über Service-Layer
- Input-Validation für erforderliche Parameter
- HMAC-512 Signatur für alle API-Requests
## Handler-Struktur (Motia III)
Alle Steps folgen dem gleichen Pattern:
```python
from motia import http, ApiRequest, ApiResponse, FlowContext
from services.advoware_service import AdvowareService
config = {
'name': 'Advoware Proxy {METHOD}',
'flows': ['advoware'],
'triggers': [
http('{METHOD}', '/advoware/proxy')
],
'enqueues': []
}
async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse:
# Extract endpoint from query params
endpoint = request.query_params.get('endpoint')
if not endpoint:
return ApiResponse(
status=400,
body={'error': 'Missing required query parameter: endpoint'}
)
# Call Advoware API
advoware_service = AdvowareService()
result = await advoware_service.{method}(endpoint, **params)
return ApiResponse(status=200, body={'result': result})
```
## Testing
### Unit Tests
```bash
# Test GET Proxy
curl -X GET "http://localhost:3111/advoware/proxy?endpoint=employees"
# Test POST Proxy
curl -X POST "http://localhost:3111/advoware/proxy?endpoint=employees" \
-H "Content-Type: application/json" \
-d '{"name": "Test Employee"}'
# Test PUT Proxy
curl -X PUT "http://localhost:3111/advoware/proxy?endpoint=employees/1" \
-H "Content-Type: application/json" \
-d '{"name": "Updated Employee"}'
# Test DELETE Proxy
curl -X DELETE "http://localhost:3111/advoware/proxy?endpoint=employees/1"
```
### Integration Tests
Überprüfen Sie die Logs in der iii Console:
```bash
# Check logs
curl http://localhost:3111/_console/logs
```
## Konfiguration
### Umgebungsvariablen
Stellen Sie sicher, dass folgende Variablen gesetzt sind:
```env
ADVOWARE_API_BASE_URL=https://www2.advo-net.net:90/
ADVOWARE_PRODUCT_ID=64
ADVOWARE_APP_ID=your_app_id
ADVOWARE_API_KEY=your_api_key
ADVOWARE_KANZLEI=your_kanzlei
ADVOWARE_DATABASE=your_database
ADVOWARE_USER=your_user
ADVOWARE_ROLE=2
ADVOWARE_PASSWORD=your_password
ADVOWARE_TOKEN_LIFETIME_MINUTES=55
ADVOWARE_API_TIMEOUT_SECONDS=30
# Redis (für Token-Caching)
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0
```
### Dependencies
- `services/advoware_service.py` - Advoware API Client mit HMAC Auth
- `config.py` - Konfigurationsmanagement
- `motia` - Motia III Python SDK
- `asyncpg` - PostgreSQL Client
- `redis` - Redis Client für Token-Caching
## Erweiterungen
### Geplante Features
- Request/Response Caching für häufige Queries
- Rate Limiting pro Client
- Request Validation Schemas mit Pydantic
- Batch-Operations Support
### Custom Endpoints
Für spezifische Endpoints können zusätzliche Steps erstellt werden, die direkt auf bestimmte Ressourcen zugreifen und erweiterte Validierung/Transformation bieten.
## Architektur
```
Client Request
HTTP Trigger (http('METHOD', '/advoware/proxy'))
Handler (ApiRequest → ApiResponse)
├─► Extract 'endpoint' from query params
├─► Extract other params/body
AdvowareService
├─► Check Redis for valid token
├─► If expired: Get new token (HMAC-512 auth)
├─► Build HTTP request
Advoware API
Response → Transform → Return ApiResponse
```
## Migration Notes
Dieses System wurde von **Motia v0.17** nach **Motia III v1.0-RC** migriert:
### Wichtige Änderungen:
-`type: 'api'``triggers: [http('METHOD', 'path')]`
-`ApiRouteConfig``StepConfig` mit `as const satisfies`
-`Handlers['StepName']``Handlers<typeof config>`
-`context``ctx`
-`req` dict → `ApiRequest` typed object
- ✅ Return dict → `ApiResponse` typed object
-`method`, `path` moved into trigger
- ✅ Motia Workbench → iii Console
### Kompatibilität:
- ✅ Alle 4 Proxy Steps vollständig migriert
- ✅ AdvowareService kompatibel (keine Änderungen)
- ✅ Redis Token-Caching unverändert
- ✅ HMAC-512 Auth unverändert
- ✅ API-Endpoints identisch