2026-02-08 03:03:53 -05:00
|
|
|
import { NextResponse } from 'next/server';
|
2026-02-09 00:50:40 -05:00
|
|
|
import { getVisitorsDb } from '@/lib/visitors-db';
|
|
|
|
|
import geoip from 'geoip-lite';
|
2026-02-08 03:03:53 -05:00
|
|
|
|
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
|
|
|
|
|
|
export async function GET() {
|
|
|
|
|
try {
|
2026-02-09 00:50:40 -05:00
|
|
|
const db = await getVisitorsDb();
|
2026-02-08 03:03:53 -05:00
|
|
|
|
2026-02-09 00:50:40 -05:00
|
|
|
if (!db) {
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
locations: [],
|
|
|
|
|
totalVisitors: 0,
|
|
|
|
|
active24h: 0,
|
|
|
|
|
active7d: 0,
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-02-08 03:03:53 -05:00
|
|
|
|
2026-02-09 00:50:40 -05:00
|
|
|
// Count unique visitors across time windows
|
|
|
|
|
const [total, last24h, last7d] = await Promise.all([
|
|
|
|
|
db.get(`SELECT COUNT(DISTINCT ip_address) as count FROM visits`),
|
|
|
|
|
db.get(`SELECT COUNT(DISTINCT ip_address) as count FROM visits WHERE visited_at >= datetime('now', '-24 hours')`),
|
|
|
|
|
db.get(`SELECT COUNT(DISTINCT ip_address) as count FROM visits WHERE visited_at >= datetime('now', '-7 days')`),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Get unique IPs from last 7 days for globe markers
|
|
|
|
|
const recentIps = await db.all(
|
|
|
|
|
`SELECT ip_address, COUNT(*) as hits FROM visits WHERE visited_at >= datetime('now', '-7 days') GROUP BY ip_address`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Geo-locate IPs and group by location
|
|
|
|
|
const locationMap = new Map<string, { lat: number; lon: number; city: string; country: string; count: number }>();
|
|
|
|
|
|
|
|
|
|
for (const row of recentIps) {
|
|
|
|
|
const geo = geoip.lookup(row.ip_address);
|
|
|
|
|
if (geo && geo.ll) {
|
|
|
|
|
const key = `${geo.ll[0]},${geo.ll[1]}`;
|
|
|
|
|
const existing = locationMap.get(key);
|
|
|
|
|
if (existing) {
|
|
|
|
|
existing.count += row.hits;
|
|
|
|
|
} else {
|
|
|
|
|
locationMap.set(key, {
|
|
|
|
|
lat: geo.ll[0],
|
|
|
|
|
lon: geo.ll[1],
|
|
|
|
|
city: geo.city || 'Unknown',
|
|
|
|
|
country: geo.country || 'Unknown',
|
|
|
|
|
count: row.hits,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const locations = Array.from(locationMap.values()).map(loc => ({
|
|
|
|
|
location: [loc.lat, loc.lon],
|
|
|
|
|
size: Math.min(0.1, 0.05 + loc.count * 0.005),
|
|
|
|
|
city: loc.city,
|
|
|
|
|
country: loc.country,
|
|
|
|
|
}));
|
2026-02-08 03:03:53 -05:00
|
|
|
|
|
|
|
|
return NextResponse.json({
|
2026-02-09 00:50:40 -05:00
|
|
|
locations,
|
|
|
|
|
totalVisitors: total?.count || 0,
|
|
|
|
|
active24h: last24h?.count || 0,
|
|
|
|
|
active7d: last7d?.count || 0,
|
2026-02-08 03:03:53 -05:00
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Visitor fetch error:', error);
|
2026-02-09 00:50:40 -05:00
|
|
|
return NextResponse.json({
|
|
|
|
|
locations: [],
|
|
|
|
|
totalVisitors: 0,
|
|
|
|
|
active24h: 0,
|
|
|
|
|
active7d: 0,
|
|
|
|
|
});
|
2026-02-08 03:03:53 -05:00
|
|
|
}
|
|
|
|
|
}
|