Gisle Aune
5 years ago
commit
f47d8af9d3
12 changed files with 2419 additions and 0 deletions
-
4.gitignore
-
1573package-lock.json
-
21package.json
-
74src/api/bike.js
-
74src/api/program.js
-
178src/api/workout.js
-
10src/drivers/index.js
-
103src/drivers/mock.js
-
227src/repositories/sqlite3.js
-
20src/server.js
-
135src/systems/workout.js
-
BINstuff.db
@ -0,0 +1,4 @@ |
|||
/node_modules |
|||
|
|||
/.vscode |
|||
/.idea |
1573
package-lock.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -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; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue