ыввы
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"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",
|
"dev:classic": "node --watch src/index.js",
|
||||||
"start": "node src/index.js",
|
"start": "node src/index.js",
|
||||||
"db:migrate": "prisma migrate dev",
|
"db:migrate": "prisma migrate dev",
|
||||||
|
|||||||
Binary file not shown.
+57
-35
@@ -141,10 +141,24 @@ await registerUserNotificationRoutes(fastify)
|
|||||||
await registerOAuthSocialRoutes(fastify)
|
await registerOAuthSocialRoutes(fastify)
|
||||||
await registerYookassaWebhookRoute(fastify)
|
await registerYookassaWebhookRoute(fastify)
|
||||||
await registerApiRoutes(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()
|
notificationQueue.start()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -158,9 +172,40 @@ const {
|
|||||||
} = NOTIFICATION_EVENTS
|
} = NOTIFICATION_EVENTS
|
||||||
|
|
||||||
async function dispatchNotification(eventType, payload) {
|
async function dispatchNotification(eventType, payload) {
|
||||||
if (eventType === AUTH_CODE_REQUESTED) {
|
try {
|
||||||
const targets = await resolveAuthCodeTargets(eventType, payload)
|
if (eventType === AUTH_CODE_REQUESTED) {
|
||||||
for (const target of targets.filter((t) => t.channel === 'telegram')) {
|
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({
|
const log = await prisma.notificationLog.create({
|
||||||
data: {
|
data: {
|
||||||
eventType,
|
eventType,
|
||||||
@@ -171,35 +216,8 @@ async function dispatchNotification(eventType, payload) {
|
|||||||
})
|
})
|
||||||
notificationQueue.enqueue({ ...target, eventType, payload, logId: log.id })
|
notificationQueue.enqueue({ ...target, eventType, payload, logId: log.id })
|
||||||
}
|
}
|
||||||
return
|
} catch (err) {
|
||||||
}
|
console.error(`[notification] Error dispatching ${eventType}:`, err.message)
|
||||||
|
|
||||||
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 })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +239,10 @@ async function shutdown() {
|
|||||||
process.on('SIGINT', shutdown)
|
process.on('SIGINT', shutdown)
|
||||||
process.on('SIGTERM', shutdown)
|
process.on('SIGTERM', shutdown)
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason) => {
|
||||||
|
console.error('[process] Unhandled rejection:', reason?.message || reason)
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fastify.listen({ port, host: '0.0.0.0' })
|
await fastify.listen({ port, host: '0.0.0.0' })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
+16
-8
@@ -9,6 +9,9 @@ function createTransporter() {
|
|||||||
host: process.env.SMTP_HOST,
|
host: process.env.SMTP_HOST,
|
||||||
port: Number(process.env.SMTP_PORT),
|
port: Number(process.env.SMTP_PORT),
|
||||||
secure: process.env.SMTP_SECURE === 'true',
|
secure: process.env.SMTP_SECURE === 'true',
|
||||||
|
connectionTimeout: 5000,
|
||||||
|
greetingTimeout: 5000,
|
||||||
|
socketTimeout: 5000,
|
||||||
auth: {
|
auth: {
|
||||||
user: process.env.SMTP_USER,
|
user: process.env.SMTP_USER,
|
||||||
pass: process.env.SMTP_PASS,
|
pass: process.env.SMTP_PASS,
|
||||||
@@ -22,15 +25,20 @@ export async function sendLoginCodeEmail({ to, code }) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const transporter = createTransporter()
|
try {
|
||||||
const from = process.env.MAIL_FROM || process.env.SMTP_USER
|
const transporter = createTransporter()
|
||||||
|
const from = process.env.MAIL_FROM || process.env.SMTP_USER
|
||||||
|
|
||||||
await transporter.sendMail({
|
await transporter.sendMail({
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
subject: 'Код входа',
|
subject: 'Код входа',
|
||||||
text: `Ваш код: ${code}\n\nЕсли это были не вы — просто проигнорируйте письмо.`,
|
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 }) {
|
export async function sendNotificationEmail({ to, subject, html }) {
|
||||||
|
|||||||
Reference in New Issue
Block a user