source: Klonkt/src/routes/admin-shows.js@ d3b9f68

main
Last change on this file since d3b9f68 was 318e1ce, checked in by Robin Genis <roboburr@…>, 2 months ago

fix(i18n): translate the premium-gate 403 messages (newsletter/agenda)

Two hardcoded Dutch res.send(403) messages now use a generic aset.premium_gate key
(with {feature}, reusing the admin.t_* title keys), resolved to the request language.

  • services/i18n.js — aset.premium_gate (nl/en/de)
  • routes/admin-newsletter.js, admin-shows.js — translate the 403
  • Property mode set to 100644
File size: 4.5 KB
RevLine 
[8d32dcf]1/**
[834bcc3]2 * Show agenda (premium feature #8) — admin side.
[8d32dcf]3 *
[834bcc3]4 * GET /admin/shows -> list + add form
5 * POST /admin/shows -> add show (optional notify email to subscribers)
[8d32dcf]6 * POST /admin/shows/:id/delete
7 *
[834bcc3]8 * Premium + site manager. Notify email requires SMTP; without SMTP the show is
9 * simply saved (no email sent).
[8d32dcf]10 */
11
12import express from 'express';
13import db from '../config/database.js';
14import { v4 as uuid } from 'uuid';
15import { renderPage } from '../middleware/render.js';
16import { requireSiteManager } from '../middleware/auth.js';
17import { premiumUnlocked } from '../services/PatreonService.js';
18import { mailerConfigured, sendMail } from '../config/mailer.js';
19import { confirmedFor, counts } from '../services/SubscriberService.js';
[68b27a4]20import { getSetting, setSetting } from '../services/SettingsService.js';
[318e1ce]21import { t, resolveLang } from '../services/i18n.js';
[8d32dcf]22
23const router = express.Router();
24
25function esc(s) { return String(s || '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }
26function fullUrl(req, p) {
27 const base = (process.env.PUBLIC_BASE_URL || ('https://' + (req.get('host') || ''))).replace(/\/$/, '');
28 return base + (req.res.locals.siteUrlBase || '') + p;
29}
30function premiumGate(req, res, next) {
[318e1ce]31 if (!premiumUnlocked()) {
32 const lang = resolveLang(req, { defaultLang: getSetting('default_lang') });
33 return res.status(403).send(t(lang, 'aset.premium_gate', { feature: t(lang, 'admin.t_shows') }));
34 }
[8d32dcf]35 next();
36}
37function render(req, res, extra = {}) {
38 const site = res.locals.site;
39 const shows = db.prepare('SELECT * FROM shows WHERE site_id = ? ORDER BY date DESC, time DESC').all(site.id);
40 renderPage(req, res, 'pages/admin-shows', {
[3487567]41 pageTitleKey: 'admin.t_shows', bodyClass: 'on-admin',
[8d32dcf]42 shows, smtp: mailerConfigured(), notifyCount: confirmedFor(site.id, 'notify').length,
[68b27a4]43 agendaEnabled: getSetting('agenda_enabled') === '1',
[8d32dcf]44 ...extra,
45 });
46}
47
48router.get('/', requireSiteManager, premiumGate, (req, res) => {
49 if (!res.locals.site) return res.status(404).send('Geen site.');
50 render(req, res);
51});
52
53router.post('/', requireSiteManager, premiumGate, async (req, res) => {
54 const site = res.locals.site;
55 if (!site) return res.status(404).send('Geen site.');
56 const b = req.body || {};
57 const date = (b.date || '').trim();
58 const city = (b.city || '').trim();
59 if (!date || !city) return render(req, res, { msg: 'Datum en plaats zijn verplicht.', msgKind: 'bad' });
60 let ticket = (b.ticket_url || '').trim();
61 if (ticket && !/^https?:\/\//i.test(ticket)) ticket = '';
62
63 db.prepare(`INSERT INTO shows (id, site_id, date, time, city, venue, country, ticket_url, notes)
64 VALUES (?,?,?,?,?,?,?,?,?)`).run(
65 uuid(), site.id, date, (b.time || '').trim() || null, city, (b.venue || '').trim() || null,
66 (b.country || '').trim() || null, ticket || null, (b.notes || '').trim() || null,
67 );
68
69 let sent = 0;
70 if (b.notify && mailerConfigured()) {
71 const subs = confirmedFor(site.id, 'notify');
72 const where = city + (b.venue ? ' — ' + b.venue : '');
73 for (const s of subs) {
74 const unsub = fullUrl(req, '/nieuwsbrief/uitschrijven/' + s.token);
75 try {
76 await sendMail({
77 to: s.email,
78 subject: 'Nieuwe show: ' + where + ' (' + date + ')',
79 text: (site.title || '') + ' speelt op ' + date + ' in ' + where + '.' + (ticket ? ('\nTickets: ' + ticket) : '') + '\n\nUitschrijven: ' + unsub,
80 html: '<p><strong>' + esc(site.title) + '</strong> speelt op <strong>' + esc(date) + '</strong> in ' + esc(where) + '.</p>' +
81 (ticket ? ('<p><a href="' + ticket + '">Tickets</a></p>') : '') +
82 '<p style="color:#888;font-size:12px"><a href="' + unsub + '">Uitschrijven</a></p>',
83 });
84 sent++;
[834bcc3]85 } catch { /* skip */ }
[8d32dcf]86 }
87 }
88 render(req, res, { msg: 'Show toegevoegd.' + (sent ? (' Notify gestuurd naar ' + sent + ' abonnee(s).') : ''), msgKind: 'ok' });
89});
90
[68b27a4]91router.post('/toggle', requireSiteManager, premiumGate, (req, res) => {
[834bcc3]92 // Show the agenda on the site (Agenda button in the pill + /shows page).
[68b27a4]93 setSetting('agenda_enabled', req.body.enabled ? '1' : '0');
94 res.redirect((res.locals.siteUrlBase || '') + '/admin/shows');
95});
96
[8d32dcf]97router.post('/:id/delete', requireSiteManager, premiumGate, (req, res) => {
98 const site = res.locals.site;
99 if (site) db.prepare('DELETE FROM shows WHERE id = ? AND site_id = ?').run(req.params.id, site.id);
100 res.redirect((res.locals.siteUrlBase || '') + '/admin/shows');
101});
102
103export default router;
Note: See TracBrowser for help on using the repository browser.