189 lines
7.7 KiB
JavaScript
189 lines
7.7 KiB
JavaScript
import { createRequestError } from './request-client.js'
|
|
import {
|
|
normalizeOptionalNonnegativeInteger,
|
|
normalizeOptionalNumericId,
|
|
normalizeResponseText
|
|
} from './response-normalizers.js'
|
|
import { normalizeBusinessFileAccess } from './business-file-contract.js'
|
|
|
|
const promotionPlacements = new Set(['home_banner', 'home_bottom', 'message_bottom', 'profile_bottom'])
|
|
const siteArticleTypeLabels = Object.freeze({
|
|
news: '资讯',
|
|
notice: '公告'
|
|
})
|
|
|
|
export const normalizePromotionPlacement = (value) => {
|
|
if (!promotionPlacements.has(value)) throw new TypeError('推广位无效')
|
|
return value
|
|
}
|
|
|
|
export const normalizeAppPromotions = (value, expectedPlacement) => {
|
|
if (!Array.isArray(value)) throw createRequestError('应用推广响应不是列表', 'PROMOTION_RESPONSE_INVALID')
|
|
const promotions = value.map((item) => {
|
|
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
throw createRequestError('应用推广响应包含无效条目', 'PROMOTION_RESPONSE_INVALID')
|
|
}
|
|
const id = normalizeOptionalNumericId(item.promotionId, '推广标识', 'PROMOTION_RESPONSE_INVALID')
|
|
const title = normalizeResponseText(item.promotionTitle, 'promotionTitle')
|
|
if (!id || !title) {
|
|
throw createRequestError('应用推广响应缺少稳定字段', 'PROMOTION_RESPONSE_INVALID')
|
|
}
|
|
const platform = normalizeResponseText(item.platform, 'platform')
|
|
const placement = normalizeResponseText(item.placement, 'placement')
|
|
if ((platform && platform !== 'app' && platform !== 'all') || placement !== expectedPlacement) {
|
|
throw createRequestError('应用推广响应位置与请求不匹配', 'PROMOTION_RESPONSE_INVALID')
|
|
}
|
|
const targetUrl = normalizeResponseText(item.targetUrl, 'targetUrl')
|
|
if (
|
|
targetUrl &&
|
|
!(/^\/(?!\/)[^\s]*$/.test(targetUrl) || /^https:\/\/[^\s/?#]+(?:[/?#][^\s]*)?$/.test(targetUrl))
|
|
) {
|
|
throw createRequestError('应用推广跳转地址无效', 'PROMOTION_RESPONSE_INVALID')
|
|
}
|
|
return {
|
|
id,
|
|
title,
|
|
description: normalizeResponseText(item.promotionDesc, 'promotionDesc'),
|
|
coverFile: normalizeBusinessFileAccess(item.coverFile, '推广封面', 'PROMOTION_RESPONSE_INVALID'),
|
|
targetUrl,
|
|
placement
|
|
}
|
|
})
|
|
if (new Set(promotions.map((item) => item.id)).size !== promotions.length) {
|
|
throw createRequestError('应用推广响应包含重复标识', 'PROMOTION_RESPONSE_INVALID')
|
|
}
|
|
return promotions
|
|
}
|
|
|
|
export const normalizeSiteArticles = (value) => {
|
|
if (!Array.isArray(value)) {
|
|
throw createRequestError('官网文章响应不是列表', 'SITE_ARTICLE_RESPONSE_INVALID')
|
|
}
|
|
const articles = value.map((item) => {
|
|
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
throw createRequestError('官网文章响应包含无效条目', 'SITE_ARTICLE_RESPONSE_INVALID')
|
|
}
|
|
const id = normalizeOptionalNumericId(item.articleId, '官网文章标识', 'SITE_ARTICLE_RESPONSE_INVALID')
|
|
const title = normalizeResponseText(item.articleTitle, 'articleTitle', {
|
|
code: 'SITE_ARTICLE_RESPONSE_INVALID',
|
|
subject: '官网文章响应'
|
|
})
|
|
if (!id || !title) {
|
|
throw createRequestError('官网文章响应缺少稳定字段', 'SITE_ARTICLE_RESPONSE_INVALID')
|
|
}
|
|
const externalUrl = normalizeResponseText(item.externalUrl, 'externalUrl', {
|
|
code: 'SITE_ARTICLE_RESPONSE_INVALID',
|
|
subject: '官网文章响应'
|
|
})
|
|
if (externalUrl && !/^https:\/\/[^\s/?#]+(?:[/?#][^\s]*)?$/.test(externalUrl)) {
|
|
throw createRequestError('官网文章外部链接无效', 'SITE_ARTICLE_RESPONSE_INVALID')
|
|
}
|
|
const type = normalizeResponseText(item.articleType, 'articleType', {
|
|
code: 'SITE_ARTICLE_RESPONSE_INVALID',
|
|
subject: '官网文章响应'
|
|
})
|
|
return {
|
|
id,
|
|
title,
|
|
type,
|
|
typeLabel: siteArticleTypeLabels[type] || '资讯',
|
|
summary: normalizeResponseText(item.articleSummary, 'articleSummary', {
|
|
code: 'SITE_ARTICLE_RESPONSE_INVALID',
|
|
subject: '官网文章响应'
|
|
}),
|
|
content: normalizeResponseText(item.articleContent, 'articleContent', {
|
|
code: 'SITE_ARTICLE_RESPONSE_INVALID',
|
|
subject: '官网文章响应'
|
|
}),
|
|
publishTime: normalizeResponseText(item.publishTime, 'publishTime', {
|
|
code: 'SITE_ARTICLE_RESPONSE_INVALID',
|
|
subject: '官网文章响应'
|
|
}),
|
|
externalUrl,
|
|
sortOrder: normalizeOptionalNonnegativeInteger(
|
|
item.sortOrder,
|
|
'官网文章排序值',
|
|
'SITE_ARTICLE_RESPONSE_INVALID'
|
|
)
|
|
}
|
|
})
|
|
if (new Set(articles.map((article) => article.id)).size !== articles.length) {
|
|
throw createRequestError('官网文章响应包含重复标识', 'SITE_ARTICLE_RESPONSE_INVALID')
|
|
}
|
|
return articles
|
|
}
|
|
|
|
const normalizeHelpArticleText = (value, field) => {
|
|
if (typeof value !== 'string' || !value.trim()) {
|
|
throw createRequestError(`帮助文章缺少 ${field}`, 'HELP_ARTICLE_RESPONSE_INVALID')
|
|
}
|
|
return value.trim()
|
|
}
|
|
|
|
const normalizeOptionalHelpArticleText = (value, field) => {
|
|
if (value === undefined || value === null) return ''
|
|
return normalizeHelpArticleText(value, field)
|
|
}
|
|
|
|
const helpArticleCategories = new Set(['common', 'member', 'lineage'])
|
|
|
|
export const normalizeHelpArticles = (value) => {
|
|
if (!Array.isArray(value)) throw createRequestError('帮助文章响应不是列表', 'HELP_ARTICLE_RESPONSE_INVALID')
|
|
const articles = value.map((item) => {
|
|
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
throw createRequestError('帮助文章响应包含无效条目', 'HELP_ARTICLE_RESPONSE_INVALID')
|
|
}
|
|
const id = normalizeOptionalNumericId(item.helpId, '帮助文章标识', 'HELP_ARTICLE_RESPONSE_INVALID')
|
|
const category = normalizeHelpArticleText(item.helpCategory, 'helpCategory')
|
|
if (!id || !helpArticleCategories.has(category)) {
|
|
throw createRequestError('帮助文章标识或分类无效', 'HELP_ARTICLE_RESPONSE_INVALID')
|
|
}
|
|
return {
|
|
key: `help-${id}`,
|
|
category,
|
|
categoryLabel: normalizeOptionalHelpArticleText(item.helpCategoryLabel, 'helpCategoryLabel') || category,
|
|
title: normalizeHelpArticleText(item.helpTitle, 'helpTitle'),
|
|
content: normalizeHelpArticleText(item.helpContent, 'helpContent')
|
|
}
|
|
})
|
|
if (new Set(articles.map((article) => article.key)).size !== articles.length) {
|
|
throw createRequestError('帮助文章响应包含重复标识', 'HELP_ARTICLE_RESPONSE_INVALID')
|
|
}
|
|
return articles
|
|
}
|
|
|
|
export const COMPLIANCE_DOCUMENT_KEY = Object.freeze({
|
|
USER_AGREEMENT: 'user_agreement',
|
|
PRIVACY_POLICY: 'privacy_policy'
|
|
})
|
|
|
|
const complianceDocumentKeys = new Set(Object.values(COMPLIANCE_DOCUMENT_KEY))
|
|
|
|
export const normalizeComplianceDocumentKey = (value) => {
|
|
if (typeof value !== 'string' || !complianceDocumentKeys.has(value)) {
|
|
throw new TypeError('合规文档类型无效')
|
|
}
|
|
return value
|
|
}
|
|
|
|
export const normalizeComplianceDocument = (value, expectedKey) => {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
throw createRequestError('协议正文响应无效', 'COMPLIANCE_DOCUMENT_RESPONSE_INVALID')
|
|
}
|
|
const key = normalizeResponseText(value.documentKey, 'documentKey')
|
|
const title = normalizeResponseText(value.documentTitle, 'documentTitle')
|
|
const versionNo = normalizeResponseText(value.versionNo, 'versionNo')
|
|
const content = normalizeResponseText(value.documentContent, 'documentContent')
|
|
if (key !== expectedKey || !title || !versionNo || !content) {
|
|
throw createRequestError('协议正文响应缺少稳定字段', 'COMPLIANCE_DOCUMENT_RESPONSE_INVALID')
|
|
}
|
|
return {
|
|
key,
|
|
title,
|
|
versionNo,
|
|
content,
|
|
effectiveAt: normalizeResponseText(value.effectiveAt, 'effectiveAt'),
|
|
publishedAt: normalizeResponseText(value.publishedAt, 'publishedAt')
|
|
}
|
|
}
|