first commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
/node_modules
|
||||
|
||||
/.vscode
|
||||
/.idea
|
||||
Generated
+1573
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "ykonsole-server",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"description": "Monitoring tool using the bluetooth client.",
|
||||
"main": "./src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Gisle",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.17.1",
|
||||
"iconsole-bike-client": "^0.1.3",
|
||||
"sqlite3": "^4.1.0",
|
||||
"underscore": "^1.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
const express = require("express");
|
||||
|
||||
/**
|
||||
* @param {import("../repositories/sqlite3")} repo
|
||||
*/
|
||||
module.exports = function bikeRouter(repo) {
|
||||
const router = express.Router({caseSensitive: false});
|
||||
|
||||
router.get("/", async(_, res) => {
|
||||
try {
|
||||
const bikes = await repo.listBikes();
|
||||
return res.status(200).json(bikes);
|
||||
} catch (err) {
|
||||
return res.status(400).json({code: 400, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/:id", async(req, res) => {
|
||||
try {
|
||||
const bike = await repo.findBike(req.params.id);
|
||||
if (bike == null) {
|
||||
return res.status(404).json({code: 404, message: "bike not found"})
|
||||
}
|
||||
|
||||
return res.status(200).json(bike);
|
||||
} catch (err) {
|
||||
return res.status(500).json({code: 500, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/", async(req, res) => {
|
||||
try {
|
||||
const {name, driver, maxLevel, connect} = req.body;
|
||||
const id = await repo.insertBike({name, driver, maxLevel, connect})
|
||||
|
||||
return res.status(200).json({id, name, driver, maxLevel, connect});
|
||||
} catch (err) {
|
||||
return res.status(400).json({code: 400, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
router.put("/:id", async(req, res) => {
|
||||
try {
|
||||
const bike = await repo.findBike(req.params.id);
|
||||
if (bike == null) {
|
||||
return res.status(404).json({code: 404, message: "bike not found"})
|
||||
}
|
||||
|
||||
bike.name = req.body.name || "";
|
||||
await repo.updateBike(bike);
|
||||
|
||||
return res.status(200).json(bike);
|
||||
} catch (err) {
|
||||
return res.status(400).json({code: 400, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/:id", async(req, res) => {
|
||||
try {
|
||||
const bike = await repo.findBike(req.params.id);
|
||||
if (bike == null) {
|
||||
return res.status(404).json({code: 404, message: "bike not found"})
|
||||
}
|
||||
|
||||
await repo.deleteBike(bike);
|
||||
|
||||
return res.status(200).json(bike);
|
||||
} catch (err) {
|
||||
return res.status(500).json({code: 500, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
const express = require("express");
|
||||
|
||||
/**
|
||||
* @param {import("../repositories/sqlite3")} repo
|
||||
*/
|
||||
module.exports = function programRouter(repo) {
|
||||
const router = express.Router({caseSensitive: false});
|
||||
|
||||
router.get("/", async(_, res) => {
|
||||
try {
|
||||
const programs = await repo.listPrograms();
|
||||
return res.status(200).json(programs);
|
||||
} catch (err) {
|
||||
return res.status(400).json({code: 400, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/:id", async(req, res) => {
|
||||
try {
|
||||
const program = await repo.findProgram(req.params.id);
|
||||
if (program == null) {
|
||||
return res.status(404).json({code: 404, message: "program not found"})
|
||||
}
|
||||
|
||||
return res.status(200).json(program);
|
||||
} catch (err) {
|
||||
return res.status(500).json({code: 500, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/", async(req, res) => {
|
||||
try {
|
||||
const {name, cpm, warmupMin, warmupCpm} = req.body;
|
||||
const id = await repo.insertProgram({name, cpm, warmupMin, warmupCpm})
|
||||
|
||||
return res.status(200).json({id, name, cpm, warmupMin, warmupCpm});
|
||||
} catch (err) {
|
||||
return res.status(400).json({code: 400, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
router.put("/:id", async(req, res) => {
|
||||
try {
|
||||
const program = await repo.findProgram(req.params.id);
|
||||
if (program == null) {
|
||||
return res.status(404).json({code: 404, message: "program not found"})
|
||||
}
|
||||
|
||||
program.name = req.body.name || "";
|
||||
await repo.updateProgram(program);
|
||||
|
||||
return res.status(200).json(program);
|
||||
} catch (err) {
|
||||
return res.status(400).json({code: 400, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/:id", async(req, res) => {
|
||||
try {
|
||||
const program = await repo.findProgram(req.params.id);
|
||||
if (program == null) {
|
||||
return res.status(404).json({code: 404, message: "program not found"})
|
||||
}
|
||||
|
||||
await repo.deleteProgram(program);
|
||||
|
||||
return res.status(200).json(program);
|
||||
} catch (err) {
|
||||
return res.status(500).json({code: 500, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
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 = [];
|
||||
|
||||
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,
|
||||
active: true,
|
||||
})));
|
||||
}
|
||||
|
||||
try {
|
||||
const dbWorkouts = await repo.listWorkouts();
|
||||
return res.status(200).json(dbWorkouts.map(w => ({
|
||||
...w,
|
||||
active: workouts.find(w2 => w2.id === w.id) != null,
|
||||
})));
|
||||
} 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, active: true})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
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});
|
||||
} 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});
|
||||
} 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});
|
||||
} 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});
|
||||
} 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});
|
||||
} 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});
|
||||
} 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(workout);
|
||||
} catch (err) {
|
||||
return res.status(500).json({code: 500, message: err.message || err})
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
const MockDriver = require("./mock");
|
||||
|
||||
module.exports = function createDriver(name) {
|
||||
switch (name) {
|
||||
case "mock":
|
||||
return new MockDriver();
|
||||
default:
|
||||
throw new Error(`Driver ${name} doesn't exist!`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
const EventEmitter = require("events");
|
||||
const _ = require("underscore");
|
||||
|
||||
class MockDriver {
|
||||
constructor() {
|
||||
this.started = false;
|
||||
this.events = new EventEmitter();
|
||||
this.seconds = 0;
|
||||
this.level = 1;
|
||||
this.workoutState = {
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
speed: 0,
|
||||
rpm: 0,
|
||||
distance: 0,
|
||||
calories: 0,
|
||||
pulse: 0,
|
||||
watt: 0,
|
||||
level: 1,
|
||||
};
|
||||
|
||||
this.interval = null;
|
||||
}
|
||||
|
||||
connect() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.started) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.started = true;
|
||||
this.interval = setInterval(() => {
|
||||
this.seconds++;
|
||||
|
||||
this.workoutState.minutes = Math.floor(this.seconds / 60);
|
||||
this.workoutState.seconds = this.seconds % 60;
|
||||
this.workoutState.speed = Math.floor(300 + Math.random() * 200) / 10;
|
||||
this.workoutState.rpm = Math.floor(57 + Math.random() * 5);
|
||||
this.workoutState.distance += (this.seconds % 5 == 0) ? 0.1 : 0;
|
||||
this.workoutState.calories += (this.seconds % 2 == 0) ? 1 : 0;
|
||||
this.workoutState.pulse = null;
|
||||
this.workoutState.watt = Math.floor(100 + Math.random() * 20);
|
||||
this.workoutState.level = this.level;
|
||||
|
||||
this.events.emit("workoutState", {...this.workoutState});
|
||||
}, 1000);
|
||||
|
||||
this.workoutState.level = 1;
|
||||
this.workoutState.speed = 0;
|
||||
this.workoutState.rpm = 0;
|
||||
this.workoutState.watt = 0;
|
||||
|
||||
if (this.seconds === 0) {
|
||||
this.events.emit("workoutState", {...this.workoutState});
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
setLevel(n) {
|
||||
if (n < 0 || n > 32) {
|
||||
return Promise.reject(new Error("Level is out of range (0..=32)"))
|
||||
}
|
||||
|
||||
this.level = n;
|
||||
}
|
||||
|
||||
pause() {
|
||||
clearInterval(this.interval);
|
||||
this.started = false;
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.pause();
|
||||
this.workoutState = {
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
speed: 0,
|
||||
rpm: 0,
|
||||
distance: 0,
|
||||
calories: 0,
|
||||
pulse: 0,
|
||||
watt: 0,
|
||||
level: 1,
|
||||
};
|
||||
this.seconds = 0;
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.stop();
|
||||
this.events.emit("destroy");
|
||||
this.events.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MockDriver;
|
||||
@@ -0,0 +1,227 @@
|
||||
const sqlite3 = require("sqlite3").verbose();
|
||||
|
||||
class SQLite3Repository {
|
||||
constructor(path) {
|
||||
this.db = new sqlite3.Database(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
setup() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(SQL_TABLE_BIKE, (err) => {
|
||||
if (err != null) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
this.db.run(SQL_TABLE_PROGRAM, (err) => {
|
||||
if (err != null) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
this.db.run(SQL_TABLE_WORKOUT, (err) => {
|
||||
if (err != null) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
this.db.run(SQL_TABLE_MEASUREMENT, (err) => {
|
||||
if (err != null) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
insert(obj, table, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = `INSERT INTO ${table} (${params.join(", ")}) VALUES (${params.map(p => ':'+p).join(", ")});`;
|
||||
const values = params.map(p => obj[p]);
|
||||
this.db.run(query, values, function(err) {
|
||||
if (err != null) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(this.lastID);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
update(obj, table, key, keys) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = `UPDATE ${table} SET ${keys.map(k => `${k}=:${k}`).join(" AND ")} WHERE ${key}=:${key}`;
|
||||
const values = [
|
||||
...keys.map(k => obj[k]),
|
||||
obj[key],
|
||||
];
|
||||
|
||||
this.db.run(query, values, function(err) {
|
||||
if (err != null) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
delete(obj, table, key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = `DELETE FROM ${table} WHERE ${key}=:${key};`
|
||||
this.db.run(query, [obj[key]], function(err) {
|
||||
if (err != null) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(this.changes);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
findOne(table, match) {
|
||||
return this.find(table, match).then(res => res[0] || null)
|
||||
}
|
||||
|
||||
find(table, match) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const keys = Object.keys(match).filter(k => match[k] != null);
|
||||
const params = keys.map(k => match[k]);
|
||||
const query = `SELECT * FROM ${table}${keys.length > 0 ? ` WHERE ${keys.map(k => `${k}=:${k}`).join(" AND ")}` : ""};`;
|
||||
this.db.all(query, params, function(err, rows) {
|
||||
if (err != null) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(rows);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
findBike(id) {
|
||||
return this.findOne("bike", {id});
|
||||
}
|
||||
|
||||
listBikes() {
|
||||
return this.find("bike", {});
|
||||
}
|
||||
|
||||
insertBike(bike) {
|
||||
return this.insert(bike, "bike", ["name", "driver", "connect", "maxLevel"]);
|
||||
}
|
||||
|
||||
updateBike(bike) {
|
||||
return this.update(bike, "bike", "id", ["name"]);
|
||||
}
|
||||
|
||||
deleteBike(bike) {
|
||||
return this.delete(bike, "bike", "id");
|
||||
}
|
||||
|
||||
findProgram(id) {
|
||||
return this.findOne("program", {id});
|
||||
}
|
||||
|
||||
listPrograms() {
|
||||
return this.find("program", {});
|
||||
}
|
||||
|
||||
insertProgram(program) {
|
||||
return this.insert(program, "program", ["name", "cpm", "warmupMin", "warmupCpm"]);
|
||||
}
|
||||
|
||||
updateProgram(program) {
|
||||
return this.update(program, "program", "id", ["name"]);
|
||||
}
|
||||
|
||||
deleteProgram(program) {
|
||||
return this.delete(program, "program", "id");
|
||||
}
|
||||
|
||||
findWorkout(id) {
|
||||
return this.findOne("workout", {id}).then(d => ({...d, date: new Date(d.date)}));
|
||||
}
|
||||
|
||||
listWorkouts({programId, bikeId} = {}) {
|
||||
return this.find("workout", {programId, bikeId}).then(l => l.map(d => ({...d, date: new Date(d.date)})));
|
||||
}
|
||||
|
||||
insertWorkout(workout) {
|
||||
return this.insert(workout, "workout", ["bikeId","programId","date"]);
|
||||
}
|
||||
|
||||
deleteWorkout(workout) {
|
||||
return this.delete(workout, "workout", "id");
|
||||
}
|
||||
|
||||
findMeasurement(id) {
|
||||
return this.findOne("measurement", {id});
|
||||
}
|
||||
|
||||
listMeasurements(workoutId) {
|
||||
return this.find("measurement", {workoutId});
|
||||
}
|
||||
|
||||
insertMeasurement(measurement) {
|
||||
return this.insert(measurement, "measurement", ["workoutId","minutes","seconds","speed","rpm","distance","calories","pulse","watt","level"]);
|
||||
}
|
||||
|
||||
deleteMeasurement(measurement) {
|
||||
return this.delete(measurement, "measurement", "id");
|
||||
}
|
||||
|
||||
deleteMeasurements({workoutId}) {
|
||||
return this.delete({workoutId}, "measurement", "workoutId");
|
||||
}
|
||||
}
|
||||
|
||||
const SQL_TABLE_BIKE = `
|
||||
CREATE TABLE IF NOT EXISTS bike (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
driver VARCHAR(255) NOT NULL,
|
||||
connect VARCHAR(255) NOT NULL,
|
||||
maxLevel INT
|
||||
);
|
||||
`;
|
||||
|
||||
const SQL_TABLE_PROGRAM = `
|
||||
CREATE TABLE IF NOT EXISTS program (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
cpm INT NOT NULL,
|
||||
warmupMin INT NOT NULL,
|
||||
warmupCpm INT NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
const SQL_TABLE_WORKOUT = `
|
||||
CREATE TABLE IF NOT EXISTS workout (
|
||||
id INTEGER PRIMARY KEY,
|
||||
bikeId INT NOT NULL,
|
||||
programId INT NOT NULL,
|
||||
date DATETIME NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
const SQL_TABLE_MEASUREMENT = `
|
||||
CREATE TABLE IF NOT EXISTS measurement (
|
||||
id INTEGER PRIMARY KEY,
|
||||
workoutId INT NOT NULL,
|
||||
minutes INT,
|
||||
seconds INT,
|
||||
speed REAL,
|
||||
rpm REAL,
|
||||
distance REAL,
|
||||
calories REAL,
|
||||
pulse INT,
|
||||
watt REAL,
|
||||
level INT
|
||||
);
|
||||
`;
|
||||
|
||||
module.exports = SQLite3Repository;
|
||||
@@ -0,0 +1,20 @@
|
||||
const express = require('express');
|
||||
const SQLite3Repository = require("./repositories/sqlite3");
|
||||
|
||||
const repo = new SQLite3Repository("stuff.db");
|
||||
|
||||
repo.setup().catch(err => {
|
||||
console.error("Failed to setup db:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(express.json({strict: false}));
|
||||
|
||||
app.use("/api/bike/", require("./api/bike")(repo));
|
||||
app.use("/api/program/", require("./api/program")(repo));
|
||||
app.use("/api/workout/", require("./api/workout")(repo));
|
||||
|
||||
app.listen(8780);
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
const EventEmitter = require("events");
|
||||
const createDriver = require("../drivers");
|
||||
|
||||
class Workout {
|
||||
/**
|
||||
* @param {import("../repositories/sqlite3")} repo
|
||||
* @param {number} id
|
||||
* @param {{
|
||||
* id: string,
|
||||
* name: string,
|
||||
* driver: string,
|
||||
* connect: string,
|
||||
* maxLevel: number,
|
||||
* }} bike
|
||||
* @param {{
|
||||
* id: number,
|
||||
* name: string,
|
||||
* cpm: number,
|
||||
* warmupMin: number,
|
||||
* warmupCpm: number,
|
||||
* }} program
|
||||
* @param {Date} date
|
||||
*/
|
||||
constructor(repo, id, bike, program, date) {
|
||||
this.repo = repo;
|
||||
this.id = id;
|
||||
this.bike = bike;
|
||||
this.program = program;
|
||||
this.date = date;
|
||||
this.driver = null;
|
||||
|
||||
this.offsetSeconds = 0;
|
||||
this.offsets = {};
|
||||
|
||||
this.events = new EventEmitter();
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.driver = createDriver(this.bike.driver);
|
||||
this.driver.events.on("workoutState", ws => this.handleWorkoutState(ws));
|
||||
|
||||
return this.driver.connect();
|
||||
}
|
||||
|
||||
start() {
|
||||
return this.driver.start();
|
||||
}
|
||||
|
||||
pause() {
|
||||
return this.driver.pause();
|
||||
}
|
||||
|
||||
stop() {
|
||||
return this.driver.stop();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.events.emit("destroy");
|
||||
this.events.removeAllListeners();
|
||||
|
||||
if (this.driver != null) {
|
||||
this.driver.destroy();
|
||||
this.driver = null;
|
||||
}
|
||||
}
|
||||
|
||||
listMeasurements() {
|
||||
return this.repo.listMeasurements(this.id);
|
||||
}
|
||||
|
||||
handleWorkoutState(ws) {
|
||||
if (this.offsetSeconds > 0) {
|
||||
const seconds = (ws.minutes * 60) + ws.seconds + this.offsetSeconds;
|
||||
ws.minutes = Math.floor(seconds / 60);
|
||||
ws.seconds = seconds % 60;
|
||||
|
||||
for (const key in this.offsets) {
|
||||
if (!this.offsets.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ws[key] += this.offsets[key];
|
||||
}
|
||||
}
|
||||
|
||||
this.repo.insertMeasurement({...ws, workoutId: this.id});
|
||||
|
||||
this.events.emit("workoutState", ws);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../repositories/sqlite3")} repo
|
||||
* @param {number} bikeId
|
||||
* @param {number} programId
|
||||
*/
|
||||
static async create(repo, bikeId, programId) {
|
||||
const bike = await repo.findBike(bikeId);
|
||||
const program = await repo.findProgram(programId);
|
||||
const date = new Date();
|
||||
const id = await repo.insertWorkout({
|
||||
bikeId: bike.id,
|
||||
programId: program.id,
|
||||
date: date,
|
||||
});
|
||||
|
||||
return new Workout(repo, id, bike, program, date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../repositories/sqlite3")} repo
|
||||
* @param {number} id
|
||||
*/
|
||||
static async continue(repo, id) {
|
||||
const data = await repo.findWorkout(id);
|
||||
const bike = await repo.findBike(data.bikeId);
|
||||
const program = await repo.findWorkout(data.workoutId);
|
||||
|
||||
const workout = new Workout(repo, parseInt(id), bike, program, new Date(data.date));
|
||||
|
||||
const list = await repo.listMeasurements(id);
|
||||
if (list.length > 0) {
|
||||
const last = list[list.length - 1];
|
||||
|
||||
workout.offsetSeconds = (last.minutes * 60 + last.seconds);
|
||||
workout.offsets = {
|
||||
distance: last.distance,
|
||||
calories: last.calories,
|
||||
}
|
||||
}
|
||||
|
||||
return workout;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Workout;
|
||||
Reference in New Issue
Block a user