import express from 'express'; import cors from 'cors'; import { readFileSync, writeFileSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const DATA_FILE = join(__dirname, 'data', 'popularity.json'); const DOWNLOADS_LOG = join(__dirname, 'data', 'downloads.json'); const app = express(); app.use(cors()); app.use(express.json()); // Ensure data dir exists import { mkdirSync } from 'fs'; mkdirSync(join(__dirname, 'data'), { recursive: true }); // Load or init popularity data function loadPopularity() { if (existsSync(DATA_FILE)) { return JSON.parse(readFileSync(DATA_FILE, 'utf8')); } return {}; } function savePopularity(data) { writeFileSync(DATA_FILE, JSON.stringify(data, null, 2)); } function loadDownloads() { if (existsSync(DOWNLOADS_LOG)) { return JSON.parse(readFileSync(DOWNLOADS_LOG, 'utf8')); } return { total: 0, history: [] }; } function saveDownloads(data) { writeFileSync(DOWNLOADS_LOG, JSON.stringify(data, null, 2)); } // POST /api/analytics/download — record a bundle download app.post('/api/analytics/download', (req, res) => { const { apps, timestamp } = req.body; if (!apps || !Array.isArray(apps)) { return res.status(400).json({ error: 'apps array required' }); } // Update popularity counts const popularity = loadPopularity(); for (const appId of apps) { popularity[appId] = (popularity[appId] || 0) + 1; } savePopularity(popularity); // Log the download const downloads = loadDownloads(); downloads.total += 1; downloads.history.push({ timestamp: timestamp || new Date().toISOString(), apps, count: apps.length, }); // Keep last 10,000 entries to avoid unbounded growth if (downloads.history.length > 10000) { downloads.history = downloads.history.slice(-10000); } saveDownloads(downloads); res.json({ ok: true, total_downloads: downloads.total }); }); // GET /api/analytics/popularity — get app download counts app.get('/api/analytics/popularity', (req, res) => { const popularity = loadPopularity(); res.json(popularity); }); // GET /api/analytics/stats — overview stats app.get('/api/analytics/stats', (req, res) => { const popularity = loadPopularity(); const downloads = loadDownloads(); // Top apps const sorted = Object.entries(popularity) .sort((a, b) => b[1] - a[1]); res.json({ total_downloads: downloads.total, unique_apps_selected: sorted.length, top_20: sorted.slice(0, 20).map(([id, count]) => ({ id, count })), recent: downloads.history.slice(-10).reverse(), }); }); // GET /api/analytics/app/:id — stats for a specific app app.get('/api/analytics/app/:id', (req, res) => { const popularity = loadPopularity(); const count = popularity[req.params.id] || 0; const downloads = loadDownloads(); const total = downloads.total || 1; const percentage = ((count / total) * 100).toFixed(1); res.json({ id: req.params.id, download_count: count, percentage: `${percentage}%`, total_bundles: total, }); }); const PORT = process.env.PORT || 3052; app.listen(PORT, () => { console.log(`StackPilot Analytics API running on port ${PORT}`); });