feat: migrate app routes and business modules

This commit is contained in:
2026-08-12 18:22:59 +08:00
parent 555aa00043
commit cc706378c2
247 changed files with 28623 additions and 14988 deletions
@@ -0,0 +1,236 @@
import {
createPreviewMyJoinApplications,
createPreviewPendingJoinApplications,
previewCurrentUser
} from '@/data/preview/genealogy-membership.js'
import { hasRemoteConfig } from '@/utils/runtime-config.js'
import { normalizeGenealogyPathId } from './genealogy-contract.js'
import {
JOIN_APPLICATION_STATUS,
normalizeGenealogyInvitation,
normalizeGenealogyInvitationRedeemPayload,
normalizeGenealogyInvitationToken,
normalizeIssuedGenealogyInvitation,
normalizeJoinApplicationPayload,
normalizeJoinAuditPayload,
normalizeMyGenealogyInvitations
} from './genealogy-membership-contract.js'
import { normalizeResourcePathId } from './request-normalizers.js'
import { createRequestError, requestStrict } from './request-client.js'
const requireRemoteMembership = (label, operation) => {
if (hasRemoteConfig()) return
throw createRequestError(
`${label}${operation}需要真实服务,当前本地预览不会伪造结果`,
operation === '读取' ? 'REMOTE_READ_REQUIRED' : 'REMOTE_WRITE_REQUIRED'
)
}
const previewMyJoinApplications = createPreviewMyJoinApplications()
const previewPendingJoinApplications = createPreviewPendingJoinApplications()
let nextPreviewApplicationId = Date.now()
const snapshotPreviewJoinApplications = (applications) =>
applications.map((application) => ({ ...application }))
const createPreviewApplicationId = () => String(nextPreviewApplicationId++)
const readMembershipList = async (url, label, requestOptions) => {
requireRemoteMembership(label, '读取')
const rows = await requestStrict({ url, method: 'GET' }, {
requestController: requestOptions.requestController ?? null
})
if (!Array.isArray(rows)) {
throw createRequestError(`${label}响应不是列表`, 'LIST_RESPONSE_INVALID')
}
return rows
}
const writeMembership = (url, method, data, label, requestOptions) => {
requireRemoteMembership(label, '写入')
return requestStrict({
url,
method,
...(data === undefined ? {} : { data })
}, {
requestController: requestOptions.requestController ?? null
})
}
export const genealogyMembershipApi = {
async previewGenealogyInvitation(token, requestOptions = {}) {
const normalizedToken = normalizeGenealogyInvitationToken(token)
requireRemoteMembership('家谱邀请码', '读取')
const invitation = await requestStrict({
url: `/genealogy/app/genealogies/invitations/preview?token=${encodeURIComponent(normalizedToken)}`,
method: 'GET'
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeGenealogyInvitation(invitation, 'ACTIVE')
},
async issueGenealogyInvitation(genealogyId, requestOptions = {}) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const invitationResponse = await writeMembership(
`/genealogy/app/genealogies/${normalizedGenealogyId}/invitations`,
'POST',
undefined,
'家谱邀请码签发',
requestOptions
)
const invitation = normalizeIssuedGenealogyInvitation(invitationResponse)
if (invitation.genealogyId !== normalizedGenealogyId) {
throw createRequestError(
'家谱邀请码归属与当前家谱不匹配',
'GENEALOGY_INVITATION_RESPONSE_INVALID'
)
}
return invitation
},
async getMyGenealogyInvitations(requestOptions = {}) {
requireRemoteMembership('我的家谱邀请', '读取')
const invitations = await requestStrict({
url: '/genealogy/app/genealogies/invitations/mine',
method: 'GET'
}, {
requestController: requestOptions.requestController ?? null
})
return normalizeMyGenealogyInvitations(invitations)
},
async revokeGenealogyInvitation(inviteId, requestOptions = {}) {
const normalizedInviteId = normalizeResourcePathId(inviteId, '家谱邀请标识')
requireRemoteMembership('家谱邀请码撤销', '写入')
await requestStrict({
url: `/genealogy/app/genealogies/invitations/${normalizedInviteId}`,
method: 'DELETE'
}, {
requireData: false,
requestController: requestOptions.requestController ?? null
})
return null
},
async redeemGenealogyInvitation(payload, requestOptions = {}) {
const invitation = await writeMembership(
'/genealogy/app/genealogies/invitations/redeem',
'POST',
normalizeGenealogyInvitationRedeemPayload(payload),
'家谱邀请码兑换',
requestOptions
)
return normalizeGenealogyInvitation(invitation, 'REDEEMED')
},
async applyToJoin(genealogyId, payload, requestOptions = {}) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const applicationDraft = normalizeJoinApplicationPayload(payload)
if (hasRemoteConfig()) {
return requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/join-applies`,
method: 'POST',
data: applicationDraft
}, {
requestController: requestOptions.requestController ?? null
})
}
previewMyJoinApplications.unshift({
id: createPreviewApplicationId(),
genealogyId: normalizedGenealogyId,
name: applicationDraft.applicantName || previewCurrentUser.name,
phone: applicationDraft.phone || previewCurrentUser.phone,
relation: applicationDraft.relationDesc || '关系待补充',
reason: applicationDraft.applyReason || '',
appliedAt: '刚刚',
status: JOIN_APPLICATION_STATUS.PENDING
})
return null
},
async getMyJoinApplications(requestOptions = {}) {
if (!hasRemoteConfig()) {
return snapshotPreviewJoinApplications(previewMyJoinApplications)
}
return readMembershipList(
'/genealogy/app/genealogies/join-applies/mine',
'我的加入申请',
requestOptions
)
},
async withdrawJoinApplication(applicationId, requestOptions = {}) {
const normalizedApplicationId = normalizeResourcePathId(applicationId, '加入申请标识')
if (!hasRemoteConfig()) {
const application = previewMyJoinApplications.find(
(candidate) =>
candidate.id === normalizedApplicationId &&
candidate.status === JOIN_APPLICATION_STATUS.PENDING
)
if (!application) {
throw createRequestError(
'待撤回申请不存在或已处理',
'PREVIEW_JOIN_APPLICATION_NOT_FOUND'
)
}
application.status = JOIN_APPLICATION_STATUS.CANCELLED
return null
}
await requestStrict({
url: `/genealogy/app/genealogies/join-applies/${normalizedApplicationId}`,
method: 'DELETE'
}, {
requireData: false,
requestController: requestOptions.requestController ?? null
})
return null
},
async getPendingApplications(genealogyId, requestOptions = {}) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
if (!hasRemoteConfig()) {
return snapshotPreviewJoinApplications(
previewPendingJoinApplications.filter(
(application) =>
application.genealogyId === normalizedGenealogyId &&
application.status === JOIN_APPLICATION_STATUS.PENDING
)
)
}
return readMembershipList(
`/genealogy/app/genealogies/${normalizedGenealogyId}/join-applies/pending`,
'待审核申请',
requestOptions
)
},
async auditApplication(genealogyId, applicationId, payload, requestOptions = {}) {
const normalizedGenealogyId = normalizeGenealogyPathId(genealogyId)
const normalizedApplicationId = normalizeResourcePathId(applicationId, '加入申请标识')
const auditDecision = normalizeJoinAuditPayload(payload)
if (hasRemoteConfig()) {
await requestStrict({
url: `/genealogy/app/genealogies/${normalizedGenealogyId}/join-applies/${normalizedApplicationId}/audit`,
method: 'PUT',
data: auditDecision
}, {
requireData: false,
requestController: requestOptions.requestController ?? null
})
return null
}
const application = previewPendingJoinApplications.find(
(candidate) =>
candidate.id === normalizedApplicationId &&
candidate.genealogyId === normalizedGenealogyId &&
candidate.status === JOIN_APPLICATION_STATUS.PENDING
)
if (!application) {
throw createRequestError(
'待审核申请不存在、已处理或不属于当前家谱',
'PREVIEW_JOIN_APPLICATION_NOT_FOUND'
)
}
Object.assign(application, auditDecision)
return null
}
}