let db; const LH = "ip NOT IN ('127.0.0.1', '::1', '::ffff:127.0.0.1')"; function init(database) { db = database; } function getStats(req, res) { db.get(`SELECT COUNT(DISTINCT session_id) as total FROM visitor_log WHERE ${LH}`, [], (err, totalRow) => { if (err) return res.status(500).json({ error: err.message }); db.get(`SELECT COUNT(DISTINCT session_id) as today FROM visitor_log WHERE visit_date = date('now') AND ${LH}`, [], (err, todayRow) => { if (err) return res.status(500).json({ error: err.message }); db.get(`SELECT COUNT(DISTINCT session_id) as week FROM visitor_log WHERE visit_date >= date('now', '-6 days') AND ${LH}`, [], (err, weekRow) => { if (err) return res.status(500).json({ error: err.message }); db.get(`SELECT COUNT(DISTINCT session_id) as month FROM visitor_log WHERE visit_date >= date('now', 'start of month') AND ${LH}`, [], (err, monthRow) => { if (err) return res.status(500).json({ error: err.message }); db.get(`SELECT COUNT(*) as page_views FROM visitor_log WHERE ${LH}`, [], (err, pvRow) => { if (err) return res.status(500).json({ error: err.message }); db.get(`SELECT COUNT(DISTINCT ip) as unique_ips FROM visitor_log WHERE ${LH}`, [], (err, ipRow) => { if (err) return res.status(500).json({ error: err.message }); res.json({ total: totalRow ? totalRow.total : 0, today: todayRow ? todayRow.today : 0, week: weekRow ? weekRow.week : 0, month: monthRow ? monthRow.month : 0, pageViews: pvRow ? pvRow.page_views : 0, uniqueIps: ipRow ? ipRow.unique_ips : 0 }); }); }); }); }); }); }); } function getCountries(req, res) { db.all(`SELECT COALESCE(NULLIF(country, ''), 'Другое') as country, COALESCE(NULLIF(country_code, ''), '--') as country_code, COUNT(DISTINCT session_id) as count FROM visitor_log WHERE ${LH} GROUP BY COALESCE(NULLIF(country_code, ''), '--') ORDER BY count DESC LIMIT 30`, [], (err, rows) => { if (err) return res.status(500).json({ error: err.message }); res.json(rows || []); }); } function getLanguages(req, res) { db.all(`SELECT accept_language, COUNT(DISTINCT session_id) as count FROM visitor_log WHERE accept_language IS NOT NULL AND accept_language != '' AND ${LH} GROUP BY accept_language ORDER BY count DESC`, [], (err, rows) => { if (err) return res.status(500).json({ error: err.message }); const parsed = (rows || []).map(r => { const primary = (r.accept_language || '').split(',')[0].split(';')[0].trim(); const lang = primary.split('-')[0]; return { lang, raw: primary, count: r.count }; }); res.json(parsed); }); } function getDaily(req, res) { db.all(`SELECT visit_date, COUNT(DISTINCT session_id) as count, COUNT(*) as page_views FROM visitor_log WHERE visit_date >= date('now', '-29 days') AND ${LH} GROUP BY visit_date ORDER BY visit_date ASC`, [], (err, rows) => { if (err) return res.status(500).json({ error: err.message }); res.json(rows || []); }); } function getIPs(req, res) { const limit = Math.min(parseInt(req.query.limit) || 50, 200); const offset = parseInt(req.query.offset) || 0; const { id, ip, country, lang, date_from, date_to, page } = req.query; const whereClauses = [LH]; const params = []; if (id) { whereClauses.push('id = ?'); params.push(parseInt(id)); } if (ip) { whereClauses.push('ip LIKE ?'); params.push('%' + ip + '%'); } if (country) { whereClauses.push('(country LIKE ? OR country_code LIKE ?)'); params.push('%' + country + '%'); params.push('%' + country + '%'); } if (lang) { whereClauses.push('accept_language LIKE ?'); params.push('%' + lang + '%'); } if (date_from) { whereClauses.push('visit_date >= ?'); params.push(date_from); } if (date_to) { whereClauses.push('visit_date <= ?'); params.push(date_to); } if (page) { whereClauses.push('page_path LIKE ?'); params.push('%' + page + '%'); } const whereSQL = whereClauses.length > 0 ? 'WHERE ' + whereClauses.join(' AND ') : ''; db.get(`SELECT COUNT(*) as total FROM visitor_log ${whereSQL}`, params, (err, countRow) => { if (err) return res.status(500).json({ error: err.message }); const allParams = [...params, limit, offset]; db.all(`SELECT id, ip, COALESCE(NULLIF(country, ''), 'Другое') as country, country_code, session_id, visit_date, page_path, COALESCE(NULLIF(accept_language, ''), '-') as accept_language, created_at FROM visitor_log ${whereSQL} ORDER BY created_at DESC LIMIT ? OFFSET ?`, allParams, (err, rows) => { if (err) return res.status(500).json({ error: err.message }); res.json({ entries: rows || [], total: countRow ? countRow.total : 0, limit, offset }); }); }); } function getFilterOptions(req, res) { db.all(`SELECT DISTINCT COALESCE(NULLIF(country, ''), 'Другое') as country FROM visitor_log ORDER BY country`, [], (err, countries) => { if (err) return res.status(500).json({ error: err.message }); db.all(`SELECT DISTINCT accept_language FROM visitor_log WHERE accept_language IS NOT NULL AND accept_language != '' ORDER BY accept_language`, [], (err, langs) => { if (err) return res.status(500).json({ error: err.message }); db.all(`SELECT DISTINCT page_path FROM visitor_log ORDER BY page_path`, [], (err, pages) => { if (err) return res.status(500).json({ error: err.message }); res.json({ countries: (countries || []).map(c => c.country), languages: (langs || []).map(l => l.accept_language), pages: (pages || []).map(p => p.page_path) }); }); }); }); } function setupRoutes(app, authenticateToken) { app.get('/api/admin/visitors/stats', authenticateToken, getStats); app.get('/api/admin/visitors/countries', authenticateToken, getCountries); app.get('/api/admin/visitors/languages', authenticateToken, getLanguages); app.get('/api/admin/visitors/daily', authenticateToken, getDaily); app.get('/api/admin/visitors/ips', authenticateToken, getIPs); app.get('/api/admin/visitors/filters', authenticateToken, getFilterOptions); } module.exports = { init, setupRoutes };