source: Klonkt/src/routes/shows.js@ 5c27e8e

main
Last change on this file since 5c27e8e was 8d32dcf, checked in by roboburr <roboburr@…>, 3 months ago

Show agenda + notify-me (premium feature #8) — last of the 8

  • DB: shows (site_id, date, time, city, venue, country, ticket_url, notes).
  • routes/shows.js (public, premium-gated): GET /shows (upcoming shows + notify form), POST /shows/notify (subscribers source 'notify', double opt-in if SMTP; confirm/unsub via the generic /nieuwsbrief links).
  • routes/admin-shows.js (site admin + premium): GET /admin/shows (list + add form + subscriber count), POST / (add; optional notify email to 'notify' subscribers if SMTP is available), POST /:id/delete.
  • SubscriberService.confirmedFor(siteId, source?) — optional source filter ('notify').
  • views: pages/shows.ejs + pages/admin-shows.ejs. Dashboard 📅 Agenda link.

Notify email requires SMTP (like #1/#2 sending); without SMTP the show is saved
and sign-ups still work. node --check passed.

Co-Authored-By: Claude <noreply@…>

  • Property mode set to 100644
File size: 3.2 KB
RevLine 
[8d32dcf]1/**
2 * Show-agenda + notify-me (premium feature #8) — publieke kant.
3 *
4 * GET /shows -> komende optredens + "houd me op de hoogte"-formulier
5 * POST /shows/notify -> aanmelden voor show-aankondigingen (subscribers, source
6 * 'notify'; double opt-in als SMTP er is)
7 *
8 * De notify-bevestiging/uitschrijving hergebruikt de generieke subscriber-links
9 * (/nieuwsbrief/bevestigen|uitschrijven/:token). Hub: /user/:slug/shows.
10 */
11
12import express from 'express';
13import db from '../config/database.js';
14import { renderPage } from '../middleware/render.js';
15import { premiumUnlocked } from '../services/PatreonService.js';
16import { mailerConfigured, sendMail } from '../config/mailer.js';
17import { addSubscriber } from '../services/SubscriberService.js';
18
19const router = express.Router();
20
21function esc(s) { return String(s || '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }
22function fullUrl(req, p) {
23 const base = (process.env.PUBLIC_BASE_URL || ('https://' + (req.get('host') || ''))).replace(/\/$/, '');
24 return base + (req.res.locals.siteUrlBase || '') + p;
25}
26function upcoming(siteId) {
27 const today = new Date().toISOString().slice(0, 10);
28 return db.prepare('SELECT * FROM shows WHERE site_id = ? AND date >= ? ORDER BY date ASC, time ASC').all(siteId, today);
29}
30
31router.get('/shows', (req, res, next) => {
32 if (!premiumUnlocked()) return next();
33 const site = res.locals.site;
34 if (!site) return next();
35 renderPage(req, res, 'pages/shows', {
36 pageTitle: 'Agenda — ' + (site.title || ''),
37 bodyClass: 'on-shows',
38 shows: upcoming(site.id),
39 notifyState: req.query.ok ? 'done' : (req.query.check ? 'check' : null),
40 });
41});
42
43router.post('/shows/notify', async (req, res, next) => {
44 if (!premiumUnlocked()) return next();
45 const site = res.locals.site;
46 if (!site) return next();
47 const email = (req.body.email || '').trim();
48 const doubleOptin = mailerConfigured();
49 const r = addSubscriber(site.id, email, 'notify', { doubleOptin });
50 if (!r.ok) {
51 return renderPage(req, res, 'pages/shows', {
52 pageTitle: 'Agenda', bodyClass: 'on-shows', shows: upcoming(site.id),
53 notifyState: 'error', notifyMsg: r.error === 'invalid_email' ? 'Controleer je e-mailadres.' : 'Er ging iets mis.',
54 });
55 }
56 if (r.status === 'pending') {
57 const link = fullUrl(req, '/nieuwsbrief/bevestigen/' + r.token);
58 const unsub = fullUrl(req, '/nieuwsbrief/uitschrijven/' + r.token);
59 try {
60 await sendMail({
61 to: email,
62 subject: 'Bevestig — show-updates van ' + (site.title || ''),
63 text: 'Bevestig dat je show-aankondigingen wilt ontvangen: ' + link + '\n\nUitschrijven: ' + unsub,
64 html: '<p>Bevestig dat je show-aankondigingen van <strong>' + esc(site.title) + '</strong> wilt ontvangen:</p>' +
65 '<p><a href="' + link + '">Bevestigen</a></p><p style="color:#888;font-size:12px"><a href="' + unsub + '">Uitschrijven</a></p>',
66 });
67 } catch { return res.redirect((res.locals.siteUrlBase || '') + '/shows'); }
68 return res.redirect((res.locals.siteUrlBase || '') + '/shows?check=1');
69 }
70 res.redirect((res.locals.siteUrlBase || '') + '/shows?ok=1');
71});
72
73export default router;
Note: See TracBrowser for help on using the repository browser.