253 lines
11 KiB
JavaScript
253 lines
11 KiB
JavaScript
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
|
|
}
|