Files
BYW-VoiceHub/server/config/constants.ts
T

495 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 系统常量配置文件
* 消除魔法数字,提供统一的配置管理
*/
// API Key 相关常量
export const API_KEY_CONSTANTS = {
// API Key 格式
PREFIX: 'vhub_',
TOTAL_LENGTH: 37,
PREFIX_LENGTH: 10,
// 数据库字段长度
NAME_MAX_LENGTH: 255,
KEY_HASH_MAX_LENGTH: 255,
PREFIX_MAX_LENGTH: 10,
PERMISSION_MAX_LENGTH: 100,
ENDPOINT_MAX_LENGTH: 500,
METHOD_MAX_LENGTH: 10,
// 默认值
DEFAULT_USAGE_COUNT: 0,
DEFAULT_IS_ACTIVE: true
} as const
// HTTP 状态码常量
export const HTTP_STATUS = {
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
CONFLICT: 409,
UNPROCESSABLE_ENTITY: 422,
TOO_MANY_REQUESTS: 429,
INTERNAL_SERVER_ERROR: 500,
SERVICE_UNAVAILABLE: 503
} as const
// API 错误码常量
export const API_ERROR_CODES = {
MISSING_API_KEY: 'MISSING_API_KEY',
INVALID_API_KEY: 'INVALID_API_KEY',
INVALID_API_KEY_FORMAT: 'INVALID_API_KEY_FORMAT',
API_KEY_DISABLED: 'API_KEY_DISABLED',
API_KEY_EXPIRED: 'API_KEY_EXPIRED',
INSUFFICIENT_PERMISSIONS: 'INSUFFICIENT_PERMISSIONS',
RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
INVALID_PARAMETERS: 'INVALID_PARAMETERS',
INTERNAL_ERROR: 'INTERNAL_ERROR'
} as const
// API 错误消息常量
export const API_ERROR_MESSAGES = {
[API_ERROR_CODES.MISSING_API_KEY]:
'API Key is required. Please provide a valid API Key in the X-API-Key header.',
[API_ERROR_CODES.INVALID_API_KEY]: 'Invalid API Key',
[API_ERROR_CODES.INVALID_API_KEY_FORMAT]: 'Invalid API Key format',
[API_ERROR_CODES.API_KEY_DISABLED]: 'API Key is disabled',
[API_ERROR_CODES.API_KEY_EXPIRED]: 'API Key has expired',
[API_ERROR_CODES.INSUFFICIENT_PERMISSIONS]: 'Insufficient permissions',
[API_ERROR_CODES.RATE_LIMIT_EXCEEDED]: 'Rate limit exceeded',
[API_ERROR_CODES.INVALID_PARAMETERS]: 'Invalid parameters',
[API_ERROR_CODES.INTERNAL_ERROR]: 'Internal server error'
} as const
// 用户可见业务错误码(按子系统分组)。
// 配合 server/utils/apiError.ts 的 createApiError() 抛出,客户端通过 serverErrors 词典按 code 本地化。
// 说明:admin/** 子系统暂缓码化,仍直接返回中文文案,故此处不含 ADMIN_* 分组。
export const SERVER_ERROR_CODES = {
// 点歌券(card-codes)
CARD_CODE_AUTH_REQUIRED: 'CARD_CODE_AUTH_REQUIRED',
CARD_CODE_REQUIRED: 'CARD_CODE_REQUIRED',
CARD_CODE_DISABLED: 'CARD_CODE_DISABLED',
CARD_CODE_INVALID_OR_USED: 'CARD_CODE_INVALID_OR_USED',
CARD_CODE_LOCKED_OR_UNAVAILABLE: 'CARD_CODE_LOCKED_OR_UNAVAILABLE',
CARD_CODE_REQUIRED_FOR_SITE: 'CARD_CODE_REQUIRED_FOR_SITE',
CARD_CODE_TOO_LONG: 'CARD_CODE_TOO_LONG',
AUTH_CSRF_COOKIE_MISSING: 'AUTH_CSRF_COOKIE_MISSING',
AUTH_CHALLENGE_EXPIRED: 'AUTH_CHALLENGE_EXPIRED',
AUTH_CHALLENGE_INVALID: 'AUTH_CHALLENGE_INVALID',
AUTH_STATE_INVALID: 'AUTH_STATE_INVALID',
AUTH_MISSING_CODE_OR_STATE: 'AUTH_MISSING_CODE_OR_STATE',
AUTH_MISSING_PROVIDER: 'AUTH_MISSING_PROVIDER',
AUTH_OAUTH_PROVIDER_DISABLED: 'AUTH_OAUTH_PROVIDER_DISABLED',
AUTH_OAUTH_PROVIDER_STATE_MISMATCH: 'AUTH_OAUTH_PROVIDER_STATE_MISMATCH',
AUTH_OAUTH_REDIRECT_ORIGIN_MISMATCH: 'AUTH_OAUTH_REDIRECT_ORIGIN_MISMATCH',
AUTH_OAUTH_REDIRECT_INVALID: 'AUTH_OAUTH_REDIRECT_INVALID',
AUTH_WEBAUTHN_RP_ID_INVALID: 'AUTH_WEBAUTHN_RP_ID_INVALID',
AUTH_UNSUPPORTED_VERIFICATION_TYPE: 'AUTH_UNSUPPORTED_VERIFICATION_TYPE',
AUTH_CAPTCHA_FAILED_OR_EXPIRED: 'AUTH_CAPTCHA_FAILED_OR_EXPIRED',
AUTH_CAPTCHA_SERVICE_UNAVAILABLE: 'AUTH_CAPTCHA_SERVICE_UNAVAILABLE',
AUTH_SESSION_EXPIRED: 'AUTH_SESSION_EXPIRED',
AUTH_CREDENTIAL_CORRUPTED: 'AUTH_CREDENTIAL_CORRUPTED',
AUTH_CREDENTIAL_MISSING: 'AUTH_CREDENTIAL_MISSING',
AUTH_CREDENTIAL_MISSING_PUBLICKEY: 'AUTH_CREDENTIAL_MISSING_PUBLICKEY',
AUTH_TOTP_CODE_INVALID: 'AUTH_TOTP_CODE_INVALID',
AUTH_TOTP_TOO_MANY_ATTEMPTS: 'AUTH_TOTP_TOO_MANY_ATTEMPTS',
AUTH_INCOMPLETE_PARAMS: 'AUTH_INCOMPLETE_PARAMS',
COMMON_INVALID_PARAMS: 'COMMON_INVALID_PARAMS',
THEME_INVALID_LIST: 'THEME_INVALID_LIST',
THEME_INVALID_DEFAULT: 'THEME_INVALID_DEFAULT',
THEME_SYSTEM_REQUIRES_CLASSIC: 'THEME_SYSTEM_REQUIRES_CLASSIC',
SETTINGS_GRADE_CLASS_OPTIONS_MISSING: 'SETTINGS_GRADE_CLASS_OPTIONS_MISSING',
BILIBILI_PLAYURL_FAILED: 'BILIBILI_PLAYURL_FAILED',
AUTH_NAME_USERNAME_PASSWORD_REQUIRED: 'AUTH_NAME_USERNAME_PASSWORD_REQUIRED',
AUTH_USERNAME_LENGTH_INVALID: 'AUTH_USERNAME_LENGTH_INVALID',
AUTH_USERNAME_PATTERN_INVALID: 'AUTH_USERNAME_PATTERN_INVALID',
AUTH_PASSWORD_TOO_SHORT: 'AUTH_PASSWORD_TOO_SHORT',
AUTH_PASSWORD_TOO_LONG: 'AUTH_PASSWORD_TOO_LONG',
AUTH_PASSWORD_TOO_MANY_BYTES: 'AUTH_PASSWORD_TOO_MANY_BYTES',
AUTH_PASSWORD_TOO_COMMON: 'AUTH_PASSWORD_TOO_COMMON',
AUTH_PASSWORD_COMPLEXITY_REQUIRED: 'AUTH_PASSWORD_COMPLEXITY_REQUIRED',
AUTH_INITIAL_PASSWORD_COMPLEXITY_REQUIRED: 'AUTH_INITIAL_PASSWORD_COMPLEXITY_REQUIRED',
AUTH_PASSWORD_NOT_SET: 'AUTH_PASSWORD_NOT_SET',
AUTH_INITIAL_PASSWORD_NOT_REQUIRED: 'AUTH_INITIAL_PASSWORD_NOT_REQUIRED',
AUTH_PASSWORD_CHANGE_REQUIRED: 'AUTH_PASSWORD_CHANGE_REQUIRED',
AUTH_PASSWORD_STATE_CHANGED: 'AUTH_PASSWORD_STATE_CHANGED',
AUTH_GRADE_CLASS_TOGETHER: 'AUTH_GRADE_CLASS_TOGETHER',
AUTH_UNSUPPORTED_OAUTH_PROVIDER: 'AUTH_UNSUPPORTED_OAUTH_PROVIDER',
AUTH_CURRENT_PASSWORD_INCORRECT: 'AUTH_CURRENT_PASSWORD_INCORRECT',
AUTH_CURRENT_AND_NEW_PASSWORD_REQUIRED: 'AUTH_CURRENT_AND_NEW_PASSWORD_REQUIRED',
AUTH_PASSWORD_ALREADY_SET: 'AUTH_PASSWORD_ALREADY_SET',
AUTH_IP_BLOCKED_RETRY_MINUTES: 'AUTH_IP_BLOCKED_RETRY_MINUTES',
AUTH_ACCOUNT_NAME_NOT_FOUND: 'AUTH_ACCOUNT_NAME_NOT_FOUND',
AUTH_RATE_LIMITED_MINUTES: 'AUTH_RATE_LIMITED_MINUTES',
AUTH_RATE_LIMITED_SECONDS: 'AUTH_RATE_LIMITED_SECONDS',
AUTH_DATABASE_UNAVAILABLE: 'AUTH_DATABASE_UNAVAILABLE',
AUTH_NEW_PASSWORD_SAME_AS_CURRENT: 'AUTH_NEW_PASSWORD_SAME_AS_CURRENT',
AUTH_NEW_PASSWORD_REQUIRED: 'AUTH_NEW_PASSWORD_REQUIRED',
AUTH_REGISTER_PASSWORD_MISMATCH: 'AUTH_REGISTER_PASSWORD_MISMATCH',
AUTH_INVALID_REGISTER_TOKEN: 'AUTH_INVALID_REGISTER_TOKEN',
AUTH_INVALID_BINDING_TOKEN: 'AUTH_INVALID_BINDING_TOKEN',
AUTH_INVALID_RESET_LINK: 'AUTH_INVALID_RESET_LINK',
AUTH_UPDATE_FAILED: 'AUTH_UPDATE_FAILED',
AUTH_SERVER_CONFIG_ERROR: 'AUTH_SERVER_CONFIG_ERROR',
AUTH_AGGREGATED_LOGIN_UNSUPPORTED: 'AUTH_AGGREGATED_LOGIN_UNSUPPORTED',
AUTH_TOTP_NOT_ENABLED: 'AUTH_TOTP_NOT_ENABLED',
AUTH_PASSKEY_ACCOUNT_NOT_FOUND: 'AUTH_PASSKEY_ACCOUNT_NOT_FOUND',
AUTH_DEVICE_NOT_FOUND: 'AUTH_DEVICE_NOT_FOUND',
AUTH_UNAUTHORIZED: 'AUTH_UNAUTHORIZED',
AUTH_UNAUTHORIZED_ACCESS: 'AUTH_UNAUTHORIZED_ACCESS',
AUTH_TOKEN_MISSING: 'AUTH_TOKEN_MISSING',
AUTH_MULTI_IP_PROTECTION: 'AUTH_MULTI_IP_PROTECTION',
AUTH_REGISTER_SESSION_EXPIRED: 'AUTH_REGISTER_SESSION_EXPIRED',
USER_NOT_FOUND: 'USER_NOT_FOUND',
AUTH_USER_NOT_FOUND_OR_NO_EMAIL: 'AUTH_USER_NOT_FOUND_OR_NO_EMAIL',
AUTH_USERNAME_PASSWORD_REQUIRED: 'AUTH_USERNAME_PASSWORD_REQUIRED',
AUTH_USERNAME_TAKEN: 'AUTH_USERNAME_TAKEN',
AUTH_INVALID_CREDENTIALS: 'AUTH_INVALID_CREDENTIALS',
AUTH_LOGIN_UNKNOWN_ERROR: 'AUTH_LOGIN_UNKNOWN_ERROR',
AUTH_OAUTH_REGISTER_DISABLED: 'AUTH_OAUTH_REGISTER_DISABLED',
AUTH_OAUTH_REGISTER_DISABLED_BIND: 'AUTH_OAUTH_REGISTER_DISABLED_BIND',
AUTH_REGISTER_DISABLED: 'AUTH_REGISTER_DISABLED',
AUTH_USER_PENDING_APPROVAL: 'AUTH_USER_PENDING_APPROVAL',
AUTH_SYSTEM_ERROR: 'AUTH_SYSTEM_ERROR',
AUTH_BINDING_SESSION_INVALID: 'AUTH_BINDING_SESSION_INVALID',
AUTH_BINDING_SESSION_EXPIRED: 'AUTH_BINDING_SESSION_EXPIRED',
AUTH_MISSING_CREDENTIAL_ID: 'AUTH_MISSING_CREDENTIAL_ID',
AUTH_MISSING_REQUIRED_PARAMS: 'AUTH_MISSING_REQUIRED_PARAMS',
AUTH_MISSING_PROVIDER_PARAM: 'AUTH_MISSING_PROVIDER_PARAM',
AUTH_MISSING_PRE_TOKEN: 'AUTH_MISSING_PRE_TOKEN',
AUTH_MISSING_PRE_TOKEN_RELOGIN: 'AUTH_MISSING_PRE_TOKEN_RELOGIN',
AUTH_AGGREGATED_CALLBACK_STATE_MISMATCH: 'AUTH_AGGREGATED_CALLBACK_STATE_MISMATCH',
AUTH_AGGREGATED_METHOD_CHANGED: 'AUTH_AGGREGATED_METHOD_CHANGED',
AUTH_AGGREGATED_STATE_INVALID: 'AUTH_AGGREGATED_STATE_INVALID',
AUTH_DEVICE_NAME_TOO_LONG: 'AUTH_DEVICE_NAME_TOO_LONG',
AUTH_OAUTH_BOUND_OTHER_USER: 'AUTH_OAUTH_BOUND_OTHER_USER',
AUTH_OAUTH_ALREADY_BOUND: 'AUTH_OAUTH_ALREADY_BOUND',
AUTH_ACCOUNT_UNAVAILABLE_OR_RESTRICTED: 'AUTH_ACCOUNT_UNAVAILABLE_OR_RESTRICTED',
AUTH_ACCOUNT_GRADUATED: 'AUTH_ACCOUNT_GRADUATED',
AUTH_ACCOUNT_BANNED: 'AUTH_ACCOUNT_BANNED',
AUTH_ACCOUNT_WITHDRAWN: 'AUTH_ACCOUNT_WITHDRAWN',
AUTH_ACCOUNT_CURRENTLY_UNAVAILABLE: 'AUTH_ACCOUNT_CURRENTLY_UNAVAILABLE',
AUTH_NO_SECURITY_EMAIL: 'AUTH_NO_SECURITY_EMAIL',
AUTH_RESET_LINK_INVALIDATED: 'AUTH_RESET_LINK_INVALIDATED',
AUTH_CAPTCHA_REQUIRED: 'AUTH_CAPTCHA_REQUIRED',
AUTH_IMAGE_CAPTCHA_REQUIRED: 'AUTH_IMAGE_CAPTCHA_REQUIRED',
AUTH_ACCOUNT_NAME_REQUIRED: 'AUTH_ACCOUNT_NAME_REQUIRED',
AUTH_GRADE_CLASS_MUST_EXIST: 'AUTH_GRADE_CLASS_MUST_EXIST',
AUTH_GRADE_CLASS_REQUIRED: 'AUTH_GRADE_CLASS_REQUIRED',
AUTH_ACCOUNT_DISABLED_OR_RESTRICTED: 'AUTH_ACCOUNT_DISABLED_OR_RESTRICTED',
AUTH_ACCOUNT_RISK_CONTROL: 'AUTH_ACCOUNT_RISK_CONTROL',
AUTH_ACCOUNT_LOCKED_MINUTES: 'AUTH_ACCOUNT_LOCKED_MINUTES',
AUTH_ACCOUNT_LOCKED: 'AUTH_ACCOUNT_LOCKED',
AUTH_EMAIL_MISMATCH: 'AUTH_EMAIL_MISMATCH',
AUTH_EMAIL_CODE_INVALID: 'AUTH_EMAIL_CODE_INVALID',
AUTH_EMAIL_CODE_COOLDOWN: 'AUTH_EMAIL_CODE_COOLDOWN',
AUTH_EMAIL_CODE_DAILY_LIMIT: 'AUTH_EMAIL_CODE_DAILY_LIMIT',
AUTH_EMAIL_SERVICE_UNAVAILABLE: 'AUTH_EMAIL_SERVICE_UNAVAILABLE',
AUTH_RESET_PASSWORD_TOO_MANY: 'AUTH_RESET_PASSWORD_TOO_MANY',
AUTH_RESET_LINK_EXPIRED: 'AUTH_RESET_LINK_EXPIRED',
AUTH_USER_ID_MISMATCH: 'AUTH_USER_ID_MISMATCH',
AUTH_VERIFICATION_FAILED: 'AUTH_VERIFICATION_FAILED',
AUTH_TOO_MANY_VERIFY_ATTEMPTS: 'AUTH_TOO_MANY_VERIFY_ATTEMPTS',
AUTH_CODE_SEND_FAILED: 'AUTH_CODE_SEND_FAILED',
AUTH_CODE_ALREADY_USED: 'AUTH_CODE_ALREADY_USED',
AUTH_CODE_EXPIRED: 'AUTH_CODE_EXPIRED',
AUTH_CODE_EXPIRED_OR_MISSING: 'AUTH_CODE_EXPIRED_OR_MISSING',
AUTH_CODE_SERVICE_CONFIG_ERROR: 'AUTH_CODE_SERVICE_CONFIG_ERROR',
AUTH_CODE_INVALID_OR_EXPIRED: 'AUTH_CODE_INVALID_OR_EXPIRED',
AUTH_CODE_WRONG_ATTEMPTS_LEFT: 'AUTH_CODE_WRONG_ATTEMPTS_LEFT',
SONG_INVALID_ID: 'SONG_INVALID_ID',
HTTP_METHOD_NOT_ALLOWED: 'HTTP_METHOD_NOT_ALLOWED',
SONG_ALREADY_EXISTS: 'SONG_ALREADY_EXISTS',
SONG_TITLE_ARTIST_REQUIRED: 'SONG_TITLE_ARTIST_REQUIRED',
SONG_CANNOT_SELF_VOTE: 'SONG_CANNOT_SELF_VOTE',
SONG_VOTE_NOT_FOUND: 'SONG_VOTE_NOT_FOUND',
SONG_ALREADY_VOTED: 'SONG_ALREADY_VOTED',
SONG_REPLAY_CANCEL_FAILED: 'SONG_REPLAY_CANCEL_FAILED',
SONG_VOTERS_ADMIN_ONLY: 'SONG_VOTERS_ADMIN_ONLY',
SONG_WITHDRAW_OWN_ONLY: 'SONG_WITHDRAW_OWN_ONLY',
SONG_REPLAY_CURRENT_SEMESTER_ONLY: 'SONG_REPLAY_CURRENT_SEMESTER_ONLY',
SONG_INVITATION_FAILED: 'SONG_INVITATION_FAILED',
SONG_SCHEDULED_CANNOT_WITHDRAW: 'SONG_SCHEDULED_CANNOT_WITHDRAW',
SONG_PLAYED_CANNOT_WITHDRAW: 'SONG_PLAYED_CANNOT_WITHDRAW',
SONG_NO_ACTIVE_SEMESTER_REPLAY: 'SONG_NO_ACTIVE_SEMESTER_REPLAY',
SONG_RESTRICTION_SAME_SONG: 'SONG_RESTRICTION_SAME_SONG',
SONG_RESTRICTION_SAME_ARTIST: 'SONG_RESTRICTION_SAME_ARTIST',
SONG_REPLAY_ALREADY_REQUESTED: 'SONG_REPLAY_ALREADY_REQUESTED',
SONG_REPLAY_COOLDOWN: 'SONG_REPLAY_COOLDOWN',
SONG_REPLAY_INVALID_REQUEST: 'SONG_REPLAY_INVALID_REQUEST',
SONG_REPLAY_PLAY_TIME_DISABLED: 'SONG_REPLAY_PLAY_TIME_DISABLED',
SONG_REPLAY_PLAY_TIME_INVALID: 'SONG_REPLAY_PLAY_TIME_INVALID',
SONG_REQUESTER_NOT_FOUND: 'SONG_REQUESTER_NOT_FOUND',
SONG_OPERATION_FAILED: 'SONG_OPERATION_FAILED',
SONG_INVITATION_NOT_FOUND: 'SONG_INVITATION_NOT_FOUND',
AUTH_LOGIN_REQUIRED: 'AUTH_LOGIN_REQUIRED',
SONG_NO_ACTIVE_SEMESTER_VOTE: 'SONG_NO_ACTIVE_SEMESTER_VOTE',
COMMON_INSUFFICIENT_PERMISSION: 'COMMON_INSUFFICIENT_PERMISSION',
COMMON_TARGET_NOT_FOUND: 'COMMON_TARGET_NOT_FOUND',
USER_NOT_PENDING: 'USER_NOT_PENDING',
GRADE_CLASS_DUPLICATE: 'GRADE_CLASS_DUPLICATE',
SONG_ID_REQUIRED: 'SONG_ID_REQUIRED',
SONG_NOT_FOUND: 'SONG_NOT_FOUND',
SONG_DURATION_PLATFORM_REQUIRED: 'SONG_DURATION_PLATFORM_REQUIRED',
SONG_CARD_RELEASE_FAILED: 'SONG_CARD_RELEASE_FAILED',
SONG_NO_ACTIVE_SEMESTER_IMPORT: 'SONG_NO_ACTIVE_SEMESTER_IMPORT',
SONG_FETCH_VOTERS_FAILED: 'SONG_FETCH_VOTERS_FAILED',
SONG_FETCH_STATUS_FAILED: 'SONG_FETCH_STATUS_FAILED',
SONG_FETCH_COUNT_FAILED: 'SONG_FETCH_COUNT_FAILED',
SONG_TEMP_PROTECTED_SECONDS: 'SONG_TEMP_PROTECTED_SECONDS',
SONG_NOT_PLAYED_NO_REPLAY: 'SONG_NOT_PLAYED_NO_REPLAY',
SONG_SCHEDULED_NO_VOTE: 'SONG_SCHEDULED_NO_VOTE',
SONG_PLAYED_NO_VOTE: 'SONG_PLAYED_NO_VOTE',
SONG_SELECT_TO_IMPORT: 'SONG_SELECT_TO_IMPORT',
SONG_REPLAY_NOT_FOUND_OR_FORBIDDEN: 'SONG_REPLAY_NOT_FOUND_OR_FORBIDDEN',
SONG_REPLAY_DISABLED: 'SONG_REPLAY_DISABLED',
SONG_LOGIN_REQUIRED_CANCEL_REPLAY: 'SONG_LOGIN_REQUIRED_CANCEL_REPLAY',
SONG_LOGIN_REQUIRED_VOTE: 'SONG_LOGIN_REQUIRED_VOTE',
SONG_LOGIN_REQUIRED_WITHDRAW: 'SONG_LOGIN_REQUIRED_WITHDRAW',
SONG_LOGIN_REQUIRED_VIEW_VOTERS: 'SONG_LOGIN_REQUIRED_VIEW_VOTERS',
SONG_LOGIN_REQUIRED_VIEW_STATUS: 'SONG_LOGIN_REQUIRED_VIEW_STATUS',
SONG_LOGIN_REQUIRED_REQUEST: 'SONG_LOGIN_REQUIRED_REQUEST',
SONG_LOGIN_REQUIRED_REPLAY: 'SONG_LOGIN_REQUIRED_REPLAY',
SONG_INACTIVE_SEMESTER_VOTE: 'SONG_INACTIVE_SEMESTER_VOTE',
USER_API_KEY_NOT_FOUND: 'USER_API_KEY_NOT_FOUND',
USER_API_KEY_ID_REQUIRED: 'USER_API_KEY_ID_REQUIRED',
USER_API_KEY_ID_INVALID: 'USER_API_KEY_ID_INVALID',
USER_API_KEY_CREATE_FAILED: 'USER_API_KEY_CREATE_FAILED',
USER_API_KEY_CREATE_FAILED_DETAIL: 'USER_API_KEY_CREATE_FAILED_DETAIL',
USER_API_KEY_DELETE_FAILED_DETAIL: 'USER_API_KEY_DELETE_FAILED_DETAIL',
USER_CODE_SEND_FAILED: 'USER_CODE_SEND_FAILED',
USER_PASSWORD_INCORRECT: 'USER_PASSWORD_INCORRECT',
USER_2FA_ALREADY_ENABLED: 'USER_2FA_ALREADY_ENABLED',
USER_API_KEY_LIMIT_REACHED: 'USER_API_KEY_LIMIT_REACHED',
USER_EMAIL_BIND_FAILED: 'USER_EMAIL_BIND_FAILED',
USER_MISSING_CODE_OR_SECRET: 'USER_MISSING_CODE_OR_SECRET',
USER_API_KEY_FETCH_FAILED_DETAIL: 'USER_API_KEY_FETCH_FAILED_DETAIL',
USER_API_KEY_LOGS_FETCH_FAILED_DETAIL: 'USER_API_KEY_LOGS_FETCH_FAILED_DETAIL',
USER_YEAR_REVIEW_FETCH_FAILED: 'USER_YEAR_REVIEW_FETCH_FAILED',
USER_EMAIL_UNBIND_FAILED: 'USER_EMAIL_UNBIND_FAILED',
USER_EMAIL_TAKEN: 'USER_EMAIL_TAKEN',
USER_SEND_CODE_FIRST: 'USER_SEND_CODE_FIRST',
USER_BIND_EMAIL_FIRST: 'USER_BIND_EMAIL_FIRST',
USER_REQUEST_VALIDATION_FAILED: 'USER_REQUEST_VALIDATION_FAILED',
USER_PASSWORD_REQUIRED: 'USER_PASSWORD_REQUIRED',
USER_INVALID_EMAIL: 'USER_INVALID_EMAIL',
USER_EMAIL_AND_CODE_REQUIRED: 'USER_EMAIL_AND_CODE_REQUIRED',
USER_EMAIL_CHANGED_RESEND: 'USER_EMAIL_CHANGED_RESEND',
USER_EMAIL_ALREADY_VERIFIED: 'USER_EMAIL_ALREADY_VERIFIED',
USER_RESEND_EMAIL_FAILED: 'USER_RESEND_EMAIL_FAILED',
USER_CODE_ALREADY_USED: 'USER_CODE_ALREADY_USED',
USER_CODE_EXPIRED_RESEND: 'USER_CODE_EXPIRED_RESEND',
USER_CODE_INVALID: 'USER_CODE_INVALID',
USER_CODE_TOO_MANY_ATTEMPTS: 'USER_CODE_TOO_MANY_ATTEMPTS',
// 音源平台
MUSIC_SOURCE_PLATFORM_DISABLED: 'MUSIC_SOURCE_PLATFORM_DISABLED',
QQ_COMMENT_FETCH_FAILED: 'QQ_COMMENT_FETCH_FAILED',
// 自动备份
BACKUP_DISABLED: 'BACKUP_DISABLED',
BACKUP_NOT_CONFIGURED: 'BACKUP_NOT_CONFIGURED',
NO_BACKUP_METHOD_ENABLED: 'NO_BACKUP_METHOD_ENABLED',
BACKUP_FAILED: 'BACKUP_FAILED',
SMTP_NOT_CONFIGURED: 'SMTP_NOT_CONFIGURED',
// 通知
NOTIFICATION_AUTH_REQUIRED: 'NOTIFICATION_AUTH_REQUIRED',
NOTIFICATION_ADMIN_REQUIRED: 'NOTIFICATION_ADMIN_REQUIRED',
NOTIFICATION_IMPORTANT_INVALID: 'NOTIFICATION_IMPORTANT_INVALID',
NOTIFICATION_TITLE_CONTENT_REQUIRED: 'NOTIFICATION_TITLE_CONTENT_REQUIRED',
NOTIFICATION_TITLE_TOO_LONG: 'NOTIFICATION_TITLE_TOO_LONG',
NOTIFICATION_CONTENT_TOO_LONG: 'NOTIFICATION_CONTENT_TOO_LONG',
NOTIFICATION_USER_ID_INVALID: 'NOTIFICATION_USER_ID_INVALID',
NOTIFICATION_SCOPE_INVALID: 'NOTIFICATION_SCOPE_INVALID',
NOTIFICATION_GRADE_REQUIRED: 'NOTIFICATION_GRADE_REQUIRED',
NOTIFICATION_GRADE_CLASS_REQUIRED: 'NOTIFICATION_GRADE_CLASS_REQUIRED',
NOTIFICATION_CLASSES_REQUIRED: 'NOTIFICATION_CLASSES_REQUIRED',
NOTIFICATION_USERS_REQUIRED: 'NOTIFICATION_USERS_REQUIRED',
NOTIFICATION_SEND_FAILED: 'NOTIFICATION_SEND_FAILED',
NOTIFICATION_FETCH_FAILED: 'NOTIFICATION_FETCH_FAILED',
NOTIFICATION_ID_INVALID: 'NOTIFICATION_ID_INVALID',
NOTIFICATION_NOT_FOUND: 'NOTIFICATION_NOT_FOUND',
NOTIFICATION_FORBIDDEN: 'NOTIFICATION_FORBIDDEN',
NOTIFICATION_MARK_READ_FAILED: 'NOTIFICATION_MARK_READ_FAILED',
NOTIFICATION_DELETE_FAILED: 'NOTIFICATION_DELETE_FAILED',
NOTIFICATION_CLEAR_FAILED: 'NOTIFICATION_CLEAR_FAILED',
NOTIFICATION_HISTORY_STATUS_INVALID: 'NOTIFICATION_HISTORY_STATUS_INVALID',
NOTIFICATION_HISTORY_FILTER_INVALID: 'NOTIFICATION_HISTORY_FILTER_INVALID',
NOTIFICATION_HISTORY_FETCH_FAILED: 'NOTIFICATION_HISTORY_FETCH_FAILED',
NOTIFICATION_HISTORY_UPDATE_FAILED: 'NOTIFICATION_HISTORY_UPDATE_FAILED',
NOTIFICATION_HISTORY_DELETE_FAILED: 'NOTIFICATION_HISTORY_DELETE_FAILED'
// 认证(AUTH_*)、歌曲(SONG_*)、用户(USER_*)等分组在 B2 转换时逐步补充
} as const
export type ServerErrorCode = keyof typeof SERVER_ERROR_CODES
// 分页常量
export const PAGINATION_CONSTANTS = {
DEFAULT_PAGE: 1,
DEFAULT_LIMIT: 20,
MAX_LIMIT: 100,
MIN_LIMIT: 1
} as const
// 日志相关常量
export const LOG_CONSTANTS = {
MAX_RESPONSE_BODY_LENGTH: 10000, // 最大响应体长度
LOG_LEVEL: {
ERROR: 'error',
WARN: 'warn',
INFO: 'info',
DEBUG: 'debug'
} as const,
// 日志轮转和归档配置
LOG_DIR: 'logs',
ARCHIVE_DIR: 'logs/archive',
RETENTION_DAYS: 30, // 日志文件保留天数
ARCHIVE_AFTER_DAYS: 7, // 多少天后归档
DB_RETENTION_DAYS: 90, // 数据库日志保留天数
MAX_LOG_FILE_SIZE: 100 * 1024 * 1024, // 100MB
ROTATION_INTERVAL: 24 * 60 * 60 * 1000 // 24小时轮转间隔
} as const
// 通知设置默认值常量
export const NOTIFICATION_DEFAULTS = {
ENABLED: true,
SONG_REQUEST_ENABLED: true,
SONG_VOTED_ENABLED: true,
SONG_PLAYED_ENABLED: true,
REFRESH_INTERVAL: 60,
SONG_VOTED_THRESHOLD: 1
} as const
// 系统设置默认值常量
export const SYSTEM_DEFAULTS = {
ENABLE_PLAY_TIME_SELECTION: false,
ENABLE_SUBMISSION_LIMIT: false,
SHOW_BLACKLIST_KEYWORDS: false,
HIDE_STUDENT_INFO: true,
IS_ACTIVE: false
} as const
// 排序相关常量
export const SORT_CONSTANTS = {
DEFAULT_SORT_BY: 'createdAt',
DEFAULT_SORT_ORDER: 'desc',
VALID_SORT_ORDERS: ['asc', 'desc'] as const,
// 排期排序字段
SCHEDULE_SORT_FIELDS: ['playDate', 'createdAt'] as const,
// 歌曲排序字段
SONG_SORT_FIELDS: ['createdAt', 'title', 'artist', 'playedAt', 'votes'] as const
} as const
// 权限常量
export const PERMISSIONS = {
SCHEDULES_READ: 'schedules:read',
SONGS_READ: 'songs:read',
SONGS_REQUEST: 'songs:request',
ADMIN_ACCESS: 'admin:access'
} as const
// 用户角色常量
export const USER_ROLES = {
USER: 'USER',
SONG_ADMIN: 'SONG_ADMIN',
ADMIN: 'ADMIN',
SUPER_ADMIN: 'SUPER_ADMIN'
} as const
// 黑名单类型常量
export const BLACKLIST_TYPES = {
SONG: 'SONG',
KEYWORD: 'KEYWORD',
LANGUAGE: 'LANGUAGE',
GENRE: 'GENRE'
} as const
// 语种黑名单候选值(存储值即匹配值;新增需同步前端 app/utils/blacklist.ts)
export const BLACKLIST_LANGUAGE_VALUES = ['华语', '粤语', '闽南语', '英语', '日语', '韩语', '其他'] as const
// 曲风黑名单候选值(取网易云曲风标签一级分类与 QQ 官网流派分类;新增需同步前端 app/utils/blacklist.ts)
export const BLACKLIST_GENRE_VALUES = [
'流行', '摇滚', '民谣', '电子', '舞曲', '说唱', '古典', '爵士',
'乡村', '原声带', '蓝调', '轻音乐', '其他'
] as const
// QQ 音乐语种数字码 → 黑名单语种值(track_info.language 实测映射;info.lan 缺失时兜底)
export const QQ_LANGUAGE_CODE_MAP: Record<number, string> = {
0: '华语',
1: '粤语',
2: '闽南语',
3: '日语',
4: '韩语',
5: '英语'
}
// QQ 官网流派分类英文名(get_song_detail_yqq 的 info.genre)→ 黑名单曲风值
// 仅覆盖候选曲风;未收录流派名(Animation/World Music 等)由调用方归入「其他」
export const QQ_GENRE_NAME_MAP: Record<string, string> = {
Pop: '流行',
Rock: '摇滚',
Folk: '民谣',
Electronic: '电子',
Dance: '舞曲',
Rap: '说唱',
'Hip Hop': '说唱',
'Hip-Hop': '说唱',
Classical: '古典',
Jazz: '爵士',
Country: '乡村',
Soundtrack: '原声带',
OST: '原声带',
Blues: '蓝调',
'Easy Listening': '轻音乐'
}
// 音乐平台常量
export const MUSIC_PLATFORMS = {
NETEASE: 'netease',
TENCENT: 'tencent',
KUGOU: 'kugou',
KUWO: 'kuwo',
MIGU: 'migu'
} as const
// 音源控制功能平台白名单(enabledPlatforms/platformOrder 校验用;新增平台需同步 app/drizzle/schema.ts 默认值、迁移文件与前端 app/utils/platforms.ts)
export const MUSIC_SOURCE_PLATFORMS = ['netease', 'tencent', 'bilibili', 'migu'] as const
// 歌曲时长合法区间(秒),投稿、往期导入、后台补齐共用
export const SONG_DURATION_MIN_SECONDS = 0
export const SONG_DURATION_MAX_SECONDS = 7200
// 主题白名单(主题管理校验用;新增主题需同步 app/drizzle/schema.ts 默认值、迁移文件与前端 app/composables/useTheme.ts)
export const THEMES = ['System', 'ClassicDark', 'ClassicLight', 'ModernLight'] as const
export const DEFAULT_THEMES = [...THEMES]
export const RISK_CONTROL_CONSTANTS = {
IP_SWITCH_WINDOW_MS: 5 * 60 * 1000,
IP_SWITCH_THRESHOLD: 3,
IP_SWITCH_BLOCK_MINUTES: 10,
USER_BLOCK_MINUTES: 10,
SONG_VOTE_PROTECT_WINDOW_MS: 60 * 60 * 1000,
SONG_VOTE_PROTECT_THRESHOLD: 10,
SONG_VOTE_PROTECT_DURATION_MS: 10 * 60 * 1000,
USER_BASELINE_MIN_PER_MIN: 0.5,
USER_BASELINE_MULTIPLIER: 3,
EMA_ALPHA: 0.2,
SONG_IP_DOMINANCE_PERCENT: 0.6,
SONG_IP_MIN_SAMPLE: 8
} as const
// 图形验证码策略
export const CAPTCHA_MAX_FAILURES = 3
export const CAPTCHA_CODE_EXPIRE = 300 // 5分钟
// 投稿公开留言审核状态(Song.submissionNotePublicStatus / song_replay_requests.submission_note_public_status)
export const SUBMISSION_NOTE_STATUS = {
PENDING: 'pending',
APPROVED: 'approved',
REJECTED: 'rejected'
} as const