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

main
Last change on this file since bb9e54c was bb9e54c, checked in by Robin <roboburr@…>, 3 weeks ago

SEO-paneel kon niets opslaan: twee regels stonden in de SQL-string

In f50a84b belandden de twee const-regels van de MusicBrainz-koppeling BINNEN
de template-literal van de UPDATE. Daarmee waren ze geen code maar tekst in de
SQL, en faalde elke opslag op /admin/seo met near "/": syntax error.

Niet alleen de koppeling: het hele paneel. Titelsjabloon, canonical,
omschrijving, og:image, alle verificatiecodes, publisher -- alles ging door
dezelfde UPDATE.

Waarom niemand het zag. Het is geldig JavaScript, dus node --check, de lader en
de suite bleven groen; de fout leeft pas bij prepare(). En het zoekscherm loopt
langs een aparte GET, dus 'Ben jij dit?' toonde de artiest netjes -- alleen
bewaren deed niets. Het actor-document liet daarom terecht geen schema:sameAs
zien: er stond niets in de kolom. Gemeten op soundfabrics: mb_artist_id is
null, op alle instanties.

Vier routetests erbij, door de echte route heen, plus de tweede helft: dat de
koppeling ook echt als schema:sameAs op de actor komt. Controleproef gedraaid --
met de oude opstelling vallen alle vier om terwijl node --check groen blijft.

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

  • Property mode set to 100644
File size: 6.1 KB
RevLine 
[6623453]1/**
[834bcc3]2 * Admin: advanced SEO settings for the primary site.
[6623453]3 *
[834bcc3]4 * GET /admin/seo -> form with all SEO/social fields for the main site
5 * POST /admin/seo -> save (god-only)
[6623453]6 *
[834bcc3]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.
[6623453]12 *
[834bcc3]13 * Operates on the PRIMARY site (solo = the only site; hub = the company site).
[6623453]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';
[013e16c]21import { isMbid } from '../services/ap-core.js';
22import MusicBrainz from '../services/MusicBrainzService.js';
[6623453]23
24const router = express.Router();
25
26function trimOrNull(v, max) {
27 const s = (v == null ? '' : String(v)).trim();
28 return s ? s.slice(0, max) : null;
29}
30
31// ==================== FORM ====================
32router.get('/', requireGod, (req, res) => {
33 const primary = getPrimarySite();
34 if (!primary) {
35 return res.redirect('/admin/sites/new?error=' + encodeURIComponent('Maak eerst een site aan'));
36 }
37 const site = db.prepare('SELECT * FROM sites WHERE id = ?').get(primary.id);
38
39 renderPage(req, res, 'pages/admin-seo', {
[3487567]40 pageTitleKey: 'admin.t_seo',
[013e16c]41 pageJs: 'admin-seo',
[6623453]42 bodyClass: 'on-admin',
43 site,
44 success: req.query.success || null,
45 error: req.query.error || null,
46 });
47});
48
[013e16c]49/**
50 * "Zoek jezelf op" -- kandidaten uit MusicBrainz (shaer-mbz).
51 *
52 * De zoekopdracht draait HIER en niet in de browser: MusicBrainz staat een
53 * verzoek per seconde toe per APPLICATIE, en dat is alleen af te dwingen als
54 * alles langs een plek gaat. Bovendien eisen ze een User-Agent met contact, en
55 * die kan een browser niet zetten.
56 *
57 * Wij kiezen NIET. Ook niet als er precies een treffer is: een verkeerd geraden
58 * MBID zet jouw naam onder andermans werk.
59 */
60router.get('/api/musicbrainz', requireGod, async (req, res) => {
61 const site = getPrimarySite(req);
62 const q = String(req.query.q || (site && (site.publisher_name || site.title)) || '').trim();
63 if (!q) return res.json({ ok: true, q: '', kandidaten: [] });
[f50a84b]64 // Wie zijn id al kent plakt het hier. Een zoekopdracht op een UUID levert bij
65 // MusicBrainz niets op, dus zonder deze tak geeft plakken juist het slechtste
66 // resultaat.
67 if (isMbid(q)) {
68 const een = await MusicBrainz.haalArtiest(q);
69 return res.json({ ok: true, q, kandidaten: een ? [een] : [] });
70 }
[013e16c]71 res.json({ ok: true, q, kandidaten: await MusicBrainz.zoekArtiesten(q) });
72});
73
[f50a84b]74/**
75 * De terug-weg: noemt de MusicBrainz-pagina ons domein? (shaer-mbz)
76 *
77 * Een koppeling van onze kant is een bewering -- iedereen kan een id typen.
78 * Pas als de artiestenpagina TERUGWIJST is het een paar. Wij zetten die
79 * verwijzing niet zelf: dat kan niet via hun API en hoort ook niet, de artiest
80 * doet dat op musicbrainz.org onder "social networking".
81 */
82router.get('/api/musicbrainz/terugweg', requireGod, async (req, res) => {
83 const mbid = String(req.query.mbid || '').trim().toLowerCase();
84 const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
85 if (!isMbid(mbid) || !base) return res.json({ ok: true, verified: false, urls: [] });
86 res.json({ ok: true, ...(await MusicBrainz.controleerTerugweg(mbid, base)) });
87});
88
[6623453]89// ==================== SAVE ====================
90router.post('/', requireGod, (req, res) => {
91 const primary = getPrimarySite();
92 if (!primary) return res.redirect('/admin/seo?error=' + encodeURIComponent('Geen site gevonden'));
93
94 const f = req.body;
95 const schemaType = f.schema_type === 'Organization' ? 'Organization' : 'Person';
96
[bb9e54c]97 // De MusicBrainz-koppeling (shaer-mbz). Alleen een echte MBID komt de kolom
98 // in: zonder deze zeef sluipt er een URL of een handle in het veld dat naar
99 // buiten gaat, en het gaat naar TWEE uitgangen -- de JSON-LD en de actor.
100 // Leeg is een geldige keuze; dat is ontkoppelen.
101 //
102 // Deze twee regels stonden BINNEN de template-literal hieronder, dus ze waren
103 // geen code maar tekst in de SQL. Daardoor faalde elke opslag op dit paneel
104 // met `near "/": syntax error` -- niet alleen de koppeling, ook het
105 // titelsjabloon, de omschrijving en alle verificatiecodes.
106 const mbRuw = String(f.mb_artist_id || '').trim().toLowerCase();
107 const mbArtistId = isMbid(mbRuw) ? mbRuw : null;
108
[6623453]109 db.prepare(`
110 UPDATE sites SET
111 robots_index = ?,
112 title_template = ?,
113 canonical = ?,
114 default_description = ?,
115 og_image_default = ?,
[3b4095f]116 og_theme = ?,
[6623453]117 og_locale = ?,
118 author = ?,
119 twitter = ?,
120 facebook_app_id = ?,
121 google_verification = ?,
122 bing_verification = ?,
123 pinterest_verification = ?,
124 yandex_verification = ?,
125 schema_type = ?,
126 publisher_name = ?,
127 publisher_url = ?,
128 publisher_logo = ?,
[013e16c]129 mb_artist_id = ?, mb_artist_name = ?,
[6623453]130 updated_at = CURRENT_TIMESTAMP
131 WHERE id = ?
132 `).run(
133 f.robots_index ? 1 : 0,
134 (f.title_template || '{title} — {site}').slice(0, 200),
135 trimOrNull(f.canonical, 200),
136 trimOrNull(f.default_description, 500),
137 trimOrNull(f.og_image_default, 500),
[3b4095f]138 (f.og_theme === 'light' || f.og_theme === 'dark') ? f.og_theme : null, // null = auto (follow site theme)
[6623453]139 trimOrNull(f.og_locale, 32),
140 trimOrNull(f.author, 120),
141 trimOrNull(f.twitter, 64),
142 trimOrNull(f.facebook_app_id, 64),
143 trimOrNull(f.google_verification, 200),
144 trimOrNull(f.bing_verification, 200),
145 trimOrNull(f.pinterest_verification, 200),
146 trimOrNull(f.yandex_verification, 200),
147 schemaType,
148 trimOrNull(f.publisher_name, 200),
149 trimOrNull(f.publisher_url, 200),
150 trimOrNull(f.publisher_logo, 500),
[013e16c]151 mbArtistId,
152 mbArtistId ? (String(f.mb_artist_name || '').trim().slice(0, 200) || null) : null,
[6623453]153 primary.id,
154 );
155
156 res.redirect('/admin/seo?success=' + encodeURIComponent('SEO-instellingen opgeslagen'));
157});
158
159export default router;
Note: See TracBrowser for help on using the repository browser.