import bcrypt from 'bcrypt' import { z } from 'zod' import { createError, getRequestIP, readBody } from 'h3' import { getUserByEmail, touchUserActivity } from '../../repositories/member-repository' import { setMemberSession } from '../../utils/member-auth' const loginSchema = z.object({ email: z.string().trim().email(), password: z.string().min(1) }) /** * 회원 로그인 API * @param {import('h3').H3Event} event - 요청 이벤트 * @returns {Promise<{ id: string, username: string, email: string }>} 회원 정보 */ export default defineEventHandler(async (event) => { const parsedBody = loginSchema.safeParse(await readBody(event)) if (!parsedBody.success) { throw createError({ statusCode: 400, message: '로그인 요청 형식이 올바르지 않습니다.' }) } const body = parsedBody.data const user = await getUserByEmail(body.email) if (!user || !(await bcrypt.compare(body.password, user.passwordHash))) { throw createError({ statusCode: 401, message: '이메일 또는 비밀번호가 올바르지 않습니다.' }) } setMemberSession(event, { userId: user.id, email: user.email }) await touchUserActivity({ userId: user.id, ip: String(getRequestIP(event) || '') }) return { id: user.id, username: user.username, email: user.email, avatarUrl: user.avatarUrl || '' } })