Files
WhoPays/server.js
T

254 lines
6.7 KiB
JavaScript

import express from "express";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
// Trust proxy - CRITICAL for reverse proxy setup
app.set('trust proxy', true);
app.use(express.json());
// Logging middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.url} - ${req.headers['x-forwarded-proto'] || 'http'}`);
next();
});
// CRITICAL: Serve static files with proper headers
app.use(express.static(path.join(__dirname, "public"), {
setHeaders: (res, filepath) => {
if (filepath.endsWith('.js')) {
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
}
if (filepath.endsWith('.json')) {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
}
if (filepath.endsWith('service-worker.js')) {
res.setHeader('Service-Worker-Allowed', '/');
res.setHeader('Cache-Control', 'no-cache');
}
}
}));
const stateFile = path.join(__dirname, "state.json");
// --------------------
// Konfiguration
// --------------------
const CONFIG = {
A: {
name: "Marc",
color: 0x2ecc71,
icon: "a.png"
},
B: {
name: "Laila",
color: 0xe74c3c,
icon: "b.png"
}
};
const PUBLIC_BASE_URL = process.env.PUBLIC_BASE_URL || "http://localhost:3000";
const DISCORD_WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL || "https://discord.com/api/webhooks/1450872860607381515/zRbiXOykKPUfLJ_H87wD2OxQJPLdro6yQzU2T48Pg4fxSNaXEqBFgxdHGTiIDmmKGHIF";
// --------------------
// State Handling - MIT PERSISTENT lastReason
// --------------------
function getState() {
if (!fs.existsSync(stateFile)) {
const initialState = {
current: "A",
lastReason: null
};
fs.writeFileSync(
stateFile,
JSON.stringify(initialState, null, 2)
);
console.log("✅ Neue state.json erstellt");
return initialState;
}
try {
const state = JSON.parse(fs.readFileSync(stateFile, "utf-8"));
// Ensure lastReason exists in state
if (!state.hasOwnProperty('lastReason')) {
state.lastReason = null;
}
console.log(`📖 State geladen: ${state.current}, Letzter Grund: ${state.lastReason || 'keiner'}`);
return state;
} catch (err) {
console.error("❌ Fehler beim Lesen von state.json:", err);
return { current: "A", lastReason: null };
}
}
function setState(newState) {
try {
fs.writeFileSync(stateFile, JSON.stringify(newState, null, 2));
console.log(`💾 State gespeichert: ${newState.current}, Grund: ${newState.lastReason || 'keiner'}`);
} catch (err) {
console.error("❌ Fehler beim Speichern von state.json:", err);
}
}
// --------------------
// Discord Webhook
// --------------------
async function sendDiscordEmbed(previousKey, currentKey, reason) {
const previous = CONFIG[previousKey];
const current = CONFIG[currentKey];
if (!DISCORD_WEBHOOK_URL) return;
const fields = [
{
name: "Von",
value: previous.name,
inline: true
},
{
name: "Zu",
value: current.name,
inline: true
}
];
if (reason) {
fields.push({
name: "📝 Grund",
value: reason,
inline: false
});
}
const embed = {
title: "💸 Zahl-Wechsel",
description: `**${current.name}** ist jetzt dran zu zahlen`,
color: current.color,
thumbnail: {
url: `${PUBLIC_BASE_URL}/img/${current.icon}`
},
fields: fields,
footer: {
text: new Date().toLocaleString("de-DE")
}
};
try {
await fetch(DISCORD_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ embeds: [embed] })
});
console.log("✅ Discord Benachrichtigung gesendet");
} catch (err) {
console.error("❌ Discord Webhook Fehler:", err.message);
}
}
// --------------------
// API Routes
// --------------------
app.get("/status", (req, res) => {
const state = getState();
res.json(state);
});
app.post("/toggle", async (req, res) => {
const state = getState();
const previousKey = state.current;
const reason = req.body?.reason;
// Wechseln
state.current = state.current === "A" ? "B" : "A";
// Grund speichern (oder null wenn keiner angegeben)
state.lastReason = reason || null;
// State persistent speichern
setState(state);
const logMessage = reason
? `Zahl-Wechsel: ${CONFIG[previousKey].name} → ${CONFIG[state.current].name} (Grund: ${reason})`
: `Zahl-Wechsel: ${CONFIG[previousKey].name} → ${CONFIG[state.current].name}`;
console.log(logMessage);
// Discord Benachrichtigung
await sendDiscordEmbed(previousKey, state.current, reason);
res.json({
current: state.current,
name: CONFIG[state.current].name,
lastReason: state.lastReason
});
});
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
// --------------------
// SPA Fallback - NO CRASH
// --------------------
app.use((req, res, next) => {
if (res.headersSent) return;
if (req.url.startsWith('/api/')) {
return res.status(404).json({ error: 'Not found' });
}
const acceptsHtml = req.accepts('html');
if (acceptsHtml) {
return res.sendFile(path.join(__dirname, "public/index.html"));
}
res.status(404).send('Not found');
});
// Error handler
app.use((err, req, res, next) => {
console.error('Server error:', err);
res.status(500).json({ error: 'Internal server error' });
});
// --------------------
// Start
// --------------------
const PORT = process.env.PORT || 3000;
app.listen(PORT, "0.0.0.0", () => {
console.log(`\n✅ WhoPays läuft auf http://0.0.0.0:${PORT}\n`);
console.log(`📁 Static files directory: ${path.join(__dirname, "public")}`);
console.log(`🔒 Trust proxy: enabled (for reverse proxy support)`);
// Initial state laden und anzeigen
const initialState = getState();
console.log(`\n🎲 Aktueller Status:`);
console.log(` 👤 Wer zahlt: ${CONFIG[initialState.current].name}`);
console.log(` 📝 Letzter Grund: ${initialState.lastReason || 'Keiner gespeichert'}`);
// Verify critical files exist
const criticalFiles = [
'public/index.html',
'public/service-worker.js',
'public/manifest.json'
];
console.log('\n🔍 Checking critical files:');
criticalFiles.forEach(file => {
const fullPath = path.join(__dirname, file);
const exists = fs.existsSync(fullPath);
console.log(` ${exists ? '✅' : '❌'} ${file}`);
});
console.log('\n📝 Test URLs:');
console.log(` http://localhost:${PORT}/`);
console.log(` http://localhost:${PORT}/service-worker.js`);
console.log(` http://localhost:${PORT}/manifest.json\n`);
});