Files
livedash-node/app/api/register/route.ts
Kaj Kowalski 93fbb44eec feat: comprehensive Biome linting fixes and code quality improvements
Major code quality overhaul addressing 58% of all linting issues:

• Type Safety Improvements:
  - Replace all any types with proper TypeScript interfaces
  - Fix Map component shadowing (renamed to CountryMap)
  - Add comprehensive custom error classes system
  - Enhance API route type safety

• Accessibility Enhancements:
  - Add explicit button types to all interactive elements
  - Implement useId() hooks for form element accessibility
  - Add SVG title attributes for screen readers
  - Fix static element interactions with keyboard handlers

• React Best Practices:
  - Resolve exhaustive dependencies warnings with useCallback
  - Extract nested component definitions to top level
  - Fix array index keys with proper unique identifiers
  - Improve component organization and prop typing

• Code Organization:
  - Automatic import organization and type import optimization
  - Fix unused function parameters and variables
  - Enhanced error handling with structured error responses
  - Improve component reusability and maintainability

Results: 248 → 104 total issues (58% reduction)
- Fixed all critical type safety and security issues
- Enhanced accessibility compliance significantly
- Improved code maintainability and performance
2025-06-29 07:35:45 +02:00

137 lines
3.4 KiB
TypeScript

import bcrypt from "bcryptjs";
import { type NextRequest, NextResponse } from "next/server";
import { prisma } from "../../../lib/prisma";
import { registerSchema, validateInput } from "../../../lib/validation";
// In-memory rate limiting (for production, use Redis or similar)
const registrationAttempts = new Map<
string,
{ count: number; resetTime: number }
>();
function checkRateLimit(ip: string): boolean {
const now = Date.now();
const attempts = registrationAttempts.get(ip);
if (!attempts || now > attempts.resetTime) {
registrationAttempts.set(ip, { count: 1, resetTime: now + 60 * 60 * 1000 }); // 1 hour window
return true;
}
if (attempts.count >= 3) {
// Max 3 registrations per hour per IP
return false;
}
attempts.count++;
return true;
}
export async function POST(request: NextRequest) {
try {
// Rate limiting check
const ip =
request.ip || request.headers.get("x-forwarded-for") || "unknown";
if (!checkRateLimit(ip)) {
return NextResponse.json(
{
success: false,
error: "Too many registration attempts. Please try again later.",
},
{ status: 429 }
);
}
const body = await request.json();
// Validate input with Zod schema
const validation = validateInput(registerSchema, body);
if (!validation.success) {
return NextResponse.json(
{
success: false,
error: "Validation failed",
details: validation.errors,
},
{ status: 400 }
);
}
const { email, password, company } = validation.data;
// Check if email exists
const existingUser = await prisma.user.findUnique({
where: { email },
});
if (existingUser) {
return NextResponse.json(
{
success: false,
error: "Email already exists",
},
{ status: 409 }
);
}
// Check if company name already exists
const existingCompany = await prisma.company.findFirst({
where: { name: company },
});
if (existingCompany) {
return NextResponse.json(
{
success: false,
error: "Company name already exists",
},
{ status: 409 }
);
}
// Create company and user in a transaction
const result = await prisma.$transaction(async (tx) => {
const newCompany = await tx.company.create({
data: {
name: company,
csvUrl: "", // Empty by default, can be set later in settings
},
});
const hashedPassword = await bcrypt.hash(password, 12); // Increased rounds for better security
const newUser = await tx.user.create({
data: {
email,
password: hashedPassword,
companyId: newCompany.id,
role: "USER", // Changed from ADMIN - users should be promoted by existing admins
},
});
return { company: newCompany, user: newUser };
});
return NextResponse.json(
{
success: true,
data: {
message: "Registration successful",
userId: result.user.id,
companyId: result.company.id,
},
},
{ status: 201 }
);
} catch (error) {
console.error("Registration error:", error);
return NextResponse.json(
{
success: false,
error: "Internal server error",
},
{ status: 500 }
);
}
}