From d6ee6a9b286d49114b2b1f5858239ea7dcb56822 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:19:19 +0330 Subject: [PATCH] Import source: app/backend/src/lib/error-digest Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- app/backend/src/lib/error-digest.ts | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 app/backend/src/lib/error-digest.ts diff --git a/app/backend/src/lib/error-digest.ts b/app/backend/src/lib/error-digest.ts new file mode 100644 index 0000000..a3efc51 --- /dev/null +++ b/app/backend/src/lib/error-digest.ts @@ -0,0 +1,48 @@ +import { prisma } from '../db.js'; +import { sendEmail } from './email.js'; + +const ALERT_THRESHOLD = 50; +const ALERT_RECIPIENT = process.env.ADMIN_ALERT_EMAIL || ''; + +export async function sendErrorDigest(): Promise<{ sent: boolean; count: number }> { + const since = new Date(); + since.setHours(0, 0, 0, 0); + + const errors = await prisma.clientError.findMany({ + where: { createdAt: { gte: since } }, + orderBy: { createdAt: 'desc' }, + take: 100, + }); + + const count = await prisma.clientError.count({ + where: { createdAt: { gte: since } }, + }); + + if (count < ALERT_THRESHOLD || !ALERT_RECIPIENT) { + return { sent: false, count }; + } + + const topMessages = Object.entries( + errors.reduce>((acc, e) => { + acc[e.message] = (acc[e.message] || 0) + 1; + return acc; + }, {}), + ) + .sort(([, a], [, b]) => b - a) + .slice(0, 10); + + const html = ` +
+

گزارش خطاهای روزانه تکناو

+

تعداد خطاها در ۲۴ ساعت گذشته: ${count}

+

بیشترین تکرار:

+
    + ${topMessages.map(([msg, cnt]) => `
  • ${msg} (${cnt} بار)
  • `).join('')} +
+

این گزارش از پنل خطاهای تکناو ارسال شده است.

+
+ `; + + await sendEmail({ to: ALERT_RECIPIENT, subject: `گزارش خطاهای تکناو — ${count} خطا`, html }); + return { sent: true, count }; +}