116 lines
3.2 KiB
JavaScript
116 lines
3.2 KiB
JavaScript
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}`);
|
|
});
|