Files
BYW-VoiceHub/server/utils/notification-history-policy.ts
T
Henry_Du bd8df1f026
构建并部署 BYW VoiceHub / Preflight 语法与构建检查 (push) Failing after 6m31s
构建并部署 BYW VoiceHub / 构建并推送容器镜像 (push) Skipped
构建并部署 BYW VoiceHub / 部署到生产服务器 (push) Failing after 4s
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 接入自动审查逻辑
2026-09-06 17:03:20 +08:00

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
}
}