1import express from 'express';
2import * as tests from '../controller/speedtests.js';
3import * as pauseController from '../controller/pause.js';
4import * as config from '../controller/config.js';
5import * as testTask from '../tasks/speedtest.js';
6import password from '../middlewares/password.js';
7
8const app = express.Router();
9
10const validateDateRange = (from, to) => {
11 if (!from || !to) {
12 return { valid: false, message: "Both 'from' and 'to' date parameters are required" };
13 }
14 if (!/^\d{4}-\d{2}-\d{2}$/.test(from)) {
15 return { valid: false, message: "Invalid 'from' date format. Use YYYY-MM-DD" };
16 }
17 if (!/^\d{4}-\d{2}-\d{2}$/.test(to)) {
18 return { valid: false, message: "Invalid 'to' date format. Use YYYY-MM-DD" };
19 }
20 return { valid: true };
21};
22
23app.get("/", password(true), async (req, res) => {
24 if (req.query.limit && /[^0-9]/.test(req.query.limit))
25 return res.status(400).json({message: "You need to provide a correct number in the limit parameter"});
26
27 if (req.query.afterId && /[^0-9]/.test(req.query.afterId))
28 return res.status(400).json({message: "You need to provide a correct number in the afterId parameter"});
29
30 res.json(await tests.listTests(req.query.afterId, req.query.limit));
31});
32
33app.get("/statistics", password(true), async (req, res) => {
34 const { from, to } = req.query;
35 const validation = validateDateRange(from, to);
36 if (!validation.valid) {
37 return res.status(400).json({ message: validation.message });
38 }
39
40 res.json(await tests.listStatistics(from, to));
41});
42
43app.get("/export", password(true), async (req, res) => {
44 const { from, to, format } = req.query;
45 const validation = validateDateRange(from, to);
46 if (!validation.valid) {
47 return res.status(400).json({ message: validation.message });
48 }
49
50 const exportData = await tests.exportTests(from, to);
51
52 if (format === 'csv') {
53 const csvHeader = 'id,ping,jitter,download,upload,time,type,created,error\n';
54 const csvRows = exportData.map(test =>
55 `${test.id},${test.ping ?? ''},${test.jitter ?? ''},${test.download ?? ''},${test.upload ?? ''},${test.time ?? ''},${test.type ?? ''},${test.created ?? ''},${(test.error ?? '').replace(/,/g, ';').replace(/\n/g, ' ')}`
56 ).join('\n');
57
58 res.setHeader('Content-Type', 'text/csv');
59 res.setHeader('Content-Disposition', `attachment; filename="pixies-export-${from}-to-${to}.csv"`);
60 res.send(csvHeader + csvRows);
61 } else {
62 res.setHeader('Content-Type', 'application/json');
63 res.setHeader('Content-Disposition', `attachment; filename="pixies-export-${from}-to-${to}.json"`);
64 res.json(exportData);
65 }
66});
67
68app.post("/run", password(false), async (req, res) => {
69 if (pauseController.currentState) return res.status(410).json({message: "The speedtests are currently paused"});
70 if (await config.getValue("provider") === "none") return res.status(410).json({message: "No provider selected"});
71 let speedtest = await testTask.create("custom");
72 if (speedtest !== undefined) return res.status(409).json({message: "An speedtest is already running"});
73 res.json({message: "Speedtest successfully created"});
74});
75
76app.get("/status", password(true), (req, res) => {
77 res.json({paused: pauseController.currentState, running: testTask.isRunning()});
78});
79
80app.post("/pause", password(false), (req, res) => {
81 if (!req.body.resumeIn) return res.status(400).json({message: "You need to provide when to resume"});
82
83 if (req.body.resumeIn === -1) {
84 pauseController.updateState(true);
85 } else if (!pauseController.resumeIn(req.body.resumeIn)) {
86 return res.status(400).json({message: "You need to provide when to resume"});
87 }
88
89 res.json({message: "Successfully paused the speedtests"});
90});
91
92app.post("/continue", password(false), (req, res) => {
93 pauseController.updateState(false);
94 res.json({message: "Successfully resumed the speedtests"});
95});
96
97app.get("/:id", password(true), async (req, res) => {
98 let test = await tests.getOne(req.params.id);
99 if (test === null) return res.status(404).json({message: "Speedtest not found"});
100 res.json(test);
101});
102
103app.delete("/:id", password(false), async (req, res) => {
104 let test = await tests.deleteOne(req.params.id);
105 if (!test) return res.status(404).json({message: "Speedtest not found"});
106 res.json({message: "Successfully deleted the provided speedtest"});
107});
108
109export default app;