more QOL improvements
Build Images and Deploy / Update-PROD-Stack (push) Successful in 29s

This commit is contained in:
2026-02-28 01:37:32 -05:00
parent b9981d0e70
commit 4f9e92bda7
18 changed files with 426 additions and 32 deletions
+35 -1
View File
@@ -56,6 +56,7 @@ router.post('/hunts', (req, res) => {
try {
const huntId = Hunts.create(name, shortName, description, count, expiry_date, req.session.userId);
req.session.flash = { type: 'success', message: `Hunt "${name}" created with ${count} packages.` };
res.redirect(`/admin/hunts/${huntId}`);
} catch (err) {
console.error('Hunt creation error:', err);
@@ -73,7 +74,40 @@ router.get('/hunts/:id', (req, res) => {
const packages = Packages.getByHunt(hunt.id);
const baseUrl = process.env.BASE_URL || `http://localhost:${process.env.PORT || 3000}`;
res.render('admin/manage-hunt', { title: `Manage: ${hunt.name}`, hunt, packages, baseUrl });
const stats = Hunts.getStats(hunt.id);
res.render('admin/manage-hunt', { title: `Manage: ${hunt.name}`, hunt, packages, baseUrl, stats });
});
// 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.' });
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.' });
const { name, description, expiry_date } = req.body;
if (!name || !name.trim()) {
return res.render('admin/edit-hunt', { title: `Edit: ${hunt.name}`, hunt: { ...hunt, ...req.body }, error: 'Hunt name is required.' });
}
Hunts.update(hunt.id, name.trim(), (description || '').trim(), expiry_date);
req.session.flash = { type: 'success', message: 'Hunt updated successfully.' };
res.redirect(`/admin/hunts/${hunt.id}`);
});
// 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.' });
Hunts.delete(hunt.id);
req.session.flash = { type: 'success', message: `Hunt "${hunt.name}" deleted.` };
res.redirect('/admin');
});
// Download PDF of QR codes
+1
View File
@@ -63,6 +63,7 @@ router.post('/register', (req, res) => {
req.session.userId = userId;
req.session.username = username;
req.session.isAdmin = false;
req.session.flash = { type: 'success', message: 'Account created! Welcome to Loot Hunt.' };
const returnTo = req.session.returnTo || '/';
delete req.session.returnTo;
+16 -5
View File
@@ -22,14 +22,22 @@ router.get('/hunt/:shortName/leaderboard', (req, res) => {
return res.status(404).render('error', { title: 'Not Found', message: 'Hunt not found.' });
}
const leaderboard = Hunts.getLeaderboard(hunt.id);
res.render('hunt/leaderboard', { title: `${hunt.name} - Leaderboard`, hunt, leaderboard });
const perPage = 25;
const page = Math.max(1, parseInt(req.query.page) || 1);
const totalCount = Hunts.getLeaderboardCount(hunt.id);
const totalPages = Math.max(1, Math.ceil(totalCount / perPage));
const leaderboard = Hunts.getLeaderboard(hunt.id, perPage, (page - 1) * perPage);
res.render('hunt/leaderboard', { title: `${hunt.name} - Leaderboard`, hunt, leaderboard, page, totalPages, offset: (page - 1) * perPage });
});
// ─── Global leaderboard ──────────────────────────────────
router.get('/leaderboard', (req, res) => {
const leaderboard = Scans.getGlobalLeaderboard();
res.render('leaderboard/global', { title: 'Global Leaderboard', leaderboard });
const perPage = 25;
const page = Math.max(1, parseInt(req.query.page) || 1);
const totalCount = Scans.getGlobalLeaderboardCount();
const totalPages = Math.max(1, Math.ceil(totalCount / perPage));
const leaderboard = Scans.getGlobalLeaderboard(perPage, (page - 1) * perPage);
res.render('leaderboard/global', { title: 'Global Leaderboard', leaderboard, page, totalPages, offset: (page - 1) * perPage });
});
// ─── Package profile (by card number — no secret code exposed) ────
@@ -74,13 +82,16 @@ router.get('/player/:username', (req, res) => {
const rank = Users.getRank(user.id);
const totalPlayers = Users.getTotalPlayerCount();
const isOwnProfile = req.session && req.session.userId === user.id;
res.render('player/profile', {
title: `${user.username}'s Profile`,
profile,
recentScans,
huntBreakdown,
rank,
totalPlayers
totalPlayers,
isOwnProfile
});
});
+4
View File
@@ -113,6 +113,7 @@ router.post('/:shortName/:code/image', requireAuth, upload.single('image'), (req
const imagePath = `/uploads/${req.file.filename}`;
Packages.updateFirstScanImage(pkg.id, imagePath);
req.session.flash = { type: 'success', message: 'Photo uploaded successfully.' };
res.redirect(`/hunt/${shortName}/${pkg.card_number}`);
});
@@ -139,6 +140,7 @@ router.post('/:shortName/:code/image/delete', requireAuth, (req, res) => {
}
Packages.removeFirstScanImage(pkg.id);
req.session.flash = { type: 'success', message: 'Image removed.' };
res.redirect(`/hunt/${shortName}/${pkg.card_number}`);
});
@@ -157,6 +159,7 @@ router.post('/:shortName/:code/hint', requireAuth, (req, res) => {
const hint = (req.body.hint || '').trim().substring(0, 500);
Packages.updateLastScanHint(pkg.id, req.session.userId, hint);
req.session.flash = { type: 'success', message: 'Hint saved.' };
res.redirect(`/hunt/${shortName}/${pkg.card_number}`);
});
@@ -171,6 +174,7 @@ router.post('/:shortName/:code/hint/delete', requireAuth, (req, res) => {
return res.status(404).render('error', { title: 'Not Found', message: 'Package not found.' });
}
Packages.clearHint(pkg.id);
req.session.flash = { type: 'success', message: 'Hint cleared.' };
res.redirect(`/hunt/${shortName}/${pkg.card_number}`);
});