初次提交
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s

This commit is contained in:
2026-09-06 08:19:02 +08:00
commit 4ce4649aaf
1061 changed files with 337258 additions and 0 deletions
@@ -0,0 +1,139 @@
import { db, songs, songReplayRequests, users, semesters } from '~/drizzle/db'
import { desc, eq, sql, and, or } from 'drizzle-orm'
export default defineEventHandler(async (event) => {
// 1. 检查权限
const user = event.context.user
if (!user || !['ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
throw createError({ statusCode: 403, message: '权限不足' })
}
// 获取查询参数
const query = getQuery(event)
const status = (query.status as string) || 'PENDING' // PENDING, FULFILLED, REJECTED, ALL
const semesterName = query.semester as string
// 构建查询条件
const conditions = []
if (status !== 'ALL') {
conditions.push(eq(songReplayRequests.status, status as any))
}
if (semesterName) {
conditions.push(eq(songs.semester, semesterName))
}
// 2. 查询
// 获取歌曲信息及重播申请数量,同时关联用户信息
const requestsQuery = db
.select({
song: songs,
requester: {
id: users.id,
name: users.name,
grade: users.grade,
class: users.class
},
requestCount: sql<number>`count(${songReplayRequests.id})`
.mapWith(Number)
.as('request_count'),
lastRequestedAt: sql<string>`max(${songReplayRequests.createdAt})`.as('last_requested_at')
})
.from(songs)
.innerJoin(songReplayRequests, eq(songs.id, songReplayRequests.songId))
.leftJoin(users, eq(songs.requesterId, users.id))
.groupBy(songs.id, users.id)
.orderBy(desc(sql`request_count`))
if (conditions.length > 0) {
requestsQuery.where(and(...conditions))
}
const requests = await requestsQuery
// 获取所有用户姓名,用于处理同名情况
const allUsers = await db
.select({
name: users.name,
grade: users.grade
})
.from(users)
const nameToUsers = new Map()
allUsers.forEach((u) => {
if (u.name) {
if (!nameToUsers.has(u.name)) {
nameToUsers.set(u.name, [])
}
nameToUsers.get(u.name).push(u)
}
})
const formatDisplayName = (userObj: any) => {
if (!userObj || !userObj.name) return '未知用户'
let displayName = userObj.name
const sameNameUsers = nameToUsers.get(displayName)
if (sameNameUsers && sameNameUsers.length > 1) {
if (userObj.grade) {
displayName = `${displayName}(${userObj.grade})`
}
}
return displayName
}
// 获取详细的重播申请人信息
const requestDetailsQuery = db
.select({
songId: songReplayRequests.songId,
user: {
name: users.name,
grade: users.grade,
class: users.class
},
createdAt: songReplayRequests.createdAt,
status: songReplayRequests.status
})
.from(songReplayRequests)
.innerJoin(users, eq(songReplayRequests.userId, users.id))
.innerJoin(songs, eq(songReplayRequests.songId, songs.id))
.orderBy(desc(songReplayRequests.createdAt))
const subConditions = []
if (status !== 'ALL') {
subConditions.push(eq(songReplayRequests.status, status as any))
}
if (semesterName) {
subConditions.push(eq(songs.semester, semesterName))
}
if (subConditions.length > 0) {
requestDetailsQuery.where(and(...subConditions))
}
const requestDetails = await requestDetailsQuery
const detailsMap = new Map()
requestDetails.forEach((d) => {
if (!detailsMap.has(d.songId)) detailsMap.set(d.songId, [])
detailsMap.get(d.songId).push({
name: formatDisplayName(d.user),
grade: d.user.grade,
class: d.user.class,
createdAt: d.createdAt,
status: d.status
})
})
return requests.map((item) => ({
...item.song,
requester: formatDisplayName(item.requester),
requesterGrade: item.requester?.grade,
requesterClass: item.requester?.class,
voteCount: item.requestCount,
requestCount: item.requestCount,
lastRequestedAt: item.lastRequestedAt,
requestDetails: detailsMap.get(item.song.id) || []
}))
})
@@ -0,0 +1,56 @@
import { db, songReplayRequests, songs } from '~/drizzle/db'
import { eq, and } from 'drizzle-orm'
import { createReplayRequestRejectedNotification } from '~~/server/services/notificationService'
export default defineEventHandler(async (event) => {
// 检查管理员权限
const user = event.context.user
if (!user || !['ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
throw createError({ statusCode: 403, message: '权限不足' })
}
const body = await readBody(event)
const { songId } = body
if (!songId) {
throw createError({ statusCode: 400, message: '缺少参数' })
}
// 获取歌曲信息用于通知
const song = await db
.select()
.from(songs)
.where(eq(songs.id, songId))
.limit(1)
.then((r) => r[0])
if (!song) {
throw createError({ statusCode: 404, message: '歌曲不存在' })
}
// 查找所有待处理的申请
const pendingRequests = await db
.select()
.from(songReplayRequests)
.where(and(eq(songReplayRequests.songId, songId), eq(songReplayRequests.status, 'PENDING')))
// 更新状态
await db
.update(songReplayRequests)
.set({ status: 'REJECTED' })
.where(and(eq(songReplayRequests.songId, songId), eq(songReplayRequests.status, 'PENDING')))
// 发送通知
const ipAddress = getRequestIP(event, { xForwardedFor: true })
for (const req of pendingRequests) {
await createReplayRequestRejectedNotification(
req.userId,
{
title: song.title,
artist: song.artist
},
ipAddress
)
}
return { success: true }
})