feat: emit notification events from existing routes

This commit is contained in:
Kirill
2026-05-18 11:39:02 +05:00
parent e73a0ae09a
commit 84cdccaa17
7 changed files with 84 additions and 2 deletions
+1
View File
@@ -27,6 +27,7 @@ export async function issueEmailCode({ email, purpose, userId = null }) {
}, },
}) })
await sendLoginCodeEmail({ to: email, code }) await sendLoginCodeEmail({ to: email, code })
return code
} }
function parseEnvBool(raw) { function parseEnvBool(raw) {
+17
View File
@@ -1,5 +1,6 @@
import { prisma } from '../../lib/prisma.js' import { prisma } from '../../lib/prisma.js'
import { canTransitionAdminOrderStatus } from '../../lib/order-status.js' import { canTransitionAdminOrderStatus } from '../../lib/order-status.js'
import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js'
export async function registerAdminOrderRoutes(fastify) { export async function registerAdminOrderRoutes(fastify) {
fastify.get( fastify.get(
@@ -108,6 +109,14 @@ export async function registerAdminOrderRoutes(fastify) {
} }
const updated = await prisma.order.update({ where: { id }, data: { status: next } }) const updated = await prisma.order.update({ where: { id }, data: { status: next } })
request.server.eventBus.emit(NOTIFICATION_EVENTS.ORDER_STATUS_CHANGED, {
orderId: updated.id,
userId: existing.userId,
oldStatus: existing.status,
newStatus: next,
})
return { item: updated } return { item: updated }
}, },
) )
@@ -156,6 +165,14 @@ export async function registerAdminOrderRoutes(fastify) {
if (!order) return reply.code(404).send({ error: 'Заказ не найден' }) if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
const msg = await prisma.orderMessage.create({ data: { orderId: id, authorType: 'admin', text } }) const msg = await prisma.orderMessage.create({ data: { orderId: id, authorType: 'admin', text } })
request.server.eventBus.emit(NOTIFICATION_EVENTS.ORDER_MESSAGE_ADMIN_REPLY, {
orderId: id,
userId: order.userId,
messageId: msg.id,
preview: text,
})
return reply.code(201).send({ item: msg }) return reply.code(201).send({ item: msg })
}, },
) )
+13 -1
View File
@@ -1,4 +1,5 @@
import { prisma } from '../../lib/prisma.js' import { prisma } from '../../lib/prisma.js'
import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js'
export async function registerAdminReviewRoutes(fastify) { export async function registerAdminReviewRoutes(fastify) {
fastify.get( fastify.get(
@@ -43,7 +44,10 @@ export async function registerAdminReviewRoutes(fastify) {
return reply.code(400).send({ error: 'action должен быть approve или reject' }) return reply.code(400).send({ error: 'action должен быть approve или reject' })
} }
const existing = await prisma.review.findUnique({ where: { id } }) const existing = await prisma.review.findUnique({
where: { id },
include: { product: { select: { title: true } }, user: { select: { name: true, email: true } } },
})
if (!existing) return reply.code(404).send({ error: 'Отзыв не найден' }) if (!existing) return reply.code(404).send({ error: 'Отзыв не найден' })
const updated = await prisma.review.update({ const updated = await prisma.review.update({
@@ -53,6 +57,14 @@ export async function registerAdminReviewRoutes(fastify) {
moderatedAt: new Date(), moderatedAt: new Date(),
}, },
}) })
request.server.eventBus.emit('review:created', {
rating: updated.rating,
text: updated.text || '',
productTitle: existing.product?.title || '',
userName: existing.user?.name || existing.user?.email || '',
reviewId: updated.id,
})
return { item: updated } return { item: updated }
}, },
) )
+19 -1
View File
@@ -1,5 +1,6 @@
import { issueEmailCode, normalizeEmail, verifyEmailCode } from '../lib/auth.js' import { issueEmailCode, normalizeEmail, verifyEmailCode } from '../lib/auth.js'
import { prisma } from '../lib/prisma.js' import { prisma } from '../lib/prisma.js'
import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js'
function mapUserForClient(user) { function mapUserForClient(user) {
const adminEmail = normalizeEmail(process.env.ADMIN_EMAIL) const adminEmail = normalizeEmail(process.env.ADMIN_EMAIL)
@@ -18,7 +19,17 @@ export async function registerAuthRoutes(fastify) {
const email = normalizeEmail(request.body?.email) const email = normalizeEmail(request.body?.email)
if (!email || !email.includes('@')) return reply.code(400).send({ error: 'Некорректная почта' }) if (!email || !email.includes('@')) return reply.code(400).send({ error: 'Некорректная почта' })
await issueEmailCode({ email, purpose: 'login' }) const code = await issueEmailCode({ email, purpose: 'login' })
const adminEmail = process.env.ADMIN_EMAIL?.trim().toLowerCase()
const isAdmin = email === adminEmail
request.server.eventBus.emit(NOTIFICATION_EVENTS.AUTH_CODE_REQUESTED, {
email,
code,
isAdmin,
})
return { ok: true } return { ok: true }
}) })
@@ -37,6 +48,13 @@ export async function registerAuthRoutes(fastify) {
create: { email }, create: { email },
}) })
// Ensure notification preference exists
await prisma.notificationPreference.upsert({
where: { userId: user.id },
create: { userId: user.id, globalEnabled: true },
update: {},
})
const token = fastify.jwt.sign({ sub: user.id, email: user.email }) const token = fastify.jwt.sign({ sub: user.id, email: user.email })
return { token, user: mapUserForClient(user) } return { token, user: mapUserForClient(user) }
}) })
+9
View File
@@ -1,4 +1,5 @@
import { prisma } from '../lib/prisma.js' import { prisma } from '../lib/prisma.js'
import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js'
export async function registerUserMessageRoutes(fastify) { export async function registerUserMessageRoutes(fastify) {
fastify.get( fastify.get(
@@ -26,6 +27,14 @@ export async function registerUserMessageRoutes(fastify) {
if (!text) return reply.code(400).send({ error: 'Сообщение пустое' }) if (!text) return reply.code(400).send({ error: 'Сообщение пустое' })
if (text.length > 2000) return reply.code(400).send({ error: 'Сообщение слишком длинное' }) if (text.length > 2000) return reply.code(400).send({ error: 'Сообщение слишком длинное' })
const msg = await prisma.orderMessage.create({ data: { orderId: id, authorType: 'user', text } }) const msg = await prisma.orderMessage.create({ data: { orderId: id, authorType: 'user', text } })
request.server.eventBus.emit(NOTIFICATION_EVENTS.ORDER_MESSAGE_SENT, {
orderId: id,
authorType: 'user',
messageId: msg.id,
preview: text,
})
return reply.code(201).send({ item: msg }) return reply.code(201).send({ item: msg })
}, },
) )
+18
View File
@@ -1,5 +1,6 @@
import { isDeliveryCarrier } from '../lib/delivery-carrier.js' import { isDeliveryCarrier } from '../lib/delivery-carrier.js'
import { prisma } from '../lib/prisma.js' import { prisma } from '../lib/prisma.js'
import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js'
export async function registerUserOrderRoutes(fastify) { export async function registerUserOrderRoutes(fastify) {
// ---- Создание заказа (checkout) ---- // ---- Создание заказа (checkout) ----
@@ -156,6 +157,23 @@ export async function registerUserOrderRoutes(fastify) {
return reply.code(409).send({ error: (e instanceof Error && e.message) || 'Недостаточно товара' }) return reply.code(409).send({ error: (e instanceof Error && e.message) || 'Недостаточно товара' })
} }
// Emit notification events
request.server.eventBus.emit(NOTIFICATION_EVENTS.ORDER_CREATED, {
orderId: created.id,
userId,
totalCents: created.totalCents,
itemsCount: cartItems.length,
})
// Also emit admin notification
request.server.eventBus.emit('order:created:admin', {
orderId: created.id,
userId,
userEmail: request.user.email || '',
totalCents: created.totalCents,
itemsCount: cartItems.length,
})
return reply.code(201).send({ orderId: created.id }) return reply.code(201).send({ orderId: created.id })
}, },
) )
+7
View File
@@ -2,6 +2,7 @@ import { prisma } from '../lib/prisma.js'
import { escapeHtml } from '../lib/escape-html.js' import { escapeHtml } from '../lib/escape-html.js'
import { getOtherUploadMaxFileBytes } from '../lib/upload-limits.js' import { getOtherUploadMaxFileBytes } from '../lib/upload-limits.js'
import { saveImageBufferToUploads } from '../lib/upload-images.js' import { saveImageBufferToUploads } from '../lib/upload-images.js'
import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js'
export async function registerUserPaymentRoutes(fastify) { export async function registerUserPaymentRoutes(fastify) {
fastify.post( fastify.post(
@@ -105,6 +106,12 @@ export async function registerUserPaymentRoutes(fastify) {
return reply.code(500).send({ error: 'Не удалось сохранить оплату' }) return reply.code(500).send({ error: 'Не удалось сохранить оплату' })
} }
request.server.eventBus.emit(NOTIFICATION_EVENTS.PAYMENT_STATUS_CHANGED, {
orderId: id,
userId,
paymentStatus: 'pending',
})
return { ok: true, status: 'PENDING_PAYMENT' } return { ok: true, status: 'PENDING_PAYMENT' }
}, },
) )