This commit is contained in:
Leo
2024-01-24 15:51:56 +08:00
54 changed files with 812 additions and 999 deletions

View File

@ -1,29 +1,46 @@
const express = require("express");
const { rateLimit } = require("express-rate-limit");
const path = require("path");
const app = express();
const port = 3000;
const ejs = require("ejs");
module.exports = app;
app.use(express.json());
app.set("json spaces", 2);
//express-rate-limit stolen from docs
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 600, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
standardHeaders: 'draft-7', // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
legacyHeaders: false, // Disable the `X-RateLimit-*` headers.
});
// Hold list of functions to run when the server is ready
app.onListen = [function(){console.log('Express is ready')}];
// Apply the rate limiting middleware to all requests.
app.use(limiter);
//disable x-powered-by header for security reasons
app.disable("x-powered-by");
// Set up the templating engine to build HTML for the front end.
app.set("views", path.join(__dirname, "../views"));
app.set("views", path.join(__dirname, "./views"));
app.set("view engine", "ejs");
// Have express server static content( images, CSS, browser JS) from the public
app.use(express.static(path.join(__dirname, "../public")));
//middleware logic ( called by next() )
const auth = require("../middleware/authChecker");
app.use(express.static(path.join(__dirname, "./public")));
//route logic
app.use("/api/v0", require("../routes/api_routes"));
app.use("/api/seed/v0" ,require("./routes/seed_route.js"));
app.use("/api/v0", require("./routes/api_routes"));
//render logic
app.use("/", require("../routes/render"));
app.use("/", require("./routes/render"));
// Catch 404 and forward to error handler. If none of the above routes are
// used, this is what will be called.
@ -69,8 +86,4 @@ app.use(function (err, req, res, next) {
keyErrors,
});
});
app.listen(port, () => {
console.log(`app listening on port ${port}`);
});
module.exports = { app };

102
consumerWebsite/bin/www Normal file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
const app = require('../app');
const mqttApp = require('../mqttApp');
const debug = require('debug')('proxy-api:server');
const http = require('http');
const path = require('path');
require('dotenv').config({ path: path.resolve(__dirname, '../../.env')})
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.NODE_PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
//create server with app
var server = http.createServer(app);
var io = require('socket.io')(server);
app.io = io;
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
console.log('Listening on ' + bind);
// execute list of functions when app is ready
for(let listener of app.onListen){
listener()
}
}

View File

@ -0,0 +1,71 @@
"use strict";
const { Sequelize, DataTypes } = require("sequelize");
const { sequelize } = require("../mySQL");
//sequelize.sync();
const api_log_Model = sequelize.define(
"api-logs",
{
// Model attributes are defined here
id: {
type: DataTypes.INTEGER,
allowNull: true,
primaryKey: true,
autoIncrement: true,
},
ip: {
type: DataTypes.STRING,
allowNull: false,
length: 45,
},
time: {
type: DataTypes.STRING,
allowNull: false,
length: 20,
},
method: {
type: DataTypes.STRING,
allowNull: false,
length: 10,
},
host: {
type: DataTypes.STRING,
allowNull: false,
length: 45,
},
statusCode: {
type: DataTypes.STRING,
allowNull: false,
length: 10,
},
Responsesize: {
type: DataTypes.STRING,
allowNull: false,
length: 10,
},
referrer: {
type: DataTypes.STRING,
allowNull: false,
length: 45,
},
userAgent: {
type: DataTypes.STRING,
allowNull: false,
length: 100,
},
createdAt: {
type: DataTypes.DATE,
allowNull: true,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: true,
},
},
{
timestamps: true,
}
);
module.exports = { api_log_Model };

View File

@ -0,0 +1,75 @@
"use strict";
const { Sequelize, DataTypes } = require("sequelize");
const { sequelize } = require("../mySQL");
const { isAlphaNumericwithSpaces } = require('../../functions/validateData')
//sequelize.sync();
const locationModel = sequelize.define(
"location",
{
id: {
type: DataTypes.INTEGER,
allowNull: true,
primaryKey: true,
autoIncrement: true,
validate: {
isNumeric: true,
},
},
name: {
type: DataTypes.STRING,
allowNull: false,
length: 10,
unique: true,
validate: {
notEmpty: true,
len: [1, 20],
//accept only alphanumeric and spaces
isAlphaNumericwithSpaces(value){
if(!isAlphaNumericwithSpaces(value)){
throw new Error('Invalid characters in name')
}
}
},
},
added_by: {
type: DataTypes.STRING,
allowNull: false,
length: 10,
validate: {
notEmpty: true,
len: [1, 20],
is: ["^[a-z0-9]+$", "i"],
isIn: [['admin', 'system' , 'Admin', 'System']],
},
},
description: {
type: DataTypes.STRING,
allowNull: true,
length: 100,
validate: {
notEmpty: true,
len: [1, 100],
/*
//will not validate this and fail it
"hello@123" (contains a symbol)
"" (empty string)
*/
is: ["^[a-zA-Z0-9 ]+$", "i"]
},
},
createdAt: {
type: DataTypes.DATE,
allowNull: true,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: true,
},
},
{
timestamps: true,
}
);
module.exports = { locationModel };

View File

@ -0,0 +1,77 @@
"use strict";
const { Sequelize, DataTypes } = require("sequelize");
const { sequelize } = require("../mySQL");
const { locationModel } = require("./locationModel");
const { sensorModel } = require("./sensorModel");
const { isJson } = require('../../functions/validateData');
//sequelize.sync();
const sensorDataModel = sequelize.define(
"sensorData",
{
id: {
type: DataTypes.INTEGER,
allowNull: true,
primaryKey: true,
autoIncrement: true,
unique: true,
validate: {
isNumeric: true,
notEmpty: true,
},
},
sensorid: {
type: DataTypes.INTEGER,
allowNull: false,
length: 100,
//FK
references: {
model: sensorModel,
key: "id",
},
validate: {
isNumeric: true,
notEmpty: true,
},
},
locationid: {
type: DataTypes.INTEGER,
allowNull: false,
length: 100,
//FK
references: {
model: locationModel,
key: "id",
},
validate: {
isNumeric: true,
notEmpty: true,
},
},
measurement: {
type: DataTypes.JSON,
allowNull: false,
validate: {
notEmpty: true,
isJson(value)
{
if (isJson(value) !== true)
throw new Error("sensordata must be a JSON");
},
},
},
createdAt: {
type: DataTypes.DATE,
allowNull: true,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: true,
},
},
{
timestamps: true,
}
);
module.exports = { sensorDataModel };

View File

@ -0,0 +1,110 @@
"use strict";
const { Sequelize, DataTypes } = require("sequelize");
const { sequelize } = require("../mySQL");
const { locationModel } = require("./locationModel");
const {
isAlphaNumericwithSpaces,
isAlphaNumericWithSpacesAndDash,
isMacAddress,
} = require('../../functions/validateData');
//sequelize.sync();
const sensorModel = sequelize.define(
"sensors",
{
id: {
type: DataTypes.INTEGER,
allowNull: true,
primaryKey: true,
autoIncrement: true,
unique: true,
validate: {
notEmpty: true,
isNumeric: true,
},
},
name: {
type: DataTypes.STRING,
allowNull: false,
length: 10,
unique: true,
validate: {
notEmpty: true,
len: [1, 30],
//accept only alphanumeric and spaces
isAlphaNumericWithSpacesAndDash(value) {
if (!isAlphaNumericWithSpacesAndDash(value)) {
throw new Error("Invalid characters in name");
}
},
},
},
added_by: {
type: DataTypes.STRING,
allowNull: false,
length: 10,
validate: {
notEmpty: { msg: "Added by cannot be empty" },
len: [1, 20],
is: ["^[a-z0-9]+$", "i"],
isIn: [["admin", "system", "Admin", "System"]],
},
},
mac_address: {
type: DataTypes.STRING,
allowNull: false,
length: 12,
unique: true,
validate: {
notEmpty: true,
len: [12, 18],
isMacAddress(value) {
if (!isMacAddress(value)) {
throw new Error("Invalid Mac Address");
}
},
},
},
description: {
type: DataTypes.STRING,
allowNull: true,
length: 100,
validate: {
notEmpty: true,
len: [1, 100],
isAlphaNumericwithSpaces(value) {
if (!isAlphaNumericwithSpaces(value)) {
throw new Error("Invalid characters in name");
}
},
},
},
locationid: {
type: DataTypes.INTEGER,
allowNull: true,
length: 100,
//one to many relationship
references: {
model: locationModel,
key: "id",
},
validate: {
notEmpty: true,
isNumeric: true,
},
},
createdAt: {
type: DataTypes.DATE,
allowNull: true,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: true,
},
},
{
timestamps: true,
}
);
module.exports = { sensorModel };

View File

@ -0,0 +1,47 @@
const { hash, compareHash } = require("./bcrypt.js");
const { apikeyModel } = require("../database/model/apiKeyModel");
const { generateUUID } = require("./generateUUID.js");
/*
1) take userid
2) generate random api key
3) hash the api key
4) append userid with - and api key
5) you give the user rowid-uuidv4
6) store in database
*/
//can be used for api key or token. Both are the same logic
async function addAPIKey(userId, permission) {
let hashtoken = await generateUUID();
let apikey = await hash(hashtoken);
let token = await apikeyModel.create({
userid: userId,
apikey: apikey,
permission: permission,
});
//user token with - tokenid is table id
return token.id + "-" + hashtoken;
}
async function checkAPikey(SuppliedKey, rowid) {
try {
const retrivedKey = await apikeyModel.findOne({
raw: true,
attributes: ["apikey", "permission"],
where: {
id: rowid,
},
});
//console.log(retrivedKey.apikey);
if (compareHash(SuppliedKey, retrivedKey.apikey)) {
//return true;
return retrivedKey.permission;
}
} catch (error) {
console.error(error);
}
}
module.exports = { addAPIKey , checkAPikey };

View File

@ -2,44 +2,19 @@ const bcrypt = require('bcrypt');
const saltRounds = 10;
//https://github.com/kelektiv/node.bcrypt.js#readme
/*
// Load hash from your password DB.
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
// result == true
});
bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) {
// result == false
});
*/
/*
//hash with salt
bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
// Store hash in your password DB.
});
*/
//hash for pass or token lol doesnt matter
async function hashPassword(password) {
async function hash(password) {
return await bcrypt.hash(password, saltRounds);
}
async function hashAPIKey(apikey) {
return await bcrypt.hash(apikey, saltRounds);
}
//can be used to compare password or token
async function comparePassword(password, hash) {
async function compareHash(password, hash) {
return await bcrypt.compare(password, hash);
}
module.exports = {
hashPassword,
hashAPIKey,
comparePassword
hash,
compareHash
};

View File

@ -0,0 +1,56 @@
const {locationModel} = require("../database/model/locationModel");
async function getLocation() {
const location = await locationModel.findAll();
return location;
}
async function addLocation(name, added_by, description) {
console.log(name, added_by, description);
const location = await locationModel.create({
name: name,
added_by: added_by,
description: description,
});
}
async function updateLocation(id, name, added_by, description) {
const location = await locationModel.update(
{
name: name,
added_by: added_by,
description: description,
},
{
where: {
id: id,
},
}
);
}
async function deleteLocation(id) {
//delete by id
const location = await locationModel.destroy({
where: {
id: id,
},
});
}
async function getLocationById(id) {
const location = await locationModel.findAll({
where: {
id: id,
},
});
return location;
}
module.exports = {
getLocation,
addLocation,
updateLocation,
deleteLocation,
getLocationById,
};

View File

@ -0,0 +1,23 @@
const { api_log_Model } = require("../database/model/apiLogModel.js");
async function insertLogData(log) {
try {
api_log_Model.create({
ip: log.ip,
time: log.time,
method: log.method,
host: log.host,
statusCode: log.statusCode,
Responsesize: log.Responsesize,
referrer: log.referrer,
userAgent: log.userAgent,
});
} catch (error) {
console.error(error);
}
}
module.exports = {
insertLogData,
};

View File

@ -0,0 +1,75 @@
const {sensorModel} = require("../database/model/sensorModel");
async function getSensor() {
const sensor = await sensorModel.findAll();
return sensor;
}
async function addSensor(
sensorname,
added_by,
mac_address,
description,
location
) {
const sensor = await sensorModel.create({
name: sensorname,
added_by: added_by,
mac_address: mac_address,
description: description,
location: location,
});
}
async function updateSensor(
id,
sensorname,
added_by,
mac_address,
description,
location
) {
const sensor = await sensorModel.update(
{
name: sensorname,
added_by: added_by,
mac_address: mac_address,
description: description,
location: location,
},
{
where: {
id: id,
},
}
);
}
async function deleteSensor(id) {
//delete by id
const sensor = await sensorModel.destroy({
where: {
id: id,
},
});
console.error(error);
}
async function getSensorById(id) {
const sensor = await sensorModel.findAll({
where: {
id: id,
},
});
return sensor;
}
module.exports = {
getSensor,
addSensor,
updateSensor,
deleteSensor,
getSensorById,
};

View File

@ -0,0 +1,728 @@
const { Op, Sequelize } = require("sequelize");
const { sequelize } = require("../database/mySQL.js");
const { sensorDataModel } = require("../database/model/sensorDataModel.js");
const io = require('../functions/socket');
//helper function to convert month name to month number
//https://stackoverflow.com/questions/13566552/easiest-way-to-convert-month-name-to-month-number-in-js-jan-01
function getMonthFromString(mon) {
var d = Date.parse(mon + "1, 2012");
if (!isNaN(d)) {
return new Date(d).getMonth() + 1;
}
return -1;
}
async function getSensorData() {
const sensorData = await sensorDataModel.findAll();
return sensorData;
}
async function addSensorData(id_sensor, id_location, sensordata) {
const sensorData = await sensorDataModel.create({
sensorid: id_sensor,
locationid: id_location,
measurement: sensordata,
});
io().emit('sensordata:new', sensorData)
return sensorData;
}
async function updateSensorData(id, id_sensor, id_location, sensordata) {
const sensorData = await sensorDataModel.update(
{
ensorid: id_sensor,
locationid: id_location,
measurement: sensordata,
},
{
where: {
id: id,
},
}
);
}
async function deleteSensorData(id) {
const sensorData = await sensorDataModel.destroy({
where: {
id: id,
},
});
}
async function getSensorDataById(id) {
const sensorData = await sensorDataModel.findAll({
where: {
id: id,
},
});
return sensorData;
}
var ormQuery = {};
var whereClause = {};
var whereDate = {};
const allowedQuery = [
"limit",
"order",
"year",
"month",
"week",
"day",
"hour",
"minute",
"sensorid",
"locationid",
];
const validMonths = [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
];
//handle buildfunc for query
buildQuery = {
limit: async function (queryString) {
if (queryString.limit !== undefined) {
ormQuery.limit = parseInt(queryString.limit);
}
},
order: async function (queryString) {
if (queryString.order !== undefined) {
ormQuery = {
...ormQuery,
order: [["createdAt", queryString.order.toUpperCase()]],
};
}
},
year: async function (queryString) {
if (queryString.year !== undefined) {
//whereclause assign a value
whereClause.year = sequelize.where(
sequelize.fn("YEAR", sequelize.col("createdAt")),
queryString.year
);
}
},
month: async function (queryString) {
if (queryString.month !== undefined) {
if (validMonths.includes(queryString.month)) {
whereClause.month = sequelize.where(
sequelize.fn("MONTH", sequelize.col("createdAt")),
queryString.month
);
} else {
queryString.month = getMonthFromString(queryString.month)
whereClause.month = sequelize.where(
sequelize.fn("MONTH", sequelize.col("createdAt")),
queryString.month
);
}
}
},
week: async function (queryString) {
if (queryString.week !== undefined) {
whereClause.week = sequelize.where(
sequelize.fn("WEEK", sequelize.col("createdAt")),
queryString.week
);
}
},
day: async function (queryString) {
if (queryString.day !== undefined) {
whereClause.day = sequelize.where(
sequelize.fn("DAY", sequelize.col("createdAt")),
queryString.day
);
}
},
hour: async function (queryString) {
if (queryString.hour !== undefined) {
whereClause.hour = sequelize.where(
sequelize.fn("HOUR", sequelize.col("createdAt")),
queryString.hour
);
}
},
minute: async function (queryString) {
if (queryString.minute !== undefined) {
whereClause.minute = sequelize.where(
sequelize.fn("MINUTE", sequelize.col("createdAt")),
queryString.minute
);
}
},
sensorid: async function (queryString) {
if (queryString.sensorid !== undefined) {
whereClause.sensorid = sequelize.where(
sequelize.col("sensorid"),
queryString.sensorid
);
}
},
locationid: async function (queryString) {
if (queryString.locationid !== undefined) {
whereClause.locationid = sequelize.where(
sequelize.col("locationid"),
queryString.locationid
);
}
},
psi: async function (queryString) {
if (queryString.psi !== undefined && queryString.psi === "highest") {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[sequelize.literal("JSON_EXTRACT(measurement, '$.psi')"), "psi"],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("psi"), "DESC"]],
limit: 10,
};
}
if (queryString.psi !== undefined && queryString.psi === "lowest") {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[sequelize.literal("JSON_EXTRACT(measurement, '$.psi')"), "psi"],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("psi"), "ASC"]],
limit: 10,
};
}
},
co: async function (queryString) {
if (queryString.co !== undefined && queryString.co === "highest") {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[sequelize.literal("JSON_EXTRACT(measurement, '$.co')"), "co"],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("co"), "DESC"]],
limit: 10,
};
}
if (queryString.co !== undefined && queryString.co === "lowest") {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[sequelize.literal("JSON_EXTRACT(measurement, '$.co')"), "co"],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("co"), "ASC"]],
limit: 10,
};
}
},
o3: async function (queryString) {
if (queryString.o3 !== undefined && queryString.o3 === "highest") {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[sequelize.literal("JSON_EXTRACT(measurement, '$.o3')"), "o3"],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("o3"), "DESC"]],
limit: 10,
};
}
if (queryString.o3 !== undefined && queryString.o3 === "lowest") {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[sequelize.literal("JSON_EXTRACT(measurement, '$.o3')"), "o3"],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("o3"), "ASC"]],
limit: 10,
};
}
},
no2: async function (queryString) {
if (queryString.no2 !== undefined && queryString.no2 === "highest") {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[sequelize.literal("JSON_EXTRACT(measurement, '$.no2')"), "no2"],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("no2"), "DESC"]],
limit: 10,
};
}
if (queryString.no2 !== undefined && queryString.no2 === "lowest") {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[sequelize.literal("JSON_EXTRACT(measurement, '$.no2')"), "no2"],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("no2"), "ASC"]],
limit: 10,
};
}
},
so2: async function (queryString) {
if (queryString.so2 !== undefined && queryString.so2 === "highest") {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[sequelize.literal("JSON_EXTRACT(measurement, '$.so2')"), "so2"],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("so2"), "DESC"]],
limit: 10,
};
}
if (queryString.so2 !== undefined && queryString.so2 === "lowest") {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[sequelize.literal("JSON_EXTRACT(measurement, '$.so2')"), "so2"],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("so2"), "ASC"]],
limit: 10,
};
}
},
humidity: async function (queryString) {
if (
queryString.humidity !== undefined &&
queryString.humidity === "highest"
) {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[
sequelize.literal("JSON_EXTRACT(measurement, '$.humidity')"),
"humidity",
],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("humidity"), "DESC"]],
limit: 10,
};
}
if (
queryString.humidity !== undefined &&
queryString.humidity === "lowest"
) {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[
sequelize.literal("JSON_EXTRACT(measurement, '$.humidity')"),
"humidity",
],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("humidity"), "ASC"]],
limit: 10,
};
}
},
windspeed: async function (queryString) {
if (
queryString.windspeed !== undefined &&
queryString.windspeed === "highest"
) {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[
sequelize.literal("JSON_EXTRACT(measurement, '$.windspeed')"),
"windspeed",
],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("windspeed"), "DESC"]],
limit: 10,
};
}
if (
queryString.windspeed !== undefined &&
queryString.windspeed === "lowest"
) {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[
sequelize.literal("JSON_EXTRACT(measurement, '$.windspeed')"),
"windspeed",
],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("windspeed"), "ASC"]],
limit: 10,
};
}
},
temperature: async function (queryString) {
if (
queryString.temperature !== undefined &&
queryString.temperature === "highest"
) {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[
sequelize.literal("JSON_EXTRACT(measurement, '$.temperature')"),
"temperature",
],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("temperature"), "DESC"]],
limit: 10,
};
}
if (
queryString.temperature !== undefined &&
queryString.temperature === "lowest"
) {
ormQuery = {
attributes: [
"id",
"sensorid",
"locationid",
[
sequelize.literal("JSON_EXTRACT(measurement, '$.temperature')"),
"temperature",
],
"createdAt",
],
group: ["id", "sensorid", "locationid"],
order: [[sequelize.literal("temperature"), "ASC"]],
limit: 10,
};
}
},
//average
avg: async function (queryString) {
if (queryString.avg !== undefined) {
ormQuery = {
attributes: [
//round to 2 decimal places
[
sequelize.fn(
"ROUND",
sequelize.fn(
"AVG",
Sequelize.literal(
`JSON_EXTRACT(measurement, '$.${queryString.avg}')`
)
),
2
),
"avg of " + queryString.avg,
],
],
};
}
},
sum: async function (queryString) {
if (queryString.sum !== undefined) {
ormQuery = {
attributes: [
[
sequelize.fn(
"SUM",
Sequelize.literal(
`JSON_EXTRACT(measurement, '$.${queryString.sum}')`
)
),
"sum of " + queryString.sum,
],
],
};
}
},
//total number of records
total: async function (queryString) {
if (queryString.total !== undefined) {
ormQuery = {
attributes: [
[sequelize.fn("COUNT", sequelize.col("id")), "total id / records"],
],
};
}
},
};
buildFunc = {
limit: async function (queryString) {
if (queryString.limit !== undefined) {
ormQuery.limit = parseInt(queryString.limit);
}
},
startdate: async function (queryString) {
if (queryString.startdate !== undefined) {
whereDate.startdate = new Date(queryString.startdate);
}
},
enddate: async function (queryString) {
if (queryString.enddate !== undefined) {
whereDate.enddate = new Date(queryString.enddate);
}
},
//total by startdate and enddate
total: async function (queryString) {
if (queryString.total !== undefined) {
ormQuery = {
attributes: [
[sequelize.fn("COUNT", sequelize.col("id")), "total id / records"],
],
};
}
},
//average
avg: async function (queryString) {
if (queryString.avg !== undefined) {
ormQuery = {
attributes: [
//round to 2 decimal places
[
sequelize.fn(
"ROUND",
sequelize.fn(
"AVG",
Sequelize.literal(
`JSON_EXTRACT(measurement, '$.${queryString.avg}')`
)
),
2
),
"avg of " + queryString.avg,
],
],
};
}
},
sum: async function (queryString) {
if (queryString.sum !== undefined) {
ormQuery = {
attributes: [
[
sequelize.fn(
"SUM",
Sequelize.literal(
`JSON_EXTRACT(measurement, '$.${queryString.sum}')`
)
),
"sum of " + queryString.sum,
],
],
};
}
},
//total number of records
total: async function (queryString) {
if (queryString.total !== undefined) {
ormQuery = {
attributes: [
[sequelize.fn("COUNT", sequelize.col("id")), "total id / records"],
],
};
}
},
};
async function getData(queryString) {
if (queryString.pagesize || queryString.page) {
//https://blog.bitsrc.io/pagination-with-sequelize-explained-83054df6e041
//pass pageSize taken from page=4 or default to 50
queryString.pagesize = queryString.pagesize || 50;
let offset = (queryString.page || 0) * queryString.pagesize;
queryString.limit = queryString.pagesize;
//reset keys in whereClause and ormQuery. else it will keep appending to the previous query
ormQuery = {};
whereClause = {};
whereDate = {};
for (let query in queryString) {
if (buildQuery[query]) {
await buildQuery[query](queryString);
}
}
if (!whereClause) {
return await sensorDataModel.findAll(ormQuery);
} else if (whereClause) {
console.log(whereClause);
console.log(ormQuery);
console.log(whereDate);
return await sensorDataModel.findAll({
limit: queryString.limit || 1000000,
//https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#limits-and-pagination
offset: parseInt(offset),
//The operators Op.and, Op.or and Op.not can be used to create arbitrarily complex nested logical comparisons.
//https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#examples-with-opand-and-opor
where: {
[Op.and]: [whereClause],
},
//only use where clause to lookup based on condition that i put into whereClause
...ormQuery,
});
}
} else {
//reset keys in whereClause and ormQuery. else it will keep appending to the previous query
ormQuery = {};
whereClause = {};
whereDate = {};
for (let query in queryString) {
if (buildQuery[query]) {
await buildQuery[query](queryString);
}
}
if (!whereClause) {
return await sensorDataModel.findAll(ormQuery);
} else if (whereClause) {
console.log(whereClause);
console.log(ormQuery);
console.log(whereDate);
return await sensorDataModel.findAll({
limit: queryString.limit || 1000000,
//The operators Op.and, Op.or and Op.not can be used to create arbitrarily complex nested logical comparisons.
//https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#examples-with-opand-and-opor
where: {
[Op.and]: [whereClause],
},
//only use where clause to lookup based on condition that i put into whereClause
...ormQuery,
});
}
}
}
async function getDatabyRange(queryString) {
if (queryString.pagesize || queryString.page) {
//https://blog.bitsrc.io/pagination-with-sequelize-explained-83054df6e041
//pass pageSize taken from page=4 or default to 50
queryString.pagesize = queryString.pagesize || 50;
let offset = (queryString.page || 0) * queryString.pagesize;
queryString.limit = queryString.pagesize;
whereDate = {};
for (let query in queryString) {
if (buildFunc[query]) {
await buildFunc[query](queryString);
}
}
if (whereClause) {
console.log(ormQuery);
console.log(whereDate);
return await sensorDataModel.findAll({
limit: queryString.limit || 1000000,
offset: offset,
//The operators Op.and, Op.or and Op.not can be used to create arbitrarily complex nested logical comparisons.
//https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#examples-with-opand-and-opor
where: {
createdAt: {
[Op.between]: [whereDate.startdate, whereDate.enddate],
},
},
//only use where clause to lookup based on condition that i put into whereClause
...ormQuery,
});
} else {
return "Invalid query";
}
} else {
whereDate = {};
for (let query in queryString) {
if (buildFunc[query]) {
await buildFunc[query](queryString);
}
}
if (whereClause) {
console.log(ormQuery);
console.log(whereDate);
return await sensorDataModel.findAll({
limit: queryString.limit || 1000000,
//The operators Op.and, Op.or and Op.not can be used to create arbitrarily complex nested logical comparisons.
//https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#examples-with-opand-and-opor
where: {
createdAt: {
[Op.between]: [whereDate.startdate, whereDate.enddate],
},
},
//only use where clause to lookup based on condition that i put into whereClause
...ormQuery,
});
} else {
return "Invalid query";
}
}
}
module.exports = {
getSensorData,
addSensorData,
updateSensorData,
deleteSensorData,
getSensorDataById,
getData,
getDatabyRange,
};

View File

@ -0,0 +1,17 @@
const app = require("../app");
const io = ()=> app.io;
// We have to wait for the express HTTP server to be finished starting before we
// can use any of the socket.io stuff.
app.onListen.push(function(){
app.io.on('connection', (socket) => {
console.log('User connected via WebsSocket')
socket.on('disconnect', (socket) => {
console.log('User disconnect via WebsSocket')
});
});
});
module.exports = io;

View File

@ -1,13 +1,9 @@
const { sequelize } = require("../database/mySql.js");
const { apikeyModel } = require("../database/model/apikeyModel.js");
const { userModel } = require("../database/model/userModel.js");
const { Op, Sequelize } = require("sequelize");
const { generateUUID } = require("../functions/generateUUID.js");
const {
hashPassword,
comparePassword,
hashAPIKey,
} = require("../functions/bcrypt.js");
const { Op } = require('sequelize')
const { hash, compareHash } = require("./bcrypt.js");
const { addAPIKey } = require("./api");
const { userModel } = require("../database/model/userModel");
//getuser
//api/v0/user/me
@ -24,6 +20,28 @@ async function getUserID(userid) {
return userRes;
}
//register
//api/v0/auth/register
async function addUser(user) {
//hash password
let hashed = await hash(user.password);
const addRes = await userModel.create({
firstname: user.firstname,
lastname: user.lastname,
username: user.username,
password: hashed,
email: user.email,
address: user.address,
phone: user.phone,
});
if (addRes) {
return true;
} else {
return false;
}
}
//api/v0/auth/register
/* Registering new user
1) req.body is taken from html form or wtv
@ -32,13 +50,13 @@ async function getUserID(userid) {
*/
async function addUser(user) {
//hash password
let hash = await hashPassword(user.password);
let hashed = await hash(user.password);
const addRes = await userModel.create({
firstname: user.firstname,
lastname: user.lastname,
username: user.username,
password: hash,
password: hashed,
email: user.email,
address: user.address,
phone: user.phone,
@ -70,7 +88,7 @@ async function loginUser(user) {
if (!userRes) return false;
// Compare passwords
let match = await comparePassword(user.password, userRes.password);
let match = await compareHash(user.password, userRes.password);
if (!match) return false;
//console.log('loginUser', userRes.id, userRes.username);
@ -89,20 +107,6 @@ async function loginUser(user) {
6) store in database
*/
//can be used for api key or token. Both are the same logic
async function addAPIKey(userId, permission) {
let hashtoken = await generateUUID();
let apikey = await hashAPIKey(hashtoken);
let token = await apikeyModel.create({
userid: userId,
apikey: apikey,
permission: permission,
});
//user token with - tokenid is table id
return token.id + "-" + hashtoken;
}
//api/v0/user/update
async function updateProfile(user, body) {
@ -125,7 +129,7 @@ async function updateProfile(user, body) {
if (!updateUser) return false;
return true;
} else {
let hash = await hashPassword(body.password);
let hashed = await hash(body.password);
let updateUser = await userModel.update(
{
firstname: body.first_name,
@ -134,7 +138,7 @@ async function updateProfile(user, body) {
email: body.email,
address: body.address,
phone: body.phone,
password: hash,
password: hashed,
},
{
where: {
@ -152,5 +156,4 @@ module.exports = {
addUser,
loginUser,
updateProfile,
addAPIKey,
};
};

View File

@ -1,5 +1,27 @@
var validator = require("validator");
const dateRegex = /^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
function isValidDateString(value) {
return dateRegex.test(value);
}
function isMacAddress(value) {
// Joi.string().regex(/^([0-9a-f]{2}-){5}([0-9a-f]{2})$/i).lowercase()
//return validator.isMACAddress(value, { no_separators: true, eui: 48 });
const macAddress = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
const valid = macAddress.test(value);
return valid;
}
function isNumber(value) {
if (typeof value === "number") {
return true;
}
}
function isAlphaNumericwithSpaces(value) {
return validator.isAlphanumeric(value, ["en-US"], { ignore: " " });
}
@ -16,7 +38,6 @@ function isAlphaNumericWithSpacesAndDash(value) {
function isJson(value) {
//check if its object
if(typeof value === "object"){
console.log("its an object")
return true
}
@ -44,6 +65,8 @@ module.exports = {
isAlphaNumericwithSpaces,
isAlphaNumericWithSpacesAndDash,
isJson,
isAddress
};
isAddress,
isValidDateString,
isMacAddress,
isNumber,
};

View File

@ -0,0 +1,65 @@
const { checkAPikey } = require("../functions/api.js");
async function apikeyCheck(req, res, next) {
//const authHeader = req.headers.authorization
try {
let apikey = req.headers.authorization;
if (!apikey) {
res.status(401).json({
message: "No API key was supplied. Invalid request",
});
//throw new Error("No API key was supplied. Invalid request");
} else {
//split the string by the -
let splitAPIkey = apikey.split("-");
let rowid = splitAPIkey[0];
//rejoin withouth the rowid
let SuppliedKey = splitAPIkey.slice(1).join("-");
if (checkAPikey(SuppliedKey, rowid)) {
//get permission
let permission = await checkAPikey(SuppliedKey, rowid);
console.log(permission);
if (req.method === "GET" && permission === "canRead") {
return next();
}
//['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)
if (
["GET", "POST", "PUT", "DELETE"].includes(req.method) &&
permission === "canWrite"
) {
console.log("write");
return next();
}
//throw status 403
res.status(403).json({
message:
"Your API key does not have the correct permissions to access this resource",
});
}
}
} catch (error) {
next(error);
}
}
module.exports = { apikeyCheck };
/*
//web server microservice
1) take user supplied rowid-apikey
2) split the string by -
3) get the rowid or table id
4) get the apikey
5) compare the apikey with the one in database
6) if match, return true
*/
/*
I plan to seed some data in user and api
Than use the system info and my API middleware will somehow check the supplied API key and check
If it's correct API key and has canWrite perms
I allow it to access put and post
*/

View File

@ -0,0 +1,36 @@
const { insertLogData } = require("../functions/logger.js");
const APIlogger = (req, res, next) => {
try {
const log = {
ip: req.ip,
time: new Date().toUTCString(),
method: req.method,
//https://stackoverflow.com/questions/10183291/how-to-get-the-full-url-in-express
host: `${req.protocol}://${req.get("host")}${req.originalUrl}`,
statusCode: res.statusCode,
Responsesize: res.get('Content-Length') ? res.get('Content-Length') : 0,
referrer: res.get('content-type') ? res.get('content-type') : "none",
userAgent: req.headers["user-agent"],
};
//upload to db logic here for api logs
insertLogData(log);
next();
}
catch (error) {
console.error(error);
}
};
module.exports = { APIlogger };
/*
method: req.method,
statusCode: res.statusCode,
protocol: req.protocol,
//formatted in nice utc format
time: new Date().toUTCString(),
ip: req.ip,
userAgent: req.headers["user-agent"],
host: `${req.protocol}://${req.get("host")}${req.originalUrl}`,
*/

View File

@ -1,6 +1,6 @@
const { apikeyModel } = require("../database/model/apiKeyModel");
const { userModel } = require("../database/model/userModel");
const { comparePassword } = require("../functions/bcrypt");
const { compareHash } = require("../functions/bcrypt");
async function auth(req, res, next){
try{
@ -15,7 +15,7 @@ async function auth(req, res, next){
if (!token) return false;
//compare
let isMatch = await comparePassword(suppliedToken, token.apikey);
let isMatch = await compareHash(suppliedToken, token.apikey);
if (!isMatch) return false;
//else do logic
@ -28,4 +28,4 @@ async function auth(req, res, next){
}
}
module.exports = { auth };
module.exports = { auth };

View File

@ -0,0 +1,20 @@
const mqtt = require('mqtt');
const fs = require('fs');
const path = require('path')
require('dotenv').config({ path: path.resolve(__dirname, '../../.env') })
const { validateData } = require("../functions/validateData.js");
const brokerUrl = 'mqtt://mqtt.teeseng.uk';
const options = {
port: 8883,
username: process.env.MQTT_USER,
password: process.env.MQTT_PASS,
protocol: 'mqtts', // Use MQTT over TLS
key: fs.readFileSync(path.resolve(__dirname, '../cert/privkey.pem')),
cert: fs.readFileSync(path.resolve(__dirname, '../cert/cert.pem')),
};
const client = mqtt.connect(brokerUrl, options);
module.exports = client;

View File

@ -0,0 +1,66 @@
const client = require("./modules/mqtt.js");
const { isJson, isNumber } = require("./functions/validateData.js");
const { addSensorData } = require("./functions/sensorData");
// Event handlers
client.on("connect", () => {
console.log("Connected to MQTT broker");
client.subscribe("iot-data");
});
client.on("message", (topic, message) => {
try {
let datas = JSON.parse(message);
if (isJson(datas)) {
for (let key in datas) {
let data = parseInt(
datas[key].locationid +
" " +
datas[key].sensorid +
" " +
datas[key].measurement.psi +
" " +
datas[key].measurement.humidity +
" " +
datas[key].measurement.o3 +
" " +
datas[key].measurement.no2 +
" " +
datas[key].measurement.so2 +
" " +
datas[key].measurement.co +
" " +
datas[key].measurement.temperature +
" " +
datas[key].measurement.windspeed
);
if (isNumber(data)) {
{
//pass datas to database
addSensorData(datas[key].sensorid, datas[key].locationid, datas[key]);
}
} else {
console.log("Invalid data");
client.end();
}
}
} else {
console.log("Invalid data");
client.end();
}
} catch (err) {
console.error(err);
}
});
client.on("error", (err) => {
console.error("Error:", err);
client.end();
});
client.on("end", () => {
console.log("Disconnected from MQTT broker");
client.reconnect();
});

View File

@ -144,6 +144,34 @@ app.api = (function (app) {
return { post: post, get: get, put: put, delete: remove };
})(app);
//socket.io
//socket.io
app.socket = (function (app) {
//need to replace with domain name of server when published
var socket = io();
socket.on("disconnect", () => {
console.log("disconnected");
});
socket.on('connect', ()=>{
console.info('WS connected');
})
socket.io.on("reconnect", () => {
console.log("reconnected");
});
socket.io.on("connect_error", (err) => {
console.log(err);
});
return socket;
})(app);
//sensor data
app.sensordata = (function (app) {
})(app);
app.auth = (function (app) {
var user = {};
function setToken(token) {
@ -154,18 +182,20 @@ app.auth = (function (app) {
return localStorage.getItem("APIToken");
}
function isLoggedIn(callback) {
if (getToken()) {
return app.api.get("user/me", function (error, data) {
if (!error) app.auth.user = data;
//for navbar to show username
$.scope.getUsername.update(data);
if (!location.pathname === "/login")
{
$.scope.getUsername.update(data);
}
//for edit profile to show user details
//if not in edit profile page, it will not show
if (location.pathname === "/profile")
{
if (location.pathname === "/profile") {
$.scope.getUserDetails.update(data);
}
return callback(error, data);
@ -232,12 +262,9 @@ app.auth = (function (app) {
logInRedirect,
homeRedirect,
profileRedirect,
//showUser,
//redirectIfLoggedIn,
};
})(app);
app.user = (function (app) {
//delete profile
function deleteProfile() {
@ -250,12 +277,10 @@ app.user = (function (app) {
});
}
});
}
return {
deleteProfile,
};
})(app);
//ajax form submit and pass to api

View File

@ -1,3 +1,31 @@
'use strict';
const router = require('express').Router();
const { auth } = require("../middleware/authChecker")
const { APIlogger } = require('../middleware/apiLogger.js');
const { apikeyCheck } = require('../middleware/apiKey.js');
router.use('/auth', require('./auth'));
router.use('/apikey', require('./apikey'));
router.use('/user', [auth, APIlogger], require('./user'));
//TO REFACTOR INTO ONE MIDDLWARE
//location route
router.use('/location', [apikeyCheck , APIlogger], require('./location.js'));
//location route
router.use('/sensor', [apikeyCheck , APIlogger], require('./sensor.js'));
//location route
router.use('/sensor-data', [apikeyCheck, APIlogger], require('./sensorData.js'));
module.exports = router;
/*
'use strict';
const router = require('express').Router();
const { auth } = require("../middleware/authChecker")
@ -12,3 +40,4 @@ router.use('/user', auth ,require('./user'));
module.exports = router;
*/

View File

@ -1,19 +1,9 @@
const { getAPIKey , addAPIKey } = require("../functions/apiDatabase.js");
const { addAPIKey } = require("../functions/api");
const express = require("express");
const router = express.Router();
router.get("/", async (req, res, next) => {
try {
const location = await getAPIKey();
res.status(200).json(location);
} catch (error) {
console.error(error);
next(error);
}
});
/*
1) ensure user is logged in (frontend session validation blah or wtv)
2) when user click on generate api key button, it will generate a random api key. how to get userid can be done by session or wtv
@ -34,9 +24,6 @@ router.post("/new", async (req, res, next) => {
}
});
//update
//delete
//getbyid
module.exports = router;

View File

@ -1,4 +1,4 @@
const { addUser, loginUser } = require("../functions/apiDatabase.js");
const { addUser, loginUser } = require("../functions/user");
const express = require("express");
const router = express.Router();

View File

@ -0,0 +1,77 @@
const {
addLocation,
getLocation,
getLocationById,
updateLocation,
deleteLocation,
} = require("../functions/location");
const express = require("express");
const router = express.Router();
//get location
router.get("/", async (req, res, next) => {
try {
const location = await getLocation();
//res send json and status code
res.status(200).json(location);
} catch (error) {
console.error(error);
next(error);
}
});
//add location
router.post("/new", async (req, res, next) => {
try {
console.log(req.body);
const { name, added_by, description } = req.body;
await addLocation(name, added_by, description);
res.sendStatus(200)
} catch (error) {
console.error(error);
next(error);
}
});
//update location
router.put("/update", async (req, res, next) => {
try {
const { id, name, added_by, description } = req.body;
await updateLocation(id, name, added_by, description);
res.status(200).json({ message: "Location " + id + " updated" });
} catch (error) {
console.error(error);
next(error);
}
});
//delete location
router.delete("/delete", async (req, res, next) => {
try {
const { id } = req.body;
await deleteLocation(id);
res.status(200).json({ message: "Location " + id + " deleted" });
} catch (error) {
console.error(error);
next(error);
}
});
//get location by id
router.get("/:id", async (req, res, next) => {
try {
//get params
const { id } = req.params;
const location = await getLocationById(id);
res.status(200).json(location);
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;

View File

@ -92,5 +92,9 @@ router.get("/api", function (req, res, next) {
res.render("api");
});
// sensor data
router.get("/sensor-data", function (req, res, next) {
res.render("sensor-data");
});
module.exports = router;
module.exports = router;

View File

@ -0,0 +1,41 @@
const { sequelize } = require("../database/mySQL.js");
const { locationModel } = require("../database/model/locationModel.js");
const { sensorModel } = require("../database/model/sensorModel.js");
const express = require("express");
const router = express.Router();
let mockLocation = []
//add seed
router.post("/new", async (req, res, next) => {
try {
console.log(mockLocation)
for(let locationName of req.body.mockLocation){
//create location and create sensor
let location = await locationModel.create({
name: locationName,
added_by: "system",
description: "system generated location",
});
await sensorModel.create({
name: `AQI-${Math.floor(Math.random()*898)+101}`,
added_by: "system",
mac_address: `${Math.floor(Math.random()*256).toString(16).padStart(2, '0')}-${Math.floor(Math.random()*256).toString(16).padStart(2, '0')}-${Math.floor(Math.random()*256).toString(16).padStart(2, '0')}-${Math.floor(Math.random()*256).toString(16).padStart(2, '0')}-${Math.floor(Math.random()*256).toString(16).padStart(2, '0')}-${Math.floor(Math.random()*256).toString(16).padStart(2, '0')}`,
description: "system generated sensor",
locationid: location.id
});
}
res.sendStatus(200)
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;

View File

@ -0,0 +1,14 @@
'use strict';
const router = require('express').Router();
//location route
router.use('/seedSensorData', require('./seedsensorData.js'));
router.use('/seed', require('./seedLocationAndSensor.js'));
module.exports = router;

View File

@ -0,0 +1,122 @@
const { sequelize } = require("../database/mySQL.js");
const { locationModel } = require("../database/model/locationModel.js");
const { sensorModel } = require("../database/model/sensorModel.js");
const { sensorDataModel } = require("../database/model/sensorDataModel.js");
const express = require("express");
const router = express.Router();
var moment = require("moment");
async function seedSensorData(seedOptions) {
seedOptions.endDate = new Date(seedOptions.endDate) || Date.now();
seedOptions.interval = seedOptions.interval || 15; //15 minutes
seedOptions.sensorid = seedOptions.sensorid || (await sensorModel.findAll()).map((i) => i.id);
seedOptions.seedData = seedOptions.seedData || {};
let rows = [];
for (let sensorId of seedOptions.sensorid) {
let sensor = await sensorModel.findByPk(sensorId);
let locationId = sensor.locationid;
let currentRow = firstDataRow(seedOptions.startDate, sensorId, locationId);
rows.push(currentRow);
while (currentRow.createdAt <= seedOptions.endDate) {
currentRow = nextDataRow(currentRow, seedOptions.interval);
rows.push(currentRow);
}
}
await sensorDataModel.bulkCreate(rows)
}
function convertDateToUTC(startDate) {
let date = new Date(startDate);
date = moment(date).utc().toDate();
//return as object
return date;
}
//populate first row of sensordata model with random data from seedData
function firstDataRow(startDate, sensorId, locationId) {
return {
sensorid: sensorId,
locationid: locationId,
measurement: {
psi: Math.floor(Math.random() * 30) + 5,
humidity: Math.floor(Math.random() * (90 - 80 + 1) + 80),
o3: Math.floor(Math.random() * (100 - 20 + 1) + 30),
no2: Math.floor(Math.random() * 30) + 5,
so2: Math.floor(Math.random() * 30) + 5,
co: Math.floor(Math.random() * 25 - 0.5),
temperature: Math.floor(Math.random() * (30 - 23 + 1) + 25),
windspeed: Math.floor(Math.random() * (10 - 1 + 1) + 1),
},
createdAt: convertDateToUTC(startDate),
};
}
function nextDataRow(currentRow, interval) {
return {
sensorid: currentRow.sensorid,
locationid: currentRow.locationid,
measurement: {
psi: numberWithinPercent(currentRow.measurement.psi),
humidity: Math.floor(Math.random() * (90 - 80 + 1) + 80),
o3: numberWithinPercent(currentRow.measurement.o3),
no2: numberWithinPercent(currentRow.measurement.no2),
so2: numberWithinPercent(currentRow.measurement.so2),
co: numberWithinPercent(currentRow.measurement.co),
temperature: Math.floor(Math.random() * (30 - 23 + 1) + 25),
windspeed: Math.floor(Math.random() * (10 - 1 + 1) + 1),
},
//add 15 minutes to current row time to get next row time in UTC
createdAt: moment(currentRow.createdAt).add(interval, "m").toDate(),
};
}
function numberWithinPercent(inputNumber) {
// Define a reasonable range for the random offset
const maxOffset = 5;
const minOffset = -5;
// Add a random offset within the defined range
const randomOffset = Math.random() * (maxOffset - minOffset) + minOffset;
// Calculate the new number with the offset
const newNumber = inputNumber + randomOffset;
// Ensure the new number is within a reasonable range
return Math.max(5, Math.min(100, Math.floor(newNumber)));
}
//add seed
router.post("/new", async (req, res, next) => {
try {
const seedOptions = req.body;
console.log(seedOptions);
seedSensorData(seedOptions);
res.status(200)
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;
/*
POST /api/v0/seed/sensordata
{
"startDate" : "10-10-2010", // Date to start faking data REQUIRED
"endDate": "10-10-2011", // Date to stop fake data, optional defaults today
"interval": 150000, // Time in seconds between sensor polling
"sensors": [0,1,2], // ID of sensors to fake, optional defaults to all
"seedData": {"object of sensor data"} // The first sensor data row to start with, optional, will use random function as default
}
1) firstDataRow(startDate)
2) nextDataRow(lastRow, interval)
3) seedSensorData({post object from abovr})
*/

View File

@ -0,0 +1,66 @@
const {
getSensor,
addSensor,
updateSensor,
deleteSensor,
getSensorById
} = require("../functions/sensor.js");
const express = require("express");
const router = express.Router();
router.get("/", async (req, res, next) => {
try {
const sensor = await getSensor();
res.status(200).json(sensor);
} catch (error) {
console.error(error);
next(error);
}
});
router.post("/new", async (req, res, next) => {
try {
const { sensorname, added_by, mac_address , description, location } = req.body;
await addSensor(sensorname, added_by, mac_address ,description, location);
res.sendStatus(200)
} catch (error) {
console.error(error);
next(error);
}
});
router.put("/update", async (req, res, next) => {
try {
const { id, sensorname, added_by, mac_address ,description, location } = req.body;
await updateSensor(id, sensorname, added_by, mac_address , description, location);
res.status(200).json({ message: "Sensor " + id + " updated" });
} catch (error) {
console.error(error);
next(error);
}
});
router.delete("/delete", async (req, res, next) => {
try {
const { id } = req.body;
await deleteSensor(id);
res.status(200).json({ message: "Sensor " + id + " deleted" });
} catch (error) {
console.error(error);
next(error);
}
});
router.get("/:id", async (req, res, next) => {
try {
const sensor = await getSensorById(req.params.id);
res.status(200).json(sensor);
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;

View File

@ -0,0 +1,92 @@
const {
getSensorData,
addSensorData,
updateSensorData,
deleteSensorData,
getSensorDataById,
getData,
getDatabyRange,
} = require("../functions/sensorData");
const express = require("express");
const router = express.Router();
router.get("/", async (req, res, next) => {
try {
const sensor = await getSensorData();
res.status(200).json(sensor);
} catch (error) {
console.error(error);
next(error);
}
});
router.post("/new", async (req, res, next) => {
try {
const { id_sensor, id_location, sensordata } = req.body;
let data = await addSensorData(id_sensor, id_location, sensordata);
res.json({ message: "SensorData " + data.id + " added", ...data });
} catch (error) {
console.error(error);
next(error);
}
});
router.put("/update", async (req, res, next) => {
try {
const { id, id_sensor, id_location, sensordata } = req.body;
await updateSensorData(id, id_sensor, id_location, sensordata);
res.status(200).json({ message: "SensorData " + id + " updated" });
} catch (error) {
console.error(error);
next(error);
}
});
router.delete("/delete", async (req, res, next) => {
try {
const { id } = req.body;
await deleteSensorData(id);
res.status(200).json({ message: "SensorData " + id + " deleted" });
} catch (error) {
console.error(error);
next(error);
}
});
router.get("/data", async (req, res, next) => {
try {
console.log(req.query);
const data = await getData(req.query);
res.status(200).json(data);
} catch (error) {
console.error(error);
next(error);
}
});
//date range
router.get("/range", async (req, res, next) => {
try {
console.log(req.query);
const data = await getDatabyRange(req.query);
res.status(200).json(data);
} catch (error) {
console.error(error);
next(error);
}
});
router.get("/:id", async (req, res, next) => {
try {
const sensor = await getSensorDataById(req.params.id);
res.status(200).json(sensor);
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;

View File

@ -1,4 +1,4 @@
const { getUserID, updateProfile } = require("../functions/apiDatabase.js");
const { getUserID, updateProfile } = require("../functions/user");
const express = require("express");
const router = express.Router();

View File

@ -1,5 +1,12 @@
<%- include('top') %>
<script>
//call socket.io
app.socket.on("sensorData:new", function (data) {
console.log("new data!!")
console.log(data);
});
</script>
<header class="slider-main">
@ -47,43 +54,55 @@
<!-- Page Content -->
<div class="container">
<div class="services-bar">
<h1 class="my-4">Services </h1>
<!-- Services Section -->
<div class="row">
<div class="col-lg-4 mb-4">
<div class="card">
<h4 class="card-header">Humidity</h4>
<div class="card-body text-center">
<p class="card-text display-4"> 70% - 75% </p>
<h1 class="my-4">Services</h1>
<!-- Services Section -->
<div class="row">
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Air Quality Index</h4>
<div class="card-body text-center">
<p class="card-text display-4">15 - 18 PSI</p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Humidity</h4>
<div class="card-body text-center">
<p class="card-text display-4">70% - 75%</p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
</div>
</div>
</div>
<div class="col-lg-4 mb-4">
<div class="card">
<h4 class="card-header">Air Quality Index</h4>
<div class="card-body text-center">
<p class="card-text display-4"> 15 - 18 PSI </p>
</div>
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Temperature</h4>
<div class="card-body text-center">
<p class="card-text display-4">30&deg; - 37&deg;</p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
<div class="col-lg-3 mb-4">
<div class="card">
<h4 class="card-header">Another Category</h4>
<div class="card-body text-center">
<p class="card-text display-4">values</p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
</div>
</div>
</div>
<div class="col-lg-4 mb-4">
<div class="card">
<h4 class="card-header">Temperature</h4>
<div class="card-body text-center">
<p class="card-text display-4"> 30&deg; - 37&deg; </p>
</div>
<div class="card-footer">
<a href="/learnmore" class="btn btn-primary">Learn More</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /.row -->
</div>
<!-- About Section -->
@ -121,4 +140,4 @@
</div>
<hr>
</div>
<%- include('bot') %>
<%- include('bot') %>

View File

@ -26,6 +26,8 @@
<!-- Mustache JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.1/mustache.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.1/mustache.js"></script>
<!-- socket.io scriot -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.min.js"></script>
<!-- jquery app.js -->
<script src="js/app.js"></script>

View File

@ -0,0 +1,41 @@
<%- include('top') %>
<style>
#sensorDataList{
padding-top: 2.5em;
}
</style>
<ul id="sensorDataList">
<li jq-repeat='sensorData'>
rowid: {{ id }}
sensorId: {{ sensorid }}
created: {{ createdAt }}
location: {{ locationid }}
<br/ >
co: {{ measurement.co }}
humidity: {{ measurement.humidity }}
no2: {{ measurement.no2 }}
o3: {{ measurement.o3 }}
psi: {{ measurement.psi }}
so2: {{ measurement.so2 }}
temperature: {{ measurement.temperature }}
windspeed: {{ measurement.windspeed }}
<hr />
</li>
<li jq-repeat-defualt='sensorData'>
Loading...
</li>
</ul>
<script type="text/javascript">
$(document).ready(async function(){
$.scope.sensorData.push(...await app.api.get('sensor-data/data?order=DESC&limit=40'));
app.socket.on('sensordata:new', function(data){
$.scope.sensorData.unshift(data);
});
})
</script>
<%- include('bot') %>

View File

@ -1,144 +1,133 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<meta http-equiv="cleartype" content="on" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" type="images/logo.ico" href="images/logo.ico" />
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<meta http-equiv="cleartype" content="on" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" type="images/logo.ico" href="images/logo.ico" />
<!-- Bootstrap core CSS -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
crossorigin="anonymous" />
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
<!-- Custom styles for this template -->
<link href="css/all.css" rel="stylesheet" />
<link href="css/style.css" rel="stylesheet" />
<!-- weird api page cdn -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;1,300&family=Source+Code+Pro:wght@300&display=swap"
rel="stylesheet" />
<!-- Mustache JS -->
<script src="https://sso.theta42.com/static/js/mustache.min.js"></script>
<!-- jQuery library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<!-- Custom styles for this template -->
<link href="css/all.css" rel="stylesheet" />
<link href="css/style.css" rel="stylesheet" />
<!-- weird api page cdn -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;1,300&family=Source+Code+Pro:wght@300&display=swap"
rel="stylesheet" />
<!-- Mustache JS -->
<script src="https://sso.theta42.com/static/js/mustache.min.js"></script>
<!-- jQuery library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<!-- Bootstrap 5 JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script>
<!-- Bootstrap 5 JavaScript -->
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script>
<!-- weird api page cdn -->
<!-- https://github.com/floriannicolas/API-Documentation-HTML-Template/tree/master -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.8.0/highlight.min.js"></script>
<script>
hljs.initHighlightingOnLoad();
</script>
<!-- jq-repeat -->
<script src="js/jq-repeat.js"></script>
<!-- jquery app.js -->
<script src="js/app.js"></script>
</head>
<!-- javascript function to check if user is auth -->
<!-- weird api page cdn -->
<!-- https://github.com/floriannicolas/API-Documentation-HTML-Template/tree/master -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.8.0/highlight.min.js"></script>
<script>
//make document ready
$(document).ready(function () {
//check if user is logged in
app.auth.isLoggedIn(function (error, data) {
if (data) {
$("#cl-logout-button").show("fast");
$("#cl-profile-button").show("fast");
$("#cl-login-button").hide("fast");
} else {
$("#cl-login-button").show("fast");
}
$("body").show("fast");
});
});
hljs.initHighlightingOnLoad();
</script>
<!-- socket.io scriot -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.min.js"></script>
<body>
<nav
class="navbar fixed-top navbar-expand-lg navbar-dark bg-light top-nav fixed-top">
<div class="container">
<a class="navbar-brand" href="/">
<img src="images/logo.png" alt="logo" />
</a>
<button
class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="fas fa-bars"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ms-auto">
<li jq-repeat="getUsername" class="nav-item">
<a class="nav-link"> Welcome {{ user.username }} </a>
</li>
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/news">News</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/contact">Contact</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/api">API Doc</a>
</li>
<!-- profile button -->
<div class="form-inline mt-2 mt-md-0">
<!-- Profile Button -->
<a
id="cl-profile-button"
class="btn btn-outline-info btn-sm my-2 my-sm-0"
href="/profile"
style="display: none">
<i class="fas fa-sign-out"></i> Profile
</a>
<!-- Login Button -->
<a
id="cl-login-button"
class="btn btn-outline-danger btn-sm my-2 my-sm-0"
onclick="app.auth.forceLogin()"
style="display: none">
<i class="fas fa-sign-out"></i> Login
</a>
<!-- jq-repeat -->
<script src="js/jq-repeat.js"></script>
<!-- Logout Button -->
<button
id="cl-logout-button"
class="btn btn-outline-danger btn-sm my-2 my-sm-0"
href="/"
onclick="app.auth.logOut(e => window.location.href='/')"
style="display: none">
<i class="fas fa-sign-out"></i> Logout
</button>
</div>
</ul>
</div>
<!-- jquery app.js -->
<script src="js/app.js"></script>
</head>
<!-- javascript function to check if user is auth -->
<script>
//make document ready
$(document).ready(function () {
//check if user is logged in
app.auth.isLoggedIn(function (error, data) {
if (data) {
$("#cl-logout-button").show("fast");
$("#cl-api-button").show("fast");
$("#cl-profile-button").show("fast");
$("#cl-login-button").hide("fast");
} else {
$("#cl-login-button").show("fast");
}
$("body").show("fast");
});
});
</script>
<body>
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-light top-nav fixed-top">
<div class="container">
<a class="navbar-brand" href="/">
<img src="images/logo.png" alt="logo" />
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="fas fa-bars"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ms-auto">
<li jq-repeat="getUsername" class="nav-item">
<a class="nav-link"> Welcome {{ user.username }} </a>
</li>
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/news">News</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/contact">Contact</a>
</li>
<!--
<li class="nav-item">
<a class="nav-link" href="/api">API Doc</a>
</li>
-->
<!-- profile button -->
<div class="form-inline mt-2 mt-md-0">
<a id="cl-api-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/api"
style="display: none">
<i class="fas fa-sign-out"></i> API
</a>
<!-- Profile Button -->
<a id="cl-profile-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/profile"
style="display: none">
<i class="fas fa-sign-out"></i> Profile
</a>
<!-- Login Button -->
<a id="cl-login-button" class="btn btn-outline-danger btn-sm my-2 my-sm-0"
onclick="app.auth.forceLogin()" style="display: none">
<i class="fas fa-sign-out"></i> Login
</a>
<!-- Logout Button -->
<button id="cl-logout-button" class="btn btn-outline-danger btn-sm my-2 my-sm-0" href="/"
onclick="app.auth.logOut(e => window.location.href='/')" style="display: none">
<i class="fas fa-sign-out"></i> Logout
</button>
</div>
</ul>
</div>
</nav>
</body>
</html>
</div>
</nav>
</body>
</html>