import { Router } from 'express'; import bcrypt from 'bcrypt'; import { prisma } from '../db.js'; import { publicUser, requireAuth } from '../middleware.js'; const router = Router(); const STARTING_BALANCE = Number(process.env.STARTING_BALANCE || 10000); const STARTING_OSU = Number(process.env.STARTING_OSU || 100); router.post('/register', async (req, res) => { try { const username = String(req.body.username || '').trim(); const password = String(req.body.password || ''); if (username.length < 3 || username.length > 32) { return res.status(400).json({ error: 'Username must be 3–32 characters' }); } if (!/^[a-zA-Z0-9_]+$/.test(username)) { return res.status(400).json({ error: 'Username may only contain letters, numbers, and underscores' }); } if (password.length < 6) { return res.status(400).json({ error: 'Password must be at least 6 characters' }); } const existing = await prisma.user.findUnique({ where: { username } }); if (existing) { return res.status(409).json({ error: 'Username already taken' }); } const passwordHash = await bcrypt.hash(password, 10); const user = await prisma.user.create({ data: { username, passwordHash, balance: STARTING_BALANCE, osuBalance: STARTING_OSU, role: 'user', }, }); await prisma.transaction.create({ data: { userId: user.id, type: 'seed_credit', amount: STARTING_BALANCE, meta: JSON.stringify({ reason: 'starting_balance' }), }, }); await prisma.transaction.create({ data: { userId: user.id, type: 'seed_osu', amount: STARTING_OSU, meta: JSON.stringify({ reason: 'starting_osu' }), }, }); req.session.userId = user.id; req.session.role = user.role; res.status(201).json({ user: publicUser(user) }); } catch (err) { console.error(err); res.status(500).json({ error: 'Registration failed' }); } }); router.post('/login', async (req, res) => { try { const username = String(req.body.username || '').trim(); const password = String(req.body.password || ''); const user = await prisma.user.findUnique({ where: { username } }); if (!user) { return res.status(401).json({ error: 'Invalid username or password' }); } const ok = await bcrypt.compare(password, user.passwordHash); if (!ok) { return res.status(401).json({ error: 'Invalid username or password' }); } req.session.userId = user.id; req.session.role = user.role; res.json({ user: publicUser(user) }); } catch (err) { console.error(err); res.status(500).json({ error: 'Login failed' }); } }); router.post('/logout', (req, res) => { req.session.destroy((err) => { if (err) { return res.status(500).json({ error: 'Logout failed' }); } res.clearCookie('connect.sid'); res.json({ ok: true }); }); }); router.get('/me', requireAuth, async (req, res) => { try { const user = await prisma.user.findUnique({ where: { id: req.session.userId } }); if (!user) { return res.status(401).json({ error: 'User not found' }); } res.json({ user: publicUser(user) }); } catch (err) { console.error(err); res.status(500).json({ error: 'Failed to load user' }); } }); export default router;