zkt26/zadanie 1/backend/server.js
2026-03-22 12:58:49 +01:00

182 lines
5.4 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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}`);
});