From e5e1e01c7ea4c61f31d66a4391492f21588eb85d Mon Sep 17 00:00:00 2001 From: Kirill Date: Mon, 25 May 2026 23:06:41 +0500 Subject: [PATCH] =?UTF-8?q?=D1=8B=D0=B2=D0=B2=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/package.json | 2 +- server/prisma/prisma/dev.db | Bin 348160 -> 344064 bytes server/src/index.js | 92 ++++++++++++++++++++++-------------- server/src/lib/email.js | 24 ++++++---- 4 files changed, 74 insertions(+), 44 deletions(-) diff --git a/server/package.json b/server/package.json index 8c8d921..e784264 100644 --- a/server/package.json +++ b/server/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "node --env-file=.env --watch-path=./src src/index.js", + "dev": "node --env-file=.env --unhandled-rejections=warn --watch src/index.js", "dev:classic": "node --watch src/index.js", "start": "node src/index.js", "db:migrate": "prisma migrate dev", diff --git a/server/prisma/prisma/dev.db b/server/prisma/prisma/dev.db index cab328bd028380398f6a24278d027f0dd5e157f9..685d90097d0935a50c87146012c5d70b69377343 100644 GIT binary patch delta 9202 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 wWV#l30fq+aAFQKri8XIg?rpOZv8r t.channel === 'telegram')) { + try { + if (eventType === AUTH_CODE_REQUESTED) { + const targets = await resolveAuthCodeTargets(eventType, payload) + for (const target of targets.filter((t) => t.channel === 'telegram')) { + const log = await prisma.notificationLog.create({ + data: { + eventType, + channel: target.channel, + status: 'pending', + payload: JSON.stringify(payload), + }, + }) + notificationQueue.enqueue({ ...target, eventType, payload, logId: log.id }) + } + return + } + + const userTargets = await resolveUserNotificationTargets(eventType, payload) + for (const target of userTargets) { + const log = await prisma.notificationLog.create({ + data: { + userId: payload.userId, + eventType, + channel: target.channel, + status: 'pending', + payload: JSON.stringify(payload), + }, + }) + notificationQueue.enqueue({ ...target, eventType, payload, logId: log.id }) + } + + const adminEventType = eventType === 'order:created:admin' ? ORDER_CREATED : eventType + const adminTargets = await resolveAdminNotificationTargets(adminEventType, payload) + for (const target of adminTargets) { const log = await prisma.notificationLog.create({ data: { eventType, @@ -171,35 +216,8 @@ async function dispatchNotification(eventType, payload) { }) notificationQueue.enqueue({ ...target, eventType, payload, logId: log.id }) } - return - } - - const userTargets = await resolveUserNotificationTargets(eventType, payload) - for (const target of userTargets) { - const log = await prisma.notificationLog.create({ - data: { - userId: payload.userId, - eventType, - channel: target.channel, - status: 'pending', - payload: JSON.stringify(payload), - }, - }) - notificationQueue.enqueue({ ...target, eventType, payload, logId: log.id }) - } - - const adminEventType = eventType === 'order:created:admin' ? ORDER_CREATED : eventType - const adminTargets = await resolveAdminNotificationTargets(adminEventType, payload) - for (const target of adminTargets) { - const log = await prisma.notificationLog.create({ - data: { - eventType, - channel: target.channel, - status: 'pending', - payload: JSON.stringify(payload), - }, - }) - notificationQueue.enqueue({ ...target, eventType, payload, logId: log.id }) + } catch (err) { + console.error(`[notification] Error dispatching ${eventType}:`, err.message) } } @@ -221,6 +239,10 @@ async function shutdown() { process.on('SIGINT', shutdown) process.on('SIGTERM', shutdown) +process.on('unhandledRejection', (reason) => { + console.error('[process] Unhandled rejection:', reason?.message || reason) +}) + try { await fastify.listen({ port, host: '0.0.0.0' }) } catch (err) { diff --git a/server/src/lib/email.js b/server/src/lib/email.js index 60bd976..d2d995f 100644 --- a/server/src/lib/email.js +++ b/server/src/lib/email.js @@ -9,6 +9,9 @@ function createTransporter() { host: process.env.SMTP_HOST, port: Number(process.env.SMTP_PORT), secure: process.env.SMTP_SECURE === 'true', + connectionTimeout: 5000, + greetingTimeout: 5000, + socketTimeout: 5000, auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS, @@ -22,15 +25,20 @@ export async function sendLoginCodeEmail({ to, code }) { return } - const transporter = createTransporter() - const from = process.env.MAIL_FROM || process.env.SMTP_USER + try { + const transporter = createTransporter() + const from = process.env.MAIL_FROM || process.env.SMTP_USER - await transporter.sendMail({ - from, - to, - subject: 'Код входа', - text: `Ваш код: ${code}\n\nЕсли это были не вы — просто проигнорируйте письмо.`, - }) + await transporter.sendMail({ + from, + to, + subject: 'Код входа', + text: `Ваш код: ${code}\n\nЕсли это были не вы — просто проигнорируйте письмо.`, + }) + } catch (err) { + console.error(`[email] Failed to send login code to ${to}: ${err.message}`) + console.info(`[DEV] login code for ${to}: ${code}`) + } } export async function sendNotificationEmail({ to, subject, html }) {