/** @type {readonly string[]} 가입 금지 닉네임 기본값 */ export const DEFAULT_SIGNUP_BLOCKED_USERNAMES = Object.freeze([ 'admin', 'master', 'zenn', 'sori', 'sori.studio' ]) /** @type {number} 금지 닉네임 최대 개수 */ export const MAX_SIGNUP_BLOCKED_USERNAME_COUNT = 100 /** @type {number} 금지 닉네임 한 항목 최대 길이 */ export const MAX_SIGNUP_BLOCKED_USERNAME_LENGTH = 60 /** * 금지 닉네임 목록을 정리한다. * @param {unknown} value - 원본 값 * @returns {string[]} 정리된 목록 */ export const normalizeSignupBlockedUsernames = (value) => { const source = Array.isArray(value) ? value : [] const seen = new Set() const result = [] for (const item of source) { const term = String(item || '').trim() if (!term) { continue } const key = term.toLowerCase() if (seen.has(key)) { continue } seen.add(key) result.push(term.slice(0, MAX_SIGNUP_BLOCKED_USERNAME_LENGTH)) if (result.length >= MAX_SIGNUP_BLOCKED_USERNAME_COUNT) { break } } return result.length > 0 ? result : [...DEFAULT_SIGNUP_BLOCKED_USERNAMES] } /** * DB에 저장된 금지 닉네임 JSON 문자열을 파싱한다. * @param {unknown} raw - DB 값 * @returns {string[]} 정리된 목록 */ export const parseSignupBlockedUsernamesFromDb = (raw) => { if (Array.isArray(raw)) { return normalizeSignupBlockedUsernames(raw) } if (typeof raw === 'string' && raw.trim()) { try { return normalizeSignupBlockedUsernames(JSON.parse(raw)) } catch { return [...DEFAULT_SIGNUP_BLOCKED_USERNAMES] } } return [...DEFAULT_SIGNUP_BLOCKED_USERNAMES] } /** * 줄 단위 텍스트를 금지 닉네임 배열로 변환한다. * @param {string} text - 줄바꿈 구분 입력 * @returns {string[]} 정리된 목록 */ export const parseSignupBlockedUsernamesFromText = (text) => { const lines = String(text || '').split(/\r?\n/) return normalizeSignupBlockedUsernames(lines) } /** * 닉네임에 금지 단어가 포함되는지 확인한다. * @param {string} username - 닉네임 * @param {string[]} blockedList - 금지 목록 * @returns {string | null} 매칭된 금지 단어(표시용) */ export const getSignupBlockedUsernameMatch = (username, blockedList) => { const normalized = username.trim().toLowerCase() if (!normalized) { return null } const terms = normalizeSignupBlockedUsernames(blockedList) .slice() .sort((left, right) => right.length - left.length) for (const term of terms) { const key = term.toLowerCase() if (normalized === key || normalized.includes(key)) { return term } } return null } /** * 금지 닉네임 안내 문구를 만든다. * @param {string} matchedTerm - 매칭된 금지 단어 * @returns {string} 안내 문구 */ export const formatSignupBlockedUsernameMessage = (matchedTerm) => `${matchedTerm}은 사용할 수 없는 단어입니다.`