Initial commit
This commit is contained in:
6
app/api/auth/[...nextauth]/route.ts
Normal file
6
app/api/auth/[...nextauth]/route.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import NextAuth from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
87
app/api/register/route.ts
Normal file
87
app/api/register/route.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { hash } from "bcryptjs";
|
||||
import { NextResponse } from "next/server";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { DEFAULT_THEME_ID } from "@/lib/constants";
|
||||
import { db } from "@/lib/db";
|
||||
import { normalizeUsername, sanitizePlainText } from "@/lib/sanitize";
|
||||
import { registerSchema } from "@/lib/validators/auth";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
// Rate limiting should be applied here (IP/email key) at proxy or middleware level in production.
|
||||
const body = await request.json().catch(() => null);
|
||||
if (!body) {
|
||||
return NextResponse.json({ message: "Invalid JSON payload" }, { status: 400 });
|
||||
}
|
||||
|
||||
const parsed = registerSchema.safeParse({
|
||||
email: sanitizePlainText(String(body.email ?? "")).toLowerCase(),
|
||||
password: String(body.password ?? ""),
|
||||
username: normalizeUsername(String(body.username ?? "")),
|
||||
displayName: sanitizePlainText(String(body.displayName ?? ""))
|
||||
});
|
||||
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ message: parsed.error.issues[0]?.message ?? "Invalid input" }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const existingEmail = await db.user.findUnique({
|
||||
where: { email: parsed.data.email },
|
||||
select: { id: true }
|
||||
});
|
||||
|
||||
if (existingEmail) {
|
||||
return NextResponse.json({ message: "Email is already registered" }, { status: 409 });
|
||||
}
|
||||
|
||||
const existingUsername = await db.profile.findUnique({
|
||||
where: { username: parsed.data.username },
|
||||
select: { id: true }
|
||||
});
|
||||
|
||||
if (existingUsername) {
|
||||
return NextResponse.json({ message: "Username is already taken" }, { status: 409 });
|
||||
}
|
||||
|
||||
const hashedPassword = await hash(parsed.data.password, 12);
|
||||
|
||||
await db.$transaction(async (tx) => {
|
||||
const theme = await tx.theme.findUnique({ where: { id: DEFAULT_THEME_ID } });
|
||||
if (!theme) {
|
||||
throw new Error(`Missing default theme: ${DEFAULT_THEME_ID}`);
|
||||
}
|
||||
|
||||
const user = await tx.user.create({
|
||||
data: {
|
||||
email: parsed.data.email,
|
||||
hashedPassword
|
||||
}
|
||||
});
|
||||
|
||||
await tx.profile.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
username: parsed.data.username,
|
||||
displayName: parsed.data.displayName,
|
||||
themeId: DEFAULT_THEME_ID,
|
||||
isPublic: true
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientInitializationError) {
|
||||
return NextResponse.json(
|
||||
{ message: "Database is not reachable. Start PostgreSQL and run migrations first." },
|
||||
{ status: 503 }
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
|
||||
return NextResponse.json({ message: "Account already exists" }, { status: 409 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ message: "Could not create account" }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ message: "Account created" }, { status: 201 });
|
||||
}
|
||||
Reference in New Issue
Block a user