import { Router } from 'express'; import { z } from 'zod'; import { db } from '../db/index.js'; import { users } from '../db/schema.js'; import { eq } from 'drizzle-orm'; import { hashPassword, verifyPassword, generateToken, authenticateHttp, AuthenticatedRequest, } from '../auth/index.js'; const router = Router(); const registerSchema = z.object({ username: z .string() .min(2) .max(64) .regex(/^[a-zA-Z0-9_-]+$/, 'Username must be alphanumeric with dashes/underscores'), email: z.string().email().max(255), password: z.string().min(8).max(128), }); const loginSchema = z.object({ email: z.string().email(), password: z.string(), }); router.post('/register', async (req, res) => { try { const body = registerSchema.parse(req.body); const existingUser = await db.query.users.findFirst({ where: eq(users.email, body.email), }); if (existingUser) { res.status(409).json({ error: 'Email already registered' }); return; } const existingUsername = await db.query.users.findFirst({ where: eq(users.username, body.username), }); if (existingUsername) { res.status(409).json({ error: 'Username already taken' }); return; } const passwordHash = await hashPassword(body.password); const [user] = await db .insert(users) .values({ username: body.username, email: body.email, passwordHash, }) .returning({ id: users.id, username: users.username, email: users.email, createdAt: users.createdAt, }); const token = generateToken({ userId: user!.id, username: user!.username }); res.status(201).json({ user: { id: user!.id, username: user!.username, email: user!.email, createdAt: user!.createdAt, }, token, }); } catch (err) { if (err instanceof z.ZodError) { res.status(400).json({ error: 'Validation failed', details: err.errors }); return; } console.error('Registration error:', err); res.status(500).json({ error: 'Internal server error' }); } }); router.post('/login', async (req, res) => { try { const body = loginSchema.parse(req.body); const user = await db.query.users.findFirst({ where: eq(users.email, body.email), }); if (!user) { res.status(401).json({ error: 'Invalid email or password' }); return; } const valid = await verifyPassword(body.password, user.passwordHash); if (!valid) { res.status(401).json({ error: 'Invalid email or password' }); return; } const token = generateToken({ userId: user.id, username: user.username }); res.json({ user: { id: user.id, username: user.username, email: user.email, createdAt: user.createdAt, }, token, }); } catch (err) { if (err instanceof z.ZodError) { res.status(400).json({ error: 'Validation failed', details: err.errors }); return; } console.error('Login error:', err); res.status(500).json({ error: 'Internal server error' }); } }); router.get('/me', authenticateHttp, async (req, res) => { try { const authReq = req as AuthenticatedRequest; const userId = authReq.user!.userId; const user = await db.query.users.findFirst({ where: eq(users.id, userId), columns: { id: true, username: true, email: true, createdAt: true, }, }); if (!user) { res.status(404).json({ error: 'User not found' }); return; } res.json({ user }); } catch (err) { console.error('Get user error:', err); res.status(500).json({ error: 'Internal server error' }); } }); export default router;