Initial commit
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
const express = require("express");
|
||||
const Database = require("better-sqlite3");
|
||||
const winston = require("winston");
|
||||
const path = require("path");
|
||||
const fileUpload = require("express-fileupload");
|
||||
const fs = require("fs");
|
||||
const app_conf = require("./app/config/app.conf.js");
|
||||
const db_conf = require("./app/config/db.conf.js");
|
||||
|
||||
const app = express();
|
||||
var db;
|
||||
var shutting_down = false;
|
||||
|
||||
/*
|
||||
* Static middleware - serve css and js files to public
|
||||
*/
|
||||
app.use(app_conf.web_paths.css, express.static(path.join(__dirname, app_conf.web_paths.css)));
|
||||
app.use(app_conf.web_paths.js, express.static(path.join(__dirname, app_conf.web_paths.js)));
|
||||
app.use(express.json());
|
||||
app.use(fileUpload({
|
||||
limits: { fileSize: 102400 },
|
||||
|
||||
useTempFiles: true,
|
||||
tempFileDir: path.join(__dirname, app_conf.temp_directory)
|
||||
}));
|
||||
|
||||
/*
|
||||
* 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(__dirname, app_conf.logger.error_path), level: "error", }),
|
||||
new winston.transports.File({ filename: path.join(__dirname, app_conf.logger.full_path) }),
|
||||
new winston.transports.Console()
|
||||
],
|
||||
});
|
||||
|
||||
app.delete("/app/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();
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/app/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();
|
||||
});
|
||||
|
||||
app.get("/app/download", async (req, res) => {
|
||||
res.download(db_conf.path);
|
||||
});
|
||||
|
||||
app.post("/app/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();
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/app/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();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Main endpoint - might implement authentication step here...
|
||||
*/
|
||||
app.get("/", (req, res) => {
|
||||
res.redirect("app/");
|
||||
});
|
||||
|
||||
function errorPageRenderer(res, emoji, message) {
|
||||
res.render("error", {
|
||||
page_title: app_conf.name,
|
||||
css_path: path.join(app_conf.web_paths.css, "stylesheet.css"),
|
||||
error_emoji: emoji,
|
||||
error_message: message
|
||||
})
|
||||
}
|
||||
|
||||
app.get("/app/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.web_paths.css, "stylesheet.css"),
|
||||
js_path: path.join(app_conf.web_paths.js, "add.js"),
|
||||
column_options: JSON.stringify(column_options),
|
||||
table_header: table_header,
|
||||
table_data: table_data
|
||||
});
|
||||
} else {
|
||||
errorPageRenderer(res, "❓", "The database is empty.");
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Edit endpoint - edits a specific row
|
||||
*/
|
||||
app.get("/app/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.web_paths.css, "stylesheet.css"),
|
||||
js_path: path.join(app_conf.web_paths.js, "edit.js"),
|
||||
column_options: JSON.stringify(column_options),
|
||||
table_header: table_header,
|
||||
table_data: table_data
|
||||
});
|
||||
} else {
|
||||
errorPageRenderer(res, "🔍❌", "Not found.");
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
} else {
|
||||
errorPageRenderer(res, "✋⛔", "Bad request.");
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* App endpoint - shows the database
|
||||
*/
|
||||
app.get("/app", 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.web_paths.css, "stylesheet.css"),
|
||||
js_path: path.join(app_conf.web_paths.js, "index.js"),
|
||||
table_header: table_header,
|
||||
table_data: table_data
|
||||
});
|
||||
} else {
|
||||
errorPageRenderer(res, "❓", "The database is empty.");
|
||||
}
|
||||
} catch (error) {
|
||||
errorPageRenderer(res, "🌋💥", "Internal server error. Check logs for more information.");
|
||||
logger.error(error.message);
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* Initialise ExpressJS
|
||||
* Using ejs to pass variables from ExpressJS to HTML pages.
|
||||
*/
|
||||
app.set("view engine", "ejs");
|
||||
app.set("views", path.join(__dirname, app_conf.views_directory));
|
||||
const server = app.listen(app_conf.port, () => {
|
||||
logger.info("Starting Database Editor.");
|
||||
logger.info("Connecting to Database...");
|
||||
try {
|
||||
db = new Database(db_conf.path, { fileMustExist: true });
|
||||
logger.info("Connected to Database.");
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
shutdown();
|
||||
}
|
||||
fs.rm(path.join(__dirname, app_conf.temp_directory, "/."), { recursive: true }, (error) => {
|
||||
if (error == null) {
|
||||
logger.info("Removed temp directory.");
|
||||
} else if (error.code === "ENOENT") {
|
||||
logger.info("Temp directory not found.");
|
||||
} else {
|
||||
logger.error(error.message);
|
||||
}
|
||||
});
|
||||
logger.info(`Listening to port ${app_conf.port}.`);
|
||||
})
|
||||
|
||||
/*
|
||||
* Handle Ctrl+C to shutdown gracefully
|
||||
*/
|
||||
process.on("SIGINT", () => {
|
||||
if (shutting_down) {
|
||||
logger.emerg("Shutting down forcefully.");
|
||||
process.exit(-1);
|
||||
} else {
|
||||
shutdown();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* This function shuts the server down
|
||||
*/
|
||||
function shutdown() {
|
||||
shutting_down = true;
|
||||
logger.info("Closing Database connection.");
|
||||
try {
|
||||
db.close();
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
server.close(() => {
|
||||
logger.info("Shutting down server.");
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user