- Improved logging for file uploads in EspoCRMAPI to include upload parameters and error details. - Updated cron job configurations for calendar sync and participant sync to trigger every 15 minutes on the first minute of the hour. - Enhanced document create, delete, and update webhook handlers to determine and log the entity type. - Refactored document sync event handler to include entity type in sync operations and logging. - Added a new test script for uploading preview images to EspoCRM and verifying the upload process. - Created a test script for document thumbnail generation, including document creation, file upload, webhook triggering, and preview verification.
280 lines
8.8 KiB
Python
Executable File
280 lines
8.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Test Script: Preview Image Upload zu EspoCRM
|
|
|
|
Testet das Hochladen eines Preview-Bildes (WebP) als Attachment
|
|
zu einem CDokumente Entity via EspoCRM API.
|
|
|
|
Usage:
|
|
python test_preview_upload.py <document_id>
|
|
|
|
Example:
|
|
python test_preview_upload.py 69a68906ac3d0fd25
|
|
"""
|
|
|
|
import asyncio
|
|
import aiohttp
|
|
import base64
|
|
import os
|
|
import sys
|
|
from io import BytesIO
|
|
from PIL import Image
|
|
|
|
|
|
# EspoCRM Config (aus Environment oder hardcoded für Test)
|
|
ESPOCRM_API_BASE_URL = os.getenv('ESPOCRM_API_BASE_URL', 'https://crm.bitbylaw.com/api/v1')
|
|
ESPOCRM_API_KEY = os.getenv('ESPOCRM_API_KEY', '')
|
|
|
|
# Test-Parameter
|
|
ENTITY_TYPE = 'CDokumente'
|
|
FIELD_NAME = 'preview'
|
|
|
|
|
|
def generate_test_webp(text: str = "TEST PREVIEW", size: tuple = (600, 800)) -> bytes:
|
|
"""
|
|
Generiert ein einfaches Test-WebP-Bild
|
|
|
|
Args:
|
|
text: Text der im Bild angezeigt wird
|
|
size: Größe des Bildes (width, height)
|
|
|
|
Returns:
|
|
WebP image als bytes
|
|
"""
|
|
print(f"📐 Generating test image ({size[0]}x{size[1]})...")
|
|
|
|
# Erstelle einfaches Bild mit Text
|
|
img = Image.new('RGB', size, color='lightblue')
|
|
|
|
# Optional: Füge Text hinzu (benötigt PIL ImageDraw)
|
|
try:
|
|
from PIL import ImageDraw, ImageFont
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
# Versuche ein größeres Font zu laden
|
|
try:
|
|
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 40)
|
|
except:
|
|
font = ImageFont.load_default()
|
|
|
|
# Text zentriert
|
|
bbox = draw.textbbox((0, 0), text, font=font)
|
|
text_width = bbox[2] - bbox[0]
|
|
text_height = bbox[3] - bbox[1]
|
|
x = (size[0] - text_width) // 2
|
|
y = (size[1] - text_height) // 2
|
|
|
|
draw.text((x, y), text, fill='darkblue', font=font)
|
|
except Exception as e:
|
|
print(f"⚠️ Text rendering failed: {e}")
|
|
|
|
# Konvertiere zu WebP
|
|
buffer = BytesIO()
|
|
img.save(buffer, format='WEBP', quality=85)
|
|
webp_bytes = buffer.getvalue()
|
|
|
|
print(f"✅ Test image generated: {len(webp_bytes)} bytes")
|
|
return webp_bytes
|
|
|
|
|
|
async def upload_preview_to_espocrm(
|
|
document_id: str,
|
|
preview_data: bytes,
|
|
entity_type: str = 'CDokumente'
|
|
) -> dict:
|
|
"""
|
|
Upload Preview zu EspoCRM Attachment API
|
|
|
|
Args:
|
|
document_id: ID des CDokumente/Document Entity
|
|
preview_data: WebP image als bytes
|
|
entity_type: Entity-Type (CDokumente oder Document)
|
|
|
|
Returns:
|
|
Response dict mit Attachment ID
|
|
"""
|
|
print(f"\n📤 Uploading preview to {entity_type}/{document_id}...")
|
|
print(f" Preview size: {len(preview_data)} bytes")
|
|
|
|
# Base64-encode
|
|
base64_data = base64.b64encode(preview_data).decode('ascii')
|
|
file_data_uri = f"data:image/webp;base64,{base64_data}"
|
|
|
|
print(f" Base64 encoded: {len(base64_data)} chars")
|
|
|
|
# API Request
|
|
url = ESPOCRM_API_BASE_URL.rstrip('/') + '/Attachment'
|
|
headers = {
|
|
'X-Api-Key': ESPOCRM_API_KEY,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = {
|
|
'name': 'preview.webp',
|
|
'type': 'image/webp',
|
|
'role': 'Attachment',
|
|
'field': FIELD_NAME,
|
|
'relatedType': entity_type,
|
|
'relatedId': document_id,
|
|
'file': file_data_uri
|
|
}
|
|
|
|
print(f"\n🌐 POST {url}")
|
|
print(f" Headers: X-Api-Key={ESPOCRM_API_KEY[:20]}...")
|
|
print(f" Payload keys: {list(payload.keys())}")
|
|
print(f" - name: {payload['name']}")
|
|
print(f" - type: {payload['type']}")
|
|
print(f" - role: {payload['role']}")
|
|
print(f" - field: {payload['field']}")
|
|
print(f" - relatedType: {payload['relatedType']}")
|
|
print(f" - relatedId: {payload['relatedId']}")
|
|
print(f" - file: data:image/webp;base64,... ({len(base64_data)} chars)")
|
|
|
|
timeout = aiohttp.ClientTimeout(total=30)
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
async with session.post(url, headers=headers, json=payload) as response:
|
|
print(f"\n📥 Response Status: {response.status}")
|
|
print(f" Content-Type: {response.content_type}")
|
|
|
|
response_text = await response.text()
|
|
|
|
if response.status >= 400:
|
|
print(f"\n❌ Upload FAILED!")
|
|
print(f" Status: {response.status}")
|
|
print(f" Response: {response_text}")
|
|
raise Exception(f"Upload error {response.status}: {response_text}")
|
|
|
|
# Parse JSON response
|
|
result = await response.json()
|
|
attachment_id = result.get('id')
|
|
|
|
print(f"\n✅ Upload SUCCESSFUL!")
|
|
print(f" Attachment ID: {attachment_id}")
|
|
print(f" Full response: {result}")
|
|
|
|
return result
|
|
|
|
|
|
async def update_entity_with_preview(
|
|
document_id: str,
|
|
attachment_id: str,
|
|
entity_type: str = 'CDokumente'
|
|
) -> dict:
|
|
"""
|
|
Update Entity mit previewId und previewName
|
|
|
|
Args:
|
|
document_id: Entity ID
|
|
attachment_id: Attachment ID vom Upload
|
|
entity_type: Entity-Type
|
|
|
|
Returns:
|
|
Updated entity data
|
|
"""
|
|
print(f"\n📝 Updating {entity_type}/{document_id} with previewId...")
|
|
|
|
url = f"{ESPOCRM_API_BASE_URL.rstrip('/')}/{entity_type}/{document_id}"
|
|
headers = {
|
|
'X-Api-Key': ESPOCRM_API_KEY,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = {
|
|
'previewId': attachment_id,
|
|
'previewName': 'preview.webp'
|
|
}
|
|
|
|
print(f" PUT {url}")
|
|
print(f" Payload: {payload}")
|
|
|
|
timeout = aiohttp.ClientTimeout(total=30)
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
async with session.put(url, headers=headers, json=payload) as response:
|
|
print(f" Response Status: {response.status}")
|
|
|
|
if response.status >= 400:
|
|
response_text = await response.text()
|
|
print(f"\n❌ Update FAILED!")
|
|
print(f" Status: {response.status}")
|
|
print(f" Response: {response_text}")
|
|
raise Exception(f"Update error {response.status}: {response_text}")
|
|
|
|
result = await response.json()
|
|
print(f"\n✅ Entity updated successfully!")
|
|
print(f" previewId: {result.get('previewId')}")
|
|
print(f" previewName: {result.get('previewName')}")
|
|
|
|
return result
|
|
|
|
|
|
async def main():
|
|
"""Main test flow"""
|
|
print("=" * 80)
|
|
print("🖼️ ESPOCRM PREVIEW UPLOAD TEST")
|
|
print("=" * 80)
|
|
|
|
# Check arguments
|
|
if len(sys.argv) < 2:
|
|
print("\n❌ Error: Document ID required!")
|
|
print(f"\nUsage: {sys.argv[0]} <document_id>")
|
|
print(f"Example: {sys.argv[0]} 69a68906ac3d0fd25")
|
|
sys.exit(1)
|
|
|
|
document_id = sys.argv[1]
|
|
|
|
# Check API key
|
|
if not ESPOCRM_API_KEY:
|
|
print("\n❌ Error: ESPOCRM_API_KEY environment variable not set!")
|
|
sys.exit(1)
|
|
|
|
print(f"\n📋 Test Parameters:")
|
|
print(f" API Base URL: {ESPOCRM_API_BASE_URL}")
|
|
print(f" API Key: {ESPOCRM_API_KEY[:20]}...")
|
|
print(f" Entity Type: {ENTITY_TYPE}")
|
|
print(f" Document ID: {document_id}")
|
|
print(f" Field: {FIELD_NAME}")
|
|
|
|
try:
|
|
# Step 1: Generate test image
|
|
print("\n" + "=" * 80)
|
|
print("STEP 1: Generate Test Image")
|
|
print("=" * 80)
|
|
preview_data = generate_test_webp(f"Preview Test\n{document_id[:8]}", size=(600, 800))
|
|
|
|
# Step 2: Upload to EspoCRM
|
|
print("\n" + "=" * 80)
|
|
print("STEP 2: Upload to EspoCRM Attachment API")
|
|
print("=" * 80)
|
|
result = await upload_preview_to_espocrm(document_id, preview_data, ENTITY_TYPE)
|
|
attachment_id = result.get('id')
|
|
|
|
# Step 3: Update Entity
|
|
print("\n" + "=" * 80)
|
|
print("STEP 3: Update Entity with Preview Reference")
|
|
print("=" * 80)
|
|
await update_entity_with_preview(document_id, attachment_id, ENTITY_TYPE)
|
|
|
|
# Success summary
|
|
print("\n" + "=" * 80)
|
|
print("✅ TEST SUCCESSFUL!")
|
|
print("=" * 80)
|
|
print(f"\n📊 Summary:")
|
|
print(f" - Attachment ID: {attachment_id}")
|
|
print(f" - Entity: {ENTITY_TYPE}/{document_id}")
|
|
print(f" - Preview Size: {len(preview_data)} bytes")
|
|
print(f"\n🔗 View in EspoCRM:")
|
|
print(f" {ESPOCRM_API_BASE_URL.replace('/api/v1', '')}/#CDokumente/view/{document_id}")
|
|
|
|
except Exception as e:
|
|
print("\n" + "=" * 80)
|
|
print("❌ TEST FAILED!")
|
|
print("=" * 80)
|
|
print(f"\nError: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
asyncio.run(main())
|