mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-02 19:55:18 +02:00
feat(gateway): improve WhatsApp account mode handling and connection filtering
This commit is contained in:
parent
a151e8f729
commit
fc2467be3d
3 changed files with 82 additions and 12 deletions
|
|
@ -16,6 +16,7 @@ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
|||
from app.config import config
|
||||
from app.db import (
|
||||
ExternalChatAccount,
|
||||
ExternalChatAccountMode,
|
||||
ExternalChatBinding,
|
||||
ExternalChatBindingState,
|
||||
ExternalChatEventStatus,
|
||||
|
|
@ -40,6 +41,21 @@ def _dashboard_url() -> str:
|
|||
return config.NEXT_FRONTEND_URL or "/dashboard"
|
||||
|
||||
|
||||
def _active_whatsapp_account_mode() -> ExternalChatAccountMode | None:
|
||||
if config.GATEWAY_WHATSAPP_INTAKE_MODE == "cloud":
|
||||
return ExternalChatAccountMode.CLOUD_SHARED
|
||||
if config.GATEWAY_WHATSAPP_INTAKE_MODE == "baileys":
|
||||
return ExternalChatAccountMode.SELF_HOST_BYO
|
||||
return None
|
||||
|
||||
|
||||
def _is_inactive_whatsapp_account(account: ExternalChatAccount) -> bool:
|
||||
return (
|
||||
account.platform == ExternalChatPlatform.WHATSAPP
|
||||
and account.mode != _active_whatsapp_account_mode()
|
||||
)
|
||||
|
||||
|
||||
async def claim_next_inbound_event(
|
||||
session_maker: SessionMaker = async_session_maker,
|
||||
) -> int | None:
|
||||
|
|
@ -293,6 +309,11 @@ async def _dispatch_inbound_event(
|
|||
event.last_error = "account_missing"
|
||||
await session.commit()
|
||||
return
|
||||
if _is_inactive_whatsapp_account(account):
|
||||
event.status = ExternalChatEventStatus.IGNORED
|
||||
event.last_error = "inactive_whatsapp_mode"
|
||||
await session.commit()
|
||||
return
|
||||
|
||||
try:
|
||||
bundle = resolve_platform_bundle(account)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from uuid import UUID
|
|||
import httpx
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from starlette.responses import JSONResponse, RedirectResponse, Response
|
||||
|
||||
|
|
@ -173,6 +173,21 @@ class UpdateAccountSearchSpaceRequest(BaseModel):
|
|||
search_space_id: int
|
||||
|
||||
|
||||
def _active_whatsapp_account_mode() -> ExternalChatAccountMode | None:
|
||||
if config.GATEWAY_WHATSAPP_INTAKE_MODE == "cloud":
|
||||
return ExternalChatAccountMode.CLOUD_SHARED
|
||||
if config.GATEWAY_WHATSAPP_INTAKE_MODE == "baileys":
|
||||
return ExternalChatAccountMode.SELF_HOST_BYO
|
||||
return None
|
||||
|
||||
|
||||
def _is_inactive_whatsapp_account(account: ExternalChatAccount) -> bool:
|
||||
return (
|
||||
account.platform == ExternalChatPlatform.WHATSAPP
|
||||
and account.mode != _active_whatsapp_account_mode()
|
||||
)
|
||||
|
||||
|
||||
def _classify_telegram_event(payload: dict[str, Any]) -> str:
|
||||
if "message" in payload:
|
||||
return "message"
|
||||
|
|
@ -712,6 +727,10 @@ async def list_connections(
|
|||
user: User = Depends(current_active_user),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
) -> list[dict[str, Any]]:
|
||||
active_whatsapp_mode = _active_whatsapp_account_mode()
|
||||
if platform == ExternalChatPlatform.WHATSAPP and active_whatsapp_mode is None:
|
||||
return []
|
||||
|
||||
filters = [
|
||||
ExternalChatBinding.user_id == user.id,
|
||||
ExternalChatBinding.state.in_(
|
||||
|
|
@ -720,6 +739,17 @@ async def list_connections(
|
|||
]
|
||||
if platform is not None:
|
||||
filters.append(ExternalChatAccount.platform == platform)
|
||||
if platform == ExternalChatPlatform.WHATSAPP and active_whatsapp_mode is not None:
|
||||
filters.append(ExternalChatAccount.mode == active_whatsapp_mode)
|
||||
elif active_whatsapp_mode is None:
|
||||
filters.append(ExternalChatAccount.platform != ExternalChatPlatform.WHATSAPP)
|
||||
else:
|
||||
filters.append(
|
||||
or_(
|
||||
ExternalChatAccount.platform != ExternalChatPlatform.WHATSAPP,
|
||||
ExternalChatAccount.mode == active_whatsapp_mode,
|
||||
)
|
||||
)
|
||||
|
||||
result = await session.execute(
|
||||
select(ExternalChatBinding, ExternalChatAccount)
|
||||
|
|
@ -782,7 +812,10 @@ async def list_connections(
|
|||
}
|
||||
)
|
||||
|
||||
if platform is None or platform == ExternalChatPlatform.WHATSAPP:
|
||||
if (
|
||||
active_whatsapp_mode == ExternalChatAccountMode.SELF_HOST_BYO
|
||||
and (platform is None or platform == ExternalChatPlatform.WHATSAPP)
|
||||
):
|
||||
account_result = await session.execute(
|
||||
select(ExternalChatAccount).where(
|
||||
ExternalChatAccount.owner_user_id == user.id,
|
||||
|
|
@ -855,6 +888,9 @@ async def update_binding_search_space(
|
|||
ExternalChatBindingState.SUSPENDED,
|
||||
}:
|
||||
raise HTTPException(status_code=400, detail="Only active bindings can be routed")
|
||||
account = await session.get(ExternalChatAccount, binding.account_id)
|
||||
if account is None or _is_inactive_whatsapp_account(account):
|
||||
raise HTTPException(status_code=404, detail="Binding not found")
|
||||
|
||||
await check_search_space_access(session, user, body.search_space_id)
|
||||
if binding.search_space_id != body.search_space_id:
|
||||
|
|
@ -878,6 +914,7 @@ async def update_gateway_account_search_space(
|
|||
or account.owner_user_id != user.id
|
||||
or account.platform != ExternalChatPlatform.WHATSAPP
|
||||
or account.mode != ExternalChatAccountMode.SELF_HOST_BYO
|
||||
or _is_inactive_whatsapp_account(account)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="Gateway account not found")
|
||||
|
||||
|
|
@ -912,6 +949,9 @@ async def delete_binding(
|
|||
binding = await session.get(ExternalChatBinding, binding_id)
|
||||
if binding is None or binding.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Binding not found")
|
||||
account = await session.get(ExternalChatAccount, binding.account_id)
|
||||
if account is None or _is_inactive_whatsapp_account(account):
|
||||
raise HTTPException(status_code=404, detail="Binding not found")
|
||||
revoke_binding(binding)
|
||||
await session.commit()
|
||||
return {"ok": True}
|
||||
|
|
@ -929,6 +969,7 @@ async def delete_gateway_account(
|
|||
or account.owner_user_id != user.id
|
||||
or account.platform != ExternalChatPlatform.WHATSAPP
|
||||
or account.mode != ExternalChatAccountMode.SELF_HOST_BYO
|
||||
or _is_inactive_whatsapp_account(account)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="Gateway account not found")
|
||||
|
||||
|
|
@ -961,6 +1002,9 @@ async def resume_external_chat_binding(
|
|||
binding = await session.get(ExternalChatBinding, binding_id)
|
||||
if binding is None or binding.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Binding not found")
|
||||
account = await session.get(ExternalChatAccount, binding.account_id)
|
||||
if account is None or _is_inactive_whatsapp_account(account):
|
||||
raise HTTPException(status_code=404, detail="Binding not found")
|
||||
resume_binding(binding)
|
||||
binding.updated_at = datetime.now(UTC)
|
||||
await session.commit()
|
||||
|
|
|
|||
|
|
@ -208,12 +208,18 @@ export function MessagingChannelsContent() {
|
|||
await refreshPlatform(connection.platform as GatewayPlatform);
|
||||
}
|
||||
|
||||
const isConnectionInActiveMode = (connection: GatewayConnection) => {
|
||||
if (connection.platform !== "whatsapp") return true;
|
||||
if (whatsappMode === "baileys") return connection.mode === "self_host_byo";
|
||||
if (whatsappMode === "cloud") return connection.mode !== "self_host_byo";
|
||||
return false;
|
||||
};
|
||||
const baileysQr = baileysHealth?.qr || null;
|
||||
const hasTelegramConnection = connections.some(
|
||||
(connection) => connection.platform === "telegram"
|
||||
);
|
||||
const hasWhatsAppConnection = connections.some(
|
||||
(connection) => connection.platform === "whatsapp"
|
||||
(connection) => connection.platform === "whatsapp" && isConnectionInActiveMode(connection)
|
||||
);
|
||||
const isRefreshing = (platform: GatewayPlatform) => refreshingPlatform === platform;
|
||||
const refreshButtonClassName = "gap-2";
|
||||
|
|
@ -242,7 +248,7 @@ export function MessagingChannelsContent() {
|
|||
`${platformLabel(connection.platform)} connection`;
|
||||
const renderConnectionRows = (platform: GatewayConnection["platform"], emptyText: string) => {
|
||||
const platformConnections = connections.filter(
|
||||
(connection) => connection.platform === platform
|
||||
(connection) => connection.platform === platform && isConnectionInActiveMode(connection)
|
||||
);
|
||||
|
||||
if (platformConnections.length === 0) {
|
||||
|
|
@ -327,7 +333,7 @@ export function MessagingChannelsContent() {
|
|||
|
||||
return (
|
||||
<div className="grid items-stretch gap-3 sm:grid-cols-2">
|
||||
<Card className="group relative h-full overflow-hidden border-accent bg-accent/20 transition-all duration-200 hover:shadow-md">
|
||||
<Card className="order-1 group relative h-full overflow-hidden border-accent bg-accent/20 transition-all duration-200 hover:shadow-md">
|
||||
<CardHeader className="space-y-1.5 p-4 pb-2">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<CardTitle className="flex items-center gap-2 text-sm">Telegram</CardTitle>
|
||||
|
|
@ -360,7 +366,7 @@ export function MessagingChannelsContent() {
|
|||
</Card>
|
||||
|
||||
{slackGatewayEnabled ? (
|
||||
<Card className="group relative h-full overflow-hidden border-accent bg-accent/20 transition-all duration-200 hover:shadow-md">
|
||||
<Card className="order-4 group relative h-full overflow-hidden border-accent bg-accent/20 transition-all duration-200 hover:shadow-md">
|
||||
<CardHeader className="space-y-1.5 p-4 pb-2">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<CardTitle className="flex items-center gap-2 text-sm">Slack</CardTitle>
|
||||
|
|
@ -392,7 +398,7 @@ export function MessagingChannelsContent() {
|
|||
) : null}
|
||||
|
||||
{discordGatewayEnabled ? (
|
||||
<Card className="group relative h-full overflow-hidden border-accent bg-accent/20 transition-all duration-200 hover:shadow-md">
|
||||
<Card className="order-3 group relative h-full overflow-hidden border-accent bg-accent/20 transition-all duration-200 hover:shadow-md">
|
||||
<CardHeader className="space-y-1.5 p-4 pb-2">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<CardTitle className="flex items-center gap-2 text-sm">Discord</CardTitle>
|
||||
|
|
@ -424,16 +430,15 @@ export function MessagingChannelsContent() {
|
|||
) : null}
|
||||
|
||||
{whatsappMode !== "disabled" ? (
|
||||
<Card className="group relative h-full overflow-hidden border-accent bg-accent/20 transition-all duration-200 hover:shadow-md">
|
||||
<Card className="order-2 group relative h-full overflow-hidden border-accent bg-accent/20 transition-all duration-200 hover:shadow-md">
|
||||
<CardHeader className="space-y-1.5 p-4 pb-2">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<CardTitle className="flex items-center gap-2 text-sm">WhatsApp</CardTitle>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Pair this search space with WhatsApp using the configured gateway mode.
|
||||
{whatsappMode === "baileys"
|
||||
? " Send messages to your own WhatsApp chat. Other chats are ignored."
|
||||
: ""}
|
||||
? "Use the WhatsApp bridge for your own WhatsApp chat. Other chats are ignored."
|
||||
: "Pair this search space with WhatsApp Cloud API."}
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 p-4 pt-0">
|
||||
|
|
@ -469,7 +474,7 @@ export function MessagingChannelsContent() {
|
|||
disabled={isRefreshing("whatsapp")}
|
||||
>
|
||||
<RefreshCw className={refreshIconClassName("whatsapp")} />
|
||||
Refresh WhatsApp Bridge
|
||||
Refresh
|
||||
</Button>
|
||||
{baileysQr ? (
|
||||
<div className="rounded-lg border border-accent bg-accent/20 p-3">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue