Files
alibb-server/backend/src/routes/ingest.ts
T

86 lines
3.3 KiB
TypeScript
Raw Normal View History

2026-08-27 16:11:49 +08:00
import { Router } from 'express';
import { z } from 'zod';
import { ingestAuth } from '../middleware/ingestAuth.js';
import { ingestLimiter } from '../middleware/rateLimit.js';
import { ingest } from '../services/ingest.service.js';
const MessageSchema = z.object({
messageId: z.string().min(1),
sendTime: z.string().datetime(),
sender: z.string().optional(),
direction: z.enum(['incoming', 'outgoing', 'system']),
content: z.string(),
translation: z.string().optional().default(''),
});
const BehaviorSchema = z
.object({
productViews: z.number().nullable().optional(),
validInquiries: z.number().nullable().optional(),
validRfq: z.number().nullable().optional(),
loginDays: z.number().nullable().optional(),
spamInquiries: z.number().nullable().optional(),
blacklistCount: z.number().nullable().optional(),
topIndustry: z.string().nullable().optional(),
})
.optional();
const PayloadSchema = z.object({
inquiry: z.object({
inquiryId: z.string().min(1),
manager: z.string().optional(),
createTime: z.string().datetime(),
updateTime: z.string().datetime(),
status: z.string().optional(),
msgPreview: z.string().optional(),
isFlagged: z.boolean().optional().default(false),
}),
customer: z
.object({
customerId: z.string().min(1),
name: z.string().optional(),
country: z.string().nullable().optional(),
email: z.string().nullable().optional(),
landline: z.string().nullable().optional(),
businessType: z.string().nullable().optional(),
salesPlatform: z.string().nullable().optional(),
website: z.string().nullable().optional(),
phone: z.string().nullable().optional(),
company: z.string().nullable().optional(),
registerTime: z.string().nullable().optional(), // 页面是 "2016-08-27"(日期非 ISO),放宽
avatarUrl: z.string().optional(),
buyerTags: z.string().optional(),
buyerFeatures: z.string().optional(),
recentInquiryProducts: z.array(z.string()).optional(),
storeInquiryProduct: z.string().nullable().optional(),
storeInquiryQty: z.string().nullable().optional(),
storeVisitProducts: z.array(z.object({ img: z.string().optional(), url: z.string().optional(), productId: z.string().optional() })).optional(),
behavior: BehaviorSchema,
})
.nullable(),
messages: z.array(MessageSchema).min(0), // 阶段1:允许空(先走通列表+客户+行为,聊天后续补充)
});
export const ingestRouter = Router();
ingestRouter.post('/', ingestLimiter, ingestAuth, async (req, res, next) => {
const parsed = PayloadSchema.safeParse(req.body);
if (!parsed.success) {
// format() 显示完整 path(如 messages.0.sendTime),便于定位 null 字段
console.error('[ingest 400]', JSON.stringify(parsed.error.format()));
return res
.status(400)
.json({ error: 'invalid payload', issues: parsed.error.flatten() });
}
try {
const d = parsed.data;
console.log(`[ingest in] inquiry=${d.inquiry?.inquiryId} customer=${d.customer?.customerId ?? '-'} messages=${d.messages?.length ?? 0}`);
const result = await ingest(d);
console.log(`[ingest ok] inquiry=${d.inquiry?.inquiryId} -> ${JSON.stringify(result)}`);
res.json(result);
} catch (e) {
console.error('[ingest err] inquiry=', parsed.data.inquiry?.inquiryId, e);
next(e);
}
});