import { NextResponse } from 'next/server'; import argon2 from 'argon2'; import { createPrismaClient } from '@startover/database'; const prisma = createPrismaClient(); export async function POST(request: Request) { const { token, password, name } = await request.json(); if (!token || !password || !name) { return NextResponse.json({ error: '필수 정보가 누락되었습니다' }, { status: 400 }); } if (password.length < 8 || !/[a-zA-Z]/.test(password) || !/[0-9]/.test(password)) { return NextResponse.json( { error: '비밀번호는 8자 이상, 영문+숫자를 포함해야 합니다' }, { status: 400 }, ); } const inviteToken = await prisma.inviteToken.findUnique({ where: { token } }); if (!inviteToken) { return NextResponse.json({ error: '유효하지 않은 초대입니다' }, { status: 400 }); } if (inviteToken.usedAt) { return NextResponse.json({ error: '이미 사용된 초대입니다' }, { status: 400 }); } if (inviteToken.expires < new Date()) { return NextResponse.json({ error: '초대가 만료되었습니다' }, { status: 400 }); } const emailNormalized = inviteToken.email.toLowerCase().trim(); const existing = await prisma.user.findFirst({ where: { emailNormalized } }); if (existing) { return NextResponse.json({ error: '이미 가입된 이메일입니다' }, { status: 400 }); } const passwordHash = await argon2.hash(password, { type: argon2.argon2id }); await prisma.$transaction(async (tx) => { const user = await tx.user.create({ data: { email: inviteToken.email, emailNormalized, name, passwordHash, primaryRole: inviteToken.role, status: 'ACTIVE', emailVerifiedAt: new Date(), }, }); await tx.userProfile.create({ data: { userId: user.id, profileType: 'OPERATOR', }, }); await tx.inviteToken.update({ where: { id: inviteToken.id }, data: { usedAt: new Date() }, }); await tx.auditLog.create({ data: { actorUserId: user.id, resourceType: 'User', resourceId: user.id.toString(), actionType: 'OPERATOR_INVITED_ACCEPTED', afterJson: { role: inviteToken.role, invitedBy: inviteToken.createdBy.toString() }, }, }); }); return NextResponse.json({ success: true }); }