93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { db } from '~/drizzle/db'
|
|
import { schedules, songReplayRequests } 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 })
|
|
|
|
return { deletedCount: existingSchedules.length, insertedCount: inserted.length }
|
|
})
|
|
|
|
await Promise.allSettled([cacheService.clearSchedulesCache(), cacheService.clearSongsCache()])
|
|
|
|
return {
|
|
success: true,
|
|
...result
|
|
}
|
|
})
|