From ded044b4c0cbb8380b76b02d25c6c6935a160e7b Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Wed, 27 May 2026 22:21:24 +0300 Subject: [PATCH] fix: make support replies finish promptly --- backend/bot/services/support_service.py | 31 +++++++++++++------ .../src/admin/sections/SupportSection.svelte | 3 +- frontend/src/lib/admin/stores/supportStore.js | 27 ++++++++++------ .../src/lib/webapp/stores/supportStore.js | 20 +++++++----- 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/backend/bot/services/support_service.py b/backend/bot/services/support_service.py index b173926..333007e 100644 --- a/backend/bot/services/support_service.py +++ b/backend/bot/services/support_service.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio import logging from dataclasses import dataclass from datetime import datetime, timezone @@ -134,6 +135,16 @@ class SupportService: email_auth_service=self.email_auth_service, ) + @staticmethod + def _schedule_notification(coro, error_message: str, *error_args: Any) -> None: + async def _runner(): + try: + await coro + except Exception: + logger.exception(error_message, *error_args) + + asyncio.create_task(_runner(), name="support-notification") + async def _ensure_user_allowed(self, session, user_id: int) -> User: user = await user_dal.get_user_by_id(session, user_id) if not user or user.is_banned or not self.settings.SUPPORT_TICKETS_ENABLED: @@ -218,8 +229,8 @@ class SupportService: await session.commit() if notification_decision.send_telegram or notification_decision.send_email: - try: - await self.notification_service.notify_support_user_reply( + self._schedule_notification( + self.notification_service.notify_support_user_reply( ticket, message, user, @@ -227,9 +238,10 @@ class SupportService: unread_count=int(ticket.unread_admin_count or 0), send_telegram=notification_decision.send_telegram, send_email=notification_decision.send_email, - ) - except Exception: - logger.exception("Failed to notify about support user reply %s", ticket_id) + ), + "Failed to notify about support user reply %s", + ticket_id, + ) return ticket, message async def reply_as_admin( @@ -275,10 +287,11 @@ class SupportService: await session.commit() if user and not is_internal_note: - try: - await self.notification_service.notify_support_admin_reply(ticket, message, user) - except Exception: - logger.exception("Failed to notify user about support admin reply %s", ticket_id) + self._schedule_notification( + self.notification_service.notify_support_admin_reply(ticket, message, user), + "Failed to notify user about support admin reply %s", + ticket_id, + ) return ticket, message async def change_status(self, admin_id: int, ticket_id: int, status: str) -> SupportTicket: diff --git a/frontend/src/admin/sections/SupportSection.svelte b/frontend/src/admin/sections/SupportSection.svelte index ef6ac23..a6cab42 100644 --- a/frontend/src/admin/sections/SupportSection.svelte +++ b/frontend/src/admin/sections/SupportSection.svelte @@ -93,7 +93,8 @@ }); async function send(body) { - await supportStore.sendReply(body); + const sent = await supportStore.sendReply(body); + if (!sent) return; reply = ""; } diff --git a/frontend/src/lib/admin/stores/supportStore.js b/frontend/src/lib/admin/stores/supportStore.js index 6a580d2..1528b64 100644 --- a/frontend/src/lib/admin/stores/supportStore.js +++ b/frontend/src/lib/admin/stores/supportStore.js @@ -193,17 +193,26 @@ export function createAdminSupportStore({ api, onToast, at }) { body: JSON.stringify({ body, is_internal_note: internal }), }); if (!res?.ok) throw res; - state.update((s) => ({ - ...s, - openedTicket: res.ticket - ? { ...s.openedTicket, ...res.ticket, user: res.ticket.user || s.openedTicket?.user } - : s.openedTicket, - messages: [...s.messages, res.message], - })); - await loadList(); - await loadStats(); + state.update((s) => + s.openedTicketId === current + ? { + ...s, + openedTicket: res.ticket + ? { + ...s.openedTicket, + ...res.ticket, + user: res.ticket.user || s.openedTicket?.user, + } + : s.openedTicket, + messages: res.message ? [...s.messages, res.message] : s.messages, + } + : s + ); + void Promise.allSettled([loadList({ silent: true }), loadStats()]); + return true; } catch (error) { onToast(error?.message || at("support_send_failed", {}, "Send failed")); + return false; } finally { state.update((s) => ({ ...s, sending: false })); } diff --git a/frontend/src/lib/webapp/stores/supportStore.js b/frontend/src/lib/webapp/stores/supportStore.js index 6341dfa..95d7202 100644 --- a/frontend/src/lib/webapp/stores/supportStore.js +++ b/frontend/src/lib/webapp/stores/supportStore.js @@ -258,13 +258,19 @@ export function createSupportStore({ api, t, showToast }) { body: JSON.stringify({ body }), }); if (!res?.ok) throw res; - state.update((s) => ({ - ...s, - openedTicket: res.ticket || s.openedTicket, - messages: [...s.messages, res.message], - })); - await refreshUnread(); - await loadList({ silent: true, force: true }); + state.update((s) => + s.openedTicketId === ticketId + ? { + ...s, + openedTicket: res.ticket || s.openedTicket, + messages: res.message ? [...s.messages, res.message] : s.messages, + } + : s + ); + void Promise.allSettled([ + refreshUnread({ silent: true }), + loadList({ silent: true, force: true }), + ]); return true; } catch (error) { showToast(error?.message || t("wa_support_send_failed"));