feat(gateway): register multi-platform gateway routes

This commit is contained in:
Anish Sarkar 2026-05-29 10:20:43 +05:30
parent 51bf2a8361
commit 185759de1f
3 changed files with 72 additions and 25 deletions

View file

@ -19,6 +19,8 @@ from .editor_routes import router as editor_router
from .export_routes import router as export_router
from .folders_routes import router as folders_router
from .gateway_webhook_routes import router as gateway_router
from .gateway_whatsapp_baileys_routes import router as gateway_whatsapp_baileys_router
from .gateway_whatsapp_webhook_routes import router as gateway_whatsapp_webhook_router
from .google_calendar_add_connector_route import (
router as google_calendar_add_connector_router,
)
@ -70,6 +72,8 @@ router.include_router(export_router)
router.include_router(documents_router)
router.include_router(folders_router)
router.include_router(gateway_router)
router.include_router(gateway_whatsapp_webhook_router)
router.include_router(gateway_whatsapp_baileys_router)
router.include_router(notes_router)
router.include_router(new_chat_router) # Chat with assistant-ui persistence
router.include_router(agent_revert_router) # POST /threads/{id}/revert/{action_id}

View file

@ -7,6 +7,7 @@ import logging
import uuid
from datetime import UTC, datetime
from typing import Any
from urllib.parse import quote
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
@ -16,14 +17,17 @@ from starlette.responses import Response
from app.config import config
from app.db import (
ExternalChatBindingState,
ExternalChatBinding,
ExternalChatPlatform,
ExternalChatAccount,
ExternalChatBinding,
ExternalChatBindingState,
ExternalChatPlatform,
User,
get_async_session,
)
from app.gateway.accounts import get_or_create_system_telegram_account
from app.gateway.accounts import (
get_or_create_system_telegram_account,
get_or_create_system_whatsapp_account,
)
from app.gateway.bindings import resume_binding, revoke_binding
from app.gateway.inbox import persist_inbound_event, telegram_event_dedupe_key
from app.gateway.pairing import generate_pairing_code, pairing_expires_at
@ -131,11 +135,39 @@ async def start_binding(
user: User = Depends(current_active_user),
session: AsyncSession = Depends(get_async_session),
) -> StartBindingResponse:
if body.platform != ExternalChatPlatform.TELEGRAM:
raise HTTPException(status_code=400, detail="Only Telegram is supported in v1")
account = await get_or_create_system_telegram_account(session)
code = generate_pairing_code()
if body.platform == ExternalChatPlatform.TELEGRAM:
account = await get_or_create_system_telegram_account(session)
username = account.bot_username or config.TELEGRAM_SHARED_BOT_USERNAME
if not username:
raise HTTPException(
status_code=500,
detail="Telegram bot username is not configured",
)
deep_link = f"https://t.me/{username}?start={code}"
elif body.platform == ExternalChatPlatform.WHATSAPP:
if config.GATEWAY_WHATSAPP_INTAKE_MODE != "cloud":
raise HTTPException(
status_code=400,
detail="WhatsApp /start pairing requires GATEWAY_WHATSAPP_INTAKE_MODE=cloud",
)
account = await get_or_create_system_whatsapp_account(session)
phone = config.WHATSAPP_SHARED_DISPLAY_PHONE_NUMBER
if not phone:
raise HTTPException(
status_code=500,
detail="WHATSAPP_SHARED_DISPLAY_PHONE_NUMBER is not configured",
)
normalized_phone = "".join(ch for ch in phone if ch.isdigit())
if not normalized_phone:
raise HTTPException(
status_code=500,
detail="WHATSAPP_SHARED_DISPLAY_PHONE_NUMBER must contain digits",
)
deep_link = f"https://wa.me/{normalized_phone}?text={quote(f'/start {code}')}"
else:
raise HTTPException(status_code=400, detail="Unsupported platform")
expires_at = pairing_expires_at()
binding = ExternalChatBinding(
account_id=account.id,
@ -149,13 +181,10 @@ async def start_binding(
await session.commit()
await session.refresh(binding)
username = account.bot_username or config.TELEGRAM_SHARED_BOT_USERNAME
if not username:
raise HTTPException(status_code=500, detail="Telegram bot username is not configured")
return StartBindingResponse(
binding_id=binding.id,
code=code,
deep_link=f"https://t.me/{username}?start={code}",
deep_link=deep_link,
expires_at=expires_at,
)
@ -166,21 +195,21 @@ async def list_bindings(
session: AsyncSession = Depends(get_async_session),
) -> list[dict[str, Any]]:
result = await session.execute(
select(ExternalChatBinding).where(
ExternalChatBinding.user_id == user.id
)
select(ExternalChatBinding, ExternalChatAccount)
.join(ExternalChatAccount, ExternalChatBinding.account_id == ExternalChatAccount.id)
.where(ExternalChatBinding.user_id == user.id)
)
return [
{
"id": binding.id,
"platform": "telegram",
"platform": account.platform.value,
"state": binding.state.value,
"search_space_id": binding.search_space_id,
"external_display_name": binding.external_display_name,
"external_username": binding.external_username,
"suspended_reason": binding.suspended_reason,
}
for binding in result.scalars()
for binding, account in result.all()
]

View file

@ -9,14 +9,14 @@ from sqlalchemy import select, update
from app.celery_app import celery_app
from app.db import (
ExternalChatAccount,
ExternalChatEventStatus,
ExternalChatHealthStatus,
ExternalChatInboundEvent,
ExternalChatPlatform,
ExternalChatAccount,
)
from app.gateway.accounts import account_token
from app.gateway.inbox import persist_inbound_event, telegram_event_dedupe_key
from app.gateway.registry import resolve_platform_bundle
from app.gateway.telegram.adapter import TelegramAdapter
from app.observability.metrics import (
record_gateway_health_check_failure,
@ -69,15 +69,29 @@ def gateway_health_check_task() -> None:
result = await session.execute(select(ExternalChatAccount))
accounts = list(result.scalars())
for account in accounts:
token = account_token(account)
if not token or account.platform != ExternalChatPlatform.TELEGRAM:
continue
try:
metadata = await TelegramAdapter(token).validate_credentials()
bundle = resolve_platform_bundle(account)
metadata = await bundle.adapter.validate_credentials()
account.health_status = ExternalChatHealthStatus.OK
account.bot_username = metadata.get("username")
if account.platform == ExternalChatPlatform.TELEGRAM:
account.bot_username = metadata.get("username")
elif account.platform == ExternalChatPlatform.WHATSAPP:
cursor_state = dict(account.cursor_state or {})
for key in (
"quality_rating",
"account_review_status",
"status",
):
if key in metadata:
cursor_state[key] = metadata[key]
account.cursor_state = cursor_state
except Exception:
logger.warning("External chat Telegram health check failed", exc_info=True)
logger.warning(
"External chat health check failed platform=%s account_id=%s",
account.platform.value,
account.id,
exc_info=True,
)
account.health_status = ExternalChatHealthStatus.FAILING
record_gateway_health_check_failure(platform=account.platform.value)
account.last_health_check_at = datetime.now(UTC)