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

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

feat(seo): og:image light/dark override setting

The auto-generated share card followed the site's default theme (1.2.0); Admin →
SEO now has an explicit override: Auto (follow site theme) / Light / Dark.

  • src/config/database.js — sites.og_theme column (NULL = auto).
  • src/services/OgImageService.js — og_theme wins over theme_override when set.
  • src/routes/og.js — select og_theme for the card route.
  • src/routes/admin-seo.js — save og_theme (validated to light/dark, else auto).
  • src/views/pages/admin-seo.ejs — select under the share-image field.
  • src/services/i18n.js — aseo.og_theme* (nl/en/de).
  • CHANGELOG(.nl/.de).md — under Added.

Closes prutfolio-src-krm.

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

  • Property mode set to 100644
File size: 3.3 KB
Line 
1/**
2 * Admin: advanced SEO settings for the primary site.
3 *
4 * GET /admin/seo -> form with all SEO/social fields for the main site
5 * POST /admin/seo -> save (god-only)
6 *
7 * These fields are already consumed by the <head> (shell.ejs) and the JSON-LD/
8 * OpenGraph tags, but were previously not editable anywhere. The basic
9 * fields (title/bio/robots) remain in Appearance; this is the advanced layer:
10 * title template, canonical, social share image, verification metas,
11 * publisher/JSON-LD and OpenGraph locale.
12 *
13 * Operates on the PRIMARY site (solo = the only site; hub = the company site).
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 pageTitleKey: 'admin.t_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_theme = ?,
62 og_locale = ?,
63 author = ?,
64 twitter = ?,
65 facebook_app_id = ?,
66 google_verification = ?,
67 bing_verification = ?,
68 pinterest_verification = ?,
69 yandex_verification = ?,
70 schema_type = ?,
71 publisher_name = ?,
72 publisher_url = ?,
73 publisher_logo = ?,
74 updated_at = CURRENT_TIMESTAMP
75 WHERE id = ?
76 `).run(
77 f.robots_index ? 1 : 0,
78 (f.title_template || '{title} — {site}').slice(0, 200),
79 trimOrNull(f.canonical, 200),
80 trimOrNull(f.default_description, 500),
81 trimOrNull(f.og_image_default, 500),
82 (f.og_theme === 'light' || f.og_theme === 'dark') ? f.og_theme : null, // null = auto (follow site theme)
83 trimOrNull(f.og_locale, 32),
84 trimOrNull(f.author, 120),
85 trimOrNull(f.twitter, 64),
86 trimOrNull(f.facebook_app_id, 64),
87 trimOrNull(f.google_verification, 200),
88 trimOrNull(f.bing_verification, 200),
89 trimOrNull(f.pinterest_verification, 200),
90 trimOrNull(f.yandex_verification, 200),
91 schemaType,
92 trimOrNull(f.publisher_name, 200),
93 trimOrNull(f.publisher_url, 200),
94 trimOrNull(f.publisher_logo, 500),
95 primary.id,
96 );
97
98 res.redirect('/admin/seo?success=' + encodeURIComponent('SEO-instellingen opgeslagen'));
99});
100
101export default router;
Note: See TracBrowser for help on using the repository browser.