"""EspoCRM Webhook - Document Delete Empfängt Delete-Webhooks von EspoCRM für Documents. Loggt detailliert alle Payload-Informationen für Analyse. """ import json import datetime from typing import Any from motia import FlowContext, http, ApiRequest, ApiResponse config = { "name": "EspoCRM Document Delete Webhook", "description": "Empfängt Delete-Webhooks von EspoCRM für Document Entities", "flows": ["espocrm-documents"], "triggers": [ http("POST", "/api/espocrm/document/delete") ], "enqueues": ["espocrm.document.delete"], } async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse: """ Webhook handler for Document deletion in EspoCRM. Receives notifications when documents are deleted. Note: Bei Deletion haben wir ggf. nur die ID, keine vollständigen Entity-Daten. """ try: payload = request.body or [] # ═══════════════════════════════════════════════════════════════ # DETAILLIERTES LOGGING FÜR ANALYSE # ═══════════════════════════════════════════════════════════════ ctx.logger.info("=" * 80) ctx.logger.info("📥 EspoCRM DOCUMENT DELETE WEBHOOK EMPFANGEN") ctx.logger.info("=" * 80) # Log Request Headers ctx.logger.info("\n🔍 REQUEST HEADERS:") if hasattr(request, 'headers'): for key, value in request.headers.items(): ctx.logger.info(f" {key}: {value}") else: ctx.logger.info(" (keine Headers verfügbar)") # Log Payload Type & Structure ctx.logger.info(f"\n📦 PAYLOAD TYPE: {type(payload).__name__}") ctx.logger.info(f"📦 PAYLOAD LENGTH: {len(payload) if isinstance(payload, (list, dict)) else 'N/A'}") # Log Full Payload (pretty-printed) ctx.logger.info("\n📄 FULL PAYLOAD:") ctx.logger.info(json.dumps(payload, indent=2, ensure_ascii=False)) # ═══════════════════════════════════════════════════════════════ # PAYLOAD ANALYSE & ID EXTRAKTION # ═══════════════════════════════════════════════════════════════ entity_ids = set() if isinstance(payload, list): ctx.logger.info(f"\n✅ Payload ist LIST mit {len(payload)} Einträgen") for idx, entity in enumerate(payload): if isinstance(entity, dict): entity_id = entity.get('id') if entity_id: entity_ids.add(entity_id) ctx.logger.info(f"\n 🗑️ Document #{idx + 1}:") ctx.logger.info(f" ID: {entity_id}") ctx.logger.info(f" Verfügbare Felder: {', '.join(entity.keys())}") # Bei Delete haben wir oft nur minimale Daten if 'name' in entity: ctx.logger.info(f" Name: {entity.get('name')}") if 'deletedAt' in entity or 'deleted' in entity: ctx.logger.info(f" Deleted At: {entity.get('deletedAt', entity.get('deleted', 'N/A'))}") # xAI-relevante Felder (falls vorhanden) xai_fields = {k: v for k, v in entity.items() if 'xai' in k.lower() or 'collection' in k.lower()} if xai_fields: ctx.logger.info(f" 🤖 xAI-Felder: {json.dumps(xai_fields, ensure_ascii=False)}") elif isinstance(payload, dict): ctx.logger.info("\n✅ Payload ist SINGLE DICT") entity_id = payload.get('id') if entity_id: entity_ids.add(entity_id) ctx.logger.info(f"\n 🗑️ Document:") ctx.logger.info(f" ID: {entity_id}") ctx.logger.info(f" Verfügbare Felder: {', '.join(payload.keys())}") if 'name' in payload: ctx.logger.info(f" Name: {payload.get('name')}") if 'deletedAt' in payload or 'deleted' in payload: ctx.logger.info(f" Deleted At: {payload.get('deletedAt', payload.get('deleted', 'N/A'))}") # xAI-relevante Felder xai_fields = {k: v for k, v in payload.items() if 'xai' in k.lower() or 'collection' in k.lower()} if xai_fields: ctx.logger.info(f" 🤖 xAI-Felder: {json.dumps(xai_fields, ensure_ascii=False)}") else: ctx.logger.warning(f"⚠️ Unerwarteter Payload-Typ: {type(payload)}") # ═══════════════════════════════════════════════════════════════ # QUEUE EVENTS EMITTIEREN # ═══════════════════════════════════════════════════════════════ ctx.logger.info("\n" + "=" * 80) ctx.logger.info(f"📊 ZUSAMMENFASSUNG: {len(entity_ids)} Document(s) gefunden") ctx.logger.info("=" * 80) if not entity_ids: ctx.logger.warning("⚠️ Keine Document-IDs im Payload gefunden!") return ApiResponse( status=200, body={ 'status': 'received', 'action': 'delete', 'ids_count': 0, 'warning': 'No document IDs found in payload' } ) # Emit events für Queue-Processing for entity_id in entity_ids: await ctx.enqueue({ 'topic': 'espocrm.document.delete', 'data': { 'entity_id': entity_id, 'action': 'delete', 'source': 'webhook', 'timestamp': datetime.datetime.now().isoformat() } }) ctx.logger.info(f"✅ Event emittiert: espocrm.document.delete für ID {entity_id}") ctx.logger.info("\n" + "=" * 80) ctx.logger.info(f"✅ WEBHOOK VERARBEITUNG ABGESCHLOSSEN") ctx.logger.info("=" * 80) return ApiResponse( status=200, body={ 'status': 'received', 'action': 'delete', 'ids_count': len(entity_ids), 'document_ids': list(entity_ids) } ) except Exception as e: ctx.logger.error("=" * 80) ctx.logger.error(f"❌ FEHLER beim Verarbeiten des Document Delete Webhooks") ctx.logger.error("=" * 80) ctx.logger.error(f"Error Type: {type(e).__name__}") ctx.logger.error(f"Error Message: {str(e)}") import traceback ctx.logger.error(f"Stack Trace:\n{traceback.format_exc()}") return ApiResponse( status=500, body={ 'error': 'Internal server error', 'error_type': type(e).__name__, 'details': str(e) } )