From d8db8b1031dacf61ec99357232e658f4703c731f Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 27 Sep 2021 08:58:32 -0400 Subject: [PATCH 1/8] initial commit --- .gitignore | 2 + package-lock.json | 797 ++++++++++++++++++++++++++++++++++++++ package.json | 30 ++ public/css/style.css | 47 +++ public/index.html | 147 +++++++ public/js/scripts.js | 305 +++++++++++++++ public/js/tablebuilder.js | 76 ++++ public/login.html | 1 + schema.md | 21 + server.js | 319 +++++++++++++++ 10 files changed, 1745 insertions(+) create mode 100644 .gitignore create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/css/style.css create mode 100644 public/index.html create mode 100644 public/js/scripts.js create mode 100644 public/js/tablebuilder.js create mode 100644 public/login.html create mode 100644 schema.md create mode 100644 server.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..99ddbd67 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/* +.env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..2113cd39 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,797 @@ +{ + "name": "a3-persistence", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "16.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.1.tgz", + "integrity": "sha512-4/Z9DMPKFexZj/Gn3LylFgamNKHm4K3QDi0gz9B26Uk0c8izYf97B5fxfpspMNkWlFupblKM/nV8+NA9Ffvr+w==" + }, + "@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + }, + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "bson": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.5.2.tgz", + "integrity": "sha512-8CEMJpwc7qlQtrn2rney38jQSEeMar847lz0LyitwRmVknAW8iHXrzW4fTjHfyWm0E3sukyD/zppdH+QU1QefA==", + "requires": { + "buffer": "^5.6.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "denque": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", + "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", + "requires": { + "jake": "^10.6.1" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "express-partials": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/express-partials/-/express-partials-0.3.0.tgz", + "integrity": "sha1-iLnEAWSv2aVSeGKbKUjmrOe/9F8=" + }, + "express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "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==" + } + } + }, + "filelist": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", + "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "follow-redirects": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", + "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "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==" + }, + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "method-override": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-3.0.0.tgz", + "integrity": "sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==", + "requires": { + "debug": "3.1.0", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" + }, + "mime-types": { + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "requires": { + "mime-db": "1.49.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mongodb": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.2.tgz", + "integrity": "sha512-pHCKDoOy1h6mVurziJmXmTMPatYWOx8pbnyFgSgshja9Y36Q+caHUzTDY6rrIy9HCSrjnbXmx3pCtvNZHmR8xg==", + "requires": { + "bson": "^4.5.2", + "denque": "^2.0.1", + "mongodb-connection-string-url": "^2.0.0", + "saslprep": "^1.0.3" + } + }, + "mongodb-connection-string-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.1.0.tgz", + "integrity": "sha512-Qf9Zw7KGiRljWvMrrUFDdVqo46KIEiDuCzvEN97rh/PcKzk2bd6n9KuzEwBwW9xo5glwx69y1mI6s+jFUD/aIQ==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^9.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "passport": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", + "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-github2": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/passport-github2/-/passport-github2-0.1.12.tgz", + "integrity": "sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw==", + "requires": { + "passport-oauth2": "1.x.x" + } + }, + "passport-oauth": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-oauth/-/passport-oauth-1.0.0.tgz", + "integrity": "sha1-kK/2M4dUDwIImvKM2tOep/gNd98=", + "requires": { + "passport-oauth1": "1.x.x", + "passport-oauth2": "1.x.x" + } + }, + "passport-oauth1": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/passport-oauth1/-/passport-oauth1-1.2.0.tgz", + "integrity": "sha512-Sv2YWodC6jN12M/OXwmR4BIXeeIHjjbwYTQw4kS6tHK4zYzSEpxBgSJJnknBjICA5cj0ju3FSnG1XmHgIhYnLg==", + "requires": { + "oauth": "0.9.x", + "passport-strategy": "1.x.x", + "utils-merge": "1.x.x" + } + }, + "passport-oauth2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.6.0.tgz", + "integrity": "sha512-emXPLqLcVEcLFR/QvQXZcwLmfK8e9CqvMgmOFJxcNT3okSFMtUbRRKpY20x5euD+01uHsjjCa07DYboEeLXYiw==", + "requires": { + "base64url": "3.x.x", + "oauth": "0.9.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, + "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==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "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==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "router": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/router/-/router-1.3.5.tgz", + "integrity": "sha512-kozCJZUhuSJ5VcLhSb3F8fsmGXy+8HaDbKCAerR1G6tq3mnMZFMuSohbFvGv1c5oMFipijDjRZuuN/Sq5nMf3g==", + "requires": { + "array-flatten": "3.0.0", + "debug": "2.6.9", + "methods": "~1.1.2", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "setprototypeof": "1.2.0", + "utils-merge": "1.0.1" + }, + "dependencies": { + "array-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "requires": { + "punycode": "^2.1.1" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" + }, + "whatwg-url": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-9.1.0.tgz", + "integrity": "sha512-CQ0UcrPHyomtlOCot1TL77WyMIm/bCwrJ2D6AOKGwEczU9EpyoqAokfqrf/MioU9kHcMsmJZcg1egXix2KYEsA==", + "requires": { + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..b5e2e4d3 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "a3-persistence", + "version": "1.0.0", + "description": "Assignment 3 - Persistence: Two-tier Web Application with Database, Express server, and CSS template ===", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git@github.com_wpi:AlexKinley/a3-persistence.git" + }, + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^0.21.4", + "body-parser": "^1.19.0", + "dotenv": "^10.0.0", + "ejs": "^3.1.6", + "express": "^4.17.1", + "express-partials": "^0.3.0", + "express-session": "^1.17.2", + "method-override": "^3.0.0", + "mongodb": "^4.1.2", + "passport": "^0.4.1", + "passport-github2": "^0.1.12", + "passport-oauth": "^1.0.0", + "router": "^1.3.5" + } +} diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 00000000..f95c914c --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,47 @@ +body { + display: flex; + flex-direction: column; +} + +.header { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-bottom: 2em; +} + +.title { + display: flex; + flex-direction: column; +} + +.login-controls { + display: flex; + flex-direction: row; + align-items: center; + height: 100%; +} + +.user-info { + display: flex; + flex-direction: row; + font-size: 2em; + align-items: center; + margin: 20px; +} + +.user-info img { + height: 40px; + width: 40px; + border-radius: 50% !important; +} + +.my-books-header { + position: relative; + height: 4.0rem; +} + +.my-books-header button { + position: absolute; + right: 0; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 00000000..53c33f37 --- /dev/null +++ b/public/index.html @@ -0,0 +1,147 @@ + + + + + CS4241 Assignment 3 + + + + + + + + + + + + + + + + +
+
+

My Digital Library

+
+ The new way for you to keep track of your books. +
+
+ +
+ +
+
+
+

My Books

+ +
+
+
+
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/js/scripts.js b/public/js/scripts.js new file mode 100644 index 00000000..48b25fbc --- /dev/null +++ b/public/js/scripts.js @@ -0,0 +1,305 @@ +/** + * Returns true if c is a digit character + */ +function isCharNumber(c) { + return c >= '0' && c <= '9'; +} + +function checkISBN10(isbn) { + // in ISBN10 the last digit can be an X, and this represents 10 + let justDigits = Array.from(isbn) + .filter(c => (isCharNumber(c) || c === 'X')) + .map(c => (isCharNumber(c) ? parseInt(c) : 10)); + + if (justDigits.length !== 10) { + return false; + } + + let index = 0; + let multiplier = 10; + let sum = 0; + while (index < 10) { + sum += multiplier * justDigits[index]; + index++; + multiplier--; + } + + return sum % 11 === 0; +} + +function checkISBN13(isbn) { + let justDigits = Array.from(isbn) + .filter(isCharNumber) + .map(c => parseInt(c)); + + if (justDigits.length !== 13) { + return false; + } + + let index = 0; + let weight = 1; + let sum = 0; + while (index < 13) { + sum += weight * justDigits[index]; + index++; + // weight alternates between 1 and 3 + weight = weight === 1 ? 3 : 1; + } + + return sum % 10 === 0; +} + +/** + * Count the number of digits to figure out which length of isbn it is, + * and call the correct validator + */ +function checkISBN(isbn) { + let numDigits = Array.from(isbn).filter(isCharNumber).length; + + if (numDigits < 13) { + return checkISBN10(isbn); + } else { + return checkISBN13(isbn); + } +} + +function addBook() { + let form = document.getElementById("add-book-form"); + let formValues = {}; + form.querySelectorAll("input").forEach(input => { + console.log('input:', input); + const realId = input.id.split('-')[2]; + console.log("realId:", realId); + formValues[realId] = input.value; + }); + + if (!checkISBN(formValues["ISBN"])) { + console.log("INVALID ISBN!"); + return; + } + + apiAddBook(formValues).then(res => { + getBookData(); + const addBookModal = bootstrap.Modal.getInstance(document.getElementById('add-book-modal')); + addBookModal.hide(); + }); +} + + +async function apiAddBook(json) { + console.log("adding book:"); + console.log(json); + let res = await fetch('/addBook', { + method: 'POST', + body: JSON.stringify(json), + headers: { + 'Content-Type': 'application/json' + }, + }); +} + +function deleteBook(event) { + console.log("delete book event:"); + console.log(event); + const bookId = event.getAttribute("data-bs-book-id"); + fetch('/deleteBook', { + method: 'DELETE', + body: JSON.stringify({ _id: bookId }), + headers: { + 'Content-Type': 'application/json' + }, + }).then(response => { + console.log('response:', response.json()); + getBookData(); + const deleteBookModal = bootstrap.Modal.getInstance(document.getElementById('delete-book-modal')); + deleteBookModal.hide(); + + }); +} + +function modifyBookEntry(entry_id) { + let form = document.getElementById("modify-book-form"); + let formValues = { _id: entry_id }; + + form.querySelectorAll("input").forEach(input => { + console.log('input:', input); + formValues[input.id.split('-')[2]] = input.value; + }); + + + if (!checkISBN(formValues["ISBN"])) { + console.log("INVALID ISBN!"); + return; + } + + fetch('/modifyBook', { + method: 'PUT', + body: JSON.stringify(formValues), + headers: { + 'Content-Type': 'application/json' + }, + }).then(response => { + console.log('response:', response.json()); + getBookData(); + const modifyBookModal = bootstrap.Modal.getInstance(document.getElementById('modify-book-modal')); + modifyBookModal.hide(); + }); + + +} + +function makeBookEditDropdown(datarow) { + const bookId = datarow._id; + + // this is emotionally challenging, but just so easy + const content = ``; + return content; + +} + +const tableColumns = { + title: { name: "Title", isHeader: true }, + authors: { + name: "Author(s)", + formatter: (authors) => authors.join(', ') + }, + num_pages: "# Pages", + date_added: { + name: "Date Added", + formatter: (timestamp) => (new Date(timestamp)).toLocaleDateString('en-us', { year: "numeric", month: "short", day: "numeric" }), + }, + rating: "Rating", + location: "Location", + modify_book: { name: "Edit", className: "modify-book", contentCallback: makeBookEditDropdown, sortable: false } +}; + + + +// redux but by hand and worse +var bookData = []; + +function getBookById(id) { + return bookData.find(entry => entry._id === id); +} + + +function getBookData() { + fetch('/me/books').then(res => res.json()).then(books => { + console.log('books: '); + console.log(books); + bookData = books; + const bookTable = document.getElementById("book-table"); + bookTable.innerHTML = ''; + buildTable(bookTable, tableColumns, books); + + const tableHeaders = Array.from(bookTable.querySelector("thead tr").children); + console.log("tableHeaders"); + console.log(tableHeaders); + Object.keys(tableColumns).forEach((columnKey, index) => { + const sortCallback = () => { + bookData = bookData.sort((a, b) => { + const aVal = a[columnKey]; + const bVal = b[columnKey]; + if (aVal > bVal) { + return 1; + } else if (aVal === bVal) { + return 0; + } + return -1; + }); + const bookTable = document.getElementById("book-table"); + bookTable.removeChild(bookTable.lastChild); + bookTable.appendChild(makeTableBody(tableColumns, bookData)); + } + const columnData = tableColumns[columnKey]; + if (isString(columnData)) { + const header = tableHeaders.find(th => th.innerText === columnData); + header.addEventListener('click', sortCallback); + } else { + const header = tableHeaders.find(th => th.innerText === columnData.name); + if (!(columnData.sortable === false)) { + header.addEventListener('click', sortCallback); + } + } + }); + }); +} + +function getUserData() { + fetch('/me').then(res => res.json()).then(user => { + console.log('username: ', user); + const usernameDiv = document.getElementById('username'); + const avatarImg = document.getElementById('avatar'); + usernameDiv.innerText = user.name; + avatarImg.src = user.avatar_url; + }); +} + +window.addEventListener('load', event => { + getUserData(); + getBookData(); + + setupModifyBookHandler(); + setupDeleteBookHandler(); +}); + + + + +function setupModifyBookHandler() { + let modifyBookModal = document.getElementById('modify-book-modal'); + modifyBookModal.addEventListener('show.bs.modal', function(event) { + // Button that triggered the modal + const button = event.relatedTarget; + + const book_id = button.getAttribute('data-bs-book-id'); + const book = getBookById(book_id); + const book_title = book.title; + + // Update the modal's content. + const modalTitle = modifyBookModal.querySelector('.modal-title'); + + const modalBodyISBN = modifyBookModal.querySelector('.modal-body #modify-book-ISBN'); + const modalBodyRating = modifyBookModal.querySelector('.modal-body #modify-book-rating'); + const modalBodyLocation = modifyBookModal.querySelector('.modal-body #modify-book-location'); + modalBodyISBN.value = book.isbn; + modalBodyRating.value = book.rating; + modalBodyLocation.value = book.location; + + modalTitle.textContent = 'Modify ' + book_title; + + const modifyButton = modifyBookModal.querySelector('.book-modify-button'); + modifyButton.addEventListener('click', (event) => { + modifyBookEntry(book_id); + }); + }); +} + +function setupDeleteBookHandler() { + const deleteBookModal = document.getElementById('delete-book-modal'); + deleteBookModal.addEventListener('show.bs.modal', function(event) { + // Button that triggered the modal + const button = event.relatedTarget; + + const book = getBookById(button.getAttribute('data-bs-book-id')) + const book_title = book.title; + + // Update the modal's content. + const modalBody = deleteBookModal.querySelector('.modal-body'); + // var modalBodyInput = exampleModal.querySelector('.modal-body input') + + modalBody.textContent = 'Are you sure you want to delete ' + book_title + '?'; + + const deleteButton = deleteBookModal.querySelector('.book-deletion-button'); + deleteButton.setAttribute('data-bs-book-id', book._id); + }); +} \ No newline at end of file diff --git a/public/js/tablebuilder.js b/public/js/tablebuilder.js new file mode 100644 index 00000000..493178fe --- /dev/null +++ b/public/js/tablebuilder.js @@ -0,0 +1,76 @@ +/** + * + * @param {Element} table The tag to add the data to (will be emptied) + * @param {*} columns A map from the keys in the data to the column header + * @param {*} data An array of objects containing keys and the data for that key + */ +function buildTable(table, columns, data) { + + const tableHeader = makeTableHeader(columns); + table.appendChild(tableHeader); + + const tableBody = makeTableBody(columns, data); + table.appendChild(tableBody); +} + +function makeTableHeader(columns) { + let tableHead = document.createElement('thead'); + let tableHeadRow = document.createElement('tr'); + + Object.keys(columns).forEach(columnKey => { + const columnEntry = columns[columnKey]; + let columnName = ""; + if (isString(columnEntry)) { + columnName = columnEntry; + } else { + columnName = columnEntry.name; + } + let th = document.createElement('th'); + th.innerText = columnName; + th.scope = "col"; + tableHeadRow.appendChild(th); + }); + + tableHead.appendChild(tableHeadRow); + + return tableHead; +} + +function makeTableBody(columns, data) { + let tableBody = document.createElement('tbody'); + + data.forEach(rowData => { + const tableRow = makeTableRow(columns, rowData); + tableBody.appendChild(tableRow); + }); + + return tableBody; +} + +function makeTableRow(columns, rowData) { + let tableRow = document.createElement('tr'); + + Object.keys(columns).forEach(columnKey => { + const columnData = columns[columnKey]; + let columnValue = rowData[columnKey]; + if (columnData.formatter) { + columnValue = columnData.formatter(columnValue); + } + + + let td = document.createElement(columnData.isHeader ? 'th' : 'td'); + if (columnData.contentCallback) { + td.innerHTML = columnData.contentCallback(rowData); + } else { + td.innerText = columnData.text || columnValue; + td.scope = columnData.isHeader ? 'row' : null; + td.className = columnData.className; + } + + tableRow.appendChild(td); + }); + + return tableRow; +} + +const isString = (val) => (typeof val === 'string' || val instanceof String); \ No newline at end of file diff --git a/public/login.html b/public/login.html new file mode 100644 index 00000000..48c4bc3e --- /dev/null +++ b/public/login.html @@ -0,0 +1 @@ +Login with Github diff --git a/schema.md b/schema.md new file mode 100644 index 00000000..fa02d0ec --- /dev/null +++ b/schema.md @@ -0,0 +1,21 @@ +# Users Collection +- _id +- github_id +- name +- avatar_url + +# Books +- user_id +- isbn +- title +- authors +- release_date +- num_pages +- date_added +- genre +- rating +- location + +# Locations +- user_id +- loc_name diff --git a/server.js b/server.js new file mode 100644 index 00000000..796e8ab9 --- /dev/null +++ b/server.js @@ -0,0 +1,319 @@ +require('dotenv').config() +const express = require('express'); +const app = express(); +const bodyparser = require('body-parser'); +const dreams = []; +const passport = require('passport'); +const util = require('util'); +const session = require('express-session'); +const methodOverride = require('method-override'); +const GitHubStrategy = require('passport-github2').Strategy; +const partials = require('express-partials'); +const axios = require("axios"); + + +const DEFAULT_PORT = 3000; + +const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID; +const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET; + +const MONGO_USERNAME = process.env.DB_USER; +const MONGO_PASS = process.env.DB_PASS; +const MONGO_HOST = process.env.DB_HOST; + +const OAUTH_CALLBACK_HOST = process.env.OAUTH_CALLBACK_HOST; + +const { MongoClient, ObjectId } = require('mongodb'); +const uri = `mongodb+srv://${MONGO_USERNAME}:${MONGO_PASS}@${MONGO_HOST}`; +const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true }); + +let usersCollection = null; +let bookCollection = null; +let locationCollection = null; + +// app.set('views', __dirname); +// app.set('view engine', 'ejs'); + +client.connect() + .then(() => { + return client.db('MyDigitalLib') + }) + .then((db) => { + usersCollection = db.collection('Users'); + bookCollection = db.collection('Books'); + locationCollection = db.collection('Locations'); + }); +// Passport session setup. +// To support persistent login sessions, Passport needs to be able to +// serialize users into and deserialize users out of the session. Typically, +// this will be as simple as storing the user ID when serializing, and finding +// the user by ID when deserializing. However, since this example does not +// have a database of user records, the complete GitHub profile is serialized +// and deserialized. +passport.serializeUser(function(user, done) { + done(null, user); +}); + +passport.deserializeUser(function(obj, done) { + done(null, obj); +}); + + +// Use the GitHubStrategy within Passport. +// Strategies in Passport require a `verify` function, which accept +// credentials (in this case, an accessToken, refreshToken, and GitHub +// profile), and invoke a callback with a user object. +passport.use(new GitHubStrategy({ + clientID: GITHUB_CLIENT_ID, + clientSecret: GITHUB_CLIENT_SECRET, + callbackURL: `http://${OAUTH_CALLBACK_HOST}/auth/github/callback`, + }, + function(accessToken, refreshToken, profile, done) { + // asynchronous verification, for effect... + process.nextTick(function() { + + // To keep the example simple, the user's GitHub profile is returned to + // represent the logged-in user. In a typical application, you would want + // to associate the GitHub account with a user record in your database, + // and return that user instead. + + handleUserLogin(profile).then(res => done(null, res)); + // return done(null, profile); + }); + } +)); + +async function handleUserLogin(profile) { + const matching_user = await usersCollection.findOne({ github_id: profile.id }); + + if (matching_user === null) { + + console.log("No existing user found!"); + console.log("profile:"); + console.log(profile); + const user_entry = { + github_id: profile.id, + name: profile.username, + avatar_url: profile._json.avatar_url, + } + console.log(user_entry); + const insert_result = await usersCollection.insertOne(user_entry) + console.log("insert result:"); + console.log(insert_result); + const user_entry_with_id = { + ...user_entry, + _id: insert_result.insertedId, + }; + return user_entry_with_id; + + } else { + console.log("this user already exists"); + console.log(matching_user); + return matching_user; + } +} + +// automatically deliver all files in the public folder +// with the correct headers / MIME type. +app.use(express.static('public', { index: false })); + +// get json when appropriate +app.use(bodyparser.json()); +app.use(methodOverride()); + +app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: false })); +// Initialize Passport! Also use passport.session() middleware, to support +// persistent login sessions (recommended). +app.use(passport.initialize()); +app.use(passport.session()); + +// GET /auth/github +// Use passport.authenticate() as route middleware to authenticate the +// request. The first step in GitHub authentication will involve redirecting +// the user to github.com. After authorization, GitHub will redirect the user +// back to this application at /auth/github/callback +app.get('/auth/github', + passport.authenticate('github', { scope: ['user:email'] }), + function(req, res) { + // The request will be redirected to GitHub for authentication, so this + // function will not be called. + }); + +// GET /auth/github/callback +// Use passport.authenticate() as route middleware to authenticate the +// request. If authentication fails, the user will be redirected back to the +// login page. Otherwise, the primary route function will be called, +// which, in this example, will redirect the user to the home page. +app.get('/auth/github/callback', + passport.authenticate('github', { failureRedirect: '/login' }), + function(req, res) { + res.redirect('/'); + }); + +app.get('/logout', function(req, res) { + req.logout(); + res.redirect('/'); +}); + +app.get('/login', function(req, res) { + res.sendFile(__dirname + '/public/login.html'); +}); +// even with our static file handler, we still +// need to explicitly handle the domain name alone... +app.get('/', ensureAuthenticated, function(request, response) { + response.sendFile(__dirname + '/public/index.html'); +}); + + + +// API ENDPOINTS + +// So the frontend can get info about the user +app.get('/me', ensureAuthenticated, (req, res) => { + // console.log('/me'); + // console.log(req.user); + res.send(req.user); +}) + +app.get('/me/books', ensureAuthenticated, (req, res) => { + // console.log('/me/books'); + // console.log(req.user); + const user_id = req.user._id; + + bookCollection.find({ user_id }).toArray() + .then(books => res.send(books)); +}); + +const OPENLIB_BASE_URL = 'https://openlibrary.org' +app.post('/addBook', ensureAuthenticated, async(req, res) => { + const user_id = req.user._id; + + const body = req.body; + const isbn = body.ISBN; + + + const isbn_url = `${OPENLIB_BASE_URL}/isbn/${isbn}`; + const book_info = (await axios.get(isbn_url)).data; + console.log(book_info); + const authors = book_info.authors; + + let authors_names = []; + if (authors) { + // the authors are given by their author entry in openlibrary + // so we have to go through and get the name for each other + author_names = await Promise.all(authors.map(async author => { + + const author_info = (await axios.get(`${OPENLIB_BASE_URL}/${author.key}`)).data; + return author_info.name; + })); + + } else { + author_names = ["Author Unknown"] + } + + + const book_entry = { + user_id, + isbn, + title: book_info.title || "Title Unknown", + authors: author_names, + release_date: book_info.publish_date ? Date.parse(book_info.publish_date) : "Unknown", + num_pages: book_info.number_of_pages || "Unknown", + date_added: Date.now(), + location: body.location, + rating: body.rating, + }; + + bookCollection.insertOne(book_entry); + + res.send(book_entry); +}); + +app.put('/modifyBook', ensureAuthenticated, async(req, res) => { + const user_id = req.user._id; + + const body = req.body; + const book_entry_id = body._id; + const isbn = body.ISBN; + + + const isbn_url = `${OPENLIB_BASE_URL}/isbn/${isbn}`; + const book_info = (await axios.get(isbn_url)).data; + const authors = book_info.authors; + + // the authors are given by their author entry in openlibrary + // so we have to go through and get the name for each other + const author_names = await Promise.all(authors.map(async author => { + + const author_info = (await axios.get(`${OPENLIB_BASE_URL}/${author.key}`)).data; + return author_info.name; + })); + + + const book_entry = { + user_id, + isbn, + title: book_info.title, + authors: author_names, + release_date: Date.parse(book_info.publish_date), + num_pages: book_info.number_of_pages, + date_added: Date.now(), + location: body.location, + rating: body.rating, + }; + + const update = await bookCollection.updateOne({ _id: new ObjectId(book_entry_id) }, { $set: book_entry }); + + res.send(update); +}); + + +app.delete('/deleteBook', ensureAuthenticated, async(req, res) => { + const user_id = req.user._id; + + const body = req.body; + const book_entry_id = body._id; + + const doc = { + _id: new ObjectId(book_entry_id), + user_id + } + console.log("deleting document matching: "); + console.log(doc); + const deletion_result = await bookCollection.deleteOne(doc); + res.send(deletion_result); +}); + +// JUST FOR TESTING +app.delete('/user/:id', (req, res) => { + console.log("removing user: ", req.params.id); + usersCollection.deleteOne({ github_id: req.params.id }).then(response => { + console.log(response); + res.send(response); + }); +}); + +// JUST FOR TESTING +app.get('/users/', (req, res) => { + usersCollection.find({}).toArray().then(response => { + console.log(response); + res.send(response); + }) +}) + +app.get('/books/', (req, res) => { + bookCollection.find({}).toArray().then(response => res.send(response)); +}); + + +app.listen(process.env.PORT ? process.env.PORT : DEFAULT_PORT); + +// Simple route middleware to ensure user is authenticated. +// Use this route middleware on any resource that needs to be protected. If +// the request is authenticated (typically via a persistent login session), +// the request will proceed. Otherwise, the user will be redirected to the +// login page. +function ensureAuthenticated(req, res, next) { + if (req.isAuthenticated()) { return next(); } + res.redirect('/login') +} \ No newline at end of file From 1f234982b505735b9ab0b9222db60dc598fde842 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 27 Sep 2021 09:20:01 -0400 Subject: [PATCH 2/8] style login page --- public/css/style.css | 16 ++++++++++++++++ public/login.html | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/public/css/style.css b/public/css/style.css index f95c914c..084f5eb0 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -44,4 +44,20 @@ body { .my-books-header button { position: absolute; right: 0; +} + +.login { + display: flex; + flex-direction: column; + align-items: center; +} + +.login .title { + display: flex; + flex-direction: column; + align-items: center; +} + +.login-link { + margin: 2em; } \ No newline at end of file diff --git a/public/login.html b/public/login.html index 48c4bc3e..73e07b0e 100644 --- a/public/login.html +++ b/public/login.html @@ -1 +1,33 @@ -Login with Github + + + + + CS4241 Assignment 3 + + + + + + + + + + + + +
+ + +
+ + + + \ No newline at end of file From daa93a2a4a5c344371a0f2dd8558b094bb3eaf0c Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 27 Sep 2021 09:26:24 -0400 Subject: [PATCH 3/8] error handling of missing data from api --- server.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/server.js b/server.js index 796e8ab9..ab236f64 100644 --- a/server.js +++ b/server.js @@ -241,22 +241,24 @@ app.put('/modifyBook', ensureAuthenticated, async(req, res) => { const book_info = (await axios.get(isbn_url)).data; const authors = book_info.authors; - // the authors are given by their author entry in openlibrary - // so we have to go through and get the name for each other - const author_names = await Promise.all(authors.map(async author => { - - const author_info = (await axios.get(`${OPENLIB_BASE_URL}/${author.key}`)).data; - return author_info.name; - })); + let author_names = ['Author Unkown']; + if (authors) { + // the authors are given by their author entry in openlibrary + // so we have to go through and get the name for each other + author_names = await Promise.all(authors.map(async author => { + const author_info = (await axios.get(`${OPENLIB_BASE_URL}/${author.key}`)).data; + return author_info.name; + })); + } const book_entry = { user_id, isbn, - title: book_info.title, + title: book_info.title || 'Book Title Unknown', authors: author_names, - release_date: Date.parse(book_info.publish_date), - num_pages: book_info.number_of_pages, + release_date: book_info.publish_date ? Date.parse(book_info.publish_date) : 'Publish Data Unknown', + num_pages: book_info.number_of_pages || "# Pages Unkown", date_added: Date.now(), location: body.location, rating: body.rating, From 57b882bbdee2e87d80578cb5bf382aa7a3d90c2d Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 27 Sep 2021 09:27:56 -0400 Subject: [PATCH 4/8] fixup --- server.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/server.js b/server.js index ab236f64..410faf88 100644 --- a/server.js +++ b/server.js @@ -197,7 +197,7 @@ app.post('/addBook', ensureAuthenticated, async(req, res) => { console.log(book_info); const authors = book_info.authors; - let authors_names = []; + let author_names = []; if (authors) { // the authors are given by their author entry in openlibrary // so we have to go through and get the name for each other @@ -241,24 +241,27 @@ app.put('/modifyBook', ensureAuthenticated, async(req, res) => { const book_info = (await axios.get(isbn_url)).data; const authors = book_info.authors; - let author_names = ['Author Unkown']; + let author_names = []; if (authors) { // the authors are given by their author entry in openlibrary // so we have to go through and get the name for each other author_names = await Promise.all(authors.map(async author => { + const author_info = (await axios.get(`${OPENLIB_BASE_URL}/${author.key}`)).data; return author_info.name; })); - } + } else { + author_names = ["Author Unknown"] + } const book_entry = { user_id, isbn, - title: book_info.title || 'Book Title Unknown', + title: book_info.title || "Title Unknown", authors: author_names, - release_date: book_info.publish_date ? Date.parse(book_info.publish_date) : 'Publish Data Unknown', - num_pages: book_info.number_of_pages || "# Pages Unkown", + release_date: book_info.publish_date ? Date.parse(book_info.publish_date) : "Unknown", + num_pages: book_info.number_of_pages || "Unknown", date_added: Date.now(), location: body.location, rating: body.rating, From 2d9a5d2c6d92feb27e31b391606323bb6f5405e2 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 27 Sep 2021 11:22:34 -0400 Subject: [PATCH 5/8] form validation --- public/index.html | 8 ++++---- public/js/scripts.js | 7 ++++--- server.js | 27 +++------------------------ 3 files changed, 11 insertions(+), 31 deletions(-) diff --git a/public/index.html b/public/index.html index 53c33f37..529268c4 100644 --- a/public/index.html +++ b/public/index.html @@ -60,7 +60,7 @@
- +
@@ -78,7 +78,7 @@
@@ -98,7 +98,7 @@
- +
@@ -116,7 +116,7 @@
diff --git a/public/js/scripts.js b/public/js/scripts.js index 48b25fbc..bb089d53 100644 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -63,18 +63,17 @@ function checkISBN(isbn) { } } -function addBook() { +function addBook(event) { let form = document.getElementById("add-book-form"); let formValues = {}; form.querySelectorAll("input").forEach(input => { - console.log('input:', input); const realId = input.id.split('-')[2]; - console.log("realId:", realId); formValues[realId] = input.value; }); if (!checkISBN(formValues["ISBN"])) { console.log("INVALID ISBN!"); + form.querySelector("#add-book-ISBN").setCustomValidity("ISBN number invalid"); return; } @@ -129,6 +128,8 @@ function modifyBookEntry(entry_id) { if (!checkISBN(formValues["ISBN"])) { console.log("INVALID ISBN!"); + console.log(form.querySelector("#modify-book-ISBN")); + form.querySelector("#modify-book-ISBN").setCustomValidity("ISBN number invalid"); return; } diff --git a/server.js b/server.js index 410faf88..4ddc1f30 100644 --- a/server.js +++ b/server.js @@ -29,7 +29,6 @@ const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: let usersCollection = null; let bookCollection = null; -let locationCollection = null; // app.set('views', __dirname); // app.set('view engine', 'ejs'); @@ -41,8 +40,8 @@ client.connect() .then((db) => { usersCollection = db.collection('Users'); bookCollection = db.collection('Books'); - locationCollection = db.collection('Locations'); }); + // Passport session setup. // To support persistent login sessions, Passport needs to be able to // serialize users into and deserialize users out of the session. Typically, @@ -119,9 +118,9 @@ app.use(express.static('public', { index: false })); // get json when appropriate app.use(bodyparser.json()); -app.use(methodOverride()); +// app.use(methodOverride()); -app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: false })); +app.use(session({ secret: 'dreadlockanyonewhackydeviator', resave: false, saveUninitialized: false })); // Initialize Passport! Also use passport.session() middleware, to support // persistent login sessions (recommended). app.use(passport.initialize()); @@ -289,26 +288,6 @@ app.delete('/deleteBook', ensureAuthenticated, async(req, res) => { res.send(deletion_result); }); -// JUST FOR TESTING -app.delete('/user/:id', (req, res) => { - console.log("removing user: ", req.params.id); - usersCollection.deleteOne({ github_id: req.params.id }).then(response => { - console.log(response); - res.send(response); - }); -}); - -// JUST FOR TESTING -app.get('/users/', (req, res) => { - usersCollection.find({}).toArray().then(response => { - console.log(response); - res.send(response); - }) -}) - -app.get('/books/', (req, res) => { - bookCollection.find({}).toArray().then(response => res.send(response)); -}); app.listen(process.env.PORT ? process.env.PORT : DEFAULT_PORT); From 28a59649e7186e7b0b761024b30e16ed2f8d4941 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 27 Sep 2021 11:32:27 -0400 Subject: [PATCH 6/8] readme update --- README.markdown | 124 +++++++++--------------------------------------- 1 file changed, 23 insertions(+), 101 deletions(-) diff --git a/README.markdown b/README.markdown index de506211..9545a7bf 100644 --- a/README.markdown +++ b/README.markdown @@ -1,116 +1,38 @@ -Assignment 3 - Persistence: Two-tier Web Application with Database, Express server, and CSS template -=== +# My Digital Library -Due: September 20th, by 11:59 AM. +https://alex-kinley-a3-persistence-csgmg.ondigitalocean.app -This assignnment continues where we left off, extending it to use the most popular Node.js server framework (express), -a database (mongodb), and a CSS application framework / template of your choice (Boostrap, Material Design, Semantic UI, Pure etc.) +My Digital Library is a simple way to keep track of the books you own. +You enter the ISBN, location, and your rating of a book, and then it gets added to your list of books. (for testing purpoes abe books lists isbns of books ex: https://www.abebooks.com/book-search/author/ROALD-DAHL) +The app makes use of (openlibrary.org)[https://openlibrary.org/dev/docs/api/books] books API to get information about a book from its ISBN. -Baseline Requirements ---- +Authenticaion is done using Github OATUH2. Using passportjs with passport-github2 and following their example application this was fairly straightforward. -Your application is required to implement the following functionalities: +The app uses bootstrap 5 as the css framework. In some cases I wrote my own css styling for layout, particuarly for the login screen, and the header of the application. I suspect bootstrap could have done these, but it was going to be more effort to figure out how to do it with bootstrap alone. -- a `Server`, created using Express (no alternatives will be accepted for this assignment) -- a `Results` functionality which shows all data associated with a logged in user (except passwords) -- a `Form/Entry` functionality which allows users to add, modify, and delete data items (must be all three!) associated with their user name / account. -- Use of at least five [Express middleware packages](https://expressjs.com/en/resources/middleware.html). Explore! One of these five middleware -can be a custom function that you write yourself; if you choose to do this, make sure to describe what this function is in your README. -- Persistent data storage in between server sessions using [mongodb](https://www.mongodb.com/cloud/atlas) -- Use of a [CSS framework or template](https://github.com/troxler/awesome-css-frameworks). -This should do the bulk of your styling/CSS for you and be appropriate to your application. -For example, don't use [NES.css](https://nostalgic-css.github.io/NES.css/) (which is awesome!) unless you're creating a game or some type of retro 80s site. +The Express middlware used is: +- bodyparser - for parsing the data passed along with requests +- session - used by passportjs for managing session cookies +- passport.initialize - the basic form of passportjs for authentication +- passport.session - makes passportjs use sessions for persistant connections +- ensureAuthentication (custom function only used for some endpoints) - for endpoint that require login, this checks if the user is authentication, and if they are not, they are redirected to the login page, this just ensures that for pages and api calls that require the user to be logged in, that they are in fact authenticated. -Your application is required to demonstrate the use of the following concepts: +The main challenges I faced with this project were learning how to correctly use bootstrap, especially dealing with the modals was a non-trival endeavor. Additionally using external APIs is always a little bit of a challenge. -HTML: -- HTML input tags and form fields of various flavors (`