225 lines
7.2 KiB
TypeScript
225 lines
7.2 KiB
TypeScript
import { db } from '~/drizzle/db'
|
|
import { schedules, songs } from '~/drizzle/schema'
|
|
import { and, desc, eq, gte, lte, ne } from 'drizzle-orm'
|
|
import { createSongSelectedNotification, createReplaySongSelectedNotification } from '../../services/notificationService'
|
|
import { redeemCardCodeForSchedule } from '~~/server/services/cardCodeLifecycleService'
|
|
import { fulfillReplayRequestsForSchedule } from '~~/server/utils/scheduleReplayBinding'
|
|
import { getServerDate } from '~~/server/utils/serverTime'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
// 检查用户认证和权限
|
|
const user = event.context.user
|
|
|
|
if (!user) {
|
|
throw createError({
|
|
statusCode: 401,
|
|
message: '未授权访问'
|
|
})
|
|
}
|
|
|
|
if (!['SONG_ADMIN', 'ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
message: '只有歌曲管理员及以上权限才能创建排期'
|
|
})
|
|
}
|
|
|
|
const body = await readBody(event)
|
|
|
|
if (!body.songId || !body.playDate) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: '歌曲ID和播放日期不能为空'
|
|
})
|
|
}
|
|
|
|
// 检查是否为草稿模式(默认直接发布)
|
|
const isDraft = body.isDraft === true
|
|
|
|
// 前端显式选择的重播申请,发布时优先绑定
|
|
const preferredReplayRequestId =
|
|
Number.isInteger(Number(body.replayRequestId)) && Number(body.replayRequestId) > 0
|
|
? Number(body.replayRequestId)
|
|
: null
|
|
|
|
try {
|
|
// 检查歌曲是否存在
|
|
const songResult = await db.select().from(songs).where(eq(songs.id, body.songId)).limit(1)
|
|
|
|
const song = songResult[0]
|
|
|
|
if (!song) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
message: '歌曲不存在'
|
|
})
|
|
}
|
|
|
|
const transactionResult = await db.transaction(async (tx) => {
|
|
// 获取序号,如果未提供则查找当天最大序号+1
|
|
let sequence = body.sequence || 1
|
|
|
|
if (!body.sequence) {
|
|
// 解析输入的日期字符串
|
|
const inputDateStr =
|
|
typeof body.playDate === 'string' ? body.playDate : body.playDate.toISOString()
|
|
|
|
// 创建当天的开始和结束时间
|
|
const startOfDay = new Date(inputDateStr + 'T00:00:00.000Z')
|
|
const endOfDay = new Date(inputDateStr + 'T23:59:59.999Z')
|
|
|
|
console.log('查询当天排期范围:', {
|
|
输入日期: body.playDate,
|
|
开始时间: startOfDay.toISOString(),
|
|
结束时间: endOfDay.toISOString()
|
|
})
|
|
|
|
const sameDaySchedules = await tx
|
|
.select()
|
|
.from(schedules)
|
|
.where(and(gte(schedules.playDate, startOfDay), lte(schedules.playDate, endOfDay)))
|
|
.orderBy(desc(schedules.sequence))
|
|
.limit(1)
|
|
|
|
const latestSchedule = sameDaySchedules[0]
|
|
if (latestSchedule) sequence = (latestSchedule.sequence || 0) + 1
|
|
}
|
|
|
|
// 解析输入的日期字符串,确保日期正确
|
|
const inputDateStr =
|
|
typeof body.playDate === 'string' ? body.playDate : body.playDate.toISOString()
|
|
|
|
// 直接使用输入的日期字符串,添加时间部分以避免时区问题
|
|
const playDate = new Date(inputDateStr + 'T00:00:00.000Z')
|
|
|
|
// 创建排期
|
|
const scheduleResult = await tx
|
|
.insert(schedules)
|
|
.values({
|
|
songId: body.songId,
|
|
playDate: playDate,
|
|
sequence: sequence,
|
|
playTimeId: body.playTimeId || null,
|
|
// 草稿支持:根据参数决定是否为草稿
|
|
isDraft: isDraft,
|
|
publishedAt: isDraft ? null : getServerDate()
|
|
})
|
|
.returning()
|
|
|
|
const createdSchedule = scheduleResult[0]
|
|
if (!createdSchedule) {
|
|
throw createError({ statusCode: 500, message: '创建排期失败' })
|
|
}
|
|
|
|
const schedule = {
|
|
...createdSchedule,
|
|
song: {
|
|
id: song.id,
|
|
title: song.title,
|
|
artist: song.artist,
|
|
requesterId: song.requesterId,
|
|
cardCodeId: song.cardCodeId
|
|
}
|
|
}
|
|
|
|
let shouldNotify = false
|
|
let replayRequesterIds: number[] = []
|
|
|
|
// 只有在非草稿模式下才更新重播状态
|
|
if (!isDraft) {
|
|
// 检查该歌曲是否已经有其他已发布的排期
|
|
const existingPublished = await tx
|
|
.select({ id: schedules.id })
|
|
.from(schedules)
|
|
.where(
|
|
and(
|
|
eq(schedules.songId, schedule.song.id),
|
|
eq(schedules.isDraft, false),
|
|
ne(schedules.id, createdSchedule.id)
|
|
)
|
|
)
|
|
.limit(1)
|
|
|
|
// 如果之前没有正式发布过,才发送通知(首次排期通知)
|
|
shouldNotify = existingPublished.length === 0
|
|
|
|
const replayBinding = await fulfillReplayRequestsForSchedule({
|
|
tx,
|
|
songId: schedule.song.id,
|
|
scheduleId: createdSchedule.id,
|
|
at: createdSchedule.publishedAt || getServerDate(),
|
|
preferredRequestId: preferredReplayRequestId || undefined
|
|
})
|
|
|
|
if (replayBinding) {
|
|
replayRequesterIds = replayBinding.replayRequesterIds
|
|
}
|
|
|
|
if (existingPublished.length > 0) {
|
|
console.log(`歌曲 ${schedule.song.id} 已有其他正式排期,不再重复发送通知`)
|
|
}
|
|
|
|
await redeemCardCodeForSchedule(tx, {
|
|
songId: schedule.song.id,
|
|
cardCodeId: schedule.song.cardCodeId,
|
|
operatorId: user.id,
|
|
at: createdSchedule.publishedAt || getServerDate()
|
|
})
|
|
}
|
|
|
|
return { schedule, shouldNotify, replayRequesterIds }
|
|
})
|
|
|
|
let notificationSent = true
|
|
if (transactionResult.shouldNotify) {
|
|
try {
|
|
await createSongSelectedNotification(song.requesterId, song.id, {
|
|
title: song.title,
|
|
artist: song.artist,
|
|
playDate: transactionResult.schedule.playDate
|
|
})
|
|
} catch (notificationError) {
|
|
notificationSent = false
|
|
console.error('排期已创建,但发送歌曲入选通知失败:', notificationError)
|
|
}
|
|
}
|
|
|
|
// 发送重播申请已安排通知
|
|
if (!isDraft && transactionResult.replayRequesterIds.length > 0) {
|
|
for (const replayUserId of transactionResult.replayRequesterIds) {
|
|
try {
|
|
await createReplaySongSelectedNotification(replayUserId, song.id, {
|
|
title: song.title,
|
|
artist: song.artist,
|
|
playDate: transactionResult.schedule.playDate
|
|
}, transactionResult.schedule.id)
|
|
} catch (error) {
|
|
console.error(`发送重播安排通知给用户 ${replayUserId} 失败:`, error)
|
|
}
|
|
}
|
|
}
|
|
|
|
const schedule = transactionResult.schedule
|
|
|
|
return {
|
|
...schedule,
|
|
isDraft: isDraft,
|
|
publishedAt: isDraft ? null : schedule.publishedAt,
|
|
message: isDraft
|
|
? '排期草稿保存成功'
|
|
: transactionResult.shouldNotify
|
|
? notificationSent
|
|
? '排期创建成功,通知已发送'
|
|
: '排期创建成功,但通知发送失败'
|
|
: '排期创建成功'
|
|
}
|
|
} catch (error: any) {
|
|
console.error('创建排期失败:', error)
|
|
if (error.statusCode) throw error
|
|
throw createError({
|
|
statusCode: 500,
|
|
message: error.message || '创建排期失败'
|
|
})
|
|
}
|
|
})
|