import { Router } from "express"; import bcrypt from "bcryptjs"; import jwt from "jsonwebtoken"; import { z } from "zod"; import { prisma } from "../db"; export const authRouter = Router(); const registerSchema = z.object({ username: z.string().min(3), password: z.string().min(6), inviteCode: z.string().min(4), }); authRouter.post("/register", async (req, res) => { const parsed = registerSchema.safeParse(req.body); if (!parsed.success) { return res.status(400).json({ error: "Invalid payload" }); } const { username, password, inviteCode } = parsed.data; const now = new Date(); try { const result = await prisma.$transaction(async (tx) => { const invite = await tx.invite.findFirst({ where: { code: inviteCode, revokedAt: null, expiresAt: { gt: now }, }, }); if (!invite || invite.usedCount >= invite.maxUses) { throw new Error("Invalid invite"); } const existing = await tx.user.findUnique({ where: { username } }); if (existing) { throw new Error("Username taken"); } const passwordHash = await bcrypt.hash(password, 10); const user = await tx.user.create({ data: { username, passwordHash }, }); await tx.invite.update({ where: { id: invite.id }, data: { usedCount: invite.usedCount + 1 }, }); return user; }); const token = jwt.sign({ userId: result.id }, process.env.JWT_SECRET || "dev-secret", { expiresIn: "7d", }); return res.json({ token, user: { id: result.id, username: result.username } }); } catch (err) { const message = err instanceof Error ? err.message : "Register failed"; const status = message === "Invalid invite" ? 400 : 409; return res.status(status).json({ error: message }); } }); const loginSchema = z.object({ username: z.string().min(3), password: z.string().min(6), }); authRouter.post("/login", async (req, res) => { const parsed = loginSchema.safeParse(req.body); if (!parsed.success) { return res.status(400).json({ error: "Invalid payload" }); } const { username, password } = parsed.data; const user = await prisma.user.findUnique({ where: { username } }); if (!user) { return res.status(401).json({ error: "Invalid credentials" }); } const ok = await bcrypt.compare(password, user.passwordHash); if (!ok) { return res.status(401).json({ error: "Invalid credentials" }); } const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET || "dev-secret", { expiresIn: "7d", }); return res.json({ token, user: { id: user.id, username: user.username } }); });