feat: 迁移自定义功能至 1.5.9.9 版本
包含音乐源限制、CI 防 OOM 优化、数据库连接池优化、管理端权限及 UI 细节定制等功能
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user