import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/lib/db' import { hashPassword } from '@/lib/auth' import { sendEmail } from '@/lib/email' import { randomBytes } from 'crypto' import { z } from 'zod' import { registerLimiter, getClientIp, rateLimitResponse } from '@/lib/rate-limit' const registerSchema = z.object({ organizationName: z.string().min(2, 'Organisationsname zu kurz').max(200), name: z.string().min(2, 'Name zu kurz').max(200), email: z.string().email('Ungültige E-Mail-Adresse'), password: z.string().min(8, 'Passwort muss mindestens 8 Zeichen haben'), }) export async function POST(req: NextRequest) { try { const ip = getClientIp(req) const rl = registerLimiter.check(ip) if (!rl.success) return rateLimitResponse(rl.resetAt) const body = await req.json() const data = registerSchema.parse(body) // Check if email already exists const existingUser = await (prisma as any).user.findUnique({ where: { email: data.email }, include: { memberships: true }, }) if (existingUser) { // If the user is an orphan (no memberships) or never verified their email, // clean them up so they can re-register const isOrphan = !existingUser.memberships || existingUser.memberships.length === 0 const isUnverified = existingUser.emailVerified === false if (isOrphan || isUnverified) { // Force-delete orphan/unverified user and all their remaining data try { await (prisma as any).upgradeRequest.deleteMany({ where: { requestedById: existingUser.id } }) await (prisma as any).iconAsset.updateMany({ where: { ownerId: existingUser.id }, data: { ownerId: null } }) await (prisma as any).project.updateMany({ where: { ownerId: existingUser.id }, data: { ownerId: null } }) await (prisma as any).tenantMembership.deleteMany({ where: { userId: existingUser.id } }) await (prisma as any).user.delete({ where: { id: existingUser.id } }) console.log(`[Register] Cleaned up orphan/unverified user: ${data.email}`) } catch (cleanupErr) { console.error('[Register] Failed to cleanup existing user:', cleanupErr) return NextResponse.json({ error: 'Diese E-Mail-Adresse ist bereits registriert. Bitte kontaktieren Sie den Administrator.' }, { status: 400 }) } } else { return NextResponse.json({ error: 'Diese E-Mail-Adresse ist bereits registriert.' }, { status: 400 }) } } // Generate slug from organization name let slug = data.organizationName .toLowerCase() .replace(/[äÄ]/g, 'ae').replace(/[öÖ]/g, 'oe').replace(/[üÜ]/g, 'ue') .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, '') // Ensure slug is unique const existingTenant = await (prisma as any).tenant.findUnique({ where: { slug } }) if (existingTenant) { slug = `${slug}-${Date.now().toString(36)}` } // Hash password const hashedPassword = await hashPassword(data.password) // Generate email verification token const verificationToken = randomBytes(32).toString('hex') // Create tenant (no trial, directly ACTIVE) with privacy consent const tenant = await (prisma as any).tenant.create({ data: { name: data.organizationName, slug, plan: 'FREE', subscriptionStatus: 'ACTIVE', maxUsers: 5, maxProjects: 10, contactEmail: data.email, privacyAccepted: body.privacyAccepted === true, privacyAcceptedAt: body.privacyAccepted ? new Date() : null, adminAccessAccepted: body.adminAccessAccepted === true, }, }) // Create user as TENANT_ADMIN with email not yet verified const user = await (prisma as any).user.create({ data: { email: data.email, password: hashedPassword, name: data.name, role: 'TENANT_ADMIN', emailVerified: false, emailVerificationToken: verificationToken, }, }) // Create tenant membership await (prisma as any).tenantMembership.create({ data: { userId: user.id, tenantId: tenant.id, role: 'TENANT_ADMIN', }, }) // Send verification email let baseUrl = process.env.NEXTAUTH_URL || req.headers.get('origin') || `${req.headers.get('x-forwarded-proto') || 'https'}://${req.headers.get('host')}` || 'http://localhost:3000' if (baseUrl && !baseUrl.startsWith('http://') && !baseUrl.startsWith('https://')) { baseUrl = `https://${baseUrl}` } const verifyUrl = `${baseUrl}/api/auth/verify-email?token=${verificationToken}` try { await sendEmail( data.email, 'E-Mail-Adresse bestätigen — Lageplan', `

E-Mail bestätigen

Hallo ${data.name},

Bitte bestätigen Sie Ihre E-Mail-Adresse, um Ihr Konto für ${data.organizationName} zu aktivieren.

E-Mail bestätigen

Falls der Button nicht funktioniert, kopieren Sie diesen Link:
${verifyUrl}

` ) } catch (e) { console.warn('Failed to send verification email:', e) } // Notify server admin about new registration (#13) try { const adminSetting = await (prisma as any).systemSetting.findUnique({ where: { key: 'notify_registration_email' } }) const adminEmail = adminSetting?.value if (adminEmail) { await sendEmail( adminEmail, `Neue Registrierung — ${data.organizationName}`, `

Neue Registrierung

Organisation: ${data.organizationName}

Name: ${data.name}

E-Mail: ${data.email}

Mandant-Slug: ${slug}

Datum: ${new Date().toLocaleString('de-CH')}

` ) } } catch (e) { console.warn('Failed to send registration notification:', e) } return NextResponse.json({ success: true, message: 'Registrierung erfolgreich! Bitte bestätigen Sie Ihre E-Mail-Adresse.', tenantSlug: tenant.slug, requiresVerification: true, }, { status: 201 }) } catch (error) { if (error instanceof z.ZodError) { const firstError = error.errors[0] return NextResponse.json({ error: firstError.message }, { status: 400 }) } console.error('Registration error:', error) return NextResponse.json({ error: 'Registrierung fehlgeschlagen. Bitte versuchen Sie es später.' }, { status: 500 }) } }