esp32blockly/server/teacher.js

84 lines
2.7 KiB
JavaScript

import { Router } from 'express';
import bcrypt from 'bcryptjs';
import { requireTeacher } from './auth.js';
import { findUserByUsername, createUser, listStudents } from './db.js';
const router = Router();
const BCRYPT_ROUNDS = 8;
const USERNAME_RE = /^[a-zA-Z0-9_.-]{3,64}$/;
const WORDS_A = ['red', 'blue', 'green', 'happy', 'sunny', 'brave', 'tiny', 'swift', 'kind', 'bright'];
const WORDS_B = ['cat', 'dog', 'fox', 'duck', 'frog', 'bear', 'star', 'moon', 'kite', 'fish'];
const WORDS_C = ['jump', 'play', 'run', 'smile', 'spin', 'dance', 'wave', 'clap', 'sing', 'grow'];
function sanitizeStudent(row) {
return {
id: row.id,
username: row.username,
role: row.role,
createdAt: row.created_at,
};
}
function randomItem(list) {
return list[Math.floor(Math.random() * list.length)];
}
function candidateUsername() {
const useThree = Math.random() < 0.45;
const first = randomItem(WORDS_A);
const second = randomItem(WORDS_B);
if (!useThree) return `${first}${second}`;
const third = randomItem(WORDS_C);
return `${first}${second}${third}`;
}
async function generateUniqueUsername() {
for (let i = 0; i < 40; i += 1) {
const candidate = candidateUsername();
const existing = await findUserByUsername(candidate);
if (!existing) return candidate;
}
return `${candidateUsername()}${Math.floor(100 + Math.random() * 900)}`;
}
router.get('/students', requireTeacher, async (_req, res) => {
try {
const students = (await listStudents()).map(sanitizeStudent);
res.json({ students });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
router.get('/students/suggest-username', requireTeacher, async (_req, res) => {
try {
const username = await generateUniqueUsername();
res.json({ username });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
router.post('/students', requireTeacher, async (req, res) => {
let { username } = req.body || {};
try {
username = username ? String(username).trim() : '';
if (!username) username = await generateUniqueUsername();
if (!USERNAME_RE.test(username)) {
return res.status(400).json({ error: 'username must be 3-64 chars, letters/digits/._-' });
}
const existing = await findUserByUsername(username);
if (existing) return res.status(409).json({ error: 'username already taken' });
// Requested behavior: default password equals username.
const hash = await bcrypt.hash(username, BCRYPT_ROUNDS);
const user = await createUser({ username, passwordHash: hash, role: 'student' });
res.json({ student: sanitizeStudent(user), defaultPassword: username });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
export default router;