Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/admin-settings.js

    r63edb0d r0091cb7  
    22 * Admin: globale instellingen.
    33 *  - tenancy-modus (Solo/Hub)
    4  *  - hub-branding (naam/tagline/intro/hero van de generieke hub-hoofdpagina)
     4 *  - hub-branding (naam/tagline/intro van de generieke hub-hoofdpagina)
    55 *
    66 * GET  /admin/settings   -> toon huidige instellingen
    7  * POST /admin/settings   -> sla op (god-only). Accepteert nu ook een geuploade
    8  *                           hero-afbeelding (multipart); een upload wint van het
    9  *                           URL-tekstveld. Zonder upload blijft het URL-veld leidend.
     7 * POST /admin/settings   -> sla op (god-only)
    108 *
    119 * De hub-pagina is generiek (van geen enkele user); deze branding leeft in
     
    1412
    1513import express from 'express';
    16 import path from 'path';
    17 import fs from 'fs';
    18 import { fileURLToPath } from 'url';
    19 import multer from 'multer';
    20 import { v4 as uuid } from 'uuid';
    2114import { renderPage } from '../middleware/render.js';
    2215import { requireGod } from '../middleware/auth.js';
     
    2417
    2518const router = express.Router();
    26 
    27 // Hero dark-overlay: percentage 0-100 (0 = geen overlay, 100 = volledig zwart).
    28 // Default 45 = de oude hardgecodeerde waarde, zodat bestaande hubs niet wijzigen.
    29 function clampOverlay(raw) {
    30   const v = parseInt(raw, 10);
    31   return Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 45;
    32 }
    33 
    34 const __dirname = path.dirname(fileURLToPath(import.meta.url));
    35 // Hero-uploads landen in storage/media/hero → bereikbaar als /media/hero/<file>
    36 // (de /media static handler serveert storage/media). Zelfde model als avatars.
    37 const HERO_DIR = path.resolve(
    38   process.env.HERO_PATH || path.join(__dirname, '..', '..', 'storage', 'media', 'hero')
    39 );
    40 fs.mkdirSync(HERO_DIR, { recursive: true });
    41 
    42 // Alleen raster-formaten voor de upload. SVG mag bewust NIET via upload (raw
    43 // SVG kan script bevatten → opgeslagen-XSS bij direct openen); een SVG-hero kan
    44 // nog steeds via het URL-veld (zoals de meegeleverde demo-placeholder).
    45 const ALLOWED_HERO_EXT = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif']);
    46 const MAX_HERO_BYTES = 5 * 1024 * 1024;
    47 
    48 const heroUpload = multer({
    49   storage: multer.diskStorage({
    50     destination: (req, file, cb) => cb(null, HERO_DIR),
    51     filename: (req, file, cb) => {
    52       const ext = path.extname(file.originalname).toLowerCase();
    53       cb(null, `${uuid()}${ext}`);
    54     },
    55   }),
    56   limits: { fileSize: MAX_HERO_BYTES },
    57   fileFilter: (req, file, cb) => {
    58     const ext = path.extname(file.originalname).toLowerCase();
    59     if (!ALLOWED_HERO_EXT.has(ext)) {
    60       return cb(new Error('Hero-afbeelding moet jpg/png/webp/gif zijn'));
    61     }
    62     cb(null, true);
    63   },
    64 });
    6519
    6620router.get('/', requireGod, (req, res) => {
     
    7327    hubIntro: getSetting('hub_intro') || '',
    7428    hubHeroImage: getSetting('hub_hero_image') || '',
    75     hubHeroOverlay: clampOverlay(getSetting('hub_hero_overlay')),
    7629    success: req.query.success || null,
    77     error: req.query.error || null,
    7830  });
    7931});
    8032
    8133router.post('/', requireGod, (req, res) => {
    82   // multer.single verwerkt multipart (hub-branding form). Bij een gewone
    83   // urlencoded POST (tenancy-form) doet multer niets en blijft req.body intact.
    84   heroUpload.single('hub_hero_file')(req, res, (err) => {
    85     if (err) {
    86       return res.redirect('/admin/settings?error=' + encodeURIComponent(err.message));
    87     }
    88 
    89     if (typeof req.body.tenancy !== 'undefined') {
    90       setTenancy(req.body.tenancy === 'hub' ? 'hub' : 'solo');
    91     }
    92     if (typeof req.body.hub_title !== 'undefined') {
    93       setSetting('hub_title', (req.body.hub_title || '').toString().slice(0, 80).trim());
    94     }
    95     if (typeof req.body.hub_tagline !== 'undefined') {
    96       setSetting('hub_tagline', (req.body.hub_tagline || '').toString().slice(0, 120).trim());
    97     }
    98     if (typeof req.body.hub_intro !== 'undefined') {
    99       setSetting('hub_intro', (req.body.hub_intro || '').toString().slice(0, 400).trim());
    100     }
    101 
    102     // Hero: een geüploade afbeelding wint; anders het URL-tekstveld.
    103     if (req.file) {
    104       const newUrl = `/media/hero/${req.file.filename}`;
    105       // Ruim een vorige geüploade hero op (alleen als die uit onze hero-map kwam).
    106       const old = getSetting('hub_hero_image') || '';
    107       if (old.startsWith('/media/hero/')) {
    108         try { fs.unlinkSync(path.join(HERO_DIR, path.basename(old))); } catch {}
    109       }
    110       setSetting('hub_hero_image', newUrl);
    111     } else if (typeof req.body.hub_hero_image !== 'undefined') {
    112       setSetting('hub_hero_image', (req.body.hub_hero_image || '').toString().slice(0, 300).trim());
    113     }
    114 
    115     if (typeof req.body.hub_hero_overlay !== 'undefined') {
    116       setSetting('hub_hero_overlay', String(clampOverlay(req.body.hub_hero_overlay)));
    117     }
    118 
    119     res.redirect('/admin/settings?success=' + encodeURIComponent('Opgeslagen'));
    120   });
     34  if (typeof req.body.tenancy !== 'undefined') {
     35    setTenancy(req.body.tenancy); // valideert naar solo | hub | circle
     36  }
     37  if (typeof req.body.hub_title !== 'undefined') {
     38    setSetting('hub_title', (req.body.hub_title || '').toString().slice(0, 80).trim());
     39  }
     40  if (typeof req.body.hub_tagline !== 'undefined') {
     41    setSetting('hub_tagline', (req.body.hub_tagline || '').toString().slice(0, 120).trim());
     42  }
     43  if (typeof req.body.hub_intro !== 'undefined') {
     44    setSetting('hub_intro', (req.body.hub_intro || '').toString().slice(0, 400).trim());
     45  }
     46  if (typeof req.body.hub_hero_image !== 'undefined') {
     47    setSetting('hub_hero_image', (req.body.hub_hero_image || '').toString().slice(0, 300).trim());
     48  }
     49  res.redirect('/admin/settings?success=' + encodeURIComponent('Opgeslagen'));
    12150});
    12251
Note: See TracChangeset for help on using the changeset viewer.