diff --git a/package-lock.json b/package-lock.json index 231cd2b..7c434c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@types/express": "^4.17.21", "bcrypt": "^5.1.0", "body-parser": "^1.19.0", "cookie-parser": "^1.4.5", @@ -19,6 +20,7 @@ "express": "^4.18.2", "express-jwt": "^8.4.1", "express-session": "^1.17.3", + "jsdom": "^22.1.0", "jsonwebtoken": "^8.5.1", "mongodb": "^3.6.8", "mysql": "^2.18.1", @@ -269,6 +271,31 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", @@ -282,6 +309,33 @@ "@types/node": "*" } }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -296,11 +350,50 @@ "@types/node": "*" } }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, "node_modules/@types/node": { "version": "18.17.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==" }, + "node_modules/@types/qs": { + "version": "6.9.10", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", + "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -965,6 +1058,61 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" }, + "node_modules/cssstyle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/data-urls": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -973,6 +1121,11 @@ "ms": "2.0.0" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1053,6 +1206,25 @@ "node": ">=6.0.0" } }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, "node_modules/dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -1144,6 +1316,17 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", @@ -1969,20 +2152,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -2237,6 +2406,17 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2252,6 +2432,40 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -2547,6 +2761,11 @@ "node": ">=8" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -2677,6 +2896,98 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", + "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", + "dependencies": { + "abab": "^2.0.6", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", + "decimal.js": "^10.4.3", + "domexception": "^4.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.4", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3228,6 +3539,11 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nwsapi": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3442,6 +3758,17 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3554,6 +3881,11 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -3564,7 +3896,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, "engines": { "node": ">=6" } @@ -3583,6 +3914,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3689,6 +4025,11 @@ "node": ">=4" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.3", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.3.tgz", @@ -3738,6 +4079,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3843,6 +4189,17 @@ "node": ">=6" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -4260,6 +4617,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "node_modules/tar": { "version": "6.1.15", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", @@ -4334,6 +4696,20 @@ "node": "*" } }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -4489,6 +4865,14 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -4506,6 +4890,15 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4535,6 +4928,17 @@ "node": ">= 0.8" } }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/wait-on": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz", @@ -4558,6 +4962,36 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/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==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -4649,6 +5083,19 @@ } } }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 88d64db..031f9cf 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "license": "MIT", "homepage": "https://bitbucket.org/online-mentor/multi-stub#readme", "dependencies": { + "@types/express": "^4.17.21", "bcrypt": "^5.1.0", "body-parser": "^1.19.0", "cookie-parser": "^1.4.5", @@ -34,6 +35,7 @@ "express": "^4.18.2", "express-jwt": "^8.4.1", "express-session": "^1.17.3", + "jsdom": "^22.1.0", "jsonwebtoken": "^8.5.1", "mongodb": "^3.6.8", "mysql": "^2.18.1", diff --git a/server/index.js b/server/index.js index d67bdd2..1b427a7 100644 --- a/server/index.js +++ b/server/index.js @@ -43,9 +43,9 @@ app.use(require('./root')) app.use('/lobsters', require('./routers/lobsters')) app.use('/example', require('./routers/example')) // app.use('/coder', require('./routers/coder')) -app.use('/stc-21-03', require('./routers/stc-21-03')) -app.use('/stc-21', require('./routers/stc')) -app.use('/stc-22-24', require('./routers/stc-22-24')) +//app.use('/stc-21-03', require('./routers/stc-21-03')) +//app.use('/stc-21', require('./routers/stc')) +//app.use('/stc-22-24', require('./routers/stc-22-24')) // app.use('/bushou-api', require('./routers/bushou')) // app.use('/uryndyklar-api', require('./routers/uryndyklar')) @@ -55,9 +55,9 @@ app.use('/stc-22-24', require('./routers/stc-22-24')) // app.use('/task-boss', require('./routers/task-boss')) // app.use('/car-wash', require('./routers/car-wash')) app.use('/zoom-bar', require('./routers/zoom-bar')) -app.use('/basket', require('./routers/basket')) -app.use('/easy-project', require('./routers/easy-project')) -app.use('/sugarbun', require('./routers/sugarbun')) +//app.use('/basket', require('./routers/basket')) +//app.use('/easy-project', require('./routers/easy-project')) +//app.use('/sugarbun', require('./routers/sugarbun')) app.use('/epja-2023-2', require('./routers/epja-2023-2')) require('./routers/hub-video') diff --git a/server/routers/epja-2023-2/index.js b/server/routers/epja-2023-2/index.js index 34c59b3..c46fe4a 100644 --- a/server/routers/epja-2023-2/index.js +++ b/server/routers/epja-2023-2/index.js @@ -3,5 +3,6 @@ const router = express.Router() router.use('/example', require('./example/index')) +router.use('/pen-plotter', require('./pen-plotter/index')) module.exports = router diff --git a/server/routers/epja-2023-2/pen-plotter/.gitignore b/server/routers/epja-2023-2/pen-plotter/.gitignore new file mode 100644 index 0000000..6405610 --- /dev/null +++ b/server/routers/epja-2023-2/pen-plotter/.gitignore @@ -0,0 +1,3 @@ +dist/ +static/ +profiles/ diff --git a/server/routers/epja-2023-2/pen-plotter/index.js b/server/routers/epja-2023-2/pen-plotter/index.js new file mode 100644 index 0000000..4528f8d --- /dev/null +++ b/server/routers/epja-2023-2/pen-plotter/index.js @@ -0,0 +1,35 @@ +const express = require('express') +const router = express.Router() +const fs = require("fs"); +const path = require("path"); + +const BASE_PATH = __dirname; +const STATIC_PATH = `${BASE_PATH}/static`; +// Serve static files +router.use(express.static(path.join(__dirname, './assets/'))) +// Add the required directories +router.use((req, res, next) => { + const directories = ['/static', '/profiles'] + directories.forEach((dir) => { + if (!fs.existsSync(BASE_PATH + dir)) { + fs.mkdirSync(BASE_PATH + dir) + } + }) + next() +}) +// Serve Static generated SVGs +router.get('/static/:name', async (req, res, next) => { + const fileName = req.params.name + const filePath = `${STATIC_PATH}/${fileName}` + + const file = await fs.readFileSync(filePath) + res.setHeader('Content-Type', 'image/svg+xml') + res.send(file) +}) +router.use('/api', require('./routes/api').default) + +router.get('/info', (req, res) => { + res.send('Pen-Plotter backend') +}) + +module.exports = router diff --git a/server/routers/epja-2023-2/pen-plotter/paths.js b/server/routers/epja-2023-2/pen-plotter/paths.js new file mode 100644 index 0000000..799383d --- /dev/null +++ b/server/routers/epja-2023-2/pen-plotter/paths.js @@ -0,0 +1,9 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.STATIC_PATH = exports.PROFILES_PATH = exports.BASE_PATH = void 0; +const BASE_PATH = __dirname; +exports.BASE_PATH = BASE_PATH; +const PROFILES_PATH = `${BASE_PATH}/profiles`; +exports.PROFILES_PATH = PROFILES_PATH; +const STATIC_PATH = `${BASE_PATH}/static`; +exports.STATIC_PATH = STATIC_PATH; diff --git a/server/routers/epja-2023-2/pen-plotter/routes/api.js b/server/routers/epja-2023-2/pen-plotter/routes/api.js new file mode 100644 index 0000000..61a9b08 --- /dev/null +++ b/server/routers/epja-2023-2/pen-plotter/routes/api.js @@ -0,0 +1,10 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const router = express_1.default.Router(); +router.use("/users", require("./sub-routes/users").default); +router.use("/svg", require("./sub-routes/svg").default); +exports.default = router; diff --git a/server/routers/epja-2023-2/pen-plotter/routes/sub-routes/svg.js b/server/routers/epja-2023-2/pen-plotter/routes/sub-routes/svg.js new file mode 100644 index 0000000..3187d03 --- /dev/null +++ b/server/routers/epja-2023-2/pen-plotter/routes/sub-routes/svg.js @@ -0,0 +1,111 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const svg_1 = require("../../utilities/svg"); +const user_1 = require("../../utilities/user"); +const paths_1 = require("../../paths"); +const router = express_1.default.Router(); +router.post("/characters", (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + try { + const { name, username, svg } = req.body; + // Validate input + if (!name || !username || !svg) { + return res.status(400).json({ error: "Missing name, username, or SVG" }); + } + // Check if the user exists + if (!(yield (0, user_1.userExists)(username))) { + return res.status(400).json({ error: "User does not exist" }); + } + // Create character directory if it doesn't exist + const characterName = (0, svg_1.validateName)(name); + const userDir = `${paths_1.PROFILES_PATH}/${username}`; + yield (0, svg_1.createCharacterDirectory)(userDir, characterName); + // Generate a unique filename for the SVG + const svgPath = yield (0, svg_1.generateUniqueSVGPath)(`${userDir}/${characterName}`); + // Extract the SVG content from the data URL + const svgContent = decodeURIComponent(svg.split(",")[1]); + // Write the SVG content to a file + yield (0, svg_1.writeSVGToFile)(svgPath, svgContent); + res.json({ success: true }); + } + catch (err) { + console.error(err); + res.status(500).json({ error: "Something went wrong!" }); + } +})); +router.post("/number-entries", (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + const { char, username } = req.body; + try { + if (!char || !username) { + return res + .status(400) + .json({ error: "Missing characterName or username" }); + } + const validCharName = (0, svg_1.validateName)(char); + const characterDir = `${paths_1.PROFILES_PATH}/${username}/${validCharName}`; + const numberOfEntries = yield (0, svg_1.numberOfFiles)(characterDir); + res.json({ numberOfEntries: numberOfEntries }); + } + catch (err) { + console.error(err); + res.status(500).json({ error: "Something went wrong!" }); + } +})); +router.post("/handwriting", (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + const { username, text, scaleFactor, defects } = req.body; + if (!text) { + return res.status(400).json({ error: "Missing text" }); + } + try { + // Split by lines + const lines = text.split("\n"); + let totalMissing = []; // Expected to be one dim array + let totalPaths = []; // Expected to be two dim array + for (let line of lines) { + const { paths, missing } = yield (0, svg_1.svgLinePathNames)(username, line); + totalMissing.push(...missing); + totalPaths.push(paths); + } + // If there is missing char + if (totalMissing.length > 0) { + // Remove duplicates + totalMissing = [...new Set(totalMissing)]; + return res + .status(404) + .json({ error: `Missing character/s ${totalMissing.join(",")}` }); + } + // Generate a larger + const path = yield (0, svg_1.generateSvg)(totalPaths, scaleFactor || 1, defects); + // Read the SVG files + return res.json({ svgLink: path }); + } + catch (err) { + console.error(err); + res.status(500).json({ error: "Unable to create the handwriting" }); + } +})); +router.post("/print", (req, res) => __awaiter(void 0, void 0, void 0, function* () { + var _a; + try { + const printingStatus = yield (0, svg_1.printSVG)(`${paths_1.STATIC_PATH}/generated.svg`); + res.status(printingStatus.success ? 200 : 500).json({ + error: (_a = printingStatus.message.split("\n")[1]) !== null && _a !== void 0 ? _a : "Something went wrong", + }); + } + catch (e) { + return res.status(500).json({ error: e }); + } +})); +exports.default = router; diff --git a/server/routers/epja-2023-2/pen-plotter/routes/sub-routes/users.js b/server/routers/epja-2023-2/pen-plotter/routes/sub-routes/users.js new file mode 100644 index 0000000..514ef36 --- /dev/null +++ b/server/routers/epja-2023-2/pen-plotter/routes/sub-routes/users.js @@ -0,0 +1,64 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const paths_1 = require("../../paths"); +const express_1 = __importDefault(require("express")); +const fs_1 = __importDefault(require("fs")); +const router = express_1.default.Router(); +router.get("/", (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + try { + // Get all the users from the profiles directory + const users = yield fs_1.default.promises.readdir(paths_1.PROFILES_PATH); + // For each user, read the meta.json file and add the createdAt property + let response = []; + for (let i = 0; i < users.length; i++) { + // Get the last modification time + const timestamp = yield fs_1.default.statSync(`${paths_1.PROFILES_PATH}/${users[i]}`).mtime; + response.push({ name: users[i], createdAt: timestamp.toISOString() }); + } + res.json(response); + } + catch (err) { + console.error(err); + res.status(500).json({ error: "Unable to read the directory" }); + } +})); +router.post("/", (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + const { name } = req.body; + if (!name) { + return res.status(400).json({ error: "Missing name" }); + } + try { + yield fs_1.default.promises.mkdir(`${paths_1.PROFILES_PATH}/${name}`); + res.json({ success: true }); + } + catch (err) { + console.error(err); + res.status(500).json({ error: "Unable to create the directory" }); + } +})); +router.delete("/", (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + const { name } = req.body; + try { + yield fs_1.default.promises.rm(`${paths_1.PROFILES_PATH}/${name}`, { + recursive: true, + }); + res.json({ success: true }); + } + catch (err) { + console.error(err); + res.status(500).json({ error: "Unable to delete the directory" }); + } +})); +exports.default = router; diff --git a/server/routers/epja-2023-2/pen-plotter/utilities/svg.js b/server/routers/epja-2023-2/pen-plotter/utilities/svg.js new file mode 100644 index 0000000..7bdc1c2 --- /dev/null +++ b/server/routers/epja-2023-2/pen-plotter/utilities/svg.js @@ -0,0 +1,556 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.writeSVGToFile = exports.generateUniqueSVGPath = exports.createCharacterDirectory = exports.validateName = exports.numberOfFiles = exports.svgLinePathNames = exports.generateSvg = exports.printSVG = void 0; +const fs_1 = __importDefault(require("fs")); +const paths_1 = require("../paths"); +const jsdom_1 = __importDefault(require("jsdom")); +/** + * Returns the number of files in a directory. + * @param dir - The directory path. + * @returns A promise that resolves to the number of files in the directory. + */ +const numberOfFiles = (dir) => __awaiter(void 0, void 0, void 0, function* () { + try { + const files = yield fs_1.default.promises.readdir(dir); + return files.length; + } + catch (err) { + // File is not found + return 0; + } +}); +exports.numberOfFiles = numberOfFiles; +/** + * Returns a string with a valid name based on the input string. + * Replaces invalid characters with their corresponding names or "lower-" / "upper-" prefix. + * @param name - The input string to be validated. + * @returns A string with a valid name. + */ +const validateName = (name) => { + const map = { + "?": "questionMark", + "*": "asterisk", + "/": "slash", + "\\": "backslash", + ":": "colon", + "|": "pipe", + "<": "lessThan", + ">": "greaterThan", + '"': "doubleQuote", + "'": "singleQuote", + "@": "at", + "#": "hash", + $: "dollar", + "%": "percent", + "^": "caret", + "&": "ampersand", + "(": "leftParenthesis", + ")": "rightParenthesis", + "-": "hyphen", + _: "underscore", + "=": "equal", + "+": "plus", + "{": "leftCurlyBrace", + "}": "rightCurlyBrace", + "[": "leftSquareBracket", + "]": "rightSquareBracket", + ",": "comma", + ".": "period", + "!": "exclamationMark", + "~": "tilde", + "`": "graveAccent", + }; + const numbers = { + "0": "zero", + "1": "one", + "2": "two", + "3": "three", + "4": "four", + "5": "five", + "6": "six", + "7": "seven", + "8": "eight", + "9": "nine", + }; + if (name in map) + return map[name]; + if (name in numbers) + return numbers[name]; + // distingush between upper and lower case + if (isUpperCase(name)) + return `upper-${name}`; + return `lower-${name}`; +}; +exports.validateName = validateName; +const isUpperCase = (char) => { + return char === char.toUpperCase(); +}; +/** + * Returns a random entity path for a given user and character name. + * @param userName - The name of the user. + * @param charName - The title of the character. + * @returns A promise that resolves to a string representing the path to the random entity or empty. + */ +const getRandomEntityPath = (userName, charName) => __awaiter(void 0, void 0, void 0, function* () { + try { + const basePath = `${paths_1.PROFILES_PATH}/${userName}/${charName}`; + const characters = yield fs_1.default.promises.readdir(basePath); + const randomIndex = Math.floor(Math.random() * characters.length); + return `${basePath}/${characters[randomIndex]}`; + } + catch (err) { + console.error("Could not get random entity path"); + return ""; + } +}); +/** + * Checks if a given character path contains any special characters. + * Special characters include: questionMark, asterisk, slash, backslash, colon, pipe, lessThan, greaterThan, + * doubleQuote, singleQuote, at, hash, dollar, percent, caret, ampersand, leftParenthesis, rightParenthesis, + * hyphen, underscore, equal, plus, leftCurlyBrace, rightCurlyBrace, leftSquareBracket, rightSquareBracket, + * comma, period, exclamationMark, tilde, graveAccent. + * + * @param charPath - The character path to check. + * @returns True if the character path contains any special characters, false otherwise. + */ +const isSpecialChar = (charPath) => { + const specialLocatedTop = [ + "singleQuote", + "doubleQuote", + "graveAccent", + "asterisk", + "caret", + ]; + const specialLocatedMiddle = [ + "colon", + "lessThan", + "greaterThan", + "leftParenthesis", + "rightParenthesis", + "hyphen", + "equal", + "plus", + "leftCurlyBrace", + "rightCurlyBrace", + "leftSquareBracket", + "rightSquareBracket", + "exclamationMark", + "tilde", + ]; + const specialLocatedBottom = ["underscore", "comma", "period"]; + let isSpecial = false; + let position = "bottom"; + for (const special of specialLocatedTop) { + if (charPath.includes(special)) { + isSpecial = true; + position = "top"; + break; + } + } + if (!isSpecial) { + for (const special of specialLocatedMiddle) { + if (charPath.includes(special)) { + isSpecial = true; + position = "middle"; + break; + } + } + } + if (!isSpecial) { + for (const special of specialLocatedBottom) { + if (charPath.includes(special)) { + isSpecial = true; + position = "bottom"; + break; + } + } + } + return { isSpecial, position }; +}; +/** + * Determines if the given character path should extend below the baseline. + * @param charPath The character path to check. + * @returns A boolean indicating whether the character path should extend below the baseline. + */ +const extendBelowBaseline = (charPath) => { + const extendBelowBaseline = [ + "lower-q", + "lower-y", + "lower-g", + "lower-p", + "lower-j", + ]; + let extend = false; + for (const char of extendBelowBaseline) { + if (charPath.includes(char)) { + extend = true; + break; + } + } + return extend; +}; +/** + * Returns an object containing an array of SVG paths for each word in the input text and an array of missing characters. + * @param userName - The name of the user. + * @param text - The input text to generate SVG paths for. + * @returns An object containing an array of SVG paths for each word in the input text and an array of missing characters. + */ +const svgLinePathNames = (userName, text) => __awaiter(void 0, void 0, void 0, function* () { + let paths = []; + let missing = []; + const words = text.split(" "); + for (const word of words) { + let wordPath = []; + const chars = word.trim().split(""); + for (const c of chars) { + const cName = validateName(c); + const path = yield getRandomEntityPath(userName, cName); + if (path === "") { + missing.push(c); + } + else { + wordPath.push(path); + } + } + paths.push(wordPath); + } + return { + missing, + paths, + }; +}); +exports.svgLinePathNames = svgLinePathNames; +/** + * Parses an SVG string and returns the SVG element and its paths. + * @param svg - The SVG string to parse. + * @returns An object containing the SVG element and its paths. + */ +const parseSVG = (svg) => { + const dom = new jsdom_1.default.JSDOM(svg); + const svgElement = dom.window.document.querySelector("svg"); + const svgPaths = svgElement === null || svgElement === void 0 ? void 0 : svgElement.querySelectorAll("path"); + return { + parent: svgElement, + paths: svgPaths, + }; +}; +/** + * Returns a random number between the given minimum and maximum values, with an optional percentage range. + * @param min The minimum value for the random number. + * @param max The maximum value for the random number. + * @param percentage The percentage range for the random number. Defaults to 25%. + * @returns A random number between the given minimum and maximum values. + */ +const getRandomNumber = (min, max, percentage = 25, scale = 1) => { + const howRandom = Math.round((max - min) * (percentage / 100)); + const randomNumber = Math.floor(Math.random() * (howRandom + 1)); + // const randomSign = Math.random() < 0.5 ? -1 : 1; + return Math.round(min + randomNumber) * scale; +}; +// Get standard values for the characters +const getStandardValues = (isSpecial, position) => { + // Standard values for the characters + const standard = { + char_width: 20, + space_width: 10, + special_char_located_top_width: 5, + special_char_located_middle_width: 15, + special_char_located_top_max_width: 10, + special_char_located_middle_max_width: 20, + special_char_located_bottom_width: 5, + special_char_located_bottom_max_width: 15, + special_char_height_top: 10, + special_char_height_middle: 20, + special_char_height_bottom: 30, + max_char_width: 30, + max_char_height: 30, + }; + const standerdWidth = isSpecial + ? position === "top" + ? standard.special_char_located_top_width + : position === "middle" + ? standard.special_char_located_middle_width + : standard.special_char_located_bottom_width + : standard.char_width; + const standerdMaxWidth = isSpecial + ? position === "top" + ? standard.special_char_located_top_max_width + : position === "middle" + ? standard.special_char_located_middle_max_width + : standard.special_char_located_bottom_max_width + : standard.max_char_width; + const standerdHeight = isSpecial + ? position === "top" + ? standard.special_char_height_top + : position === "middle" + ? standard.special_char_height_middle + : standard.special_char_height_bottom + : standard.max_char_height; + const standerdMaxHeight = isSpecial + ? position === "top" + ? standard.special_char_height_top + : position === "middle" + ? standard.special_char_height_middle + : standard.special_char_height_bottom + : standard.max_char_height; + return { standerdWidth, standerdMaxWidth, standerdHeight, standerdMaxHeight }; +}; +// Get Random Defects +const getRandomDefects = (defects, scaleFactor, charPath = "") => { + const { baseline, kerning, letterSize, lineSpacing, indent } = defects; + const { isSpecial, position } = isSpecialChar(charPath); + const { standerdWidth, standerdMaxWidth, standerdHeight, standerdMaxHeight } = getStandardValues(isSpecial, position); + const indentRandom = getRandomNumber(0, 80, indent, scaleFactor); + const lineSpacingRandom = getRandomNumber(0, 30, lineSpacing, scaleFactor); + const kerningDeffects = getRandomNumber(0, 10, kerning, scaleFactor); + const baselineOffset = getRandomNumber(0, 10, baseline, scaleFactor); + const letterSizeWidthRandom = getRandomNumber(standerdWidth, standerdMaxWidth, letterSize, scaleFactor); + const letterSizeRandomHeight = getRandomNumber(standerdHeight, standerdMaxHeight, letterSize, scaleFactor); + return { + indentRandom, + lineSpacingRandom, + kerningDeffects, + baselineOffset, + letterSizeWidthRandom, + letterSizeRandomHeight, + }; +}; +/** + * Assembles a word by processing each character and generating SVG elements. + * + * @param {AssembleWord} options - The options for assembling the word. + * @param {string} options.word - The word to assemble. + * @param {number} options.offsetX - The initial X offset. + * @param {number} options.offsetY - The initial Y offset. + * @param {number} options.scaleFactor - The scale factor for the word. + * @param {number} options.indentRandom - The random indentation for the word. + * @param {Defects} options.defects - The defects for the word. + * + * @returns {Object} - The assembled word elements, the height of the word, and the updated X offset. + */ +const assembleWord = ({ word, offsetX, offsetY, scaleFactor, indentRandom, defects, }) => { + const space_width = 10 * scaleFactor; + let wordElements = []; + let wordHeight = 0; + if (word.length === 0) { + offsetX += space_width; + } + else { + offsetX += indentRandom; + for (let j = 0; j < word.length; j++) { + const char = word[j]; + const { kerningDeffects, baselineOffset } = getRandomDefects(defects, scaleFactor); + const { isSpecial, position } = isSpecialChar(char); + const { letterSizeWidthRandom, letterSizeRandomHeight } = getRandomDefects(defects, scaleFactor, char); + // You need to load the SVG content from the file + const svgFileContent = fs_1.default.readFileSync(char, "utf-8"); + // Get the width and height of the SVG and its paths children + const { parent } = parseSVG(svgFileContent); + const width = parent === null || parent === void 0 ? void 0 : parent.getAttribute("width"); + const height = parent === null || parent === void 0 ? void 0 : parent.getAttribute("height"); + // Scale down the width to the standerd width while keeping the aspect ratio + const widthScale = letterSizeWidthRandom / Number(width); + const heightScale = letterSizeRandomHeight / Number(height); + const scale = Math.min(widthScale, heightScale); + // Calculate the scaled width and height + const scaledHeight = Number(height) * scale * scaleFactor; + const scaledWidth = Number(width) * scale * scaleFactor; + // Change the width and height of the SVG + parent === null || parent === void 0 ? void 0 : parent.setAttribute("width", String(scaledWidth)); + parent === null || parent === void 0 ? void 0 : parent.setAttribute("height", String(scaledHeight)); + // Add viewBox attribute to scale the paths inside the SVG + parent === null || parent === void 0 ? void 0 : parent.setAttribute("viewBox", `0 0 ${width} ${height}`); + // Change the position of the SVG + parent === null || parent === void 0 ? void 0 : parent.setAttribute("x", offsetX.toString()); + parent === null || parent === void 0 ? void 0 : parent.setAttribute("y", String(offsetY + baselineOffset)); + // Add the SVG content to the SVG content variable + offsetX += scaledWidth + kerningDeffects; + wordElements.push({ + element: parent, + isSpecial, + position, + extendBelowBaseline: extendBelowBaseline(char), + }); + wordHeight = Math.max(wordHeight, scaledHeight + baselineOffset); + } + // Align the line elements to the bottom of the line + let extended = false; + wordElements.forEach((e) => { + const { element, isSpecial, position, extendBelowBaseline } = e; + const elementHeight = parseInt(element.getAttribute("height")); + const lineYOffset = wordHeight - elementHeight; + if (isSpecial) { + if (position === "top") { + element.setAttribute("y", String(offsetY)); + } + else if (position === "middle") { + element.setAttribute("y", String(offsetY + lineYOffset / 2)); + } + else { + element.setAttribute("y", String(offsetY + lineYOffset)); + } + } + else { + if (extendBelowBaseline) { + element.setAttribute("y", String(offsetY + lineYOffset + wordHeight / 2)); + extended = true; + } + else { + element.setAttribute("y", String(offsetY + lineYOffset)); + } + } + }); + // Fix the line height + if (extended) + wordHeight += wordHeight / 2; + // Add a space between words + offsetX += space_width * scaleFactor; + } + return { wordElements, wordHeight, offsetX }; +}; +/** + * Assembles a line of text into SVG elements. + * + * @param {AssembleLine} options - The options for assembling the line. + * @param {number} options.defects - The number of defects in the line. + * @param {number} options.scaleFactor - The scale factor for the line. + * @param {string[]} options.line - The words in the line. + * @param {number} options.offsetY - The vertical offset of the line. + * @returns {Object} - The assembled line content and updated vertical offset. + */ +const assembleLine = ({ defects, scaleFactor, line, offsetY, }) => { + const { indentRandom, lineSpacingRandom } = getRandomDefects(defects, scaleFactor); + let lineContent = ""; + // Add a line container for each line of text + let lineHeight = 0; + let offsetX = indentRandom; + let lineElements = []; + for (let i = 0; i < line.length; i++) { + const word = line[i]; + const { wordElements, wordHeight, offsetX: newOffsetX, } = assembleWord({ + word, + offsetX, + offsetY, + scaleFactor, + indentRandom, + defects, + }); + // Update the offset + offsetX = newOffsetX; + lineHeight = Math.max(lineHeight, wordHeight); + lineElements = lineElements.concat(wordElements); + } + // Update the offset + offsetY += lineHeight + lineSpacingRandom; + // Append the line elements to the SVG content + lineElements.forEach((e) => { + lineContent += e.element.outerHTML; + }); + return { lineContent, offsetY, offsetX }; +}; +/** + * Writes the SVG content to a file and returns the server file path. + * @param svgContent - The SVG content to be written to the file. + * @returns The server file path of the generated SVG file. + */ +const writeSVG = (svgContent, totalHeight, totalWidth) => __awaiter(void 0, void 0, void 0, function* () { + // wrap the SVG content in an SVG document + const outputFile = `${svgContent}`; // Change this to your desired SVG content + // Write the SVG content to a file + const svgFilePath = `${paths_1.STATIC_PATH}/generated.svg`; // Change this to your desired file path + fs_1.default.writeFileSync(svgFilePath, outputFile); + // Return the SVG file path + const basePath = `${process.env.BASE_URL || "http://localhost"}`; + const port = process.env.PORT || "5000"; + // Date.now() is used to prevent caching (cache busting) + const serverFilePath = `${basePath}:${port}/static/generated.svg?v=${Date.now()}`; + return serverFilePath; +}); +/** + * Generates an SVG file based on the provided paths, scale factor, and defects. + * @param paths - A 3D array of file paths representing the characters to be included in the SVG. + * @param scaleFactor - The scale factor to apply to the SVG. Default is 1. + * @param defects - An object containing defect values for line spacing, kerning, letter size, and baseline offset. + * @returns A Promise that resolves to the file path of the generated SVG. + */ +const generateSvg = (paths, scaleFactor = 1, defects) => __awaiter(void 0, void 0, void 0, function* () { + let svgContent = ""; + let offsetY = 0; + let totalHeight = 0; + let totalWidth = 0; + // Iterate over the lines, words and chars creating the SVG content + paths.forEach((line) => { + const { lineContent, offsetY: newOffsetY, offsetX, } = assembleLine({ + defects, + scaleFactor, + line, + offsetY, + }); + svgContent += lineContent; + offsetY = newOffsetY; + totalHeight = Math.max(totalHeight, offsetY); + totalWidth = Math.max(totalWidth, offsetX); + }); + // Write the SVG content to a file + const serverFilePath = yield writeSVG(svgContent, totalHeight, totalWidth); + return serverFilePath; +}); +exports.generateSvg = generateSvg; +/** + * Renders an SVG file to a connected plotter. + * @param inputPath - The path to the input SVG file. + * @param outputPath - Optional. The path to the output SVG file. + * @returns An error if one occurs during the rendering process. + */ +const printSVG = (inputPath) => __awaiter(void 0, void 0, void 0, function* () { + // Execute the following command : axicli inputPath usin os.system + const { execSync } = require("child_process"); + try { + const command = `axicli ${inputPath}`; + const result = execSync(command, { encoding: "utf-8" }); + // Process the result and return an object + return { + success: true, + message: `success: ${result}`, + }; + } + catch (error) { + const errorMessage = error; + return { + success: false, + message: `error: ${errorMessage.message}`, + }; + } +}); +exports.printSVG = printSVG; +// Function to create the character directory +const createCharacterDirectory = (userPath, charTitle) => __awaiter(void 0, void 0, void 0, function* () { + const characters = yield fs_1.default.promises.readdir(userPath); + if (!characters.includes(charTitle)) { + yield fs_1.default.promises.mkdir(`${userPath}/${charTitle}`); + } +}); +exports.createCharacterDirectory = createCharacterDirectory; +// Function to generate a unique SVG filename +const generateUniqueSVGPath = (characterDir) => __awaiter(void 0, void 0, void 0, function* () { + const characterLength = yield numberOfFiles(characterDir); + return `${characterDir}/${characterLength + 1}.svg`; +}); +exports.generateUniqueSVGPath = generateUniqueSVGPath; +// Function to write SVG content to a file +const writeSVGToFile = (svgPath, svgContent) => __awaiter(void 0, void 0, void 0, function* () { + yield fs_1.default.promises.writeFile(svgPath, svgContent); +}); +exports.writeSVGToFile = writeSVGToFile; diff --git a/server/routers/epja-2023-2/pen-plotter/utilities/user.js b/server/routers/epja-2023-2/pen-plotter/utilities/user.js new file mode 100644 index 0000000..6b81f24 --- /dev/null +++ b/server/routers/epja-2023-2/pen-plotter/utilities/user.js @@ -0,0 +1,23 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.userExists = void 0; +const fs_1 = __importDefault(require("fs")); +const paths_1 = require("../paths"); +// Function to check if the user exists +const userExists = (userName) => __awaiter(void 0, void 0, void 0, function* () { + const users = yield fs_1.default.promises.readdir(paths_1.PROFILES_PATH); + return users.includes(userName); +}); +exports.userExists = userExists;