fix: make support replies finish promptly

This commit is contained in:
3252a8
2026-05-27 22:21:24 +03:00
parent bce78c4f28
commit ded044b4c0
4 changed files with 55 additions and 26 deletions
+22 -9
View File
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timezone from datetime import datetime, timezone
@@ -134,6 +135,16 @@ class SupportService:
email_auth_service=self.email_auth_service, 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: async def _ensure_user_allowed(self, session, user_id: int) -> User:
user = await user_dal.get_user_by_id(session, user_id) 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: if not user or user.is_banned or not self.settings.SUPPORT_TICKETS_ENABLED:
@@ -218,8 +229,8 @@ class SupportService:
await session.commit() await session.commit()
if notification_decision.send_telegram or notification_decision.send_email: if notification_decision.send_telegram or notification_decision.send_email:
try: self._schedule_notification(
await self.notification_service.notify_support_user_reply( self.notification_service.notify_support_user_reply(
ticket, ticket,
message, message,
user, user,
@@ -227,9 +238,10 @@ class SupportService:
unread_count=int(ticket.unread_admin_count or 0), unread_count=int(ticket.unread_admin_count or 0),
send_telegram=notification_decision.send_telegram, send_telegram=notification_decision.send_telegram,
send_email=notification_decision.send_email, send_email=notification_decision.send_email,
) ),
except Exception: "Failed to notify about support user reply %s",
logger.exception("Failed to notify about support user reply %s", ticket_id) ticket_id,
)
return ticket, message return ticket, message
async def reply_as_admin( async def reply_as_admin(
@@ -275,10 +287,11 @@ class SupportService:
await session.commit() await session.commit()
if user and not is_internal_note: if user and not is_internal_note:
try: self._schedule_notification(
await self.notification_service.notify_support_admin_reply(ticket, message, user) self.notification_service.notify_support_admin_reply(ticket, message, user),
except Exception: "Failed to notify user about support admin reply %s",
logger.exception("Failed to notify user about support admin reply %s", ticket_id) ticket_id,
)
return ticket, message return ticket, message
async def change_status(self, admin_id: int, ticket_id: int, status: str) -> SupportTicket: async def change_status(self, admin_id: int, ticket_id: int, status: str) -> SupportTicket:
@@ -93,7 +93,8 @@
}); });
async function send(body) { async function send(body) {
await supportStore.sendReply(body); const sent = await supportStore.sendReply(body);
if (!sent) return;
reply = ""; reply = "";
} }
+18 -9
View File
@@ -193,17 +193,26 @@ export function createAdminSupportStore({ api, onToast, at }) {
body: JSON.stringify({ body, is_internal_note: internal }), body: JSON.stringify({ body, is_internal_note: internal }),
}); });
if (!res?.ok) throw res; if (!res?.ok) throw res;
state.update((s) => ({ state.update((s) =>
...s, s.openedTicketId === current
openedTicket: res.ticket ? {
? { ...s.openedTicket, ...res.ticket, user: res.ticket.user || s.openedTicket?.user } ...s,
: s.openedTicket, openedTicket: res.ticket
messages: [...s.messages, res.message], ? {
})); ...s.openedTicket,
await loadList(); ...res.ticket,
await loadStats(); 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) { } catch (error) {
onToast(error?.message || at("support_send_failed", {}, "Send failed")); onToast(error?.message || at("support_send_failed", {}, "Send failed"));
return false;
} finally { } finally {
state.update((s) => ({ ...s, sending: false })); state.update((s) => ({ ...s, sending: false }));
} }
+13 -7
View File
@@ -258,13 +258,19 @@ export function createSupportStore({ api, t, showToast }) {
body: JSON.stringify({ body }), body: JSON.stringify({ body }),
}); });
if (!res?.ok) throw res; if (!res?.ok) throw res;
state.update((s) => ({ state.update((s) =>
...s, s.openedTicketId === ticketId
openedTicket: res.ticket || s.openedTicket, ? {
messages: [...s.messages, res.message], ...s,
})); openedTicket: res.ticket || s.openedTicket,
await refreshUnread(); messages: res.message ? [...s.messages, res.message] : s.messages,
await loadList({ silent: true, force: true }); }
: s
);
void Promise.allSettled([
refreshUnread({ silent: true }),
loadList({ silent: true, force: true }),
]);
return true; return true;
} catch (error) { } catch (error) {
showToast(error?.message || t("wa_support_send_failed")); showToast(error?.message || t("wa_support_send_failed"));