diff --git a/server/src/lib/auth.js b/server/src/lib/auth.js index 19a47c4..1191c3f 100644 --- a/server/src/lib/auth.js +++ b/server/src/lib/auth.js @@ -27,6 +27,7 @@ export async function issueEmailCode({ email, purpose, userId = null }) { }, }) await sendLoginCodeEmail({ to: email, code }) + return code } function parseEnvBool(raw) { diff --git a/server/src/routes/api/admin-orders.js b/server/src/routes/api/admin-orders.js index bc31d89..e033be3 100644 --- a/server/src/routes/api/admin-orders.js +++ b/server/src/routes/api/admin-orders.js @@ -1,5 +1,6 @@ import { prisma } from '../../lib/prisma.js' import { canTransitionAdminOrderStatus } from '../../lib/order-status.js' +import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js' export async function registerAdminOrderRoutes(fastify) { fastify.get( @@ -108,6 +109,14 @@ export async function registerAdminOrderRoutes(fastify) { } 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 } }, ) @@ -156,6 +165,14 @@ export async function registerAdminOrderRoutes(fastify) { if (!order) return reply.code(404).send({ error: 'Заказ не найден' }) 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 }) }, ) diff --git a/server/src/routes/api/admin-reviews.js b/server/src/routes/api/admin-reviews.js index 8b7548f..9a64ade 100644 --- a/server/src/routes/api/admin-reviews.js +++ b/server/src/routes/api/admin-reviews.js @@ -1,4 +1,5 @@ import { prisma } from '../../lib/prisma.js' +import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js' export async function registerAdminReviewRoutes(fastify) { fastify.get( @@ -43,7 +44,10 @@ export async function registerAdminReviewRoutes(fastify) { 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: 'Отзыв не найден' }) const updated = await prisma.review.update({ @@ -53,6 +57,14 @@ export async function registerAdminReviewRoutes(fastify) { 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 } }, ) diff --git a/server/src/routes/auth.js b/server/src/routes/auth.js index 2d3e218..41b63ab 100644 --- a/server/src/routes/auth.js +++ b/server/src/routes/auth.js @@ -1,5 +1,6 @@ import { issueEmailCode, normalizeEmail, verifyEmailCode } from '../lib/auth.js' import { prisma } from '../lib/prisma.js' +import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js' function mapUserForClient(user) { const adminEmail = normalizeEmail(process.env.ADMIN_EMAIL) @@ -18,7 +19,17 @@ export async function registerAuthRoutes(fastify) { const email = normalizeEmail(request.body?.email) 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 } }) @@ -37,6 +48,13 @@ export async function registerAuthRoutes(fastify) { 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 }) return { token, user: mapUserForClient(user) } }) diff --git a/server/src/routes/user-messages.js b/server/src/routes/user-messages.js index 76eb835..bae01ac 100644 --- a/server/src/routes/user-messages.js +++ b/server/src/routes/user-messages.js @@ -1,4 +1,5 @@ import { prisma } from '../lib/prisma.js' +import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js' export async function registerUserMessageRoutes(fastify) { fastify.get( @@ -26,6 +27,14 @@ export async function registerUserMessageRoutes(fastify) { if (!text) 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 } }) + + 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 }) }, ) diff --git a/server/src/routes/user-orders.js b/server/src/routes/user-orders.js index 9089bc5..2822590 100644 --- a/server/src/routes/user-orders.js +++ b/server/src/routes/user-orders.js @@ -1,5 +1,6 @@ import { isDeliveryCarrier } from '../lib/delivery-carrier.js' import { prisma } from '../lib/prisma.js' +import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js' export async function registerUserOrderRoutes(fastify) { // ---- Создание заказа (checkout) ---- @@ -156,6 +157,23 @@ export async function registerUserOrderRoutes(fastify) { 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 }) }, ) diff --git a/server/src/routes/user-payments.js b/server/src/routes/user-payments.js index 058cad7..c9c3633 100644 --- a/server/src/routes/user-payments.js +++ b/server/src/routes/user-payments.js @@ -2,6 +2,7 @@ import { prisma } from '../lib/prisma.js' import { escapeHtml } from '../lib/escape-html.js' import { getOtherUploadMaxFileBytes } from '../lib/upload-limits.js' import { saveImageBufferToUploads } from '../lib/upload-images.js' +import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js' export async function registerUserPaymentRoutes(fastify) { fastify.post( @@ -105,6 +106,12 @@ export async function registerUserPaymentRoutes(fastify) { 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' } }, )