Files
singlechat/server/index.js
Torsten Schulz (local) 06182a4a95 Add FAQ, Rules, and Safety pages with corresponding routes and SEO metadata
- Introduced new links in ImprintContainer.vue for FAQ, Rules, and Safety pages.
- Added FaqView, RulesView, and SafetyView components to handle the new routes.
- Implemented SEO metadata for the new pages in routes-seo.js and router/index.js.
- Updated server routes to include the new paths for proper handling in production.

These changes enhance the site's informational resources and improve SEO visibility for user inquiries.
2026-03-27 14:15:37 +01:00

179 lines
5.8 KiB
JavaScript

import express from 'express';
import { createServer } from 'http';
import { Server as SocketIOServer } from 'socket.io';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import cors from 'cors';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { setupBroadcast } from './broadcast.js';
import { setupRoutes } from './routes.js';
import { setupSEORoutes } from './routes-seo.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const server = createServer(app);
// Umgebungsvariablen
const NODE_ENV = process.env.NODE_ENV || 'development';
const PORT = process.env.PORT || (NODE_ENV === 'production' ? 4000 : 3300);
const IS_PRODUCTION = NODE_ENV === 'production';
const PRIMARY_HOST = 'www.ypchat.net';
const LEGACY_HOSTS = new Set(['ypchat.net']);
// CORS-Origins konfigurieren
const allowedOrigins = IS_PRODUCTION
? ['https://ypchat.net', 'https://www.ypchat.net']
: ['http://localhost:5175', 'http://127.0.0.1:5175'];
// Socket.IO auf dem gleichen HTTP-Server wie Express
const io = new SocketIOServer(server, {
cors: {
origin: allowedOrigins,
credentials: true,
methods: ['GET', 'POST']
},
transports: ['websocket', 'polling'],
allowEIO3: true,
maxHttpBufferSize: 10 * 1024 * 1024, // 10MB für große Bilder (Base64-kodiert)
pingTimeout: 60000,
pingInterval: 25000
});
console.log('Socket.IO Server initialisiert auf Express-Server');
console.log('Umgebung:', NODE_ENV);
console.log('CORS erlaubt für:', allowedOrigins);
// CORS-Konfiguration
app.use(cors({
origin: (origin, callback) => {
// Erlaube Requests ohne Origin (z.B. Postman, mobile Apps)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin) || !IS_PRODUCTION) {
callback(null, true);
} else {
callback(new Error('Nicht erlaubt durch CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// Session-Konfiguration
const sessionSecret = process.env.SESSION_SECRET || 'singlechat-secret-key-change-in-production';
app.use(session({
secret: sessionSecret,
resave: false,
saveUninitialized: false,
cookie: {
secure: IS_PRODUCTION, // true für HTTPS in Production
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 Stunden
sameSite: IS_PRODUCTION ? 'lax' : false // Lax für HTTPS, false für Development
}
}));
// Trust Proxy für Apache Reverse Proxy (muss vor Routes stehen)
if (IS_PRODUCTION) {
app.set('trust proxy', 1); // Vertraue dem ersten Proxy (Apache)
// SEO-Fallback: erzwinge kanonischen Host + HTTPS, falls der Proxy es nicht bereits tut.
app.use((req, res, next) => {
const forwardedHost = String(req.headers['x-forwarded-host'] || '')
.split(',')[0]
.trim()
.toLowerCase();
const rawHost = String(req.headers.host || '')
.split(':')[0]
.trim()
.toLowerCase();
const host = forwardedHost || rawHost;
const forwardedProto = String(req.headers['x-forwarded-proto'] || '')
.split(',')[0]
.trim()
.toLowerCase();
const isHttps = forwardedProto ? forwardedProto === 'https' : req.secure;
const isKnownPublicHost = host === PRIMARY_HOST || LEGACY_HOSTS.has(host);
if (!isKnownPublicHost) {
next();
return;
}
if (!isHttps || host !== PRIMARY_HOST) {
res.redirect(301, `https://${PRIMARY_HOST}${req.originalUrl || '/'}`);
return;
}
next();
});
}
// Statische Dateien aus docroot
app.use('/static', express.static(join(__dirname, '../docroot')));
// SEO-Routes (robots.txt, sitemap.xml, Pre-Rendering)
// Müssen vor anderen Routes stehen, damit sie nicht vom SPA-Fallback abgefangen werden
setupSEORoutes(app, __dirname);
// API Routes (müssen vor SPA-Fallback stehen)
setupRoutes(app, __dirname);
// Socket.IO-Handling
setupBroadcast(io, __dirname);
// In Production: Serviere auch die gebauten Client-Dateien
// SPA-Fallback muss nach allen anderen Routen stehen
if (IS_PRODUCTION) {
const distPath = join(__dirname, '../docroot/dist');
const KNOWN_SPA_ROUTES = new Set(['/', '/partners', '/feedback', '/faq', '/regeln', '/sicherheit', '/mockup-redesign']);
app.use(express.static(distPath));
// Fallback für Vue Router (SPA) - muss am Ende stehen
app.get('*', (req, res) => {
// Überspringe SEO-Routes in Production (werden bereits von setupSEORoutes behandelt)
if (IS_PRODUCTION && (req.path === '/' || req.path === '/partners' || req.path === '/feedback' || req.path === '/faq' || req.path === '/regeln' || req.path === '/sicherheit')) {
return; // Route wurde bereits behandelt
}
// In Production: /src/ Pfade sollten nicht existieren (404)
if (IS_PRODUCTION && req.path.startsWith('/src/')) {
res.status(404).send('Not found');
return;
}
// Nur bekannte SPA-Routen als index.html ausliefern, unbekannte Routen mit 404 beantworten
if (
!req.path.startsWith('/api') &&
!req.path.startsWith('/static') &&
req.path !== '/robots.txt' &&
req.path !== '/sitemap.xml' &&
KNOWN_SPA_ROUTES.has(req.path)
) {
res.sendFile(join(distPath, 'index.html'));
} else {
res.status(404).send('Not found');
}
});
}
// Server starten
const HOST = '127.0.0.1'; // Nur localhost, da Apache als Reverse Proxy fungiert
server.listen(PORT, HOST, () => {
console.log(`Server läuft auf http://${HOST}:${PORT}`);
console.log(`Umgebung: ${NODE_ENV}`);
console.log(`CORS erlaubt für: ${allowedOrigins.join(', ')}`);
if (IS_PRODUCTION) {
console.log('Production-Modus: HTTPS über Apache Reverse Proxy');
}
});