Host-Routing: Bot-Produktseite und Community-Hub getrennt
Eine Anwendung, zwei Gesichter — der Server erkennt an der Domain, welche Seite gefragt ist, und schreibt das als data-site an den <body>. Das Frontend rendert daraufhin entweder den Community-Hub wie bisher oder die neue Produktseite. - Bot-Produktseite (BotApp): Landing mit Feature-Übersicht, Befehls- referenz, Dashboard unter /dashboard, Orange als Leitfarbe - Weiterleitungen: Community-Routen auf der Bot-Domain und umgekehrt werden dauerhaft (301) auf die richtige Adresse geschickt — geteilte Devlog-Permalinks laufen also nicht ins Leere. Rechtstexte bleiben auf beiden erreichbar. - Open-Graph-Tags je Domain, inklusive eigener Vorschau für frei angelegte Seiten (Entwürfe bekommen bewusst keine) - Login: neue Einstellung für die Cookie-Domain, damit die Anmeldung auf beiden Seiten gilt; nach dem Discord-Login landet man wieder auf der Seite, von der man gestartet ist (vorher immer auf der Hauptadresse) - Alles greift erst, wenn beide Adressen im Setup eingetragen sind — bis dahin verhält sich die Anwendung unverändert Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+56
-9
@@ -8,8 +8,8 @@ import { existsSync, readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { config } from '../config.js';
|
||||
import { saveCommits, saveRelease, takeBugReport, getDevlog } from '../db.js';
|
||||
import { commitFeedEnabled, branchAllowed, repoIgnored, publicUrl, brandName } from '../runtime-settings.js';
|
||||
import { saveCommits, saveRelease, takeBugReport, getDevlog, getPage } from '../db.js';
|
||||
import { commitFeedEnabled, branchAllowed, repoIgnored, publicUrl, brandName, hubUrl, botUrl } from '../runtime-settings.js';
|
||||
import { postPushEmbed } from '../bot/commit-feed.js';
|
||||
import { postReleaseEmbed } from '../bot/release-feed.js';
|
||||
import { registerAuthRoutes } from './auth.js';
|
||||
@@ -100,13 +100,21 @@ export async function startWebServer(client) {
|
||||
const indexHtml = readFileSync(join(frontendDist, 'index.html'), 'utf8');
|
||||
const esc = (s) => String(s).replaceAll('&', '&').replaceAll('<', '<').replaceAll('"', '"');
|
||||
|
||||
function ogTagsFor(url) {
|
||||
const base = publicUrl();
|
||||
let title = `${brandName()} // Community-Hub`;
|
||||
let description = 'Devlogs, Roadmap, Server-Status und alles aus der Community — direkt aus dem Discord.';
|
||||
function ogTagsFor(url, isBotSite) {
|
||||
const base = isBotSite ? botUrl() : hubUrl();
|
||||
let title = isBotSite
|
||||
? `${brandName()} // Der Bot`
|
||||
: `${brandName()} // Community-Hub`;
|
||||
let description = isBotSite
|
||||
? 'Devlogs, Level, Moderation, Server-Monitoring — ein selbst gebauter Discord-Bot ohne Paywall.'
|
||||
: 'Devlogs, Roadmap, Server-Status und alles aus der Community — direkt aus dem Discord.';
|
||||
let image = null;
|
||||
|
||||
const pageTitles = {
|
||||
const pageTitles = isBotSite ? {
|
||||
'/features': ['Funktionen', 'Alles, was der Bot kann — von Devlogs bis Server-Monitoring.'],
|
||||
'/commands': ['Befehle', 'Alle Slash-Commands im Überblick.'],
|
||||
'/dashboard': ['Dashboard', 'Einstellungen des Bots.'],
|
||||
} : {
|
||||
'/devlogs': ['Devlog-Archiv', 'Entwicklungs-Updates, automatisch archiviert.'],
|
||||
'/roadmap': ['Roadmap', 'Meilensteine und Community-Wünsche zum Abstimmen.'],
|
||||
'/server': ['Server-Status', 'Alle Game-Server live — Spielerzahlen und Verlauf.'],
|
||||
@@ -128,6 +136,14 @@ export async function startWebServer(client) {
|
||||
} else if (pageTitles[path]) {
|
||||
title = `${brandName()} // ${pageTitles[path][0]}`;
|
||||
description = pageTitles[path][1];
|
||||
} else if (!isBotSite && /^\/[a-z0-9-]+$/.test(path)) {
|
||||
// Frei angelegte Seite? Titel und Anriss daraus übernehmen
|
||||
const page = getPage(path.slice(1));
|
||||
if (page?.published) {
|
||||
const prose = page.content.replace(/^[-*>#]\s*/gm, '').replace(/[*`_]/g, '').replace(/\s+/g, ' ').trim();
|
||||
title = `${brandName()} // ${page.title}`;
|
||||
description = prose.slice(0, 200) + (prose.length > 200 ? ' …' : '');
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -136,15 +152,46 @@ export async function startWebServer(client) {
|
||||
`<meta property="og:description" content="${esc(description)}">`,
|
||||
`<meta property="og:url" content="${esc(base + path)}">`,
|
||||
`<meta property="og:type" content="website">`,
|
||||
`<meta name="theme-color" content="#f5c518">`,
|
||||
`<meta name="theme-color" content="${isBotSite ? '#ff4d00' : '#f5c518'}">`,
|
||||
`<meta name="description" content="${esc(description)}">`,
|
||||
image ? `<meta property="og:image" content="${esc(image)}">` : '',
|
||||
image ? `<meta name="twitter:card" content="summary_large_image">` : '<meta name="twitter:card" content="summary">',
|
||||
].filter(Boolean).join('\n ');
|
||||
}
|
||||
|
||||
// Welche der beiden Seiten ist gemeint? Solange keine getrennten
|
||||
// Adressen konfiguriert sind, verhält sich alles wie bisher.
|
||||
function isBotHost(request) {
|
||||
const bot = botUrl();
|
||||
const hub = hubUrl();
|
||||
if (bot === hub) return false; // noch nicht getrennt
|
||||
const host = String(request.headers.host ?? '').toLowerCase().split(':')[0];
|
||||
return host === new URL(bot).hostname;
|
||||
}
|
||||
|
||||
// Routen, die nur auf der Bot-Seite existieren — alles andere gehört zum Hub
|
||||
const BOT_ROUTES = ['/features', '/commands', '/docs', '/dashboard'];
|
||||
const SHARED_ROUTES = ['/impressum', '/datenschutz'];
|
||||
|
||||
const sendInjected = (request, reply) => {
|
||||
const html = indexHtml.replace('</head>', ` ${ogTagsFor(request.url)}\n</head>`);
|
||||
const botSite = isBotHost(request);
|
||||
const path = request.url.split('?')[0];
|
||||
|
||||
// Falsche Domain? Dauerhaft auf die richtige umleiten, damit alte
|
||||
// Links (geteilte Devlog-Permalinks) nicht ins Leere laufen.
|
||||
if (botUrl() !== hubUrl() && !SHARED_ROUTES.includes(path)) {
|
||||
const wantsBot = path === '/' ? false : BOT_ROUTES.some((r) => path.startsWith(r));
|
||||
if (wantsBot && !botSite) {
|
||||
return reply.redirect(`${botUrl()}${request.url}`, 301);
|
||||
}
|
||||
if (!wantsBot && botSite && path !== '/') {
|
||||
return reply.redirect(`${hubUrl()}${request.url}`, 301);
|
||||
}
|
||||
}
|
||||
|
||||
const html = indexHtml
|
||||
.replace('</head>', ` ${ogTagsFor(request.url, botSite)}\n</head>`)
|
||||
.replace('<body>', `<body data-site="${botSite ? 'bot' : 'hub'}">`);
|
||||
return reply.type('text/html; charset=utf-8').send(html);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user