159 lines
5.2 KiB
TypeScript
159 lines
5.2 KiB
TypeScript
import { and, eq, gte, lte } from 'drizzle-orm'
|
|
import { db } from '~/drizzle/db'
|
|
import { schedules, songs } from '~/drizzle/schema'
|
|
import { getClientIP } from '~~/server/utils/ip-utils'
|
|
import { getServerDate } from '~~/server/utils/serverTime'
|
|
|
|
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 startTime = Date.now()
|
|
const clientIP = getClientIP(event)
|
|
|
|
const body = await readBody(event)
|
|
const fromDate = typeof body?.fromDate === 'string' ? body.fromDate.trim() : ''
|
|
const toDate = typeof body?.toDate === 'string' ? body.toDate.trim() : ''
|
|
const overwriteDrafts = body?.overwriteDrafts === true
|
|
|
|
if (!fromDate || !toDate) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: '缺少来源日期或目标日期'
|
|
})
|
|
}
|
|
|
|
if (fromDate === toDate) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: '目标日期不能与当前日期相同'
|
|
})
|
|
}
|
|
|
|
const fromStart = new Date(`${fromDate}T00:00:00.000Z`)
|
|
const fromEnd = new Date(`${fromDate}T23:59:59.999Z`)
|
|
const toPlayDate = new Date(`${toDate}T00:00:00.000Z`)
|
|
const toStart = new Date(`${toDate}T00:00:00.000Z`)
|
|
const toEnd = new Date(`${toDate}T23:59:59.999Z`)
|
|
|
|
if (
|
|
Number.isNaN(fromStart.getTime()) ||
|
|
Number.isNaN(fromEnd.getTime()) ||
|
|
Number.isNaN(toPlayDate.getTime()) ||
|
|
Number.isNaN(toStart.getTime()) ||
|
|
Number.isNaN(toEnd.getTime()) ||
|
|
fromStart.toISOString().split('T')[0] !== fromDate ||
|
|
toPlayDate.toISOString().split('T')[0] !== toDate
|
|
) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: '日期无效,请使用 YYYY-MM-DD 格式并确保日期有效'
|
|
})
|
|
}
|
|
|
|
const copyResult = await db.transaction(async (tx) => {
|
|
// 检查目标日期是否已有排期(含草稿)
|
|
const [existingCount, draftCount] = await Promise.all([
|
|
tx
|
|
.select({ id: schedules.id })
|
|
.from(schedules)
|
|
.where(and(gte(schedules.playDate, toStart), lte(schedules.playDate, toEnd), eq(schedules.isDraft, false)))
|
|
.limit(1),
|
|
tx
|
|
.select({ id: schedules.id })
|
|
.from(schedules)
|
|
.where(and(gte(schedules.playDate, toStart), lte(schedules.playDate, toEnd), eq(schedules.isDraft, true)))
|
|
.limit(999)
|
|
])
|
|
|
|
const hasExistingSchedule = existingCount.length > 0
|
|
const overwritingDrafts = overwriteDrafts && draftCount.length > 0
|
|
|
|
// 有已发布排期不允许复制
|
|
if (hasExistingSchedule) {
|
|
throw createError({
|
|
statusCode: 409,
|
|
message: '目标日期已存在已发布排期,无法复制。请先清空目标日期的排期。'
|
|
})
|
|
}
|
|
|
|
// 有草稿但未请求覆盖,返回信息供前端询问用户
|
|
if (draftCount.length > 0 && !overwritingDrafts) {
|
|
throw createError({
|
|
statusCode: 409,
|
|
message: '目标日期已存在草稿,请确认是否覆盖。',
|
|
data: { draftCount: draftCount.length, requiresConfirmation: true }
|
|
})
|
|
}
|
|
|
|
let replacedDrafts = []
|
|
|
|
// 清除目标日期草稿(如用户确认覆盖)
|
|
if (overwritingDrafts) {
|
|
replacedDrafts = await tx
|
|
.select()
|
|
.from(schedules)
|
|
.where(and(gte(schedules.playDate, toStart), lte(schedules.playDate, toEnd), eq(schedules.isDraft, true)))
|
|
await tx.delete(schedules).where(and(gte(schedules.playDate, toStart), lte(schedules.playDate, toEnd)))
|
|
}
|
|
|
|
// 查询来源日期的所有排期(含草稿和已发布),按 sequence 排序
|
|
const sourceSchedules = await tx
|
|
.select({
|
|
songId: schedules.songId,
|
|
sequence: schedules.sequence,
|
|
playTimeId: schedules.playTimeId
|
|
})
|
|
.from(schedules)
|
|
.innerJoin(songs, eq(schedules.songId, songs.id))
|
|
.where(and(gte(schedules.playDate, fromStart), lte(schedules.playDate, fromEnd)))
|
|
.orderBy(schedules.sequence)
|
|
|
|
if (sourceSchedules.length === 0) {
|
|
return { copiedCount: 0, createdScheduleIds: [], replacedDrafts }
|
|
}
|
|
|
|
let copiedCount = 0
|
|
const createdScheduleIds = []
|
|
const now = getServerDate()
|
|
|
|
for (const source of sourceSchedules) {
|
|
const inserted = await tx
|
|
.insert(schedules)
|
|
.values({
|
|
songId: source.songId,
|
|
playDate: toPlayDate,
|
|
sequence: source.sequence,
|
|
playTimeId: source.playTimeId,
|
|
isDraft: true, // 复制的排期默认保存为草稿,管理员审核后发布
|
|
publishedAt: null,
|
|
updatedAt: now
|
|
})
|
|
.returning({ id: schedules.id })
|
|
|
|
copiedCount++
|
|
if (inserted[0]) createdScheduleIds.push(inserted[0].id)
|
|
}
|
|
|
|
return { copiedCount, createdScheduleIds, replacedDrafts }
|
|
})
|
|
|
|
console.log(`[Performance] 复制排期耗时: ${Date.now() - startTime}ms`)
|
|
|
|
return {
|
|
success: true,
|
|
fromDate,
|
|
toDate,
|
|
copiedCount: copyResult.copiedCount ?? 0,
|
|
createdScheduleIds: copyResult.createdScheduleIds ?? [],
|
|
replacedDrafts: copyResult.replacedDrafts ?? [],
|
|
draftCount: copyResult.draftCount,
|
|
overwriteDrafts: copyResult.overwriteDrafts
|
|
}
|
|
})
|