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

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

Naar SEO & social, want sameAs is schema.org (Robins vraag)

Hij vroeg of het niet onder SEO en social hoort, "omdat het ook een soort
koppeling is". Dat is het, en sterker dan dat: op die pagina staat een blok
"Uitgever (JSON-LD / rich results)", en sameAs IS een schema.org-eigenschap.
Zelfde vocabulaire, zelfde plek. Het paneel staat nu direct onder Uitgever.

DAARDOOR VOEDT EEN KOPPELING NU TWEE UITGANGEN. Naast schema:sameAs op de
fediverse-actor komt hij in de JSON-LD van de pagina zelf, in _publisher. Dat
is hetzelfde feit, verteld aan de andere lezer -- en het was gratis, want het
blok stond er al.

BETERE LABELS, want de oude waren geschreven voor het audioscherm. "Ben jij dit
in MusicBrainz?" werd "MusicBrainz (dezelfde artiest elders)" als legenda, met
een hint die zegt wat het OPLEVERT in plaats van alleen wat het is: je komt als
sameAs in de JSON-LD en in je fediverse-profiel, en er verandert niets bij
MusicBrainz zelf. Dat laatste is de geruststelling die er hoort te staan. Plus
een veldlabel ("Zoek jezelf op") met de toelichting "de naam waaronder je muziek
uitbrengt" -- want dat is de vraag, niet je sitenaam. Drie talen.

Het zoekveld begint nu op publisher_name en valt terug op de sitetitel: op deze
pagina heb je die naam net ingevuld.

Meeverhuisd: het endpoint (/admin/seo/api/musicbrainz) en het clientblok, in een
nieuwe mod/admin-seo.js. Die pagina had nog geen JavaScript; opzoeken vraagt er
wel om. Het profielscherm is weer helemaal schoon.

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

  • Property mode set to 100644
File size: 4.7 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';
21import { isMbid } from '../services/ap-core.js';
22import MusicBrainz from '../services/MusicBrainzService.js';
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', {
40 pageTitleKey: 'admin.t_seo',
41 pageJs: 'admin-seo',
42 bodyClass: 'on-admin',
43 site,
44 success: req.query.success || null,
45 error: req.query.error || null,
46 });
47});
48
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: [] });
64 res.json({ ok: true, q, kandidaten: await MusicBrainz.zoekArtiesten(q) });
65});
66
67// ==================== SAVE ====================
68router.post('/', requireGod, (req, res) => {
69 const primary = getPrimarySite();
70 if (!primary) return res.redirect('/admin/seo?error=' + encodeURIComponent('Geen site gevonden'));
71
72 const f = req.body;
73 const schemaType = f.schema_type === 'Organization' ? 'Organization' : 'Person';
74
75 db.prepare(`
76 UPDATE sites SET
77 robots_index = ?,
78 title_template = ?,
79 canonical = ?,
80 default_description = ?,
81 og_image_default = ?,
82 og_theme = ?,
83 og_locale = ?,
84 author = ?,
85 twitter = ?,
86 facebook_app_id = ?,
87 google_verification = ?,
88 bing_verification = ?,
89 pinterest_verification = ?,
90 yandex_verification = ?,
91 schema_type = ?,
92 publisher_name = ?,
93 publisher_url = ?,
94 publisher_logo = ?,
95 mb_artist_id = ?, mb_artist_name = ?,
96 updated_at = CURRENT_TIMESTAMP
97 WHERE id = ?
98 // De MusicBrainz-koppeling (shaer-mbz). Alleen een echte MBID komt de kolom
99 // in: zonder deze zeef sluipt er een URL of een handle in het veld dat naar
100 // buiten gaat, en het gaat naar TWEE uitgangen -- de JSON-LD en de actor.
101 // Leeg is een geldige keuze; dat is ontkoppelen.
102 const mbRuw = String(f.mb_artist_id || '').trim().toLowerCase();
103 const mbArtistId = isMbid(mbRuw) ? mbRuw : null;
104
105 `).run(
106 f.robots_index ? 1 : 0,
107 (f.title_template || '{title} — {site}').slice(0, 200),
108 trimOrNull(f.canonical, 200),
109 trimOrNull(f.default_description, 500),
110 trimOrNull(f.og_image_default, 500),
111 (f.og_theme === 'light' || f.og_theme === 'dark') ? f.og_theme : null, // null = auto (follow site theme)
112 trimOrNull(f.og_locale, 32),
113 trimOrNull(f.author, 120),
114 trimOrNull(f.twitter, 64),
115 trimOrNull(f.facebook_app_id, 64),
116 trimOrNull(f.google_verification, 200),
117 trimOrNull(f.bing_verification, 200),
118 trimOrNull(f.pinterest_verification, 200),
119 trimOrNull(f.yandex_verification, 200),
120 schemaType,
121 trimOrNull(f.publisher_name, 200),
122 trimOrNull(f.publisher_url, 200),
123 trimOrNull(f.publisher_logo, 500),
124 mbArtistId,
125 mbArtistId ? (String(f.mb_artist_name || '').trim().slice(0, 200) || null) : null,
126 primary.id,
127 );
128
129 res.redirect('/admin/seo?success=' + encodeURIComponent('SEO-instellingen opgeslagen'));
130});
131
132export default router;
Note: See TracBrowser for help on using the repository browser.