feat: align with upstream 1.5.9.9
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import { db } from '~/drizzle/db'
|
||||
import {
|
||||
schedules,
|
||||
songs,
|
||||
votes,
|
||||
songCollaborators,
|
||||
collaborationLogs,
|
||||
requestTimes
|
||||
} from '~/drizzle/schema'
|
||||
import { and, eq, sql } from 'drizzle-orm'
|
||||
import {
|
||||
getTimeRange,
|
||||
isCardCodeLimitBypassActive,
|
||||
type LimitType
|
||||
} from '~~/server/utils/submissionLimit'
|
||||
import { releaseCardCodeAfterSongWithdrawal } from '~~/server/services/cardCodeLifecycleService'
|
||||
import { getSystemSettingsCached } from '~~/server/utils/system-settings-helper'
|
||||
import { createApiError } from '~~/server/utils/apiError'
|
||||
import { getClientIP } from '~~/server/utils/ip-utils'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户认证
|
||||
const user = event.context.user
|
||||
|
||||
if (!user) {
|
||||
throw createApiError(401, 'SONG_LOGIN_REQUIRED_WITHDRAW', '需要登录才能撤回歌曲')
|
||||
}
|
||||
|
||||
const body = await readBody(event)
|
||||
|
||||
if (!body.songId) {
|
||||
throw createApiError(400, 'SONG_ID_REQUIRED', '歌曲ID不能为空')
|
||||
}
|
||||
|
||||
// 查找歌曲
|
||||
const songResult = await db.select().from(songs).where(eq(songs.id, body.songId)).limit(1)
|
||||
const song = songResult[0]
|
||||
|
||||
if (!song) {
|
||||
throw createApiError(404, 'SONG_NOT_FOUND', '歌曲不存在')
|
||||
}
|
||||
|
||||
// 检查是否是用户自己的投稿或联合投稿
|
||||
const isRequester = song.requesterId === user.id
|
||||
let isCollaborator = false
|
||||
let collaboratorRecord = null
|
||||
|
||||
if (!isRequester) {
|
||||
const collabResult = await db
|
||||
.select()
|
||||
.from(songCollaborators)
|
||||
.where(and(eq(songCollaborators.songId, song.id), eq(songCollaborators.userId, user.id)))
|
||||
.limit(1)
|
||||
|
||||
if (collabResult.length > 0) {
|
||||
isCollaborator = true
|
||||
collaboratorRecord = collabResult[0]
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!isRequester &&
|
||||
!isCollaborator &&
|
||||
!['SONG_ADMIN', 'ADMIN', 'SUPER_ADMIN'].includes(user.role)
|
||||
) {
|
||||
throw createApiError(403, 'SONG_WITHDRAW_OWN_ONLY', '只能撤回自己的投稿或退出联合投稿')
|
||||
}
|
||||
|
||||
// 检查歌曲是否已经播放
|
||||
if (song.played) {
|
||||
throw createApiError(400, 'SONG_PLAYED_CANNOT_WITHDRAW', '已播放的歌曲不能撤回')
|
||||
}
|
||||
|
||||
// 检查歌曲是否已排期(只检查已发布的排期,草稿不算)
|
||||
const scheduleResult = await db
|
||||
.select()
|
||||
.from(schedules)
|
||||
.where(and(eq(schedules.songId, body.songId), eq(schedules.isDraft, false)))
|
||||
.limit(1)
|
||||
const schedule = scheduleResult[0]
|
||||
|
||||
if (schedule) {
|
||||
throw createApiError(400, 'SONG_SCHEDULED_CANNOT_WITHDRAW', '已排期的歌曲不能撤回')
|
||||
}
|
||||
|
||||
// 如果是联合投稿人撤回(退出)
|
||||
if (
|
||||
isCollaborator &&
|
||||
!isRequester &&
|
||||
!['SONG_ADMIN', 'ADMIN', 'SUPER_ADMIN'].includes(user.role)
|
||||
) {
|
||||
await db.delete(songCollaborators).where(eq(songCollaborators.id, collaboratorRecord.id))
|
||||
|
||||
// 记录日志
|
||||
await db.insert(collaborationLogs).values({
|
||||
collaboratorId: collaboratorRecord.id,
|
||||
action: 'LEAVE',
|
||||
operatorId: user.id,
|
||||
ipAddress: getClientIP(event)
|
||||
})
|
||||
|
||||
return {
|
||||
message: '已成功退出联合投稿',
|
||||
songId: body.songId,
|
||||
action: 'leave'
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是主投稿人撤回(删除歌曲)
|
||||
// 获取系统设置以检查限制类型
|
||||
const settings = await getSystemSettingsCached()
|
||||
|
||||
// 检查撤销的歌曲是否在当前限制期间内(用于返还配额)
|
||||
let canReturnQuota = false
|
||||
|
||||
const cardCodeDidNotUseOrdinaryQuota = isCardCodeLimitBypassActive(settings) && !!song.cardCodeId
|
||||
|
||||
if (settings?.enableSubmissionLimit && !cardCodeDidNotUseOrdinaryQuota) {
|
||||
let activeLimit: { type: LimitType; value: number | null } | null = null
|
||||
if (settings.dailySubmissionLimit !== null && settings.dailySubmissionLimit !== undefined) {
|
||||
activeLimit = { type: 'daily', value: settings.dailySubmissionLimit }
|
||||
} else if (
|
||||
settings.weeklySubmissionLimit !== null &&
|
||||
settings.weeklySubmissionLimit !== undefined
|
||||
) {
|
||||
activeLimit = { type: 'weekly', value: settings.weeklySubmissionLimit }
|
||||
} else if (
|
||||
settings.monthlySubmissionLimit !== null &&
|
||||
settings.monthlySubmissionLimit !== undefined
|
||||
) {
|
||||
activeLimit = { type: 'monthly', value: settings.monthlySubmissionLimit }
|
||||
}
|
||||
|
||||
if (activeLimit?.value && activeLimit.value > 0) {
|
||||
const { start, end } = getTimeRange(activeLimit.type)
|
||||
canReturnQuota = song.createdAt >= start && song.createdAt <= end
|
||||
}
|
||||
}
|
||||
|
||||
// 投稿关联数据一起进入事务,避免撤回失败时只删掉一部分数据。
|
||||
try {
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.delete(songCollaborators).where(eq(songCollaborators.songId, body.songId))
|
||||
await tx.delete(votes).where(eq(votes.songId, body.songId))
|
||||
|
||||
if (song.hitRequestId) {
|
||||
await tx
|
||||
.update(requestTimes)
|
||||
.set({
|
||||
accepted: sql`GREATEST(0, accepted - 1)`
|
||||
})
|
||||
.where(eq(requestTimes.id, song.hitRequestId))
|
||||
console.log(`已减少投稿时段 ${song.hitRequestId} 的接纳数量`)
|
||||
}
|
||||
|
||||
if (song.cardCodeId) {
|
||||
const releaseResult = await releaseCardCodeAfterSongWithdrawal(tx, {
|
||||
songId: song.id,
|
||||
cardCodeId: song.cardCodeId,
|
||||
operatorId: user.id
|
||||
})
|
||||
if (
|
||||
!releaseResult.changed &&
|
||||
['CONCURRENT_CHANGE', 'MISSING_CARD_CODE'].includes(String(releaseResult.reason || ''))
|
||||
) {
|
||||
throw createApiError(409, 'SONG_CARD_RELEASE_FAILED', '点歌券释放失败,撤回已终止')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除歌曲(在事务内)
|
||||
await tx.delete(songs).where(eq(songs.id, body.songId))
|
||||
})
|
||||
} catch (txErr: any) {
|
||||
console.error('撤回事务失败:', txErr)
|
||||
throw createError({
|
||||
statusCode: txErr.statusCode || 500,
|
||||
message: txErr.message || '撤回歌曲失败,请稍后重试'
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
message: canReturnQuota ? '歌曲已成功撤回,投稿配额已返还' : '歌曲已成功撤回',
|
||||
songId: body.songId,
|
||||
quotaReturned: canReturnQuota
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user