Feature 4: Webinterface — React-Frontend, Discord-OAuth2, REST-API
- Discord-OAuth2-Login (identify-Scope, CSRF-State, signierte Session-Cookies, keine Token-Speicherung) - REST-API: /api/devlogs (öffentlich), /api/commits (nur ADMIN_DISCORD_ID), /api/me - React + Vite Frontend: Devlog-Archiv mit Mini-Markdown-Renderer, Commit-Tabelle, dunkles EcoGame-Theme - Fastify liefert frontend/dist mit SPA-Fallback aus; Vite-Dev-Proxy für lokale Entwicklung - Multi-Stage-Dockerfile (Frontend-Build im Image), neue Env-Vars in Compose + .env.example - README: OAuth2-Setup (Redirect-URLs, Client Secret) und Frontend-Workflow Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+38
-7
@@ -1,9 +1,20 @@
|
||||
// Fastify-Webserver: Gitea-Webhook-Endpoint (später auch REST-API + Webinterface)
|
||||
// Fastify-Webserver: Gitea-Webhook, Discord-OAuth2, REST-API + React-Frontend
|
||||
import Fastify from 'fastify';
|
||||
import fastifyCookie from '@fastify/cookie';
|
||||
import fastifyStatic from '@fastify/static';
|
||||
import crypto from 'node:crypto';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { config } from '../config.js';
|
||||
import { saveCommits } from '../db.js';
|
||||
import { postPushEmbed } from '../bot/commit-feed.js';
|
||||
import { registerAuthRoutes } from './auth.js';
|
||||
import { registerApiRoutes } from './api.js';
|
||||
|
||||
// Gebautes React-Frontend (frontend/dist) — im Container immer vorhanden,
|
||||
// lokal nur nach `npm run build` im frontend/-Ordner
|
||||
const frontendDist = join(dirname(fileURLToPath(import.meta.url)), '../../frontend/dist');
|
||||
|
||||
/** Gitea-Signatur prüfen: HMAC-SHA256 (hex) über den rohen Request-Body */
|
||||
function verifySignature(rawBody, signatureHex) {
|
||||
@@ -39,16 +50,36 @@ export async function startWebServer(client) {
|
||||
}
|
||||
});
|
||||
|
||||
// Root — bis hier das Webinterface (Feature 4) wohnt
|
||||
app.get('/', async () => ({
|
||||
name: 'ecobot',
|
||||
status: 'ok',
|
||||
endpoints: ['/health', '/webhooks/gitea'],
|
||||
}));
|
||||
// Signierte Cookies (Session + OAuth-State)
|
||||
await app.register(fastifyCookie, { secret: config.sessionSecret });
|
||||
|
||||
registerAuthRoutes(app);
|
||||
registerApiRoutes(app);
|
||||
|
||||
// Healthcheck (für Portainer/NPM)
|
||||
app.get('/health', async () => ({ status: 'ok' }));
|
||||
|
||||
if (existsSync(frontendDist)) {
|
||||
// React-Frontend ausliefern; unbekannte GET-Pfade → index.html (SPA-Routing)
|
||||
await app.register(fastifyStatic, { root: frontendDist });
|
||||
app.setNotFoundHandler((request, reply) => {
|
||||
const isApiPath = ['/api', '/auth', '/webhooks'].some((p) =>
|
||||
request.url.startsWith(p)
|
||||
);
|
||||
if (request.method === 'GET' && !isApiPath) {
|
||||
return reply.sendFile('index.html');
|
||||
}
|
||||
return reply.code(404).send({ error: 'not found' });
|
||||
});
|
||||
} else {
|
||||
// Ohne gebautes Frontend (lokale Dev ohne `npm run build`): JSON-Hinweis auf /
|
||||
app.get('/', async () => ({
|
||||
name: 'ecobot',
|
||||
status: 'ok',
|
||||
hint: 'Frontend nicht gebaut — cd frontend && npm run build (oder Vite-Dev-Server nutzen)',
|
||||
}));
|
||||
}
|
||||
|
||||
// Gitea-Push-Webhook → Embed posten + Commits archivieren
|
||||
app.post('/webhooks/gitea', async (request, reply) => {
|
||||
if (!verifySignature(request.rawBody, request.headers['x-gitea-signature'])) {
|
||||
|
||||
Reference in New Issue
Block a user