Files
jiapuapp/services/api/family-article-contract.js
2026-09-13 17:45:52 +08:00

132 lines
6.3 KiB
JavaScript

import { createRequestError } from './request-client.js'
import {
normalizeNormalDisableResponseStatus,
normalizeOptionalNonnegativeInteger,
normalizeOptionalNumericId,
normalizeResponseText
} from './response-normalizers.js'
import {
assertPlainPayload,
normalizeOptionalNormalDisableStatus,
normalizeOptionalOssIdList,
normalizeOptionalSafeInteger,
normalizeOptionalText,
normalizeOssIdString,
normalizeResourcePathId
} from './request-normalizers.js'
import {
normalizeBusinessFileAccess,
normalizeBusinessFileAccessRows,
normalizeContentProtectionCapabilities,
normalizeResourceCapabilities
} from './business-file-contract.js'
export const normalizeArticleCategoryOptions = (value) => {
if (!Array.isArray(value)) {
throw createRequestError('谱文分类响应不是列表', 'ARTICLE_CATEGORY_RESPONSE_INVALID')
}
const categories = value.map((item) => {
if (!item || typeof item !== 'object' || Array.isArray(item)) {
throw createRequestError('谱文分类响应包含无效条目', 'ARTICLE_CATEGORY_RESPONSE_INVALID')
}
const id = normalizeOptionalNumericId(item.categoryId, '谱文分类标识', 'ARTICLE_CATEGORY_RESPONSE_INVALID')
const name = normalizeResponseText(item.categoryName, 'categoryName')
if (!id || !name || typeof item.enabled !== 'boolean') {
throw createRequestError('谱文分类响应缺少稳定字段', 'ARTICLE_CATEGORY_RESPONSE_INVALID')
}
return {
id,
name,
enabled: item.enabled,
sortOrder: normalizeOptionalNonnegativeInteger(item.sortOrder, '谱文分类排序值', 'ARTICLE_CATEGORY_RESPONSE_INVALID')
}
})
if (new Set(categories.map((item) => item.id)).size !== categories.length) {
throw createRequestError('谱文分类响应包含重复标识', 'ARTICLE_CATEGORY_RESPONSE_INVALID')
}
return categories
}
export const normalizeAppArticle = (value, expectedGenealogyId, expectedArticleId = '') => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw createRequestError('谱文响应无效', 'ARTICLE_RESPONSE_INVALID')
}
const id = normalizeOptionalNumericId(value.articleId, '谱文标识', 'ARTICLE_RESPONSE_INVALID')
const genealogyId = normalizeOptionalNumericId(value.genealogyId, '谱文家谱标识', 'ARTICLE_RESPONSE_INVALID')
const title = normalizeResponseText(value.articleTitle, 'articleTitle')
if (!id || genealogyId !== expectedGenealogyId || !title || (expectedArticleId && id !== expectedArticleId)) {
throw createRequestError('谱文响应缺少稳定归属字段', 'ARTICLE_RESPONSE_INVALID')
}
return {
id,
genealogyId,
title,
summary: normalizeResponseText(value.articleSummary, 'articleSummary'),
content: normalizeResponseText(value.articleContent, 'articleContent'),
authorName: normalizeResponseText(value.authorName, 'authorName'),
author: normalizeResponseText(value.authorName, 'authorName') || '家族成员',
time: normalizeResponseText(value.publishTime, 'publishTime'),
categoryId: normalizeOptionalNumericId(value.categoryId, '谱文分类标识', 'ARTICLE_RESPONSE_INVALID'),
category: normalizeResponseText(value.categoryName, 'categoryName'),
coverFile: normalizeBusinessFileAccess(value.coverFile, '谱文封面', 'ARTICLE_RESPONSE_INVALID'),
mediaFiles: normalizeBusinessFileAccessRows(value.mediaFiles, '谱文正文图片', 'ARTICLE_RESPONSE_INVALID'),
viewCount: normalizeOptionalNonnegativeInteger(value.viewCount, '谱文阅读数', 'ARTICLE_RESPONSE_INVALID'),
sortOrder: normalizeOptionalNonnegativeInteger(value.sortOrder, '谱文排序值', 'ARTICLE_RESPONSE_INVALID'),
status: normalizeNormalDisableResponseStatus(value.status, '谱文状态', 'ARTICLE_RESPONSE_INVALID'),
...normalizeContentProtectionCapabilities(value, '谱文', 'ARTICLE_RESPONSE_INVALID'),
...normalizeResourceCapabilities(value, '谱文', 'ARTICLE_RESPONSE_INVALID')
}
}
export const normalizeAppArticles = (value, expectedGenealogyId) => {
if (!Array.isArray(value)) throw createRequestError('谱文响应不是列表', 'ARTICLE_RESPONSE_INVALID')
const articles = value.map((item) => normalizeAppArticle(item, expectedGenealogyId))
if (new Set(articles.map((item) => item.id)).size !== articles.length) {
throw createRequestError('谱文响应包含重复标识', 'ARTICLE_RESPONSE_INVALID')
}
return articles
}
export const normalizeArticleCreatePayload = (payload) => {
const allowedFields = new Set([
'categoryId',
'articleTitle',
'articleSummary',
'coverOssId',
'mediaOssIds',
'articleContent',
'authorName',
'sortOrder',
'status'
])
assertPlainPayload(payload, allowedFields, '谱文请求')
const articleTitle = typeof payload.articleTitle === 'string' ? payload.articleTitle.trim() : ''
const articleContent = typeof payload.articleContent === 'string' ? payload.articleContent.trim() : ''
if (!articleTitle) throw new TypeError('谱文标题不能为空')
if (!articleContent) throw new TypeError('谱文正文不能为空')
const normalizedPayload = { articleTitle, articleContent }
for (const field of ['articleSummary', 'authorName']) {
const normalizedText = normalizeOptionalText(payload[field], field)
if (normalizedText) normalizedPayload[field] = normalizedText
}
const status = normalizeOptionalNormalDisableStatus(payload.status, '谱文状态')
if (status) normalizedPayload.status = status
if (payload.categoryId !== undefined && payload.categoryId !== null && payload.categoryId !== '') {
normalizedPayload.categoryId = normalizeResourcePathId(payload.categoryId, '谱文分类标识')
}
const sortOrder = normalizeOptionalSafeInteger(payload.sortOrder, 'sortOrder')
if (sortOrder !== undefined) normalizedPayload.sortOrder = sortOrder
if (Object.prototype.hasOwnProperty.call(payload, 'coverOssId')) {
if (payload.coverOssId === null) normalizedPayload.coverOssId = null
else if (payload.coverOssId !== '') normalizedPayload.coverOssId = normalizeOssIdString(payload.coverOssId, 'coverOssId')
}
if (Object.prototype.hasOwnProperty.call(payload, 'mediaOssIds')) {
const mediaOssIds = normalizeOptionalOssIdList(payload.mediaOssIds)
if (mediaOssIds && mediaOssIds.length > 1000) {
throw new TypeError('谱文正文图片标识总长度不能超过1000个字符')
}
normalizedPayload.mediaOssIds = mediaOssIds || ''
}
return normalizedPayload
}