import { createRequestError } from './request-client.js' import { normalizeNormalDisableResponseStatus, normalizeOptionalNonnegativeInteger, normalizeOptionalNumericId, normalizeResponseText } from './response-normalizers.js' import { assertPlainPayload, normalizeOptionalNormalDisableStatus, normalizeOptionalOssIdList, normalizeOptionalText } from './request-normalizers.js' import { normalizeBusinessFileAccessRows, normalizeResourceCapabilities } from './business-file-contract.js' export const normalizeFeedResourceId = (value, label) => { if (typeof value === 'string' && /^[1-9]\d*$/.test(value)) return value if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) return String(value) throw createRequestError(`家族动态评论${label}无效`, 'FEED_COMMENT_RESPONSE_INVALID') } const normalizeFeedCommentText = (value, label, { required = false } = {}) => { if (value === undefined || value === null) { if (required) throw createRequestError(`家族动态评论缺少${label}`, 'FEED_COMMENT_RESPONSE_INVALID') return '' } if (typeof value !== 'string') { throw createRequestError(`家族动态评论${label}无效`, 'FEED_COMMENT_RESPONSE_INVALID') } const normalized = value.trim() if (required && !normalized) { throw createRequestError(`家族动态评论缺少${label}`, 'FEED_COMMENT_RESPONSE_INVALID') } return normalized } export const FEED_COMMENT_LEVEL = Object.freeze({ ROOT: 'root', REPLY: 'reply' }) const feedCommentLevels = new Set(Object.values(FEED_COMMENT_LEVEL)) const normalizeFeedParentCommentId = (value) => { if (value === undefined || value === null || value === 0) return null return normalizeFeedResourceId(value, '父评论标识') } export const normalizeFeedComments = ( value, expectedGenealogyId, expectedFeedId, expectedLevel = '' ) => { if (expectedLevel && !feedCommentLevels.has(expectedLevel)) { throw new TypeError('预期评论层级无效') } if (!Array.isArray(value)) { throw createRequestError('家族动态评论响应不是列表', 'FEED_COMMENT_RESPONSE_INVALID') } const comments = value.map((item) => { if (!item || typeof item !== 'object' || Array.isArray(item)) { throw createRequestError('家族动态评论包含无效条目', 'FEED_COMMENT_RESPONSE_INVALID') } const genealogyId = normalizeFeedResourceId(item.genealogyId, '家谱标识') const feedId = normalizeFeedResourceId(item.feedId, '动态标识') if (genealogyId !== expectedGenealogyId || feedId !== expectedFeedId) { throw createRequestError('家族动态评论归属与请求不匹配', 'FEED_COMMENT_RESPONSE_INVALID') } const level = normalizeFeedCommentText(item.commentLevel, '评论层级', { required: true }) if (!feedCommentLevels.has(level) || (expectedLevel && level !== expectedLevel)) { throw createRequestError('家族动态评论层级无效', 'FEED_COMMENT_RESPONSE_INVALID') } const parentCommentId = normalizeFeedParentCommentId(item.parentCommentId) if ( (level === FEED_COMMENT_LEVEL.ROOT && parentCommentId !== null) || (level === FEED_COMMENT_LEVEL.REPLY && parentCommentId === null) ) { throw createRequestError('家族动态评论父级与层级不匹配', 'FEED_COMMENT_RESPONSE_INVALID') } const deletedStatus = item.userDeleted === undefined || item.userDeleted === null ? '0' : item.userDeleted if (deletedStatus !== '0' && deletedStatus !== '1') { throw createRequestError('家族动态评论删除状态无效', 'FEED_COMMENT_RESPONSE_INVALID') } const userDeleted = deletedStatus === '1' if (item.canDelete !== undefined && typeof item.canDelete !== 'boolean') { throw createRequestError('家族动态评论删除权限无效', 'FEED_COMMENT_RESPONSE_INVALID') } return { id: normalizeFeedResourceId(item.commentId, '标识'), author: normalizeFeedCommentText(item.appUserNickName, '用户昵称') || '未署名成员', content: userDeleted ? '该评论已删除' : normalizeFeedCommentText(item.commentContent, '评论内容', { required: true }), time: normalizeFeedCommentText(item.createTime, '创建时间'), parentCommentId, parentAuthor: normalizeFeedCommentText(item.parentAppUserNickName, '被回复用户昵称'), level, status: normalizeNormalDisableResponseStatus( item.status, '评论状态', 'FEED_COMMENT_RESPONSE_INVALID' ), userDeleted, canDelete: item.canDelete === true, replyCount: normalizeOptionalNonnegativeInteger( item.replyCount, '评论回复数', 'FEED_COMMENT_RESPONSE_INVALID' ) } }) if (new Set(comments.map((item) => item.id)).size !== comments.length) { throw createRequestError('家族动态评论包含重复标识', 'FEED_COMMENT_RESPONSE_INVALID') } return comments } export const normalizeDirectFeedReplies = (value, expectedGenealogyId, expectedFeedId, expectedParentCommentId) => { const replies = normalizeFeedComments( value, expectedGenealogyId, expectedFeedId, FEED_COMMENT_LEVEL.REPLY ) if (replies.some((item) => item.parentCommentId !== expectedParentCommentId)) { throw createRequestError('评论回复父级与请求不匹配', 'FEED_COMMENT_RESPONSE_INVALID') } return replies } export const normalizeFeedCommentPayload = (payload) => { assertPlainPayload(payload, new Set(['parentCommentId', 'commentContent']), '家族动态评论请求') const commentContent = normalizeFeedCommentText(payload.commentContent, '评论内容', { required: true }) if (Array.from(commentContent).length > 1000) throw new TypeError('家族动态评论不能超过 1000 个字符') const normalizedPayload = { commentContent } if (payload.parentCommentId !== undefined && payload.parentCommentId !== null) { normalizedPayload.parentCommentId = normalizeFeedResourceId(payload.parentCommentId, '父评论标识') } return normalizedPayload } export const normalizeFeedCreatePayload = (payload) => { const allowedFields = new Set(['feedType', 'feedContent', 'mediaOssIds', 'sortOrder', 'status']) assertPlainPayload(payload, allowedFields, '家族动态请求') if (typeof payload.feedContent !== 'string' || !payload.feedContent.trim()) { throw new TypeError('家族动态内容不能为空') } const normalizedPayload = { feedContent: payload.feedContent.trim() } const feedType = normalizeOptionalText(payload.feedType, 'feedType') if (feedType) normalizedPayload.feedType = feedType const status = normalizeOptionalNormalDisableStatus(payload.status, '动态状态') if (status) normalizedPayload.status = status const mediaOssIds = normalizeOptionalOssIdList(payload.mediaOssIds) if (mediaOssIds) normalizedPayload.mediaOssIds = mediaOssIds if (payload.sortOrder !== undefined && payload.sortOrder !== null && payload.sortOrder !== '') { const sortOrder = typeof payload.sortOrder === 'number' ? payload.sortOrder : Number(payload.sortOrder) if (!Number.isSafeInteger(sortOrder)) throw new TypeError('sortOrder 必须是安全整数') normalizedPayload.sortOrder = sortOrder } return normalizedPayload } export const normalizeFamilyFeedRows = (value, expectedGenealogyId) => { if (!Array.isArray(value)) { throw createRequestError('家族动态响应不是列表', 'FEED_RESPONSE_INVALID') } const rows = value.map((item) => { if (!item || typeof item !== 'object' || Array.isArray(item)) { throw createRequestError('家族动态响应包含无效条目', 'FEED_RESPONSE_INVALID') } const genealogyId = normalizeOptionalNumericId(item.genealogyId, '动态家谱标识', 'FEED_RESPONSE_INVALID') if (genealogyId && genealogyId !== expectedGenealogyId) { throw createRequestError('家族动态归属与请求不匹配', 'FEED_RESPONSE_INVALID') } const id = normalizeOptionalNumericId(item.feedId, '动态标识', 'FEED_RESPONSE_INVALID') const content = normalizeResponseText(item.feedContent, 'feedContent') if (!id || !content) { throw createRequestError('家族动态响应缺少标识或内容', 'FEED_RESPONSE_INVALID') } if ( item.likedByMe !== undefined && item.likedByMe !== null && typeof item.likedByMe !== 'boolean' ) { throw createRequestError('动态点赞状态无效', 'FEED_RESPONSE_INVALID') } const pinned = normalizeResponseText(item.pinned, 'pinned') if (pinned && pinned !== '0' && pinned !== '1') { throw createRequestError('动态置顶状态无效', 'FEED_RESPONSE_INVALID') } return { id, genealogyId: expectedGenealogyId, content, publisher: normalizeResponseText(item.publisherNickName, 'publisherNickName') || '家族成员', type: normalizeResponseText(item.feedType, 'feedType') || '文字动态', time: normalizeResponseText(item.createTime, 'createTime'), mediaFiles: normalizeBusinessFileAccessRows(item.mediaFiles, '动态媒体', 'FEED_RESPONSE_INVALID'), likeCount: normalizeOptionalNonnegativeInteger(item.likeCount, '动态点赞数', 'FEED_RESPONSE_INVALID'), commentCount: normalizeOptionalNonnegativeInteger(item.commentCount, '动态评论数', 'FEED_RESPONSE_INVALID'), likedByMe: item.likedByMe ?? null, pinned, sortOrder: normalizeOptionalNonnegativeInteger(item.sortOrder, '动态排序值', 'FEED_RESPONSE_INVALID'), status: normalizeNormalDisableResponseStatus(item.status, '动态状态', 'FEED_RESPONSE_INVALID'), ...normalizeResourceCapabilities(item, '家族动态', 'FEED_RESPONSE_INVALID') } }) if (new Set(rows.map((item) => item.id)).size !== rows.length) { throw createRequestError('家族动态响应包含重复标识', 'FEED_RESPONSE_INVALID') } return rows } export const normalizeFamilyFeed = (value, expectedGenealogyId, expectedFeedId) => { const [feed] = normalizeFamilyFeedRows([value], expectedGenealogyId) if (feed.id !== expectedFeedId) { throw createRequestError('动态详情响应标识不匹配', 'FEED_RESPONSE_INVALID') } return feed } export const normalizeFamilyFeedRecommendations = (value, expectedGenealogyId) => { if (!Array.isArray(value)) { throw createRequestError('动态推荐响应不是列表', 'FEED_RECOMMENDATION_RESPONSE_INVALID') } const reasonCodes = new Set([ 'MANUAL_PRIORITY', 'FOLLOWED_INTERACTION', 'POPULAR_IN_GENEALOGY', 'RECENT_IN_GENEALOGY' ]) const feeds = value.map((item) => { if (!item || typeof item !== 'object' || Array.isArray(item) || !reasonCodes.has(item.reasonCode)) { throw createRequestError('动态推荐响应包含无效条目', 'FEED_RECOMMENDATION_RESPONSE_INVALID') } if (typeof item.reasonText !== 'string' || !item.reasonText.trim()) { throw createRequestError('动态推荐响应缺少说明', 'FEED_RECOMMENDATION_RESPONSE_INVALID') } const [feed] = normalizeFamilyFeedRows([item.feed], expectedGenealogyId) return { ...feed, recommendationReason: item.reasonText.trim() } }) if (new Set(feeds.map((item) => item.id)).size !== feeds.length) { throw createRequestError('动态推荐响应包含重复动态', 'FEED_RECOMMENDATION_RESPONSE_INVALID') } return feeds }