const express = require('express'); const rateLimit = require('express-rate-limit'); const pool = require('../db'); const router = express.Router(); router.get('/services', async (req, res) => { const { rows: categories } = await pool.query( 'SELECT id, slug, title, description, icon FROM service_categories ORDER BY sort_order, id' ); const { rows: items } = await pool.query( 'SELECT id, category_id, name, price_text, period, note FROM service_items WHERE active = true ORDER BY sort_order, id' ); const grouped = categories .map((cat) => ({ ...cat, items: items.filter((it) => it.category_id === cat.id) })) .filter((cat) => cat.items.length > 0); res.json(grouped); }); router.get('/promotions', async (req, res) => { const { rows } = await pool.query( 'SELECT id, title, price_text, old_price_text, description FROM promotions WHERE active = true ORDER BY sort_order, id' ); res.json(rows); }); router.get('/reviews', async (req, res) => { const { rows } = await pool.query( "SELECT id, name, role, rating, text, created_at FROM reviews WHERE status = 'approved' ORDER BY created_at DESC" ); res.json(rows); }); router.get('/cases', async (req, res) => { const { rows } = await pool.query( 'SELECT id, title, tag, status, tone, description FROM cases WHERE active = true ORDER BY sort_order, id' ); res.json(rows); }); router.get('/faqs', async (req, res) => { const { rows } = await pool.query( 'SELECT id, question, answer FROM faqs WHERE active = true ORDER BY sort_order, id' ); res.json(rows); }); router.get('/blog', async (req, res) => { const { rows } = await pool.query( 'SELECT id, slug, title, excerpt, published_at FROM blog_posts WHERE published = true ORDER BY published_at DESC' ); res.json(rows); }); router.get('/blog/:slug', async (req, res) => { const { rows } = await pool.query( 'SELECT id, slug, title, excerpt, content, published_at FROM blog_posts WHERE slug = $1 AND published = true', [req.params.slug] ); if (!rows.length) return res.status(404).json({ error: 'not found' }); res.json(rows[0]); }); router.get('/contact', async (req, res) => { const { rows } = await pool.query('SELECT address, phone, email, hours, map_embed FROM contact_info WHERE id = 1'); res.json(rows[0] || { address: '', phone: '', email: '', hours: '', map_embed: '' }); }); const reviewLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, standardHeaders: true, legacyHeaders: false, message: { error: 'too many reviews submitted, try again later' }, }); router.post('/reviews', reviewLimiter, async (req, res) => { const { name, role, rating, text, website } = req.body || {}; // honeypot field - real users never fill it in, bots often do if (website) return res.status(201).json({ ok: true }); if (!name || !text || !rating) { return res.status(400).json({ error: 'name, rating and text are required' }); } const ratingNum = Number(rating); if (!Number.isInteger(ratingNum) || ratingNum < 1 || ratingNum > 5) { return res.status(400).json({ error: 'rating must be an integer from 1 to 5' }); } await pool.query( "INSERT INTO reviews (name, role, rating, text, status) VALUES ($1, $2, $3, $4, 'pending')", [String(name).slice(0, 120), role ? String(role).slice(0, 160) : null, ratingNum, String(text).slice(0, 2000)] ); res.status(201).json({ ok: true }); }); router.post('/stats/visit', async (req, res) => { await pool.query( `INSERT INTO visits (date, count) VALUES (CURRENT_DATE, 1) ON CONFLICT (date) DO UPDATE SET count = visits.count + 1` ); res.status(204).end(); }); module.exports = router;