// server.js – Blog API server // Používame Express.js – minimálny webový framework pre Node.js // Počúva na porte 3000 a odpovedá na HTTP požiadavky od frontendu const express = require('express'); const cors = require('cors'); const db = require('./db'); const app = express(); const PORT = process.env.PORT || 3000; // MIDDLEWARE // Middleware sú funkcie, ktoré spracujú každý request // predtým, než sa dostane k nášmu kódu (route handleru) // Povolíme príjem JSON v tele requestu (napr. pri POST) app.use(express.json()); // CORS – Cross-Origin Resource Sharing // Povolíme frontendu (Nginx na porte 80) volať náš backend (port 3000) // Bez toho by prehliadač zablokoval požiadavky z inej domény/portu app.use(cors()); // ROUTES – definujeme čo sa stane pri každej URL // GET /health – jednoduchý endpoint na overenie, že server beží // Používa sa napr. v Dockeri ako "healthcheck" app.get('/health', (req, res) => { res.json({ status: 'ok' }); }); // GET /api/posts – vráti zoznam všetkých príspevkov // Príspevky sú zoradené od najnovšieho po najstarší (ORDER BY created_at DESC) app.get('/api/posts', async (req, res) => { try { const result = await db.query( 'SELECT * FROM posts ORDER BY created_at DESC' ); // result.rows je pole objektov – každý riadok z DB je jeden objekt res.json(result.rows); } catch (err) { console.error('Chyba pri načítaní príspevkov:', err.message); res.status(500).json({ error: 'Interná chyba servera' }); } }); // GET /api/posts/:id – vráti jeden konkrétny príspevok podľa ID // :id je parameter v URL – napr. /api/posts/5 → id = 5 app.get('/api/posts/:id', async (req, res) => { const id = parseInt(req.params.id); // načítame ID z URL // Základná validácia – ID musí byť číslo if (isNaN(id)) { return res.status(400).json({ error: 'ID musí byť číslo' }); } try { // $1 je placeholder – ochrana pred SQL injection útokom // Nikdy nevkladáme premennú priamo do SQL reťazca! const result = await db.query( 'SELECT * FROM posts WHERE id = $1', [id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Príspevok nenájdený' }); } res.json(result.rows[0]); } catch (err) { console.error('Chyba pri načítaní príspevku:', err.message); res.status(500).json({ error: 'Interná chyba servera' }); } }); // POST /api/posts – vytvorí nový príspevok // Dáta prídu v tele requestu ako JSON app.post('/api/posts', async (req, res) => { // Destructuring – vyberieme len polia, ktoré potrebujeme const { title, content, category, excerpt } = req.body; // Validácia vstupných dát if (!title || !content) { return res.status(400).json({ error: 'Nadpis a obsah sú povinné' }); } try { // RETURNING * – PostgreSQL vráti vložený riadok aj s vygenerovaným ID a dátumom const result = await db.query( `INSERT INTO posts (title, content, category, excerpt) VALUES ($1, $2, $3, $4) RETURNING *`, [title.trim(), content.trim(), category || null, excerpt?.trim() || null] ); // HTTP 201 Created – signalizuje, že bol vytvorený nový zdroj res.status(201).json(result.rows[0]); } catch (err) { console.error('Chyba pri vytváraní príspevku:', err.message); res.status(500).json({ error: 'Interná chyba servera' }); } }); app.put('/api/posts/:id', async (req, res) => { const id = parseInt(req.params.id); if (isNaN(id)) { return res.status(400).json({ error: 'ID musí byť číslo' }); } const { title, content, category, excerpt } = req.body; if (!title || !content) { return res.status(400).json({ error: 'Nadpis a obsah sú povinné' }); } try { const result = await db.query( `UPDATE posts SET title = $1, content = $2, category = $3, excerpt = $4, updated_at = NOW() WHERE id = $5 RETURNING *`, [title.trim(), content.trim(), category || null, excerpt?.trim() || null, id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Príspevok nenájdený' }); } res.json(result.rows[0]); } catch (err) { console.error('Chyba pri úprave príspevku:', err.message); res.status(500).json({ error: 'Interná chyba servera' }); } }); // DELETE /api/posts/:id – zmaže príspevok podľa ID app.delete('/api/posts/:id', async (req, res) => { const id = parseInt(req.params.id); if (isNaN(id)) { return res.status(400).json({ error: 'ID musí byť číslo' }); } try { const result = await db.query( 'DELETE FROM posts WHERE id = $1 RETURNING id', [id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Príspevok nenájdený' }); } // HTTP 200 s potvrdením zmazania res.json({ message: 'Príspevok bol zmazaný', id }); } catch (err) { console.error('Chyba pri mazaní príspevku:', err.message); res.status(500).json({ error: 'Interná chyba servera' }); } }); // SPUSTENIE SERVERA // app.listen() spustí server a začne počúvať na danom porte // '0.0.0.0' znamená: počúvaj na všetkých sieťových rozhraniach // (potrebné v Dockeri, inak by počúval len na localhost) app.listen(PORT, '0.0.0.0', () => { console.log(`Backend server beží na porte ${PORT}`); });