mirror of
https://github.com/sinanyuntu/trade-message-center.git
synced 2026-09-17 13:22:11 +08:00
208 lines
7.7 KiB
TypeScript
208 lines
7.7 KiB
TypeScript
// 投影已验证的 OneTalk 中心读取响应
|
|
|
|
import {
|
|
isOneTalkCenterMessageContent,
|
|
isOneTalkMessageContent,
|
|
isOneTalkRenderedCardContent,
|
|
type OneTalkHttpConversation,
|
|
type OneTalkCenterMessage,
|
|
type OneTalkMessage,
|
|
type OneTalkCenterMessageContent,
|
|
type OneTalkRenderedCardContent,
|
|
} from "@trade-message-center/onetalk-contract";
|
|
|
|
import type {
|
|
CenterConversation,
|
|
OneTalkReadConversation,
|
|
OneTalkReadConversationRow,
|
|
OneTalkReadCustomerProfile,
|
|
OneTalkReadMessageRow,
|
|
} from "./read-model.ts";
|
|
|
|
/** 归一化 repository 会话行供传输边界继续投影。 */
|
|
export const projectReadConversation = (
|
|
row: OneTalkReadConversationRow,
|
|
): OneTalkReadConversation => {
|
|
return {
|
|
conversationId: row.conversationId,
|
|
conversationType: "direct",
|
|
name: row.name,
|
|
avatarUrl: row.avatarUrl,
|
|
customerProfile: {
|
|
name: row.name,
|
|
avatarUrl: row.avatarUrl,
|
|
buyerTags:
|
|
row.buyerTags === null || row.buyerTags === undefined ? null : [...row.buyerTags],
|
|
buyerFeatures:
|
|
row.buyerFeatures === null || row.buyerFeatures === undefined
|
|
? null
|
|
: [...row.buyerFeatures],
|
|
email: row.email ?? null,
|
|
registrationDate: row.registrationDate ?? null,
|
|
companyWebsite: row.companyWebsite ?? null,
|
|
countryCode: row.countryCode ?? null,
|
|
companyName: row.companyName ?? null,
|
|
},
|
|
lastContactTimeLong: row.lastContactTimeMs,
|
|
messagePreview: row.messagePreview,
|
|
};
|
|
};
|
|
|
|
/** 保持 WebSocket 公开会话契约不含 HTTP 客户资料包装。 */
|
|
export const toCenterConversation = (conversation: OneTalkReadConversation): CenterConversation => {
|
|
return {
|
|
conversationId: conversation.conversationId,
|
|
conversationType: conversation.conversationType,
|
|
name: conversation.name,
|
|
avatarUrl: conversation.avatarUrl,
|
|
lastContactTimeLong: conversation.lastContactTimeLong,
|
|
messagePreview: conversation.messagePreview,
|
|
};
|
|
};
|
|
|
|
/** 投影 HTTP 会话,不暴露扁平客户字段或同步内部字段。 */
|
|
export const toHttpConversation = (
|
|
conversation: OneTalkReadConversation,
|
|
): OneTalkHttpConversation => {
|
|
const customerProfile = conversation.customerProfile;
|
|
if (!customerProfile) throw new Error("Missing HTTP customer profile");
|
|
return {
|
|
conversationId: conversation.conversationId,
|
|
conversationType: conversation.conversationType,
|
|
customerProfile: {
|
|
name: customerProfile.name,
|
|
avatarUrl: customerProfile.avatarUrl,
|
|
buyer_tags: customerProfile.buyerTags === null ? null : [...customerProfile.buyerTags],
|
|
buyer_features:
|
|
customerProfile.buyerFeatures === null ? null : [...customerProfile.buyerFeatures],
|
|
email: customerProfile.email,
|
|
registration_date: customerProfile.registrationDate,
|
|
company_website: customerProfile.companyWebsite,
|
|
country_code: customerProfile.countryCode,
|
|
company_name: customerProfile.companyName,
|
|
},
|
|
lastContactTimeLong: conversation.lastContactTimeLong,
|
|
messagePreview: conversation.messagePreview,
|
|
};
|
|
};
|
|
|
|
type OneTalkCenterMessageInput = Pick<
|
|
OneTalkMessage,
|
|
| "messageId"
|
|
| "conversationId"
|
|
| "senderId"
|
|
| "participantIds"
|
|
| "direction"
|
|
| "sentAtMs"
|
|
| "content"
|
|
>;
|
|
|
|
const supplementMessageContent = (
|
|
content: OneTalkMessage["content"],
|
|
renderedCardContent: OneTalkRenderedCardContent | undefined,
|
|
): OneTalkCenterMessageContent => {
|
|
if (!isOneTalkMessageContent(content)) {
|
|
throw new Error("Invalid persisted OneTalk message content");
|
|
}
|
|
if (renderedCardContent === undefined) return content;
|
|
if (!isOneTalkRenderedCardContent(renderedCardContent)) {
|
|
throw new Error("Invalid persisted OneTalk rendered-card supplement");
|
|
}
|
|
if (renderedCardContent.kind === "rendered_inquiry") {
|
|
if (content.kind !== "inquiry") {
|
|
throw new Error("OneTalk rendered-card supplement kind does not match base message");
|
|
}
|
|
return {
|
|
...content,
|
|
product: renderedCardContent.product,
|
|
purchaseQuantity: renderedCardContent.purchaseQuantity,
|
|
requirementText: renderedCardContent.requirementText,
|
|
inquiryReference: renderedCardContent.inquiryReference,
|
|
actions: renderedCardContent.actions,
|
|
};
|
|
}
|
|
if (renderedCardContent.kind === "rendered_product") {
|
|
if (content.kind !== "product") {
|
|
throw new Error("OneTalk rendered-card supplement kind does not match base message");
|
|
}
|
|
if (
|
|
content.sourceUrl !== renderedCardContent.product.sourceUrl ||
|
|
content.productId !== renderedCardContent.product.productId
|
|
) {
|
|
throw new Error(
|
|
"OneTalk rendered-product supplement identity does not match base message",
|
|
);
|
|
}
|
|
return {
|
|
...content,
|
|
title: renderedCardContent.product.title,
|
|
imageUrl: renderedCardContent.product.imageUrl,
|
|
priceDisplay: renderedCardContent.priceDisplay,
|
|
minimumOrder: renderedCardContent.minimumOrder,
|
|
serviceBadges: renderedCardContent.serviceBadges,
|
|
};
|
|
}
|
|
if (content.kind !== "order") {
|
|
throw new Error("OneTalk rendered-card supplement kind does not match base message");
|
|
}
|
|
return {
|
|
...content,
|
|
title: renderedCardContent.title,
|
|
products: renderedCardContent.products,
|
|
productCount: renderedCardContent.productCount,
|
|
status: renderedCardContent.status,
|
|
payment: renderedCardContent.payment,
|
|
delivery: renderedCardContent.delivery,
|
|
action: renderedCardContent.action,
|
|
};
|
|
};
|
|
|
|
const projectMessageContent = (
|
|
content: OneTalkCenterMessageContent,
|
|
customerProfile?: Pick<
|
|
OneTalkReadCustomerProfile,
|
|
"name" | "avatarUrl" | "countryCode" | "companyName"
|
|
>,
|
|
): OneTalkCenterMessage["content"] => {
|
|
if (content.kind !== "business_card" || customerProfile === undefined) return { ...content };
|
|
return {
|
|
...content,
|
|
contactName: customerProfile?.name ?? null,
|
|
companyName: customerProfile?.companyName ?? null,
|
|
countryCode: customerProfile?.countryCode ?? null,
|
|
avatarUrl: customerProfile?.avatarUrl ?? null,
|
|
};
|
|
};
|
|
|
|
/** 从 normalized JSONB 事实和会话客户资料投影 Mind-facing 消息。 */
|
|
export const toOneTalkCenterMessage = (
|
|
message: OneTalkCenterMessageInput,
|
|
renderedCardContent?: OneTalkRenderedCardContent,
|
|
customerProfile?: Pick<
|
|
OneTalkReadCustomerProfile,
|
|
"name" | "avatarUrl" | "countryCode" | "companyName"
|
|
>,
|
|
): OneTalkCenterMessage => {
|
|
const content = supplementMessageContent(message.content, renderedCardContent);
|
|
if (!isOneTalkCenterMessageContent(content)) {
|
|
throw new Error("Invalid persisted OneTalk message content");
|
|
}
|
|
return {
|
|
messageId: message.messageId,
|
|
conversationId: message.conversationId,
|
|
senderId: message.senderId,
|
|
participantIds: [...message.participantIds],
|
|
direction: message.direction,
|
|
sentAtMs: message.sentAtMs,
|
|
content: projectMessageContent(content, customerProfile),
|
|
};
|
|
};
|
|
|
|
export const projectCenterMessage = (
|
|
row: OneTalkReadMessageRow,
|
|
customerProfile?: Pick<
|
|
OneTalkReadCustomerProfile,
|
|
"name" | "avatarUrl" | "countryCode" | "companyName"
|
|
>,
|
|
): OneTalkCenterMessage => toOneTalkCenterMessage(row, row.renderedCardContent, customerProfile);
|