118 lines
3.3 KiB
JavaScript
118 lines
3.3 KiB
JavaScript
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;
|