后端迁移后台鉴权与人工兑换
This commit is contained in:
+62
-30
@@ -14,7 +14,23 @@ import { addHours, nowIso } from '../../utils/time.js'
|
|||||||
import { createHttpError } from '../../utils/http.js'
|
import { createHttpError } from '../../utils/http.js'
|
||||||
import { normalizePage, normalizePageSize } from './admin-query-utils.js'
|
import { normalizePage, normalizePageSize } from './admin-query-utils.js'
|
||||||
|
|
||||||
export async function ensureAdminUsersBootstrapped() {
|
type AdminUserRow = NonNullable<Awaited<ReturnType<typeof getAdminUserById>>>
|
||||||
|
|
||||||
|
type JsonObject = Record<string, any>
|
||||||
|
|
||||||
|
type AdminSession = {
|
||||||
|
sessionId: string
|
||||||
|
userId: number
|
||||||
|
username: string
|
||||||
|
role: AdminRole
|
||||||
|
allowedInventoryGroups: string[]
|
||||||
|
expiresAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminRole = 'admin' | 'operator' | 'support'
|
||||||
|
type AdminUserStatus = 'active' | 'disabled'
|
||||||
|
|
||||||
|
export async function ensureAdminUsersBootstrapped(): Promise<void> {
|
||||||
ensureAdminAuthConfigured()
|
ensureAdminAuthConfigured()
|
||||||
|
|
||||||
const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers) ? runtimeConfig.admin.defaultUsers : []
|
const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers) ? runtimeConfig.admin.defaultUsers : []
|
||||||
@@ -44,7 +60,7 @@ export async function ensureAdminUsersBootstrapped() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginAdmin(username, password) {
|
export async function loginAdmin(username: unknown, password: unknown): Promise<JsonObject> {
|
||||||
ensureAdminAuthConfigured()
|
ensureAdminAuthConfigured()
|
||||||
|
|
||||||
const normalizedUsername = String(username || '').trim().toLowerCase()
|
const normalizedUsername = String(username || '').trim().toLowerCase()
|
||||||
@@ -68,7 +84,7 @@ export async function loginAdmin(username, password) {
|
|||||||
return createAdminSession(user)
|
return createAdminSession(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyAdminSessionToken(token) {
|
export async function verifyAdminSessionToken(token: unknown): Promise<AdminSession> {
|
||||||
ensureAdminAuthConfigured()
|
ensureAdminAuthConfigured()
|
||||||
|
|
||||||
const normalizedToken = String(token || '').trim()
|
const normalizedToken = String(token || '').trim()
|
||||||
@@ -95,7 +111,7 @@ export async function verifyAdminSessionToken(token) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload = null
|
let payload: JsonObject | null = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'))
|
payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'))
|
||||||
@@ -133,7 +149,7 @@ export async function verifyAdminSessionToken(token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAdminSessionSummary(token) {
|
export async function getAdminSessionSummary(token: unknown): Promise<JsonObject> {
|
||||||
const session = await verifyAdminSessionToken(token)
|
const session = await verifyAdminSessionToken(token)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -148,7 +164,7 @@ export async function getAdminSessionSummary(token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requireAdminRole(session, allowedRoles) {
|
export function requireAdminRole(session: { role?: string }, allowedRoles: string[]): void {
|
||||||
if (allowedRoles.includes(session.role)) {
|
if (allowedRoles.includes(session.role)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -159,7 +175,7 @@ export function requireAdminRole(session, allowedRoles) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAdminUserList(query = {}) {
|
export async function getAdminUserList(query: JsonObject = {}): Promise<JsonObject> {
|
||||||
const page = normalizePage(query.page)
|
const page = normalizePage(query.page)
|
||||||
const pageSize = normalizePageSize(query.pageSize)
|
const pageSize = normalizePageSize(query.pageSize)
|
||||||
const { items, total } = await listAdminUsers({
|
const { items, total } = await listAdminUsers({
|
||||||
@@ -176,7 +192,7 @@ export async function getAdminUserList(query = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createManagedAdminUser(payload = {}) {
|
export async function createManagedAdminUser(payload: JsonObject = {}): Promise<JsonObject> {
|
||||||
const username = normalizeUsername(payload.username)
|
const username = normalizeUsername(payload.username)
|
||||||
const password = normalizePassword(payload.password)
|
const password = normalizePassword(payload.password)
|
||||||
|
|
||||||
@@ -219,7 +235,11 @@ export async function createManagedAdminUser(payload = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateManagedAdminUserInventoryGroups(userId, payload = {}, session) {
|
export async function updateManagedAdminUserInventoryGroups(
|
||||||
|
userId: number | string,
|
||||||
|
payload: JsonObject = {},
|
||||||
|
session: AdminSession,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const user = await getRequiredAdminUser(userId)
|
const user = await getRequiredAdminUser(userId)
|
||||||
const inventoryGroupCodes = normalizeInventoryGroupCodes(payload.inventoryGroupCodes)
|
const inventoryGroupCodes = normalizeInventoryGroupCodes(payload.inventoryGroupCodes)
|
||||||
|
|
||||||
@@ -231,7 +251,11 @@ export async function updateManagedAdminUserInventoryGroups(userId, payload = {}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateManagedAdminUserRole(userId, payload = {}, session) {
|
export async function updateManagedAdminUserRole(
|
||||||
|
userId: number | string,
|
||||||
|
payload: JsonObject = {},
|
||||||
|
session: AdminSession,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const user = await getRequiredAdminUser(userId)
|
const user = await getRequiredAdminUser(userId)
|
||||||
const role = normalizeAdminRole(payload.role)
|
const role = normalizeAdminRole(payload.role)
|
||||||
|
|
||||||
@@ -252,7 +276,11 @@ export async function updateManagedAdminUserRole(userId, payload = {}, session)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateManagedAdminUserStatus(userId, payload = {}, session) {
|
export async function updateManagedAdminUserStatus(
|
||||||
|
userId: number | string,
|
||||||
|
payload: JsonObject = {},
|
||||||
|
session: AdminSession,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const user = await getRequiredAdminUser(userId)
|
const user = await getRequiredAdminUser(userId)
|
||||||
const status = normalizeAdminUserStatus(payload.status)
|
const status = normalizeAdminUserStatus(payload.status)
|
||||||
|
|
||||||
@@ -273,7 +301,7 @@ export async function updateManagedAdminUserStatus(userId, payload = {}, session
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resetManagedAdminUserPassword(userId, payload = {}) {
|
export async function resetManagedAdminUserPassword(userId: number | string, payload: JsonObject = {}): Promise<JsonObject> {
|
||||||
const user = await getRequiredAdminUser(userId)
|
const user = await getRequiredAdminUser(userId)
|
||||||
const password = normalizePassword(payload.password)
|
const password = normalizePassword(payload.password)
|
||||||
|
|
||||||
@@ -295,7 +323,7 @@ export async function resetManagedAdminUserPassword(userId, payload = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureAdminAuthConfigured() {
|
export function ensureAdminAuthConfigured(): void {
|
||||||
const sessionSecret = String(runtimeConfig.admin?.sessionSecret || '').trim()
|
const sessionSecret = String(runtimeConfig.admin?.sessionSecret || '').trim()
|
||||||
const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers) ? runtimeConfig.admin.defaultUsers : []
|
const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers) ? runtimeConfig.admin.defaultUsers : []
|
||||||
|
|
||||||
@@ -309,7 +337,7 @@ export function ensureAdminAuthConfigured() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAdminSession(user) {
|
function createAdminSession(user: AdminUserRow): JsonObject {
|
||||||
const issuedAt = nowIso()
|
const issuedAt = nowIso()
|
||||||
const expiresAt = addHours(issuedAt, Number(runtimeConfig.admin.sessionTtlHours || 12))
|
const expiresAt = addHours(issuedAt, Number(runtimeConfig.admin.sessionTtlHours || 12))
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -335,13 +363,13 @@ function createAdminSession(user) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hashAdminPassword(password) {
|
export function hashAdminPassword(password: string): string {
|
||||||
const salt = crypto.randomBytes(16).toString('hex')
|
const salt = crypto.randomBytes(16).toString('hex')
|
||||||
const derived = crypto.scryptSync(password, salt, 64).toString('hex')
|
const derived = crypto.scryptSync(password, salt, 64).toString('hex')
|
||||||
return `scrypt$${salt}$${derived}`
|
return `scrypt$${salt}$${derived}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyAdminPassword(password, storedHash) {
|
function verifyAdminPassword(password: string, storedHash: string): boolean {
|
||||||
const [algorithm, salt, expectedHash] = String(storedHash || '').split('$')
|
const [algorithm, salt, expectedHash] = String(storedHash || '').split('$')
|
||||||
if (algorithm !== 'scrypt' || !salt || !expectedHash) {
|
if (algorithm !== 'scrypt' || !salt || !expectedHash) {
|
||||||
return false
|
return false
|
||||||
@@ -351,14 +379,14 @@ function verifyAdminPassword(password, storedHash) {
|
|||||||
return safeCompare(actualHash, expectedHash)
|
return safeCompare(actualHash, expectedHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
function signPayload(encodedPayload) {
|
function signPayload(encodedPayload: string): string {
|
||||||
return crypto
|
return crypto
|
||||||
.createHmac('sha256', String(runtimeConfig.admin.sessionSecret || ''))
|
.createHmac('sha256', String(runtimeConfig.admin.sessionSecret || ''))
|
||||||
.update(encodedPayload)
|
.update(encodedPayload)
|
||||||
.digest('base64url')
|
.digest('base64url')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeAdminRole(role) {
|
export function normalizeAdminRole(role: unknown): AdminRole {
|
||||||
const normalized = String(role || '').trim().toLowerCase()
|
const normalized = String(role || '').trim().toLowerCase()
|
||||||
|
|
||||||
if (normalized === 'admin') {
|
if (normalized === 'admin') {
|
||||||
@@ -372,11 +400,11 @@ export function normalizeAdminRole(role) {
|
|||||||
return 'operator'
|
return 'operator'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeAdminUserStatus(status) {
|
export function normalizeAdminUserStatus(status: unknown): AdminUserStatus {
|
||||||
return String(status || '').trim().toLowerCase() === 'disabled' ? 'disabled' : 'active'
|
return String(status || '').trim().toLowerCase() === 'disabled' ? 'disabled' : 'active'
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeCompare(input, expected) {
|
function safeCompare(input: unknown, expected: unknown): boolean {
|
||||||
const left = Buffer.from(String(input || ''), 'utf8')
|
const left = Buffer.from(String(input || ''), 'utf8')
|
||||||
const right = Buffer.from(String(expected || ''), 'utf8')
|
const right = Buffer.from(String(expected || ''), 'utf8')
|
||||||
|
|
||||||
@@ -387,25 +415,25 @@ function safeCompare(input, expected) {
|
|||||||
return crypto.timingSafeEqual(left, right)
|
return crypto.timingSafeEqual(left, right)
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeRoleQuery(role) {
|
function normalizeRoleQuery(role: unknown): string {
|
||||||
const normalized = String(role || '').trim().toLowerCase()
|
const normalized = String(role || '').trim().toLowerCase()
|
||||||
return ['admin', 'operator', 'support'].includes(normalized) ? normalized : ''
|
return ['admin', 'operator', 'support'].includes(normalized) ? normalized : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeStatusQuery(status) {
|
function normalizeStatusQuery(status: unknown): string {
|
||||||
const normalized = String(status || '').trim().toLowerCase()
|
const normalized = String(status || '').trim().toLowerCase()
|
||||||
return ['active', 'disabled'].includes(normalized) ? normalized : ''
|
return ['active', 'disabled'].includes(normalized) ? normalized : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeUsername(username) {
|
function normalizeUsername(username: unknown): string {
|
||||||
return String(username || '').trim().toLowerCase()
|
return String(username || '').trim().toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePassword(password) {
|
function normalizePassword(password: unknown): string {
|
||||||
return String(password || '').trim()
|
return String(password || '').trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateUsername(username) {
|
function validateUsername(username: string): void {
|
||||||
if (!/^[a-zA-Z0-9._-]{3,32}$/.test(username)) {
|
if (!/^[a-zA-Z0-9._-]{3,32}$/.test(username)) {
|
||||||
throw createHttpError('后台账号格式无效,需为 3-32 位字母数字或 ._-', {
|
throw createHttpError('后台账号格式无效,需为 3-32 位字母数字或 ._-', {
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
@@ -414,7 +442,7 @@ function validateUsername(username) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validatePassword(password) {
|
function validatePassword(password: string): void {
|
||||||
if (password.length < 8) {
|
if (password.length < 8) {
|
||||||
throw createHttpError('后台密码至少 8 位', {
|
throw createHttpError('后台密码至少 8 位', {
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
@@ -423,7 +451,7 @@ function validatePassword(password) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRequiredAdminUser(userId) {
|
async function getRequiredAdminUser(userId: number | string): Promise<AdminUserRow> {
|
||||||
const user = await getAdminUserById(Number(userId))
|
const user = await getAdminUserById(Number(userId))
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -436,7 +464,11 @@ async function getRequiredAdminUser(userId) {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureAdminUserChangeAllowed(user, options = {}, session) {
|
async function ensureAdminUserChangeAllowed(
|
||||||
|
user: AdminUserRow,
|
||||||
|
options: { nextRole?: AdminRole, nextStatus?: AdminUserStatus } = {},
|
||||||
|
session: AdminSession,
|
||||||
|
): Promise<void> {
|
||||||
const nextRole = options.nextRole || user.role
|
const nextRole = options.nextRole || user.role
|
||||||
const nextStatus = options.nextStatus || user.status
|
const nextStatus = options.nextStatus || user.status
|
||||||
|
|
||||||
@@ -455,7 +487,7 @@ async function ensureAdminUserChangeAllowed(user, options = {}, session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapAdminUser(user) {
|
function mapAdminUser(user: AdminUserRow | null): JsonObject {
|
||||||
return {
|
return {
|
||||||
userId: Number(user.id),
|
userId: Number(user.id),
|
||||||
username: String(user.username || ''),
|
username: String(user.username || ''),
|
||||||
@@ -467,7 +499,7 @@ function mapAdminUser(user) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeInventoryGroupCodes(values) {
|
function normalizeInventoryGroupCodes(values: unknown): string[] {
|
||||||
return Array.from(new Set((Array.isArray(values) ? values : [])
|
return Array.from(new Set((Array.isArray(values) ? values : [])
|
||||||
.map((value) => String(value || '').trim())
|
.map((value) => String(value || '').trim())
|
||||||
.filter(Boolean)))
|
.filter(Boolean)))
|
||||||
+50
-36
@@ -21,21 +21,24 @@ import { randomId } from '../../utils/random.js'
|
|||||||
import { closeAdminTask, confirmAdminTaskAssistedRole, redeemAdminTaskAssisted } from './admin-write-service.js'
|
import { closeAdminTask, confirmAdminTaskAssistedRole, redeemAdminTaskAssisted } from './admin-write-service.js'
|
||||||
import { createAdminViewerContext, isAssistedClaimTask, parseTaskContext } from './admin-read-shared-helpers.js'
|
import { createAdminViewerContext, isAssistedClaimTask, parseTaskContext } from './admin-read-shared-helpers.js'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
AdminEntityIdInput,
|
||||||
|
AdminViewerSessionInput,
|
||||||
|
} from '../../types/admin-read-inputs.js'
|
||||||
|
import type { AdminManualRedeemCreateInput } from '../../types/admin-write-inputs.js'
|
||||||
|
import type { TaskRow } from '../../types/repository-rows.js'
|
||||||
|
|
||||||
const MANUAL_PROVIDER = 'manual'
|
const MANUAL_PROVIDER = 'manual'
|
||||||
const MANUAL_PLATFORM = 'manual_redeem'
|
const MANUAL_PLATFORM = 'manual_redeem'
|
||||||
const MANUAL_SOURCE_TYPE = 'admin_manual_redeem'
|
const MANUAL_SOURCE_TYPE = 'admin_manual_redeem'
|
||||||
const ASSISTED_PROFILE_KEY = 'tencent_claim_assisted'
|
const ASSISTED_PROFILE_KEY = 'tencent_claim_assisted'
|
||||||
|
|
||||||
/** @typedef {import('../../types/admin-read-inputs.js').AdminEntityIdInput} AdminEntityIdInput */
|
type JsonObject = Record<string, any>
|
||||||
/** @typedef {import('../../types/admin-read-inputs.js').AdminViewerSessionInput} AdminViewerSessionInput */
|
|
||||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminManualRedeemCreateInput} AdminManualRedeemCreateInput */
|
|
||||||
|
|
||||||
/** @param {AdminManualRedeemCreateInput} [payload] */
|
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
|
||||||
export async function createAdminManualRedeemTask(
|
export async function createAdminManualRedeemTask(
|
||||||
payload = /** @type {AdminManualRedeemCreateInput} */ ({}),
|
payload: AdminManualRedeemCreateInput = {},
|
||||||
session = null,
|
session: AdminViewerSessionInput | null = null,
|
||||||
) {
|
): Promise<JsonObject> {
|
||||||
const viewerContext = createAdminViewerContext(session)
|
const viewerContext = createAdminViewerContext(session)
|
||||||
const proofValue = normalizeManualProofValue(payload.proofValue)
|
const proofValue = normalizeManualProofValue(payload.proofValue)
|
||||||
const skuCode = String(payload.skuCode || '').trim()
|
const skuCode = String(payload.skuCode || '').trim()
|
||||||
@@ -285,72 +288,83 @@ export async function createAdminManualRedeemTask(
|
|||||||
return getAdminManualRedeemDetail(createdTask.id, session)
|
return getAdminManualRedeemDetail(createdTask.id, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
export async function getAdminManualRedeemDetail(
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
taskId: AdminEntityIdInput,
|
||||||
export async function getAdminManualRedeemDetail(taskId, session = null) {
|
session: AdminViewerSessionInput | null = null,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const task = await getRequiredManualRedeemTask(taskId, session)
|
const task = await getRequiredManualRedeemTask(taskId, session)
|
||||||
const detail = await getClaimDetailForAdminTask(task.id)
|
const detail = await getClaimDetailForAdminTask(task.id)
|
||||||
return decorateManualRedeemDetail(task, detail)
|
return decorateManualRedeemDetail(task, detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
export async function createAdminManualRedeemSession(
|
||||||
/** @param {{ loginType?: string, forceRecreate?: boolean }} [payload] */
|
taskId: AdminEntityIdInput,
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
payload: { loginType?: string, forceRecreate?: boolean } = {},
|
||||||
export async function createAdminManualRedeemSession(taskId, payload = {}, session = null) {
|
session: AdminViewerSessionInput | null = null,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const task = await getRequiredManualRedeemTask(taskId, session)
|
const task = await getRequiredManualRedeemTask(taskId, session)
|
||||||
const detail = await createClaimSessionForAdminTask(task.id, payload)
|
const detail = await createClaimSessionForAdminTask(task.id, payload)
|
||||||
return decorateManualRedeemDetail(task, detail)
|
return decorateManualRedeemDetail(task, detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
export async function getAdminManualRedeemSessionSummary(
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
taskId: AdminEntityIdInput,
|
||||||
export async function getAdminManualRedeemSessionSummary(taskId, session = null) {
|
session: AdminViewerSessionInput | null = null,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const task = await getRequiredManualRedeemTask(taskId, session)
|
const task = await getRequiredManualRedeemTask(taskId, session)
|
||||||
const detail = await getClaimSessionSummaryForAdminTask(task.id)
|
const detail = await getClaimSessionSummaryForAdminTask(task.id)
|
||||||
return decorateManualRedeemDetail(task, detail)
|
return decorateManualRedeemDetail(task, detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
export async function reloadAdminManualRedeemSession(
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
taskId: AdminEntityIdInput,
|
||||||
export async function reloadAdminManualRedeemSession(taskId, session = null) {
|
session: AdminViewerSessionInput | null = null,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const task = await getRequiredManualRedeemTask(taskId, session)
|
const task = await getRequiredManualRedeemTask(taskId, session)
|
||||||
const detail = await reloadClaimSessionForAdminTask(task.id)
|
const detail = await reloadClaimSessionForAdminTask(task.id)
|
||||||
return decorateManualRedeemDetail(task, detail)
|
return decorateManualRedeemDetail(task, detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
export async function closeAdminManualRedeemSession(
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
taskId: AdminEntityIdInput,
|
||||||
export async function closeAdminManualRedeemSession(taskId, session = null) {
|
session: AdminViewerSessionInput | null = null,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const task = await getRequiredManualRedeemTask(taskId, session)
|
const task = await getRequiredManualRedeemTask(taskId, session)
|
||||||
const detail = await closeClaimSessionForAdminTask(task.id)
|
const detail = await closeClaimSessionForAdminTask(task.id)
|
||||||
return decorateManualRedeemDetail(task, detail)
|
return decorateManualRedeemDetail(task, detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
export async function closeAdminManualRedeemTask(
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
taskId: AdminEntityIdInput,
|
||||||
export async function closeAdminManualRedeemTask(taskId, session = null) {
|
session: AdminViewerSessionInput | null = null,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const task = await getRequiredManualRedeemTask(taskId, session)
|
const task = await getRequiredManualRedeemTask(taskId, session)
|
||||||
await closeAdminTask(task.id)
|
await closeAdminTask(task.id)
|
||||||
return getAdminManualRedeemDetail(task.id, session)
|
return getAdminManualRedeemDetail(task.id, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
export async function confirmAdminManualRedeemRole(
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
taskId: AdminEntityIdInput,
|
||||||
export async function confirmAdminManualRedeemRole(taskId, session = null) {
|
session: AdminViewerSessionInput | null = null,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const task = await getRequiredManualRedeemTask(taskId, session)
|
const task = await getRequiredManualRedeemTask(taskId, session)
|
||||||
await confirmAdminTaskAssistedRole(task.id, session)
|
await confirmAdminTaskAssistedRole(task.id, session)
|
||||||
return getAdminManualRedeemDetail(task.id, session)
|
return getAdminManualRedeemDetail(task.id, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
export async function redeemAdminManualRedeemTask(
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
taskId: AdminEntityIdInput,
|
||||||
export async function redeemAdminManualRedeemTask(taskId, session = null) {
|
session: AdminViewerSessionInput | null = null,
|
||||||
|
): Promise<JsonObject> {
|
||||||
const task = await getRequiredManualRedeemTask(taskId, session)
|
const task = await getRequiredManualRedeemTask(taskId, session)
|
||||||
await redeemAdminTaskAssisted(task.id, session)
|
await redeemAdminTaskAssisted(task.id, session)
|
||||||
return getAdminManualRedeemDetail(task.id, session)
|
return getAdminManualRedeemDetail(task.id, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRequiredManualRedeemTask(taskId, session = null) {
|
async function getRequiredManualRedeemTask(
|
||||||
|
taskId: AdminEntityIdInput,
|
||||||
|
session: AdminViewerSessionInput | null = null,
|
||||||
|
): Promise<TaskRow> {
|
||||||
const task = await getTaskById(Number(taskId))
|
const task = await getTaskById(Number(taskId))
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
@@ -392,7 +406,7 @@ async function getRequiredManualRedeemTask(taskId, session = null) {
|
|||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
function decorateManualRedeemDetail(task, detail) {
|
function decorateManualRedeemDetail(task: TaskRow, detail: any): JsonObject {
|
||||||
const taskContext = parseTaskContext(task)
|
const taskContext = parseTaskContext(task)
|
||||||
const manualRedeem = taskContext.manualRedeem && typeof taskContext.manualRedeem === 'object'
|
const manualRedeem = taskContext.manualRedeem && typeof taskContext.manualRedeem === 'object'
|
||||||
? taskContext.manualRedeem
|
? taskContext.manualRedeem
|
||||||
@@ -415,6 +429,6 @@ function decorateManualRedeemDetail(task, detail) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeManualProofValue(value) {
|
function normalizeManualProofValue(value: unknown): string {
|
||||||
return String(value || '').replace(/\s+/g, ' ').trim()
|
return String(value || '').replace(/\s+/g, ' ').trim()
|
||||||
}
|
}
|
||||||
@@ -785,6 +785,16 @@
|
|||||||
- `npm run typecheck`
|
- `npm run typecheck`
|
||||||
- `npm run build`
|
- `npm run build`
|
||||||
- `npm test` 共 139 个用例通过
|
- `npm test` 共 139 个用例通过
|
||||||
|
201. 后台 Admin 鉴权与人工兑换服务迁移到 `.ts`:
|
||||||
|
- `src/services/admin/admin-auth-service.ts`
|
||||||
|
- `src/services/admin/admin-manual-redeem-service.ts`
|
||||||
|
202. 后台默认用户初始化、登录 / session token 验证、用户管理、角色 / 状态归一化、人工兑换任务创建、人工兑换 claim session 包装、确认角色 / 兑换 / 关闭操作已进入 TS 编译链路;admin user row、session、role/status、manual redeem input、entity id、viewer session 与动态详情响应补齐类型
|
||||||
|
203. Docker 内验证通过:
|
||||||
|
- `src/services/admin/*.test.js` 共 8 个用例通过
|
||||||
|
- `npm run typecheck`
|
||||||
|
- `npm run build`
|
||||||
|
- `npm test` 共 139 个用例通过
|
||||||
|
204. `src/services/admin` 目录下非测试 `.js` 服务文件已全部迁移为 `.ts`
|
||||||
|
|
||||||
## 下一步建议
|
## 下一步建议
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user