v1.3.1: 어나운스 바·가입 금지 닉네임·설정 UI 개선
공개 상단 어나운스 바와 관리자 맞춤 설정을 추가하고, 스팸 필터에서 가입 금지 닉네임을 관리·검증한다. POST 설정 읽기 모드 비활성 토글과 설정 내비 아이콘 틀을 반영한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import { z } from 'zod'
|
||||
import { getUserById, isUsernameTaken, updateMemberProfile } from '../../repositories/member-repository'
|
||||
import { requireMemberSession } from '../../utils/member-auth'
|
||||
import { isManagedAvatarUrl, removeManagedAvatarAsset } from '../../utils/member-avatar'
|
||||
import { assertSignupUsernameAllowed } from '../../utils/member-username-policy'
|
||||
|
||||
const updateProfileSchema = z.object({
|
||||
username: z.string().trim().min(1).max(30),
|
||||
@@ -25,6 +26,8 @@ export default defineEventHandler(async (event) => {
|
||||
})
|
||||
}
|
||||
|
||||
await assertSignupUsernameAllowed(parsedBody.data.username)
|
||||
|
||||
const taken = await isUsernameTaken({
|
||||
username: parsedBody.data.username,
|
||||
excludeUserId: session.userId
|
||||
|
||||
@@ -7,6 +7,7 @@ import { setMemberSession } from '../../utils/member-auth'
|
||||
import { setAdminSession } from '../../utils/admin-auth'
|
||||
import { isResendConfigured } from '../../utils/resend-mail'
|
||||
import { getRuntimeEnvValue } from '../../utils/runtime-env'
|
||||
import { assertSignupUsernameAllowed } from '../../utils/member-username-policy'
|
||||
|
||||
const signupSchema = z.object({
|
||||
username: z.string().trim().min(1),
|
||||
@@ -50,6 +51,8 @@ export default defineEventHandler(async (event) => {
|
||||
const otpRequired = isSignupOtpRequired(config, bootstrap)
|
||||
const emailNorm = body.email.trim().toLowerCase()
|
||||
|
||||
await assertSignupUsernameAllowed(body.username)
|
||||
|
||||
const usernameTaken = await isUsernameTaken({
|
||||
username: body.username
|
||||
})
|
||||
|
||||
@@ -9,6 +9,10 @@ import { getDefaultNavigationItems } from '../utils/navigation-items'
|
||||
import { buildPublicPrimaryTree, orderNavigationItemsForInsert } from '../utils/navigation-tree'
|
||||
import { getDefaultSiteSettings } from '../utils/site-settings'
|
||||
import { toAdminPostFormTitle } from '../../lib/admin-post-title.js'
|
||||
import {
|
||||
normalizeSignupBlockedUsernames,
|
||||
parseSignupBlockedUsernamesFromDb
|
||||
} from '../../lib/signup-blocked-usernames.js'
|
||||
import { getPostgresClient } from './postgres-client'
|
||||
|
||||
/**
|
||||
@@ -97,6 +101,11 @@ const mapSiteSettingsRow = (row) => ({
|
||||
homeCoverImageUrl: row.home_cover_image_url || '',
|
||||
homeCoverTitle: row.home_cover_title || '',
|
||||
homeCoverText: row.home_cover_text || '',
|
||||
announcementEnabled: Boolean(row.announcement_enabled),
|
||||
announcementText: row.announcement_text || '',
|
||||
announcementUrl: row.announcement_url || '',
|
||||
announcementBackgroundColor: row.announcement_background_color || '#15171a',
|
||||
signupBlockedUsernames: parseSignupBlockedUsernamesFromDb(row.signup_blocked_usernames),
|
||||
updatedAt: row.updated_at.toISOString()
|
||||
})
|
||||
|
||||
@@ -816,6 +825,11 @@ export const updateSiteSettings = async (input) => {
|
||||
home_cover_image_url,
|
||||
home_cover_title,
|
||||
home_cover_text,
|
||||
announcement_enabled,
|
||||
announcement_text,
|
||||
announcement_url,
|
||||
announcement_background_color,
|
||||
signup_blocked_usernames,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
@@ -831,6 +845,11 @@ export const updateSiteSettings = async (input) => {
|
||||
${input.homeCoverImageUrl || ''},
|
||||
${input.homeCoverTitle || ''},
|
||||
${input.homeCoverText || ''},
|
||||
${input.announcementEnabled ? true : false},
|
||||
${input.announcementText || ''},
|
||||
${input.announcementUrl || ''},
|
||||
${input.announcementBackgroundColor || '#15171a'},
|
||||
${JSON.stringify(normalizeSignupBlockedUsernames(input.signupBlockedUsernames))},
|
||||
now()
|
||||
)
|
||||
ON CONFLICT (id) DO UPDATE
|
||||
@@ -846,6 +865,11 @@ export const updateSiteSettings = async (input) => {
|
||||
home_cover_image_url = EXCLUDED.home_cover_image_url,
|
||||
home_cover_title = EXCLUDED.home_cover_title,
|
||||
home_cover_text = EXCLUDED.home_cover_text,
|
||||
announcement_enabled = EXCLUDED.announcement_enabled,
|
||||
announcement_text = EXCLUDED.announcement_text,
|
||||
announcement_url = EXCLUDED.announcement_url,
|
||||
announcement_background_color = EXCLUDED.announcement_background_color,
|
||||
signup_blocked_usernames = EXCLUDED.signup_blocked_usernames,
|
||||
updated_at = now()
|
||||
RETURNING *
|
||||
`
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
DEFAULT_ANNOUNCEMENT_BACKGROUND_COLOR,
|
||||
isValidAnnouncementBackgroundColor,
|
||||
normalizeAnnouncementUrl
|
||||
} from '../../lib/announcement-bar.js'
|
||||
import {
|
||||
DEFAULT_SIGNUP_BLOCKED_USERNAMES,
|
||||
MAX_SIGNUP_BLOCKED_USERNAME_COUNT,
|
||||
MAX_SIGNUP_BLOCKED_USERNAME_LENGTH,
|
||||
normalizeSignupBlockedUsernames
|
||||
} from '../../lib/signup-blocked-usernames.js'
|
||||
|
||||
export const adminSiteSettingsInputSchema = z.object({
|
||||
title: z.string().trim().min(1),
|
||||
@@ -11,8 +22,35 @@ export const adminSiteSettingsInputSchema = z.object({
|
||||
showPostUpdatedAt: z.boolean().optional().default(false),
|
||||
homeCoverImageUrl: z.string().trim().max(500).optional().default(''),
|
||||
homeCoverTitle: z.string().trim().max(120).optional().default(''),
|
||||
homeCoverText: z.string().trim().max(280).optional().default('')
|
||||
})
|
||||
homeCoverText: z.string().trim().max(280).optional().default(''),
|
||||
announcementEnabled: z.boolean().optional().default(false),
|
||||
announcementText: z.string().trim().max(200).optional().default(''),
|
||||
announcementUrl: z.string().trim().max(500).optional().default(''),
|
||||
announcementBackgroundColor: z.string().trim().optional().default(DEFAULT_ANNOUNCEMENT_BACKGROUND_COLOR),
|
||||
signupBlockedUsernames: z.array(
|
||||
z.string().trim().min(1).max(MAX_SIGNUP_BLOCKED_USERNAME_LENGTH)
|
||||
).max(MAX_SIGNUP_BLOCKED_USERNAME_COUNT).optional().default([...DEFAULT_SIGNUP_BLOCKED_USERNAMES])
|
||||
}).superRefine((data, ctx) => {
|
||||
if (!isValidAnnouncementBackgroundColor(data.announcementBackgroundColor)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: '어나운스 바 배경색이 올바르지 않습니다.',
|
||||
path: ['announcementBackgroundColor']
|
||||
})
|
||||
}
|
||||
|
||||
if (data.announcementEnabled && !data.announcementText.trim()) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: '어나운스 바를 사용할 때는 공지 문구를 입력해야 합니다.',
|
||||
path: ['announcementText']
|
||||
})
|
||||
}
|
||||
}).transform((data) => ({
|
||||
...data,
|
||||
announcementUrl: normalizeAnnouncementUrl(data.announcementUrl),
|
||||
signupBlockedUsernames: normalizeSignupBlockedUsernames(data.signupBlockedUsernames)
|
||||
}))
|
||||
|
||||
/**
|
||||
* 관리자 사이트 설정 입력값 정리
|
||||
|
||||
23
server/utils/member-username-policy.js
Normal file
23
server/utils/member-username-policy.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createError } from 'h3'
|
||||
import {
|
||||
formatSignupBlockedUsernameMessage,
|
||||
getSignupBlockedUsernameMatch
|
||||
} from '../../lib/signup-blocked-usernames.js'
|
||||
import { getSiteSettings } from '../repositories/content-repository.js'
|
||||
|
||||
/**
|
||||
* 가입·프로필 변경 시 닉네임이 금지 목록에 해당하는지 검사한다.
|
||||
* @param {string} username - 닉네임
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const assertSignupUsernameAllowed = async (username) => {
|
||||
const settings = await getSiteSettings()
|
||||
const match = getSignupBlockedUsernameMatch(username, settings.signupBlockedUsernames || [])
|
||||
|
||||
if (match) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: formatSignupBlockedUsernameMessage(match)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DEFAULT_SIGNUP_BLOCKED_USERNAMES } from '../../lib/signup-blocked-usernames.js'
|
||||
|
||||
/**
|
||||
* 기본 사이트 설정 반환
|
||||
* @returns {Object} 기본 사이트 설정
|
||||
@@ -18,6 +20,11 @@ export const getDefaultSiteSettings = () => {
|
||||
homeCoverImageUrl: '',
|
||||
homeCoverTitle: '',
|
||||
homeCoverText: '',
|
||||
announcementEnabled: false,
|
||||
announcementText: '',
|
||||
announcementUrl: '',
|
||||
announcementBackgroundColor: '#15171a',
|
||||
signupBlockedUsernames: [...DEFAULT_SIGNUP_BLOCKED_USERNAMES],
|
||||
updatedAt: null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user