Refactored codebase & auth bug fix

This commit is contained in:
2025-05-15 13:33:58 +03:00
parent 36226dbbac
commit eeda1b500d
13 changed files with 424 additions and 347 deletions
+15
View File
@@ -0,0 +1,15 @@
const Database = require("better-sqlite3");
const logger = require("../utils/logger.util");
const db_conf = require("../config/db.conf");
var db;
try {
logger.info("Connecting to Database...");
db = new Database(db_conf.path, { fileMustExist: true });
logger.info("Connected to Database.");
} catch (error) {
logger.error(error.message);
}
module.exports = db;
+292
View File
@@ -0,0 +1,292 @@
const db = require("../database/sqlite.database.js");
const logger = require("../utils/logger.util.js");
const Database = require("better-sqlite3");
const path = require("path");
const fs = require("fs");
const app_conf = require("../config/app.conf.js");
const db_conf = require("../config/db.conf.js");
const express = require('express');
const router = express.Router();
function errorPageRenderer(res, emoji, message) {
res.render("error", {
page_title: app_conf.name,
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
error_emoji: emoji,
error_message: message
})
}
router.delete("/remove", async (req, res) => {
if (req.query["element"]) {
try {
const remove = db.prepare(`DELETE FROM ${db_conf.table_name} WHERE Name='${req.query["element"]}';`);
if (remove.run()["changes"]) {
res.status(200).send();
} else {
res.status(404).send();
}
} catch (error) {
res.status(500).send();
logger.error(error.message);
}
} else {
res.status(400).send();
}
});
router.post("/upload", async (req, res) => {
if (!req.files || !req.files.file) {
res.status(422).send();
} else {
const uploadedFile = req.files.file;
db.close();
fs.copyFile(uploadedFile.tempFilePath, db_conf.path, (error) => {
if (error) {
logger.error(error);
} else {
try {
db = new Database(db_conf.path, { fileMustExist: true });
} catch (error) {
logger.error(error);
shutting_down = true;
shutdown();
}
}
});
}
res.status(201).send();
});
router.get("/download", async (req, res) => {
res.download(db_conf.path);
});
router.post("/add", async (req, res) => {
if (req.query["element"] && req.body) {
try {
let keys = Object.keys(req.body);
let values = `'${req.query["element"]}',`;
for (const k of keys) {
if (req.body[k] === "null") {
values += `NULL,`
} else {
values += `'${req.body[k]}',`;
}
}
if (values) {
values = values.slice(0, -1);
const update = db.prepare(`INSERT INTO ${db_conf.table_name} VALUES (${values});`);
update.run();
res.status(200).send();
} else {
res.status(400).send();
}
} catch (error) {
res.status(500).send();
logger.error(error.message);
}
} else {
res.status(400).send();
}
});
router.post("/edit", async (req, res) => {
if (req.query["element"] && req.body) {
try {
let keys = Object.keys(req.body);
let values = "";
for (const k of keys) {
if (req.body[k] === "null") {
values += `${k}=NULL,`
} else {
values += `${k}='${req.body[k]}',`;
}
}
if (values) {
values = values.slice(0, -1);
const update = db.prepare(`UPDATE ${db_conf.table_name} SET ${values} WHERE Name='${req.query["element"]}'`);
update.run();
res.status(200).send();
} else {
res.status(400).send();
}
} catch (error) {
res.status(500).send();
logger.error(error.message);
}
} else {
res.status(400).send();
}
});
router.get("/add", async (req, res) => {
try {
const select_get = db.prepare(`SELECT * FROM ${db_conf.table_name};`);
const row = select_get.get();
const sql_header = Object.keys(row);
var table_header = `<th>${sql_header[0]}</th>\n`;
sql_header.splice(0, 1);
if (row) {
var column_options = {};
for (const key of sql_header) {
table_header += `<th>${key}</th>\n`;
const select_distinct = db.prepare(`
SELECT DISTINCT ${key}
FROM ${db_conf.table_name};
`);
values = select_distinct.all();
for (v of values) {
if (column_options[key] == null) {
column_options[key] = Object.values(v);
} else {
column_options[key] = column_options[key].concat(Object.values(v));
}
}
}
var table_data = "";
const sql_row_values = Object.values(row);
table_data += `<tr><td><input type="text" placeholder="Sensor name"></td>\n`;
for (let i = 1; i < sql_row_values.length; i++) {
table_data += `<td><select></select></td>\n`;
}
table_data += "</tr>";
res.render("add", {
page_title: app_conf.name,
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
js_path: path.join(app_conf.paths.js.web, "add.js"),
column_options: JSON.stringify(column_options),
table_header: table_header,
table_data: table_data
});
} else {
errorPageRenderer(res, "&#10067;", "The database is empty.");
}
} catch (error) {
logger.error(error.message);
}
});
router.get("/edit", async (req, res) => {
if (req.query["element"]) {
try {
const select_get = db.prepare(`SELECT * FROM ${db_conf.table_name} WHERE Name='${req.query["element"]}';`);
const row = select_get.get();
if (row) {
const sql_header = Object.keys(row);
var table_header = `<th>${sql_header[0]}</th>\n`;
sql_header.splice(0, 1);
var column_options = {};
for (const key of sql_header) {
table_header += `<th>${key}</th>\n`;
const select_distinct = db.prepare(`
SELECT DISTINCT ${key}
FROM ${db_conf.table_name}
WHERE ${key} IS NOT NULL
AND ${key} NOT IN (
SELECT ${key}
FROM ${db_conf.table_name}
WHERE Name='${req.query["element"]}'
AND ${key} IS NOT NULL);
`);
values = select_distinct.all();
for (v of values) {
if (column_options[key] == null) {
column_options[key] = Object.values(v);
} else {
column_options[key] = column_options[key].concat(Object.values(v));
}
}
}
var table_data = "";
const sql_row_values = Object.values(row);
table_data += `<tr><td>${sql_row_values[0]}</td>\n`;
sql_row_values.splice(0, 1);
for (const v of sql_row_values) {
table_data += `<td><select><option selected="selected">${v}</option></select></td>\n`;
}
table_data += "</tr>";
res.render("edit", {
page_title: app_conf.name,
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
js_path: path.join(app_conf.paths.js.web, "edit.js"),
column_options: JSON.stringify(column_options),
table_header: table_header,
table_data: table_data
});
} else {
errorPageRenderer(res, "&#128269;&#10060;", "Not found.");
}
} catch (error) {
logger.error(error.message);
}
} else {
errorPageRenderer(res, "&#9995;&#9940;", "Bad request.");
}
});
router.get("", async (req, res) => {
logger.info(`${req.method}: "${req.url}" => ${req.get("User-Agent")}`);
try {
const select = db.prepare(`SELECT * FROM ${db_conf.table_name}`);
const rows = select.all();
if (rows[0]) {
const sql_header = Object.keys(rows[0]);
var table_header = "";
for (const key of sql_header) {
table_header += `<th>${key}</th>\n`;
}
var table_data = "";
for (const row of rows) {
const sql_row_values = Object.values(row);
table_data += "<tr>";
for (const v of sql_row_values) {
table_data += `<td>${v}</td>\n`;
}
table_data += "</tr>";
}
res.render("index", {
page_title: app_conf.name,
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
js_path: path.join(app_conf.paths.js.web, "index.js"),
table_header: table_header,
table_data: table_data
});
} else {
errorPageRenderer(res, "&#10067;", "The database is empty.");
}
} catch (error) {
errorPageRenderer(res, "&#127755;&#128165;", "Internal server error. Check logs for more information.");
logger.error(error.message);
}
});
module.exports = router;
+22
View File
@@ -0,0 +1,22 @@
const path = require("path");
const app_conf = require("../config/app.conf.js");
const express = require('express');
const router = express.Router();
router.use(app_conf.paths.css.web, express.static(path.join(path.dirname(require.main.filename), app_conf.paths.css.src)));
router.use(app_conf.paths.js.web, express.static(path.join(path.dirname(require.main.filename), app_conf.paths.js.src)));
/*
* Main endpoint - might implement authentication step here...
*/
router.get("/", (req, res) => {
res.render("auth", {
page_title: app_conf.name,
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
protocol: app_conf.protocol,
host: app_conf.host
});
});
module.exports = router;
-18
View File
@@ -1,18 +0,0 @@
module.exports = {
name: "CRTelemetry",
port: 80,
username: "admin",
password: "password",
logger: {
level: "info",
error_path: "/app/logs/error.log",
full_path: "/app/logs/combined.log"
},
timestamp_format: "HH:mm:ss DD-MM-YYYY",
views_directory: "/app/views/",
temp_directory: "/app/temp/",
web_paths: {
css: "/app/src/css",
js: "/app/src/javascript"
}
};
-4
View File
@@ -1,4 +0,0 @@
module.exports = {
path: "./sensors.db",
table_name: "sensors"
};
-52
View File
@@ -1,52 +0,0 @@
body {
display: flex;
flex-direction: column;
background-color: #121212;
font-family: "Ubuntu", sans-serif;
font-weight: 500;
font-style: normal;
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */
}
a:visited {
color: blue;
}
a:hover {
color: white;
background-color: blue;
}
.control_div {
color: #ececec;
background-color: #272727;
margin: 3px;
padding: 5px;
border-radius: 5px;
}
.main_div {
color: #ececec;
background-color: #272727;
margin: 3px;
padding: 5px;
border-radius: 5px;
}
table {
background-color: #272727;
padding: 10px;
border-radius: 5px;
width: 100%;
}
th,
td {
border: 2px solid black;
padding-top: 5px;
padding-bottom: 5px;
}
-67
View File
@@ -1,67 +0,0 @@
const sql_table = document.getElementById("sql_table");
const sql_row = sql_table.rows;
const commit = document.getElementById("commit");
column_options = JSON.parse(column_options);
console.log(column_options);
commit.onmouseenter = async function () {
this.style.backgroundColor = "blue";
}
commit.onmouseleave = async function () {
this.style.backgroundColor = "";
};
commit.onmousedown = async function () {
this.style.backgroundColor = "red";
};
commit.ondblclick = async function () {
if (sql_row[1].cells[0].firstChild.value) {
let req_body = {};
for (let i = 1; i < sql_row[1].cells.length; i++) {
req_body[sql_row[0].cells[i].firstChild.textContent] = sql_row[1].cells[i].firstChild[sql_row[1].cells[i].firstChild.selectedIndex].text;
}
try {
const response = await fetch(`/app/add?element=${sql_row[1].cells[0].firstChild.value}`, {
method: "POST",
body: JSON.stringify(req_body),
headers: {
"Content-Type": "application/json",
}
});
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
} else {
this.style.backgroundColor = "blue";
window.location.href = "/app";
}
} catch (error) {
console.error(error.message);
}
}
};
for (let i = 1; i < sql_row[1].cells.length; i++) {
let opts = column_options[sql_row[0].cells[i].textContent];
if (opts != null) {
for (o of opts) {
sql_row[1].cells[i].firstChild.add(new Option(o, o));
}
}
sql_row[1].cells[i].firstChild.oncontextmenu = async function (ev) {
ev.preventDefault();
let new_value = prompt("Enter a new value");
if ((new_value != null) && (new_value != "")) {
if (this[this.length - 1].value != "custom") {
this.add(new Option(new_value, "custom"));
} else {
this[this.length - 1].text = new_value;
}
this.value = "custom";
this.dispatchEvent(new Event("change"));
}
};
}
-102
View File
@@ -1,102 +0,0 @@
const sql_table = document.getElementById("sql_table");
const sql_row = sql_table.rows;
const commit = document.getElementById("commit");
const remove = document.getElementById("remove");
var changes = 0;
column_options = JSON.parse(column_options);
function setEventListeners(element, cb) {
element.onmouseenter = async function () {
this.style.backgroundColor = "blue";
};
element.onmouseleave = async function () {
this.style.backgroundColor = "";
};
element.onmousedown = async function () {
this.style.backgroundColor = "red";
};
element.ondblclick = async function () {
cb();
this.style.backgroundColor = "blue";
};
}
setEventListeners(remove, async () => {
try {
const response = await fetch(`/app/remove?element=${sql_row[1].cells[0].firstChild.textContent}`, {
method: "DELETE"
});
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
} else {
window.location.href = "/app";
}
} catch (error) {
console.error(error.message);
}
});
setEventListeners(commit, async () => {
if (changes) {
let req_body = {};
for (let i = 1; i < sql_row[1].cells.length; i++) {
if (sql_row[1].cells[i].firstChild.selectedIndex != 0) {
req_body[sql_row[0].cells[i].firstChild.textContent] = sql_row[1].cells[i].firstChild[sql_row[1].cells[i].firstChild.selectedIndex].text;
}
}
try {
const response = await fetch(`/app/edit?element=${sql_row[1].cells[0].firstChild.textContent}`, {
method: "POST",
body: JSON.stringify(req_body),
headers: {
"Content-Type": "application/json",
}
});
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
} else {
window.location.href = "/app";
}
} catch (error) {
console.error(error.message);
}
}
});
for (let i = 1; i < sql_row[1].cells.length; i++) {
let opts = column_options[sql_row[0].cells[i].textContent];
if (opts != null) {
for (o of opts) {
sql_row[1].cells[i].firstChild.add(new Option(o, o));
}
}
sql_row[1].cells[i].firstChild.oncontextmenu = async function (ev) {
ev.preventDefault();
let new_value = prompt("Enter a new value");
if ((new_value != null) && (new_value != "")) {
if (this[this.length - 1].value != "custom") {
this.add(new Option(new_value, "custom"));
} else {
this[this.length - 1].text = new_value;
}
this.value = "custom";
this.dispatchEvent(new Event("change"));
}
};
sql_row[1].cells[i].firstChild.onchange = async function () {
if (this.selectedIndex) {
console.log(this);
this.parentElement.style.backgroundColor = "blue";
changes++;
} else {
this.parentElement.style.backgroundColor = "";
changes--;
}
};
}
-59
View File
@@ -1,59 +0,0 @@
const sql_table = document.getElementById("sql_table");
const upload = document.getElementById("upload");
const download = document.getElementById("download");
const logout = document.getElementById("logout");
const add = document.getElementById("add");
const sql_rows = sql_table.rows;
function setEventListeners(element, cb) {
element.onmouseenter = async function () {
this.style.backgroundColor = "blue";
};
element.onmouseleave = async function () {
this.style.backgroundColor = "";
};
element.onmousedown = async function () {
this.style.backgroundColor = "red";
};
element.ondblclick = async function () {
this.style.backgroundColor = "blue";
cb();
};
}
setEventListeners(upload, async () => {
const input = document.getElementById("file_input");
const upload = (file) => {
const formData = new FormData();
formData.append('file', file);
fetch('/app/upload', {
method: 'POST',
body: formData
}).then((response) => {
location.reload();
});
}
const onSelectFile = () => upload(input.files[0]);
input.addEventListener('change', onSelectFile, false);
input.click();
});
setEventListeners(add, () => {
window.location.href = "/app/add";
});
setEventListeners(download, () => {
window.location.href = "/app/download";
});
setEventListeners(logout, () => {
window.location.href = `${window.location.protocol}//log:out@${window.location.host}`;
});
for (let i = 1; i < sql_rows.length; i++) {
setEventListeners(sql_rows[i], () => {
window.location.href = `/app/edit?element=${sql_rows[i].cells[0].innerText}`;
});
}
+24
View File
@@ -0,0 +1,24 @@
const winston = require("winston");
const app_conf = require("../config/app.conf");
const path = require("path");
/*
* Initialize console and file logger
*/
const logger = winston.createLogger({
levels: winston.config.syslog.levels,
level: process.env.LOG_LEVEL || app_conf.logger.level,
format: winston.format.combine(
winston.format.timestamp({
format: app_conf.timestamp_format,
}),
winston.format.printf((info) => `[${info.timestamp}][${info.level}]: ${info.message}`)
),
transports: [
new winston.transports.File({ filename: path.join(path.dirname(require.main.filename), app_conf.logger.error_path), level: "error", }),
new winston.transports.File({ filename: path.join(path.dirname(require.main.filename), app_conf.logger.full_path) }),
new winston.transports.Console()
],
});
module.exports = logger;
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<title>
<%= page_title %>
</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="<%= css_path %>">
</head>
<body>
<div class="control_div">
<label
style='font-family: "Ubuntu", sans-serif; font-weight: 500; font-style: normal; font-size: 200%;'>Centaurus
Racing Team</label>
</div>
<div class="main_div" style="display: flex; flex-direction: column; justify-content: space-between;">
<div style="display: flex; flex-direction: row; justify-content: space-evenly;">
<label>CRTelemetry SQLite3</label>
<a href="<%= protocol %>://<%= host %>/app">Enter</a>
</div>
</div>
</body>
</html>