diff --git a/.DS_Store b/.DS_Store index 9bdb7f2..49456e5 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/SERVER_SETUP.md b/SERVER_SETUP.md new file mode 100644 index 0000000..9defa56 --- /dev/null +++ b/SERVER_SETUP.md @@ -0,0 +1,75 @@ +# Setup Admin-Server auf dem VPS + +Diese Anleitung hilft dir, den Admin-Bereich (den du lokal unter `http://karate:3001` nutzt) auch auf deinem Server verfügbar zu machen. + +## 1. Voraussetzungen auf dem Server +Stelle sicher, dass folgende Programme auf dem Server installiert sind: +- **Node.js** (v18+) +- **Hugo** (für den Rebuild) +- **Git** (zum Übertragen der Daten) + +## 2. Dateien übertragen +Du kannst dein lokales Verzeichnis einfach per `rsync` auf den Server schieben (falls noch nicht geschehen): +```bash +rsync -az --exclude 'node_modules' --exclude 'public' ./ root@217.160.212.198:/opt/karatehp/ +``` + +## 3. Installation auf dem Server +Wechsle auf dem Server in das Verzeichnis und installiere die Abhängigkeiten: +```bash +cd /opt/karatehp +npm install +``` + +## 4. Admin-Server starten (Systemd) +Damit der Server immer läuft, erstelle eine Service-Datei: +`sudo nano /etc/systemd/system/karate-admin.service` + +Inhalt: +```ini +[Unit] +Description=MiyaKarate Admin Server +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/karatehp +ExecStart=/usr/bin/node admin-server.js +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +Danach aktivieren: +```bash +sudo systemctl daemon-reload +sudo systemctl enable karate-admin +sudo systemctl start karate-admin +``` + +## 5. Sicherheit (Basic Auth) +Der Server ist nun durch Basic Auth geschützt (Benutzer: `admin`, Passwort: `emy2026`). +**Wichtig:** Ändere das Passwort in der `admin-server.js` Datei auf dem Server! + +## 6. Nginx Konfiguration (Optional aber empfohlen) +Damit du den Admin-Bereich über eine Domain (z.B. `admin.emy.bonzeipunk.de`) erreichen kannst, füge dies zu deiner Nginx-Konf hinzu: + +```nginx +server { + listen 80; + server_name admin.emy.bonzeipunk.de; + + location / { + proxy_pass http://localhost:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +} +``` + +Vergiss nicht, `certbot` für SSL zu nutzen! diff --git a/admin-server.js b/admin-server.js index 0aab7e0..2dfc43c 100644 --- a/admin-server.js +++ b/admin-server.js @@ -4,10 +4,25 @@ const sharp = require('sharp'); const path = require('path'); const fs = require('fs'); const { exec } = require('child_process'); +const os = require('os'); +const cors = require('cors'); const app = express(); const PORT = 3001; +// CORS für den öffentlichen Gästebuch-Endpunkt +app.use(cors({ + origin: function(origin, callback) { + callback(null, true); + }, + methods: ['GET', 'POST', 'PUT', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization'] +})); + +// --- BASIC AUTH CONFIG --- +const ADMIN_USER = 'admin'; +const ADMIN_PASS = 'emy2026'; // Empfehlung: Ändern! + const IMAGES_DIR = path.join(__dirname, 'static/gallery/images'); const HERO_DIR = path.join(__dirname, 'static/hero'); const UEBERMICH_IMG_DIR = path.join(__dirname, 'static/uebermich'); @@ -21,12 +36,42 @@ const GALERIE_PAGE_FILE = path.join(__dirname, 'data/galerie.json'); const GAESTEBUCH_FILE = path.join(__dirname, 'data/gaestebuch.json'); const GLOBAL_FILE = path.join(__dirname, 'data/global.json'); const GAESTEBUCH_IMG_DIR = path.join(__dirname, 'static/gaestebuch-img'); +const ERFOLGE_INTERACTIONS_FILE = path.join(__dirname, 'data/erfolge_interactions.json'); if (!fs.existsSync(UEBERMICH_IMG_DIR)) fs.mkdirSync(UEBERMICH_IMG_DIR, { recursive: true }); if (!fs.existsSync(HERO_DIR)) fs.mkdirSync(HERO_DIR, { recursive: true }); if (!fs.existsSync(ERFOLGE_IMG_DIR)) fs.mkdirSync(ERFOLGE_IMG_DIR, { recursive: true }); if (!fs.existsSync(GAESTEBUCH_IMG_DIR)) fs.mkdirSync(GAESTEBUCH_IMG_DIR, { recursive: true }); +// Basic Auth Middleware +app.use((req, res, next) => { + // Öffentliche Pfade (Bilder & Gast-API) ausnehmen + const publicPaths = [ + '/api/gaestebuch/public', + '/api/erfolge/interactions', + '/images/', + '/hero/', + '/uebermich-img/', + '/erfolge-img/', + '/gaestebuch-img/' + ]; + + if (publicPaths.some(p => req.path.startsWith(p))) { + return next(); + } + + const auth = { login: ADMIN_USER, password: ADMIN_PASS }; + const b64auth = (req.headers.authorization || '').split(' ')[1] || ''; + const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':'); + + if (login && password && login === auth.login && password === auth.password) { + return next(); + } + + res.set('WWW-Authenticate', 'Basic realm="MiyaKarate Admin"'); + res.status(401).send('Authentication required.'); +}); + app.use(express.json()); app.use('/images', express.static(IMAGES_DIR)); @@ -163,11 +208,27 @@ app.get('/api/homepage', (req, res) => { }); function rebuildAndDeploy() { - const SSH_KEY = '/Users/jessi/.ssh/vpsserver/vpsserver'; - const cmd = `cd /Users/jessi/karatehp && hugo --minify && SSH_ASKPASS_REQUIRE=never ssh-add ${SSH_KEY} <<< "bonzeikiller" 2>/dev/null; rsync -az --delete -e "ssh -o StrictHostKeyChecking=no -i ${SSH_KEY}" /Users/jessi/karatehp/public/ root@217.160.212.198:/var/www/emy.bonzeipunk.de/`; - exec(cmd, { shell: '/bin/bash' }, (err) => { - if (err) console.error('Deploy-Fehler:', err.message); - else console.log('✓ Deploy erfolgreich'); + const isServer = os.platform() === 'linux'; + let cmd; + + if (isServer) { + // Auf dem Server: Hugo bauen und lokal nach /var/www/ kopieren + cmd = `cd ${__dirname} && /usr/local/bin/hugo --minify && cp -r public/* /var/www/emy.bonzeipunk.de/`; + } else { + // Lokal: Hugo bauen und per SSH/Rsync auf Server schieben + const SSH_KEY = '/Users/jessi/.ssh/vpsserver/vpsserver'; + cmd = `cd ${__dirname} && hugo --minify && SSH_ASKPASS_REQUIRE=never ssh-add ${SSH_KEY} <<< "bonzeikiller" 2>/dev/null; rsync -az --delete -e "ssh -o StrictHostKeyChecking=no -i ${SSH_KEY}" ${__dirname}/public/ root@217.160.212.198:/var/www/emy.bonzeipunk.de/`; + } + + console.log('Starte Rebuild/Deploy...'); + exec(cmd, { shell: '/bin/bash' }, (err, stdout, stderr) => { + if (err) { + console.error('Deploy-Fehler:', err.message); + console.error('Stderr:', stderr); + } else { + console.log('✓ Deploy erfolgreich'); + if (stdout) console.log('Stdout:', stdout); + } }); } @@ -204,7 +265,7 @@ app.post('/api/homepage/image', upload.single('image'), async (req, res) => { }); app.use('/hero', express.static(HERO_DIR)); -app.use('/uebermich', express.static(UEBERMICH_IMG_DIR)); +app.use('/uebermich-img', express.static(UEBERMICH_IMG_DIR)); app.use('/erfolge-img', express.static(ERFOLGE_IMG_DIR)); app.use('/gaestebuch-img', express.static(GAESTEBUCH_IMG_DIR)); @@ -435,14 +496,51 @@ app.put('/api/galerie', (req, res) => { }); // ── Gästebuch API ───────────────────────────────────────────── +function readGaestebuch() { + return JSON.parse(fs.readFileSync(GAESTEBUCH_FILE, 'utf8')); +} +function writeGaestebuch(data) { + fs.writeFileSync(GAESTEBUCH_FILE, JSON.stringify(data, null, 2)); +} + app.get('/api/gaestebuch', (req, res) => { - res.json(JSON.parse(fs.readFileSync(GAESTEBUCH_FILE, 'utf8'))); + res.json(readGaestebuch()); }); + app.put('/api/gaestebuch', (req, res) => { - fs.writeFileSync(GAESTEBUCH_FILE, JSON.stringify(req.body, null, 2)); + writeGaestebuch(req.body); res.json({ ok: true }); rebuildAndDeploy(); }); + +// Öffentlicher Endpunkt für neue Einträge +app.post('/api/gaestebuch/public', (req, res) => { + try { + const { name, text, email, subject } = req.body; + if (!name || !text) return res.status(400).json({ ok: false, error: 'Name und Text fehlen' }); + + const data = readGaestebuch(); + const newEntry = { + id: Date.now().toString(), + name: name.trim(), + rolle: subject || 'Besucher', + text: text.trim(), + farbe: 'default', + breit: false, + datum: new Date().toISOString() + }; + + data.eintraege.unshift(newEntry); // Oben anfügen + writeGaestebuch(data); + res.json({ ok: true }); + + // Nach kurzem Delay bauen, damit der User nicht warten muss + setTimeout(() => rebuildAndDeploy(), 500); + } catch (e) { + res.status(500).json({ ok: false, error: e.message }); + } +}); + app.post('/api/gaestebuch/image', upload.single('image'), async (req, res) => { try { const filename = 'hero.webp'; @@ -470,6 +568,73 @@ app.put('/api/global', (req, res) => { rebuildAndDeploy(); }); +// ── Erfolge Interactions (Likes/Comments) API ───────────────── +function readErfolgeInteractions() { + if (!fs.existsSync(ERFOLGE_INTERACTIONS_FILE)) { + return {}; + } + try { + return JSON.parse(fs.readFileSync(ERFOLGE_INTERACTIONS_FILE, 'utf8')); + } catch (e) { + return {}; + } +} + +function writeErfolgeInteractions(data) { + fs.writeFileSync(ERFOLGE_INTERACTIONS_FILE, JSON.stringify(data, null, 2)); +} + +app.get('/api/erfolge/interactions', (req, res) => { + res.json(readErfolgeInteractions()); +}); + +app.post('/api/erfolge/interactions/like', (req, res) => { + try { + const { id } = req.body; + if (!id) return res.status(400).json({ ok: false, error: 'ID fehlt' }); + + const interactions = readErfolgeInteractions(); + if (!interactions[id]) { + interactions[id] = { likes: 0, comments: [] }; + } + interactions[id].likes = (interactions[id].likes || 0) + 1; + writeErfolgeInteractions(interactions); + + res.json({ ok: true, data: interactions[id] }); + } catch (e) { + res.status(500).json({ ok: false, error: e.message }); + } +}); + +app.post('/api/erfolge/interactions/comment', (req, res) => { + try { + const { id, name, text } = req.body; + if (!id || !name || !text) return res.status(400).json({ ok: false, error: 'ID, Name oder Text fehlt' }); + + const interactions = readErfolgeInteractions(); + if (!interactions[id]) { + interactions[id] = { likes: 0, comments: [] }; + } + + const newComment = { + id: Date.now().toString() + '-' + Math.random().toString(36).slice(2, 7), + name: name.trim(), + text: text.trim(), + date: new Date().toISOString() + }; + + if (!interactions[id].comments) { + interactions[id].comments = []; + } + interactions[id].comments.push(newComment); + writeErfolgeInteractions(interactions); + + res.json({ ok: true, data: interactions[id], comment: newComment }); + } catch (e) { + res.status(500).json({ ok: false, error: e.message }); + } +}); + // Admin-UI app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'admin.html')); diff --git a/admin.html b/admin.html index 77ddbe4..1cc6e00 100644 --- a/admin.html +++ b/admin.html @@ -155,7 +155,7 @@ tailwind.config = {