- 新增 /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 接入自动审查逻辑
109 lines
3.6 KiB
TypeScript
109 lines
3.6 KiB
TypeScript
export const NOTIFICATION_HISTORY_STATUSES = ['ALL', 'READ', 'UNREAD'] as const
|
|
|
|
export type NotificationHistoryStatus = (typeof NOTIFICATION_HISTORY_STATUSES)[number]
|
|
|
|
export const NOTIFICATION_HISTORY_TYPES = ['ALL', 'IMPORTANT', 'NORMAL'] as const
|
|
export const NOTIFICATION_HISTORY_SORT_ORDERS = ['DESC', 'ASC'] as const
|
|
export const NOTIFICATION_HISTORY_KEYWORD_MAX_LENGTH = 100
|
|
|
|
export type NotificationHistoryType = (typeof NOTIFICATION_HISTORY_TYPES)[number]
|
|
export type NotificationHistorySortOrder = (typeof NOTIFICATION_HISTORY_SORT_ORDERS)[number]
|
|
|
|
export const resolveNotificationHistoryFilters = (values: {
|
|
keyword?: unknown
|
|
type?: unknown
|
|
sender?: unknown
|
|
sortOrder?: unknown
|
|
}): {
|
|
keyword: string
|
|
type: NotificationHistoryType
|
|
senderId: number | null
|
|
sortOrder: NotificationHistorySortOrder
|
|
} | null => {
|
|
if (values.keyword !== undefined && typeof values.keyword !== 'string') return null
|
|
|
|
const keyword = typeof values.keyword === 'string' ? values.keyword.trim() : ''
|
|
if (keyword.length > NOTIFICATION_HISTORY_KEYWORD_MAX_LENGTH) return null
|
|
|
|
if (values.type !== undefined && typeof values.type !== 'string') return null
|
|
const type = typeof values.type === 'string' && values.type.trim()
|
|
? values.type.trim().toUpperCase()
|
|
: 'ALL'
|
|
if (!NOTIFICATION_HISTORY_TYPES.includes(type as NotificationHistoryType)) return null
|
|
|
|
let senderId: number | null = null
|
|
if (values.sender !== undefined && values.sender !== null && values.sender !== '') {
|
|
if (typeof values.sender !== 'string') return null
|
|
const parsedSenderId = Number(values.sender.trim())
|
|
if (!Number.isInteger(parsedSenderId) || parsedSenderId <= 0) return null
|
|
senderId = parsedSenderId
|
|
}
|
|
|
|
if (values.sortOrder !== undefined && typeof values.sortOrder !== 'string') return null
|
|
const sortOrder = typeof values.sortOrder === 'string' && values.sortOrder.trim()
|
|
? values.sortOrder.trim().toUpperCase()
|
|
: 'DESC'
|
|
if (!NOTIFICATION_HISTORY_SORT_ORDERS.includes(sortOrder as NotificationHistorySortOrder)) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
keyword,
|
|
type: type as NotificationHistoryType,
|
|
senderId,
|
|
sortOrder: sortOrder as NotificationHistorySortOrder
|
|
}
|
|
}
|
|
|
|
export const resolveNotificationHistoryStatus = (
|
|
value: unknown
|
|
): NotificationHistoryStatus | null => {
|
|
if (value === undefined || value === null || value === '') return 'ALL'
|
|
if (typeof value !== 'string') return null
|
|
|
|
const normalized = value.trim().toUpperCase()
|
|
return NOTIFICATION_HISTORY_STATUSES.includes(normalized as NotificationHistoryStatus)
|
|
? (normalized as NotificationHistoryStatus)
|
|
: null
|
|
}
|
|
|
|
export const resolveNotificationHistoryPagination = (
|
|
pageValue: unknown,
|
|
limitValue: unknown
|
|
) => {
|
|
const parsedPage = Number(pageValue)
|
|
const parsedLimit = Number(limitValue)
|
|
const page = Number.isInteger(parsedPage) && parsedPage > 0 ? parsedPage : 1
|
|
const limit = Number.isInteger(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 100) : 20
|
|
|
|
return {
|
|
page,
|
|
limit,
|
|
offset: (page - 1) * limit
|
|
}
|
|
}
|
|
|
|
const UUID_PATTERN =
|
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
const LEGACY_BATCH_PATTERN = /^legacy-([1-9]\d*)$/
|
|
|
|
export const resolveNotificationBatchReference = (value: unknown) => {
|
|
if (typeof value !== 'string') return null
|
|
|
|
const normalized = value.trim()
|
|
const legacyMatch = normalized.match(LEGACY_BATCH_PATTERN)
|
|
if (legacyMatch) {
|
|
return {
|
|
batchId: null,
|
|
notificationId: Number(legacyMatch[1])
|
|
}
|
|
}
|
|
|
|
if (!UUID_PATTERN.test(normalized)) return null
|
|
|
|
return {
|
|
batchId: normalized.toLowerCase(),
|
|
notificationId: null
|
|
}
|
|
}
|