let db; let settingsModule; function init(database, settings) { db = database; settingsModule = settings; } function getApprovedReviews(req, res) { const lang = req.query.lang || 'ru'; db.all( `SELECT id, author_name, country, city, stars, text, created_at FROM reviews WHERE is_approved = 1`, [], (err, rows) => { if (err) { console.error('Get reviews error:', err); return res.status(500).json({ error: 'Database error' }); } rows.forEach(row => { row.created_at = row.created_at ? new Date(row.created_at).toISOString() : null; }); const maxReviews = 8; let reviews = rows; if (rows.length > maxReviews) { for (let i = reviews.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [reviews[i], reviews[j]] = [reviews[j], reviews[i]]; } reviews = reviews.slice(0, maxReviews); } const stats = rows.length > 0 ? { count: rows.length, avgStars: rows.reduce((sum, r) => sum + r.stars, 0) / rows.length } : { count: 0, avgStars: 0 }; res.json({ reviews: reviews, stats: stats }); } ); } function createReview(req, res) { const { author_name, country_code, country_name, city, stars, text, review_code } = req.body; const ip = req.ip || req.connection.remoteAddress || 'unknown'; if (!author_name || !country_code || stars === undefined || !text || !review_code) { return res.status(400).json({ error: 'Missing required fields' }); } const country = country_name || country_code; if (author_name.length < 2) { return res.status(400).json({ error: 'Name must be at least 2 characters' }); } if (text.length < 20) { return res.status(400).json({ error: 'Review text must be at least 20 characters' }); } if (stars < 0 || stars > 5) { return res.status(400).json({ error: 'Stars must be between 0 and 5' }); } settingsModule.getReviewCode((err, correctCode) => { if (err) return res.status(500).json({ error: 'Server error' }); if (review_code !== correctCode) { return res.status(400).json({ error: 'Invalid review code' }); } settingsModule.checkIpCooldown(ip, (err, isCooldown) => { if (err) return res.status(500).json({ error: 'Server error' }); if (isCooldown) { return res.status(429).json({ error: 'You have recently submitted a review. Please try again later.' }); } const stmt = db.prepare( `INSERT INTO reviews (author_name, country, country_code, city, stars, text, review_code, ip_address, is_approved) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)` ); stmt.run(author_name, country, country_code, city, parseFloat(stars).toFixed(1), text, review_code, ip, function(err) { if (err) { console.error('Create review error:', err); return res.status(500).json({ error: 'Database error' }); } res.status(201).json({ message: 'Review submitted for moderation', id: this.lastID }); }); stmt.finalize(); }); }); } function getAllReviews(req, res) { const filter = req.query.filter || 'all'; let whereClause = '1=1'; if (filter === 'pending') { whereClause = 'is_approved = 0'; } else if (filter === 'approved') { whereClause = 'is_approved = 1'; } else if (filter === 'rejected') { whereClause = 'is_approved = -1'; } db.all( `SELECT * FROM reviews WHERE ${whereClause} ORDER BY created_at DESC`, [], (err, rows) => { if (err) { console.error('Get all reviews error:', err); return res.status(500).json({ error: 'Database error' }); } const stats = { total: rows.length, pending: rows.filter(r => r.is_approved === 0).length, approved: rows.filter(r => r.is_approved === 1).length, rejected: rows.filter(r => r.is_approved === -1).length }; db.get(`SELECT COUNT(*) as cnt, SUM(CASE WHEN is_approved = 0 THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN is_approved = 1 THEN 1 ELSE 0 END) as approved, SUM(CASE WHEN is_approved = -1 THEN 1 ELSE 0 END) as rejected FROM reviews`, [], (err, globalStats) => { if (!err && globalStats) { stats.total = globalStats.cnt || 0; stats.pending = globalStats.pending || 0; stats.approved = globalStats.approved || 0; stats.rejected = globalStats.rejected || 0; } rows.forEach(row => { row.created_at = row.created_at ? new Date(row.created_at).toISOString() : null; }); res.json({ reviews: rows, stats: stats }); }); } ); } function approveReview(req, res) { const reviewId = parseInt(req.params.id); const { approved } = req.body; const newStatus = approved ? 1 : -1; db.run( `UPDATE reviews SET is_approved = ? WHERE id = ?`, [newStatus, reviewId], function(err) { if (err) { console.error('Approve review error:', err); return res.status(500).json({ error: 'Database error' }); } if (this.changes === 0) { return res.status(404).json({ error: 'Review not found' }); } res.json({ message: 'Review status updated', id: reviewId, is_approved: newStatus }); } ); } function deleteReview(req, res) { const reviewId = parseInt(req.params.id); db.run(`DELETE FROM reviews WHERE id = ?`, [reviewId], function(err) { if (err) { console.error('Delete review error:', err); return res.status(500).json({ error: 'Database error' }); } if (this.changes === 0) { return res.status(404).json({ error: 'Review not found' }); } res.json({ message: 'Review deleted', id: reviewId }); }); } function getPopularCountries(req, res) { if (!db) { return res.status(500).json({ error: 'Database not initialized' }); } db.all(` SELECT country_code, COUNT(*) as count FROM reviews WHERE is_approved = 1 AND country_code IS NOT NULL AND country_code != '' GROUP BY country_code ORDER BY count DESC LIMIT 20 `, (err, countries) => { if (err) { console.error('Get popular countries error:', err); return res.status(500).json({ error: err.message }); } if (!countries || countries.length === 0) { return res.json({ countries: [], cities: {} }); } const placeholders = countries.map(() => '?').join(','); db.all(` SELECT country_code, city, COUNT(*) as count FROM reviews WHERE is_approved = 1 AND country_code IN (${placeholders}) GROUP BY country_code, city ORDER BY count DESC `, countries.map(c => c.country_code), (err, cities) => { if (err) { console.error('Get popular cities error:', err); return res.status(500).json({ error: err.message }); } const citiesByCountry = {}; if (cities) { cities.forEach(c => { if (!citiesByCountry[c.country_code]) { citiesByCountry[c.country_code] = []; } citiesByCountry[c.country_code].push(c.city); }); } res.json({ countries, cities: citiesByCountry }); }); }); } function getPopularCitiesByCountry(req, res) { const countryCode = req.params.countryCode; if (!db) { return res.status(500).json({ error: 'Database not initialized' }); } if (!countryCode) { return res.json({ popular: [], countryCode: null }); } db.all(` SELECT city, COUNT(*) as count FROM reviews WHERE is_approved = 1 AND country_code = ? GROUP BY city ORDER BY count DESC `, [countryCode], (err, popularCities) => { if (err) { console.error('Get popular cities by country error:', err); popularCities = []; } const popularCityNames = popularCities ? popularCities.map(c => c.city) : []; res.json({ popular: popularCityNames, countryCode }); }); } function setupRoutes(app, authenticateToken, requireAdmin) { app.get('/api/reviews', getApprovedReviews); app.post('/api/reviews', createReview); app.get('/api/reviews/popular', getPopularCountries); app.get('/api/reviews/cities/:countryCode', getPopularCitiesByCountry); app.get('/api/admin/reviews', authenticateToken, getAllReviews); app.patch('/api/admin/reviews/:id/approve', authenticateToken, approveReview); app.delete('/api/admin/reviews/:id', authenticateToken, deleteReview); } module.exports = { init, setupRoutes };