feat: align with upstream 1.5.9.9

This commit is contained in:
2026-09-12 18:21:11 +08:00
commit 34f0679117
805 changed files with 376275 additions and 0 deletions
+129
View File
@@ -0,0 +1,129 @@
import { db } from '~/drizzle/db'
import { cardCodes, cardCodeRedeemLogs } from '~/drizzle/schema'
import { inArray } from 'drizzle-orm'
import { z } from 'zod'
import { CARD_CODE_STATUSES } from '../../card-codes/statuses'
const MAX_ID_LENGTH = 32
const MAX_NOTE_LENGTH = 500
const updateCardCodesSchema = z.object({
id: z.union([z.number().int().positive(), z.string().max(MAX_ID_LENGTH)]).optional(),
ids: z.array(z.union([z.number().int().positive(), z.string().max(MAX_ID_LENGTH)])).max(500, '单次最多更新 500 个点歌券').optional(),
status: z.enum(CARD_CODE_STATUSES).optional(),
note: z.union([z.string().max(MAX_NOTE_LENGTH), z.null()]).optional()
})
export default defineEventHandler(async (event) => {
const apiKey = event.context.apiKey
if (!apiKey) {
throw createError({ statusCode: 401, message: 'API认证失败' })
}
const operatorId = Number(apiKey.createdByUserId)
if (!Number.isInteger(operatorId) || operatorId <= 0) {
throw createError({ statusCode: 400, message: 'API Key 缺少有效创建者,无法执行点歌券状态变更' })
}
try {
const body = await readBody(event) ?? {}
const validatedData = updateCardCodesSchema.parse(body)
const rawIds = [
...(validatedData.id !== undefined ? [validatedData.id] : []),
...(validatedData.ids || [])
]
const ids = Array.from(new Set(
rawIds.map((id) => Number(id)).filter((id) => Number.isInteger(id) && id > 0)
))
if (!ids.length) {
throw createError({ statusCode: 400, message: '缺少有效点歌券ID' })
}
const updateObj: any = { updatedAt: new Date() }
const status = validatedData.status
if (status) {
updateObj.status = status
if (status === 'REDEEMED') {
updateObj.redeemedBy = operatorId
updateObj.redeemedAt = new Date()
updateObj.lockedBy = null
updateObj.lockedAt = null
}
if (status === 'LOCKED') {
updateObj.lockedBy = operatorId
updateObj.lockedAt = new Date()
updateObj.redeemedBy = null
updateObj.redeemedAt = null
}
if (status === 'AVAILABLE') {
updateObj.lockedBy = null
updateObj.lockedAt = null
updateObj.redeemedBy = null
updateObj.redeemedAt = null
}
}
if ('note' in validatedData) {
updateObj.note = typeof validatedData.note === 'string' ? validatedData.note.trim() || null : validatedData.note
}
if (!status && !('note' in validatedData)) {
throw createError({ statusCode: 400, message: '没有需要更新的字段' })
}
const updatedCardCodes = await db.transaction(async (tx) => {
const beforeRows = await tx
.select({
id: cardCodes.id,
code: cardCodes.code,
status: cardCodes.status
})
.from(cardCodes)
.where(inArray(cardCodes.id, ids))
const beforeMap = new Map(beforeRows.map((row) => [row.id, row]))
const updatedRows = await tx.update(cardCodes).set(updateObj).where(inArray(cardCodes.id, ids)).returning()
if (status === 'REDEEMED' && updatedRows.length > 0) {
const logsToInsert = updatedRows
.filter((row) => {
const before = beforeMap.get(row.id)
return before && before.status !== status
})
.map((row) => ({
cardCodeId: row.id,
codeSnapshot: row.code,
redeemedBy: operatorId,
redeemedAt: row.redeemedAt || new Date(),
source: 'OPEN_API_MANUAL',
songId: null
}))
if (logsToInsert.length > 0) {
await tx.insert(cardCodeRedeemLogs).values(logsToInsert)
}
}
return updatedRows
})
return {
success: true,
message: '点歌券更新成功',
data: {
cardCodes: updatedCardCodes,
updated: updatedCardCodes.length,
requested: ids.length
}
}
} catch (err: any) {
if (err.statusCode) throw err
if (err.name === 'ZodError') {
throw createError({
statusCode: 400,
message: `请求参数验证失败:${(err.issues || err.errors).map((issue: any) => issue.message).join(', ')}`
})
}
console.error('开放API更新点歌券失败', err)
throw createError({ statusCode: 500, message: '更新点歌券失败' })
}
})