495 lines
22 KiB
TypeScript
495 lines
22 KiB
TypeScript
/**
|
||
* 系统常量配置文件
|
||
* 消除魔法数字,提供统一的配置管理
|
||
*/
|
||
|
||
// 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
|