관리자 레이아웃과 네비게이션 정리
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import bcrypt from 'bcrypt'
|
||||
import { createError, readBody } from 'h3'
|
||||
import { z } from 'zod'
|
||||
import { deleteMember, getUserByIdWithPassword } from '../../repositories/member-repository'
|
||||
import { countOwnerMembers, deleteMember, getUserByIdWithPassword, MEMBER_ROLE } from '../../repositories/member-repository'
|
||||
import { clearAdminSession } from '../../utils/admin-auth'
|
||||
import { clearMemberSession, requireMemberSession } from '../../utils/member-auth'
|
||||
import { removeManagedAvatarAsset } from '../../utils/member-avatar'
|
||||
|
||||
@@ -41,13 +42,20 @@ export default defineEventHandler(async (event) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (user.role === MEMBER_ROLE.OWNER && (await countOwnerMembers()) <= 1) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: '최소 1명의 소유자 권한은 유지되어야 합니다.'
|
||||
})
|
||||
}
|
||||
|
||||
if (user.avatarUrl) {
|
||||
await removeManagedAvatarAsset(user.avatarUrl)
|
||||
}
|
||||
|
||||
await deleteMember(session.userId)
|
||||
clearMemberSession(event)
|
||||
clearAdminSession(event)
|
||||
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
countOtpSendsLastHour,
|
||||
hasRecentOtpSend,
|
||||
insertOtpChallenge,
|
||||
invalidatePendingOtpChallenges
|
||||
deleteOtpChallengeById,
|
||||
invalidatePendingOtpChallenges,
|
||||
invalidatePendingOtpChallengesExcept
|
||||
} from '../../../repositories/email-otp-repository'
|
||||
import { generateSixDigitOtp, hashOtpCode, normalizeOtpEmail } from '../../../utils/email-otp'
|
||||
import { isResendConfigured, sendResendEmail } from '../../../utils/resend-mail'
|
||||
@@ -129,8 +131,7 @@ export default defineEventHandler(async (event) => {
|
||||
const expiresAt = new Date(Date.now() + OTP_TTL_MS)
|
||||
const createdIp = String(getRequestIP(event) || '')
|
||||
|
||||
await invalidatePendingOtpChallenges(sql, email, purpose)
|
||||
await insertOtpChallenge(sql, {
|
||||
const challengeId = await insertOtpChallenge(sql, {
|
||||
email,
|
||||
purpose,
|
||||
codeHash,
|
||||
@@ -147,13 +148,20 @@ export default defineEventHandler(async (event) => {
|
||||
? `<p>회원가입을 위한 인증번호입니다.</p><p style="font-size:24px;font-weight:700;letter-spacing:0.2em">${code}</p><p>15분 이내에 입력해 주세요.</p>`
|
||||
: `<p>비밀번호 재설정을 위한 인증번호입니다.</p><p style="font-size:24px;font-weight:700;letter-spacing:0.2em">${code}</p><p>15분 이내에 입력해 주세요.</p>`
|
||||
|
||||
await sendResendEmail({
|
||||
apiKey: String(config.resendApiKey).trim(),
|
||||
from: String(config.resendFromEmail).trim(),
|
||||
to: email,
|
||||
subject,
|
||||
html
|
||||
})
|
||||
try {
|
||||
await sendResendEmail({
|
||||
apiKey: String(config.resendApiKey).trim(),
|
||||
from: String(config.resendFromEmail).trim(),
|
||||
to: email,
|
||||
subject,
|
||||
html
|
||||
})
|
||||
} catch (error) {
|
||||
await deleteOtpChallengeById(sql, challengeId)
|
||||
throw error
|
||||
}
|
||||
|
||||
await invalidatePendingOtpChallengesExcept(sql, email, purpose, challengeId)
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
|
||||
Reference in New Issue
Block a user