diff --git a/backend/app.js b/backend/app.js index bf56f43..fc4f5ce 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1 +1,25 @@ -// ExpressJS server +// Dependencies +const express = require("express"); +const configFile = require("./config"); +const logger = require("./logger"); +const cors = require("cors"); + +// Global variables +const app = express(); +const config = configFile.config; +const port = config.port; + +// Routes +const apiEntry = require("./routes/api/entry"); + +// Set up the express server +app.use(cors()); + +app.use(express.json()); // Parse JSON data from requests +app.use(express.urlencoded({ extended: true })); // Parse URL encoded data from requests + +// routes +app.use("/api/entry", apiEntry); + +// Start the express server +app.listen(port, () => logger.info("Server started", `Port: ${port}`)); diff --git a/backend/config.js b/backend/config.js new file mode 100644 index 0000000..a43f684 --- /dev/null +++ b/backend/config.js @@ -0,0 +1,11 @@ +const config = { + db: { + host: "127.0.0.1", + user: "root", + password: "", // In production: "InfraTag!" SORRY FOR CLEAR TEXT PASSWORD + database: "rme-time-tracking", + }, + port: 8080, // Port for the server +}; + +module.exports = { config }; diff --git a/backend/handlers/mysql_handler.js b/backend/handlers/mysql_handler.js new file mode 100644 index 0000000..bff2f46 --- /dev/null +++ b/backend/handlers/mysql_handler.js @@ -0,0 +1,34 @@ +/* + * Handles the connection to the MySQL database + * - Connects to the database + */ + +// Dependencies +const mysql = require("mysql"); +const configFile = require("../config"); +const logger = require("../logger"); + +// Global variables +const config = configFile.config; + +// Connection to the database +const con = mysql.createConnection({ + host: config.db.host, + user: config.db.user, + password: config.db.password, + database: config.db.database, +}); + +con.connect(function (err) { + if (err) throw err; + logger.info( + `Connected to MySQL`, + `${config.db.user}@${config.db.host}/${config.db.database}` + ); +}); + +con.on("error", () => { + logger.error("MySQL Error"); +}); + +module.exports = { con }; diff --git a/backend/handlers/request_handler.js b/backend/handlers/request_handler.js new file mode 100644 index 0000000..e4d720e --- /dev/null +++ b/backend/handlers/request_handler.js @@ -0,0 +1,25 @@ +// Handlers which can be included into the request handler chain. + +// Dependencies +const logger = require("../logger"); + +// better logging of requests +function LoggerHandler(req, res, next) { + const ip = (req.headers["x-forwarded-for"] || req.socket.remoteAddress || "") + .split(",")[0] + .trim() + .split(":")[3]; // get the ip of the client + + if (req.method == "GET") { + // If the request is a GET request + logger.get(req.originalUrl, ip); // Log the request + } else if (req.method == "POST") { + // If the request is a POST request + logger.post(req.originalUrl, ip); // Log the request + } + next(); // Continue to the next handler +} + +module.exports = { + LoggerHandler, +}; diff --git a/backend/logger.js b/backend/logger.js new file mode 100644 index 0000000..cc095b7 --- /dev/null +++ b/backend/logger.js @@ -0,0 +1,103 @@ +const SHOW_TIME = true; + +function getTimestamp() { + let date_ob = new Date(); + + // current date + // adjust 0 before single digit date + let date = ("0" + date_ob.getDate()).slice(-2); + + // current month + let month = ("0" + (date_ob.getMonth() + 1)).slice(-2); + + // current year + let year = date_ob.getFullYear(); + + // current hours + let hours = ("0" + date_ob.getHours()).slice(-2); + + // current minutes + let minutes = ("0" + date_ob.getMinutes()).slice(-2); + + // current seconds + let seconds = ("0" + date_ob.getSeconds()).slice(-2); + + if (SHOW_TIME) { + return hours + ":" + minutes + ":" + seconds; + } else { + return null; + } +} + +function cmd(msg, info = null) { + console.log( + "\x1b[36m%s\x1b[0m", + `${ + getTimestamp() != null ? "[" + getTimestamp() + "]" : "" + }[COMMAND] ${msg} ${info != null ? "- " + info : ""}` + ); +} + +function info(msg, info = null) { + console.log( + "\x1b[37m%s\x1b[0m", + `${getTimestamp() != null ? "[" + getTimestamp() + "]" : ""}[INFO] ${msg} ${ + info != null ? "- " + info : "" + }` + ); +} + +function error(msg, info = null) { + console.log( + "\x1b[31m%s\x1b[0m", + `${ + getTimestamp() != null ? "[" + getTimestamp() + "]" : "" + }[ERROR] ${msg} ${info != null ? "- " + info : ""}` + ); +} + +function get(msg, info = null) { + console.log( + "\x1b[37m\x1b[2m%s\x1b[0m", + `${getTimestamp() != null ? "[" + getTimestamp() + "]" : ""}[GET] ${msg} ${ + info != null ? "- " + info : "" + }` + ); +} + +function post(msg, info = null) { + console.log( + "\x1b[37m\x1b[2m%s\x1b[0m", + `${getTimestamp() != null ? "[" + getTimestamp() + "]" : ""}[POST] ${msg} ${ + info != null ? "- " + info : "" + }` + ); +} + +function socket(msg, type) { + console.log( + "\x1b[35m\x1b[1m%s\x1b[0m", + `${ + getTimestamp() != null ? "[" + getTimestamp() + "]" : "" + }[SOCKET.IO][${type}] ${msg}` + ); +} + +function event(msg, type) { + console.log( + "\x1b[35m\x1b[1m%s\x1b[0m", + `${ + getTimestamp() != null ? "[" + getTimestamp() + "]" : "" + }[EVENT][${type}] ${msg}` + ); +} + +module.exports = { + cmd, + info, + error, + get, + post, + socket, + event, +}; diff --git a/backend/package-lock.json b/backend/package-lock.json index 9ff6659..deb6fc7 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "express": "^4.18.1" + "cors": "^2.8.5", + "express": "^4.18.1", + "mysql": "^2.18.1" } }, "node_modules/accepts": { @@ -29,6 +31,14 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "engines": { + "node": "*" + } + }, "node_modules/body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -104,6 +114,23 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -308,6 +335,11 @@ "node": ">= 0.10" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -364,6 +396,25 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "dependencies": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mysql/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -372,6 +423,14 @@ "node": ">= 0.6" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", @@ -404,6 +463,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -452,6 +516,25 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -536,6 +619,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -544,6 +635,19 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -572,6 +676,11 @@ "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -604,6 +713,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, "body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -660,6 +774,20 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -818,6 +946,11 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -856,11 +989,34 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "requires": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "object-inspect": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", @@ -884,6 +1040,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -917,6 +1078,27 @@ "unpipe": "1.0.0" } }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -980,11 +1162,31 @@ "object-inspect": "^1.9.0" } }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1004,6 +1206,11 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/backend/package.json b/backend/package.json index 01c0bfb..f5c2818 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,6 +9,8 @@ "author": "", "license": "ISC", "dependencies": { - "express": "^4.18.1" + "cors": "^2.8.5", + "express": "^4.18.1", + "mysql": "^2.18.1" } } diff --git a/backend/routes/api/entry.js b/backend/routes/api/entry.js new file mode 100644 index 0000000..2d683c2 --- /dev/null +++ b/backend/routes/api/entry.js @@ -0,0 +1,118 @@ +// Dependencies +const express = require("express"); +const mysql = require("../../handlers/mysql_handler"); +const logger = require("../../logger"); +const request_handler = require("../../handlers/request_handler"); + +// Global variables +const con = mysql.con; +const router = express.Router(); + +function formatDate(date) { + let result = date.split("T")[0].split("-"); + result = result[2] + "." + result[1] + "." + result[0]; + return result; +} + +// Calculate time difference +function calcInd(date, checkedIn, checkedOut) { + date = date.split("T")[0]; + let ind = + (new Date(date + "T" + checkedOut) - new Date(date + "T" + checkedIn)) / + 1000 / + 60 / + 60; + + // round ind to 2 decimal places + ind = Math.round(ind * 100) / 100; + + if (ind > 6) { + ind -= 0.5; + } + + return ind; +} + +//! Bei dieser Funktion habe 112312 Gehirnzellen verloren. Bitte nicht drauf ansprechen. +function calcNorm(date, checkedIn, checkedOut) { + let ind = calcInd(date, checkedIn, checkedOut).toString().split("."); + + let hours = parseInt(ind[0]); + let minutes = 0; + + if (ind.length > 1) { + minutes = parseInt(ind[1] * 6) / 10; + minutes = Math.round(minutes); + } + + if (hours < 10) { + hours = "0" + hours; + } + if (minutes < 10) { + minutes = "0" + minutes; + } + + return hours + ":" + minutes; +} + +function formatTime(time) { + let result = time.split(":"); + result = result[0] + ":" + result[1]; + return result; +} + +function isInMonth(date, monthYear) { + if (monthYear == undefined) { + return true; + } + + date = date.split("-"); + let month = parseInt(date[1]); + let year = parseInt(date[0]); + monthYear = monthYear.split("-"); + if (month == monthYear[0] && year == monthYear[1]) { + return true; + } else { + return false; + } +} + +// monthYear = month number-year number (04-2019) +router.get("/all/:monthYear?", request_handler.LoggerHandler, (req, res) => { + const monthYear = req.params.monthYear; + + con.query(`SELECT * FROM entries ORDER BY date ASC`, (err, result) => { + if (err) { + logger.error(err, "routes/api/entries.js"); + res.send(JSON.parse(JSON.stringify({ error: err }))); // Send error message + return; + } else { + let entries = []; + result = JSON.parse(JSON.stringify(result)); + + for (let i = 0; i < result.length; i++) { + if (isInMonth(result[i].date, monthYear)) { + entries.push({ + date: formatDate(result[i].date), + checkedIn: formatTime(result[i].checked_in), + checkedOut: formatTime(result[i].checked_out), + ind: calcInd( + result[i].date, + result[i].checked_in, + result[i].checked_out + ), + norm: calcNorm( + result[i].date, + result[i].checked_in, + result[i].checked_out + ), + }); + } + } + + res.send(entries); // Send result + } + }); +}); + +module.exports = router; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 136cf65..19d141b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", "sass": "^1.51.0", "web-vitals": "^2.1.4" @@ -7991,6 +7992,14 @@ "he": "bin/he" } }, + "node_modules/history": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "dependencies": { + "@babel/runtime": "^7.7.6" + } + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -13425,6 +13434,30 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", + "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", + "dependencies": { + "history": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", + "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "dependencies": { + "history": "^5.2.0", + "react-router": "6.3.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -21933,6 +21966,14 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "history": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "requires": { + "@babel/runtime": "^7.7.6" + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -25729,6 +25770,23 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", + "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", + "requires": { + "history": "^5.2.0" + } + }, + "react-router-dom": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", + "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "requires": { + "history": "^5.2.0", + "react-router": "6.3.0" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8dd567c..18b5f41 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", "sass": "^1.51.0", "web-vitals": "^2.1.4" diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index eb1d100..9c8cb50 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,15 +1,24 @@ -import "./css/App.scss"; +import "./css/app.scss"; +import ServerProvider from "./contexts/ServerContext"; +import Table from "./components/table/Table"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; function App() { return ( -
-
-

Hello World

+ +
+
+ + + } /> + + +
+
- -
+ ); } diff --git a/frontend/src/components/table/Header.jsx b/frontend/src/components/table/Header.jsx new file mode 100644 index 0000000..4779b92 --- /dev/null +++ b/frontend/src/components/table/Header.jsx @@ -0,0 +1,25 @@ +import "../../css/components/table.scss"; + +function Header({ date, checkedIn, checkedOut, ind, norm }) { + return ( +
+
+ {date} +
+
+ {checkedIn} +
+
+ {checkedOut} +
+
+ {ind} +
+
+ {norm} +
+
+ ); +} + +export default Header; diff --git a/frontend/src/components/table/Row.jsx b/frontend/src/components/table/Row.jsx new file mode 100644 index 0000000..4b18ef3 --- /dev/null +++ b/frontend/src/components/table/Row.jsx @@ -0,0 +1,25 @@ +import "../../css/components/table.scss"; + +function Row({ date, checkedIn, checkedOut, ind, norm }) { + return ( +
+
+ {date ? date : "-"} +
+
+ {checkedIn ? checkedIn : "-"} +
+
+ {checkedOut ? checkedOut : "-"} +
+
+ {ind ? ind : "-"} +
+
+ {norm ? norm : "-"} +
+
+ ); +} + +export default Row; diff --git a/frontend/src/components/table/Table.jsx b/frontend/src/components/table/Table.jsx new file mode 100644 index 0000000..80d3f26 --- /dev/null +++ b/frontend/src/components/table/Table.jsx @@ -0,0 +1,81 @@ +import React, { useState, useContext, useEffect } from "react"; +import { ServerContext } from "../../contexts/ServerContext"; +import { useParams } from "react-router-dom"; + +import "../../css/components/table.scss"; + +import Row from "./Row"; +import Header from "./Header"; + +function Table() { + const params = useParams(); + const { URL } = useContext(ServerContext); + const [entries, setEntries] = useState([]); + const [monthYear, setMonthYear] = useState(params.monthYear); + + async function fetchEntries() { + let response = await fetch(URL + `/api/entry/all/` + monthYear); + let data = await response.json(); + setEntries(data); + } + + useEffect(() => { + fetchEntries(); + }, []); + + function nextMonth() { + let month = parseInt(monthYear.split("-")[0]); + + if (month === 12) { + window.location.href = `/table/${1}-${ + parseInt(monthYear.split("-")[1]) + 1 + }`; + } else { + window.location.href = `/table/${month + 1}-${monthYear.split("-")[1]}`; + } + } + + function previousMonth() { + let month = parseInt(monthYear.split("-")[0]); + + if (month === 1) { + window.location.href = `/table/${12}-${ + parseInt(monthYear.split("-")[1]) - 1 + }`; + } else { + window.location.href = `/table/${month - 1}-${monthYear.split("-")[1]}`; + } + } + + return ( +
+
+
+

{monthYear}

+
+
+
+
+ + {/* ROWS */} + {entries.map((entry, index) => ( + + ))} +
+
+ ); +} + +export default Table; diff --git a/frontend/src/contexts/ServerContext.jsx b/frontend/src/contexts/ServerContext.jsx new file mode 100644 index 0000000..bbb28c1 --- /dev/null +++ b/frontend/src/contexts/ServerContext.jsx @@ -0,0 +1,14 @@ +import React, { createContext } from "react"; + +export const ServerContext = createContext(); + +const ServerProvider = (props) => { + const URL = "http://127.0.0.1:8080"; + return ( + + {props.children} + + ); +}; + +export default ServerProvider; diff --git a/frontend/src/css/components/table.scss b/frontend/src/css/components/table.scss new file mode 100644 index 0000000..08a9522 --- /dev/null +++ b/frontend/src/css/components/table.scss @@ -0,0 +1,59 @@ +#table { + height: 100%; + width: 100%; + + .table-nav { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + padding-top: 10px; + padding-bottom: 10px; + + div { + background-color: red; + width: 50px; + height: 50px; + display: block; + margin-left: auto; + margin-right: auto; + cursor: pointer; + } + + h2 { + text-align: center; + padding-top: 10px; + } + } + + .table-container { + height: 100%; + width: 100%; + + text-align: center; + font-size: 1rem; + + .table-header { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr 1fr; + width: 100%; + background-color: rgb(28, 28, 28); + font-size: 1.2rem; + font-weight: bold; + border-bottom: 2px solid rgb(157, 157, 157); + } + + .table-row { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr 1fr; + width: 100%; + background-color: rgb(61, 61, 61); + border-top: 1px solid rgb(85, 85, 85); + } + + .table-cell { + grid-area: auto; + padding: 10px; + border-right: 1px solid rgb(85, 85, 85); + border-left: 1px solid rgb(85, 85, 85); + } + } +}