Files
sori.studio/server/api/auth/profile.put.js
zenn b77f37a94e v1.3.1: 어나운스 바·가입 금지 닉네임·설정 UI 개선
공개 상단 어나운스 바와 관리자 맞춤 설정을 추가하고, 스팸 필터에서 가입 금지 닉네임을 관리·검증한다. POST 설정 읽기 모드 비활성 토글과 설정 내비 아이콘 틀을 반영한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 15:50:47 +09:00

88 lines
2.4 KiB
JavaScript

import { createError, readBody } from 'h3'
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),
avatarUrl: z.string().trim().max(500).optional().default('')
})
/**
* 회원 프로필 수정 API
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<{ id: string, email: string, username: string, avatarUrl: string }>} 수정된 회원 프로필
*/
export default defineEventHandler(async (event) => {
const session = requireMemberSession(event)
const parsedBody = updateProfileSchema.safeParse(await readBody(event))
if (!parsedBody.success) {
throw createError({
statusCode: 400,
message: '프로필 요청 형식이 올바르지 않습니다.'
})
}
await assertSignupUsernameAllowed(parsedBody.data.username)
const taken = await isUsernameTaken({
username: parsedBody.data.username,
excludeUserId: session.userId
})
if (taken) {
throw createError({
statusCode: 409,
message: '이미 사용 중인 닉네임입니다.'
})
}
const existing = await getUserById(session.userId)
if (!existing) {
throw createError({
statusCode: 404,
message: '회원 정보를 찾을 수 없습니다.'
})
}
const previousAvatarUrl = existing.avatarUrl || ''
const nextAvatarUrl = parsedBody.data.avatarUrl || ''
if (previousAvatarUrl && previousAvatarUrl !== nextAvatarUrl && isManagedAvatarUrl(previousAvatarUrl)) {
await removeManagedAvatarAsset(previousAvatarUrl)
}
const updated = await updateMemberProfile({
userId: session.userId,
username: parsedBody.data.username,
avatarUrl: nextAvatarUrl
})
if (!updated) {
throw createError({
statusCode: 404,
message: '회원 정보를 찾을 수 없습니다.'
})
}
const user = await getUserById(session.userId)
if (!user) {
throw createError({
statusCode: 404,
message: '회원 정보를 찾을 수 없습니다.'
})
}
return {
id: user.id,
email: user.email,
username: user.username,
avatarUrl: user.avatarUrl || ''
}
})