test commit
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { prisma } from '../prisma.js'
|
||||
import { NOTIFICATION_EVENTS } from '../../../../shared/constants/notification-events.js'
|
||||
import { prisma } from "../prisma.js";
|
||||
import { NOTIFICATION_EVENTS } from "../../../../shared/constants/notification-events.js";
|
||||
|
||||
const {
|
||||
ORDER_CREATED,
|
||||
@@ -8,93 +8,103 @@ const {
|
||||
ORDER_MESSAGE_ADMIN_REPLY,
|
||||
PAYMENT_STATUS_CHANGED,
|
||||
AUTH_CODE_REQUESTED,
|
||||
} = NOTIFICATION_EVENTS
|
||||
} = NOTIFICATION_EVENTS;
|
||||
|
||||
const userEventFieldMap = {
|
||||
[ORDER_CREATED]: 'orderCreated',
|
||||
[ORDER_STATUS_CHANGED]: 'orderStatusChanged',
|
||||
[ORDER_MESSAGE_ADMIN_REPLY]: 'orderMessageReceived',
|
||||
[PAYMENT_STATUS_CHANGED]: 'paymentStatusChanged',
|
||||
}
|
||||
[ORDER_CREATED]: "orderCreated",
|
||||
[ORDER_STATUS_CHANGED]: "orderStatusChanged",
|
||||
[ORDER_MESSAGE_ADMIN_REPLY]: "orderMessageReceived",
|
||||
[PAYMENT_STATUS_CHANGED]: "paymentStatusChanged",
|
||||
};
|
||||
|
||||
const adminEventFieldMap = {
|
||||
[ORDER_CREATED]: 'newOrder',
|
||||
[ORDER_MESSAGE_SENT]: 'newOrderMessage',
|
||||
'review:created': 'newReview',
|
||||
}
|
||||
[ORDER_CREATED]: "newOrder",
|
||||
[ORDER_MESSAGE_SENT]: "newOrderMessage",
|
||||
"review:created": "newReview",
|
||||
};
|
||||
|
||||
export async function resolveUserNotificationTargets(eventType, payload) {
|
||||
const targets = []
|
||||
const targets = [];
|
||||
|
||||
if (payload.userId) {
|
||||
const prefs = await prisma.notificationPreference.findUnique({
|
||||
where: { userId: payload.userId },
|
||||
})
|
||||
});
|
||||
|
||||
if (prefs && prefs.globalEnabled) {
|
||||
const field = userEventFieldMap[eventType]
|
||||
const field = userEventFieldMap[eventType];
|
||||
if (field && prefs[field]) {
|
||||
const user = await prisma.user.findUnique({ where: { id: payload.userId }, select: { email: true } })
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: payload.userId },
|
||||
select: { email: true },
|
||||
});
|
||||
if (user) {
|
||||
targets.push({ channel: 'email', recipient: user.email })
|
||||
targets.push({ channel: "email", recipient: user.email });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targets
|
||||
return targets;
|
||||
}
|
||||
|
||||
export async function resolveAdminNotificationTargets(eventType, payload) {
|
||||
const targets = []
|
||||
const settings = await prisma.adminNotificationSettings.findFirst()
|
||||
if (!settings) return targets
|
||||
const targets = [];
|
||||
const settings = await prisma.adminNotificationSettings.findFirst();
|
||||
if (!settings) return targets;
|
||||
|
||||
const field = adminEventFieldMap[eventType]
|
||||
if (field === 'newReview') {
|
||||
if (!settings.newReview) return targets
|
||||
const field = adminEventFieldMap[eventType];
|
||||
if (field === "newReview") {
|
||||
if (!settings.newReview) return targets;
|
||||
} else if (field && !settings[field]) {
|
||||
return targets
|
||||
return targets;
|
||||
}
|
||||
|
||||
if (settings.emailEnabled) {
|
||||
const admin = await prisma.user.findFirst({
|
||||
where: { email: process.env.ADMIN_EMAIL },
|
||||
select: { email: true },
|
||||
})
|
||||
});
|
||||
if (admin) {
|
||||
targets.push({ channel: 'email', recipient: admin.email })
|
||||
targets.push({ channel: "email", recipient: admin.email });
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.telegramEnabled && settings.telegramChatId) {
|
||||
targets.push({ channel: 'telegram', recipient: settings.telegramChatId })
|
||||
targets.push({ channel: "telegram", recipient: settings.telegramChatId });
|
||||
}
|
||||
|
||||
return targets
|
||||
return targets;
|
||||
}
|
||||
|
||||
export async function resolveAuthCodeTargets(eventType, payload) {
|
||||
const targets = []
|
||||
const targets = [];
|
||||
|
||||
if (payload.email) {
|
||||
targets.push({ channel: 'email', recipient: payload.email })
|
||||
targets.push({ channel: "email", recipient: payload.email });
|
||||
}
|
||||
|
||||
if (payload.isAdmin) {
|
||||
const settings = await prisma.adminNotificationSettings.findFirst()
|
||||
if (settings && settings.telegramEnabled && settings.telegramChatId && settings.authCodeDuplicate) {
|
||||
targets.push({ channel: 'telegram', recipient: settings.telegramChatId })
|
||||
const settings = await prisma.adminNotificationSettings.findFirst();
|
||||
if (
|
||||
settings &&
|
||||
settings.telegramEnabled &&
|
||||
settings.telegramChatId &&
|
||||
settings.authCodeDuplicate
|
||||
) {
|
||||
targets.push({ channel: "telegram", recipient: settings.telegramChatId });
|
||||
}
|
||||
}
|
||||
|
||||
return targets
|
||||
return targets;
|
||||
}
|
||||
|
||||
export async function ensureUserNotificationPreference(userId) {
|
||||
const existing = await prisma.notificationPreference.findUnique({ where: { userId } })
|
||||
if (existing) return existing
|
||||
const existing = await prisma.notificationPreference.findUnique({
|
||||
where: { userId },
|
||||
});
|
||||
if (existing) return existing;
|
||||
return prisma.notificationPreference.create({
|
||||
data: { userId, globalEnabled: true },
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,89 +8,113 @@ function baseLayout(title, body) {
|
||||
</div>
|
||||
${body}
|
||||
<div style="margin-top:24px;padding-top:16px;border-top:1px solid #e0e0e0;color:#666;font-size:14px;">
|
||||
<p>Craftshop — магазин handmade изделий</p>
|
||||
<p>Любимый Креатив — магазин handmade изделий</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
</html>`;
|
||||
}
|
||||
|
||||
export function renderOrderCreatedEmail({ orderId, totalCents, itemsCount }) {
|
||||
const total = (totalCents / 100).toLocaleString('ru-RU')
|
||||
const total = (totalCents / 100).toLocaleString("ru-RU");
|
||||
const body = `
|
||||
<p>Ваш заказ <b>#${orderId.slice(0, 8)}</b> успешно создан.</p>
|
||||
<p>Товаров: ${itemsCount} | Сумма: <b>${total} ₽</b></p>
|
||||
<p>Мы сообщим вам об изменениях статуса.</p>
|
||||
`
|
||||
return { subject: 'Заказ создан', html: baseLayout('Заказ создан', body) }
|
||||
`;
|
||||
return { subject: "Заказ создан", html: baseLayout("Заказ создан", body) };
|
||||
}
|
||||
|
||||
export function renderOrderStatusChangedEmail({ orderId, oldStatus, newStatus }) {
|
||||
export function renderOrderStatusChangedEmail({
|
||||
orderId,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
}) {
|
||||
const statusLabels = {
|
||||
DRAFT: 'Черновик',
|
||||
PENDING_PAYMENT: 'Ожидает оплаты',
|
||||
IN_PROGRESS: 'В работе',
|
||||
READY_FOR_PICKUP: 'Готов к выдаче',
|
||||
SHIPPED: 'Отправлен',
|
||||
DONE: 'Выполнен',
|
||||
CANCELLED: 'Отменён',
|
||||
}
|
||||
const oldLabel = statusLabels[oldStatus] || oldStatus
|
||||
const newLabel = statusLabels[newStatus] || newStatus
|
||||
DRAFT: "Черновик",
|
||||
PENDING_PAYMENT: "Ожидает оплаты",
|
||||
IN_PROGRESS: "В работе",
|
||||
READY_FOR_PICKUP: "Готов к выдаче",
|
||||
SHIPPED: "Отправлен",
|
||||
DONE: "Выполнен",
|
||||
CANCELLED: "Отменён",
|
||||
};
|
||||
const oldLabel = statusLabels[oldStatus] || oldStatus;
|
||||
const newLabel = statusLabels[newStatus] || newStatus;
|
||||
const body = `
|
||||
<p>Статус заказа <b>#${orderId.slice(0, 8)}</b> изменён.</p>
|
||||
<p><b>${oldLabel}</b> → <b>${newLabel}</b></p>
|
||||
`
|
||||
return { subject: `Статус заказа изменён — ${newLabel}`, html: baseLayout('Статус заказа изменён', body) }
|
||||
`;
|
||||
return {
|
||||
subject: `Статус заказа изменён — ${newLabel}`,
|
||||
html: baseLayout("Статус заказа изменён", body),
|
||||
};
|
||||
}
|
||||
|
||||
export function renderOrderMessageEmail({ orderId, preview }) {
|
||||
const truncated = preview.length > 200 ? preview.slice(0, 197) + '...' : preview
|
||||
const truncated =
|
||||
preview.length > 200 ? preview.slice(0, 197) + "..." : preview;
|
||||
const body = `
|
||||
<p>Новое сообщение к заказу <b>#${orderId.slice(0, 8)}</b>:</p>
|
||||
<div style="background:#f0f0f0;padding:12px;border-radius:6px;margin:12px 0;">
|
||||
${truncated}
|
||||
</div>
|
||||
<p>Ответьте в личном кабинете.</p>
|
||||
`
|
||||
return { subject: 'Новое сообщение к заказу', html: baseLayout('Новое сообщение', body) }
|
||||
`;
|
||||
return {
|
||||
subject: "Новое сообщение к заказу",
|
||||
html: baseLayout("Новое сообщение", body),
|
||||
};
|
||||
}
|
||||
|
||||
export function renderPaymentStatusChangedEmail({ orderId, paymentStatus }) {
|
||||
const statusLabels = {
|
||||
pending: 'Ожидает',
|
||||
confirmed: 'Подтверждён',
|
||||
rejected: 'Отклонён',
|
||||
}
|
||||
const label = statusLabels[paymentStatus] || paymentStatus
|
||||
pending: "Ожидает",
|
||||
confirmed: "Подтверждён",
|
||||
rejected: "Отклонён",
|
||||
};
|
||||
const label = statusLabels[paymentStatus] || paymentStatus;
|
||||
const body = `
|
||||
<p>Статус оплаты заказа <b>#${orderId.slice(0, 8)}</b>: <b>${label}</b>.</p>
|
||||
`
|
||||
return { subject: `Оплата заказа — ${label}`, html: baseLayout('Оплата заказа', body) }
|
||||
`;
|
||||
return {
|
||||
subject: `Оплата заказа — ${label}`,
|
||||
html: baseLayout("Оплата заказа", body),
|
||||
};
|
||||
}
|
||||
|
||||
export function renderAdminOrderCreatedEmail({ orderId, userEmail, totalCents, itemsCount }) {
|
||||
const total = (totalCents / 100).toLocaleString('ru-RU')
|
||||
export function renderAdminOrderCreatedEmail({
|
||||
orderId,
|
||||
userEmail,
|
||||
totalCents,
|
||||
itemsCount,
|
||||
}) {
|
||||
const total = (totalCents / 100).toLocaleString("ru-RU");
|
||||
const body = `
|
||||
<p>Новый заказ <b>#${orderId.slice(0, 8)}</b> от <b>${userEmail}</b>.</p>
|
||||
<p>Товаров: ${itemsCount} | Сумма: <b>${total} ₽</b></p>
|
||||
`
|
||||
return { subject: 'Новый заказ', html: baseLayout('Новый заказ', body) }
|
||||
`;
|
||||
return { subject: "Новый заказ", html: baseLayout("Новый заказ", body) };
|
||||
}
|
||||
|
||||
export function renderAdminNewReviewEmail({ rating, text, productTitle, userName }) {
|
||||
const stars = '★'.repeat(rating) + '☆'.repeat(5 - rating)
|
||||
export function renderAdminNewReviewEmail({
|
||||
rating,
|
||||
text,
|
||||
productTitle,
|
||||
userName,
|
||||
}) {
|
||||
const stars = "★".repeat(rating) + "☆".repeat(5 - rating);
|
||||
const body = `
|
||||
<p>Новый отзыв ${stars} на товар <b>${productTitle}</b> от <b>${userName}</b>.</p>
|
||||
${text ? `<div style="background:#f0f0f0;padding:12px;border-radius:6px;margin:12px 0;">${text}</div>` : ''}
|
||||
${text ? `<div style="background:#f0f0f0;padding:12px;border-radius:6px;margin:12px 0;">${text}</div>` : ""}
|
||||
<p>Проверьте отзыв в админ-панели.</p>
|
||||
`
|
||||
return { subject: 'Новый отзыв', html: baseLayout('Новый отзыв', body) }
|
||||
`;
|
||||
return { subject: "Новый отзыв", html: baseLayout("Новый отзыв", body) };
|
||||
}
|
||||
|
||||
export function renderAuthCodeEmail({ code }) {
|
||||
const body = `
|
||||
<p>Ваш код входа: <b style="font-size:24px;letter-spacing:4px;">${code}</b></p>
|
||||
<p>Если это были не вы — просто проигнорируйте письмо.</p>
|
||||
`
|
||||
return { subject: 'Код входа', html: baseLayout('Код входа', body) }
|
||||
`;
|
||||
return { subject: "Код входа", html: baseLayout("Код входа", body) };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user