feat: migrate app routes and business modules
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import { createRequestError } from './request-client.js'
|
||||
import { assertSmsCode } from '@/utils/auth/verification.js'
|
||||
import {
|
||||
assertPlainPayload,
|
||||
normalizeOptionalText,
|
||||
normalizeOssIdString
|
||||
} from './request-normalizers.js'
|
||||
import { normalizeOptionalResponseText } from './response-normalizers.js'
|
||||
import { normalizeBusinessFileAccess } from './business-file-contract.js'
|
||||
|
||||
export const PROFILE_SEX_OPTIONS = Object.freeze([
|
||||
Object.freeze({ value: '0', label: '男' }),
|
||||
Object.freeze({ value: '1', label: '女' }),
|
||||
Object.freeze({ value: '2', label: '未知' })
|
||||
])
|
||||
|
||||
const profileSexValues = new Set(PROFILE_SEX_OPTIONS.map(({ value }) => value))
|
||||
|
||||
const normalizeProfileResponseText = (value, field) => {
|
||||
if (value === undefined || value === null) return ''
|
||||
if (typeof value !== 'string') {
|
||||
throw createRequestError(`个人资料字段 ${field} 无效`, 'PROFILE_RESPONSE_INVALID')
|
||||
}
|
||||
return value.trim()
|
||||
}
|
||||
|
||||
export const normalizePhoneChangePayload = (payload) => {
|
||||
assertPlainPayload(payload, new Set(['phone', 'smsCode']), '换绑手机号请求')
|
||||
if (typeof payload.phone !== 'string' || !/^1\d{10}$/.test(payload.phone.trim())) {
|
||||
throw new TypeError('新手机号格式无效')
|
||||
}
|
||||
return { phone: payload.phone.trim(), smsCode: assertSmsCode(payload.smsCode) }
|
||||
}
|
||||
|
||||
const normalizeProfileUserId = (value) => {
|
||||
if (value === undefined || value === null || value === '') return ''
|
||||
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('个人资料字段 userId 无效', 'PROFILE_RESPONSE_INVALID')
|
||||
}
|
||||
|
||||
export const normalizeAppProfile = (payload) => {
|
||||
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
||||
throw createRequestError('个人资料响应格式无效', 'PROFILE_RESPONSE_INVALID')
|
||||
}
|
||||
return {
|
||||
userId: normalizeProfileUserId(payload.userId),
|
||||
tenantId: normalizeProfileResponseText(payload.tenantId, 'tenantId'),
|
||||
userNo: normalizeProfileResponseText(payload.userNo, 'userNo'),
|
||||
phone: normalizeProfileResponseText(payload.phone, 'phone'),
|
||||
nickName: normalizeProfileResponseText(payload.nickName, 'nickName'),
|
||||
realName: normalizeProfileResponseText(payload.realName, 'realName'),
|
||||
avatarFile: normalizeBusinessFileAccess(
|
||||
payload.avatarFile,
|
||||
'个人头像',
|
||||
'PROFILE_RESPONSE_INVALID'
|
||||
),
|
||||
sex: normalizeProfileResponseText(payload.sex, 'sex'),
|
||||
birthday: normalizeProfileResponseText(payload.birthday, 'birthday'),
|
||||
email: normalizeProfileResponseText(payload.email, 'email'),
|
||||
registerSource: normalizeProfileResponseText(payload.registerSource, 'registerSource'),
|
||||
status: normalizeProfileResponseText(payload.status, 'status')
|
||||
}
|
||||
}
|
||||
|
||||
export const normalizeProfileUpdatePayload = (payload) => {
|
||||
const allowedFields = new Set(['nickName', 'realName', 'avatar', 'sex', 'birthday', 'email'])
|
||||
assertPlainPayload(payload, allowedFields, '个人资料请求')
|
||||
const normalizedPayload = {}
|
||||
for (const [field, limit] of [['nickName', 30], ['realName', 30]]) {
|
||||
if (!Object.prototype.hasOwnProperty.call(payload, field)) continue
|
||||
const normalizedText = normalizeOptionalText(payload[field], field)
|
||||
if (!normalizedText) continue
|
||||
if (normalizedText.length > limit) throw new TypeError(`${field} 超出长度限制`)
|
||||
normalizedPayload[field] = normalizedText
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'sex')) {
|
||||
const sex = normalizeOptionalText(payload.sex, 'sex')
|
||||
if (sex) {
|
||||
if (!profileSexValues.has(sex)) throw new TypeError('sex 必须为 0、1 或 2')
|
||||
normalizedPayload.sex = sex
|
||||
}
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'birthday')) {
|
||||
const birthday = normalizeOptionalText(payload.birthday, 'birthday')
|
||||
if (birthday) {
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(birthday)) {
|
||||
throw new TypeError('birthday 必须是 yyyy-MM-dd 日期')
|
||||
}
|
||||
normalizedPayload.birthday = birthday
|
||||
}
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'email')) {
|
||||
const email = normalizeOptionalText(payload.email, 'email')
|
||||
if (email) {
|
||||
if (email.length > 100 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
throw new TypeError('email 格式无效')
|
||||
}
|
||||
normalizedPayload.email = email
|
||||
}
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'avatar')) {
|
||||
const avatarOssId = payload.avatar
|
||||
if (avatarOssId !== undefined && avatarOssId !== null && avatarOssId !== '') {
|
||||
normalizedPayload.avatar = normalizeOssIdString(avatarOssId, 'avatar')
|
||||
}
|
||||
}
|
||||
if (Object.keys(normalizedPayload).length === 0) {
|
||||
throw new TypeError('个人资料至少需要一个可更新字段')
|
||||
}
|
||||
return normalizedPayload
|
||||
}
|
||||
|
||||
export const normalizeRecommendationPreference = (value) => {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw createRequestError('个性化推荐偏好响应无效', 'RECOMMENDATION_PREFERENCE_RESPONSE_INVALID')
|
||||
}
|
||||
if (typeof value.enabled !== 'boolean') {
|
||||
throw createRequestError('个性化推荐偏好缺少 enabled', 'RECOMMENDATION_PREFERENCE_RESPONSE_INVALID')
|
||||
}
|
||||
if (!Number.isSafeInteger(value.version) || value.version < 0) {
|
||||
throw createRequestError('个性化推荐偏好 version 无效', 'RECOMMENDATION_PREFERENCE_RESPONSE_INVALID')
|
||||
}
|
||||
return {
|
||||
enabled: value.enabled,
|
||||
version: value.version,
|
||||
updatedAt: normalizeOptionalResponseText(value.updatedAt, 'updatedAt', '个性化推荐偏好响应', 'RECOMMENDATION_PREFERENCE_RESPONSE_INVALID')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user