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 cab328b..685d900 100644 Binary files a/server/prisma/prisma/dev.db and b/server/prisma/prisma/dev.db differ diff --git a/server/src/index.js b/server/src/index.js index f7f8e0b..eb50e3f 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -141,10 +141,24 @@ await registerUserNotificationRoutes(fastify) await registerOAuthSocialRoutes(fastify) await registerYookassaWebhookRoute(fastify) await registerApiRoutes(fastify) -await ensureAdminUser() -await getOrCreateUnspecifiedCategory() -await notificationQueue.flushPendingOnStartup() +try { + await ensureAdminUser() +} catch (err) { + fastify.log.error({ err }, 'ensureAdminUser failed — continuing startup') +} + +try { + await getOrCreateUnspecifiedCategory() +} catch (err) { + fastify.log.error({ err }, 'getOrCreateUnspecifiedCategory failed — continuing startup') +} + +try { + await notificationQueue.flushPendingOnStartup() +} catch (err) { + fastify.log.error({ err }, 'notificationQueue.flushPendingOnStartup failed') +} notificationQueue.start() const { @@ -158,9 +172,40 @@ const { } = NOTIFICATION_EVENTS async function dispatchNotification(eventType, payload) { - if (eventType === AUTH_CODE_REQUESTED) { - const targets = await resolveAuthCodeTargets(eventType, payload) - for (const target of targets.filter((t) => 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 }) {