feat: 完成P6-P8后端能力与P9路由重构(V1-V2,D1,D3,B1-B2,B4,I1-I4,P9A2-P9A3)

这个提交包含在:
2026-02-10 11:28:28 +08:00
父节点 bec21e1b38
当前提交 6bc09f557b
共修改 33 个文件,包含 3467 行新增和 13 行删除
+114 -1
查看文件
@@ -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;