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); } });