add organizer role and features
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 29s
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 29s
This commit is contained in:
@@ -1,17 +1,32 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { requireAdmin } = require('../middleware/auth');
|
||||
const { requireAdmin, requireOrganizerOrAdmin } = require('../middleware/auth');
|
||||
const { Hunts, Packages, Users } = require('../models');
|
||||
const { generateHuntPDF } = require('../utils/pdf');
|
||||
|
||||
// All admin routes require admin access
|
||||
router.use(requireAdmin);
|
||||
// Helper: check if user owns this hunt (or is admin)
|
||||
function requireHuntAccess(req, res, next) {
|
||||
const hunt = Hunts.findById(req.params.id);
|
||||
if (!hunt) return res.status(404).render('error', { title: 'Not Found', message: 'Hunt not found.' });
|
||||
// Admins can access any hunt; organizers only their own
|
||||
if (req.session.isAdmin || hunt.created_by === req.session.userId) {
|
||||
req.hunt = hunt;
|
||||
return next();
|
||||
}
|
||||
return res.status(403).render('error', { title: 'Forbidden', message: 'You can only manage your own hunts.' });
|
||||
}
|
||||
|
||||
// Admin dashboard
|
||||
// All admin routes require at least organizer access
|
||||
router.use(requireOrganizerOrAdmin);
|
||||
|
||||
// Admin/Organizer dashboard
|
||||
router.get('/', (req, res) => {
|
||||
const hunts = Hunts.getByCreator(req.session.userId);
|
||||
const users = Users.getAllUsers();
|
||||
res.render('admin/dashboard', { title: 'Admin Dashboard', hunts, users, resetUrl: null, resetUsername: null });
|
||||
const isAdmin = !!req.session.isAdmin;
|
||||
|
||||
// Only admins see the full user list and password reset
|
||||
const users = isAdmin ? Users.getAllUsers() : [];
|
||||
res.render('admin/dashboard', { title: isAdmin ? 'Admin Dashboard' : 'Organizer Dashboard', hunts, users, resetUrl: null, resetUsername: null, isAdmin });
|
||||
});
|
||||
|
||||
// Create hunt form
|
||||
@@ -68,9 +83,8 @@ router.post('/hunts', (req, res) => {
|
||||
});
|
||||
|
||||
// Manage hunt
|
||||
router.get('/hunts/:id', (req, res) => {
|
||||
const hunt = Hunts.findById(req.params.id);
|
||||
if (!hunt) return res.status(404).render('error', { title: 'Not Found', message: 'Hunt not found.' });
|
||||
router.get('/hunts/:id', requireHuntAccess, (req, res) => {
|
||||
const hunt = req.hunt;
|
||||
|
||||
const packages = Packages.getByHunt(hunt.id);
|
||||
const baseUrl = process.env.BASE_URL || `http://localhost:${process.env.PORT || 3000}`;
|
||||
@@ -79,16 +93,14 @@ router.get('/hunts/:id', (req, res) => {
|
||||
});
|
||||
|
||||
// Edit hunt form
|
||||
router.get('/hunts/:id/edit', (req, res) => {
|
||||
const hunt = Hunts.findById(req.params.id);
|
||||
if (!hunt) return res.status(404).render('error', { title: 'Not Found', message: 'Hunt not found.' });
|
||||
router.get('/hunts/:id/edit', requireHuntAccess, (req, res) => {
|
||||
const hunt = req.hunt;
|
||||
res.render('admin/edit-hunt', { title: `Edit: ${hunt.name}`, hunt, error: null });
|
||||
});
|
||||
|
||||
// Update hunt
|
||||
router.post('/hunts/:id/edit', (req, res) => {
|
||||
const hunt = Hunts.findById(req.params.id);
|
||||
if (!hunt) return res.status(404).render('error', { title: 'Not Found', message: 'Hunt not found.' });
|
||||
router.post('/hunts/:id/edit', requireHuntAccess, (req, res) => {
|
||||
const hunt = req.hunt;
|
||||
|
||||
const { name, description, expiry_date } = req.body;
|
||||
if (!name || !name.trim()) {
|
||||
@@ -101,9 +113,8 @@ router.post('/hunts/:id/edit', (req, res) => {
|
||||
});
|
||||
|
||||
// Delete hunt
|
||||
router.post('/hunts/:id/delete', (req, res) => {
|
||||
const hunt = Hunts.findById(req.params.id);
|
||||
if (!hunt) return res.status(404).render('error', { title: 'Not Found', message: 'Hunt not found.' });
|
||||
router.post('/hunts/:id/delete', requireHuntAccess, (req, res) => {
|
||||
const hunt = req.hunt;
|
||||
|
||||
Hunts.delete(hunt.id);
|
||||
req.session.flash = { type: 'success', message: `Hunt "${hunt.name}" deleted.` };
|
||||
@@ -111,9 +122,8 @@ router.post('/hunts/:id/delete', (req, res) => {
|
||||
});
|
||||
|
||||
// Download PDF of QR codes
|
||||
router.get('/hunts/:id/pdf', async (req, res) => {
|
||||
const hunt = Hunts.findById(req.params.id);
|
||||
if (!hunt) return res.status(404).render('error', { title: 'Not Found', message: 'Hunt not found.' });
|
||||
router.get('/hunts/:id/pdf', requireHuntAccess, async (req, res) => {
|
||||
const hunt = req.hunt;
|
||||
|
||||
const packages = Packages.getByHunt(hunt.id);
|
||||
const baseUrl = process.env.BASE_URL || `http://localhost:${process.env.PORT || 3000}`;
|
||||
@@ -129,8 +139,32 @@ router.get('/hunts/:id/pdf', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Generate password reset URL ──────────────────────────
|
||||
router.post('/reset-password', (req, res) => {
|
||||
// ─── Manage user roles (admin only) ───────────────────────
|
||||
router.post('/users/:id/role', requireAdmin, (req, res) => {
|
||||
const userId = parseInt(req.params.id, 10);
|
||||
const user = Users.findById(userId);
|
||||
if (!user) {
|
||||
req.session.flash = { type: 'danger', message: 'User not found.' };
|
||||
return res.redirect('/admin');
|
||||
}
|
||||
if (user.is_admin) {
|
||||
req.session.flash = { type: 'danger', message: 'Cannot change role of an admin.' };
|
||||
return res.redirect('/admin');
|
||||
}
|
||||
|
||||
const { role } = req.body;
|
||||
if (role === 'organizer') {
|
||||
Users.makeOrganizer(userId);
|
||||
req.session.flash = { type: 'success', message: `${user.username} is now an Organizer.` };
|
||||
} else {
|
||||
Users.removeOrganizer(userId);
|
||||
req.session.flash = { type: 'success', message: `${user.username} is no longer an Organizer.` };
|
||||
}
|
||||
res.redirect('/admin');
|
||||
});
|
||||
|
||||
// ─── Generate password reset URL (admin only) ────────────
|
||||
router.post('/reset-password', requireAdmin, (req, res) => {
|
||||
const { username } = req.body;
|
||||
const user = Users.findByUsername(username);
|
||||
|
||||
@@ -152,7 +186,8 @@ router.post('/reset-password', (req, res) => {
|
||||
hunts,
|
||||
users,
|
||||
resetUrl,
|
||||
resetUsername: username
|
||||
resetUsername: username,
|
||||
isAdmin: true
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ router.post('/login', (req, res) => {
|
||||
req.session.userId = user.id;
|
||||
req.session.username = user.username;
|
||||
req.session.isAdmin = !!user.is_admin;
|
||||
req.session.isOrganizer = !!user.is_organizer;
|
||||
|
||||
const returnTo = req.session.returnTo || '/';
|
||||
delete req.session.returnTo;
|
||||
@@ -63,6 +64,7 @@ router.post('/register', (req, res) => {
|
||||
req.session.userId = userId;
|
||||
req.session.username = username;
|
||||
req.session.isAdmin = false;
|
||||
req.session.isOrganizer = false;
|
||||
req.session.flash = { type: 'success', message: 'Account created! Welcome to Loot Hunt.' };
|
||||
|
||||
const returnTo = req.session.returnTo || '/';
|
||||
|
||||
Reference in New Issue
Block a user