Commit e14d37d3 authored by Magnus Holm Brunbjerg's avatar Magnus Holm Brunbjerg
Browse files

Api

parents
Pipeline #12286 canceled with stages
node_modules/
.vscode/
.env
\ No newline at end of file
# api
The api provides the interaction layer with the database.
\ No newline at end of file
'use strict'
require('dotenv').config()
require('./src/app/index')
This diff is collapsed.
{
"name": "api",
"version": "1.0.0",
"description": "The api for the gain.fam ITPDP platform",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js",
"generate": "node src/db/data/generator.js"
},
"repository": {
"type": "git",
"url": "git@gitlab.au.dk:ITPDP2019/gain.fam/api.git"
},
"author": "gain.fam",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"dotenv": "^8.0.0",
"express": "^4.16.4",
"ip": "^1.1.5",
"mysql": "^2.17.1"
},
"devDependencies": {
"standard": "^12.0.1"
}
}
'use strict'
const express = require('express')
const bodyParser = require('body-parser')
const db = require('../db/index')
const app = express()
const port = process.env.PORT
app.listen(port, (err) => {
if (err) return console.error(err)
console.log(`Api server startet på http://${require('ip').address()}:${port}`)
})
/*
*
* Middleware stuff
*
*/
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
// Console logging of request url and time
app.use((req, res, next) => {
console.log(`${new Date().toLocaleTimeString('en-GB', { hour12: false })} - ${req.originalUrl}`)
next()
})
/*
*
* Api endpoint stuff
*
*/
app.route('/api/experiments')
// Get all experiments
.get((req, res, next) => {
// Get experiments from db
db.experiments.all((err, experiments) => {
if (err) return next(err)
// Return all experiments to requester
res.status(200).send(experiments)
})
})
// Create a new experiment if one isn't running
.post((req, res, next) => {
// Check if an experiment is running
db.experiments.running((err, experiment) => {
if (err) return next(err)
// Don't create if experiment is running
if (experiment) {
// Return experiment if one is running
res.status(200).send(experiment)
} else {
// Create new experiment
db.experiments.create(req.body.user_id, req.body.start_time, (err) => {
if (err) return next(err)
// Return no content if experiment is created
res.status(204).end()
})
}
})
})
app.route('/api/experiments/running')
// Get the running experiment
.get((req, res, next) => {
// Get running experiment from db
db.experiments.running((err, experiment) => {
if (err) return next(err)
// Return running experiment
res.status(200).send(experiment)
})
})
// Create or end issue for the running experiment
.post((req, res, next) => {
// Get running experiment from db
db.experiments.running((err, experiment) => {
if (err) return next(err)
// Procede if running experiment exist
if (experiment) {
// Check if an issue is open
db.issues.check(req.body.hardware_id, (err, issue) => {
if (err) return next(err)
if (req.body.activated && !issue) {
// Create a new issue if requested and no issue is open
db.issues.create(experiment.id, req.body.hardware_id, req.body.step, req.body.start_time, (err) => {
if (err) return next(err)
// Return an appropriate color
res.status(200).send({ color: [Math.floor(Math.random() * Math.floor(255)), Math.floor(Math.random() * Math.floor(255)), Math.floor(Math.random() * Math.floor(255))] })
})
} else if (!req.body.activated && issue) {
// End issue if requested and issue is open
db.issues.end(req.body.hardware_id, req.body.end_time, (err) => {
if (err) return next(err)
// Return no color
res.status(200).send({ color: [0, 0, 0] })
})
} else {
// Return nothing if requested action and issue status does not match
res.status(204).end()
}
})
} else {
// Return nothing if no issue is created
res.status(204).end()
}
})
})
app.route('/api/experiments/:id')
// Get specific experiment
.get((req, res, next) => {
const experimentId = req.params.id
db.experiments.find(experimentId, (err, experiment) => {
if (err) return next(err)
res.status(200).send(experiment)
})
})
// End experiment
.post((req, res, next) => {
const experimentId = req.params.id
// End all open issues for the experiment
db.issues.endAll(experimentId, req.body.end_time, (err) => {
if (err) return next(err)
// End the experiment
db.experiments.end(experimentId, req.body.name, req.body.end_time, (err) => {
if (err) return next(err)
res.status(204).end()
})
})
})
app.route('/api/experiments/:id/issues')
// Get all issues for the experiment
.get((req, res, next) => {
const experimentId = req.params.id
db.issues.find(experimentId, (err, issues) => {
if (err) return next(err)
res.status(200).send(issues)
})
})
app.route('/api/users/:id')
// Get specific user
.get((req, res, next) => {
const userId = req.params.id
db.users.findId(userId, (err, user) => {
if (err) return next(err)
res.status(200).send(user)
})
})
app.route('/api/users/:id/experiments')
// Get experiments for user
.get((req, res, next) => {
const userId = req.params.id
db.experiments.findUser(userId, (err, user) => {
if (err) return next(err)
res.status(200).send(user)
})
})
'use strict'
require('dotenv').config()
const db = require('../index')
const pool = require('../pool')
/*
* SPAGHET CODE AND CALLBACK HELL AHEAD!!
*/
// Create function for data generation for easier repetition
function generateData (userName, userUsername, userPassword, maxExperiments, maxIssues, maxSteps, hardwareIds) {
// Create the user
db.users.create(userName, userUsername, userPassword, (err) => {
if (err) console.error(err)
// Get the user
db.users.find(userUsername, (err, user) => {
if (err) console.error(err)
const userId = user.id
// Amount of experiments to generate
const experimentAmount = Math.floor(1 + Math.random() * Math.floor(maxExperiments - 1))
// Generate experiments
for (let i = 0; i < experimentAmount; i++) {
// Create start and end time for experiment
const expStartTime = new Date(Date.now() + Math.floor(Math.random() * Math.floor(365)) * 86400000)
const expEndTime = new Date(expStartTime.valueOf() + 7200000)
// Create new experiment
pool.query({
sql: 'INSERT INTO experiments(id, user_id, name, start_time, end_time) VALUES(UUID_TO_BIN(UUID()), UUID_TO_BIN(?), ?, ?, ?)',
values: [userId, `${user.username}'s eksperiment nr. ${i}`, expStartTime, expEndTime]
}, (err, res) => {
if (err) console.error(err)
db.experiments.findUser(userId, (err, experiments) => {
if (err) console.error(err)
const experimentId = experiments[i].id
// Amount of issues to generate
const issuesAmount = Math.floor(1 + Math.random() * Math.floor(maxIssues - 1))
// Generate issues
for (let n = 0; n < issuesAmount; n++) {
// Create step for issue
const step = Math.floor(Math.random() * Math.floor(maxSteps))
// Create random start time for issue between experiment start and 5 min from end
const issStartTime = new Date(expStartTime.valueOf() + Math.floor(Math.random() * Math.floor(6900000)))
const issEndTime = new Date(issStartTime.valueOf() + 300000)
// Get random hardware id from the list for the issue
const hardwareId = hardwareIds[Math.floor(Math.random() * Math.floor(hardwareIds.length - 1))]
pool.query({
sql: 'INSERT INTO issues(id, experiment_id, hardware_id, step, start_time, end_time) VALUES(UUID_TO_BIN(UUID()), UUID_TO_BIN(?), UUID_TO_BIN(?), ?, ?, ?)',
values: [experimentId, hardwareId, step, issStartTime, issEndTime]
}, (err) => {
if (err) console.error(err)
})
}
})
})
}
})
})
}
// 15 test hardware ids
const hardwareIds = [
'b6e2e91e-7100-11e9-9695-d07e350e43bd',
'c05d308b-7100-11e9-9695-d07e350e43bd',
'cdab2f2a-7100-11e9-9695-d07e350e43bd',
'df17389f-7100-11e9-9695-d07e350e43bd',
'e33c2355-7100-11e9-9695-d07e350e43bd',
'e805e607-7100-11e9-9695-d07e350e43bd',
'eba3bdb0-7100-11e9-9695-d07e350e43bd',
'ef4b0ff3-7100-11e9-9695-d07e350e43bd',
'06d2cbb6-7101-11e9-9695-d07e350e43bd',
'0c223c8f-7101-11e9-9695-d07e350e43bd',
'86e7c2a5-710a-11e9-9695-d07e350e43bd',
'8f2de67f-710a-11e9-9695-d07e350e43bd',
'92f05b8a-710a-11e9-9695-d07e350e43bd',
'9689056f-710a-11e9-9695-d07e350e43bd',
'9a9ae959-710a-11e9-9695-d07e350e43bd'
]
generateData('Lars Larsen', 'lars', 'password', 15, 25, 6, hardwareIds)
generateData('Kim Larsen', 'kim', 'password', 15, 25, 6, hardwareIds)
'use strict'
const users = require('./schemas/users')
const experiments = require('./schemas/experiments')
const issues = require('./schemas/issues')
module.exports = { users, experiments, issues }
'use strict'
const mysql = require('mysql')
const pool = mysql.createPool({
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE
})
module.exports = pool
'use strict'
const pool = require('../pool')
/**
* Ensure schema for experiments exist.
*/
pool.query(
`CREATE TABLE IF NOT EXISTS experiments (
id BINARY(16),
user_id BINARY(16) NOT NULL,
name VARCHAR(100),
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES users(id)
)`, (err, res) => {
if (err) throw err
if (res.warningCount === 0) console.log('Database: experiments schema created')
}
)
/**
* An experiment consists of an id, user, name, start time and end time.
*
* An experiment could be: uuid, Peter Petersen, hydrogen test, 01/01/2019 15:30, 01/01/2019 16:00.
*/
class Experiments {
/**
* Create a new experiment.
* @param {string} user - The uuid of the user creating the experiment.
* @param {Date} time - The time of creation.
* @param {errCallback} callback - The callback that handles the possible errors.
*/
static create (user, time, callback) {
pool.query({
sql: 'INSERT INTO experiments(id, user_id, name, start_time) VALUES(UUID_TO_BIN(UUID()), UUID_TO_BIN(?), ?, ?)',
values: [user, 'unavngivet', new Date(time)]
}, (err, res) => {
callback(err)
})
}
/**
* End a running experiment.
* @param {string} id - The uuid of the experiment.
* @param {string} name - The name of the experiment.
* @param {Date} time - The end time of the experiment.
* @param {errCallback} callback - The callback that handles the possible errors.
*/
static end (id, name, time, callback) {
pool.query({
sql: 'UPDATE experiments SET name = ?, end_time = ? WHERE BIN_TO_UUID(id) = ?',
values: [name, new Date(time), id]
}, (err) => {
callback(err)
})
}
/**
* Find a specific experiment from uuid.
* @param {string} id - The uuid of the experiment.
* @param {resultCallback} callback - The callback that handles the result.
*/
static find (id, callback) {
pool.query({
sql: `SELECT BIN_TO_UUID(E.id) AS id, BIN_TO_UUID(E.user_id) AS user_id, U.name AS user_name, E.name AS name, E.start_time AS start_time, E.end_time AS end_time
FROM experiments AS E JOIN users AS U ON E.user_id = U.id
WHERE BIN_TO_UUID(E.id) = ?`,
values: id
}, (err, res) => {
callback(err, res[0])
})
}
/**
* Find all experiments with the given user.
* @param {string} id - The uuid of the user.
* @param {resultCallback} callback - The callback that handles the result.
*/
static findUser (id, callback) {
pool.query({
sql: `SELECT BIN_TO_UUID(E.id) AS id, BIN_TO_UUID(E.user_id) AS user_id, U.name AS user_name, E.name AS name, E.start_time AS start_time, E.end_time AS end_time
FROM experiments AS E JOIN users AS U ON E.user_id = U.id
WHERE BIN_TO_UUID(E.user_id) = ?`,
values: id
}, (err, res) => {
callback(err, res)
})
}
/**
* Get running experiment.
* @param {resultCallback} callback - The callback that handles the result.
*/
static running (callback) {
pool.query({
sql: `SELECT BIN_TO_UUID(E.id) AS id, BIN_TO_UUID(E.user_id) AS user_id, U.name AS user_name, E.name AS name, E.start_time AS start_time, E.end_time AS end_time
FROM experiments AS E JOIN users AS U ON E.user_id = U.id
WHERE end_time IS NULL`
}, (err, res) => {
callback(err, res[0])
})
}
/**
* Get all experiments.
* @param {resultCallback} callback - The callback that handles the result.
*/
static all (callback) {
pool.query({
sql: `SELECT BIN_TO_UUID(E.id) AS id, BIN_TO_UUID(E.user_id) AS user_id, U.name AS user_name, E.name AS name, E.start_time AS start_time, E.end_time AS end_time
FROM experiments AS E JOIN users AS U ON E.user_id = U.id`
}, (err, res) => {
callback(err, res)
})
}
}
/**
* @callback errCallback
* @param {Error} err - Error
*/
/**
* @callback resultCallback
* @param {Error} err - Error
* @param {Object[]} res - Results
*/
module.exports = Experiments
'use strict'
const pool = require('../pool')
/**
* Ensure schema for issues exist.
*/
pool.query(
`CREATE TABLE IF NOT EXISTS issues (
id BINARY(16),
experiment_id BINARY(16) NOT NULL,
hardware_id BINARY(16) NOT NULL,
step INTEGER NOT NULL,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY (experiment_id) REFERENCES experiments(id)
)`, (err, res) => {
if (err) throw err
if (res.warningCount === 0) console.log('Database: issues schema created')
}
)
/**
* An issue consists of an id, experiment, hardware id, step, start time and end time.
*
* An issue could be: uuid, uuid, uuid, 3, 01/01/2019 14:35:32, 01/01/2019 14:42:52.
*/
class Issues {
/**
* Create a new issue.
* @param {string} experiment - The uuid of the experiment that the issue belongs to.
* @param {string} hardware - The uuid of the hardware where the issue occured.
* @param {number} step - The step that the issue occured on.
* @param {Date} time - The time the issue occurred on.
* @param {errCallback} callback - The callback that handles possible errors.
*/
static create (experiment, hardware, step, time, callback) {
pool.query({
sql: 'INSERT INTO issues(id, experiment_id, hardware_id, step, start_time) VALUES(UUID_TO_BIN(UUID()), UUID_TO_BIN(?), UUID_TO_BIN(?), ?, ?)',
values: [experiment, hardware, step, new Date(time)]
}, (err) => {
callback(err)
})
}
/**
* End an open issue.
* @param {string} hardware - The uuid of the hardware where the issue ended.
* @param {Date} time - The time the issue ended on.
* @param {errCallback} callback - The callback that handles possible errors.
*/
static end (hardware, time, callback) {
pool.query({
sql: 'UPDATE issues SET end_time = ? WHERE BIN_TO_UUID(id) = ? AND end_time IS NULL',
values: [new Date(time), hardware]
}, (err) => {
callback(err)
})
}
/**
* End all open issues for a given experiment.
* @param {string} experiment - The uuid of the experiment that the issues belong to.
* @param {Date} time - The time the experiment ended.
* @param {errCallbac} callback - The callback that handles possible errors.
*/
static endAll (experiment, time, callback) {
pool.query({
sql: 'UPDATE issues SET end_time = ? WHERE BIN_TO_UUID(experiment_id) = ? AND end_time IS NULL',
values: [new Date(time), experiment]
}, (err) => {
callback(err)
})
}
/**
* Check if theres an open issue for a given hardware id.
* @param {string} hardware - The uuid of the hardware.
* @param {resultCallback} callback - The callback that handles the result.
*/
static check (hardware, callback) {
pool.query({
sql: 'SELECT BIN_TO_UUID(id) AS id, BIN_TO_UUID(experiment_id) AS experiment_id, BIN_TO_UUID(hardware_id) AS hardware_id, step, start_time, end_time FROM issues WHERE BIN_TO_UUID(hardware_id) = ? AND end_time IS NULL',
values: hardware
}, (err, res) => {
callback(err)
})
}
/**
* Find all issues for a given experiment.
* @param {string} experiment - The uuid of the experiment.
* @param {resultCallback} callback - The callback that handles the result.
*/
static find (experiment, callback) {
pool.query({
sql: 'SELECT BIN_TO_UUID(id) AS id, BIN_TO_UUID(experiment_id) AS experiment_id, BIN_TO_UUID(hardware_id) AS hardware_id, step, start_time, end_time FROM issues WHERE BIN_TO_UUID(experiment_id) = ?',
values: experiment
}, (err, res) => {
callback(err, res)
})
}
}
/**
* @callback errCallback
* @param {Error} err - Error
*/
/**
* @callback resultCallback
* @param {Error} err - Error
* @param {Object[]} res - Results
*/
module.exports = Issues
'use strict'
const pool = require('../pool')
/**
* Ensure schema for users exist.
*/
pool.query(
`CREATE TABLE IF NOT EXISTS users (
id BINARY(16),
name VARCHAR(100) NOT NULL,
username VARCHAR(100) NOT NULL,
password VARCHAR(100) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY user(username)
)`, (err, res) => {
if (err) throw err
if (res.warningCount === 0) console.log('Database: users schema created')
}
)
/**
* A user consists of an id, name, username and password.
*
* A user could be: uuid, Lars Larsen, larslarsen@jysk.dk, tilbudTilDig.
*/
class Users {
/**
* Create a new user.
* @param {string} name - The name of the user.
* @param {string} username - The username of the user.
* @param {string} password - The password of the user.
* @param {errCallback} callback - The callback that handles the possible errors.
*/
static create (name, username, password, callback) {
pool.query({
sql: 'INSERT INTO users(id, name, username, password) VALUES(UUID_TO_BIN(UUID()), ?, ?, ?)',
values: [name, username, password]
}, (err) => {
callback(err)
})
}