first commit

This commit is contained in:
2026-07-16 17:36:21 +02:00
commit a0d7163464
45 changed files with 7481 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
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);
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 332 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,
role: 'user',
},
});
await prisma.transaction.create({
data: {
userId: user.id,
type: 'seed_credit',
amount: STARTING_BALANCE,
meta: JSON.stringify({ reason: 'starting_balance' }),
},
});
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;