Spaces:
Running
Running
| from fastapi import FastAPI, Request | |
| import logging | |
| import json | |
| from datetime import datetime, timedelta | |
| from typing import Any, Dict, List, Tuple | |
| app = FastAPI() | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", | |
| ) | |
| log = logging.getLogger("supabase-receiver") | |
| def extract_records(payload: Any) -> List[Dict]: | |
| """ | |
| نحاول نطلع records من البودي بأي شكل محتمل: | |
| - { "record": {...} } | |
| - { "records": [ {...}, {...} ] } | |
| - [ {...}, {...} ] | |
| - أو dict واحد نعتبره row واحد. | |
| """ | |
| if isinstance(payload, dict): | |
| if "record" in payload and isinstance(payload["record"], dict): | |
| return [payload["record"]] | |
| if "records" in payload and isinstance(payload["records"], list): | |
| return [r for r in payload["records"] if isinstance(r, dict)] | |
| return [payload] | |
| elif isinstance(payload, list): | |
| return [r for r in payload if isinstance(r, dict)] | |
| else: | |
| return [] | |
| def parse_lead_timestamp(raw_ts: str) -> Tuple[str, str]: | |
| """ | |
| يحوّل lead_timestamp إلى: | |
| - deadline: تاريخ ووقت +24 ساعة (string) | |
| - due_date: تاريخ فقط (string) | |
| لو مفيش timestamp هنستخدم الوقت الحالي. | |
| """ | |
| if not raw_ts: | |
| now = datetime.utcnow() | |
| deadline = now + timedelta(hours=24) | |
| else: | |
| ts = raw_ts.replace("Z", "+00:00") | |
| dt = datetime.fromisoformat(ts) | |
| deadline = dt + timedelta(hours=24) | |
| deadline_str = deadline.strftime("%Y-%m-%d %H:%M:%S") | |
| due_date = deadline.date().isoformat() | |
| return deadline_str, due_date | |
| def build_odoo_payload_from_lead(lead: Dict) -> Dict: | |
| """ | |
| نفس المابّينج تقريبًا اللي هنعمله في السيرفر الحقيقي، | |
| بس هنا عشان نطبع كود جاهز للتست لوكال. | |
| """ | |
| full_name = lead.get("full_name") or lead.get("name") or "Lead" | |
| email = lead.get("email") or "" | |
| phone = lead.get("phone") or "" | |
| city_from_lead = lead.get("city") or False | |
| platform = lead.get("platform") or "" | |
| lead_ts = lead.get("lead_timestamp") | |
| deadline_str, due_date = parse_lead_timestamp(lead_ts) | |
| body = { | |
| "name": full_name, | |
| "date_deadline": deadline_str, | |
| "user_id": lead.get("lead_id"), # زي ما اتفقنا | |
| "active": True, | |
| "kanban_state": "grey", | |
| "type": "opportunity", | |
| "stage_id": 18, | |
| "tag_ids": [], | |
| "color": 0, | |
| "adgroup_name": lead.get("campaign_name"), | |
| "adset_name": lead.get("adset_name"), | |
| "ad_name": lead.get("ad_name", ""), | |
| "lead_timestamp": lead_ts, | |
| "source_id": None, | |
| "source_company": "Jizan", | |
| "due_date": due_date, | |
| "call_logs": "", | |
| "call_log_result": "", | |
| "age": None, | |
| "specialty_id": False, | |
| "gender_id": False, | |
| "city_ar": city_from_lead or "غير محدد", | |
| "email_from": email, | |
| "city": False, | |
| "phone": phone, | |
| "platform": platform, | |
| } | |
| return body | |
| def build_python_snippet_for_odoo(payload: Dict) -> str: | |
| """ | |
| يبني كود Python جاهز تقدر تاخده كوبي-بيست، | |
| وتستعمله لوكال عشان يبعته لـ Odoo. | |
| """ | |
| payload_json = json.dumps(payload, ensure_ascii=False, indent=4) | |
| snippet = f'''import requests | |
| ODOO_LEAD_URL = "https://odoo.binrushd.care/api/crm.lead" | |
| token = "PASTE_TOKEN_HERE" # حط التوكن اللي جالك من /api/auth/token | |
| payload = {payload_json} | |
| headers = {{ | |
| "Authorization": f"Bearer {{token}}", | |
| "Content-Type": "application/json", | |
| }} | |
| resp = requests.post(ODOO_LEAD_URL, json=payload, headers=headers, timeout=30) | |
| print(resp.status_code) | |
| print(resp.text) | |
| ''' | |
| return snippet | |
| async def receive_from_supabase(request: Request): | |
| """ | |
| Endpoint رئيسي تستقبله Supabase Webhook. | |
| - يطبع البودي بالكامل في اللوج | |
| - يطلع أول record | |
| - يبني منه payload لـ Odoo | |
| - يطبع كود Python في اللوج تقدر تاخده كوبي-بيست وتجرّبه لوكال. | |
| """ | |
| try: | |
| body = await request.json() | |
| except Exception as e: | |
| log.error("Failed to parse JSON body: %s", e) | |
| return {"status": "error", "detail": f"Invalid JSON: {e}"} | |
| # 1) طباعة البودي الخام | |
| log.info("=== RAW WEBHOOK BODY FROM SUPABASE ===") | |
| log.info(json.dumps(body, ensure_ascii=False, indent=2)) | |
| # 2) استخراج records | |
| records = extract_records(body) | |
| if not records: | |
| log.warning("No records found in webhook payload.") | |
| return {"status": "ok", "note": "no records in payload"} | |
| first = records[0] | |
| log.info("=== FIRST RECORD (lead) ===") | |
| log.info(json.dumps(first, ensure_ascii=False, indent=2)) | |
| # 3) بناء payload لـ Odoo | |
| odoo_payload = build_odoo_payload_from_lead(first) | |
| log.info("=== ODOO PAYLOAD (dict) ===") | |
| log.info(json.dumps(odoo_payload, ensure_ascii=False, indent=2)) | |
| # 4) توليد كود Python جاهز | |
| python_snippet = build_python_snippet_for_odoo(odoo_payload) | |
| log.info("=== PYTHON SNIPPET TO SEND THIS LEAD TO ODOO ===") | |
| log.info("\n%s", python_snippet) | |
| # 5) نرجّع response بسيط | |
| return { | |
| "status": "ok", | |
| "records_received": len(records), | |
| "preview_odoo_payload": odoo_payload, | |
| } | |