diff --git a/backend/db.js b/backend/db.js
new file mode 100644
index 0000000..35443f4
--- /dev/null
+++ b/backend/db.js
@@ -0,0 +1,10 @@
+import mysql from "mysql2/promise";
+
+const connection = await mysql.createConnection({
+ host: "localhost",
+ user: "monty",
+ password: "some_pass",
+ database: "timelognode",
+});
+
+export default connection;
diff --git a/backend/index.js b/backend/index.js
new file mode 100644
index 0000000..aace694
--- /dev/null
+++ b/backend/index.js
@@ -0,0 +1,21 @@
+import express from "express";
+import getAll from "./routers/getAll.js";
+import getUser from "./routers/getUser.js";
+import getTopTen from "./routers/getTopTen.js";
+import reset from "./routers/reset.js";
+import cors from "cors";
+
+const router = express.Router();
+
+const app = express();
+app.use(express.json());
+app.use(cors());
+
+app.use("/api", getAll);
+app.use("/api", getTopTen);
+app.use("/api", getUser);
+app.use("/api", reset);
+
+app.use("/api", router);
+const PORT = process.env.PORT || 5000;
+app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
diff --git a/backend/package-lock.json b/backend/package-lock.json
new file mode 100644
index 0000000..90574cd
--- /dev/null
+++ b/backend/package-lock.json
@@ -0,0 +1,953 @@
+{
+ "name": "backend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "backend",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "cors": "^2.8.5",
+ "express": "^4.21.1",
+ "mysql2": "^3.11.4"
+ },
+ "devDependencies": {
+ "@types/node": "^22.9.0",
+ "typescript": "^5.6.3"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "22.9.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz",
+ "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.19.8"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
+ "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==",
+ "license": "MIT",
+ "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",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
+ "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.10",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
+ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz",
+ "integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/mysql2": {
+ "version": "3.11.4",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.4.tgz",
+ "integrity": "sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==",
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.1",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.6.3",
+ "long": "^5.2.1",
+ "lru.min": "^1.0.0",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
+ "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
+ "license": "MIT",
+ "dependencies": {
+ "lru-cache": "^7.14.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "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": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
+ "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.10",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
+ "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ }
+ }
+}
diff --git a/backend/package.json b/backend/package.json
new file mode 100644
index 0000000..63c9c9f
--- /dev/null
+++ b/backend/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "backend",
+ "version": "1.0.0",
+ "main": "index.js",
+ "scripts": {
+ "start": "node index.js"
+ },
+ "author": "",
+ "license": "ISC",
+ "description": "",
+ "dependencies": {
+ "cors": "^2.8.5",
+ "express": "^4.21.1",
+ "mysql2": "^3.11.4"
+ },
+ "devDependencies": {
+ "@types/node": "^22.9.0",
+ "typescript": "^5.6.3"
+ }
+}
diff --git a/backend/populateDB.js b/backend/populateDB.js
new file mode 100644
index 0000000..4ff89ef
--- /dev/null
+++ b/backend/populateDB.js
@@ -0,0 +1,54 @@
+fnameS = [
+ "John",
+ "Gringo",
+ "Mark",
+ "Lisa",
+ "Maria",
+ "Sonya",
+ "Philip",
+ "Jose",
+ "Lorenzo",
+ "George",
+ "Justin",
+];
+
+lnameS = [
+ "Johnson",
+ "Lamas",
+ "Jackson",
+ "Brown",
+ "Mason",
+ "Rodriguez",
+ "Roberts",
+ "Thomas",
+ "Rose",
+ "McDonalds",
+];
+
+domain = ["hotmail.com", "gmail.com", "live.com"];
+fname = [];
+lname = [];
+email = [];
+i = 0;
+while (i < 100) {
+ fname.push(fnameS[Math.floor(Math.random() * 10)]);
+ lname.push(lnameS[Math.floor(Math.random() * 9)]);
+ email.push(
+ `${fname[i]}.${lname[i]}@${domain[Math.floor(Math.random() * 2)]}`,
+ );
+ i++;
+}
+
+today = new Date();
+for (let i = 0; i < email.length; i++) {
+ for (let j = 0; j < Math.floor(Math.random() * 20); j++) {
+ let m = Math.random() * (8 - 0.25) + 0.25;
+ m.toFixed(2);
+ console.log(
+ fname[i],
+ m,
+ `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`,
+ );
+ today.setDate(today.getDate() + 1);
+ }
+}
diff --git a/backend/routers/getAll.js b/backend/routers/getAll.js
new file mode 100644
index 0000000..561a22a
--- /dev/null
+++ b/backend/routers/getAll.js
@@ -0,0 +1,60 @@
+import express from "express";
+import connection from "../db.js";
+
+const router = express.Router();
+
+router.get("/getall", async (req, res) => {
+ let sql =
+ "SELECT u.f_name,u.l_name,u.mail,p.name,t.time,t.date,t.user \
+ FROM Timelog t \
+ INNER JOIN Project p ON p.id=t.project \
+ INNER JOIN User u ON u.id=t.user ";
+ const where = "WHERE t.date BETWEEN ? AND ? ";
+ const order = "ORDER BY ?? ";
+ const offsetSql = "LIMIT 10 OFFSET ?;";
+ let flag = 0;
+ // construct the sql statement based on incoming request
+ if (req.query.from || req.query.to) {
+ sql = sql + where;
+ flag += 1;
+ }
+ if (req.query.sortby) {
+ sql = sql + order;
+ flag += 2;
+ }
+ sql = sql + offsetSql;
+
+ let results;
+ let fields;
+ switch (flag) {
+ case 0:
+ [results, fields] = await connection.query(sql, [
+ parseInt(req.query.offset),
+ ]);
+ break;
+ case 1:
+ [results, fields] = await connection.query(sql, [
+ req.query.from,
+ req.query.to,
+ parseInt(req.query.offset),
+ ]);
+ break;
+ case 2:
+ [results, fields] = await connection.query(sql, [
+ req.query.sortby,
+ parseInt(req.query.offset),
+ ]);
+ break;
+ case 3:
+ [results, fields] = await connection.query(sql, [
+ req.query.from,
+ req.query.to,
+ req.query.sortby,
+ parseInt(req.query.offset),
+ ]);
+ break;
+ }
+ res.json(results);
+});
+
+export default router;
diff --git a/backend/routers/getTopTen.js b/backend/routers/getTopTen.js
new file mode 100644
index 0000000..e1b030e
--- /dev/null
+++ b/backend/routers/getTopTen.js
@@ -0,0 +1,35 @@
+import express from "express";
+import connection from "../db.js";
+
+const router = express.Router();
+
+router.get("/gettopten", async (req, res) => {
+ let from = "2020-01-01";
+ let to = "2029-01-01";
+ let filterBy = "user";
+ if (req.query.from) from = req.query.from;
+ if (req.query.to) to = req.query.to;
+ if (req.query.filterBy) filterBy = req.query.filterBy;
+ let results, fields;
+ let filterBySql = filterBy === "user" ? "t.user" : "t.project";
+ try {
+ [results, fields] = await connection.query(
+ "SELECT t.user,t.date,t.project,u.f_name,u.l_name,p.name,SUM(t.time) as total_time \
+ FROM Timelog t \
+ INNER JOIN Project p ON p.id=t.project \
+ INNER JOIN User u ON u.id=t.user \
+ WHERE t.date BETWEEN ? AND ? \
+ GROUP BY ?? \
+ ORDER BY total_time DESC\
+ LIMIT 10;",
+ [from, to, filterBySql],
+ );
+ } catch (err) {
+ console.log(err);
+ res.status(400).json({ message: "Error" });
+ }
+
+ res.json(results);
+});
+
+export default router;
diff --git a/backend/routers/getUser.js b/backend/routers/getUser.js
new file mode 100644
index 0000000..b52b3b3
--- /dev/null
+++ b/backend/routers/getUser.js
@@ -0,0 +1,42 @@
+import express from "express";
+import connection from "../db.js";
+
+const router = express.Router();
+
+router.get("/getUser", async (req, res) => {
+ const userId = req.query.userid;
+ let results, fields;
+ try {
+ [results, fields] = await connection.query(
+ "SELECT p.name,t.time,t.project \
+ FROM Timelog t \
+ INNER JOIN Project p ON p.id=t.project \
+ INNER JOIN User u ON u.id=t.user \
+ WHERE USER = ? ;",
+ [userId],
+ );
+ } catch (err) {
+ console.log(err);
+ res.status(400).json("ERROR");
+ }
+ let projects = {};
+ let projectIdtoName = {};
+ for (let i = 0; i < results.length; i++) {
+ if (results[i].project in projects) {
+ projects[results[i].project] += results[i].time;
+ } else {
+ projects[results[i].project] = results[i].time;
+ projectIdtoName[results[i].project] = results[i].name;
+ }
+ }
+ // map projectIds to project names before sending the data
+ let respData = {};
+ for (let key in projects) {
+ if (projects.hasOwnProperty(key)) {
+ respData[projectIdtoName[key]] = projects[key];
+ }
+ }
+ res.json(respData);
+});
+
+export default router;
diff --git a/backend/routers/reset.js b/backend/routers/reset.js
new file mode 100644
index 0000000..50400fe
--- /dev/null
+++ b/backend/routers/reset.js
@@ -0,0 +1,16 @@
+import express from "express";
+import connection from "../db.js";
+
+const router = express.Router();
+
+router.get("/reset", async (req, res) => {
+ try {
+ await connection.query("CALL InitDb;", []);
+ } catch (err) {
+ console.log(err);
+ res.status(400).json({ message: "Error" });
+ }
+ res.status(200).json({ message: "Success" });
+});
+
+export default router;
diff --git a/backend/sql/commands.sql b/backend/sql/commands.sql
new file mode 100644
index 0000000..e4dfe67
--- /dev/null
+++ b/backend/sql/commands.sql
@@ -0,0 +1,137 @@
+CREATE TABLE Timelog( user INT, project INT, date DATE, time FLOAT, id INT AUTO_INCREMENT PRIMARY KEY, FOREIGN KEY (user) REFERENCES User (id));
+CREATE TABLE Project( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));
+CREATE TABLE User ( f_name VARCHAR(50) , l_name VARCHAR(50) NOT NULL, mail VARCHAR(50) NOT NULL, id INT AUTO_INCREMENT PRIMARY KEY);
+
+####
+DELIMITER //
+
+CREATE PROCEDURE InsertIntoUser(IN f_name_in VARCHAR(50), IN l_name_in VARCHAR(50), IN mail_in VARCHAR(50))
+BEGIN
+ INSERT INTO User(f_name,l_name,mail) VALUES(f_name_in,l_name_in,mail_in);
+END //
+
+DELIMITER ;
+
+######
+DELIMITER //
+
+CREATE PROCEDURE InsertIntoProject()
+BEGIN
+ INSERT INTO Project(name) VALUES("My own");
+ INSERT INTO Project(name) VALUES("Outcons");
+ INSERT INTO Project(name) VALUES("Free time");
+END //
+
+DELIMITER ;
+
+####
+DELIMITER //
+
+CREATE PROCEDURE InsertIntoTimeLog(IN user_in INT, IN project_in INT, IN time_in FLOAT, IN date_in DATE)
+BEGIN
+ INSERT INTO Timelog(user, project, time, date) VALUES(user_in, project_in, time_in, date_in);
+END //
+
+DELIMITER ;
+
+####
+DELIMITER //
+
+CREATE PROCEDURE CleanTables()
+BEGIN
+ TRUNCATE TABLE Timelog;
+ TRUNCATE TABLE Project;
+ SET foreign_key_checks = 0;
+ TRUNCATE TABLE User;
+ SET foreign_key_checks = 1;
+END //
+
+DELIMITER ;
+
+SELECT ROUTINE_NAME
+FROM INFORMATION_SCHEMA.ROUTINES
+WHERE ROUTINE_TYPE = 'PROCEDURE'
+ AND ROUTINE_SCHEMA = 'timelog';
+
+################
+
+CREATE TEMPORARY TABLE temp_fname (fname VARCHAR(255));
+INSERT INTO temp_fname (fname) VALUES
+ ( "John" ),
+ ( "Gringo" ),
+ ( "Mark" ),
+ ( "Lisa" ),
+ ( "Maria" ),
+ ( "Sonya" ),
+ ( "Philip" ),
+ ( "Jose" ),
+ ( "Lorenzo" ),
+ ( "George" ),
+ ( "Justin" );
+
+CREATE TEMPORARY TABLE temp_lname (lname VARCHAR(255));
+INSERT INTO temp_lname (lname) VALUES
+ ( "Johnson" ),
+ ( "Lamas" ),
+ ( "Jackson" ),
+ ( "Brown" ),
+ ( "Mason" ),
+ ( "Rodriguez" ),
+ ( "Roberts" ),
+ ( "Thomas" ),
+ ( "Rose" ),
+ ( "McDonalds" );
+
+CREATE TEMPORARY TABLE temp_mail (mail VARCHAR(255));
+INSERT INTO temp_mail (mail) VALUES
+ ( "hotmail.com" ),
+ ( "gmail.com" ),
+ ( "live.com" );
+
+INSERT INTO User (f_name, l_name, mail)
+SELECT
+ (SELECT fname FROM temp_fname ORDER BY RAND() LIMIT 1),
+ (SELECT lname FROM temp_lname ORDER BY RAND() LIMIT 1),
+ (SELECT mail FROM temp_mail ORDER BY RAND() LIMIT 1)
+FROM
+ (SELECT 1 FROM information_schema.tables LIMIT 100) AS temp;
+
+UPDATE User
+SET User.mail = CONCAT(User.f_name,".", User.l_name,"@", User.mail);
+
+DELIMITER $$
+
+CREATE PROCEDURE fill_timelog ()
+BEGIN
+ DECLARE j INT DEFAULT 1;
+ DECLARE users INT DEFAULT 1;
+ DECLARE logs INT;
+ DECLARE hours FLOAT;
+ DECLARE project INT;
+ DECLARE curDate DATE DEFAULT "2024-11-18";
+
+WHILE users <= 100 DO
+ SET logs = FLOOR(1+(RAND()*20));
+ SET j=1;
+ WHILE j <= logs DO
+ SET hours = (RAND() * (8 - 0.25)) + 0.25;
+ SET curDate = DATE_ADD(curDate, INTERVAL 1 DAY);
+ SET project = FLOOR(1+(RAND()*3));
+ INSERT INTO Timelog (user, project, date,time ) VALUES (users,project,curDate,hours);
+ SET j=j+1;
+ END WHILE;
+ SET users=users+1;
+END WHILE;
+END$$
+
+DELIMITER ;
+
+
+ SELECT t.user,t.date,t.project,u.f_name,u.l_name,p.name,SUM(t.time) as total_time
+ FROM Timelog t
+ INNER JOIN Project p ON p.id=t.project
+ INNER JOIN User u ON u.id=t.user
+ GROUP BY t.user
+ ORDER BY total_time DESC
+ LIMIT 10;
+
diff --git a/backend/sql/current.sql b/backend/sql/current.sql
new file mode 100644
index 0000000..14b9b21
--- /dev/null
+++ b/backend/sql/current.sql
@@ -0,0 +1,147 @@
+CREATE TABLE User ( f_name VARCHAR(50) , l_name VARCHAR(50) NOT NULL, mail VARCHAR(50) NOT NULL, id INT AUTO_INCREMENT PRIMARY KEY);
+CREATE TABLE Timelog( user INT, project INT, date DATE, time FLOAT, id INT AUTO_INCREMENT PRIMARY KEY, FOREIGN KEY (user) REFERENCES User (id));
+CREATE TABLE Project( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));
+
+
+DELIMITER $$
+
+CREATE PROCEDURE CleanTables()
+BEGIN
+END $$
+
+DELIMITER ;
+
+
+DELIMITER $$
+CREATE PROCEDURE InitDB()
+BEGIN
+DECLARE i INT DEFAULT 1;
+TRUNCATE TABLE Timelog;
+TRUNCATE TABLE Project;
+SET foreign_key_checks = 0;
+TRUNCATE TABLE User;
+SET foreign_key_checks = 1;
+
+INSERT INTO Project(name) VALUES("My own"),("Outcons"),("Free Time");
+
+CREATE TEMPORARY TABLE temp_fname (fname VARCHAR(255));
+INSERT INTO temp_fname (fname) VALUES
+ ( "John" ),
+ ( "Gringo" ),
+ ( "Mark" ),
+ ( "Lisa" ),
+ ( "Maria" ),
+ ( "Sonya" ),
+ ( "Philip" ),
+ ( "Jose" ),
+ ( "Lorenzo" ),
+ ( "George" ),
+ ( "Justin" );
+
+CREATE TEMPORARY TABLE temp_lname (lname VARCHAR(255));
+INSERT INTO temp_lname (lname) VALUES
+ ( "Johnson" ),
+ ( "Lamas" ),
+ ( "Jackson" ),
+ ( "Brown" ),
+ ( "Mason" ),
+ ( "Rodriguez" ),
+ ( "Roberts" ),
+ ( "Thomas" ),
+ ( "Rose" ),
+ ( "McDonalds" );
+
+CREATE TEMPORARY TABLE temp_mail (mail VARCHAR(255));
+INSERT INTO temp_mail (mail) VALUES
+ ( "hotmail.com" ),
+ ( "gmail.com" ),
+ ( "live.com" );
+
+WHILE i <= 100 DO
+ INSERT INTO User (f_name, l_name, mail)
+ SELECT
+ (SELECT fname FROM temp_fname ORDER BY RAND() LIMIT 1),
+ (SELECT lname FROM temp_lname ORDER BY RAND() LIMIT 1),
+ (SELECT mail FROM temp_mail ORDER BY RAND() LIMIT 1);
+ SET i = i + 1;
+END WHILE;
+
+UPDATE User
+SET User.mail = CONCAT(User.f_name,".", User.l_name,"@", User.mail);
+
+CALL fill_timelog();
+DROP TABLE temp_mail;
+DROP TABLE temp_fname;
+DROP TABLE temp_lname;
+END$$
+DELIMITER ;
+
+DELIMITER $$
+
+CREATE PROCEDURE fill_timelog ()
+BEGIN
+ DECLARE j INT DEFAULT 1;
+ DECLARE users INT DEFAULT 1;
+ DECLARE logs INT;
+ DECLARE hours FLOAT;
+ DECLARE project INT;
+ DECLARE curDate DATE DEFAULT '2024-11-18';
+ DECLARE h2 INT;
+
+WHILE users <= 100 DO
+ SET logs = FLOOR(1+(RAND()*20));
+ SET j=1;
+ WHILE j <= logs DO
+ SET project = FLOOR(1+(RAND()*3));
+ SET curDate = DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND() * DATEDIFF('2020-02-01', '2020-01-01')) DAY);
+ SET hours = (RAND() * (8 - 0.25)) + 0.25;
+
+ SELECT SUM(time) INTO h2
+ FROM Timelog
+ WHERE date = curdate && user = users ;
+
+ WHILE (h2+hours) > 8 DO
+ SET curDate = DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND() * DATEDIFF('2020-02-01', '2020-01-01')) DAY);
+
+ SELECT SUM(time)INTO h2
+ FROM Timelog
+ WHERE date = curdate && user = users ;
+
+ END WHILE;
+ INSERT INTO Timelog (user, project, date,time ) VALUES (users,project,curDate,hours);
+ SET j=j+1;
+ END WHILE;
+ SET users=users+1;
+END WHILE;
+END$$
+
+DELIMITER ;
+
+##
+-- get data
+SELECT t.time,t.date,p.name,u.f_name,u.l_name,u.mail FROM Timelog t INNER JOIN Project p ON p.id=t.project INNER JOIN User u ON u.id=t.user;
+-- old timelog with adding each on a new day
+CREATE PROCEDURE fill_timelog ()
+BEGIN
+ DECLARE j INT DEFAULT 1;
+ DECLARE users INT DEFAULT 1;
+ DECLARE logs INT;
+ DECLARE hours FLOAT;
+ DECLARE project INT;
+ DECLARE curDate DATE DEFAULT "2024-11-18";
+
+WHILE users <= 100 DO
+ SET logs = FLOOR(1+(RAND()*20));
+ SET j=1;
+ WHILE j <= logs DO
+ SET hours = (RAND() * (8 - 0.25)) + 0.25;
+ SET project = FLOOR(1+(RAND()*3));
+ SET curDate = DATE_ADD(curDate, INTERVAL 1 DAY);
+ INSERT INTO Timelog (user, project, date,time ) VALUES (users,project,curDate,hours);
+ SET j=j+1;
+ END WHILE;
+ SET users=users+1;
+END WHILE;
+END$$
+
+DELIMITER ;
diff --git a/backendCS.Tests/GlobalUsings.cs b/backendCS.Tests/GlobalUsings.cs
deleted file mode 100644
index ab67c7e..0000000
--- a/backendCS.Tests/GlobalUsings.cs
+++ /dev/null
@@ -1 +0,0 @@
-global using Microsoft.VisualStudio.TestTools.UnitTesting;
\ No newline at end of file
diff --git a/backendCS.Tests/UnitTest1.cs b/backendCS.Tests/UnitTest1.cs
deleted file mode 100644
index 2bb7fc4..0000000
--- a/backendCS.Tests/UnitTest1.cs
+++ /dev/null
@@ -1,158 +0,0 @@
-namespace backendCs.Test;
-
-using System.Net.Http;
-using System.Text;
-
-[TestClass]
-public class UnitTest1
-{
- [TestMethod]
- public async Task TestMethodReset()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync("http://localhost:5000/api/reset");
-
- Assert.AreEqual((int)response.StatusCode, 200);
- }
-
- [TestMethod]
- public async Task TestMethodGetall1()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync(
- "http://localhost:5000/api/getall?offset=10"
- );
-
- Assert.AreEqual((int)response.StatusCode, 200);
- }
-
- [TestMethod]
- public async Task TestMethodGetall2()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync(
- "http://localhost:5000/api/getall?offset="
- );
-
- Assert.AreEqual((int)response.StatusCode, 400);
- }
-
- [TestMethod]
- public async Task TestMethodGetall3()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync(
- "http://localhost:5000/api/getall?offset=10&from=2020-01-01&to=2024-01-01&orderby=time&order=true"
- );
-
- Assert.AreEqual((int)response.StatusCode, 200);
- }
-
- [TestMethod]
- public async Task TestMethodGettopten1()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync(
- "http://localhost:5000/api/gettopten?from=2000-01-01&to=2024-01-01&filterby=project"
- );
-
- Assert.AreEqual((int)response.StatusCode, 200);
- }
-
- [TestMethod]
- public async Task TestMethodGettopten2()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync(
- "http://localhost:5000/api/gettopten?from=2000-01-01&to=2024-01-01&filterby=user"
- );
-
- Assert.AreEqual((int)response.StatusCode, 200);
- }
-
- [TestMethod]
- public async Task TestMethodGettopten3()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync(
- "http://localhost:5000/api/gettopten?to=2024-01-01&filterby=project"
- );
-
- Assert.AreEqual((int)response.StatusCode, 400);
- }
-
- [TestMethod]
- public async Task TestMethodGettopten4()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync(
- "http://localhost:5000/api/gettopten?from=2000-01-01&filterby=project"
- );
-
- Assert.AreEqual((int)response.StatusCode, 400);
- }
-
- [TestMethod]
- public async Task TestMethodGettopten5()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync(
- "http://localhost:5000/api/gettopten?from=2000-01-01&to=2024-01-01"
- );
-
- Assert.AreEqual((int)response.StatusCode, 400);
- }
-
- [TestMethod]
- public async Task TestMethodGetuser1()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync(
- "http://localhost:5000/api/getuser?userid=1"
- );
-
- Assert.AreEqual((int)response.StatusCode, 200);
- }
-
- [TestMethod]
- public async Task TestMethodGetuser2()
- {
- using HttpClient client = new();
- HttpResponseMessage response = await client.GetAsync("http://localhost:5000/api/getuser");
-
- Assert.AreEqual((int)response.StatusCode, 400);
- }
-
- [TestMethod]
- public async Task TestMethodRegister1()
- {
- using HttpClient client = new();
- // Make a GET request to a URL
- var jsonData =
- "{ \"f_name\": \"donna\", \"l_name\": \"cow\", \"mail\": \"tombo@mail.com\", \"password\": \"1234567890\" }";
- var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
-
- HttpResponseMessage response = await client.PostAsync(
- "http://localhost:5000/api/register",
- content
- );
-
- Assert.AreEqual((int)response.StatusCode, 200);
- }
-
- [TestMethod]
- public async Task TestMethodLogin()
- {
- using HttpClient client = new();
- // Make a GET request to a URL
- var jsonData = "{ \"mail\": \"tombo@mail.com\", \"password\": \"1234567890\" }";
- var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
-
- HttpResponseMessage response = await client.PostAsync(
- "http://localhost:5000/api/login",
- content
- );
-
- Assert.AreEqual((int)response.StatusCode, 200);
- }
-}
diff --git a/backendCS.Tests/backendCs.Test.csproj b/backendCS.Tests/backendCs.Test.csproj
deleted file mode 100644
index 30aa586..0000000
--- a/backendCS.Tests/backendCs.Test.csproj
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
- false
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/backendCS/Program.cs b/backendCS/Program.cs
deleted file mode 100644
index 77d622f..0000000
--- a/backendCS/Program.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-using System.Net;
-using System.Text;
-
-namespace TimelogBackend;
-
-class Program
-{
- static void Main()
- {
- // create server
- HttpListener listener = new();
- // routes need to be added first
- listener.Prefixes.Add("http://localhost:5000/api/getall/");
- listener.Prefixes.Add("http://localhost:5000/api/gettopten/");
- listener.Prefixes.Add("http://localhost:5000/api/getuser/");
- listener.Prefixes.Add("http://localhost:5000/api/reset/");
- listener.Prefixes.Add("http://localhost:5000/api/createp/");
- listener.Prefixes.Add("http://localhost:5000/api/register/");
- listener.Prefixes.Add("http://localhost:5000/api/login/");
- listener.Prefixes.Add("http://localhost:5000/api/createlog/");
-
- // listen
- listener.Start();
- Console.WriteLine("Server is listening on http://localhost:5000/");
- while (true)
- {
- HttpListenerContext context = listener.GetContext();
- HttpListenerRequest request = context.Request;
- HttpListenerResponse response = context.Response;
- response.Headers.Add("Access-Control-Allow-Origin", "http://localhost:5173");
- response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
- response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization");
-
- // url after localhost:5000/
- string uri;
- if (request != null && request.Url != null)
- uri = request.Url.AbsolutePath;
- else
- return;
- switch (request.HttpMethod)
- {
- case "GET":
- HandleGet(uri, request, response);
- break;
- case "POST":
- HandlePost(uri, request, response);
- break;
- default:
- HandleMissingPath(response);
- break;
- }
- }
- }
-
- private static void HandlePost(
- string uri,
- HttpListenerRequest request,
- HttpListenerResponse response
- )
- {
- if (request.HasEntityBody)
- switch (uri)
- {
- case "/api/register":
- Register.HandleRequest(request, response);
- break;
- case "/api/login":
- Login.HandleRequest(request, response);
- break;
- case "/api/createlog":
- CreateLog.HandleRequest(request, response);
- break;
- default:
- HandleMissingPath(response);
- break;
- }
- else
- {
- HandleMissingPath(response);
- }
- }
-
- private static void HandleGet(
- string uri,
- HttpListenerRequest request,
- HttpListenerResponse response
- )
- {
- switch (uri)
- {
- case "/api/reset":
- Reset.HandleRequest(response);
- break;
- case "/api/getall":
- Getall.HandleRequest(request, response);
- break;
- case "/api/gettopten":
- Gettopten.HandleRequest(request, response);
- break;
- case "/api/getuser":
- Getuser.HandleRequest(request, response);
- break;
- case "/api/createp":
- CreateProcedure.HandleRequest(response);
- break;
- default:
- HandleMissingPath(response);
- break;
- }
- }
-
- private static void HandleMissingPath(HttpListenerResponse response)
- {
- response.StatusCode = 404;
- string errorMessage = "Not Found";
- byte[] buffer = Encoding.UTF8.GetBytes(errorMessage);
- response.ContentType = "text/plain";
- response.ContentLength64 = buffer.Length;
- response.OutputStream.Write(buffer, 0, buffer.Length);
- response.OutputStream.Write(buffer, 0, buffer.Length);
- }
-}
diff --git a/backendCS/Route.cs b/backendCS/Route.cs
deleted file mode 100644
index ad80a69..0000000
--- a/backendCS/Route.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System.Net;
-using System.Text;
-using System.Text.RegularExpressions;
-
-namespace TimelogBackend;
-
-public abstract class Route
-{
- public static string connectionString =
- "server=127.0.0.1;uid=monty;pwd=some_pass;database=timelog";
-
- public static void SendError(HttpListenerResponse response, Exception ex)
- {
- response.StatusCode = (int)HttpStatusCode.BadRequest;
- string errorMessage = $"Error: {ex.Message}";
- byte[] buffer = Encoding.UTF8.GetBytes(errorMessage);
- response.ContentType = "text/plain";
- response.ContentLength64 = buffer.Length;
- response.OutputStream.Write(buffer, 0, buffer.Length);
- response.Close();
- }
-
- public static void SendSuccess(HttpListenerResponse response)
- {
- response.StatusCode = (int)HttpStatusCode.OK;
- response.StatusDescription = "Status OK";
- response.Close();
- }
-
- public static void SendSuccess(HttpListenerResponse response, string jsonResponse)
- {
- response.StatusCode = (int)HttpStatusCode.OK;
- response.StatusDescription = "Status OK";
- byte[] buffer = Encoding.UTF8.GetBytes(jsonResponse);
- response.ContentType = "application/json";
- response.ContentLength64 = buffer.Length;
- response.OutputStream.Write(buffer, 0, buffer.Length);
- response.Close();
- }
-
- public static bool ValidateDate(string date)
- {
- Regex regex = new(@"^\d{4}-\d{2}-\d{2}$");
- return regex.IsMatch(date);
- }
- /* public virtual void run(MySqlConnection conn, HttpListenerRequest request, HttpListenerResponse response) { } */
-}
diff --git a/backendCS/TimelogBackend.csproj b/backendCS/TimelogBackend.csproj
deleted file mode 100644
index 7b59323..0000000
--- a/backendCS/TimelogBackend.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- Exe
- net8.0
- enable
- enable
-
-
-
-
-
-
-
-
-
diff --git a/backendCS/routes/CreateLog.cs b/backendCS/routes/CreateLog.cs
deleted file mode 100644
index be41d94..0000000
--- a/backendCS/routes/CreateLog.cs
+++ /dev/null
@@ -1,134 +0,0 @@
-using System.IdentityModel.Tokens.Jwt;
-using System.Net;
-using System.Text;
-using System.Text.RegularExpressions;
-using Microsoft.IdentityModel.Tokens;
-using MySql.Data.MySqlClient;
-using Newtonsoft.Json.Linq;
-
-namespace TimelogBackend;
-
-public class CreateLog : Route
-{
- private static readonly string secretKey =
- "stronk-key-much-sercret-much-more-stronk-stronk-key-much-sercret-much-more-stronk";
-
- public static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
- {
- try
- {
- // check header
- var headers = request.Headers;
- string token = headers["token"] ?? "";
- if (!string.IsNullOrEmpty(token) && !ValidateToken(token))
- {
- throw new Exception("Invalid token");
- }
-
- MySqlCommand cmd = new();
-
- string body;
- using (StreamReader bodyReader = new(request.InputStream, request.ContentEncoding))
- {
- body = bodyReader.ReadToEnd();
- }
- JObject jsonObject = JObject.Parse(body);
- string project = jsonObject["project"]?.ToString() ?? "";
- string time = jsonObject["time"]?.ToString() ?? "";
- string date = jsonObject["date"]?.ToString() ?? "";
-
- // TODO check if the hours on given date don't combine to more
- // than 8
-
- if (!ValidateTime(time))
- {
- throw new Exception("Incorrect date format");
- }
- if (!ValidateDate(date))
- {
- throw new Exception("Incorrect date format");
- }
- // validate user
- string? usernameClaim = GetUserFromToken(token);
- if (string.IsNullOrEmpty(usernameClaim))
- {
- throw new Exception("wrong user id");
- }
- // validate project
- // TODO better project validation
- if (string.IsNullOrEmpty(project))
- {
- throw new Exception("wrong project");
- }
- SaveTimeLogToDatabase(usernameClaim, project, date, time);
- SendSuccess(response);
- }
- catch (Exception ex)
- {
- SendError(response, ex);
- }
- }
-
- private static bool ValidateToken(string token)
- {
- try
- {
- var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
- var tokenHandler = new JwtSecurityTokenHandler();
- var validationParameters = new TokenValidationParameters
- {
- ValidateIssuer = true,
- ValidateAudience = true,
- ValidateLifetime = true,
- ValidIssuer = "TimeLogServer",
- ValidAudience = "TimeLogWebsite",
- IssuerSigningKey = key,
- };
-
- var principal = tokenHandler.ValidateToken(
- token,
- validationParameters,
- out SecurityToken validatedToken
- );
- return validatedToken != null;
- }
- catch
- {
- return false;
- }
- }
-
- private static bool ValidateTime(string time)
- {
- return int.TryParse(time, out int myInt) && myInt >= 0 && myInt <= 8;
- }
-
- private static string GetUserFromToken(string token)
- {
- var handler = new JwtSecurityTokenHandler();
- var jwtToken = handler.ReadJwtToken(token);
- string? usernameClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == "user")?.Value;
- return string.IsNullOrEmpty(usernameClaim) ? "" : usernameClaim;
- }
-
- private static void SaveTimeLogToDatabase(
- string username,
- string project,
- string date,
- string time
- )
- {
- using MySqlConnection conn = new(connectionString);
- conn.Open();
- using MySqlCommand cmd = new(
- @"INSERT INTO Timelog(user, project, date, time)
- VALUES(@user, @project, @date, @time);",
- conn
- );
- cmd.Parameters.AddWithValue("@user", username);
- cmd.Parameters.AddWithValue("@project", project);
- cmd.Parameters.AddWithValue("@date", date);
- cmd.Parameters.AddWithValue("@time", time);
- cmd.ExecuteNonQuery();
- }
-}
diff --git a/backendCS/routes/CreateProcedure.cs b/backendCS/routes/CreateProcedure.cs
deleted file mode 100644
index 074a2ff..0000000
--- a/backendCS/routes/CreateProcedure.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using System.Net;
-using MySql.Data.MySqlClient;
-
-namespace TimelogBackend;
-
-public class CreateProcedure : Route
-{
- public static void HandleRequest(HttpListenerResponse response)
- {
- try
- {
- MySqlCommand cmd = new();
-
- using MySqlConnection conn = new(connectionString);
- conn.Open();
- cmd.Connection = conn;
- cmd.CommandText =
- @" CREATE PROCEDURE fill_timelog ()
- BEGIN
- DECLARE j INT DEFAULT 1;
- DECLARE users INT DEFAULT 1;
- DECLARE logs INT;
- DECLARE hours FLOAT;
- DECLARE project INT;
- DECLARE curDate DATE DEFAULT '2024-11-18';
- DECLARE h2 INT;
-
- WHILE users <= 100 DO
- SET logs = FLOOR(1 + (RAND() * 20));
- SET j = 1;
- WHILE j <= logs DO
- SET project = FLOOR(1 + (RAND() * 3));
- SET curDate = DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND() * DATEDIFF('2020-02-01', '2020-01-01')) DAY);
- SET hours = (RAND() * (8 - 0.25)) + 0.25;
-
- SELECT SUM(time) INTO h2
- FROM Timelog
- WHERE date = curdate && user = users;
-
- WHILE(h2 + hours) > 8 DO
- SET curDate = DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND() * DATEDIFF('2020-02-01', '2020-01-01')) DAY);
-
- SELECT SUM(time)INTO h2
- FROM Timelog
- WHERE date = curdate && user = users;
-
- END WHILE;
- INSERT INTO Timelog(user, project, date, time) VALUES(users, project, curDate, hours);
- SET j = j + 1;
- END WHILE;
- SET users = users + 1;
- END WHILE;
- END;";
- cmd.ExecuteNonQuery();
- cmd.CommandText =
- @"CREATE PROCEDURE InitDB()
- BEGIN
- DECLARE i INT DEFAULT 1;
- TRUNCATE TABLE Timelog;
- TRUNCATE TABLE Project;
- SET foreign_key_checks = 0;
- TRUNCATE TABLE User;
- SET foreign_key_checks = 1;
-
- INSERT INTO Project(name) VALUES('My own'),('Outcons'),('Free Time');
-
- CREATE TEMPORARY TABLE temp_fname (fname VARCHAR(255));
- INSERT INTO temp_fname (fname) VALUES
- ( 'John' ),
- ( 'Gringo' ),
- ( 'Mark' ),
- ( 'Lisa' ),
- ( 'Maria' ),
- ( 'Sonya' ),
- ( 'Philip' ),
- ( 'Jose' ),
- ( 'Lorenzo' ),
- ( 'George' ),
- ( 'Justin' );
-
- CREATE TEMPORARY TABLE temp_lname (lname VARCHAR(255));
- INSERT INTO temp_lname (lname) VALUES
- ( 'Johnson' ),
- ( 'Lamas' ),
- ( 'Jackson' ),
- ( 'Brown' ),
- ( 'Mason' ),
- ( 'Rodriguez' ),
- ( 'Roberts' ),
- ( 'Thomas' ),
- ( 'Rose' ),
- ( 'McDonalds' );
-
- CREATE TEMPORARY TABLE temp_mail (mail VARCHAR(255));
- INSERT INTO temp_mail (mail) VALUES
- ( 'hotmail.com' ),
- ( 'gmail.com' ),
- ( 'live.com' );
-
- WHILE i <= 100 DO
- INSERT INTO User (f_name, l_name, mail)
- SELECT
- (SELECT fname FROM temp_fname ORDER BY RAND() LIMIT 1),
- (SELECT lname FROM temp_lname ORDER BY RAND() LIMIT 1),
- (SELECT mail FROM temp_mail ORDER BY RAND() LIMIT 1);
- SET i = i + 1;
- END WHILE;
-
- UPDATE User
- SET User.mail = CONCAT(User.f_name,'.', User.l_name,'@', User.mail);
-
- CALL fill_timelog();
- DROP TABLE temp_mail;
- DROP TABLE temp_fname;
- DROP TABLE temp_lname;
- END;";
- cmd.ExecuteNonQuery();
- // prepare response
- SendSuccess(response);
- }
- catch (Exception ex)
- {
- SendError(response, ex);
- }
- }
-}
diff --git a/backendCS/routes/Getall.cs b/backendCS/routes/Getall.cs
deleted file mode 100644
index c0cf49a..0000000
--- a/backendCS/routes/Getall.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using System.Net;
-using MySql.Data.MySqlClient;
-using Newtonsoft.Json;
-
-namespace TimelogBackend;
-
-// there should be a better way to deal with data comming from sql
-public class Log
-{
- public object? FName { get; set; }
- public object? LName { get; set; }
- public object? Mail { get; set; }
- public object? Name { get; set; }
- public object? Time { get; set; }
- public object? Date { get; set; }
- public object? User { get; set; }
-}
-
-public class Getall : Route
-{
- private static string ConstructQuery(
- string from,
- string to,
- string order,
- string offset,
- string sortby
- )
- {
- string mainQuery =
- @"SELECT u.f_name,u.l_name,u.mail,p.name,t.time,t.date,t.user
- FROM Timelog t
- INNER JOIN Project p ON p.id=t.project
- INNER JOIN User u ON u.id=t.user ";
- // this shenanigan is needed to remove the "" around group by
- string offsetQuery = " LIMIT 10 OFFSET " + offset + ";";
- // depending on the incoming parameters construct a Query
- if (!string.IsNullOrEmpty(to) && !string.IsNullOrEmpty(from))
- {
- mainQuery += AddWhereClause(from, to);
- }
- if (!string.IsNullOrEmpty(sortby))
- {
- mainQuery += AddSortBy(sortby, order);
- }
- if (!int.TryParse(offset, out int myInt) || myInt < 0)
- throw new Exception("Incorect offset");
-
- return mainQuery + offsetQuery;
- }
-
- private static string AddWhereClause(string from, string to)
- {
- if (!ValidateDate(to) || !ValidateDate(from))
- {
- throw new Exception("Incorrect date format");
- }
- string whereQuery = " WHERE t.date BETWEEN @from AND @to ";
- return whereQuery;
- }
-
- private static string AddSortBy(string sortby, string order)
- {
- List validSorting = ["f_name", "l_name", "mail", "time", "date", "user", "project"];
- if (!validSorting.Contains(sortby))
- {
- throw new Exception("Incorrect sorting value");
- }
- string orderQuery = " ORDER BY " + sortby + " " + order;
- return orderQuery;
- }
-
- private static List ExtractDataFromDB(MySqlCommand cmd)
- {
- using MySqlConnection conn = new(connectionString);
- conn.Open();
- cmd.Connection = conn;
- // execute query and read results
- MySqlDataReader reader = cmd.ExecuteReader();
-
- List entries = [];
- while (reader.Read())
- {
- entries.Add(
- new Log
- {
- FName = reader["f_name"],
- LName = reader["l_name"],
- User = reader["user"],
- Date = reader["date"],
- Name = reader["name"],
- Time = reader["time"],
- Mail = reader["mail"],
- }
- );
- }
- return entries;
- }
-
- public static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
- {
- try
- {
- // extract data from url
- var queryString = request.QueryString;
- string? from = queryString["from"] ?? "";
- string? to = queryString["to"] ?? "";
- string? sortby = queryString["sortby"] ?? "";
- string? offset = queryString["offset"] ?? "";
- string? order = queryString["order"] ?? "";
- order = order == "true" ? "ASC" : "DESC";
- // SQL
- MySqlCommand cmd = new(ConstructQuery(from, to, order, offset, sortby));
- cmd.Parameters.AddWithValue("@from", from);
- cmd.Parameters.AddWithValue("@to", to);
- var entries = ExtractDataFromDB(cmd);
-
- // serialize JSON
- string jsonResponse = JsonConvert.SerializeObject(entries);
- SendSuccess(response, jsonResponse);
- }
- catch (Exception ex)
- {
- SendError(response, ex);
- }
- }
-}
diff --git a/backendCS/routes/Gettopten.cs b/backendCS/routes/Gettopten.cs
deleted file mode 100644
index 7c1453a..0000000
--- a/backendCS/routes/Gettopten.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using System.Net;
-using MySql.Data.MySqlClient;
-using Newtonsoft.Json;
-
-namespace TimelogBackend;
-
-public class TopTen
-{
- public object? User { get; set; }
- public object? Date { get; set; }
- public object? Project { get; set; }
- public object? FName { get; set; }
- public object? LName { get; set; }
- public object? Name { get; set; }
- public object? TotalTime { get; set; }
-}
-
-public class Gettopten : Route
-{
- private static List ExtractDataFromDB(MySqlCommand cmd)
- {
- using MySqlConnection conn = new(connectionString);
- cmd.Connection = conn;
- conn.Open();
- // Execute the query and read the results
- MySqlDataReader reader = cmd.ExecuteReader();
- List entries = [];
- while (reader.Read())
- {
- entries.Add(
- new TopTen
- {
- User = reader["user"],
- Date = reader["date"],
- Project = reader["project"],
- FName = reader["f_name"],
- LName = reader["l_name"],
- Name = reader["name"],
- TotalTime = reader["total_time"],
- }
- );
- }
- return entries;
- }
-
- private static void ValidateQueryStrings(string from, string to, string filterBy)
- {
- if (!string.IsNullOrEmpty(to) && !string.IsNullOrEmpty(from))
- {
- ValidateDate(to);
- ValidateDate(from);
- }
- else
- {
- throw new Exception("Empty date format");
- }
- if (string.IsNullOrEmpty(filterBy))
- {
- throw new Exception("Empty filterby");
- }
- if (filterBy != "user" && filterBy != "project")
- {
- throw new Exception("Incorrect filterby");
- }
- }
-
- public static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
- {
- try
- {
- var queryString = request.QueryString;
- string? from = queryString["from"] ?? "";
- string? to = queryString["to"] ?? "";
- string? filterBy = queryString["filterBy"] ?? "";
- ValidateQueryStrings(from, to, filterBy);
- string query =
- @"SELECT t.user,t.date,t.project,u.f_name,u.l_name,p.name,SUM(t.time) as total_time
- FROM Timelog t
- INNER JOIN Project p ON p.id=t.project
- INNER JOIN User u ON u.id=t.user
- WHERE t.date BETWEEN @from AND @to
- GROUP BY "
- + filterBy
- + " ORDER BY total_time DESC LIMIT 10;";
- MySqlCommand cmd = new(query);
- cmd.Parameters.AddWithValue("@from", from);
- cmd.Parameters.AddWithValue("@to", to);
-
- var entries = ExtractDataFromDB(cmd);
- string jsonResponse = JsonConvert.SerializeObject(entries);
- SendSuccess(response, jsonResponse);
- }
- catch (Exception ex)
- {
- SendError(response, ex);
- }
- }
-}
diff --git a/backendCS/routes/Getuser.cs b/backendCS/routes/Getuser.cs
deleted file mode 100644
index 498a4d3..0000000
--- a/backendCS/routes/Getuser.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System.Dynamic;
-using System.Net;
-using MySql.Data.MySqlClient;
-using Newtonsoft.Json;
-
-namespace TimelogBackend;
-
-public class Getuser : Route
-{
- private static dynamic ExtractDataFromDB(MySqlCommand cmd)
- {
- using MySqlConnection conn = new(connectionString);
- conn.Open();
- cmd.Connection = conn;
- // execute query and read results
- MySqlDataReader reader = cmd.ExecuteReader();
- dynamic expando = new ExpandoObject();
- while (reader.Read())
- {
- ((IDictionary)expando)[reader["name"].ToString()] = reader[
- "SUM(t.time)"
- ];
- }
- return expando;
- }
-
- public static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
- {
- try
- {
- var queryString = request.QueryString;
- string? userid = queryString["userid"];
- if (string.IsNullOrEmpty(userid))
- {
- throw new Exception("Missing userid");
- }
- // prepare SQL query
- string query =
- @"SELECT p.name, SUM(t.time)
- FROM Timelog t
- INNER JOIN Project p ON p.id=t.project
- INNER JOIN User u ON u.id=t.user
- WHERE User = @userid
- GROUP BY name;";
- MySqlCommand cmd = new(query);
- cmd.Parameters.AddWithValue("@userid", userid);
-
- var expando = ExtractDataFromDB(cmd);
- // serialize JSON
- string jsonResponse = JsonConvert.SerializeObject(expando);
- // prepare response
- SendSuccess(response, jsonResponse);
- }
- catch (Exception ex)
- {
- SendError(response, ex);
- }
- }
-}
diff --git a/backendCS/routes/Login.cs b/backendCS/routes/Login.cs
deleted file mode 100644
index 45347b6..0000000
--- a/backendCS/routes/Login.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-using System.IdentityModel.Tokens.Jwt;
-using System.Net;
-using System.Security.Claims;
-using System.Security.Cryptography;
-using System.Text;
-using Microsoft.IdentityModel.Tokens;
-using MySql.Data.MySqlClient;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace TimelogBackend;
-
-public class Login : Route
-{
- private static readonly string secretKey =
- "stronk-key-much-sercret-much-more-stronk-stronk-key-much-sercret-much-more-stronk";
-
- public static string GenerateToken(string user)
- {
- var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
- var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
-
- var token = new JwtSecurityToken(
- issuer: "TimeLogServer",
- audience: "TimeLogWebsite",
- claims: [new Claim("user", user)],
- expires: DateTime.Now.AddHours(2),
- signingCredentials: creds
- );
-
- return new JwtSecurityTokenHandler().WriteToken(token);
- }
-
- public static bool VerifyPassword(string enteredPassword, string storedHash)
- {
- byte[] hashBytes = Convert.FromBase64String(storedHash);
-
- byte[] salt = new byte[16];
- Array.Copy(hashBytes, 0, salt, 0, 16);
-
- using var pbkdf2 = new Rfc2898DeriveBytes(
- enteredPassword,
- salt,
- 10000,
- HashAlgorithmName.SHA256
- );
- byte[] newHash = pbkdf2.GetBytes(32);
-
- for (int i = 0; i < 32; i++)
- {
- if (newHash[i] != hashBytes[i + 16])
- return false;
- }
- return true;
- }
-
- private static string ExtractDataFromDB(MySqlCommand cmd, string password)
- {
- using MySqlConnection conn = new(connectionString);
- cmd.Connection = conn;
- conn.Open();
- // execute query and read results
- MySqlDataReader reader = cmd.ExecuteReader();
- string? userId = "";
- string? hashedPass = "";
- while (reader.Read())
- {
- userId = Convert.ToString(reader["id"]);
- hashedPass = reader.GetString("password");
- }
- // check username
- if (string.IsNullOrEmpty(userId))
- {
- throw new Exception("Invalid Username or Password");
- }
- //check password
- if (
- string.IsNullOrEmpty(password)
- || string.IsNullOrEmpty(hashedPass)
- || !VerifyPassword(password, hashedPass)
- )
- {
- throw new Exception("Invalid Username or Password");
- }
- return userId;
- }
-
- public static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
- {
- try
- {
- // extract data from body
- string body;
- using (StreamReader bodyReader = new(request.InputStream, request.ContentEncoding))
- {
- body = bodyReader.ReadToEnd();
- }
- JObject jsonObject = JObject.Parse(body);
- string mail = jsonObject["mail"]?.ToString() ?? "";
- string password = jsonObject["password"]?.ToString() ?? "";
- // prepare SQL query
- string query =
- @"SELECT u.id, password FROM User u
- INNER JOIN Password p ON p.user=u.id
- WHERE mail=@mail;";
- MySqlCommand cmd = new(query);
- cmd.Parameters.AddWithValue("@mail", mail);
-
- var userId = ExtractDataFromDB(cmd, password);
- string? jsonResponse = JsonConvert.SerializeObject(GenerateToken(userId));
- // prepare response
- SendSuccess(response, jsonResponse);
- }
- catch (Exception ex)
- {
- SendError(response, ex);
- }
- }
-}
diff --git a/backendCS/routes/Register.cs b/backendCS/routes/Register.cs
deleted file mode 100644
index 0157cd8..0000000
--- a/backendCS/routes/Register.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using System.Net;
-using System.Security.Cryptography;
-using MySql.Data.MySqlClient;
-using Newtonsoft.Json.Linq;
-
-namespace TimelogBackend;
-
-public class Register : Route
-{
- private static string HashPassword(string password)
- {
- byte[] salt = new byte[16];
- RandomNumberGenerator.Fill(salt);
-
- using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256);
- byte[] hash = pbkdf2.GetBytes(32);
-
- byte[] hashBytes = new byte[48]; // 16 (salt) + 32 (hash)
- Array.Copy(salt, 0, hashBytes, 0, 16);
- Array.Copy(hash, 0, hashBytes, 16, 32);
-
- return Convert.ToBase64String(hashBytes);
- }
-
- public static void HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
- {
- MySqlTransaction? transaction = null;
- try
- {
- // extract parameters from req body
- string body;
- using (StreamReader bodyReader = new(request.InputStream, request.ContentEncoding))
- {
- body = bodyReader.ReadToEnd();
- }
- JObject jsonObject = JObject.Parse(body);
- string f_name = jsonObject["f_name"]?.ToString() ?? "";
- string l_name = jsonObject["l_name"]?.ToString() ?? "";
- string password = jsonObject["password"]?.ToString() ?? "";
- string mail = jsonObject["mail"]?.ToString() ?? "";
-
- // validate parameters
- if (
- string.IsNullOrEmpty(f_name)
- || f_name.Length > 30
- || f_name.Length < 2
- || string.IsNullOrEmpty(l_name)
- || l_name.Length > 30
- || l_name.Length < 2
- || string.IsNullOrEmpty(mail)
- || mail.Length > 50
- || mail.Length < 6
- || string.IsNullOrEmpty(password)
- || password.Length > 30
- || password.Length < 10
- )
- {
- throw new Exception("Wrong parameters");
- }
- // TODO Validate dupes of email
- string query = "INSERT INTO User(f_name,l_name,mail) VALUES(@f_name,@l_name,@mail)";
- MySqlCommand cmd = new(query);
- cmd.Parameters.AddWithValue("@f_name", f_name);
- cmd.Parameters.AddWithValue("@l_name", l_name);
- cmd.Parameters.AddWithValue("@mail", mail);
-
- using MySqlConnection conn = new(connectionString);
- conn.Open();
- transaction = conn.BeginTransaction();
- cmd.Connection = conn;
- cmd.ExecuteNonQuery();
-
- // Get user ID
- cmd.CommandText = "SELECT id FROM User WHERE mail=@mail;";
- MySqlDataReader reader = cmd.ExecuteReader();
- reader.Read();
- var id = reader["id"];
- reader.Close();
-
- // Insert into password
- cmd.CommandText = "INSERT INTO Password(user,password) VALUES(@id,@password)";
- cmd.Parameters.AddWithValue("@password", HashPassword(password));
- cmd.Parameters.AddWithValue("@id", id);
- cmd.ExecuteNonQuery();
- transaction.Commit();
-
- SendSuccess(response);
- }
- catch (Exception ex)
- {
- transaction?.Rollback();
- SendError(response, ex);
- }
- }
-}
diff --git a/backendCS/routes/Reset.cs b/backendCS/routes/Reset.cs
deleted file mode 100644
index 5c9e536..0000000
--- a/backendCS/routes/Reset.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System.Net;
-using MySql.Data.MySqlClient;
-
-namespace TimelogBackend;
-
-public class Reset : Route
-{
- public static void HandleRequest(HttpListenerResponse response)
- {
- try
- {
- // prepare SQL query
- MySqlCommand cmd = new() { CommandText = "CALL InitDB" };
- using (MySqlConnection conn = new MySqlConnection(connectionString))
- {
- cmd.Connection = conn;
- // open connection
- conn.Open();
- // execute query
- cmd.ExecuteNonQuery();
- // set up and send response
- SendSuccess(response);
- }
- }
- catch (Exception ex)
- {
- SendError(response, ex);
- }
- }
-}
diff --git a/backendCS/test.sh b/backendCS/test.sh
deleted file mode 100755
index 147c453..0000000
--- a/backendCS/test.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-
-url="localhost:5000/" # Replace with your URL
-
-# Loop to send 1000 requests
-for i in {1..10000}
-do
- curl -s $url > /dev/null & # Send the request in the background
-done
-
-# Wait for all background processes to finish
-wait
-echo "1000 requests sent!"