Files
BYW-VoiceHub/server/api/songs/[id]/update.put.ts
T
Henry_Du 4ce4649aaf
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s
初次提交
2026-09-06 08:19:02 +08:00

272 lines
8.1 KiB
TypeScript

import { db } from '~/drizzle/db'
import { songs, users, songCollaborators, collaborationLogs } from '~/drizzle/schema'
import { eq, or } from 'drizzle-orm'
import { cacheService } from '~~/server/services/cacheService'
import { createSubmissionNoteClearedNotification } from '~~/server/services/notificationService'
export default defineEventHandler(async (event) => {
try {
// 验证请求方法
if (event.node.req.method !== 'PUT') {
throw createError({
statusCode: 405,
statusMessage: 'Method Not Allowed'
})
}
// 获取已验证的用户信息(由中间件提供)
const user = event.context.user
if (!user) {
throw createError({
statusCode: 401,
statusMessage: '未授权访问'
})
}
// 检查权限
if (!['ADMIN', 'SONG_ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
throw createError({
statusCode: 403,
statusMessage: '权限不足'
})
}
// 获取歌曲ID
const songId = parseInt(getRouterParam(event, 'id'))
if (!songId) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid song ID'
})
}
// 获取请求体
const body = await readBody(event)
const { title, artist, requester, semester, musicPlatform, musicId, cover, playUrl } = body
const ipAddress =
(event.node.req.headers['x-forwarded-for'] as string) || event.node.req.socket.remoteAddress
// 验证必填字段
if (!title || !artist) {
throw createError({
statusCode: 400,
statusMessage: 'Title and artist are required'
})
}
const existingSongResult = await db.select().from(songs).where(eq(songs.id, songId)).limit(1)
const existingSong = existingSongResult[0]
if (!existingSong) {
throw createError({
statusCode: 404,
statusMessage: '歌曲不存在'
})
}
const updateData = {
title: title.trim(),
artist: artist.trim(),
semester: semester || null,
musicPlatform: musicPlatform || null,
musicId: musicId || null,
cover: cover || null,
playUrl: playUrl || null
}
// 处理投稿人
if ('requester' in body) {
const requester = body.requester
// 只有在传递有效投稿人信息时才更新
if (requester && requester !== '' && requester !== 0) {
// 查找投稿人用户
const conditions = []
if (typeof requester === 'number') {
conditions.push(eq(users.id, requester))
}
if (typeof requester === 'string') {
conditions.push(eq(users.username, requester))
conditions.push(eq(users.name, requester))
}
const requesterUser = await db
.select()
.from(users)
.where(or(...conditions))
.limit(1)
if (requesterUser.length === 0) {
throw createError({
statusCode: 404,
statusMessage: '投稿人用户不存在'
})
}
// 设置投稿人
updateData.requesterId = requesterUser[0].id
}
// 如果投稿人为空值,则跳过处理,保持原有投稿人不变
}
const shouldClearSubmissionNote = body.clearSubmissionNote === true
const submissionNoteClearReason =
typeof body.submissionNoteClearReason === 'string' ? body.submissionNoteClearReason.trim() : ''
const shouldNotifySubmissionNoteClear = body.notifyOnSubmissionNoteClear === true
if (shouldClearSubmissionNote) {
updateData.submissionNote = null
} else if ('submissionNote' in body) {
updateData.submissionNote =
typeof body.submissionNote === 'string' && body.submissionNote.trim()
? body.submissionNote.trim()
: null
}
const currentRequesterId = updateData.requesterId || existingSong.requesterId
// 更新歌曲
const updatedSongResult = await db
.update(songs)
.set(updateData)
.where(eq(songs.id, songId))
.returning()
// 处理联合投稿人
if ('collaborators' in body && Array.isArray(body.collaborators)) {
// 获取现有联合投稿人
const existingCollaborators = await db
.select()
.from(songCollaborators)
.where(eq(songCollaborators.songId, songId))
const existingCollaboratorUserIds = existingCollaborators.map((c) => c.userId)
const newCollaboratorIds = [
...new Set(
body.collaborators
.map((id: number | string) => Number(id))
.filter((id: number) => Number.isInteger(id) && id > 0 && id !== currentRequesterId)
)
]
// 需要添加的
const toAdd = newCollaboratorIds.filter(
(id: number) => !existingCollaboratorUserIds.includes(id)
)
// 需要删除的
const toDelete = existingCollaborators.filter((c) => !newCollaboratorIds.includes(c.userId))
// 执行添加
for (const userId of toAdd) {
// 检查用户是否存在
const userExists = await db.select().from(users).where(eq(users.id, userId)).limit(1)
if (userExists.length > 0) {
const [newCollab] = await db
.insert(songCollaborators)
.values({
songId: songId,
userId: userId,
status: 'ACCEPTED'
})
.returning()
// 记录日志
await db.insert(collaborationLogs).values({
collaboratorId: newCollab.id,
action: 'ADMIN_ADD',
operatorId: user.id,
ipAddress
})
}
}
// 执行删除
for (const collab of toDelete) {
await db.delete(songCollaborators).where(eq(songCollaborators.id, collab.id))
// 记录日志
await db.insert(collaborationLogs).values({
collaboratorId: collab.id,
action: 'ADMIN_REMOVE',
operatorId: user.id,
ipAddress
})
}
}
if (shouldClearSubmissionNote && existingSong.submissionNote && shouldNotifySubmissionNoteClear) {
const latestCollaborators = await db
.select({
userId: songCollaborators.userId
})
.from(songCollaborators)
.where(eq(songCollaborators.songId, songId))
const notifyUserIds = [
currentRequesterId,
...latestCollaborators.map((collaborator) => collaborator.userId)
].filter((userId): userId is number => Number.isInteger(userId) && userId > 0)
createSubmissionNoteClearedNotification(
notifyUserIds,
{ title: updatedSongResult[0].title, artist: updatedSongResult[0].artist },
submissionNoteClearReason,
ipAddress
).catch((error) => {
console.error('发送歌曲备注清空通知失败:', error)
})
}
// 获取完整的歌曲信息(包含投稿人)
const updatedSong = await db
.select({
id: songs.id,
title: songs.title,
artist: songs.artist,
semester: songs.semester,
musicPlatform: songs.musicPlatform,
musicId: songs.musicId,
cover: songs.cover,
playUrl: songs.playUrl,
submissionNote: songs.submissionNote,
submissionNotePublic: songs.submissionNotePublic,
requesterId: songs.requesterId,
createdAt: songs.createdAt,
updatedAt: songs.updatedAt,
requester: {
id: users.id,
username: users.username,
name: users.name
}
})
.from(songs)
.leftJoin(users, eq(songs.requesterId, users.id))
.where(eq(songs.id, songId))
.limit(1)
// 清除歌曲相关缓存
try {
await cacheService.clearSongsCache()
console.log('[Cache] 歌曲缓存已清除(更新歌曲)')
} catch (cacheError) {
console.error('[Cache] 清除歌曲缓存失败:', cacheError)
}
return {
success: true,
song: updatedSong[0]
}
} catch (error) {
console.error('Update song error:', error)
if (error.statusCode) {
throw error
}
throw createError({
statusCode: 500,
statusMessage: error.message || 'Internal server error'
})
}
})