182 lines
5.4 KiB
JavaScript
Executable File
182 lines
5.4 KiB
JavaScript
Executable File
|
||
// 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}`);
|
||
});
|