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:
Mohan Ki
2026-07-27 17:00:54 +05:30
parent fba969dd55
commit 9aaf94625a
8 changed files with 163 additions and 38 deletions
+15 -6
View File
@@ -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);
+5 -4
View File
@@ -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
+57
View File
@@ -0,0 +1,57 @@
const http = require('http');
const options = {
hostname: 'localhost',
port: 4001,
path: '/api/auth/login',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
if (!res.headers['set-cookie']) {
console.log('No cookie', data); return;
}
const cookie = res.headers['set-cookie'][0];
// Generate QR
const largeStr = 'a'.repeat(200000);
const postData = JSON.stringify({
name: 'Test QR',
type: 'dynamic',
dataType: 'URL',
destinationUrl: 'https://google.com',
designData: { image: 'data:image/png;base64,' + largeStr }
});
const options2 = {
hostname: 'localhost',
port: 4001,
path: '/api/qr/generate',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
'Cookie': cookie
}
};
const req2 = http.request(options2, (res2) => {
let d = '';
res2.on('data', c => d+=c);
res2.on('end', () => {
console.log('Status:', res2.statusCode);
console.log('Body:', d);
});
});
req2.write(postData);
req2.end();
});
});
req.write(JSON.stringify({email: 'admin@ckqr.com', password: 'password123'}));
req.end();