feat: add SVG/PNG download options, track geolocation from CF headers, and fix analytics pie charts by parsing counts as integers
This commit is contained in:
+15
-6
@@ -22,8 +22,8 @@ router.get('/:qrCodeId', verifyToken, async (req, res) => {
|
||||
return res.status(403).json({ error: 'Unauthorized or QR code not found' });
|
||||
}
|
||||
|
||||
// Get total scans
|
||||
const totalQuery = await pgPool.query('SELECT COUNT(*) as total FROM qr_scans WHERE qr_code_id = $1', [qrCodeId]);
|
||||
// Get total scans and unique scanners
|
||||
const totalQuery = await pgPool.query('SELECT COUNT(*) as total, COUNT(DISTINCT ip_address) as unique_scanners FROM qr_scans WHERE qr_code_id = $1', [qrCodeId]);
|
||||
|
||||
// Get OS breakdown
|
||||
const osQuery = await pgPool.query(
|
||||
@@ -37,6 +37,12 @@ router.get('/:qrCodeId', verifyToken, async (req, res) => {
|
||||
[qrCodeId]
|
||||
);
|
||||
|
||||
// Get Country breakdown
|
||||
const countryQuery = await pgPool.query(
|
||||
'SELECT country, COUNT(*) as count FROM qr_scans WHERE qr_code_id = $1 GROUP BY country ORDER BY count DESC',
|
||||
[qrCodeId]
|
||||
);
|
||||
|
||||
// Get Scans over time (last 7 days grouped by date)
|
||||
const timeQuery = await pgPool.query(
|
||||
`SELECT DATE(scanned_at) as date, COUNT(*) as count
|
||||
@@ -46,11 +52,14 @@ router.get('/:qrCodeId', verifyToken, async (req, res) => {
|
||||
[qrCodeId]
|
||||
);
|
||||
|
||||
// Parse string counts from PG back to integers for Recharts
|
||||
res.json({
|
||||
totalScans: parseInt(totalQuery.rows[0].total),
|
||||
osBreakdown: osQuery.rows,
|
||||
deviceBreakdown: deviceQuery.rows,
|
||||
timeline: timeQuery.rows
|
||||
totalScans: parseInt(totalQuery.rows[0].total) || 0,
|
||||
uniqueScanners: parseInt(totalQuery.rows[0].unique_scanners) || 0,
|
||||
osBreakdown: osQuery.rows.map(r => ({ ...r, count: parseInt(r.count) })),
|
||||
deviceBreakdown: deviceQuery.rows.map(r => ({ ...r, count: parseInt(r.count) })),
|
||||
countryBreakdown: countryQuery.rows.map(r => ({ country: r.country || 'Unknown', count: parseInt(r.count) })),
|
||||
timeline: timeQuery.rows.map(r => ({ ...r, count: parseInt(r.count) }))
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
@@ -53,12 +53,13 @@ router.get('/:shortUrlId', async (req, res) => {
|
||||
const parser = new UAParser(req.headers['user-agent']);
|
||||
const os = parser.getOS().name || 'Unknown';
|
||||
const deviceType = parser.getDevice().type || 'desktop';
|
||||
const ip = req.ip || req.connection.remoteAddress;
|
||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
const country = req.headers['cf-ipcountry'] || 'Unknown';
|
||||
|
||||
pgPool.query(
|
||||
`INSERT INTO qr_scans (qr_code_id, ip_address, user_agent, os, device_type)
|
||||
VALUES ($1, $2, $3, $4, $5)`,
|
||||
[qrData.qr_id, ip, req.headers['user-agent'], os, deviceType]
|
||||
`INSERT INTO qr_scans (qr_code_id, ip_address, user_agent, os, device_type, country)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[qrData.qr_id, ip, req.headers['user-agent'], os, deviceType, country]
|
||||
).catch(err => console.error('Failed to log scan:', err));
|
||||
|
||||
// 4. Check Tier & Redirect
|
||||
|
||||
Reference in New Issue
Block a user