diff --git a/.env.vault b/.env.vault index 1b99dda..b9f6e05 100644 --- a/.env.vault +++ b/.env.vault @@ -13,8 +13,8 @@ SECRET="dev only" ADMIN_CLIENT_BASE_URL="http://localhost:5500/index.html" # development -DOTENV_VAULT_DEVELOPMENT="tvbPfq+YJ+bg2ngApO1D61AO6Z5XfEejpoNjLi265pjwqoPWvGOc1e4AijCSKB8cFECxgI0X/VoSRYmUmMOe0SDH5g7L3t+2AqNZuNLxVG/7kEPpxpCNp9FQhrpcgjt83svSsjKk8yljLAy9I/3DuEPZ9fu+7tR1t+6lC1b4S6wR80Ci4mIVX89JdoXD+/kuIUG73aSKyICT0Z5f9gSW+5CHgtVRM80mjSKitA6dsNWcxjuVO/emIxeqIWCPb1MSLnToMQCbK+j6" +DOTENV_VAULT_DEVELOPMENT="mk0ti7lLmG/jrGuAPr+qSYjuNneSX64I5tl1Jf1nVQr50vuBKUH9rDwasUlufWBn7f/IWF41+abK62LhoW/8FSqsHNjxa+q3UPSk34CdChVRPMs/szFNU3vbWfj5D/O+52/OM/M+W1kUqqTNacDsyhn1ZaR0td0CPe0JTeH2bZeR1aPhgUk69buhvho5jfRwtTYkb8SkwbW970fciMg9CHerWn29BWvZ6y4VUJnOqPzZ24xLMS/FgKyJHgKLmuWOircATgUS6RymWGX/NMdMV086ZWgIL7JhsnVWyYTI3i/RTMisIN+PTjTEVYLHSHvDdv3Ra1LTUYPXr+KSm4aJqfT76xL4O59QV3GpRWafPzDqLN4Bs9bPRN5NtUlLoux19txE0aBxA2ibZUCBYsJTR7+NDS37OqznC5IpEnYmbiEenMmdZQ69efizCViXsuwwNfoe7MtXUIbiVNPSayySKALihB99eepvI4PMFHGktcmgjZ7CxVabjr0vL865g11EUvLUwX/ljhqlL1NGLojuylTy2KIIK9oNmiyuKty22DPHku2VbXijB67XQtkKFwpnMZiXXMOTNBNkPIeBPkUv2YCLYNQ4ETtLzD/NrCsxDRMmXCaMmY7YURkeC36kraeHmLGILO8HIX1RZkXg5OTsVDpKDFm4" # ci -DOTENV_VAULT_CI="le0ReA3X2VGzcdHktA+2F7CaXwY4DooV7Ni+M95Ij5ULExqPuE2hO9BAsXNXCqNhOEJJy/8Wah70KO0FwWy8LMjSgyk8vFbzfXIMViDS6WRKvxHD3SJ5A79+bd/ECoZOapiF7Aa1s5ESSHuiZPbY35uwJFbNQyE/xaZDbrMJuNpMvloIMiBlEWCtwtigrP/YB275YFvSKuzshFKW389XFkMSgWWHWHxJF4gN690kXwrXjISCzadJCcL0tv/0sfAVBFLEaugQ" +DOTENV_VAULT_CI="ZG6WoGa4agRarWODwHiy3hbJV4xANDDBTiiCr+pc52CYYjZOT6BDlKoqqpc1eKce+pRW5yj2C2vvJW72se2IMmE5lRdswu35tQhQ3sI+fQjWCNOLSyshZLBNdz3rDFOFrCbW7EfnZtwEsK3vHIpTKVYkl4x1x7zQ59XC879xNuH8zi79NCBGj/BbvwEGl64/pqJyH6gh3Q/S2hqXmsPlL5a5Np40bJh6YNWZRmNhLvZVdw1lHNQ/SrVvsVu0L9XGqWF4OY9WcuwpgSlJpnbXBtqc2p8K9eOAxLstGJBg9j1t6t0BbW1OVzSP/xuJnu7noBrl48/1diiFiK4A1yl80sT3cO1ZyaBoRB3VTisQe3DuSk8KWx4I3hDVV7C2djNKorCXFrbtbIrCVdZaJ+Ql0B/Cimmj3jociCuxDumHmlzGV3EoEFS4G3qfphgMs8x7epXpCliR324wzNmZTj9LYJ8SCzU+29u9yiTIqFx3Ow4=" diff --git a/.gitignore b/.gitignore index 09d7fe2..f16c44b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ tmp # environment variables .env .env.ci -.env.keys \ No newline at end of file +.env.keys + +# google auth credentials +credentials.json \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7ef3346..cc99b4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,18 +9,22 @@ "version": "1.0.0", "dependencies": { "@dotenvx/dotenvx": "^0.15.4", + "@google-cloud/local-auth": "^2.1.0", "cors": "^2.8.5", "express": "^4.18.2", "express-jwt": "^8.4.1", + "googleapis": "^105.0.0", "jimp": "^0.22.10", "jsonwebtoken": "^9.0.2", "jsqr": "^1.4.0", + "mongoose": "^8.1.2", "qrcode": "^1.5.3" }, "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", + "@types/mongoose": "^5.11.97", "@types/node": "^20.11.17", "@types/qrcode": "^1.5.5", "@types/supertest": "^6.0.2", @@ -874,6 +878,35 @@ "node": ">=14" } }, + "node_modules/@google-cloud/local-auth": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/local-auth/-/local-auth-2.1.0.tgz", + "integrity": "sha512-ymZ1XuyKcRcro0aiMYz3hVGbZ+QZmN5V1Eyjvw2k1xqq76PwmDer0DIPxdxkLzfW9Inr8+g+MS9t9fZ7dOlTOQ==", + "dependencies": { + "arrify": "^2.0.1", + "google-auth-library": "^8.0.2", + "open": "^7.0.3", + "server-destroy": "^1.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/local-auth/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -1948,6 +1981,14 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz", + "integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2258,6 +2299,16 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, + "node_modules/@types/mongoose": { + "version": "5.11.97", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.11.97.tgz", + "integrity": "sha512-cqwOVYT3qXyLiGw7ueU2kX9noE8DPGRY6z8eUxudhXY8NZ7DMKYAxyZkLSevGfhCX3dO/AoX5/SO9lAzfjon0Q==", + "deprecated": "Mongoose publishes its own types, so you do not need to install this package.", + "dev": true, + "dependencies": { + "mongoose": "*" + } + }, "node_modules/@types/mute-stream": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", @@ -2354,6 +2405,19 @@ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/wrap-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", @@ -2803,6 +2867,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2983,6 +3058,14 @@ "node": ">=8" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -3149,6 +3232,14 @@ } ] }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3315,6 +3406,14 @@ "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.3.0.tgz", + "integrity": "sha512-balJfqwwTBddxfnidJZagCBPP/f48zj9Sdp3OJswREOgsJzHiQSaOIAtApSgDQFYgHqAvFkp53AFSqjMDZoTFw==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -4106,7 +4205,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -4817,6 +4915,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -4881,6 +4984,11 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -5110,6 +5218,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5293,6 +5427,102 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-auth-library/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis": { + "version": "105.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-105.0.0.tgz", + "integrity": "sha512-wH/jU/6QpqwsjTKj4vfKZz97ne7xT7BBbKwzQEwnbsG8iH9Seyw19P+AuLJcxNNrmgblwLqfr3LORg4Okat1BQ==", + "dependencies": { + "google-auth-library": "^8.0.2", + "googleapis-common": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-6.0.4.tgz", + "integrity": "sha512-m4ErxGE8unR1z0VajT6AYk3s6a9gIMM6EkDZfkPnES8joeOlEtFEJeF8IyZkb0tjPXkktUfYrE4b3Li1DNyOwA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^5.0.1", + "google-auth-library": "^8.0.2", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -5347,6 +5577,38 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5442,6 +5704,18 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -6475,6 +6749,14 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -6561,6 +6843,14 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6796,6 +7086,11 @@ "node": ">= 0.6" } }, + "node_modules/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==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -6913,6 +7208,136 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mongodb": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", + "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongodb-connection-string-url/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/mongodb-connection-string-url/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/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mongoose": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.1.2.tgz", + "integrity": "sha512-5KMq7k6KmFCIB8/YMKMFsWdsdNkBwuARDRHDRpp5GKC78eT0LwHIaMEKo6gDUg3zBuMoy9OdcM/6f4dkW06C/A==", + "dependencies": { + "bson": "^6.2.0", + "kareem": "2.5.1", + "mongodb": "6.3.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6971,6 +7396,14 @@ } } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -8376,6 +8809,11 @@ "node": ">= 0.8.0" } }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==" + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -8438,6 +8876,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -8502,6 +8945,14 @@ "source-map": "^0.6.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -9278,6 +9729,11 @@ "node": ">=4" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "node_modules/utif2": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", @@ -9299,6 +9755,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index 7fe9cbe..1b6c7c5 100644 --- a/package.json +++ b/package.json @@ -15,18 +15,22 @@ }, "dependencies": { "@dotenvx/dotenvx": "^0.15.4", + "@google-cloud/local-auth": "^2.1.0", "cors": "^2.8.5", "express": "^4.18.2", "express-jwt": "^8.4.1", + "googleapis": "^105.0.0", "jimp": "^0.22.10", "jsonwebtoken": "^9.0.2", "jsqr": "^1.4.0", + "mongoose": "^8.1.2", "qrcode": "^1.5.3" }, "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", + "@types/mongoose": "^5.11.97", "@types/node": "^20.11.17", "@types/qrcode": "^1.5.5", "@types/supertest": "^6.0.2", diff --git a/src/index.ts b/src/index.ts index 6d74436..5f085a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,10 @@ import { server } from "./server"; import { hostname } from "os"; +import mg from "mongoose"; -server.listen(process.env.PORT, () => { - console.log(`Server listening on ${hostname}:${process.env.PORT}`); +mg.connect(process.env.MONGODB_URI as string).then(() => { + console.log(`Connected to DB ${mg.connection.host}`); + server.listen(process.env.PORT, () => { + console.log(`Server listening on ${hostname}:${process.env.PORT}`); + }); }); diff --git a/src/models/entry.model.ts b/src/models/entry.model.ts new file mode 100644 index 0000000..ecfad5d --- /dev/null +++ b/src/models/entry.model.ts @@ -0,0 +1,23 @@ +import mg from "mongoose"; + +const EntrySchema = new mg.Schema( + { + first_name: String, + last_name: String, + email: String, + start: Date, + end: Date, + }, + { + timestamps: true, + virtuals: { + full_name: { + get() { + return this.first_name + " " + this.last_name; + }, + }, + }, + }, +); + +export const EntryModel = mg.model("Entry", EntrySchema); diff --git a/src/routers/attendance.router.ts b/src/routers/attendance.router.ts new file mode 100644 index 0000000..0ea7fee --- /dev/null +++ b/src/routers/attendance.router.ts @@ -0,0 +1,29 @@ +import ex from "express"; +import { checkIn, checkOut } from "../services/attendance.service"; +import { expressjwt } from "express-jwt"; +import { SubmissionType, User } from "../types"; + +const attendanceRouter = ex.Router(); + +attendanceRouter.use( + expressjwt({ secret: process.env.SECRET as string, algorithms: ["HS256"] }), +); + +attendanceRouter.post("/log-entry", async (req, res, next) => { + const { fields, type } = req.body as { fields: User; type: SubmissionType }; + try { + switch (type) { + case SubmissionType.CheckIn: + await checkIn(fields); + break; + case SubmissionType.CheckOut: + await checkOut(fields); + } + res.status(200).send({ success: true }); + } catch (err) { + console.error(err); + return next(Error("There was a problem processing the request")); + } +}); + +export { attendanceRouter }; diff --git a/src/server.ts b/src/server.ts index 73f8f41..b0038c3 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,20 +3,24 @@ import path from "path"; import { qrCodeRouter } from "./routers/qr-code.router"; import cors from "cors"; import { ORIGIN_WHITELIST } from "./constants"; +import { attendanceRouter } from "./routers/attendance.router"; const server = ex(); +server.use(ex.json()); +server.use((req, res, next) => { + console.log(req.method); + console.log(req.body); + next(); +}); +server.use(ex.static(path.resolve(__dirname, "..", "public"))); server.use( - (req, res, next) => { - console.log(req.method, req.hostname, req.headers.origin); - next() - }, cors((req, cb) => { cb(null, { origin: ORIGIN_WHITELIST.includes(req.headers.origin || "") }); }), ); -server.use(ex.json()); server.use(ex.static(path.resolve(__dirname, "..", "public"))); + server.get("/ping", (req, res) => res.status(200).send({ message: "pong" })); server.get(["/", "/docs"], (req, res) => { @@ -26,6 +30,7 @@ server.get(["/", "/docs"], (req, res) => { }); server.use("/api/qr-code", qrCodeRouter); +server.use("/api/attendance", attendanceRouter); server.use((req, res, next) => { res.status(404).send({ message: "Not found" }); diff --git a/src/services/attendance.service.ts b/src/services/attendance.service.ts new file mode 100644 index 0000000..e02cdff --- /dev/null +++ b/src/services/attendance.service.ts @@ -0,0 +1,64 @@ +// import { google } from "googleapis"; +// import { client } from "./google-auth.service"; +// import { User } from "../types"; +// import { randomUUID } from "crypto"; + +import { EntryModel } from "../models/entry.model"; +import { User } from "../types"; + +export const checkIn = async (user: User) => { + const entry = new EntryModel({ + first_name: user.firstName, + last_name: user.lastName, + start: new Date(), + email: user.email, + }); + await entry.save(); +}; + +export const checkOut = async (user: User) => { + const entries = await EntryModel.find({ email: user.email }).sort({ + createdAt: "desc", + }); + const latest = entries[0]; + if (!latest) throw Error("No entries found. Cannot check out"); + if (latest.end) + throw Error("Latest entry already has checked out: " + latest.end); + await latest.updateOne({ end: new Date() }); +}; + +// const sheetsAPI = google.sheets({ version: "v4", auth: client }); + +// export const checkIn = async (user: User) => { +// // proof of concept: +// await sheetsAPI.spreadsheets.batchUpdate({ +// spreadsheetId: process.env.SPREADSHEET_ID, +// requestBody: { +// requests: [ +// { +// insertDimension: { +// range: { +// dimension: 'ROWS', +// startIndex: 1, +// endIndex: 2, +// }, +// } +// } +// ] +// } +// }) + +// const date = new Date(); +// await sheetsAPI.spreadsheets.values.update({ +// spreadsheetId: process.env.SPREADSHEET_ID, +// range: "Input!A2:H", +// valueInputOption: "USER_ENTERED", +// requestBody: { +// range: "Input!A2:H", +// majorDimension: "ROWS", +// // determine entry # +// values: [[randomUUID(), 1, date.toLocaleDateString(), `${user.firstName} ${user.lastName}`, user.email, `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`, '', '=if(g2,(G2-F2)*24,0)']] +// } +// }) + +// }; diff --git a/src/services/google-auth.service.ts b/src/services/google-auth.service.ts new file mode 100644 index 0000000..68fc618 --- /dev/null +++ b/src/services/google-auth.service.ts @@ -0,0 +1,15 @@ +import path from "path"; +import { google } from "googleapis"; + +const SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]; +const CREDENTIALS_PATH = path.resolve( + __dirname, + "..", + "..", + "credentials.json", +); + +export const client = new google.auth.GoogleAuth({ + keyFile: CREDENTIALS_PATH, + scopes: SCOPES, +}); diff --git a/src/types.ts b/src/types.ts index cb0ff5c..30a3c62 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1 +1,10 @@ -export {}; +export interface User { + firstName: string; + lastName: string; + email: string; +} + +export enum SubmissionType { + CheckIn = "CHECK_IN", + CheckOut = "CHECK_OUT", +}