Files
questionnaire/server/routes/admin.ts

284 lines
8.0 KiB
TypeScript

import { Router, Request, Response } from 'express';
import { v4 as uuidv4 } from 'uuid';
import crypto from 'crypto';
import { userOps, questionnaireOps, activityOps, participantOps } from '../database.js';
import { requireAuth } from '../middleware/auth.js';
const router = Router();
// Apply auth middleware to all admin routes
router.use(requireAuth);
// Get all questionnaires
router.get('/questionnaires', (req: Request, res: Response) => {
const questionnaires = questionnaireOps.getAll();
res.json(questionnaires);
});
// Validate slug format
function isValidSlug(slug: string): boolean {
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug);
}
// Create questionnaire
router.post('/questionnaires', (req: Request, res: Response) => {
const { title, description, slug, isPrivate } = req.body;
if (!title?.trim()) {
res.status(400).json({ error: 'Titel is verplicht' });
return;
}
if (!slug?.trim()) {
res.status(400).json({ error: 'Slug is verplicht' });
return;
}
const cleanSlug = slug.trim().toLowerCase();
if (!isValidSlug(cleanSlug)) {
res.status(400).json({ error: 'Slug mag alleen kleine letters, cijfers en koppeltekens bevatten' });
return;
}
if (cleanSlug.length < 3 || cleanSlug.length > 50) {
res.status(400).json({ error: 'Slug moet tussen 3 en 50 tekens zijn' });
return;
}
if (!questionnaireOps.isSlugAvailable(cleanSlug)) {
res.status(400).json({ error: 'Deze slug is al in gebruik' });
return;
}
const uuid = uuidv4();
const id = questionnaireOps.create(uuid, cleanSlug, title.trim(), description?.trim() || null, !!isPrivate, req.session.user!.id);
const questionnaire = questionnaireOps.findById(id);
res.json({ success: true, questionnaire });
});
// Get questionnaire by ID
router.get('/questionnaires/:id', (req: Request, res: Response) => {
const questionnaire = questionnaireOps.findById(parseInt(req.params.id));
if (!questionnaire) {
res.status(404).json({ error: 'Vragenlijst niet gevonden' });
return;
}
const activities = activityOps.getByQuestionnaire(questionnaire.id);
res.json({ questionnaire, activities });
});
// Update questionnaire
router.put('/questionnaires/:id', (req: Request, res: Response) => {
const { title, description, slug, isPrivate } = req.body;
const questionnaire = questionnaireOps.findById(parseInt(req.params.id));
if (!questionnaire) {
res.status(404).json({ error: 'Vragenlijst niet gevonden' });
return;
}
if (!title?.trim()) {
res.status(400).json({ error: 'Titel is verplicht' });
return;
}
if (!slug?.trim()) {
res.status(400).json({ error: 'Slug is verplicht' });
return;
}
const cleanSlug = slug.trim().toLowerCase();
if (!isValidSlug(cleanSlug)) {
res.status(400).json({ error: 'Slug mag alleen kleine letters, cijfers en koppeltekens bevatten' });
return;
}
if (cleanSlug.length < 3 || cleanSlug.length > 50) {
res.status(400).json({ error: 'Slug moet tussen 3 en 50 tekens zijn' });
return;
}
if (!questionnaireOps.isSlugAvailable(cleanSlug, questionnaire.id)) {
res.status(400).json({ error: 'Deze slug is al in gebruik' });
return;
}
questionnaireOps.update(questionnaire.id, cleanSlug, title.trim(), description?.trim() || null, !!isPrivate);
res.json({ success: true });
});
// Check slug availability
router.get('/questionnaires/check-slug/:slug', (req: Request, res: Response) => {
const { slug } = req.params;
const { excludeId } = req.query;
const cleanSlug = slug.trim().toLowerCase();
const available = questionnaireOps.isSlugAvailable(cleanSlug, excludeId ? parseInt(excludeId as string) : undefined);
res.json({ available });
});
// Get participants for a questionnaire
router.get('/questionnaires/:id/participants', (req: Request, res: Response) => {
const questionnaire = questionnaireOps.findById(parseInt(req.params.id));
if (!questionnaire) {
res.status(404).json({ error: 'Vragenlijst niet gevonden' });
return;
}
const participants = participantOps.getByQuestionnaire(questionnaire.id);
res.json(participants);
});
// Generate unique token
function generateToken(): string {
return crypto.randomBytes(16).toString('hex');
}
// Add participant to questionnaire
router.post('/questionnaires/:id/participants', (req: Request, res: Response) => {
const { name } = req.body;
const questionnaire = questionnaireOps.findById(parseInt(req.params.id));
if (!questionnaire) {
res.status(404).json({ error: 'Vragenlijst niet gevonden' });
return;
}
if (!name?.trim()) {
res.status(400).json({ error: 'Naam is verplicht' });
return;
}
// Generate unique token
let token = generateToken();
while (!participantOps.isTokenAvailable(token)) {
token = generateToken();
}
const id = participantOps.create(questionnaire.id, name.trim(), token);
const participant = participantOps.findById(id);
res.json({ success: true, participant });
});
// Delete participant
router.delete('/participants/:id', (req: Request, res: Response) => {
participantOps.delete(parseInt(req.params.id));
res.json({ success: true });
});
// Delete questionnaire
router.delete('/questionnaires/:id', (req: Request, res: Response) => {
questionnaireOps.delete(parseInt(req.params.id));
res.json({ success: true });
});
// Update activity
router.put('/activities/:id', (req: Request, res: Response) => {
const { name, description } = req.body;
const activity = activityOps.findById(parseInt(req.params.id));
if (!activity) {
res.status(404).json({ error: 'Activiteit niet gevonden' });
return;
}
if (!name?.trim()) {
res.status(400).json({ error: 'Naam activiteit is verplicht' });
return;
}
activityOps.update(activity.id, name.trim(), description?.trim() || null);
const updatedActivity = activityOps.findById(activity.id);
res.json({ success: true, activity: updatedActivity });
});
// Delete activity
router.delete('/activities/:id', (req: Request, res: Response) => {
activityOps.delete(parseInt(req.params.id));
res.json({ success: true });
});
// Get all users
router.get('/users', (req: Request, res: Response) => {
const users = userOps.getAll();
res.json(users);
});
// Create user
router.post('/users', (req: Request, res: Response) => {
const { username, password } = req.body;
if (!username || !password) {
res.status(400).json({ error: 'Gebruikersnaam en wachtwoord zijn verplicht' });
return;
}
if (password.length < 6) {
res.status(400).json({ error: 'Wachtwoord moet minimaal 6 tekens zijn' });
return;
}
const existingUser = userOps.findByUsername(username);
if (existingUser) {
res.status(400).json({ error: 'Gebruikersnaam bestaat al' });
return;
}
const id = userOps.create(username, password);
const user = userOps.findById(id);
res.json({ success: true, user });
});
// Delete user
router.delete('/users/:id', (req: Request, res: Response) => {
const userId = parseInt(req.params.id);
if (userId === req.session.user!.id) {
res.status(400).json({ error: 'Je kunt jezelf niet verwijderen' });
return;
}
if (userOps.count() <= 1) {
res.status(400).json({ error: 'Je kunt de laatste beheerder niet verwijderen' });
return;
}
userOps.delete(userId);
res.json({ success: true });
});
// Change password
router.post('/change-password', (req: Request, res: Response) => {
const { currentPassword, newPassword } = req.body;
const user = userOps.findByUsername(req.session.user!.username);
if (!user) {
res.status(404).json({ error: 'Gebruiker niet gevonden' });
return;
}
if (!userOps.verifyPassword(user, currentPassword)) {
res.status(400).json({ error: 'Huidig wachtwoord is onjuist' });
return;
}
if (newPassword.length < 6) {
res.status(400).json({ error: 'Wachtwoord moet minimaal 6 tekens zijn' });
return;
}
userOps.updatePassword(req.session.user!.id, newPassword);
res.json({ success: true });
});
export default router;