merged
This commit is contained in:
@ -2,12 +2,10 @@ 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;
|
||||
|
||||
process.nextTick(() => require("./mqttApp"));
|
||||
//process.nextTick(() => require("./mqttApp"));
|
||||
|
||||
app.use(express.json());
|
||||
app.set("json spaces", 2);
|
||||
@ -50,49 +48,48 @@ 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.
|
||||
app.use(function (req, res, next) {
|
||||
//application/json; charset=utf-8
|
||||
if (req.is("application/json" || "application/json; charset=utf-8")) {
|
||||
var err = new Error("Not Found");
|
||||
err.message = "Page not found";
|
||||
err.status = 404;
|
||||
next(err);
|
||||
} else {
|
||||
res.status(404).render("404");
|
||||
}
|
||||
//application/json; charset=utf-8
|
||||
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.error(err.status || res.status, err.name, req.method, req.url);
|
||||
|
||||
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 = 422;
|
||||
}
|
||||
// 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 = 422;
|
||||
}
|
||||
|
||||
if (![404, 422].includes(err.status || res.status)) {
|
||||
console.error(err.message);
|
||||
console.error(err.stack);
|
||||
console.error("=========================================");
|
||||
}
|
||||
if (![404, 401, 422].includes(err.status || res.status)) {
|
||||
console.error(err.message);
|
||||
console.error(err.stack);
|
||||
console.error("=========================================");
|
||||
}
|
||||
res.status(err.status || 500);
|
||||
// res.status(err.status || 500);
|
||||
|
||||
|
||||
res.status(err.status || 500);
|
||||
res.json({
|
||||
name: err.name || "Unknown error",
|
||||
message: err.message,
|
||||
keyErrors,
|
||||
});
|
||||
if (req.get('Content-Type') && req.get('Content-Type').includes("json")) {
|
||||
res.json({
|
||||
name: err.name || "Unknown error",
|
||||
message: err.message,
|
||||
keyErrors,
|
||||
});
|
||||
}
|
||||
else {
|
||||
res.json({
|
||||
name: err.name || "Unknown error",
|
||||
message: err.message,
|
||||
keyErrors,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -107,4 +107,6 @@ const sensorModel = sequelize.define(
|
||||
}
|
||||
);
|
||||
|
||||
//sensorModel.belongsTo(locationModel);
|
||||
|
||||
module.exports = { sensorModel };
|
||||
|
@ -48,6 +48,14 @@ const tokenModel = sequelize.define(
|
||||
isIn: [["canRead", "canWrite",]],
|
||||
},
|
||||
},
|
||||
isKey: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
length: 45,
|
||||
validate:{
|
||||
isIn: [["isKey" , "isNotKey"]],
|
||||
}
|
||||
},
|
||||
expiration: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
|
@ -13,14 +13,16 @@ const sequelize = new Sequelize(
|
||||
dialect: process.env.DB_dialect,
|
||||
storage: process.env.DB_storage,
|
||||
logging: process.env.DB_logging,
|
||||
|
||||
// attributeBehavior?: 'escape' | 'throw' | 'unsafe-legacy';
|
||||
attributeBehavior: 'escape',
|
||||
dialectOptions: {
|
||||
ssl: {
|
||||
ca: fs.readFileSync(path.resolve(__dirname, '../cert/DigiCertGlobalRootCA.crt.pem')),
|
||||
ca: fs.readFileSync(path.resolve(__dirname, '../cert/DigiCertGlobalRootCA.crt_3.pem')),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
);
|
||||
|
||||
sequelize.authenticate().then(() => {
|
||||
|
@ -1,24 +1,48 @@
|
||||
const { hash, compareHash } = require("./bcrypt.js");
|
||||
const { tokenModel } = require("../database/model/tokenModel.js");
|
||||
const { userModel } = require("../database/model/userModel");
|
||||
const { hash, compareHash } = require("./bcrypt.js");
|
||||
const { generateUUID } = require("./generateUUID.js");
|
||||
const { isValid , resetIsValid } = require("./isValid");
|
||||
|
||||
/*
|
||||
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 addToken(userId, permission , expiry) {
|
||||
async function getTokenByToken(token) {
|
||||
const splitAuthToken = token.split("-");
|
||||
const rowid = splitAuthToken[0];
|
||||
const suppliedToken = splitAuthToken.slice(1).join("-");
|
||||
if (!suppliedToken) return false;
|
||||
|
||||
token = await tokenModel.findByPk(rowid, { include: userModel });
|
||||
|
||||
token.isValid = await compareHash(suppliedToken, token.token); //true
|
||||
console.log("function api getTokenByToken token", token.isValid);
|
||||
token.isValid = token.isValid && isValid(token.expiration);
|
||||
console.log("function api getTokenByToken token", token.isValid);
|
||||
if (!token.isValid) {
|
||||
//add boolean to token table
|
||||
token.destroy();
|
||||
}
|
||||
/*
|
||||
console.log(
|
||||
"function api getTokenByToken token",
|
||||
await compareHash(suppliedToken, token.token),
|
||||
isValid("token" , token.expiration)
|
||||
);
|
||||
*/
|
||||
console.log(token.isValid);
|
||||
return token;
|
||||
}
|
||||
|
||||
async function addToken(userId, permission, isKey ,expiry) {
|
||||
let uuid = await generateUUID();
|
||||
let hashtoken = await hash(uuid);
|
||||
//console.log("user id", userId);
|
||||
// return { token: token, userid: userRes.id, username: userRes.username };
|
||||
// let token = await addToken(userRes.id , "canRead" , tokenToLive);
|
||||
|
||||
let token = await tokenModel.create({
|
||||
userid: userId,
|
||||
token: hashtoken,
|
||||
permission: permission,
|
||||
isKey: isKey,
|
||||
expiration: expiry,
|
||||
});
|
||||
|
||||
@ -26,23 +50,58 @@ async function addToken(userId, permission , expiry) {
|
||||
return token.id + "-" + uuid;
|
||||
}
|
||||
|
||||
async function checkToken(Supplied, rowid) {
|
||||
try {
|
||||
const retrivedToken = await tokenModel.findOne({
|
||||
raw: true,
|
||||
attributes: ["token", "permission"],
|
||||
where: {
|
||||
id: rowid,
|
||||
},
|
||||
});
|
||||
//console.log(retrivedKey.apikey);
|
||||
if (compareHash(Supplied, retrivedToken.token)) {
|
||||
//return true;
|
||||
return retrivedToken.permission;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
async function addPasswordResetToken(data , token){
|
||||
let hashtoken = await hash(token);
|
||||
let currentDate = new Date();
|
||||
let tokenToLive = new Date(currentDate.getTime() + 5 * 60000);
|
||||
|
||||
let tokenRes = await tokenModel.create({
|
||||
userid: data.id,
|
||||
token: hashtoken,
|
||||
permission: "canRead",
|
||||
isKey: "isNotKey",
|
||||
expiration: tokenToLive,
|
||||
});
|
||||
return tokenRes.id
|
||||
}
|
||||
|
||||
module.exports = { addToken , checkToken };
|
||||
async function checkToken(id) {
|
||||
let tokenRes = await tokenModel.findOne(
|
||||
{
|
||||
where: {
|
||||
userid: id,
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
return tokenRes;
|
||||
}
|
||||
|
||||
async function checkTokenByrowID(token) {
|
||||
if (!token) return false;
|
||||
//split
|
||||
const splitAuthToken = token.split("-");
|
||||
const rowid = splitAuthToken[0];
|
||||
const suppliedToken = splitAuthToken.slice(1).join("-");
|
||||
|
||||
let tokenRes = await tokenModel.findByPk(rowid);
|
||||
//console.log(tokenRes);
|
||||
|
||||
if (!tokenRes) return false;
|
||||
|
||||
if (!compareHash(suppliedToken, tokenRes.token)) return false;
|
||||
|
||||
|
||||
//pass tokemRes.expiration to isValid
|
||||
if (!isValid(tokenRes.expiration)) {
|
||||
//add boolean to token table
|
||||
tokenRes.destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
return tokenRes;
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = { addToken, getTokenByToken , checkToken , addPasswordResetToken , checkTokenByrowID};
|
||||
|
11
consumerWebsite/functions/apilog.js
Normal file
11
consumerWebsite/functions/apilog.js
Normal file
@ -0,0 +1,11 @@
|
||||
const { api_log_Model } = require("../database/model/apiLogModel");
|
||||
const {Op} = require("sequelize");
|
||||
|
||||
async function getAllLog(){
|
||||
return await api_log_Model.findAll();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = { getAllLog };
|
@ -1,20 +1,26 @@
|
||||
const moment = require("moment");
|
||||
const currentTime = moment().format("YYYY-MM-DD HH:mm:ss");
|
||||
|
||||
//time is taken from the token
|
||||
function isValid(time){
|
||||
const timeDiff = moment(currentTime).diff(time, "minutes");
|
||||
function isValid(time) {
|
||||
|
||||
if (timeDiff > 1) {
|
||||
console.log(timeDiff);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (
|
||||
Math.floor(new Date(time).getTime() / 1000) <
|
||||
Math.floor(new Date().getTime() / 1000)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
//5 minutes
|
||||
function resetIsValid(time) {
|
||||
if (
|
||||
Math.floor(new Date(time).getTime() / 1000) <
|
||||
Math.floor(new Date().getTime() / 1000)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = { isValid };
|
||||
module.exports = { isValid , resetIsValid };
|
||||
|
@ -1,4 +1,5 @@
|
||||
const {locationModel} = require("../database/model/locationModel");
|
||||
const {Op} = require("sequelize");
|
||||
|
||||
async function getLocation() {
|
||||
const location = await locationModel.findAll();
|
||||
@ -35,7 +36,32 @@ async function deleteLocation(id) {
|
||||
},
|
||||
});
|
||||
}
|
||||
/*
|
||||
const { Op } = require("sequelize");
|
||||
|
||||
var options = {
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ 'subject': { [Op.like]: '%' + query + '%' } },
|
||||
{ '$Comment.body$': { [Op.like]: '%' + query + '%' } }
|
||||
]
|
||||
},
|
||||
include: [{ model: Comment }]
|
||||
};
|
||||
*/
|
||||
|
||||
async function getLocationByName(name) {
|
||||
const location = await locationModel.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{name: {[Op.like]: "%" + name + "%"}},
|
||||
{added_by: {[Op.like]: "%" + name + "%"}},
|
||||
{description: {[Op.like]: "%" + name + "%"}},
|
||||
],
|
||||
},
|
||||
});
|
||||
return location;
|
||||
}
|
||||
|
||||
async function getLocationById(id) {
|
||||
const location = await locationModel.findAll({
|
||||
@ -51,5 +77,6 @@ module.exports = {
|
||||
addLocation,
|
||||
updateLocation,
|
||||
deleteLocation,
|
||||
getLocationByName,
|
||||
getLocationById,
|
||||
};
|
108
consumerWebsite/functions/nodeMail.js
Normal file
108
consumerWebsite/functions/nodeMail.js
Normal file
@ -0,0 +1,108 @@
|
||||
const { transporter } = require("../modules/nodemailer");
|
||||
const path = require("path");
|
||||
require("dotenv").config({ path: path.resolve(__dirname, "../.env") });
|
||||
|
||||
/*
|
||||
var message = {
|
||||
from: "sender@server.com",
|
||||
to: "receiver@sender.com",
|
||||
subject: "Message title",
|
||||
text: "Plaintext version of the message",
|
||||
html: "<p>HTML version of the message</p>",
|
||||
};
|
||||
//send mail with defined transport object
|
||||
transporter.sendMail(data[, callback])
|
||||
|
||||
*/
|
||||
|
||||
async function sendContactEmail(email, name, message) {
|
||||
console.log(email, name, message);
|
||||
|
||||
try {
|
||||
let contactMessage = await transporter.sendMail({
|
||||
to: process.env.euser,
|
||||
subject: "Contact us Message",
|
||||
html: `
|
||||
<h1>Contact us Message</h1>
|
||||
<p><strong>From:</strong> ${name}</p>
|
||||
<p><strong>User Email:</strong> ${email}</p>
|
||||
<p><strong>Message:</strong> ${message}</p>
|
||||
<p>Thank you for contacting us. We will get back to you as soon as possible.</p>
|
||||
<p>Regards,</p>
|
||||
<p>EcoSaver Team</p>
|
||||
<p><a href="https://ecosaver.teeseng.uk/">EcoSaver Website</a></p>
|
||||
<p>Please do not reply to this email.</p>
|
||||
`,
|
||||
});
|
||||
transporter.sendMail({ contactMessage }, function (error, info) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
} else {
|
||||
console.log("Email sent: " + info.response);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendTokenEmail(email, token) {
|
||||
|
||||
try {
|
||||
let tokenMessage = await transporter.sendMail({
|
||||
to: email,
|
||||
from: process.env.euser,
|
||||
subject: "API Token",
|
||||
html: `
|
||||
<h1>API Token</h1>
|
||||
<p><strong>Token:</strong> ${token}</p>
|
||||
<p>Please do not lose this token and do not share your token with anyone!</p>
|
||||
<p>Thank you for using EcoSaver.</p>
|
||||
<p>Regards,</p>
|
||||
<p>EcoSaver Team</p>
|
||||
<p><a href="https://ecosaver.teeseng.uk/">EcoSaver Website</a></p>
|
||||
<p>Please do not reply to this email.</p>
|
||||
`,
|
||||
});
|
||||
transporter.sendMail({ tokenMessage }, function (error, info) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
} else {
|
||||
console.log("Email sent: " + info.response);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendResetPasswordEmail(email, resetToken) {
|
||||
try {
|
||||
let resetMessage = await transporter.sendMail({
|
||||
to: email,
|
||||
from: process.env.euser,
|
||||
subject: "Reset Password",
|
||||
html: `
|
||||
<h1>Reset Password</h1>
|
||||
<p><strong>Reset Password Link:</strong> <a href="localhost/resetpassword/${resetToken}">Reset Password Link </p>
|
||||
<p><strong>From:</strong> Eco Saver</p>
|
||||
<p>Kindly click on the link to reset your password!</p>
|
||||
<p>Regards,</p>
|
||||
<p>EcoSaver Team</p>
|
||||
<p><a href="https://ecosaver.teeseng.uk/">EcoSaver Website</a></p>
|
||||
<p>Please do not reply to this email.</p>
|
||||
`,
|
||||
});
|
||||
transporter.sendMail({ resetMessage }, function (error, info) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
} else {
|
||||
console.log("Email sent: " + info.response);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { sendContactEmail , sendTokenEmail, sendResetPasswordEmail };
|
@ -1,4 +1,5 @@
|
||||
const { sensorModel } = require("../database/model/sensorModel");
|
||||
const {Op} = require("sequelize");
|
||||
|
||||
async function getSensor() {
|
||||
const sensor = await sensorModel.findAll();
|
||||
@ -55,6 +56,33 @@ async function deleteSensor(id) {
|
||||
},
|
||||
});
|
||||
}
|
||||
/*
|
||||
async function getLocationByName(name) {
|
||||
const location = await locationModel.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{name: {[Op.like]: "%" + name + "%"}},
|
||||
{added_by: {[Op.like]: "%" + name + "%"}},
|
||||
{description: {[Op.like]: "%" + name + "%"}},
|
||||
],
|
||||
},
|
||||
});
|
||||
return location;
|
||||
}
|
||||
*/
|
||||
async function getSensorByName(name) {
|
||||
const sensor = await sensorModel.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{name: {[Op.like]: "%" + name + "%"}},
|
||||
{added_by: {[Op.like]: "%" + name + "%"}},
|
||||
{mac_address: {[Op.like]: "%" + name + "%"}},
|
||||
{description: {[Op.like]: "%" + name + "%"}},
|
||||
],
|
||||
},
|
||||
});
|
||||
return sensor;
|
||||
}
|
||||
|
||||
async function getSensorById(id) {
|
||||
const sensor = await sensorModel.findAll({
|
||||
@ -70,5 +98,6 @@ module.exports = {
|
||||
addSensor,
|
||||
updateSensor,
|
||||
deleteSensor,
|
||||
getSensorByName,
|
||||
getSensorById,
|
||||
};
|
||||
|
@ -18,15 +18,17 @@ async function getSensorData() {
|
||||
const sensorData = await sensorDataModel.findAll();
|
||||
return sensorData;
|
||||
}
|
||||
|
||||
async function addSensorData(id_sensor, id_location, sensordata) {
|
||||
async function addSensorData(sensorid , locationid , measurement) {
|
||||
const sensorData = await sensorDataModel.create({
|
||||
sensorid: id_sensor,
|
||||
locationid: id_location,
|
||||
measurement: sensordata,
|
||||
sensorid: sensorid,
|
||||
locationid: locationid,
|
||||
measurement: measurement
|
||||
});
|
||||
io().emit('sensorData:new', sensorData)
|
||||
|
||||
//console.log("sensorData", sensorData);
|
||||
//console.log("sensorData", sensordata.measurement);
|
||||
//console.log("sensorData", sensorData.measurement);
|
||||
|
||||
io().emit('sensorData:new', sensorData.measurement);
|
||||
return sensorData;
|
||||
}
|
||||
|
||||
@ -61,6 +63,14 @@ async function getSensorDataById(id) {
|
||||
});
|
||||
return sensorData;
|
||||
}
|
||||
|
||||
async function getLatestData() {
|
||||
const sensorData = await sensorDataModel.findAll({
|
||||
limit: 6,
|
||||
order: [["createdAt", "DESC"]],
|
||||
});
|
||||
return sensorData;
|
||||
}
|
||||
var ormQuery = {};
|
||||
var whereClause = {};
|
||||
var whereDate = {};
|
||||
@ -664,6 +674,7 @@ async function getDatabyRange(queryString) {
|
||||
queryString.limit = queryString.pagesize;
|
||||
|
||||
whereDate = {};
|
||||
ormQuery = {};
|
||||
for (let query in queryString) {
|
||||
if (buildFunc[query]) {
|
||||
await buildFunc[query](queryString);
|
||||
@ -690,6 +701,8 @@ async function getDatabyRange(queryString) {
|
||||
}
|
||||
} else {
|
||||
whereDate = {};
|
||||
ormQuery = {};
|
||||
|
||||
for (let query in queryString) {
|
||||
if (buildFunc[query]) {
|
||||
await buildFunc[query](queryString);
|
||||
@ -724,5 +737,6 @@ module.exports = {
|
||||
getSensorDataById,
|
||||
getData,
|
||||
getDatabyRange,
|
||||
getLatestData,
|
||||
|
||||
};
|
@ -2,7 +2,6 @@ const { Op } = require('sequelize')
|
||||
const { hash, compareHash } = require("./bcrypt.js");
|
||||
const { addToken } = require("./api");
|
||||
const { userModel } = require("../database/model/userModel");
|
||||
moment = require('moment')
|
||||
|
||||
|
||||
|
||||
@ -21,6 +20,16 @@ async function getUserByID(userid) {
|
||||
return userRes;
|
||||
}
|
||||
|
||||
async function getUserByEmail(email) {
|
||||
let userRes = await userModel.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
},
|
||||
});
|
||||
if (!userRes) return false;
|
||||
return userRes;
|
||||
}
|
||||
|
||||
//api/v0/auth/register
|
||||
/* Registering new user
|
||||
1) req.body is taken from html form or wtv
|
||||
@ -71,9 +80,11 @@ async function loginUser(user) {
|
||||
if (!match) return false;
|
||||
//console.log('loginUser', userRes.id, userRes.username);
|
||||
|
||||
//generate token and permission and experiation time
|
||||
const currentTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
let token = await addToken(userRes.id , "canRead" , currentTime);
|
||||
//generate token and permission and experiation time + 30 mins
|
||||
//let tokenToLive = moment().add(30, 'minutes').format();
|
||||
let currentDate = new Date();
|
||||
let tokenToLive = new Date(currentDate.getTime() + 30 * 60000);
|
||||
let token = await addToken(userRes.id , "canRead" , "isNotKey" , tokenToLive);
|
||||
return { token: token, userid: userRes.id, username: userRes.username };
|
||||
}
|
||||
|
||||
@ -130,9 +141,54 @@ async function updateProfile(user, body) {
|
||||
}
|
||||
}
|
||||
|
||||
async function checkEmail(email) {
|
||||
let emailRes = await userModel.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
},
|
||||
});
|
||||
if (!emailRes) return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
async function checkEmailDetails(email) {
|
||||
let emailRes = await userModel.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
},
|
||||
});
|
||||
if (!emailRes) return false;
|
||||
return emailRes;
|
||||
|
||||
}
|
||||
|
||||
async function resetPass(userid , data ){
|
||||
let hashed = await hash(data.password);
|
||||
let updateUser = await userModel.update(
|
||||
{
|
||||
password: hashed,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
id: userid,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!updateUser) return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
getUserByID,
|
||||
getUserByEmail,
|
||||
addUser,
|
||||
loginUser,
|
||||
updateProfile,
|
||||
checkEmail,
|
||||
checkEmailDetails,
|
||||
resetPass,
|
||||
|
||||
};
|
@ -1,5 +1,9 @@
|
||||
var validator = require("validator");
|
||||
|
||||
/*
|
||||
All the validation functions are used by database model.
|
||||
*/
|
||||
|
||||
const dateRegex = /^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
|
||||
|
||||
function isValidDateString(value) {
|
||||
|
@ -1,68 +1,48 @@
|
||||
const { tokenModel } = require("../database/model/tokenModel");
|
||||
const { userModel } = require("../database/model/userModel");
|
||||
const { compareHash } = require("../functions/bcrypt");
|
||||
const { checkToken } = require("../functions/api");
|
||||
const { isValid } = require("../functions/isValid");
|
||||
const { getTokenByToken } = require("../functions/api");
|
||||
|
||||
const permissionError = new Error('PermissionError')
|
||||
permissionError.name = "Inadequate Permission Error"
|
||||
permissionError.status = 401
|
||||
permissionError.message = "Inadequate permission to complete this response"
|
||||
|
||||
async function auth(req, res, next) {
|
||||
try {
|
||||
const authToken = req.header("auth-token");
|
||||
if (!authToken) {
|
||||
const error = new Error("No Token key was supplied. Invalid request");
|
||||
throw error;
|
||||
const token = await getTokenByToken(req.header("auth-token"));
|
||||
|
||||
if (!token || !token.isValid){
|
||||
throw permissionError;
|
||||
}
|
||||
|
||||
const splitAuthToken = authToken.split("-");
|
||||
const rowid = splitAuthToken[0];
|
||||
const suppliedToken = splitAuthToken.slice(1).join("-");
|
||||
|
||||
const token = await tokenModel.findByPk(rowid, { include: userModel });
|
||||
|
||||
if (!token) {
|
||||
const error = new Error("Token key not found. Invalid request");
|
||||
throw error;
|
||||
}
|
||||
|
||||
const isMatch = await compareHash(suppliedToken, token.token);
|
||||
|
||||
console.log(isMatch);
|
||||
if (!isMatch) {
|
||||
const error = new Error("Token key not found. Invalid request");
|
||||
throw error;
|
||||
}
|
||||
//if token is a match
|
||||
req.token = token;
|
||||
req.user = await token.getUser();
|
||||
const permission = await checkToken(suppliedToken, rowid);
|
||||
|
||||
const route = req.originalUrl.split("?")[0]; // Removing query parameters
|
||||
//if route is from user/ and permission is canRead allow it to do CRUD
|
||||
if (route.includes("/user/") && permission === "canRead") {
|
||||
next();
|
||||
if (route.includes("/user/") || route.includes("/token/") && token.permission === "canRead") {
|
||||
console.log("user route");
|
||||
return next();
|
||||
}
|
||||
if ((req.method === "GET" && permission === "canRead") || (["GET", "POST", "PUT", "DELETE"].includes(req.method) && permission === "canWrite")) {
|
||||
next();
|
||||
}
|
||||
|
||||
if (!isValid(token.expiration)){
|
||||
req.token.destroy();
|
||||
throw new Error("Token expired");
|
||||
if ((req.method === "GET" && token.permission === "canRead")){
|
||||
console.log("wtf you shldnt be here");
|
||||
return next();
|
||||
}
|
||||
if (["GET", "POST", "PUT", "DELETE"].includes(req.method) && token.permission === "canWrite") {
|
||||
console.log("wtf you shldnt be here");
|
||||
return next();
|
||||
}
|
||||
/*
|
||||
if ((req.method === "GET" && token.permission === "canRead") ||
|
||||
(["GET", "POST", "PUT", "DELETE"].includes(req.method) && token.permission === "canWrite")) {
|
||||
return next();
|
||||
}
|
||||
*/
|
||||
|
||||
throw permissionError
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { auth };
|
||||
|
||||
/*
|
||||
else {
|
||||
const error = new Error("Insufficient permission");
|
||||
error.status = 401;
|
||||
throw error;
|
||||
}
|
||||
|
||||
*/
|
@ -2,6 +2,7 @@ const nodemailer = require("nodemailer");
|
||||
const dotenv = require("dotenv");
|
||||
const path = require('path')
|
||||
require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
|
||||
//.env
|
||||
|
||||
let transporter = nodemailer.createTransport({
|
||||
service: 'gmail',
|
||||
|
@ -3812,6 +3812,7 @@
|
||||
|
||||
.card-text {
|
||||
color: #000000;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* edit profile */
|
||||
|
@ -528,22 +528,22 @@ body.one-content-column-version .content thead {
|
||||
|
||||
|
||||
.generate-key-button {
|
||||
float: right; /* Align the button to the right */
|
||||
margin-right: 85%;
|
||||
margin-top: -40px; /* Adjust the margin-top value based on your layout */
|
||||
/* Add any additional styling you want for the button */
|
||||
}
|
||||
|
||||
#content-get-api .generate-key-button {
|
||||
margin-top: -40px;
|
||||
margin-left: 25px;
|
||||
background-color: #4caf50; /* Green background color */
|
||||
color: white; /* White text color */
|
||||
color: #ffffff;
|
||||
padding: 5px 11px; /* Padding for the button */
|
||||
border: none; /* Remove button border */
|
||||
border-radius: 5px; /* Add border-radius for rounded corners */
|
||||
cursor: pointer; /* Add pointer cursor on hover */
|
||||
font-size: 14px; /* Font size */
|
||||
}
|
||||
.api-form {
|
||||
margin-top: 20px;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
#content-get-api .generate-key-button:hover {
|
||||
.generate-key-button:hover {
|
||||
background-color: #45a049; /* Darker green on hover */
|
||||
}
|
||||
}
|
||||
|
||||
|
61
consumerWebsite/public/css/data.css
Normal file
61
consumerWebsite/public/css/data.css
Normal file
@ -0,0 +1,61 @@
|
||||
.air-quality-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 800px;
|
||||
/* Adjust width as needed */
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: space-around; /* Change this value to adjust the spacing */
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
|
||||
#download-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 20px; /* Adjust the margin-top for spacing */
|
||||
|
||||
}
|
||||
|
||||
button {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.graphbutton-container {
|
||||
display: flex;
|
||||
justify-content: center; /* Center the buttons horizontally */
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
button#barButton,
|
||||
button#lineButton {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -100,3 +100,16 @@ body {
|
||||
background-color: #213f6d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.custom-btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px; /* Adjust padding as needed */
|
||||
background-color: #3498db; /* Change background color */
|
||||
color: #ffffff; /* Change text color */
|
||||
text-decoration: none;
|
||||
border-radius: 5px; /* Add rounded corners if desired */
|
||||
}
|
||||
|
||||
.custom-btn:hover {
|
||||
background-color: #2980b9; /* Change background color on hover */
|
||||
}
|
||||
|
@ -15,12 +15,11 @@ body {
|
||||
}
|
||||
.wrapper {
|
||||
position: relative;
|
||||
max-width: 470px;
|
||||
max-width: 600px; /* Increase the maximum width */
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
padding: 20px
|
||||
30px
|
||||
120px;
|
||||
padding: 40px 50px 150px; /* Adjust the padding */
|
||||
|
||||
background: #4070f4;
|
||||
box-shadow: 0
|
||||
5px
|
||||
|
@ -147,13 +147,13 @@ button.btn-secondary:hover{
|
||||
border: none;
|
||||
}
|
||||
.services-bar .card h4.card-header{
|
||||
background-color: #4e3914;
|
||||
background-color: #ffffff;
|
||||
color: #4eae3a;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.services-bar .card .card-footer{
|
||||
background-color: #4e3914;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.about-main{
|
||||
padding: 30px 0px;
|
||||
|
@ -186,9 +186,7 @@ app.auth = (function (app) {
|
||||
function isLoggedIn(callback) {
|
||||
|
||||
if (getToken()) {
|
||||
console.log("you shldnt appear at all");
|
||||
return app.api.get("user/me", function (error, data) {
|
||||
console.log(error, data);
|
||||
if (!error) app.auth.user = data;
|
||||
return callback(error, data);
|
||||
});
|
||||
@ -245,6 +243,10 @@ app.auth = (function (app) {
|
||||
location.replace(`/profile`);
|
||||
}
|
||||
|
||||
function checkEmailRedirect(){
|
||||
location.replace(`/checkemail`);
|
||||
}
|
||||
|
||||
return {
|
||||
getToken: getToken,
|
||||
setToken: setToken,
|
||||
@ -254,6 +256,7 @@ app.auth = (function (app) {
|
||||
logInRedirect,
|
||||
homeRedirect,
|
||||
profileRedirect,
|
||||
checkEmailRedirect,
|
||||
};
|
||||
})(app);
|
||||
|
||||
@ -281,13 +284,15 @@ function formAJAX(btn, del) {
|
||||
var $form = $(btn).closest("[action]"); // gets the 'form' parent
|
||||
var formData = $form.find("[name]").serializeObject(); // builds query formDataing
|
||||
var method = $form.attr("method") || "post";
|
||||
console.log("Form data", formData);
|
||||
console.log("Form method", method);
|
||||
|
||||
app.util.actionMessage("Loading...", $form, "info");
|
||||
|
||||
//console.log('Data being sent to', $form.attr('action'), formData)
|
||||
|
||||
app.api[method]($form.attr("action"), formData, function (error, data) {
|
||||
//console.log('Data back from the server', error, data)
|
||||
console.log('Data back from the server', error, data)
|
||||
app.util.actionMessage(data.message, $form, error ? "danger" : "success"); //re-populate table
|
||||
if (!error) {
|
||||
$form.trigger("reset");
|
||||
|
121
consumerWebsite/public/js/data.js
Normal file
121
consumerWebsite/public/js/data.js
Normal file
@ -0,0 +1,121 @@
|
||||
//getting button from DOM id
|
||||
const buttons = document.querySelectorAll(".button-container button");
|
||||
const weeklybuttons = document.querySelectorAll(".weeklybutton-container button");
|
||||
const queryButton = document.getElementById("querybutton-container");
|
||||
|
||||
$(document).ready(async function () {
|
||||
//https://stackoverflow.com/questions/9045868/javascript-date-getweek
|
||||
Date.prototype.getWeek = function () {
|
||||
var onejan = new Date(this.getFullYear(), 0, 1);
|
||||
var today = new Date(this.getFullYear(), this.getMonth(), this.getDate());
|
||||
var dayOfYear = (today - onejan + 86400000) / 86400000;
|
||||
return Math.ceil(dayOfYear / 7);
|
||||
};
|
||||
let date = new Date();
|
||||
var week = date.getWeek() - 1; // Subtracting 1 to get the actual week number
|
||||
var today = date.getDate();
|
||||
var month = date.getMonth() + 1; // Adding 1 to get the actual month number
|
||||
var year = date.getFullYear();
|
||||
|
||||
// Initialize initialData for chart
|
||||
const initialData = {
|
||||
labels: [], // Array to store timestamps
|
||||
datasets: [
|
||||
{
|
||||
label: "Average Measurement Data",
|
||||
data: [], // Array to store measurements objects
|
||||
backgroundColor: "green",
|
||||
borderColor: "green",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Create Chart.js chart
|
||||
const ctx = document.getElementById("DailyDataChart").getContext("2d");
|
||||
const chart = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: initialData,
|
||||
options: {
|
||||
responsive: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: "Average measurement metric data by Hour",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Function to update chart data based on button clicked
|
||||
function updateChart(metric) {
|
||||
const queryParams = `sensor-data/data?month=${month}&week=${week}&day=${today}`;
|
||||
app.api.get(queryParams, function (error, data) {
|
||||
// Clear previous data
|
||||
initialData.labels = []; //timestamp
|
||||
initialData.datasets[0].data = []; //measurement data dependinbg on metric
|
||||
|
||||
// Group data by hour and calculate average value
|
||||
const hourlyData = {};
|
||||
for (let row of data) {
|
||||
//each row contains a timestamp and measurement data of each sensor and location
|
||||
const createdAt = new Date(row.createdAt); //set to local time
|
||||
const hourString = new Date(
|
||||
createdAt.getFullYear(),
|
||||
createdAt.getMonth(),
|
||||
createdAt.getDate(),
|
||||
createdAt.getHours()
|
||||
).toISOString();
|
||||
if (!hourlyData[hourString]) {
|
||||
hourlyData[hourString] = [];
|
||||
}
|
||||
hourlyData[hourString].push(row.measurement[metric]); //pushing measurement data into hourlyData
|
||||
}
|
||||
|
||||
// Calculate average value for each hour
|
||||
//console.log(hourlyData); //24 values for each hour of the day
|
||||
for (let hourString in hourlyData) {
|
||||
const averageValue =
|
||||
hourlyData[hourString].reduce((acc, val) => acc + val, 0) /
|
||||
hourlyData[hourString].length;
|
||||
initialData.labels.push(
|
||||
new Date(hourString).toLocaleString("en-US", {
|
||||
timeZone: "UTC",
|
||||
hour12: false,
|
||||
})
|
||||
);
|
||||
initialData.datasets[0].data.push(averageValue);
|
||||
}
|
||||
|
||||
// Update chart
|
||||
chart.update();
|
||||
});
|
||||
}
|
||||
// Event listeners for buttons
|
||||
document.getElementById("psiButton").addEventListener("click", function () {
|
||||
updateChart("psi");
|
||||
});
|
||||
|
||||
document.getElementById("tempButton").addEventListener("click", function () {
|
||||
updateChart("temperature");
|
||||
});
|
||||
|
||||
document.getElementById("humButton").addEventListener("click", function () {
|
||||
updateChart("humidity");
|
||||
});
|
||||
|
||||
document.getElementById("o3Button").addEventListener("click", function () {
|
||||
updateChart("o3");
|
||||
});
|
||||
|
||||
document.getElementById("no2Button").addEventListener("click", function () {
|
||||
updateChart("no2");
|
||||
});
|
||||
|
||||
document.getElementById("so2Button").addEventListener("click", function () {
|
||||
updateChart("so2");
|
||||
});
|
||||
|
||||
document.getElementById("coButton").addEventListener("click", function () {
|
||||
updateChart("co");
|
||||
});
|
||||
|
||||
|
||||
});
|
@ -1,21 +1,18 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function updateAdditionalInfo(region) {
|
||||
const infoContainer = document.getElementById("additional-info");
|
||||
// Replace the following with actual data retrieval based on the region
|
||||
const aqi = "15";
|
||||
const temperature = "25°C";
|
||||
const humidity = "60%";
|
||||
const pm25 = "10";
|
||||
const pm10 = "20";
|
||||
const so2 = "5";
|
||||
const o3 = "35";
|
||||
const co = "0.5";
|
||||
const no2 = "15";
|
||||
// Replace the following with actual data retrieval based on the region
|
||||
const aqi = "15";
|
||||
const temperature = "25°C";
|
||||
const humidity = "60%";
|
||||
const so2 = "5";
|
||||
const o3 = "35";
|
||||
const co = "0.5";
|
||||
const no2 = "15";
|
||||
|
||||
infoContainer.innerHTML = `
|
||||
infoContainer.innerHTML = `
|
||||
<div class="additional-info-box">
|
||||
<h3>Additional Information - ${region}</h3>
|
||||
<button id="downloadCsvButton">Download CSV</button>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Air Quality Index:</span>
|
||||
<span class="info-value">${aqi}</span>
|
||||
@ -28,14 +25,6 @@ infoContainer.innerHTML = `
|
||||
<span class="info-label">Humidity:</span>
|
||||
<span class="info-value">${humidity}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">PM2.5:</span>
|
||||
<span class="info-value">${pm25}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">PM10:</span>
|
||||
<span class="info-value">${pm10}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">SO2:</span>
|
||||
<span class="info-value">${so2}</span>
|
||||
@ -54,23 +43,16 @@ infoContainer.innerHTML = `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
// Remove the 'active' class from all info-box elements
|
||||
const infoBoxes = document.querySelectorAll('.info-box');
|
||||
infoBoxes.forEach(box => box.classList.remove('active'));
|
||||
|
||||
// Add the 'active' class to the clicked info-box
|
||||
const clickedBox = document.getElementById(region.toLowerCase());
|
||||
clickedBox.classList.add('active');
|
||||
}
|
||||
|
||||
|
||||
|
||||
const defaultRegion = "North";
|
||||
const defaultBox = document.getElementById(defaultRegion.toLowerCase());
|
||||
defaultBox.classList.add('active');
|
||||
const defaultAqi = "--"; // Replace with actual data retrieval
|
||||
updateAdditionalInfo(defaultRegion, defaultAqi);
|
||||
|
||||
|
||||
// Event listeners for each region's info-box
|
||||
document.getElementById("north").addEventListener("click", function () {
|
||||
const northAqi = "--"; // Replace with actual data retrieval
|
||||
@ -117,3 +99,4 @@ document.getElementById("central").addEventListener("click", function () {
|
||||
updateAdditionalInfo("Central");
|
||||
});
|
||||
|
||||
|
||||
|
@ -18,5 +18,11 @@ router.use('/sensor', [auth, APIlogger], require('./sensor.js'));
|
||||
//sensor data route
|
||||
router.use('/sensor-data', [auth, APIlogger], require('./sensorData.js'));
|
||||
|
||||
//apilog route
|
||||
router.use('/apilog', [auth, APIlogger], require('./apilog.js'));
|
||||
|
||||
//latest sensor data to display on dashboard
|
||||
router.use('/latest-sensor-data', [APIlogger], require('./latestsensorData.js'));
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
37
consumerWebsite/routes/apilog.js
Normal file
37
consumerWebsite/routes/apilog.js
Normal file
@ -0,0 +1,37 @@
|
||||
//functions if needed
|
||||
const {
|
||||
getAllLog
|
||||
} = require("../functions/apilog.js");
|
||||
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
//get all
|
||||
router.get("/", async (req, res, next) => {
|
||||
let Res = await getAllLog();
|
||||
res.json(Res);
|
||||
});
|
||||
|
||||
/*
|
||||
//get by route name?
|
||||
router.get("/route/:name", async (req, res, next) => {
|
||||
});
|
||||
|
||||
//get ny status?
|
||||
router.get("/status/:status", async (req, res, next) => {
|
||||
});
|
||||
|
||||
//by method
|
||||
router.get("/method/:method", async (req, res, next) => {
|
||||
});
|
||||
|
||||
//by ip
|
||||
router.get("/ip/:ip", async (req, res, next) => {
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = router;
|
@ -1,6 +1,19 @@
|
||||
const { addUser, loginUser } = require("../functions/user");
|
||||
const {
|
||||
addUser,
|
||||
loginUser,
|
||||
checkEmail,
|
||||
checkEmailDetails,
|
||||
resetPass,
|
||||
} = require("../functions/user");
|
||||
const { sendContactEmail } = require("../functions/nodeMail");
|
||||
const { generateUUID } = require("../functions/generateUUID");
|
||||
const { addPasswordResetToken } = require("../functions/api");
|
||||
const { sendResetPasswordEmail } = require("../functions/nodeMail");
|
||||
const { checkTokenByrowID } = require("../functions/api");
|
||||
|
||||
|
||||
const express = require("express");
|
||||
const { render } = require("ejs");
|
||||
const router = express.Router();
|
||||
|
||||
// /user/register
|
||||
@ -12,12 +25,11 @@ router.post("/register", async (req, res, next) => {
|
||||
error.message = "The user failed to be craated";
|
||||
error.status = 400;
|
||||
return next(error);
|
||||
} else {
|
||||
return res.json({
|
||||
message: "User created successfully",
|
||||
});
|
||||
}
|
||||
else{
|
||||
return res.json({
|
||||
message: "User created successfully",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
@ -29,20 +41,18 @@ router.post("/login", async (req, res, next) => {
|
||||
try {
|
||||
let Res = await loginUser(req.body);
|
||||
if (Res == false) {
|
||||
let error = new Error("User Login Failed");
|
||||
let error = new Error("User Login Failed");
|
||||
error.status = 400;
|
||||
return next(error);
|
||||
} else {
|
||||
//pass res back to form to be set in local storage
|
||||
return res.json({
|
||||
message: "User login successfully",
|
||||
token: Res.token,
|
||||
userid: Res.userid,
|
||||
username: Res.username,
|
||||
});
|
||||
}
|
||||
else{
|
||||
//pass res back to form to be set in local storage
|
||||
return res.json({
|
||||
message: "User login successfully",
|
||||
token: Res.token,
|
||||
userid: Res.userid,
|
||||
username: Res.username,
|
||||
});
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
@ -52,6 +62,87 @@ router.post("/login", async (req, res, next) => {
|
||||
//contact
|
||||
//auth/contact
|
||||
router.post("/contact", async (req, res, next) => {
|
||||
try {
|
||||
//console.log(req.body);
|
||||
let Res = await checkEmail(req.body.email);
|
||||
if (!Res) {
|
||||
let error = new Error("Email not found");
|
||||
error.status = 400;
|
||||
return next(error);
|
||||
} else {
|
||||
//console.log(Res);
|
||||
sendContactEmail(req.body.email, req.body.name, req.body.message);
|
||||
return res.json({
|
||||
message: "Email sent successfully",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
//reset
|
||||
router.post("/checkemail", async (req, res, next) => {
|
||||
try {
|
||||
let Res = await checkEmail(req.body.email);
|
||||
if (!Res) {
|
||||
let error = new Error("Email not found");
|
||||
error.status = 400;
|
||||
return next(error);
|
||||
} else {
|
||||
//user info lookup
|
||||
let data = await checkEmailDetails(req.body.email);
|
||||
//console.log(data);
|
||||
//token generation and insert into token table
|
||||
let token = await generateUUID();
|
||||
|
||||
let tokenRes = await addPasswordResetToken(data, token);
|
||||
|
||||
//email user with temp token link
|
||||
if (!tokenRes) return false;
|
||||
|
||||
//apend table id to token
|
||||
token = tokenRes + "-" + token;
|
||||
|
||||
//email logic to send reset password link
|
||||
sendResetPasswordEmail(req.body.email, token);
|
||||
|
||||
return res.json({
|
||||
message: "Reset Password Link has successfully sent to your email!",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//reset password
|
||||
router.post("/resetpassword/:token", async (req, res, next) => {
|
||||
console.log(req.body);
|
||||
console.log(req.params.token);
|
||||
|
||||
//if token is valid
|
||||
let tokenRes = await checkTokenByrowID(req.params.token);
|
||||
|
||||
if (!tokenRes) {
|
||||
let error = new Error("Token not found");
|
||||
error.status = 400;
|
||||
return next(error);
|
||||
}
|
||||
//token is valid and reset password
|
||||
else{
|
||||
let Res = await resetPass(tokenRes.userid, req.body);
|
||||
if (!Res) return false;
|
||||
else{
|
||||
res.json({
|
||||
message: "Password reset successfully",
|
||||
});
|
||||
tokenRes.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
21
consumerWebsite/routes/latestsensorData.js
Normal file
21
consumerWebsite/routes/latestsensorData.js
Normal file
@ -0,0 +1,21 @@
|
||||
const {
|
||||
getLatestData,
|
||||
|
||||
} = require("../functions/sensorData");
|
||||
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/data", async (req, res, next) => {
|
||||
try {
|
||||
console.log(req.query);
|
||||
const data = await getLatestData();
|
||||
res.status(200).json(data);
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
@ -2,6 +2,7 @@ const {
|
||||
addLocation,
|
||||
getLocation,
|
||||
getLocationById,
|
||||
getLocationByName,
|
||||
updateLocation,
|
||||
deleteLocation,
|
||||
} = require("../functions/location");
|
||||
@ -58,6 +59,19 @@ router.delete("/delete", async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
//get location by name
|
||||
router.get("/name/:name", async (req, res, next) => {
|
||||
try {
|
||||
//get params
|
||||
const { name } = req.params;
|
||||
const location = await getLocationByName(name);
|
||||
res.status(200).json(location);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
//get location by id
|
||||
|
@ -1,4 +1,5 @@
|
||||
"use strict";
|
||||
const { checkTokenByrowID } = require("../functions/api");
|
||||
|
||||
var router = require("express").Router();
|
||||
|
||||
@ -33,16 +34,26 @@ router.get("/forgotpassword", function (req, res, next) {
|
||||
res.render("forgotpassword");
|
||||
});
|
||||
|
||||
//resetted password page
|
||||
//resetting password page
|
||||
router.get("/resetpassword", function (req, res, next) {
|
||||
res.render("resetpassword");
|
||||
});
|
||||
|
||||
//check email page
|
||||
router.get("/checkemail", function (req, res, next) {
|
||||
res.render("checkemail");
|
||||
});
|
||||
|
||||
//contact page
|
||||
router.get("/contact", function (req, res, next) {
|
||||
res.render("contact");
|
||||
});
|
||||
|
||||
//data page
|
||||
router.get("/viewdata", function (req, res, next) {
|
||||
res.render("viewdata");
|
||||
});
|
||||
|
||||
//api doc
|
||||
router.get("/api", function (req, res, next) {
|
||||
res.render("api");
|
||||
@ -53,4 +64,31 @@ router.get("/sensor-data", function (req, res, next) {
|
||||
res.render("sensor-data");
|
||||
});
|
||||
|
||||
//reset password page
|
||||
router.get("/resetpassword/:token", async (req, res, next) => {
|
||||
try{
|
||||
//pass token to reset password page
|
||||
//console.log(req.params.token);
|
||||
|
||||
//check if token is valid
|
||||
let tokenRes = await checkTokenByrowID(req.params.token);
|
||||
|
||||
if (!tokenRes) {
|
||||
let error = new Error("Token not found");
|
||||
error.status = 400;
|
||||
return next(error);
|
||||
}
|
||||
else {
|
||||
let token = req.params.token;
|
||||
console.log(token);
|
||||
res.render("resetpassword", { token: token });
|
||||
}
|
||||
|
||||
}catch(error){
|
||||
console.error(error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
@ -3,6 +3,7 @@ const {
|
||||
addSensor,
|
||||
updateSensor,
|
||||
deleteSensor,
|
||||
getSensorByName,
|
||||
getSensorById
|
||||
} = require("../functions/sensor.js");
|
||||
|
||||
@ -52,6 +53,16 @@ router.delete("/delete", async (req, res, next) => {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/name/:name", async (req, res, next) => {
|
||||
try {
|
||||
const sensor = await getSensorByName(req.params.name);
|
||||
res.status(200).json(sensor);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
router.get("/:id", async (req, res, next) => {
|
||||
try {
|
||||
const sensor = await getSensorById(req.params.id);
|
||||
|
@ -23,8 +23,9 @@ router.get("/", async (req, res, next) => {
|
||||
|
||||
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);
|
||||
//locationid
|
||||
const { sensorid , locationid , measurement } = req.body;
|
||||
let data = await addSensorData(sensorid , locationid , measurement);
|
||||
res.json({ message: "SensorData " + data.id + " added", ...data });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -34,8 +35,8 @@ router.post("/new", async (req, res, next) => {
|
||||
|
||||
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);
|
||||
const { id , sensorid , locationid , measurement } = req.body;
|
||||
await updateSensorData(id, sensorid , locationid , measurement);
|
||||
res.status(200).json({ message: "SensorData " + id + " updated" });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -55,7 +56,6 @@ router.delete("/delete", async (req, res, next) => {
|
||||
});
|
||||
router.get("/data", async (req, res, next) => {
|
||||
try {
|
||||
console.log(req.query);
|
||||
const data = await getData(req.query);
|
||||
res.status(200).json(data);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
const { addToken } = require("../functions/api");
|
||||
|
||||
const { addToken, checkToken } = require("../functions/api");
|
||||
const { checkEmail, getUserByEmail } = require("../functions/user");
|
||||
const { sendTokenEmail } = require("../functions/nodeMail");
|
||||
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
@ -11,18 +12,43 @@ const router = express.Router();
|
||||
4) store the api key in database
|
||||
*/
|
||||
//token/new
|
||||
//curl localhost:3000/api/v0/token/new -H "Content-Type: application/json" -X POST -d
|
||||
//curl localhost:3000/api/v0/token/new -H "Content-Type: application/json" -X POST -d
|
||||
//'{"userid": "5", "permission": "canRead" ,}'
|
||||
router.post("/new", async (req, res, next) => {
|
||||
try {
|
||||
const token = await addToken(req.body.userid, req.body.permission , "2204-01-24 07:34:36" );
|
||||
res.json({token: token});
|
||||
//console.log(req.body);
|
||||
const Res = await checkEmail(req.body.email);
|
||||
if (!Res) {
|
||||
let error = new Error("Email not found");
|
||||
error.status = 400;
|
||||
return next(error);
|
||||
} else {
|
||||
let userid = await getUserByEmail(req.body.email);
|
||||
if (!userid) return false;
|
||||
|
||||
const tokenRes = await checkToken(userid.id);
|
||||
if (tokenRes.isKey !== "null" && tokenRes.isKey !== "isKey") {
|
||||
//allow user to create token
|
||||
const token = await addToken(
|
||||
userid.id,
|
||||
"canRead",
|
||||
"isKey",
|
||||
"2204-01-24 07:34:36"
|
||||
);
|
||||
if (!token) return false;
|
||||
sendTokenEmail(req.body.email, token);
|
||||
res.json({
|
||||
message: "Token generated successfully and sent to email",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//const token = await addToken(req.body.userid, "canRead" , "2204-01-24 07:34:36" );
|
||||
//res.json({token: token});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
@ -7,8 +7,8 @@ const router = express.Router();
|
||||
//getbyid
|
||||
router.get("/me", async function (req, res, next) {
|
||||
try {
|
||||
let user = await getUserByID(req.user);
|
||||
console.log(user);
|
||||
let user = await getUserByID(req.user); //req.user assigned in middleware!
|
||||
//console.log(user);
|
||||
res.json({
|
||||
user: user,
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -77,11 +77,12 @@
|
||||
</div>
|
||||
<div class="container text-center">
|
||||
<br>
|
||||
<p>All Rights Reserved. © 2023 <a href="/">EcoSaver</a>
|
||||
<p>All Rights Reserved. © 2024 <a href="/">EcoSaver</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="js/search.js"></script>
|
||||
<script src="/js/search.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
19
consumerWebsite/views/checkemail.ejs
Normal file
19
consumerWebsite/views/checkemail.ejs
Normal file
@ -0,0 +1,19 @@
|
||||
<%- include('logintop') %>
|
||||
|
||||
<body>
|
||||
<section class="wrapper">
|
||||
<div class="form">
|
||||
<!-- -->
|
||||
<div class="error-contents">
|
||||
<h3>Please check your email for the reset password link</h3>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<br>
|
||||
<a>Dont have an account?</a> <a href="/login">Sign Up</a>
|
||||
<br>
|
||||
<a>Already have an account?</a> <a href="/login">Login</a>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
@ -42,7 +42,8 @@
|
||||
</p>
|
||||
<p>
|
||||
<abbr title="Email">E</abbr>:
|
||||
<a href="mailto:name@example.com">leongdingxuan@gmail.com
|
||||
<a href="mailto:name@example.com">ecosaverx@gmail.com
|
||||
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
@ -55,17 +56,18 @@
|
||||
<!-- Contact Form -->
|
||||
<!-- In order to set the email address and subject line for the contact form go to the bin/contact_me.php file. -->
|
||||
<div class="row">
|
||||
<div class="form contact iot-card">
|
||||
<div class="col-lg-8 mb-4 contact-left">
|
||||
<h3>Send us a Message</h3>
|
||||
<form id="form">
|
||||
<input type="hidden" name="access_key" value="">
|
||||
<form action="auth/contact" onsubmit="formAJAX(this)">
|
||||
<div class="card-header shadow actionMessage" style="display:none"></div>
|
||||
<div class="mb-3">
|
||||
<label for="name">Full Name</label>
|
||||
<input type="text" name="name" id="name" required>
|
||||
<input type="text" name="name" id="name" required pattern="^[a-zA-Z]{3,}( {1,2}[a-zA-Z]{3,}){0,}$">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email">Email address</label>
|
||||
<input type="email" name="email" id="email" required>
|
||||
<input type="email" name="email" id="email" required pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="message">Message</label>
|
||||
@ -75,6 +77,8 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
@ -4,10 +4,9 @@
|
||||
<section class="wrapper">
|
||||
<div class="form">
|
||||
<header>Reset Password</header>
|
||||
<form action="/resetpassword">
|
||||
<input type="text" id="email" placeholder="Email" required />
|
||||
<input type="password" id="password" placeholder="Password" required />
|
||||
<input type="password" id="confirmPassword" placeholder="Confirm Password" required />
|
||||
<form action="auth/checkemail" onsubmit="formAJAX(this) "evalAJAX="app.auth.checkEmailRedirect();">
|
||||
<input type="email" name="email" placeholder="Email" required
|
||||
pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" />
|
||||
<input type="submit" value="Reset Password" />
|
||||
</form>
|
||||
<br>
|
||||
|
@ -1,11 +1,60 @@
|
||||
<%- include('top') %>
|
||||
<script>
|
||||
//call socket.io
|
||||
app.socket.on("sensorData:new", function (data) {
|
||||
console.log("new data!!")
|
||||
console.log(data);
|
||||
<script type="text/javascript">
|
||||
function extractNumbers(str) {
|
||||
if (typeof str === 'number') return str;
|
||||
return str.match(/\d+/)[0];
|
||||
}
|
||||
|
||||
function calculateAverage(numbers) {
|
||||
if (numbers.length === 0) return 0
|
||||
const sum = numbers.reduce((acc, num) => acc + num, 0);
|
||||
return sum / numbers.length;
|
||||
}
|
||||
const values = {
|
||||
psi: [],
|
||||
humidity: [],
|
||||
temperature: [],
|
||||
windspeed: [],
|
||||
};
|
||||
|
||||
function parseRowToTemplace(row) {
|
||||
values.psi.unshift(extractNumbers(row.measurement.psi))
|
||||
values.humidity.unshift(extractNumbers(row.measurement.humidity))
|
||||
values.temperature.unshift(extractNumbers(row.measurement.temperature))
|
||||
values.windspeed.unshift(extractNumbers(row.measurement.windspeed))
|
||||
|
||||
return {
|
||||
average: {
|
||||
psi: parseInt(calculateAverage(values.psi)),
|
||||
humidity: parseInt(calculateAverage(values.humidity)),
|
||||
temperature: parseInt(calculateAverage(values.temperature)),
|
||||
windspeed: parseInt(calculateAverage(values.windspeed)),
|
||||
},
|
||||
latest: {
|
||||
psi: values.psi[0],
|
||||
humidity: values.humidity[0],
|
||||
temperature: values.temperature[0],
|
||||
windspeed: values.windspeed[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(async function () {
|
||||
|
||||
app.api.get('latest-sensor-data/data', function (error, data) {
|
||||
for (let row of data) {
|
||||
//console.log(row);
|
||||
$.scope.LatestSensorData.update(parseRowToTemplace(row));
|
||||
}
|
||||
});
|
||||
|
||||
//call socket.io to get live data
|
||||
app.socket.on("sensorData:new", function (data) {
|
||||
$.scope.LatestSensorData.update(parseRowToTemplace(data));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@ -53,56 +102,60 @@
|
||||
</header>
|
||||
<!-- Page Content -->
|
||||
<div class="container">
|
||||
<div class="services-bar">
|
||||
<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 class="services-bar" jq-repeat="LatestSensorData">
|
||||
<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"> Average: {{average.psi}} PSI</p>
|
||||
<p class="card-text display-4"> Latest: {{latest.psi}} PSI</p>
|
||||
</div>
|
||||
</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 class="card-footer">
|
||||
<a href="/learnmore" class="btn btn-primary">Learn More</a>
|
||||
</div>
|
||||
</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° - 37°</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="/learnmore" class="btn btn-primary">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
</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"> Average: {{average.humidity}} %</p>
|
||||
<p class="card-text display-4"> Latest: {{latest.humidity}} %</p>
|
||||
</div>
|
||||
</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 class="card-footer">
|
||||
<a href="/learnmore" class="btn btn-primary">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</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"> Average: {{average.temperature}}°</p>
|
||||
<p class="card-text display-4"> Latest: {{latest.temperature}}°</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="/learnmore" class="btn btn-primary">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 mb-4">
|
||||
<div class="card">
|
||||
<h4 class="card-header">Wind Speed</h4>
|
||||
<div class="card-body text-center">
|
||||
<p class="card-text display-4"> Average: {{average.windspeed}} Km/h</p>
|
||||
<p class="card-text display-4"> Latest: {{latest.windspeed}} Km/h</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="/learnmore" class="btn btn-primary">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
<!-- About Section -->
|
||||
|
@ -38,7 +38,11 @@
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
|
||||
|
||||
<script src="js/learnmore.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
|
||||
<%- include('bot') %>
|
||||
|
@ -6,14 +6,14 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link rel="shortcut icon" type="images/logo.ico" href="images/logo.ico" />
|
||||
<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">
|
||||
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link rel="stylesheet" href="css/sp.css" />
|
||||
<link rel="stylesheet" href="/css/sp.css" />
|
||||
|
||||
|
||||
<!-- jQuery library -->
|
||||
@ -27,10 +27,10 @@
|
||||
<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>
|
||||
<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>
|
||||
<script src="/js/app.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
@ -44,25 +44,29 @@
|
||||
$(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');
|
||||
if (!error) {
|
||||
$("#cl-logout-button").show("fast");
|
||||
$("#cl-viewdata-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>
|
||||
</script>
|
||||
|
||||
|
||||
<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" />
|
||||
<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">
|
||||
@ -79,11 +83,16 @@
|
||||
<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-viewdata-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/viewdata"
|
||||
style="display: none">
|
||||
<i class="fas fa-sign-out"></i>Data
|
||||
</a>
|
||||
<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>
|
||||
<a id="cl-profile-button" class="btn btn-outline-info my-2 my-sm-0" href="/profile"
|
||||
style="display: none;">
|
||||
<i class="fas fa-sign-out"></i>
|
||||
@ -103,5 +112,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</nav>
|
@ -1,4 +1,8 @@
|
||||
<%- include('top') %>
|
||||
<script type="text/javascript">
|
||||
// Require login to see this page.
|
||||
app.auth.forceLogin()
|
||||
</script>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
@ -6,9 +10,9 @@
|
||||
<div class="profile">
|
||||
<!-- <li jq-repeat="getUsername" class="nav-item"> -->
|
||||
<div class="edit_information" jq-repeat="getUserDetails">
|
||||
<div class="card-header shadow actionMessage" style="display:none"></div>
|
||||
<form id="profileForm" action="user/update" method="put" onsubmit="formAJAX(this)"
|
||||
evalAJAX="app.auth.profileRedirect();">
|
||||
<div class="card-header shadow actionMessage" style="display:none"></div>
|
||||
<h3 class="text-center">Edit Personal Information</h3>
|
||||
<br>
|
||||
<div class="row">
|
||||
|
@ -1,39 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<%- include('logintop') %>
|
||||
|
||||
<head>
|
||||
<title>Your password has been resetted</title>
|
||||
<link href="css/reset.css" rel="stylesheet">
|
||||
</head>
|
||||
<section class="wrapper">
|
||||
<div class="form">
|
||||
<header>Reset Password</header>
|
||||
<div class="card-header shadow actionMessage" style="display:none"></div>
|
||||
<!-- <form action="auth/resetpassword/<%= token.token%>" method="post" onsubmit="formAJAX(this)"> -->
|
||||
<form action="auth/resetpassword/<%= token%>" method="post" onsubmit="formAJAX(this)" evalAJAX="app.auth.logInRedirect();">
|
||||
<input type="password" id="password" name="password" placeholder="Password" required />
|
||||
<input type="password" id="confirmPassword" name="confirmPassword" placeholder="Confirm Password"
|
||||
required />
|
||||
<input type="submit" value="Reset Password" />
|
||||
</form>
|
||||
<br>
|
||||
<a>Dont have an account?</a> <a href="/login">Sign Up</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<body>
|
||||
|
||||
<table class="main-header">
|
||||
<tr>
|
||||
<td style="text-align: center;">
|
||||
<img src="images/passwordreset.png" alt="Image">
|
||||
<h1>Your password has been resetted</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<script>
|
||||
//both password fields must match
|
||||
var password = document.getElementById("password");
|
||||
var confirm_password = document.getElementById("confirmPassword");
|
||||
|
||||
<table class="content-section">
|
||||
<tr>
|
||||
<td>
|
||||
<p>Hello,</p>
|
||||
<p>Please check your email to reset your password.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
function validatePassword() {
|
||||
var passwordValue = password.value;
|
||||
|
||||
<table class="footer">
|
||||
<tr>
|
||||
<td>
|
||||
<p>© 2023 EcoSaver</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
// Strong password regex pattern
|
||||
var strongPasswordPattern = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
|
||||
|
||||
if (passwordValue != confirm_password.value) {
|
||||
confirm_password.setCustomValidity("Passwords Don't Match");
|
||||
} else if (!strongPasswordPattern.test(passwordValue)) {
|
||||
confirm_password.setCustomValidity("Password must be at least 8 characters long and include at least one letter, one number, and one special character.");
|
||||
} else {
|
||||
confirm_password.setCustomValidity('');
|
||||
}
|
||||
}
|
||||
|
||||
password.onchange = validatePassword;
|
||||
confirm_password.onkeyup = validatePassword;
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
<%- 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 () {
|
||||
app.api.get('sensor-data/data?order=DESC&limit=40', function(error, data){
|
||||
$.scope.sensorData.push(...data);
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
<%- include('bot') %>
|
@ -4,7 +4,6 @@
|
||||
//app.auth.redirectIfLoggedIn();
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<section class="wrapper">
|
||||
<div class="form signup iot-card">
|
||||
<!--<div class="form signup card" -->
|
||||
@ -67,7 +66,7 @@
|
||||
confirm_password.onkeyup = validatePassword;
|
||||
|
||||
|
||||
|
||||
|
||||
const wrapper = document.querySelector(".wrapper"),
|
||||
signupHeader = document.querySelector(".signup header"),
|
||||
loginHeader = document.querySelector(".login header");
|
||||
@ -83,4 +82,4 @@
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
@ -9,7 +9,7 @@
|
||||
<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" />
|
||||
<link rel="shortcut icon" type="images/logo.ico" href="/images/logo.ico" />
|
||||
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
@ -17,8 +17,8 @@
|
||||
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" />
|
||||
<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 />
|
||||
@ -42,13 +42,14 @@
|
||||
</script>
|
||||
<!-- socket.io scriot -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.2.0/socket.io.min.js"></script>
|
||||
|
||||
<!-- fancy table cdn -->
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/jquery.fancytable/dist/fancyTable.min.js"></script> -->
|
||||
|
||||
<!-- jq-repeat -->
|
||||
<script src="js/jq-repeat.js"></script>
|
||||
<script src="/js/jq-repeat.js"></script>
|
||||
|
||||
<!-- jquery app.js -->
|
||||
<script src="js/app.js"></script>
|
||||
<!-- jquery public app.js -->
|
||||
<script src="/js/app.js"></script>
|
||||
</head>
|
||||
<!-- javascript function to check if user is auth -->
|
||||
<script>
|
||||
@ -57,13 +58,12 @@
|
||||
//check if user is logged in
|
||||
app.auth.isLoggedIn(function (error, data) {
|
||||
if (!error) {
|
||||
console.log(error);
|
||||
$.scope.getUsername.update(data);
|
||||
|
||||
if (location.pathname == "/profile") {
|
||||
$.scope.getUserDetails.update(data);
|
||||
}
|
||||
$("#cl-logout-button").show("fast");
|
||||
$("#cl-viewdata-button").show("fast");
|
||||
$("#cl-api-button").show("fast");
|
||||
$("#cl-profile-button").show("fast");
|
||||
$("#cl-login-button").hide("fast");
|
||||
@ -80,7 +80,7 @@
|
||||
<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" />
|
||||
<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">
|
||||
@ -100,14 +100,13 @@
|
||||
<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-viewdata-button" class="btn btn-outline-info btn-sm my-2 my-sm-0" href="/viewdata"
|
||||
style="display: none">
|
||||
<i class="fas fa-sign-out"></i>Data
|
||||
</a>
|
||||
|
||||
<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
|
||||
@ -133,4 +132,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
259
consumerWebsite/views/viewdata.ejs
Normal file
259
consumerWebsite/views/viewdata.ejs
Normal file
@ -0,0 +1,259 @@
|
||||
<%- include('top') %>
|
||||
<link href="css/data.css" rel="stylesheet" />
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script type="text/javascript">
|
||||
// Require login to see this page.
|
||||
app.auth.forceLogin()
|
||||
|
||||
//get Location data
|
||||
app.api.get('location', function (error, data) {
|
||||
for (let row in data){
|
||||
//format time to local time than push
|
||||
data[row].createdAt = new Date(data[row].createdAt).toLocaleString();
|
||||
data[row].updatedAt = new Date(data[row].updatedAt).toLocaleString();
|
||||
$.scope.LocationTable.push(data[row]);
|
||||
}
|
||||
})
|
||||
|
||||
//get sensor data
|
||||
app.api.get('sensor', function (error, data) {
|
||||
for (let row in data){
|
||||
//format time to local time than push
|
||||
data[row].createdAt = new Date(data[row].createdAt).toLocaleString();
|
||||
data[row].updatedAt = new Date(data[row].updatedAt).toLocaleString();
|
||||
$.scope.SensorTable.push(data[row]);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="air-quality-container">
|
||||
<!-- header -->
|
||||
<div class="header-container">
|
||||
<h1>Daily Air Quality Data Chart</h1>
|
||||
</div>
|
||||
<br>
|
||||
<div class="chart-container">
|
||||
<canvas id="DailyDataChart"></canvas>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<button id="psiButton">PSI</button>
|
||||
<button id="tempButton">Temperature</button>
|
||||
<button id="humButton">Humidity</button>
|
||||
<button id="o3Button">O3</button>
|
||||
<button id="no2Button">NO2</button>
|
||||
<button id="so2Button">SO2</button>
|
||||
<button id="coButton">CO</button>
|
||||
</div>
|
||||
<br>
|
||||
<div class="header-container">
|
||||
<h1>Location Table</h1>
|
||||
</div>
|
||||
<table id="LocationTable" class="table table-striped LocationTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Location Name</th>
|
||||
<th>Added_By</th>
|
||||
<th>Description</th>
|
||||
<th>CreatedAt</th>
|
||||
<th>UpdatedAt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Table Content Goes Here -->
|
||||
<tr jq-repeat="LocationTable">
|
||||
<td>{{id}}</td>
|
||||
<td>{{name}}</td>
|
||||
<td>{{added_by}}</td>
|
||||
<td>{{description}}</td>
|
||||
<td>{{createdAt}}</td>
|
||||
<td>{{updatedAt}}</td>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="header-container">
|
||||
<h1>Sensor Table</h1>
|
||||
</div>
|
||||
<table id="sensorTable" class="table table-striped sensorTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Sensor Name</th>
|
||||
<th>Added_By</th>
|
||||
<th>Mac Address</th>
|
||||
<th>Description</th>
|
||||
<th>Location ID</th>
|
||||
<th>CreatedAt</th>
|
||||
<th>UpdatedAt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Table Content Goes Here -->
|
||||
<tr jq-repeat="SensorTable">
|
||||
<td>{{id}}</td>
|
||||
<td>{{name}}</td>
|
||||
<td>{{added_by}}</td>
|
||||
<td>{{mac_address}}</td>
|
||||
<td>{{description}}</td>
|
||||
<td>{{locationid}}</td>
|
||||
<td>{{createdAt}}</td>
|
||||
<td>{{updatedAt}}</td>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<div class="download-container">
|
||||
<p>Download sensor data here:</p>
|
||||
<button id="downloadCSVButton" onclick="downloadCSV()">Download CSV</button>
|
||||
<br><br>
|
||||
<button id="downloadCSVButton" onclick="downloadExcel()">Download Excel Sheet</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<script src="js/data.js"></script>
|
||||
<!-- //download csv and excel -->
|
||||
<script type="text/javascript">
|
||||
//https://stackoverflow.com/questions/14964035/how-to-export-javascript-array-info-to-csv-on-client-side
|
||||
function exportToCsv(filename, rows) {
|
||||
var processRow = function (row) {
|
||||
var finalVal = '';
|
||||
for (var j = 0; j < row.length; j++) {
|
||||
var innerValue = row[j] === null ? '' : row[j].toString();
|
||||
if (row[j] instanceof Date) {
|
||||
innerValue = row[j].toLocaleString();
|
||||
};
|
||||
var result = innerValue.replace(/"/g, '""');
|
||||
if (result.search(/("|,|\n)/g) >= 0)
|
||||
result = '"' + result + '"';
|
||||
if (j > 0)
|
||||
finalVal += ',';
|
||||
finalVal += result;
|
||||
}
|
||||
return finalVal + '\n';
|
||||
};
|
||||
|
||||
var csvFile = '';
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
csvFile += processRow(rows[i]);
|
||||
}
|
||||
|
||||
var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' });
|
||||
if (navigator.msSaveBlob) { // IE 10+
|
||||
navigator.msSaveBlob(blob, filename);
|
||||
} else {
|
||||
var link = document.createElement("a");
|
||||
if (link.download !== undefined) { // feature detection
|
||||
// Browsers that support HTML5 download attribute
|
||||
var url = URL.createObjectURL(blob);
|
||||
link.setAttribute("href", url);
|
||||
link.setAttribute("download", filename);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
//content="application/vnd.ms-excel;charset=utf-8"
|
||||
function exportToExcel(filename, rows) {
|
||||
var processRow = function (row) {
|
||||
var finalVal = '';
|
||||
for (var j = 0; j < row.length; j++) {
|
||||
var innerValue = row[j] === null ? '' : row[j].toString();
|
||||
if (row[j] instanceof Date) {
|
||||
innerValue = row[j].toLocaleString();
|
||||
}
|
||||
var result = innerValue.replace(/"/g, '""');
|
||||
if (result.search(/("|,|\n)/g) >= 0) {
|
||||
result = '"' + result + '"';
|
||||
}
|
||||
if (j > 0) {
|
||||
finalVal += '\t'; // Use tabs for Excel format
|
||||
}
|
||||
finalVal += result;
|
||||
}
|
||||
return finalVal + '\n';
|
||||
};
|
||||
|
||||
var excelFile = '';
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
excelFile += processRow(rows[i]);
|
||||
}
|
||||
|
||||
var blob = new Blob([excelFile], { type: "application/vnd.ms-excel;charset=utf-8" });
|
||||
if (navigator.msSaveBlob) { // IE 10+
|
||||
navigator.msSaveBlob(blob, filename + '.xls');
|
||||
} else {
|
||||
var link = document.createElement("a");
|
||||
if (link.download !== undefined) { // Feature detection
|
||||
// Browsers that support HTML5 download attribute
|
||||
var url = URL.createObjectURL(blob);
|
||||
link.setAttribute("href", url);
|
||||
link.setAttribute("download", filename);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//onclick call
|
||||
function downloadCSV() {
|
||||
app.api.get('sensor-data/', function (error, data) {
|
||||
//to loop through the data and assign into map array
|
||||
var formattedData = data.map(function (item) {
|
||||
return [
|
||||
item.id,
|
||||
item.sensorid,
|
||||
item.locationid,
|
||||
JSON.stringify(item.measurement), //to handle measurement object
|
||||
item.createdAt
|
||||
];
|
||||
});
|
||||
exportToCsv('export.csv', [
|
||||
['id', 'sensorid', 'locationid', 'measurement', 'createdAt'],
|
||||
...formattedData
|
||||
]);
|
||||
|
||||
});
|
||||
}
|
||||
function downloadExcel() {
|
||||
app.api.get('sensor-data/', function (error, data) {
|
||||
//to loop through the data and assign into map array
|
||||
var formattedData = data.map(function (item) {
|
||||
return [
|
||||
item.id,
|
||||
item.sensorid,
|
||||
item.locationid,
|
||||
JSON.stringify(item.measurement), //to handle measurement object
|
||||
item.createdAt
|
||||
];
|
||||
});
|
||||
exportToExcel('export.xls', [
|
||||
['id', 'sensorid', 'locationid', 'measurement', 'createdAt'],
|
||||
...formattedData
|
||||
]);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<%- include('bot') %>
|
Reference in New Issue
Block a user