Files
fredy/lib/api/routes/dashboardRouter.js

89 lines
2.8 KiB
JavaScript
Raw Permalink Normal View History

2025-12-14 12:23:59 +01:00
/*
* Copyright (c) 2026 by Christian Kellner.
2025-12-14 12:23:59 +01:00
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import * as jobStorage from '../../services/storage/jobStorage.js';
import { getListingsKpisForJobIds, getProviderDistributionForJobIds } from '../../services/storage/listingsStorage.js';
import { getSettings } from '../../services/storage/settingsStorage.js';
2026-04-27 16:56:04 +02:00
import { isAdmin } from '../security.js';
2025-12-14 12:23:59 +01:00
2026-04-27 16:56:04 +02:00
function getAccessibleJobs(request) {
const currentUser = request.session.currentUser;
const admin = isAdmin(request);
2025-12-14 12:23:59 +01:00
return jobStorage
.getJobs()
.filter((job) => admin || job.userId === currentUser || job.shared_with_user.includes(currentUser));
}
function cap(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
2026-06-09 16:52:37 +02:00
/**
* Compute the most recent job trigger timestamp across the given jobs.
*
* Returns `null` when none of the jobs has ever been triggered. The value is
* persisted per-job via `jobs.last_run_at`, so the dashboard reflects the
* scope visible to the current user (own + shared, or all for admins) rather
* than a process-wide in-memory value.
*
* @param {Array<{lastRunAt?: number|null}>} jobs
* @returns {number|null}
*/
function computeLastRun(jobs) {
let lastRun = null;
for (const job of jobs) {
const ts = job.lastRunAt;
if (typeof ts === 'number' && (lastRun == null || ts > lastRun)) {
lastRun = ts;
}
}
return lastRun;
}
2026-04-27 16:56:04 +02:00
/**
* @param {import('fastify').FastifyInstance} fastify
*/
export default async function dashboardPlugin(fastify) {
fastify.get('/', async (request) => {
const jobs = getAccessibleJobs(request);
const settings = await getSettings();
const totalJobs = jobs.length;
const totalListings = jobs.reduce((sum, j) => sum + (j.numberOfFoundListings || 0), 0);
const jobIds = jobs.map((j) => j.id);
const { numberOfActiveListings, medianPriceOfListings } = getListingsKpisForJobIds(jobIds);
2025-12-14 12:23:59 +01:00
2026-04-27 16:56:04 +02:00
const providerPieRaw = getProviderDistributionForJobIds(jobIds);
const providerPie = Array.isArray(providerPieRaw)
2025-12-14 12:23:59 +01:00
? {
2026-04-27 16:56:04 +02:00
labels: providerPieRaw.map((p) => cap(p.type)),
values: providerPieRaw.map((p) => Number(p.value) || 0),
2025-12-14 12:23:59 +01:00
}
2026-04-27 16:56:04 +02:00
: providerPieRaw && typeof providerPieRaw === 'object'
? {
labels: Array.isArray(providerPieRaw.labels) ? providerPieRaw.labels : [],
values: Array.isArray(providerPieRaw.values) ? providerPieRaw.values : [],
}
: { labels: [], values: [] };
2025-12-14 12:23:59 +01:00
2026-06-09 16:52:37 +02:00
const lastRun = computeLastRun(jobs);
2026-04-27 16:56:04 +02:00
return {
general: {
interval: settings.interval,
2026-06-09 16:52:37 +02:00
lastRun,
nextRun: lastRun == null ? 0 : lastRun + settings.interval * 60000,
2026-04-27 16:56:04 +02:00
},
kpis: {
totalJobs,
totalListings,
numberOfActiveListings,
medianPriceOfListings,
},
pie: providerPie,
};
});
}