398 lines
13 KiB
TypeScript
398 lines
13 KiB
TypeScript
import { db } from '~/drizzle/db'
|
||
import {
|
||
playTimes,
|
||
schedules,
|
||
songs,
|
||
users,
|
||
votes,
|
||
songCollaborators,
|
||
songReplayRequests
|
||
} from '~/drizzle/schema'
|
||
import { and, asc, count, eq, gte, lt, inArray, desc } from 'drizzle-orm'
|
||
|
||
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 query = getQuery(event)
|
||
const { date, playTimeId, includeDrafts = 'true' } = query
|
||
|
||
try {
|
||
// 构建查询条件
|
||
const conditions = []
|
||
|
||
// 日期过滤
|
||
if (date) {
|
||
const targetDate = new Date(date as string)
|
||
const startOfDay = new Date(
|
||
targetDate.getFullYear(),
|
||
targetDate.getMonth(),
|
||
targetDate.getDate()
|
||
)
|
||
const endOfDay = new Date(
|
||
targetDate.getFullYear(),
|
||
targetDate.getMonth(),
|
||
targetDate.getDate() + 1
|
||
)
|
||
|
||
conditions.push(and(gte(schedules.playDate, startOfDay), lt(schedules.playDate, endOfDay)))
|
||
}
|
||
|
||
// 播放时间过滤
|
||
if (playTimeId) {
|
||
conditions.push(eq(schedules.playTimeId, parseInt(playTimeId as string)))
|
||
}
|
||
|
||
// 草稿包含过滤 - 默认包含草稿和已发布的排期
|
||
if (includeDrafts === 'false') {
|
||
conditions.push(eq(schedules.isDraft, false))
|
||
} else if (includeDrafts === 'only') {
|
||
conditions.push(eq(schedules.isDraft, true))
|
||
}
|
||
// 如果 includeDrafts === 'true' 或未指定,则包含两者(无需过滤)
|
||
|
||
const whereCondition = conditions.length > 0 ? and(...conditions) : undefined
|
||
|
||
// 获取完整的排期数据,包括草稿
|
||
const schedulesData = await db
|
||
.select({
|
||
id: schedules.id,
|
||
createdAt: schedules.createdAt,
|
||
updatedAt: schedules.updatedAt,
|
||
playDate: schedules.playDate,
|
||
sequence: schedules.sequence,
|
||
played: schedules.played,
|
||
playTimeId: schedules.playTimeId,
|
||
isDraft: schedules.isDraft,
|
||
publishedAt: schedules.publishedAt,
|
||
songId: schedules.songId,
|
||
songTitle: songs.title,
|
||
songArtist: songs.artist,
|
||
songRequesterId: songs.requesterId,
|
||
songPlayed: songs.played,
|
||
songCover: songs.cover,
|
||
songMusicPlatform: songs.musicPlatform,
|
||
songMusicId: songs.musicId,
|
||
songSemester: songs.semester,
|
||
songCreatedAt: songs.createdAt,
|
||
requesterName: users.name,
|
||
requesterGrade: users.grade,
|
||
requesterClass: users.class,
|
||
playTimeName: playTimes.name,
|
||
playTimeStartTime: playTimes.startTime,
|
||
playTimeEndTime: playTimes.endTime,
|
||
playTimeEnabled: playTimes.enabled
|
||
})
|
||
.from(schedules)
|
||
.innerJoin(songs, eq(schedules.songId, songs.id))
|
||
.leftJoin(users, eq(songs.requesterId, users.id))
|
||
.leftJoin(playTimes, eq(schedules.playTimeId, playTimes.id))
|
||
.where(whereCondition)
|
||
.orderBy(asc(schedules.playDate), asc(schedules.sequence), asc(schedules.createdAt))
|
||
|
||
// 获取所有用户用于姓名消歧
|
||
const allUsers = await db
|
||
.select({
|
||
id: users.id,
|
||
name: users.name,
|
||
grade: users.grade,
|
||
class: users.class
|
||
})
|
||
.from(users)
|
||
|
||
// 创建姓名到用户的映射用于消歧
|
||
const nameToUsers = new Map()
|
||
allUsers.forEach((user) => {
|
||
if (user.name) {
|
||
if (!nameToUsers.has(user.name)) {
|
||
nameToUsers.set(user.name, [])
|
||
}
|
||
nameToUsers.get(user.name).push(user)
|
||
}
|
||
})
|
||
|
||
// 获取每首歌的投票数
|
||
const songIds = schedulesData.map((s) => s.songId)
|
||
const voteCounts =
|
||
songIds.length > 0
|
||
? await Promise.all(
|
||
songIds.map(async (songId) => {
|
||
const voteCountResult = await db
|
||
.select({ count: count() })
|
||
.from(votes)
|
||
.where(eq(votes.songId, songId))
|
||
return { songId, count: voteCountResult[0].count }
|
||
})
|
||
)
|
||
: []
|
||
|
||
const voteCountMap = new Map(voteCounts.map((v) => [v.songId, v.count]))
|
||
|
||
// 获取联合投稿人信息
|
||
const collaboratorsMap = new Map()
|
||
|
||
if (songIds.length > 0) {
|
||
const collaboratorsData = await db
|
||
.select({
|
||
songId: songCollaborators.songId,
|
||
user: {
|
||
id: users.id,
|
||
name: users.name,
|
||
grade: users.grade,
|
||
class: users.class
|
||
}
|
||
})
|
||
.from(songCollaborators)
|
||
.leftJoin(users, eq(songCollaborators.userId, users.id))
|
||
.where(
|
||
and(inArray(songCollaborators.songId, songIds), eq(songCollaborators.status, 'ACCEPTED'))
|
||
)
|
||
|
||
collaboratorsData.forEach((c) => {
|
||
if (!collaboratorsMap.has(c.songId)) {
|
||
collaboratorsMap.set(c.songId, [])
|
||
}
|
||
if (c.user) {
|
||
collaboratorsMap.get(c.songId).push(c.user)
|
||
}
|
||
})
|
||
}
|
||
|
||
// 获取重播申请信息
|
||
const replayRequestCountsMap = new Map()
|
||
const replayRequestersMap = new Map()
|
||
|
||
if (songIds.length > 0) {
|
||
// 获取每首歌的重播申请数量(统计 PENDING 和 FULFILLED 状态)
|
||
const replayCountsData = await db
|
||
.select({
|
||
songId: songReplayRequests.songId,
|
||
count: count(songReplayRequests.id)
|
||
})
|
||
.from(songReplayRequests)
|
||
.where(
|
||
and(
|
||
inArray(songReplayRequests.songId, songIds),
|
||
inArray(songReplayRequests.status, ['PENDING', 'FULFILLED'])
|
||
)
|
||
)
|
||
.groupBy(songReplayRequests.songId)
|
||
|
||
replayCountsData.forEach((r) => {
|
||
replayRequestCountsMap.set(r.songId, r.count)
|
||
})
|
||
|
||
// 获取重播申请人列表(前5个)
|
||
const replayRequestersData = await db
|
||
.select({
|
||
songId: songReplayRequests.songId,
|
||
user: {
|
||
id: users.id,
|
||
name: users.name,
|
||
grade: users.grade,
|
||
class: users.class
|
||
},
|
||
status: songReplayRequests.status,
|
||
createdAt: songReplayRequests.createdAt
|
||
})
|
||
.from(songReplayRequests)
|
||
.innerJoin(users, eq(songReplayRequests.userId, users.id))
|
||
.where(
|
||
and(
|
||
inArray(songReplayRequests.songId, songIds),
|
||
inArray(songReplayRequests.status, ['PENDING', 'FULFILLED'])
|
||
)
|
||
)
|
||
.orderBy(desc(songReplayRequests.createdAt))
|
||
|
||
replayRequestersData.forEach((r) => {
|
||
if (!replayRequestersMap.has(r.songId)) {
|
||
replayRequestersMap.set(r.songId, [])
|
||
}
|
||
// 只保留前5个
|
||
if (replayRequestersMap.get(r.songId).length < 5) {
|
||
replayRequestersMap.get(r.songId).push({
|
||
id: r.user.id,
|
||
name: r.user.name || '未知用户',
|
||
grade: r.user.grade,
|
||
class: r.user.class,
|
||
status: r.status
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
// 格式化响应数据
|
||
const formattedSchedules = schedulesData.map((schedule) => {
|
||
const dateOnly = schedule.playDate
|
||
|
||
// 处理投稿人显示:班级 + 姓名
|
||
let requesterName = schedule.requesterName || 'Unknown User'
|
||
const requesterClassParts: string[] = []
|
||
if (schedule.requesterGrade) requesterClassParts.push(String(schedule.requesterGrade))
|
||
if (schedule.requesterClass) requesterClassParts.push(String(schedule.requesterClass))
|
||
const requesterClassInfo = requesterClassParts.join(' ').trim()
|
||
if (requesterClassInfo && requesterName) {
|
||
requesterName = `${requesterClassInfo} ${requesterName}`
|
||
} else if (requesterClassInfo) {
|
||
requesterName = requesterClassInfo
|
||
}
|
||
|
||
const formatClassInfo = (userObj: any) => {
|
||
if (!userObj) return ''
|
||
const parts: string[] = []
|
||
if (userObj.grade) parts.push(String(userObj.grade))
|
||
if (userObj.class) parts.push(String(userObj.class))
|
||
return parts.join(' ').trim()
|
||
}
|
||
|
||
const formatDisplayName = (userObj: any) => {
|
||
if (!userObj) return '未知用户'
|
||
const name = userObj.name ? String(userObj.name).trim() : ''
|
||
const classInfo = formatClassInfo(userObj)
|
||
if (!name) return classInfo || '未知用户'
|
||
return classInfo ? `${classInfo} ${name}` : name
|
||
}
|
||
|
||
const collaborators = collaboratorsMap.get(schedule.songId) || []
|
||
const formattedCollaborators = collaborators.map((c: any) => ({
|
||
id: c.id,
|
||
name: c.name,
|
||
displayName: formatDisplayName(c),
|
||
grade: c.grade,
|
||
class: c.class
|
||
}))
|
||
|
||
// 获取重播申请信息
|
||
const replayRequestCount = replayRequestCountsMap.get(schedule.songId) || 0
|
||
const replayRequesters = replayRequestersMap.get(schedule.songId) || []
|
||
const formattedReplayRequesters = replayRequesters.map((r: any) => ({
|
||
id: r.id,
|
||
name: r.name,
|
||
displayName: formatDisplayName(r),
|
||
grade: r.grade,
|
||
class: r.class,
|
||
status: r.status
|
||
}))
|
||
|
||
// 判断是否为重播:有重播申请(PENDING 或 FULFILLED)
|
||
const isReplaySong = replayRequestCount > 0
|
||
|
||
return {
|
||
id: schedule.id,
|
||
createdAt: schedule.createdAt,
|
||
updatedAt: schedule.updatedAt,
|
||
playDate: dateOnly.toISOString().split('T')[0],
|
||
sequence: schedule.sequence || 1,
|
||
played: schedule.played || false,
|
||
playTimeId: schedule.playTimeId,
|
||
// 草稿状态信息
|
||
isDraft: schedule.isDraft || false,
|
||
publishedAt: schedule.publishedAt,
|
||
status: schedule.isDraft ? 'draft' : 'published',
|
||
playTime: schedule.playTimeName
|
||
? {
|
||
id: schedule.playTimeId,
|
||
name: schedule.playTimeName,
|
||
startTime: schedule.playTimeStartTime,
|
||
endTime: schedule.playTimeEndTime,
|
||
enabled: schedule.playTimeEnabled
|
||
}
|
||
: null,
|
||
song: {
|
||
id: schedule.songId,
|
||
title: schedule.songTitle,
|
||
artist: schedule.songArtist,
|
||
requester: requesterName,
|
||
requesterId: schedule.songRequesterId,
|
||
requesterGrade: schedule.requesterGrade || null,
|
||
requesterClass: schedule.requesterClass || null,
|
||
collaborators: formattedCollaborators,
|
||
voteCount: voteCountMap.get(schedule.songId) || 0,
|
||
played: schedule.songPlayed || false,
|
||
cover: schedule.songCover || null,
|
||
musicPlatform: schedule.songMusicPlatform || null,
|
||
musicId: schedule.songMusicId || null,
|
||
semester: schedule.songSemester || null,
|
||
createdAt: schedule.songCreatedAt,
|
||
// 重播申请信息
|
||
replayRequestCount: isReplaySong ? replayRequestCount : 0,
|
||
replayRequesters: isReplaySong ? formattedReplayRequesters : [],
|
||
isReplay: isReplaySong // 只有已播放过且有重播申请的才标记为重播
|
||
}
|
||
}
|
||
})
|
||
|
||
// 按日期分组以便更好地组织
|
||
const groupedByDate = {}
|
||
formattedSchedules.forEach((schedule) => {
|
||
const date = schedule.playDate
|
||
if (!groupedByDate[date]) {
|
||
groupedByDate[date] = {
|
||
date,
|
||
schedules: [],
|
||
totalCount: 0,
|
||
draftCount: 0,
|
||
publishedCount: 0
|
||
}
|
||
}
|
||
|
||
groupedByDate[date].schedules.push(schedule)
|
||
groupedByDate[date].totalCount++
|
||
|
||
if (schedule.isDraft) {
|
||
groupedByDate[date].draftCount++
|
||
} else {
|
||
groupedByDate[date].publishedCount++
|
||
}
|
||
})
|
||
|
||
// 转换为数组并按日期排序
|
||
const dateGroups = Object.values(groupedByDate).sort(
|
||
(a: any, b: any) => new Date(a.date).getTime() - new Date(b.date).getTime()
|
||
)
|
||
|
||
// 汇总统计
|
||
const summary = {
|
||
totalSchedules: formattedSchedules.length,
|
||
draftCount: formattedSchedules.filter((s) => s.isDraft).length,
|
||
publishedCount: formattedSchedules.filter((s) => !s.isDraft).length,
|
||
dateRange: {
|
||
startDate: dateGroups.length > 0 ? dateGroups[0].date : null,
|
||
endDate: dateGroups.length > 0 ? dateGroups[dateGroups.length - 1].date : null
|
||
},
|
||
playTimeFilter: playTimeId ? parseInt(playTimeId as string) : null,
|
||
includeDrafts: includeDrafts as string
|
||
}
|
||
|
||
return {
|
||
success: true,
|
||
data: {
|
||
summary,
|
||
schedules: formattedSchedules,
|
||
groupedByDate: dateGroups
|
||
}
|
||
}
|
||
} catch (error: any) {
|
||
console.error('获取完整排期数据失败:', error)
|
||
throw createError({
|
||
statusCode: 500,
|
||
message: error.message || '获取完整排期数据失败'
|
||
})
|
||
}
|
||
})
|