From 84544e115386f3dcc5715abd54e8b68bde2e2652 Mon Sep 17 00:00:00 2001 From: debu-2 Date: Wed, 14 Oct 2026 05:54:11 +0530 Subject: [PATCH] template --- backend/app/whatsapp_template_service.py | 117 ++++++++++++++++------- 1 file changed, 83 insertions(+), 34 deletions(-) diff --git a/backend/app/whatsapp_template_service.py b/backend/app/whatsapp_template_service.py index 5fda231..adef413 100644 --- a/backend/app/whatsapp_template_service.py +++ b/backend/app/whatsapp_template_service.py @@ -20,7 +20,6 @@ from .whatsapp_onboarding import ( _load_phones, _load_waba, _owned_account, - _phone_ids_from_debug, _quality_label, _token_app_id, _waba_ids_from_debug, @@ -527,30 +526,62 @@ def _wrong_template_node(exc: HTTPException) -> bool: return "message_templates" in detail and "nonexisting field" in detail -async def _correct_waba_id(db: Session, account: WhatsAppBusinessAccount, token: str) -> bool: - """Replace a stored phone-number ID with the WABA ID from the embedded-signup token.""" +def _add_id(found: list[str], value: Any) -> None: + text = str(value or "").strip() + if text and text not in found: + found.append(text) + + +async def _discover_waba_ids(db: Session, token: str, business_id: str | None) -> list[str]: + """WABA IDs this token can manage. Debug target_ids are often empty on embedded signup.""" + found: list[str] = [] try: debug = await _debug_token(db, token) except HTTPException: - return False - wabas = _waba_ids_from_debug(debug) - phone_ids = set(_phone_ids_from_debug(debug)) + debug = {} + for waba in _waba_ids_from_debug(debug): + _add_id(found, waba) + lookups: list[tuple[str, dict[str, str]]] = [ + ("me/assigned_whatsapp_business_accounts", {"fields": "id,name", "limit": "50"}), + ] + if business_id: + for edge in ("owned_whatsapp_business_accounts", "client_whatsapp_business_accounts"): + lookups.append((f"{business_id}/{edge}", {"fields": "id,name", "limit": "50"})) + for path, params in lookups: + try: + data = await _graph("GET", path, token=token, params=params) + except HTTPException: + continue + rows = data.get("data") if isinstance(data.get("data"), list) else [] + for row in rows: + if isinstance(row, dict): + _add_id(found, row.get("id")) + return found + + +async def _waba_owning_phone(token: str, wabas: list[str], phone_id: str) -> str | None: + for waba in wabas: + if waba == phone_id: + continue + try: + rows = await _load_phones(token, waba) + except HTTPException: + continue + if phone_id in {str(row.get("id")) for row in rows if row.get("id")}: + return waba + return None + + +async def _correct_waba_id(db: Session, account: WhatsAppBusinessAccount, token: str, *, allow_only_waba: bool) -> str: + """Point a phone-number ID at its WABA. Returns updated, covered, or no.""" stored = str(account.waba_id) - if not wabas or stored in wabas: - return False - real = wabas[0] if len(wabas) == 1 else None - if real is None: - for waba in wabas: - try: - rows = await _load_phones(token, waba) - except HTTPException: - continue - ids = {str(row.get("id")) for row in rows if row.get("id")} - if stored in ids or phone_ids.intersection(ids): - real = waba - break - if not real or real == stored: - return False + business_id = account.connection.meta_business_id if account.connection else None + wabas = [waba for waba in await _discover_waba_ids(db, token, business_id) if waba != stored] + real = await _waba_owning_phone(token, wabas, stored) + if real is None and allow_only_waba and len(wabas) == 1: + real = wabas[0] + if not real: + return "no" taken = db.scalar( select(WhatsAppBusinessAccount.id).where( WhatsAppBusinessAccount.waba_id == real, @@ -558,7 +589,10 @@ async def _correct_waba_id(db: Session, account: WhatsAppBusinessAccount, token: ) ) if taken is not None: - return False + owner = db.get(WhatsAppBusinessAccount, taken) + if owner is not None and owner.user_id == account.user_id: + return "covered" + return "no" account.waba_id = real try: info = await _load_waba(token, real) @@ -575,7 +609,7 @@ async def _correct_waba_id(db: Session, account: WhatsAppBusinessAccount, token: except HTTPException: pass db.flush() - return True + return "updated" async def sync_account_templates(db: Session, account: WhatsAppBusinessAccount) -> str | None: @@ -584,22 +618,37 @@ async def sync_account_templates(db: Session, account: WhatsAppBusinessAccount) if account.status == "disconnected": return None return "Reconnect this WhatsApp account to refresh its templates." + own_token = _stored_token(account.connection) last_error: str | None = None for index, token in enumerate(tokens): try: - try: - seen = await _pull_templates(db, account, token) - except HTTPException as exc: - db.rollback() - if not _wrong_template_node(exc) or not await _correct_waba_id(db, account, token): - raise - seen = await _pull_templates(db, account, token) + seen = await _pull_templates(db, account, token) except HTTPException as exc: db.rollback() - last_error = str(exc.detail) - if _retry_with_another_token(exc) and index < len(tokens) - 1: - continue - return last_error + if not _wrong_template_node(exc): + last_error = str(exc.detail) + if _retry_with_another_token(exc) and index < len(tokens) - 1: + continue + return last_error + try: + outcome = await _correct_waba_id(db, account, token, allow_only_waba=token == own_token) + except HTTPException: + outcome = "no" + if outcome == "covered": + return None + if outcome != "updated": + last_error = str(exc.detail) + if index < len(tokens) - 1: + continue + return last_error + try: + seen = await _pull_templates(db, account, token) + except HTTPException as retry_exc: + db.rollback() + last_error = str(retry_exc.detail) + if index < len(tokens) - 1: + continue + return last_error stored = db.scalars( select(WhatsAppMessageTemplate).where(WhatsAppMessageTemplate.waba_id == account.waba_id) ).all()