source: Klonkt/src/routes/admin-seo.js@ 3ca24f9

main
Last change on this file since 3ca24f9 was 6623453, checked in by roboburr <roboburr@…>, 3 months ago

Admin: dedicated SEO page (/admin/seo) for advanced SEO

The <head> already consumed a rich SEO set (title_template, canonical,
default_description, og_image_default, og_locale, author, *_verification,
facebook_app_id, publisher_*/schema_type for JSON-LD), but many fields
had nowhere to be edited. New god-only page manages the full set for the
primary site, accessible via a 🔎 SEO button in /admin.

The old hidden SEO block in Appearance has been removed (+ its columns
from the appearance-save so saving no longer zeroes them out) and replaced
with a link to the new page — single source of truth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@…>

  • Property mode set to 100644
File size: 3.1 KB
Line 
1/**
2 * Admin: geavanceerde SEO-instellingen van de primaire site.
3 *
4 * GET /admin/seo -> formulier met alle SEO/social-velden van de hoofdsite
5 * POST /admin/seo -> opslaan (god-only)
6 *
7 * Deze velden worden al door de <head> (shell.ejs) en de JSON-LD/OpenGraph-
8 * tags geconsumeerd, maar waren tot nu toe nergens te bewerken. De basis-
9 * velden (titel/bio/robots) blijven in Uiterlijk; dit is de geavanceerde laag:
10 * titel-sjabloon, canonical, social-share-afbeelding, verificatie-metas,
11 * publisher/JSON-LD en OpenGraph-locale.
12 *
13 * Werkt op de PRIMAIRE site (solo = de enige site; hub = de bedrijfssite).
14 */
15
16import express from 'express';
17import db from '../config/database.js';
18import { renderPage } from '../middleware/render.js';
19import { requireGod } from '../middleware/auth.js';
20import { getPrimarySite } from '../middleware/site.js';
21
22const router = express.Router();
23
24function trimOrNull(v, max) {
25 const s = (v == null ? '' : String(v)).trim();
26 return s ? s.slice(0, max) : null;
27}
28
29// ==================== FORM ====================
30router.get('/', requireGod, (req, res) => {
31 const primary = getPrimarySite();
32 if (!primary) {
33 return res.redirect('/admin/sites/new?error=' + encodeURIComponent('Maak eerst een site aan'));
34 }
35 const site = db.prepare('SELECT * FROM sites WHERE id = ?').get(primary.id);
36
37 renderPage(req, res, 'pages/admin-seo', {
38 pageTitle: 'SEO',
39 bodyClass: 'on-admin',
40 site,
41 success: req.query.success || null,
42 error: req.query.error || null,
43 });
44});
45
46// ==================== SAVE ====================
47router.post('/', requireGod, (req, res) => {
48 const primary = getPrimarySite();
49 if (!primary) return res.redirect('/admin/seo?error=' + encodeURIComponent('Geen site gevonden'));
50
51 const f = req.body;
52 const schemaType = f.schema_type === 'Organization' ? 'Organization' : 'Person';
53
54 db.prepare(`
55 UPDATE sites SET
56 robots_index = ?,
57 title_template = ?,
58 canonical = ?,
59 default_description = ?,
60 og_image_default = ?,
61 og_locale = ?,
62 author = ?,
63 twitter = ?,
64 facebook_app_id = ?,
65 google_verification = ?,
66 bing_verification = ?,
67 pinterest_verification = ?,
68 yandex_verification = ?,
69 schema_type = ?,
70 publisher_name = ?,
71 publisher_url = ?,
72 publisher_logo = ?,
73 updated_at = CURRENT_TIMESTAMP
74 WHERE id = ?
75 `).run(
76 f.robots_index ? 1 : 0,
77 (f.title_template || '{title} — {site}').slice(0, 200),
78 trimOrNull(f.canonical, 200),
79 trimOrNull(f.default_description, 500),
80 trimOrNull(f.og_image_default, 500),
81 trimOrNull(f.og_locale, 32),
82 trimOrNull(f.author, 120),
83 trimOrNull(f.twitter, 64),
84 trimOrNull(f.facebook_app_id, 64),
85 trimOrNull(f.google_verification, 200),
86 trimOrNull(f.bing_verification, 200),
87 trimOrNull(f.pinterest_verification, 200),
88 trimOrNull(f.yandex_verification, 200),
89 schemaType,
90 trimOrNull(f.publisher_name, 200),
91 trimOrNull(f.publisher_url, 200),
92 trimOrNull(f.publisher_logo, 500),
93 primary.id,
94 );
95
96 res.redirect('/admin/seo?success=' + encodeURIComponent('SEO-instellingen opgeslagen'));
97});
98
99export default router;
Note: See TracBrowser for help on using the repository browser.