99 lines
3.3 KiB
TypeScript
99 lines
3.3 KiB
TypeScript
import { db } from '~/drizzle/db'
|
|
import { cardCodes } from '~/drizzle/schema'
|
|
import { and, count, desc, eq, ilike, or } from 'drizzle-orm'
|
|
import { CARD_CODE_STATUSES } from '../../card-codes/statuses'
|
|
|
|
const MAX_KEYWORD_LENGTH = 100
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const apiKey = event.context.apiKey
|
|
if (!apiKey) {
|
|
throw createError({ statusCode: 401, message: 'API认证失败' })
|
|
}
|
|
|
|
try {
|
|
const query = getQuery(event)
|
|
const status = typeof query.status === 'string' ? query.status.trim().toUpperCase() : ''
|
|
const keyword = typeof query.q === 'string' ? query.q.trim() : ''
|
|
const includeStats = ['1', 'true', 'yes'].includes(String(query.includeStats || query.stats || '').toLowerCase())
|
|
const page = Math.max(1, Number.parseInt(String(query.page || '1'), 10) || 1)
|
|
const limitInput = Number.parseInt(String(query.limit || '20'), 10) || 20
|
|
const limit = Math.min(100, Math.max(1, limitInput))
|
|
const offset = (page - 1) * limit
|
|
|
|
if (status && !CARD_CODE_STATUSES.includes(status as any)) {
|
|
throw createError({ statusCode: 400, message: '不支持的状态值' })
|
|
}
|
|
if (keyword.length > MAX_KEYWORD_LENGTH) {
|
|
throw createError({ statusCode: 400, message: `搜索关键词不能超过 ${MAX_KEYWORD_LENGTH} 个字符` })
|
|
}
|
|
|
|
const conditions = [] as any[]
|
|
if (status) {
|
|
conditions.push(eq(cardCodes.status, status as any))
|
|
}
|
|
if (keyword) {
|
|
conditions.push(
|
|
or(
|
|
ilike(cardCodes.code, `%${keyword}%`),
|
|
ilike(cardCodes.note, `%${keyword}%`)
|
|
)
|
|
)
|
|
}
|
|
|
|
let queryBuilder = db.select().from(cardCodes)
|
|
let countQueryBuilder = db.select({ count: count() }).from(cardCodes)
|
|
if (conditions.length > 0) {
|
|
queryBuilder = queryBuilder.where(and(...conditions))
|
|
countQueryBuilder = countQueryBuilder.where(and(...conditions))
|
|
}
|
|
|
|
const statsQuery = includeStats
|
|
? db.select({ status: cardCodes.status, count: count() }).from(cardCodes).groupBy(cardCodes.status)
|
|
: Promise.resolve([])
|
|
|
|
const [items, totalResult, statsRows] = await Promise.all([
|
|
queryBuilder.orderBy(desc(cardCodes.createdAt)).limit(limit).offset(offset),
|
|
countQueryBuilder,
|
|
statsQuery
|
|
])
|
|
|
|
let stats
|
|
if (includeStats) {
|
|
stats = { total: 0, available: 0, locked: 0, redeemed: 0, invalid: 0 }
|
|
for (const row of statsRows) {
|
|
const value = Number(row.count || 0)
|
|
stats.total += value
|
|
if (row.status === 'AVAILABLE') stats.available = value
|
|
if (row.status === 'LOCKED') stats.locked = value
|
|
if (row.status === 'REDEEMED') stats.redeemed = value
|
|
if (row.status === 'INVALID') stats.invalid = value
|
|
}
|
|
}
|
|
|
|
const total = Number(totalResult[0]?.count || 0)
|
|
return {
|
|
success: true,
|
|
data: {
|
|
cardCodes: items,
|
|
pagination: {
|
|
page,
|
|
limit,
|
|
total,
|
|
totalPages: Math.max(1, Math.ceil(total / limit))
|
|
},
|
|
...(includeStats ? { stats } : {}),
|
|
filters: {
|
|
status: status || null,
|
|
q: keyword || null,
|
|
includeStats
|
|
}
|
|
}
|
|
}
|
|
} catch (err: any) {
|
|
if (err.statusCode) throw err
|
|
console.error('开放API获取点歌券列表失败', err)
|
|
throw createError({ statusCode: 500, message: '获取点歌券列表失败' })
|
|
}
|
|
})
|