tidy up
This commit is contained in:
71
webserver/database/model/apiLogModel.js
Normal file
71
webserver/database/model/apiLogModel.js
Normal 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 };
|
75
webserver/database/model/locationModel.js
Normal file
75
webserver/database/model/locationModel.js
Normal 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 };
|
77
webserver/database/model/sensorDataModel.js
Normal file
77
webserver/database/model/sensorDataModel.js
Normal 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 };
|
110
webserver/database/model/sensorModel.js
Normal file
110
webserver/database/model/sensorModel.js
Normal 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 };
|
35
webserver/database/mySQL.js
Normal file
35
webserver/database/mySQL.js
Normal file
@ -0,0 +1,35 @@
|
||||
const dotenv = require("dotenv");
|
||||
const path = require('path')
|
||||
require('dotenv').config({ path: path.resolve(__dirname, '../../.env') })
|
||||
const Sequelize = require("sequelize");
|
||||
const fs = require('fs');
|
||||
const { escape } = require("querystring");
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
"eco_saver",
|
||||
process.env.DB_USER,
|
||||
process.env.DB_PASS,
|
||||
{
|
||||
host: "mpsqldatabase.mysql.database.azure.com",
|
||||
dialect: 'mysql',
|
||||
// attributeBehavior?: 'escape' | 'throw' | 'unsafe-legacy';
|
||||
attributeBehavior: 'escape',
|
||||
dialectOptions: {
|
||||
ssl: {
|
||||
ca: fs.readFileSync(path.resolve(__dirname, '../cert/DigiCertGlobalRootCA.crt.pem')),
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
);
|
||||
|
||||
sequelize.authenticate().then(() => {
|
||||
console.log('Connection has been established successfully.');
|
||||
}).catch((error) => {
|
||||
console.error('Unable to connect to the database: ', error);
|
||||
});
|
||||
|
||||
module.exports = { sequelize };
|
||||
|
846
webserver/functions/APIDatabase.js
Normal file
846
webserver/functions/APIDatabase.js
Normal file
@ -0,0 +1,846 @@
|
||||
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 { Op, Sequelize } = require("sequelize");
|
||||
|
||||
//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 getLocation() {
|
||||
const location = await locationModel.findAll();
|
||||
return location;
|
||||
}
|
||||
|
||||
async function addLocation(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;
|
||||
}
|
||||
|
||||
async function getSensor() {
|
||||
const sensor = await sensorModel.findAll();
|
||||
return sensor;
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async function getSensorData() {
|
||||
const sensorData = await sensorDataModel.findAll();
|
||||
return sensorData;
|
||||
}
|
||||
|
||||
async function addSensorData(id, id_sensor, id_location, sensordata) {
|
||||
const sensorData = await sensorDataModel.create({
|
||||
id: id,
|
||||
sensorid: id_sensor,
|
||||
locationid: id_location,
|
||||
measurement: 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 = {
|
||||
getLocation,
|
||||
addLocation,
|
||||
updateLocation,
|
||||
deleteLocation,
|
||||
getLocationById,
|
||||
getSensor,
|
||||
addSensor,
|
||||
updateSensor,
|
||||
deleteSensor,
|
||||
getSensorById,
|
||||
getSensorData,
|
||||
addSensorData,
|
||||
updateSensorData,
|
||||
deleteSensorData,
|
||||
getSensorDataById,
|
||||
getData,
|
||||
getDatabyRange,
|
||||
};
|
43
webserver/functions/Database.js
Normal file
43
webserver/functions/Database.js
Normal file
@ -0,0 +1,43 @@
|
||||
const { sequelize } = require("../Database/mySql.js");
|
||||
const { api_log_Model } = require("../Database/model/apiLogModel.js");
|
||||
const { sensorDataModel } = require("../Database/model/sensorDataModel.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);
|
||||
}
|
||||
}
|
||||
|
||||
async function insertDatatoDB(data) {
|
||||
try {
|
||||
sensorDataModel.create({
|
||||
sensorid: data.sensorid,
|
||||
locationid: data.locationid,
|
||||
measurement: data.measurement,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { insertLogData , insertDatatoDB};
|
||||
|
||||
|
||||
|
||||
|
||||
|
17
webserver/functions/getAPIKey.js
Normal file
17
webserver/functions/getAPIKey.js
Normal file
@ -0,0 +1,17 @@
|
||||
//model for getting API key from database
|
||||
|
||||
async function getAPIKey() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = { getAPIKey }
|
51
webserver/functions/validateData.js
Normal file
51
webserver/functions/validateData.js
Normal file
@ -0,0 +1,51 @@
|
||||
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 isAlphaNumericwithSpaces(value) {
|
||||
return validator.isAlphanumeric(value, ["en-US"], { ignore: " " });
|
||||
}
|
||||
|
||||
//allow alphanumeric and spaces and -
|
||||
function isAlphaNumericWithSpacesAndDash(value) {
|
||||
const alphanumeric = /^[a-zA-Z0-9]+$/;
|
||||
const valid = value
|
||||
.split("")
|
||||
.every((char) => alphanumeric.test(char) || char === " " || char === "-");
|
||||
return valid;
|
||||
}
|
||||
|
||||
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 isJson(value) {
|
||||
//check if its object
|
||||
if(typeof value === "object"){
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function isNumber(value) {
|
||||
if (typeof value === "number") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isValidDateString,
|
||||
isAlphaNumericwithSpaces,
|
||||
isAlphaNumericWithSpacesAndDash,
|
||||
isMacAddress,
|
||||
isJson,
|
||||
isNumber,
|
||||
};
|
||||
|
66
webserver/index.js
Normal file
66
webserver/index.js
Normal file
@ -0,0 +1,66 @@
|
||||
const { app } = require("./modules/express.js");
|
||||
const client = require("./modules/mqtt");
|
||||
const { isJson, isNumber } = require("./functions/validateData.js");
|
||||
const { insertDatatoDB } = require("./functions/database.js");
|
||||
|
||||
// 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
|
||||
insertDatatoDB(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();
|
||||
});
|
18
webserver/middleware/apiKey.js
Normal file
18
webserver/middleware/apiKey.js
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
function apiKeyMiddleware(req, res, next) {
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
|
||||
if (!apiKey) {
|
||||
return res.status(401).json({ error: 'API key is missing' });
|
||||
}
|
||||
|
||||
//logic to check db?
|
||||
if (apiKey !== 'YOUR_API_KEY') {
|
||||
return res.status(403).json({ error: 'Invalid API key' });
|
||||
}
|
||||
|
||||
// API key is valid, continue to the next middleware or route handler
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = { apiKeyMiddleware }
|
36
webserver/middleware/apiLogger.js
Normal file
36
webserver/middleware/apiLogger.js
Normal file
@ -0,0 +1,36 @@
|
||||
const { insertLogData } = require("../functions/database.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}`,
|
||||
*/
|
83
webserver/modules/express.js
Normal file
83
webserver/modules/express.js
Normal file
@ -0,0 +1,83 @@
|
||||
const express = require("express");
|
||||
const helmet = require("helmet");
|
||||
const { rateLimit } = require("express-rate-limit");
|
||||
const { APIlogger } = require('../middleware/apiLogger.js');
|
||||
|
||||
const app = express();
|
||||
app.use(helmet());
|
||||
const port = 80;
|
||||
|
||||
//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.
|
||||
})
|
||||
|
||||
|
||||
// Apply the rate limiting middleware to all requests.
|
||||
app.use(limiter)
|
||||
|
||||
//disable x-powered-by header for security reasons
|
||||
app.disable("x-powered-by");
|
||||
|
||||
//parse json body format
|
||||
app.use(express.json());
|
||||
app.set("json spaces", 2);
|
||||
|
||||
|
||||
/*
|
||||
middleware logic ( called by next() )
|
||||
*/
|
||||
//app.use('/api/v0', require('../middleware/ApiKey.js'));
|
||||
app.use('/api/v0', APIlogger, require('../routes/api_route.js'));
|
||||
|
||||
//route logic
|
||||
app.use("/api/v0", require("../routes/api_route.js"));
|
||||
|
||||
//seed logic
|
||||
app.use("/api/seed/v0", require("../routes/seed_route.js"));
|
||||
|
||||
// Catch 404 and forward to error handler. If none of the above routes are
|
||||
// used, this is what will be called.
|
||||
app.use(function (req, res, next) {
|
||||
var err = new Error("Not Found");
|
||||
err.message = "Page not found";
|
||||
err.status = 404;
|
||||
next(err);
|
||||
});
|
||||
|
||||
// Error handler. This is where `next()` will go on error
|
||||
app.use(function(err, req, res, next) {
|
||||
console.error(err.status || res.status, err.name, req.method, req.url);
|
||||
if(![ 404].includes(err.status || res.status)){
|
||||
console.error(err.message);
|
||||
console.error(err.stack);
|
||||
console.error('=========================================');
|
||||
}
|
||||
|
||||
console.log(err.name + " validation error");
|
||||
// Parse key error for Sequilzw
|
||||
let keyErrors = {}
|
||||
if(['SequelizeValidationError'].includes(err.name) && err.errors){
|
||||
for(let item of err.errors){
|
||||
if(item.path){
|
||||
keyErrors[item.path] = item.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(err.status || 500);
|
||||
console.log(keyErrors);
|
||||
res.json({
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
keyErrors,
|
||||
});
|
||||
});
|
||||
app.listen(port, () => {
|
||||
console.log(`app listening on port ${port}`);
|
||||
});
|
||||
|
||||
module.exports = { app };
|
22
webserver/modules/mqtt.js
Normal file
22
webserver/modules/mqtt.js
Normal file
@ -0,0 +1,22 @@
|
||||
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;
|
||||
|
||||
|
76
webserver/routes/Location.js
Normal file
76
webserver/routes/Location.js
Normal file
@ -0,0 +1,76 @@
|
||||
const {
|
||||
addLocation,
|
||||
getLocation,
|
||||
getLocationById,
|
||||
updateLocation,
|
||||
deleteLocation,
|
||||
} = require("../functions/apiDatabase.js");
|
||||
|
||||
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 {
|
||||
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;
|
41
webserver/routes/SeedLocationAndSensor.js
Normal file
41
webserver/routes/SeedLocationAndSensor.js
Normal 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;
|
122
webserver/routes/SeedsensorData.js
Normal file
122
webserver/routes/SeedsensorData.js
Normal 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})
|
||||
|
||||
*/
|
67
webserver/routes/Sensor.js
Normal file
67
webserver/routes/Sensor.js
Normal file
@ -0,0 +1,67 @@
|
||||
const {
|
||||
getSensor,
|
||||
addSensor,
|
||||
updateSensor,
|
||||
deleteSensor,
|
||||
getSensorById
|
||||
} = require("../functions/apiDatabase.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;
|
95
webserver/routes/SensorData.js
Normal file
95
webserver/routes/SensorData.js
Normal file
@ -0,0 +1,95 @@
|
||||
const {
|
||||
getSensorData,
|
||||
addSensorData,
|
||||
updateSensorData,
|
||||
deleteSensorData,
|
||||
getSensorDataById,
|
||||
getData,
|
||||
getDatabyRange,
|
||||
} = require("../functions/apiDatabase.js");
|
||||
|
||||
const express = require("express");
|
||||
const { json } = require("body-parser");
|
||||
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, id_sensor, id_location, sensordata } = req.body;
|
||||
await addSensorData(id, id_sensor, id_location, sensordata);
|
||||
res.sendStatus(200).json({ message: "SensorData " + id + " added" });
|
||||
} 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;
|
||||
|
34
webserver/routes/api_route.js
Normal file
34
webserver/routes/api_route.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
'use strict';
|
||||
|
||||
const router = require('express').Router();
|
||||
const middleware = require('../middleware/auth');
|
||||
|
||||
router.use('/runner', require('./runner'));
|
||||
router.use('/worker', require('./worker'));
|
||||
router.use('/auth', require('./auth'));
|
||||
router.use('/user', middleware.auth, require('./user'));
|
||||
router.use('/token',middleware.auth, require('./token'));
|
||||
|
||||
module.exports = router;
|
||||
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
const router = require('express').Router();
|
||||
|
||||
//location route
|
||||
router.use('/location', require('./location'));
|
||||
|
||||
//sensor route
|
||||
router.use('/sensor', require('./sensor'))
|
||||
|
||||
//sensor data route
|
||||
router.use('/sensor-data', require('./sensorData'));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = router;
|
14
webserver/routes/seed_route.js
Normal file
14
webserver/routes/seed_route.js
Normal 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'));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = router;
|
Reference in New Issue
Block a user