feat: migrate app routes and business modules
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { createRequestError } from './request-client.js'
|
||||
import {
|
||||
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'])
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user