feat(sync-utils): add logging delegation method to BaseSyncUtils
This commit is contained in:
845
docs/INDEX.md
845
docs/INDEX.md
@@ -1,129 +1,662 @@
|
||||
# Documentation Index - Motia III
|
||||
# BitByLaw Motia III - Developer Guide
|
||||
|
||||
## Getting Started
|
||||
> **For AI Assistants**: This document contains all critical patterns, conventions, and best practices. Read this first to understand the codebase structure and ensure consistency.
|
||||
|
||||
**Quick Navigation:**
|
||||
- [Core Concepts](#core-concepts) - System architecture and patterns
|
||||
- [Step Development](#step-development-best-practices) - How to create new steps
|
||||
- [Services](#service-layer-patterns) - Reusable business logic
|
||||
- [Integrations](#external-integrations) - xAI, EspoCRM, Advoware
|
||||
- [Testing & Debugging](#testing-and-debugging)
|
||||
|
||||
---
|
||||
|
||||
## Project Status
|
||||
|
||||
**Migration:** ✅ 100% Complete (21/21 Steps migrated from Motia v0.17 → Motia III v1.0-RC)
|
||||
|
||||
**New to the project?** Start here:
|
||||
|
||||
1. [README.md](../README.md) - Project Overview & Quick Start
|
||||
2. [MIGRATION_STATUS.md](../MIGRATION_STATUS.md) - Migration Progress (100% Complete!)
|
||||
3. [MIGRATION_COMPLETE_ANALYSIS.md](../MIGRATION_COMPLETE_ANALYSIS.md) - Complete Migration Analysis
|
||||
2. [MIGRATION_GUIDE.md](../MIGRATION_GUIDE.md) - Complete migration patterns
|
||||
3. [ARCHITECTURE.md](ARCHITECTURE.md) - System design and architecture
|
||||
|
||||
## Migration to Motia III
|
||||
---
|
||||
|
||||
**Status: ✅ 100% Complete (21/21 Steps migrated)**
|
||||
## Core Concepts
|
||||
|
||||
- **[MIGRATION_GUIDE.md](../MIGRATION_GUIDE.md)** - Complete migration patterns
|
||||
- Old Motia v0.17 → Motia III v1.0-RC
|
||||
- TypeScript + Python Hybrid → Pure Python
|
||||
- Configuration changes, trigger patterns, API differences
|
||||
- **[MIGRATION_STATUS.md](../MIGRATION_STATUS.md)** - Current migration status
|
||||
- **[MIGRATION_COMPLETE_ANALYSIS.md](../MIGRATION_COMPLETE_ANALYSIS.md)** - Complete analysis
|
||||
### System Overview
|
||||
|
||||
## Core Documentation
|
||||
**Architecture:**
|
||||
- **Pure Python** (Motia III)
|
||||
- **Event-Driven** with queue-based async processing
|
||||
- **Redis-backed** distributed locking and caching
|
||||
- **REST APIs** for webhooks and proxies
|
||||
|
||||
### For Developers
|
||||
- **[ARCHITECTURE.md](ARCHITECTURE.md)** - System design and architecture
|
||||
- Components, Data Flow, Event-Driven Design
|
||||
- Updated for Motia III patterns
|
||||
**Key Components:**
|
||||
1. **Steps** (`steps/`) - Business logic handlers (HTTP, Queue, Cron)
|
||||
2. **Services** (`services/`) - Shared API clients and utilities
|
||||
3. **Configuration** (`iii-config.yaml`) - System setup
|
||||
|
||||
### For Operations
|
||||
- **[DEPLOYMENT.md](DEPLOYMENT.md)** - Production deployment (if available)
|
||||
- Installation, systemd, nginx, Monitoring
|
||||
- **[CONFIGURATION.md](CONFIGURATION.md)** - Environment configuration (if available)
|
||||
- All environment variables, secrets management
|
||||
|
||||
## Component Documentation
|
||||
|
||||
### Steps (Business Logic)
|
||||
|
||||
**Advoware Proxy** ([Module README](../steps/advoware_proxy/README.md)):
|
||||
Universal HTTP proxy for Advoware API with automatic authentication.
|
||||
- GET, POST, PUT, DELETE proxies
|
||||
- HMAC-512 authentication
|
||||
- Redis token caching
|
||||
|
||||
**Calendar Sync** ([Module README](../steps/advoware_cal_sync/README.md)):
|
||||
Bidirectional sync between Advoware appointments and Google Calendar.
|
||||
- `calendar_sync_cron_step.py` - Auto trigger (every 15 min)
|
||||
- `calendar_sync_api_step.py` - Manual trigger endpoint
|
||||
- `calendar_sync_all_step.py` - Employee cascade handler
|
||||
- `calendar_sync_event_step.py` - Per-employee sync logic (1053 lines)
|
||||
|
||||
**VMH Integration** ([Module README](../steps/vmh/README.md)):
|
||||
Webhooks and bidirectional sync between EspoCRM and Advoware.
|
||||
- **Beteiligte Sync** (Bidirectional EspoCRM ↔ Advoware)
|
||||
- Cron job (every 15 min)
|
||||
- Event handlers for create/update/delete
|
||||
- **Webhooks** (6 endpoints)
|
||||
- Beteiligte: create, update, delete
|
||||
- Bankverbindungen: create, update, delete
|
||||
|
||||
### Services
|
||||
|
||||
Service modules providing API clients and business logic:
|
||||
- `advoware_service.py` - Advoware API client (HMAC-512 auth, token caching)
|
||||
- `espocrm.py` - EspoCRM API client
|
||||
- `advoware.py` - Legacy Advoware service (deprecated)
|
||||
- Sync utilities and mappers
|
||||
|
||||
## Motia III Patterns
|
||||
|
||||
### Step Configuration
|
||||
|
||||
**Old (Motia v0.17):**
|
||||
```python
|
||||
config = {
|
||||
'type': 'api', # or 'event', 'cron'
|
||||
'name': 'MyStep',
|
||||
'method': 'POST',
|
||||
'path': '/my-step',
|
||||
'emits': ['my-event'],
|
||||
'subscribes': ['other-event']
|
||||
}
|
||||
**Data Flow:**
|
||||
```
|
||||
Webhook (HTTP) → Queue Event → Event Handler → External APIs
|
||||
↓ ↓
|
||||
Redis Lock Service Layer
|
||||
(EspoCRM, Advoware, xAI)
|
||||
```
|
||||
|
||||
**New (Motia III):**
|
||||
---
|
||||
|
||||
## Step Development Best Practices
|
||||
|
||||
### File Naming Convention
|
||||
|
||||
**CRITICAL: Always use `_step.py` suffix!**
|
||||
|
||||
```
|
||||
✅ CORRECT:
|
||||
steps/vmh/webhook/document_create_api_step.py
|
||||
steps/vmh/document_sync_event_step.py
|
||||
steps/vmh/beteiligte_sync_cron_step.py
|
||||
|
||||
❌ WRONG:
|
||||
steps/vmh/document_handler.py # Missing _step.py
|
||||
steps/vmh/sync.py # Missing _step.py
|
||||
```
|
||||
|
||||
**Naming Pattern:**
|
||||
- **Webhooks**: `{entity}_{action}_api_step.py`
|
||||
- Examples: `beteiligte_create_api_step.py`, `document_update_api_step.py`
|
||||
- **Event Handlers**: `{entity}_sync_event_step.py`
|
||||
- Examples: `document_sync_event_step.py`, `beteiligte_sync_event_step.py`
|
||||
- **Cron Jobs**: `{entity}_sync_cron_step.py`
|
||||
- Examples: `beteiligte_sync_cron_step.py`, `calendar_sync_cron_step.py`
|
||||
|
||||
### Step Template
|
||||
|
||||
**Complete step template with all required patterns:**
|
||||
|
||||
```python
|
||||
from motia import http, queue, cron
|
||||
"""Module-level docstring describing the step's purpose"""
|
||||
from typing import Dict, Any
|
||||
from motia import FlowContext, http, queue, cron, ApiRequest, ApiResponse
|
||||
|
||||
|
||||
config = {
|
||||
'name': 'MyStep',
|
||||
'flows': ['my-flow'],
|
||||
'triggers': [
|
||||
http('POST', '/my-step') # or queue('topic') or cron('0 */15 * * * *')
|
||||
"name": "Clear Human-Readable Name",
|
||||
"description": "Brief description of what this step does",
|
||||
"flows": ["flow-name"], # Logical grouping
|
||||
"triggers": [
|
||||
# Pick ONE trigger type:
|
||||
http("POST", "/path/to/endpoint"), # For webhooks
|
||||
# queue("topic.name"), # For event handlers
|
||||
# cron("0 */15 * * * *"), # For scheduled jobs
|
||||
],
|
||||
'enqueues': ['my-event']
|
||||
"enqueues": ["next.topic"], # Topics this step emits (optional)
|
||||
}
|
||||
|
||||
|
||||
async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
|
||||
"""
|
||||
Handler docstring explaining:
|
||||
- What triggers this handler
|
||||
- What it does
|
||||
- What events it emits
|
||||
"""
|
||||
try:
|
||||
# 1. Log entry
|
||||
ctx.logger.info("=")="=" * 80)
|
||||
ctx.logger.info(f"🔄 STEP STARTED: {config['name']}")
|
||||
ctx.logger.info("=" * 80)
|
||||
|
||||
# 2. Extract and validate input
|
||||
payload = request.body
|
||||
|
||||
# 3. Business logic
|
||||
# ...
|
||||
|
||||
# 4. Enqueue events if needed
|
||||
await ctx.enqueue({
|
||||
'topic': 'next.step',
|
||||
'data': {
|
||||
'entity_id': entity_id,
|
||||
'action': 'create'
|
||||
}
|
||||
})
|
||||
|
||||
# 5. Log success
|
||||
ctx.logger.info("✅ Step completed successfully")
|
||||
|
||||
# 6. Return response
|
||||
return ApiResponse(
|
||||
status_code=200,
|
||||
body={'success': True}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Always log errors with context
|
||||
ctx.logger.error(f"❌ Error in {config['name']}: {e}")
|
||||
ctx.logger.error(f"Payload: {request.body}")
|
||||
|
||||
return ApiResponse(
|
||||
status_code=500,
|
||||
body={'success': False, 'error': str(e)}
|
||||
)
|
||||
```
|
||||
|
||||
### Handler Signatures
|
||||
### Handler Signatures by Trigger Type
|
||||
|
||||
**HTTP Trigger:**
|
||||
**HTTP Trigger (Webhooks, APIs):**
|
||||
```python
|
||||
from motia import ApiRequest, ApiResponse, FlowContext
|
||||
|
||||
async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse:
|
||||
# Access: request.body, request.query_params, request.path_params
|
||||
# Enqueue: await ctx.enqueue(topic='...', data={...})
|
||||
# Log: ctx.logger.info('...')
|
||||
return ApiResponse(status=200, body={...})
|
||||
return ApiResponse(status_code=200, body={...})
|
||||
```
|
||||
|
||||
**Queue Trigger:**
|
||||
**Queue Trigger (Event Handlers):**
|
||||
```python
|
||||
async def handler(input_data: dict, ctx: FlowContext) -> None:
|
||||
# Process queue data
|
||||
await ctx.enqueue(topic='next-step', data={...})
|
||||
from motia import FlowContext
|
||||
|
||||
async def handler(event_data: Dict[str, Any], ctx: FlowContext) -> None:
|
||||
# Process event_data
|
||||
# No return value
|
||||
pass
|
||||
```
|
||||
|
||||
**Cron Trigger:**
|
||||
**Cron Trigger (Scheduled Jobs):**
|
||||
```python
|
||||
from motia import FlowContext
|
||||
|
||||
async def handler(input_data: None, ctx: FlowContext) -> None:
|
||||
# Cron jobs receive no input
|
||||
ctx.logger.info('Cron triggered')
|
||||
# No return value
|
||||
pass
|
||||
```
|
||||
|
||||
### Key Differences from Old Motia
|
||||
### Logging Best Practices
|
||||
|
||||
**ALWAYS use `ctx.logger`, NEVER use `print()` or module-level `logger`:**
|
||||
|
||||
```python
|
||||
# ✅ CORRECT: Context-aware logging
|
||||
ctx.logger.info("Processing started")
|
||||
ctx.logger.debug(f"Data: {data}")
|
||||
ctx.logger.warn("Skipping invalid entry")
|
||||
ctx.logger.error(f"Failed: {e}")
|
||||
|
||||
# ❌ WRONG: Direct print or module logger
|
||||
print("Processing started") # Not visible in iii logs
|
||||
logger.info("Processing started") # Loses context
|
||||
```
|
||||
|
||||
**Log Levels:**
|
||||
- `info()` - Normal operations (start, success, counts)
|
||||
- `debug()` - Detailed data dumps (payloads, responses)
|
||||
- `warn()` - Non-critical issues (skipped items, fallbacks)
|
||||
- `error()` - Failures requiring attention
|
||||
|
||||
**Log Structure:**
|
||||
```python
|
||||
# Section headers with visual separators
|
||||
ctx.logger.info("=" * 80)
|
||||
ctx.logger.info("🔄 SYNC HANDLER STARTED")
|
||||
ctx.logger.info("=" * 80)
|
||||
ctx.logger.info(f"Entity Type: {entity_type}")
|
||||
ctx.logger.info(f"Action: {action.upper()}")
|
||||
ctx.logger.info(f"Document ID: {entity_id}")
|
||||
ctx.logger.info("=" * 80)
|
||||
|
||||
# Use emojis for visual scanning
|
||||
ctx.logger.info("📥 Downloading file...")
|
||||
ctx.logger.info("✅ Downloaded 1024 bytes")
|
||||
ctx.logger.error("❌ Upload failed")
|
||||
ctx.logger.warn("⚠️ No collections found")
|
||||
```
|
||||
|
||||
### Event Topics Naming
|
||||
|
||||
**Pattern:** `{module}.{entity}.{action}`
|
||||
|
||||
```
|
||||
✅ Examples:
|
||||
vmh.document.create
|
||||
vmh.document.update
|
||||
vmh.document.delete
|
||||
vmh.beteiligte.create
|
||||
calendar.sync.employee
|
||||
advoware.proxy.response
|
||||
|
||||
❌ Avoid:
|
||||
document-create # Use dots, not dashes
|
||||
DocumentCreate # Use lowercase
|
||||
create_document # Wrong order
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Service Layer Patterns
|
||||
|
||||
### Service Base Class Pattern
|
||||
|
||||
**All services should follow this pattern:**
|
||||
|
||||
```python
|
||||
"""Service docstring"""
|
||||
import logging
|
||||
from typing import Optional
|
||||
from services.logging_utils import get_service_logger
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MyService:
|
||||
"""Service for interacting with External API"""
|
||||
|
||||
def __init__(self, context=None):
|
||||
"""
|
||||
Initialize service.
|
||||
|
||||
Args:
|
||||
context: Optional Motia FlowContext for logging
|
||||
"""
|
||||
self.context = context
|
||||
self.logger = get_service_logger('my_service', context)
|
||||
self._session = None
|
||||
|
||||
# Load config from env
|
||||
self.api_key = os.getenv('MY_API_KEY', '')
|
||||
if not self.api_key:
|
||||
raise ValueError("MY_API_KEY not configured")
|
||||
|
||||
self.logger.info("MyService initialized")
|
||||
|
||||
def _log(self, message: str, level: str = 'info') -> None:
|
||||
"""Internal logging helper"""
|
||||
log_func = getattr(self.logger, level, self.logger.info)
|
||||
log_func(message)
|
||||
|
||||
async def _get_session(self):
|
||||
"""Lazy session initialization"""
|
||||
if self._session is None or self._session.closed:
|
||||
self._session = aiohttp.ClientSession()
|
||||
return self._session
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Cleanup resources"""
|
||||
if self._session and not self._session.closed:
|
||||
await self._session.close()
|
||||
```
|
||||
|
||||
### Sync Utilities Pattern
|
||||
|
||||
**For bidirectional sync operations, inherit from `BaseSyncUtils`:**
|
||||
|
||||
```python
|
||||
from services.sync_utils_base import BaseSyncUtils
|
||||
|
||||
class MyEntitySync(BaseSyncUtils):
|
||||
"""Sync utilities for MyEntity"""
|
||||
|
||||
def _get_lock_key(self, entity_id: str) -> str:
|
||||
"""Required: Define lock key pattern"""
|
||||
return f"sync_lock:myentity:{entity_id}"
|
||||
|
||||
async def should_sync(self, entity: Dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
Decide if sync is needed.
|
||||
|
||||
Returns:
|
||||
(needs_sync: bool, reason: str)
|
||||
"""
|
||||
# Implementation
|
||||
pass
|
||||
```
|
||||
|
||||
**Base class provides:**
|
||||
- `_log()` - Context-aware logging
|
||||
- `_acquire_redis_lock()` - Distributed locking
|
||||
- `_release_redis_lock()` - Lock cleanup
|
||||
- `self.espocrm` - EspoCRM API client
|
||||
- `self.redis` - Redis client
|
||||
- `self.context` - Motia context
|
||||
- `self.logger` - Integration logger
|
||||
|
||||
---
|
||||
|
||||
## External Integrations
|
||||
|
||||
### xAI Collections Integration
|
||||
|
||||
**Status:** ✅ Fully Implemented
|
||||
|
||||
**Service:** `services/xai_service.py`
|
||||
|
||||
**Environment Variables:**
|
||||
```bash
|
||||
XAI_API_KEY=xai-... # For file uploads (api.x.ai)
|
||||
XAI_MANAGEMENT_KEY=xai-token-... # For collections (management-api.x.ai)
|
||||
```
|
||||
|
||||
**Usage Example:**
|
||||
```python
|
||||
from services.xai_service import XAIService
|
||||
|
||||
xai = XAIService(ctx)
|
||||
|
||||
# Upload file
|
||||
file_id = await xai.upload_file(
|
||||
file_content=bytes_data,
|
||||
filename="document.pdf",
|
||||
mime_type="application/pdf"
|
||||
)
|
||||
|
||||
# Add to collection
|
||||
await xai.add_to_collection("collection_id", file_id)
|
||||
|
||||
# Add to multiple collections
|
||||
added = await xai.add_to_collections(["col1", "col2"], file_id)
|
||||
|
||||
# Remove from collection
|
||||
await xai.remove_from_collection("collection_id", file_id)
|
||||
```
|
||||
|
||||
**Architecture:**
|
||||
- Files are uploaded ONCE to Files API (`api.x.ai/v1/files`)
|
||||
- Same `file_id` can be added to MULTIPLE collections
|
||||
- Removing from collection does NOT delete the file (may be used elsewhere)
|
||||
- Hash-based change detection prevents unnecessary reuploads
|
||||
|
||||
**Document Sync Flow:**
|
||||
```
|
||||
1. EspoCRM Webhook → vmh.document.{create|update|delete}
|
||||
2. Document Sync Handler:
|
||||
a. Acquire distributed lock (prevents duplicate syncs)
|
||||
b. Load document from EspoCRM
|
||||
c. Check if sync needed:
|
||||
- dateiStatus = "Neu" or "Geändert" → SYNC
|
||||
- Hash changed → SYNC
|
||||
- Entity has xAI collections → SYNC
|
||||
d. Download file from EspoCRM
|
||||
e. Calculate MD5 hash
|
||||
f. Upload to xAI (or reuse existing file_id)
|
||||
g. Add to required collections
|
||||
h. Update EspoCRM metadata (xaiFileId, xaiCollections, xaiSyncedHash)
|
||||
i. Release lock
|
||||
3. Delete: Remove from collections (keep file)
|
||||
```
|
||||
|
||||
**EspoCRM Fields (CDokumente):**
|
||||
```python
|
||||
{
|
||||
'xaiId': 'file-abc123', # xAI file_id
|
||||
'xaiCollections': ['col1', 'col2'], # Array of collection IDs
|
||||
'xaiSyncedHash': 'abc123def456', # MD5 at last sync
|
||||
'fileStatus': 'synced', # Status: neu, geändert, synced
|
||||
'md5sum': 'abc123def456', # Current file hash
|
||||
'sha256': 'def456...', # SHA-256 (optional)
|
||||
}
|
||||
```
|
||||
|
||||
### EspoCRM Integration
|
||||
|
||||
**Service:** `services/espocrm.py`
|
||||
|
||||
**Environment Variables:**
|
||||
```bash
|
||||
ESPOCRM_API_BASE_URL=https://crm.bitbylaw.com/api/v1
|
||||
ESPOCRM_API_KEY=your-api-key
|
||||
ESPOCRM_API_TIMEOUT_SECONDS=30
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
from services.espocrm import EspoCRMAPI
|
||||
|
||||
espocrm = EspoCRMAPI(ctx)
|
||||
|
||||
# Get entity
|
||||
entity = await espocrm.get_entity('CDokumente', entity_id)
|
||||
|
||||
# Update entity
|
||||
await espocrm.update_entity('CDokumente', entity_id, {
|
||||
'xaiId': file_id,
|
||||
'fileStatus': 'synced'
|
||||
})
|
||||
|
||||
# List entities
|
||||
result = await espocrm.list_entities(
|
||||
'CDokumente',
|
||||
where=[{'type': 'equals', 'attribute': 'fileStatus', 'value': 'neu'}],
|
||||
select='id,name,fileStatus',
|
||||
max_size=50
|
||||
)
|
||||
|
||||
# Download attachment
|
||||
file_bytes = await espocrm.download_attachment(attachment_id)
|
||||
```
|
||||
|
||||
### Advoware Integration
|
||||
|
||||
**Service:** `services/advoware_service.py`
|
||||
|
||||
**Authentication:** HMAC-512 with token caching
|
||||
|
||||
**Environment Variables:**
|
||||
```bash
|
||||
ADVOWARE_API_BASE_URL=https://api.advoware.de
|
||||
ADVOWARE_API_KEY=your-key
|
||||
ADVOWARE_API_SECRET=your-secret
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
```
|
||||
|
||||
**Proxy Endpoints:**
|
||||
- `GET /advoware/proxy?endpoint={path}` - Proxy GET requests
|
||||
- `POST /advoware/proxy` - Proxy POST requests
|
||||
- `PUT /advoware/proxy` - Proxy PUT requests
|
||||
- `DELETE /advoware/proxy` - Proxy DELETE requests
|
||||
|
||||
---
|
||||
|
||||
## Testing and Debugging
|
||||
|
||||
### Start System
|
||||
|
||||
```bash
|
||||
# Start iii Engine
|
||||
cd /opt/motia-iii/bitbylaw
|
||||
/opt/bin/iii -c iii-config.yaml
|
||||
|
||||
# Start iii Console (Web UI)
|
||||
/opt/bin/iii-console --enable-flow --host 0.0.0.0 --port 3113 \
|
||||
--engine-host 192.168.67.233 --engine-port 3111 --ws-port 3114
|
||||
```
|
||||
|
||||
### Check Registered Steps
|
||||
|
||||
```bash
|
||||
curl http://localhost:3111/_console/functions | python3 -m json.tool
|
||||
```
|
||||
|
||||
### Test HTTP Endpoint
|
||||
|
||||
```bash
|
||||
# Test document webhook
|
||||
curl -X POST "http://localhost:3111/vmh/webhook/document/create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '[{"id": "test123", "entityType": "CDokumente"}]'
|
||||
|
||||
# Test advoware proxy
|
||||
curl "http://localhost:3111/advoware/proxy?endpoint=employees"
|
||||
```
|
||||
|
||||
### Manually Trigger Cron
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:3111/_console/cron/trigger" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"function_id": "steps::VMH Beteiligte Sync Cron::trigger::0"}'
|
||||
```
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
# Live logs via journalctl
|
||||
journalctl -u motia-iii -f
|
||||
|
||||
# Search for specific step
|
||||
journalctl --since "today" | grep -i "document sync"
|
||||
|
||||
# Check for errors
|
||||
tail -100 /opt/motia-iii/bitbylaw/iii_new.log | grep -i error
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Step not showing up:**
|
||||
1. Check file naming: Must end with `_step.py`
|
||||
2. Check for import errors: `grep -i "importerror\|traceback" iii.log`
|
||||
3. Verify `config` dict is present
|
||||
4. Restart iii engine
|
||||
|
||||
**Redis connection failed:**
|
||||
- Check `REDIS_HOST` and `REDIS_PORT` environment variables
|
||||
- Verify Redis is running: `redis-cli ping`
|
||||
- Service will work without Redis but with warnings
|
||||
|
||||
**AttributeError '_log' not found:**
|
||||
- Ensure service inherits from `BaseSyncUtils` OR
|
||||
- Implement `_log()` method manually
|
||||
|
||||
---
|
||||
|
||||
## Key Patterns Summary
|
||||
|
||||
### ✅ DO
|
||||
|
||||
- **Always** use `_step.py` suffix for step files
|
||||
- **Always** use `ctx.logger` for logging (never `print`)
|
||||
- **Always** wrap handlers in try/except with error logging
|
||||
- **Always** use visual separators in logs (`"=" * 80`)
|
||||
- **Always** return `ApiResponse` from HTTP handlers
|
||||
- **Always** document what events a step emits
|
||||
- **Always** use distributed locks for sync operations
|
||||
- **Always** calculate hashes for change detection
|
||||
|
||||
### ❌ DON'T
|
||||
|
||||
- **Don't** use module-level `logger` in steps
|
||||
- **Don't** forget `async` on handler functions
|
||||
- **Don't** use blocking I/O (use `aiohttp`, not `requests`)
|
||||
- **Don't** return values from queue/cron handlers
|
||||
- **Don't** hardcode credentials (use environment variables)
|
||||
- **Don't** skip lock cleanup in `finally` blocks
|
||||
- **Don't** use `print()` for logging
|
||||
|
||||
---
|
||||
|
||||
## Module Documentation
|
||||
|
||||
---
|
||||
|
||||
## Module Documentation
|
||||
|
||||
### Steps
|
||||
|
||||
**Advoware Proxy** ([Module README](../steps/advoware_proxy/README.md))
|
||||
- Universal HTTP proxy with HMAC-512 authentication
|
||||
- Endpoints: GET, POST, PUT, DELETE
|
||||
- Redis token caching
|
||||
|
||||
**Calendar Sync** ([Module README](../steps/advoware_cal_sync/README.md))
|
||||
- Bidirectional Advoware ↔ Google Calendar sync
|
||||
- Cron: Every 15 minutes
|
||||
- API trigger: `/advoware/calendar/sync`
|
||||
|
||||
**VMH Integration** ([Module README](../steps/vmh/README.md))
|
||||
- EspoCRM ↔ Advoware bidirectional sync
|
||||
- Webhooks: Beteiligte, Bankverbindungen, Documents
|
||||
- xAI Collections integration for documents
|
||||
|
||||
### Services
|
||||
|
||||
| Service | Purpose | Config |
|
||||
|---------|---------|--------|
|
||||
| `xai_service.py` | xAI file uploads & collections | `XAI_API_KEY`, `XAI_MANAGEMENT_KEY` |
|
||||
| `espocrm.py` | EspoCRM REST API client | `ESPOCRM_API_BASE_URL`, `ESPOCRM_API_KEY` |
|
||||
| `advoware_service.py` | Advoware API with HMAC auth | `ADVOWARE_API_KEY`, `ADVOWARE_API_SECRET` |
|
||||
| `document_sync_utils.py` | Document sync logic | Redis connection |
|
||||
| `beteiligte_sync_utils.py` | Beteiligte sync logic | Redis connection |
|
||||
| `sync_utils_base.py` | Base class for sync utils | - |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Environment Variables
|
||||
|
||||
**Required:**
|
||||
```bash
|
||||
# EspoCRM
|
||||
ESPOCRM_API_BASE_URL=https://crm.bitbylaw.com/api/v1
|
||||
ESPOCRM_API_KEY=your-key
|
||||
|
||||
# Advoware
|
||||
ADVOWARE_API_BASE_URL=https://api.advoware.de
|
||||
ADVOWARE_API_KEY=your-key
|
||||
ADVOWARE_API_SECRET=your-secret
|
||||
|
||||
# xAI
|
||||
XAI_API_KEY=xai-...
|
||||
XAI_MANAGEMENT_KEY=xai-token-...
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_DB_ADVOWARE_CACHE=1
|
||||
```
|
||||
|
||||
**Optional:**
|
||||
```bash
|
||||
ESPOCRM_API_TIMEOUT_SECONDS=30
|
||||
ESPOCRM_METADATA_TTL_SECONDS=300
|
||||
```
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
bitbylaw/
|
||||
├── iii-config.yaml # Motia III configuration
|
||||
├── pyproject.toml # Python dependencies (uv)
|
||||
├── steps/ # Business logic
|
||||
│ ├── advoware_proxy/
|
||||
│ ├── advoware_cal_sync/
|
||||
│ └── vmh/
|
||||
│ ├── webhook/ # HTTP webhook handlers
|
||||
│ │ ├── *_create_api_step.py
|
||||
│ │ ├── *_update_api_step.py
|
||||
│ │ └── *_delete_api_step.py
|
||||
│ ├── *_sync_event_step.py # Queue event handlers
|
||||
│ └── *_sync_cron_step.py # Scheduled jobs
|
||||
├── services/ # Shared services
|
||||
│ ├── xai_service.py
|
||||
│ ├── espocrm.py
|
||||
│ ├── advoware_service.py
|
||||
│ ├── *_sync_utils.py
|
||||
│ ├── sync_utils_base.py
|
||||
│ ├── logging_utils.py
|
||||
│ └── exceptions.py
|
||||
├── docs/ # Documentation
|
||||
│ ├── INDEX.md # This file
|
||||
│ ├── ARCHITECTURE.md
|
||||
│ └── DOCUMENT_SYNC_XAI_STATUS.md
|
||||
└── tests/ # Test scripts
|
||||
└── test_xai_collections_api.py
|
||||
```
|
||||
|
||||
### Motia III vs Old Motia
|
||||
|
||||
| Old Motia v0.17 | Motia III v1.0-RC |
|
||||
|-----------------|-------------------|
|
||||
@@ -132,115 +665,53 @@ async def handler(input_data: None, ctx: FlowContext) -> None:
|
||||
| `type: 'cron'` | `triggers: [cron()]` |
|
||||
| `context.emit()` | `ctx.enqueue()` |
|
||||
| `emits: [...]` | `enqueues: [...]` |
|
||||
| `subscribes: [...]` | Moved to trigger: `queue('topic')` |
|
||||
| 5-field cron | 6-field cron (prepend seconds) |
|
||||
| `subscribes: [...]` | `triggers: [queue('topic')]` |
|
||||
| 5-field cron | 6-field cron (seconds first) |
|
||||
| `context.logger` | `ctx.logger` |
|
||||
| Motia Workbench | iii Console |
|
||||
| Node.js + Python | Pure Python |
|
||||
|
||||
## Documentation Structure
|
||||
### Cron Syntax
|
||||
|
||||
**6 fields (Motia III):** `second minute hour day month weekday`
|
||||
|
||||
```
|
||||
docs/
|
||||
├── INDEX.md # This file
|
||||
├── ARCHITECTURE.md # System design (Motia III)
|
||||
└── advoware/
|
||||
└── (optional API specs)
|
||||
|
||||
steps/{module}/
|
||||
├── README.md # Module overview
|
||||
└── {step_name}_step.py # Step implementation
|
||||
|
||||
services/
|
||||
└── {service_name}.py # Service implementations
|
||||
|
||||
MIGRATION_GUIDE.md # Complete migration guide
|
||||
MIGRATION_STATUS.md # Migration progress
|
||||
MIGRATION_COMPLETE_ANALYSIS.md # Final analysis
|
||||
0 */15 * * * * # Every 15 minutes
|
||||
0 0 */6 * * * # Every 6 hours
|
||||
0 0 2 * * * # Daily at 2 AM
|
||||
0 30 9 * * 1-5 # Monday-Friday at 9:30 AM
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
---
|
||||
|
||||
### Common Tasks
|
||||
## Additional Resources
|
||||
|
||||
| Task | Documentation |
|
||||
|------|---------------|
|
||||
| Understand migration | [MIGRATION_GUIDE.md](../MIGRATION_GUIDE.md) |
|
||||
| Check migration status | [MIGRATION_STATUS.md](../MIGRATION_STATUS.md) |
|
||||
| Understand architecture | [ARCHITECTURE.md](ARCHITECTURE.md) |
|
||||
| Calendar sync overview | [steps/advoware_cal_sync/README.md](../steps/advoware_cal_sync/README.md) |
|
||||
| Proxy API usage | [steps/advoware_proxy/README.md](../steps/advoware_proxy/README.md) |
|
||||
| VMH sync details | [steps/vmh/README.md](../steps/vmh/README.md) |
|
||||
|
||||
### Code Locations
|
||||
|
||||
| Component | Location | Documentation |
|
||||
|-----------|----------|---------------|
|
||||
| API Proxy Steps | `steps/advoware_proxy/` | [README](../steps/advoware_proxy/README.md) |
|
||||
| Calendar Sync Steps | `steps/advoware_cal_sync/` | [README](../steps/advoware_cal_sync/README.md) |
|
||||
| VMH Steps | `steps/vmh/` | [README](../steps/vmh/README.md) |
|
||||
| Advoware Service | `services/advoware_service.py` | (in-code docs) |
|
||||
| Configuration | `iii-config.yaml` | System config |
|
||||
| Environment | `.env` or systemd | Environment variables |
|
||||
|
||||
## Running the System
|
||||
|
||||
### Start iii Engine
|
||||
```bash
|
||||
cd /opt/motia-iii/bitbylaw
|
||||
/opt/bin/iii -c iii-config.yaml
|
||||
```
|
||||
|
||||
### Start iii Console (Web UI)
|
||||
```bash
|
||||
/opt/bin/iii-console --enable-flow --host 0.0.0.0 --port 3113 \
|
||||
--engine-host 192.168.67.233 --engine-port 3111 --ws-port 3114
|
||||
```
|
||||
|
||||
### Access Web Console
|
||||
Open browser: `http://localhost:3113/`
|
||||
|
||||
### Check Registered Steps
|
||||
```bash
|
||||
curl http://localhost:3111/_console/functions | python3 -m json.tool
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Test HTTP Step
|
||||
```bash
|
||||
# Calendar sync API
|
||||
curl -X POST "http://localhost:3111/advoware/calendar/sync" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"kuerzel": "PB"}'
|
||||
|
||||
# Advoware proxy
|
||||
curl "http://localhost:3111/advoware/proxy?endpoint=employees"
|
||||
```
|
||||
|
||||
### Trigger Cron Manually
|
||||
```bash
|
||||
curl -X POST "http://localhost:3111/_console/cron/trigger" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"function_id": "steps::Calendar Sync Cron Job::trigger::0"}'
|
||||
```
|
||||
|
||||
### Check Logs
|
||||
View logs in iii Console or via API:
|
||||
```bash
|
||||
curl "http://localhost:3111/_console/logs"
|
||||
```
|
||||
|
||||
## External Resources
|
||||
### Documentation
|
||||
- [MIGRATION_GUIDE.md](../MIGRATION_GUIDE.md) - v0.17 → v1.0 migration
|
||||
- [MIGRATION_STATUS.md](../MIGRATION_STATUS.md) - Migration progress
|
||||
- [ARCHITECTURE.md](ARCHITECTURE.md) - System design
|
||||
- [DOCUMENT_SYNC_XAI_STATUS.md](DOCUMENT_SYNC_XAI_STATUS.md) - xAI integration details
|
||||
|
||||
### External Resources
|
||||
- [Motia III Documentation](https://iii.dev)
|
||||
- [Python SDK](https://pypi.org/project/motia/)
|
||||
- [Google Calendar API](https://developers.google.com/calendar)
|
||||
- [xAI API Docs](https://docs.x.ai/)
|
||||
- [EspoCRM API](https://docs.espocrm.com/development/api/)
|
||||
- [Redis Documentation](https://redis.io/documentation)
|
||||
|
||||
## Support
|
||||
### Support & Troubleshooting
|
||||
|
||||
- **Migration Questions**: Check [MIGRATION_GUIDE.md](../MIGRATION_GUIDE.md)
|
||||
- **Runtime Issues**: Check iii Console logs
|
||||
- **Step Not Showing**: Verify import errors in logs
|
||||
- **Redis Issues**: Check Redis connection in `services/`
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Step not registered | Check `_step.py` suffix, restart iii engine |
|
||||
| Import errors | Check logs: `grep -i importerror iii.log` |
|
||||
| Redis unavailable | Service works with warnings, check `REDIS_HOST` |
|
||||
| `'_log' not found` | Inherit from `BaseSyncUtils` or implement `_log()` |
|
||||
| Webhook not triggering | Verify endpoint in iii Console, check EspoCRM config |
|
||||
| xAI upload fails | Verify `XAI_API_KEY` and `XAI_MANAGEMENT_KEY` |
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-03-08
|
||||
**Migration Status:** ✅ Complete
|
||||
**xAI Integration:** ✅ Implemented
|
||||
|
||||
@@ -42,6 +42,11 @@ class BaseSyncUtils:
|
||||
"Distributed Locking deaktiviert - Race Conditions möglich!"
|
||||
)
|
||||
|
||||
def _log(self, message: str, level: str = 'info') -> None:
|
||||
"""Delegate logging to the logger with optional level"""
|
||||
log_func = getattr(self.logger, level, self.logger.info)
|
||||
log_func(message)
|
||||
|
||||
def _get_lock_key(self, entity_id: str) -> str:
|
||||
"""
|
||||
Erzeugt Redis Lock-Key für eine Entity
|
||||
|
||||
Reference in New Issue
Block a user