feat: 完成P6-P8后端能力与P9路由重构(V1-V2,D1,D3,B1-B2,B4,I1-I4,P9A2-P9A3)
这个提交包含在:
@@ -2,6 +2,8 @@ const { z } = require("zod");
|
||||
|
||||
const { AppError } = require("../utils/AppError");
|
||||
const { sendData } = require("../utils/reply");
|
||||
const { buildAuditLogData } = require("../utils/audit");
|
||||
const { sendInstantMessageByChannel } = require("../services/notifications/channels");
|
||||
|
||||
const LIST_QUERY_SCHEMA = z.object({
|
||||
page: z.coerce.number().int().min(1).optional(),
|
||||
@@ -58,6 +60,19 @@ function mapNotificationItem(record) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapNotificationChannel(record) {
|
||||
return {
|
||||
id: record.id,
|
||||
channelType: record.channel_type,
|
||||
name: record.name,
|
||||
enabled: record.enabled,
|
||||
configJson: record.config_json,
|
||||
templateJson: record.template_json,
|
||||
createdAt: record.created_at?.toISOString?.() ?? null,
|
||||
updatedAt: record.updated_at?.toISOString?.() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async function notificationsRoutes(fastify) {
|
||||
fastify.get(
|
||||
"/notifications",
|
||||
@@ -189,7 +204,105 @@ async function notificationsRoutes(fastify) {
|
||||
return sendData(reply, request, mapNotificationItem(notification));
|
||||
},
|
||||
);
|
||||
|
||||
fastify.get(
|
||||
"/notifications/channels",
|
||||
{
|
||||
preHandler: fastify.requirePermission("notification.channel.manage"),
|
||||
},
|
||||
async (request, reply) => {
|
||||
const items = await fastify.prisma.notificationChannel.findMany({
|
||||
orderBy: [{ channel_type: "asc" }, { name: "asc" }],
|
||||
});
|
||||
|
||||
return sendData(reply, request, {
|
||||
items: items.map(mapNotificationChannel),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
"/notifications/channels/test",
|
||||
{
|
||||
preHandler: fastify.requirePermission("notification.channel.manage"),
|
||||
},
|
||||
async (request, reply) => {
|
||||
const schema = z
|
||||
.object({
|
||||
channelType: z.string().trim().min(1).max(32),
|
||||
name: z.string().trim().min(1).max(100),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const parsed = schema.safeParse(request.body ?? {});
|
||||
if (!parsed.success) {
|
||||
throw new AppError({
|
||||
code: "VALID_001",
|
||||
message: "参数校验失败",
|
||||
statusCode: 400,
|
||||
details: parsed.error.flatten(),
|
||||
});
|
||||
}
|
||||
|
||||
const input = parsed.data;
|
||||
|
||||
const channel = await fastify.prisma.notificationChannel.upsert({
|
||||
where: {
|
||||
channel_type_name: {
|
||||
channel_type: input.channelType,
|
||||
name: input.name,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
enabled: true,
|
||||
config_json: {
|
||||
mode: "mock",
|
||||
},
|
||||
updated_by: request.employee.id,
|
||||
},
|
||||
create: {
|
||||
channel_type: input.channelType,
|
||||
name: input.name,
|
||||
enabled: true,
|
||||
config_json: {
|
||||
mode: "mock",
|
||||
},
|
||||
created_by: request.employee.id,
|
||||
updated_by: request.employee.id,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await sendInstantMessageByChannel({
|
||||
config: fastify.config,
|
||||
channel,
|
||||
payload: {
|
||||
title: "通知渠道测试",
|
||||
body: `测试人:${request.employee.id}`,
|
||||
targetType: "notification_channel",
|
||||
targetId: channel.id,
|
||||
},
|
||||
});
|
||||
|
||||
await fastify.prisma.auditLog.create({
|
||||
data: buildAuditLogData(request, {
|
||||
actorId: request.employee.id,
|
||||
action: "NOTIFICATION_CHANNEL_TESTED",
|
||||
targetType: "notification_channel",
|
||||
targetId: channel.id,
|
||||
extra: {
|
||||
channelType: channel.channel_type,
|
||||
name: channel.name,
|
||||
resultCode: result.code,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return sendData(reply, request, {
|
||||
channel: mapNotificationChannel(channel),
|
||||
testResult: result,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = notificationsRoutes;
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户