const express = require("express"); const Workout = require("../systems/workout"); /** * @param {import("../repositories/sqlite3")} repo */ module.exports = function workoutRouter(repo) { const router = express.Router({caseSensitive: false}); /** @type {Workout[]} */ let workouts = []; function activeData(id) { const workout = workouts.find(w2 => w2.id === id); if (workout == null) { return { active: false, }; } return { active: true, state: workout.state, } } router.get("/", async(req, res) => { if (req.query.active === "true") { return res.status(200).json(workouts.map(w => ({ id: w.id, bike: w.bike, program: w.program, date: w.date, ...activeData(w.id), }))); } try { const dbWorkouts = await repo.listWorkouts(); return res.status(200).json(dbWorkouts.map(w => ({ ...w, ...activeData(w.id), }))); } catch (err) { return res.status(400).json({code: 400, message: err.message || err}) } }); router.get("/:id", async(req, res) => { let workout = workouts.find(w => w.id == req.params.id); if (workout != null) { return res.status(200).json({ id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, cooldownMin: workout.cooldownMin, ...activeData(workout.id), }); } try { workout = await repo.findWorkout(req.params.id); if (workout == null) { return res.status(404).json({code: 404, message: "workout not found"}) } return res.status(200).json({active: false, ...workout}); } catch (err) { return res.status(500).json({code: 500, message: err.message || err}) } }); router.get("/:id/measurements/", async(req, res) => { try { const measurements = await repo.listMeasurements(req.params.id); if (measurements == null) { return res.status(404).json({code: 404, message: "measurements not found"}) } for (const measurement of measurements) { delete measurement.id; delete measurement.workoutId; } return res.status(200).json(measurements); } catch (err) { return res.status(500).json({code: 500, message: err.message || err}) } }); router.post("/", async(req, res) => { try { const {bikeId, programId} = req.body; const workout = await Workout.create(repo, bikeId, programId); workouts.push(workout); return res.status(200).json({ id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, cooldownMin: workout.cooldownMin, ...activeData(workout.id), }); } catch (err) { return res.status(400).json({code: 400, message: err.message || err}); } }); router.put("/:id", async(req, res) => { let workout = workouts.find(w => w.id == req.params.id); if (workout == null) { return res.status(404).json({code: 404, message: "Workout not found or inactive."}) } try { if (req.body.cooldownMin) { workout.setCooldownMin(req.body.cooldownMin); await repo.updateWorkout(workout) } return res.status(200).json({ id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, cooldownMin: workout.cooldownMin, ...activeData(workout.id), }); } catch (err) { return res.status(400).json({code: 400, message: err.message || err}) } }); router.post("/:id/continue", async(req, res) => { let workout = workouts.find(w => w.id == req.params.id); if (workout != null) { return res.status(400).json({code: 400, message: "Workout already active."}) } try { workout = await Workout.continue(repo, req.params.id); workouts.push(workout); return res.status(200).json({ id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, cooldownMin: workout.cooldownMin, ...activeData(workout.id), }); } catch (err) { return res.status(400).json({code: 400, message: err.message || err}) } }); router.post("/:id/connect", async(req, res) => { const workout = workouts.find(w => w.id == req.params.id); if (workout == null) { return res.status(404).json({code: 404, message: "Workout not found or inactive."}) } try { await workout.connect(); return res.status(200).json({ id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, cooldownMin: workout.cooldownMin, ...activeData(workout.id), }); } catch(err) { return res.status(500).json({code: 500, message: err.message || err}); } }); router.post("/:id/start", async(req, res) => { const workout = workouts.find(w => w.id == req.params.id); if (workout == null) { return res.status(404).json({code: 404, message: "Workout not found or inactive."}) } try { await workout.start(); return res.status(200).json({ id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, cooldownMin: workout.cooldownMin, ...activeData(workout.id), }); } catch(err) { return res.status(500).json({code: 500, message: err.message || err}); } }); router.post("/:id/pause", async(req, res) => { const workout = workouts.find(w => w.id == req.params.id); if (workout == null) { return res.status(404).json({code: 404, message: "Workout not found or inactive."}) } try { await workout.pause(); return res.status(200).json({ id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, cooldownMin: workout.cooldownMin, ...activeData(workout.id), }); } catch(err) { return res.status(500).json({code: 500, message: err.message || err}); } }); router.post("/:id/stop", async(req, res) => { const workout = workouts.find(w => w.id == req.params.id); if (workout == null) { return res.status(404).json({code: 404, message: "Workout not found or inactive."}) } try { if (workout.driver != null) { await workout.stop(); } workouts = workouts.filter(w => w !== workout); await workout.destroy(); return res.status(200).json({ id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, cooldownMin: workout.cooldownMin, ...activeData(workout.id), }); } catch(err) { return res.status(500).json({code: 500, message: err.message || err}); } }); router.delete("/:id", async(req, res) => { let workout = workouts.find(w => w.id == req.params.id); if (workout != null) { return res.status(400).json({code: 400, message: "Workout already active."}) } try { workout = await repo.findWorkout(req.params.id); if (workout == null) { return res.status(404).json({code: 404, message: "workout not found"}) } await repo.deleteWorkout(workout); return res.status(200).json({ id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, cooldownMin: workout.cooldownMin, ...activeData(workout.id), }); } catch (err) { return res.status(500).json({code: 500, message: err.message || err}) } }); router.ws("/:id/subscribe", (ws, req) => { const workout = workouts.find(w => w.id === req.params.id); if (workout == null) { ws.send(JSON.stringify({error: "workout not found"})); ws.close(); } workout.listMeasurements().then((list) => { list = list.map(v => {delete v.id; delete v.workoutId; return v}); for (let i = 0; i < list.length; i += 240) { ws.send(JSON.stringify({workoutStatusBackfill: list.slice(i, i+240)})); } }).catch(err => { ws.send(JSON.stringify({error: `could not list measurements: ${err.message || err}`})); ws.close(); }); const onWorkoutStatus = (workoutStatus) => { workoutStatus = {...workoutStatus}; delete workoutStatus.id; ws.send(JSON.stringify({workoutStatus})) } workout.events.on("workoutStatus", onWorkoutStatus); const onState = (state) => { ws.send(JSON.stringify({state})) } workout.events.on("state", onState); const onCooldownMin = (cooldownMin) => { ws.send(JSON.stringify({cooldownMin})) } workout.events.on("cooldownMin", onCooldownMin); ws.onclose = () => { workout.events.removeListener("workoutStatus", onWorkoutStatus); workout.events.removeListener("state", onState); workout.events.removeListener("cooldownMin", onCooldownMin); ws.removeAllListeners(); }; ws.send(JSON.stringify({ state: workout.state, workout: { id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, cooldownMin: workout.cooldownMin, }, })); }) return router; }