This commit is contained in:
newtbot 2024-01-04 15:32:46 +08:00
parent db1b513ad5
commit 1819956bd0
21 changed files with 1541 additions and 642 deletions

View File

@ -1,9 +1,6 @@
"use strict";
const { Sequelize, DataTypes } = require("sequelize");
const { sequelize } = require("../mySQL");
const {
isValidDateString,
} = require("../../Web-Server/functions/validateData");
//sequelize.sync();
const api_log_Model = sequelize.define(
@ -14,41 +11,23 @@ const api_log_Model = sequelize.define(
type: DataTypes.INTEGER,
allowNull: true,
primaryKey: true,
autoIncrement: true,
validate: {
isNumeric: true,
},
autoIncrement: true,
},
ip: {
type: DataTypes.STRING,
allowNull: false,
length: 45,
validate: {
isIP: true,
},
},
time: {
type: DataTypes.STRING,
allowNull: false,
length: 20,
validate: {
//validate time: new Date().toUTCString(),
isValidDateString(value) {
if (!isValidDateString(value)) {
throw new Error("Time must be a valid date string");
}
},
notEmpty: { msg: "Time cannot be empty" },
len: [1, 20],
},
},
method: {
type: DataTypes.STRING,
allowNull: false,
length: 10,
validate: {
isIn: [["GET", "POST", "PUT", "DELETE"]],
},
},
host: {
type: DataTypes.STRING,
@ -59,47 +38,21 @@ const api_log_Model = sequelize.define(
type: DataTypes.STRING,
allowNull: false,
length: 10,
validate: {
isNumeric: true,
len: [1, 3],
},
},
Responsesize: {
type: DataTypes.STRING,
allowNull: false,
length: 10,
validate: {
isNumeric: true,
len: [1, 100],
},
},
referrer: {
type: DataTypes.STRING,
allowNull: false,
length: 45,
validate: {
isString(value) {
if (typeof value !== "string") {
throw new Error("Referrer must be a string");
}
},
notEmpty: { msg: "Referrer cannot be empty" },
len: [1, 45], // Length between 1 and 45 characters
},
},
userAgent: {
type: DataTypes.STRING,
allowNull: false,
length: 100,
validate: {
isString(value) {
if (typeof value !== "string") {
throw new Error("UserAgent must be a string");
}
},
notEmpty: { msg: "UserAgent cannot be empty" },
len: [1, 100], // Length between 1 and 100 characters
},
},
createdAt: {
type: DataTypes.DATE,
@ -110,7 +63,6 @@ const api_log_Model = sequelize.define(
allowNull: true,
},
},
{
timestamps: true,
}

View File

@ -1,6 +1,7 @@
"use strict";
const { Sequelize, DataTypes } = require("sequelize");
const { sequelize } = require("../mySQL");
const { isAlphaNumericwithSpaces } = require('../../Web-Server/functions/validateData')
//sequelize.sync();
const locationModel = sequelize.define(
@ -19,28 +20,27 @@ const locationModel = sequelize.define(
type: DataTypes.STRING,
allowNull: false,
length: 10,
unique: true,
validate: {
notEmpty: { msg: "Name cannot be empty" },
notEmpty: true,
len: [1, 20],
/*
//will not validate this and fail it
"hello world" (contains a space)
"hello@123" (contains a symbol)
"" (empty string)
is: /^[a-z]+$/i, // matches this RegExp
is: ["^[a-z]+$",'i'],
*/
is: ["^[a-z0-9]+$", "i"]
},
//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: { msg: "Added by cannot be empty" },
notEmpty: true,
len: [1, 20],
is: ["^[a-z0-9]+$", "i"]
is: ["^[a-z0-9]+$", "i"],
isIn: [['admin', 'system' , 'Admin', 'System']],
},
},
description: {
@ -48,13 +48,12 @@ const locationModel = sequelize.define(
allowNull: true,
length: 100,
validate: {
notEmpty: { msg: "Description cannot be empty" },
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"]
},

View File

@ -3,6 +3,7 @@ const { Sequelize, DataTypes } = require("sequelize");
const { sequelize } = require("../mySQL");
const { locationModel } = require("./locationModel");
const { sensorModel } = require("./sensorModel");
const { isJson } = require("../../Web-Server/functions/validateData");
//sequelize.sync();
const sensorDataModel = sequelize.define(
@ -13,6 +14,11 @@ const sensorDataModel = sequelize.define(
allowNull: true,
primaryKey: true,
autoIncrement: true,
unique: true,
validate: {
isNumeric: true,
notEmpty: true,
},
},
id_sensor: {
type: DataTypes.INTEGER,
@ -23,6 +29,10 @@ const sensorDataModel = sequelize.define(
model: sensorModel,
key: "id",
},
validate: {
isNumeric: true,
notEmpty: true,
},
},
id_location: {
type: DataTypes.INTEGER,
@ -33,10 +43,22 @@ const sensorDataModel = sequelize.define(
model: locationModel,
key: "id",
},
validate: {
isNumeric: true,
notEmpty: true,
},
},
sensordata: {
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,

View File

@ -2,46 +2,97 @@
const { Sequelize, DataTypes } = require("sequelize");
const { sequelize } = require("../mySQL");
const { locationModel } = require("./locationModel");
const {
isAlphaNumericwithSpaces,
isAlphaNumericWithSpacesAndDash,
isMacAddress,
} = require("../../Web-Server/functions/validateData");
//sequelize.sync();
const sensorModel = sequelize.define("sensors",
const sensorModel = sequelize.define(
"sensors",
{
id: {
type: DataTypes.INTEGER,
allowNull: true,
primaryKey: true,
autoIncrement: true,
autoIncrement: true,
unique: true,
validate: {
notEmpty: true,
isNumeric: true,
},
},
sensorname: {
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");
}
},
},
},
location: {
type: DataTypes.INTEGER,
location: {
type: DataTypes.INTEGER,
allowNull: true,
length: 100,
//one to many relationship
references: {
model: locationModel,
key: 'id'
}
},
//one to many relationship
references: {
model: locationModel,
key: "id",
},
validate: {
notEmpty: true,
isNumeric: true,
},
},
createdAt: {
type: DataTypes.DATE,
allowNull: true,

View File

@ -23,7 +23,7 @@ function insertData(data) {
}
}
function insertLogData(log){
async function insertLogData(log){
try{
api_log_Model.create({
ip: log.ip,

View File

@ -1,3 +1,5 @@
var validator = require("validator");
// Regular expressions for data validation
const psiPattern = /^\d+$/;
const humidityPattern = /^\d+%$/;
@ -8,25 +10,66 @@ const timePattern = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
const regionPattern = /^[a-zA-Z-]+$/;
function validateData(data) {
return (
psiPattern.test(data.psi) &&
humidityPattern.test(data.humidity) &&
concentrationPattern.test(data.o3) &&
concentrationPattern.test(data.no2) &&
concentrationPattern.test(data.so2) &&
concentrationPattern.test(data.co) &&
temperaturePattern.test(data.temperature) &&
windspeedPattern.test(data.windspeed) &&
timePattern.test(data.time) &&
regionPattern.test(data.region)
);
return (
psiPattern.test(data.psi) &&
humidityPattern.test(data.humidity) &&
concentrationPattern.test(data.o3) &&
concentrationPattern.test(data.no2) &&
concentrationPattern.test(data.so2) &&
concentrationPattern.test(data.co) &&
temperaturePattern.test(data.temperature) &&
windspeedPattern.test(data.windspeed) &&
timePattern.test(data.time) &&
regionPattern.test(data.region)
);
}
const dateRegex = /^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
const dateRegex =
/^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
function isValidDateString(){
return dateRegex.test(value);
function isValidDateString(value) {
return dateRegex.test(value);
}
function isAlphaNumericwithSpaces(value) {
return validator.isAlphanumeric(value, ["en-US"], { ignore: " " });
}
module.exports = { validateData , isValidDateString };
//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"){
console.log("its an object")
return true
}
}
module.exports = {
validateData,
isValidDateString,
isAlphaNumericwithSpaces,
isAlphaNumericWithSpacesAndDash,
isMacAddress,
isJson,
};
/*
isMACAddress(str [, options])
*/

View File

@ -1,7 +1,7 @@
const { app } = require("./modules/express.js");
const client = require("./modules/mqtt");
const { validateData } = require("./functions/validateData.js");
const { insertData } = require("./functions/Database.js");
const { insertData } = require("./functions/database.js");
/*
1) validate data from IoT sensor
2) upload data to database

View File

@ -1,4 +1,3 @@
const { getAPIKey } = require('../db/ApiKeys');
function apiKeyMiddleware(req, res, next) {
const apiKey = req.headers['x-api-key'];

View File

@ -1,20 +1,24 @@
const { insertLogData } = require("../functions/Database.js");
const { insertLogData } = require("../functions/database.js");
const APIlogger = (req, res, next) => {
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();
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 };

View File

@ -15,11 +15,11 @@ app.disable("x-powered-by");
app.use(express.json());
app.set("json spaces", 2);
//const { APIlogger } = require('../middleware/ApiLogger.js');
const { APIlogger } = require('../middleware/apiLogger.js');
//middleware logic ( called by next() )
//app.use('/api/v0', require('../middleware/ApiKey.js'));
//app.use('/api/v0', APIlogger, require('../routes/api_route.js'));
app.use('/api/v0', APIlogger, require('../routes/api_route.js'));
//route logic
app.use("/api/v0", require("../routes/api_route.js"));

View File

@ -6,7 +6,7 @@ const {
getLocationById,
updateLocation,
deleteLocation,
} = require("../functions/APIDatabase.js");
} = require("../functions/apiDatabase.js");
const express = require("express");
const router = express.Router();
@ -15,7 +15,8 @@ const router = express.Router();
router.get("/", async (req, res, next) => {
try {
const location = await getLocation();
res.json(location);
//res send json and status code
res.status(200).json(location);
} catch (error) {
console.error(error);
next(error);
@ -64,7 +65,7 @@ router.get("/:id", async (req, res, next) => {
//get params
const { id } = req.params;
const location = await getLocationById(id);
res.json(location);
res.status(200).json(location);
} catch (error) {
console.error(error);
next(error);

View File

@ -22,14 +22,13 @@ router.post("/new", async (req, res, next) => {
await sensorModel.create({
sensorname: `AQI-${Math.floor(Math.random()*898)+101}`,
added_by: "system",
//random mac address
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",
location: location.id
});
}
res.sendStatus(200).json({message: "seeded"})
res.sendStatus(200)
} catch (error) {
console.error(error);
next(error);

View File

@ -0,0 +1,34 @@
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 {
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;
/*
1) get id of sensor and location
2) mock data
3) take post req for start date and end date
4) post to db
that takes startDate and an option endDate (the defaults to day) and an option interval to declare the polling interval
i would have more field TBH, that i would start with that
*/

View File

@ -6,7 +6,7 @@ const {
updateSensor,
deleteSensor,
getSensorById
} = require("../functions/APIDatabase.js");
} = require("../functions/apiDatabase.js");
const express = require("express");
const router = express.Router();
@ -14,7 +14,7 @@ const router = express.Router();
router.get("/", async (req, res, next) => {
try {
const sensor = await getSensor();
res.json(sensor);
res.status(200).json(sensor);
} catch (error) {
console.error(error);
next(error);
@ -25,6 +25,7 @@ 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);
@ -35,6 +36,7 @@ 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);
@ -45,6 +47,7 @@ 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);
@ -54,7 +57,8 @@ router.delete("/delete", async (req, res, next) => {
router.get("/:id", async (req, res, next) => {
try {
const sensor = await getSensorById(req.params.id);
res.json(sensor);
res.status(200).json(sensor);
} catch (error) {
console.error(error);
next(error);

View File

@ -7,7 +7,7 @@ const {
deleteSensorData,
getSensorDataById,
} = require("../functions/APIDatabase.js");
} = require("../functions/apiDatabase.js");
const express = require("express");
const { json } = require("body-parser");
@ -16,7 +16,7 @@ const router = express.Router();
router.get("/", async (req, res, next) => {
try {
const sensor = await getSensorData();
res.json(sensor);
res.status(200).json(sensor);
} catch (error) {
console.error(error);
next(error);
@ -25,10 +25,9 @@ router.get("/", async (req, res, next) => {
router.post("/new", async (req, res, next) => {
try {
//JSON.parse(d) /* d is the parameter of the method 'add()' */
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);
@ -39,6 +38,7 @@ 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);
@ -49,6 +49,7 @@ 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);
@ -58,7 +59,7 @@ router.delete("/delete", async (req, res, next) => {
router.get("/:id", async (req, res, next) => {
try {
const sensor = await getSensorDataById(req.params.id);
res.json(sensor);
res.status(200).json(sensor);
} catch (error) {
console.error(error);
next(error);

View File

@ -18,18 +18,14 @@ module.exports = router;
const router = require('express').Router();
//location route
router.use('/location', require('./Location'));
router.use('/location', require('./location'));
//sensor route
router.use('/sensor', require('./Sensor'))
router.use('/sensor', require('./sensor'))
//sensor data route
router.use('/sensor-data', require('./SensorData'));
router.use('/sensor-data', require('./sensorData'));
router.use('/test' , require('./test'));
router.use('/latest-data', require('./latest-data'));

View File

@ -1,28 +1,9 @@
const { sequelize } = require("../../Database/mySql.js");
const { IoTModel } = require("../../Database/model/IoTModel.js");
const { getLatestData } = require("../functions/APIDatabase.js");
const { getLatestData } = require("../functions/apiDatabase.js");
const express = require('express');
const router = express.Router();
// Logic for model and API by 1
/*
async function getLatestData() {
try {
sequelize.sync();
const latestData = await IoTModel.findAll({
limit: 1,
order: [['createdAt', 'DESC']]
});
return latestData;
}
catch (error) {
console.error(error);
return null;
}
}
*/
router.get('/', async (req, res) => {
try {
const data = await getLatestData();

View File

@ -2,7 +2,9 @@
const router = require('express').Router();
//location route
router.use('/seed', require('./SeedLocationAndSensor'));
router.use('/seed', require('./seedLocationAndSensor'));
router.use('/seedSensorData ', require('./seedsensorData'));

View File

@ -1,25 +0,0 @@
const { sequelize } = require("../../Database/mySql.js");
const { IoTModel } = require("../../Database/model/IoTModel.js");
const { getallData } = require("../functions/APIDatabase.js");
const express = require('express');
const router = express.Router();
router.get('/', async (req, res) => {
try {
const data = await getallData();
if (data === null) {
res.status(404).send("No data found");
} else {
res.send(data);
}
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
});
// Export the router
module.exports = router;

25
api.MD
View File

@ -6,9 +6,11 @@ curl localhost/api/v0/location
curl http://localhost/api/v0/location/3
//post
curl localhost/api/v0/location/new -H "Content-Type: application/json" -X POST -d '{"name": "test", "added_by": "noot" , "description": "test"}'
curl localhost/api/v0/location/new -H "Content-Type: application/json" -X POST -d '{"name": "SAMPLE", "added_by": "system" , "description": "test"}'
status: 200
added_name allowed: system , admin
name allowed: shld contain alphanumeric only
//put
curl localhost/api/v0/location/update -X PUT -H "Content-Type: application/json" -d '{"id": "2" , "name": "test", "added_by": "noot", "description": "test12344444444"}'
@ -18,7 +20,6 @@ curl localhost/api/v0/location/update -X PUT -H "Content-Type: application/json"
//delete
curl localhost/api/v0/location/delete -X DELETE -H "Content-Type: application/json" -d '{"id": "6" }'
status: 200
{
"message": "Location 13 deleted"
@ -32,13 +33,26 @@ curl localhost/api/v0/sensor
curl http://localhost/api/v0/sensor/3
//POST
curl localhost/api/v0/sensor/new -H "Content-Type: application/json" -X POST -d '{"sensorname": "test", "added_by": "noot" , "mac_address": "99-6A-F8-7D-B4-90", "description": "test" , "location": "2"}'
curl localhost/api/v0/sensor/new -H "Content-Type: application/json" -X POST -d '{"sensorname": "test", "added_by": "system" , "mac_address": "99-6A-F8-7D-B4-94", "description": "test" , "location": "11"}'
status: 200
added_name allowed: system , admin
sensor name : shld contain alphanumeric only and be unique
//put
curl localhost/api/v0/sensor/update -H "Content-Type: application/json" -X PUT -d '{"id": "2" ,"sensorname": "test", "added_by": "noot" , "mac_address": "99-6A-F8-7D-B4-90" , "description": "test123333333" , "location": "3" }'
curl localhost/api/v0/sensor/update -H "Content-Type: application/json" -X PUT -d '{"id": "2" ,"sensorname": "test", "added_by": "system" , "mac_address": "99-6A-F8-7D-B4-94" , "description": "test123" , "location": "11" }'
status: 200
added_name allowed: system , admin
sensor name : shld contain alphanumeric only and be unique
"message": "Sensor 13 updated"
//delete
curl localhost/api/v0/sensor/delete -X DELETE -H "Content-Type: application/json" -d '{"id": "2" }'
{
"message": "sensor 13 deleted"
}
//sensor data
curl http://localhost/api/v0/sensor-data/
@ -48,7 +62,7 @@ curl http://localhost/api/v0/sensor-data/1
//post
curl localhost/api/v0/sensor-data/new -H "Content-Type: application/json" -X POST -d '{"id": "2", "id_sensor": "1" , "id_location": "2" , "sensordata": {
curl localhost/api/v0/sensor-data/new -H "Content-Type: application/json" -X POST -d '{"id_sensor": "1" , "id_location": "11" , "sensordata": {
"psi": "34",
"humidity": "11%",
"o3": "326ppm",
@ -79,5 +93,4 @@ curl localhost/api/v0/sensor-data/update -H "Content-Type: application/json" -X
curl localhost/api/v0/sensor-data/delete -X DELETE -H "Content-Type: application/json" -d '{"id": "3" }'
//seed
curl localhost/api/seed/v0/seed/new -H "Content-Type: application/json" -X POST -d '{"mockLocation": ["test", "test123"]}'

1739
package-lock.json generated

File diff suppressed because it is too large Load Diff