feat(songs): 歌曲管理支持批量驳回、投稿留言自动审查与排期范围可见性过滤
- 新增 /api/admin/songs/batch-reject.post 批量驳回接口(事务清排期/投票/扣接纳数/黑名单去重/异步通知) - requireSongAdmin 三角色校验 + createApiError 统一错误包装 - scheduleDaysBefore/After 四字段排期可见范围限制(开放 API 与公开排期接口三处缓存路径 + DB 条件过滤) - submissionNoteAutoModeration 基于黑名单 KEYWORD/LANGUAGE/GENRE 自动判定留言 publicStatus,命中自动关闭 submissionNotePublic - 更新 songs/request.post 与 songs/[id]/update.put 接入自动审查逻辑
This commit is contained in:
@@ -4,14 +4,16 @@ import {
|
||||
playTimes,
|
||||
requestTimes,
|
||||
semesters,
|
||||
songBlacklists,
|
||||
songCollaborators,
|
||||
songs,
|
||||
systemSettings
|
||||
} from '~/drizzle/db'
|
||||
import { and, eq, gt, gte, lt, lte, sql } from 'drizzle-orm'
|
||||
import { and, eq, gt, gte, inArray, lt, lte, sql } from 'drizzle-orm'
|
||||
import { createCollaborationInvitationNotification } from '~~/server/services/notificationService'
|
||||
import { isLimitReached } from '~~/server/utils/submissionLimit'
|
||||
import { getBeijingTimeISOString } from '~/utils/timeUtils'
|
||||
import { moderateSubmissionNote } from '~~/server/utils/submissionNoteAutoModeration'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户认证
|
||||
@@ -132,6 +134,18 @@ export default defineEventHandler(async (event) => {
|
||||
const systemSettingsData = systemSettingsResult[0]
|
||||
const isAdmin = user.role === 'SUPER_ADMIN' || user.role === 'ADMIN'
|
||||
|
||||
// 加载自动审查需要的黑名单(KEYWORD / LANGUAGE / GENRE)
|
||||
let moderationBlacklists: any[] = []
|
||||
try {
|
||||
moderationBlacklists = await db
|
||||
.select()
|
||||
.from(songBlacklists)
|
||||
.where(inArray(songBlacklists.type, ['KEYWORD', 'LANGUAGE', 'GENRE']))
|
||||
} catch (err) {
|
||||
console.warn('[Request API] 加载自动审查黑名单失败,将跳过自动审查:', err)
|
||||
moderationBlacklists = []
|
||||
}
|
||||
|
||||
// 1. 检查强制关闭
|
||||
if (systemSettingsData?.forceBlockAllRequests && !isAdmin) {
|
||||
throw createError({
|
||||
@@ -243,8 +257,34 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
const submissionNote =
|
||||
systemSettingsData?.enableSubmissionRemarks && rawSubmissionNote ? rawSubmissionNote : null
|
||||
const submissionNotePublic =
|
||||
submissionNote !== null ? body.submissionNotePublic !== false : false
|
||||
const rawSubmissionNotePublic = submissionNote !== null ? body.submissionNotePublic !== false : false
|
||||
const requiresApproval = Boolean(systemSettingsData?.submissionNoteRequiresApproval)
|
||||
|
||||
let finalSubmissionNotePublic = rawSubmissionNotePublic
|
||||
let submissionNotePublicStatus: string | null = null
|
||||
|
||||
if (submissionNote && rawSubmissionNotePublic && requiresApproval) {
|
||||
const mod = moderateSubmissionNote(
|
||||
submissionNote,
|
||||
rawSubmissionNotePublic,
|
||||
requiresApproval,
|
||||
{ title: body.title, artist: body.artist, genre: body.genre || null },
|
||||
moderationBlacklists
|
||||
)
|
||||
finalSubmissionNotePublic = mod.notePublic
|
||||
submissionNotePublicStatus = mod.publicStatus
|
||||
if (mod.rejectedReasons.length > 0) {
|
||||
console.log(
|
||||
`[Request API] 投稿留言自动审查未通过,歌曲=${body.title} - ${body.artist},原因:`,
|
||||
mod.rejectedReasons
|
||||
)
|
||||
}
|
||||
} else if (submissionNote) {
|
||||
// 没开自动审查,也记录一下状态
|
||||
submissionNotePublicStatus = null
|
||||
}
|
||||
|
||||
const submissionNotePublic = finalSubmissionNotePublic
|
||||
|
||||
// 准备发送通知的列表
|
||||
const notificationsToSend: { userId: number; songId: number; songTitle: string }[] = []
|
||||
@@ -367,6 +407,7 @@ export default defineEventHandler(async (event) => {
|
||||
playUrl: body.playUrl || null,
|
||||
submissionNote,
|
||||
submissionNotePublic,
|
||||
submissionNotePublicStatus,
|
||||
hitRequestId: hitRequestTime?.id || null,
|
||||
expectedPlayDate: body.expectedPlayDate || null,
|
||||
expectedPlaySequence: body.expectedPlaySequence !== undefined ? Number(body.expectedPlaySequence) : null
|
||||
|
||||
Reference in New Issue
Block a user