Files
BYW-VoiceHub/server/api/admin/schedule/draft-bulk.post.ts
T
Henry_Du 1106aeea6c feat: 迁移自定义功能至 1.5.9.9 版本
包含音乐源限制、CI 防 OOM 优化、数据库连接池优化、管理端权限及 UI 细节定制等功能
2026-09-12 19:26:42 +08:00

108 lines
3.5 KiB
TypeScript

import { db } from '~/drizzle/db'
import { schedules, songReplayRequests, scheduleSongPool } from '~/drizzle/schema'
import { and, eq, gte, inArray, lte } from 'drizzle-orm'
import { cacheService } from '~~/server/services/cacheService'
function isValidDateString(dateStr: unknown) {
if (typeof dateStr !== 'string') return false
const trimmed = dateStr.trim()
if (!/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) return false
const date = new Date(trimmed + 'T00:00:00.000Z')
return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === trimmed
}
export default defineEventHandler(async (event) => {
const user = event.context.user
if (!user || !['SONG_ADMIN', 'ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
throw createError({
statusCode: 403,
message: '权限不足'
})
}
const body = await readBody(event)
const playDate = body?.playDate
const playTimeId = body?.playTimeId
const songIds = body?.songIds
if (!isValidDateString(playDate)) {
throw createError({ statusCode: 400, message: '播放日期格式必须为YYYY-MM-DD' })
}
if (!Array.isArray(songIds) || !songIds.every((id) => typeof id === 'number' && id > 0)) {
throw createError({ statusCode: 400, message: '歌曲ID列表不能为空且必须为正整数数组' })
}
const playTimeFilterEnabled = playTimeId !== undefined && playTimeId !== null
if (playTimeFilterEnabled && (typeof playTimeId !== 'number' || playTimeId <= 0)) {
throw createError({ statusCode: 400, message: '播放时间ID必须是正整数' })
}
const startOfDay = new Date(playDate + 'T00:00:00.000Z')
const endOfDay = new Date(playDate + 'T23:59:59.999Z')
const result = await db.transaction(async (tx) => {
const whereBase = and(gte(schedules.playDate, startOfDay), lte(schedules.playDate, endOfDay))
const whereCondition = playTimeFilterEnabled
? and(whereBase, eq(schedules.playTimeId, playTimeId))
: whereBase
const existingSchedules = await tx
.select({ id: schedules.id, songId: schedules.songId })
.from(schedules)
.where(whereCondition)
const existingSongIds = existingSchedules.map((s) => s.songId).filter(Boolean) as number[]
await tx.delete(schedules).where(whereCondition)
if (existingSongIds.length > 0) {
await tx
.update(songReplayRequests)
.set({ status: 'PENDING', updatedAt: new Date() })
.where(
and(inArray(songReplayRequests.songId, existingSongIds), eq(songReplayRequests.status, 'FULFILLED'))
)
}
if (songIds.length === 0) {
return { deletedCount: existingSchedules.length, insertedCount: 0 }
}
const rows = songIds.map((songId, index) => ({
songId,
playDate: startOfDay,
sequence: index + 1,
playTimeId: playTimeFilterEnabled ? playTimeId : null,
isDraft: true,
publishedAt: null
}))
const inserted = await tx.insert(schedules).values(rows).returning({ id: schedules.id })
if (songIds.length > 0) {
await tx
.update(scheduleSongPool)
.set({
status: 'ASSIGNED',
targetPlayDate: startOfDay
})
.where(
and(
inArray(scheduleSongPool.songId, songIds),
eq(scheduleSongPool.status, 'PENDING')
)
)
}
return { deletedCount: existingSchedules.length, insertedCount: inserted.length }
})
await Promise.allSettled([cacheService.clearSchedulesCache(), cacheService.clearSongsCache()])
return {
success: true,
...result
}
})