From 75a123626e822882ed57d6e5f9d4a2220cb3b7ee Mon Sep 17 00:00:00 2001 From: Eroxl Date: Fri, 13 Jan 2023 22:43:07 -0800 Subject: [PATCH 01/54] Added websocket endpoint support to express server --- backend/src/index.ts | 12 ++++--- package.json | 7 ++-- yarn.lock | 85 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 21ea70eb..9c643e22 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,12 +1,13 @@ -import bodyParser from 'body-parser'; +import { middleware, errorHandler } from 'supertokens-node/framework/express'; +import supertokens from 'supertokens-node'; import swaggerJSDoc from 'swagger-jsdoc'; import swaggerUI from 'swagger-ui-express'; -import mongoose from 'mongoose'; import express from 'express'; +import expressWs from 'express-ws'; +import bodyParser from 'body-parser'; +import mongoose from 'mongoose'; import dotenv from 'dotenv'; import cors from 'cors'; -import { middleware, errorHandler } from 'supertokens-node/framework/express'; -import supertokens from 'supertokens-node'; import setupAuth from './setupAuth'; import routes from './routes/index'; @@ -36,6 +37,9 @@ app.use(cors({ // -=- Add Super Tokens Middleware -=- app.use(middleware()); +// -=- Add Websocket Support -=- +expressWs(app); + // -=- Add API Routes -=- app.use('/', routes); diff --git a/package.json b/package.json index 832941df..d394c1eb 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "

📝 Note Rack

", "main": "index.js", "devDependencies": { + "@types/express-ws": "^3.0.1", "cypress": "^11.2.0" }, "scripts": { @@ -20,6 +21,8 @@ "bugs": { "url": "https://github.com/Eroxl/Note-Rack/issues" }, - "homepage": "https://github.com/Eroxl/Note-Rack#readme" + "homepage": "https://github.com/Eroxl/Note-Rack#readme", + "dependencies": { + "express-ws": "^5.0.2" + } } - \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ea2a6168..3087f13c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,6 +39,54 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.31": + version "4.17.32" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" + integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-ws@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" + integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core" "*" + "@types/ws" "*" + +"@types/express@*": + version "4.17.15" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" + integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.31" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + "@types/node@*": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" @@ -49,6 +97,24 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.33.tgz#8c29a0036771569662e4635790ffa9e057db379b" integrity sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg== +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/serve-static@*": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" + "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -59,6 +125,13 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== +"@types/ws@*": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" @@ -441,6 +514,13 @@ executable@^4.1.1: dependencies: pify "^2.2.0" +express-ws@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" + integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== + dependencies: + ws "^7.4.6" + extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1112,6 +1192,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From 809e2701b03ff068950f44074f44ac3fdd7a6dfc Mon Sep 17 00:00:00 2001 From: Eroxl Date: Fri, 13 Jan 2023 22:43:49 -0800 Subject: [PATCH 02/54] Install express-ws deps in correct directory --- backend/package.json | 2 ++ backend/yarn.lock | 37 +++++++++++++++++++ package.json | 5 +-- yarn.lock | 85 -------------------------------------------- 4 files changed, 40 insertions(+), 89 deletions(-) diff --git a/backend/package.json b/backend/package.json index 1ef8432c..130be483 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,6 +7,7 @@ "devDependencies": { "@types/cors": "^2.8.12", "@types/express": "^4.17.13", + "@types/express-ws": "^3.0.1", "@types/redis": "^4.0.11", "@types/swagger-jsdoc": "^6.0.1", "@types/swagger-ui-express": "^4.1.3", @@ -42,6 +43,7 @@ "cors": "^2.8.5", "dotenv": "^14.2.0", "express": "^4.17.2", + "express-ws": "^5.0.2", "ioredis": "^4.28.5", "jsonwebtoken": "^8.5.1", "mongoose": "^6.1.7", diff --git a/backend/yarn.lock b/backend/yarn.lock index d8320541..5d099f18 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -237,6 +237,15 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== +"@types/express-serve-static-core@*": + version "4.17.32" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" + integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/express-serve-static-core@^4.17.18": version "4.17.28" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" @@ -255,6 +264,15 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/express-ws@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" + integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core" "*" + "@types/ws" "*" + "@types/express@*", "@types/express@^4.17.13": version "4.17.13" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" @@ -377,6 +395,13 @@ "@types/node" "*" "@types/webidl-conversions" "*" +"@types/ws@*": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + "@types/yamljs@^0.2.31": version "0.2.31" resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.31.tgz#b1a620b115c96db7b3bfdf0cf54aee0c57139245" @@ -1431,6 +1456,13 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +express-ws@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" + integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== + dependencies: + ws "^7.4.6" + express@^4.17.2: version "4.17.2" resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" @@ -3698,6 +3730,11 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" diff --git a/package.json b/package.json index d394c1eb..7b3914b0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "description": "

📝 Note Rack

", "main": "index.js", "devDependencies": { - "@types/express-ws": "^3.0.1", "cypress": "^11.2.0" }, "scripts": { @@ -22,7 +21,5 @@ "url": "https://github.com/Eroxl/Note-Rack/issues" }, "homepage": "https://github.com/Eroxl/Note-Rack#readme", - "dependencies": { - "express-ws": "^5.0.2" - } + "dependencies": {} } diff --git a/yarn.lock b/yarn.lock index 3087f13c..ea2a6168 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,54 +39,6 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.31": - version "4.17.32" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" - integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express-ws@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" - integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== - dependencies: - "@types/express" "*" - "@types/express-serve-static-core" "*" - "@types/ws" "*" - -"@types/express@*": - version "4.17.15" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" - integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.31" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - "@types/node@*": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" @@ -97,24 +49,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.33.tgz#8c29a0036771569662e4635790ffa9e057db379b" integrity sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg== -"@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/serve-static@*": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== - dependencies: - "@types/mime" "*" - "@types/node" "*" - "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -125,13 +59,6 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== -"@types/ws@*": - version "8.5.4" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" - integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== - dependencies: - "@types/node" "*" - "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" @@ -514,13 +441,6 @@ executable@^4.1.1: dependencies: pify "^2.2.0" -express-ws@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" - integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== - dependencies: - ws "^7.4.6" - extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1192,11 +1112,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^7.4.6: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From a806538fa3cedb5c582e11e2028738d620e13a99 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Fri, 13 Jan 2023 22:43:07 -0800 Subject: [PATCH 03/54] Added websocket endpoint support to express server --- backend/src/index.ts | 12 ++++--- package.json | 7 ++-- yarn.lock | 85 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index ecf3b8fc..890b38fd 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,12 +1,13 @@ -import bodyParser from 'body-parser'; +import { middleware, errorHandler } from 'supertokens-node/framework/express'; +import supertokens from 'supertokens-node'; import swaggerJSDoc from 'swagger-jsdoc'; import swaggerUI from 'swagger-ui-express'; -import mongoose from 'mongoose'; import express from 'express'; +import expressWs from 'express-ws'; +import bodyParser from 'body-parser'; +import mongoose from 'mongoose'; import dotenv from 'dotenv'; import cors from 'cors'; -import { middleware, errorHandler } from 'supertokens-node/framework/express'; -import supertokens from 'supertokens-node'; import setupAuth from './setupAuth'; import routes from './routes/index'; @@ -44,6 +45,9 @@ app.use(cors({ // -=- Add Super Tokens Middleware -=- app.use(middleware()); +// -=- Add Websocket Support -=- +expressWs(app); + // -=- Add API Routes -=- app.use('/', routes); diff --git a/package.json b/package.json index 9f3fc342..31b3c616 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Note Rack", "main": "index.js", "devDependencies": { + "@types/express-ws": "^3.0.1", "cypress": "^11.2.0" }, "scripts": { @@ -27,6 +28,8 @@ "bugs": { "url": "https://github.com/Eroxl/Note-Rack/issues" }, - "homepage": "https://github.com/Eroxl/Note-Rack#readme" + "homepage": "https://github.com/Eroxl/Note-Rack#readme", + "dependencies": { + "express-ws": "^5.0.2" + } } - \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ea2a6168..3087f13c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,6 +39,54 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.31": + version "4.17.32" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" + integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-ws@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" + integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core" "*" + "@types/ws" "*" + +"@types/express@*": + version "4.17.15" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" + integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.31" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + "@types/node@*": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" @@ -49,6 +97,24 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.33.tgz#8c29a0036771569662e4635790ffa9e057db379b" integrity sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg== +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/serve-static@*": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" + "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -59,6 +125,13 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== +"@types/ws@*": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" @@ -441,6 +514,13 @@ executable@^4.1.1: dependencies: pify "^2.2.0" +express-ws@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" + integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== + dependencies: + ws "^7.4.6" + extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1112,6 +1192,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From 07be4fcae9655f47163ad378179155cfdbbd511e Mon Sep 17 00:00:00 2001 From: Eroxl Date: Fri, 13 Jan 2023 22:43:49 -0800 Subject: [PATCH 04/54] Install express-ws deps in correct directory --- backend/package.json | 2 ++ backend/yarn.lock | 37 +++++++++++++++++++ package.json | 5 +-- yarn.lock | 85 -------------------------------------------- 4 files changed, 40 insertions(+), 89 deletions(-) diff --git a/backend/package.json b/backend/package.json index 1ef8432c..130be483 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,6 +7,7 @@ "devDependencies": { "@types/cors": "^2.8.12", "@types/express": "^4.17.13", + "@types/express-ws": "^3.0.1", "@types/redis": "^4.0.11", "@types/swagger-jsdoc": "^6.0.1", "@types/swagger-ui-express": "^4.1.3", @@ -42,6 +43,7 @@ "cors": "^2.8.5", "dotenv": "^14.2.0", "express": "^4.17.2", + "express-ws": "^5.0.2", "ioredis": "^4.28.5", "jsonwebtoken": "^8.5.1", "mongoose": "^6.1.7", diff --git a/backend/yarn.lock b/backend/yarn.lock index d8320541..5d099f18 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -237,6 +237,15 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== +"@types/express-serve-static-core@*": + version "4.17.32" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" + integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/express-serve-static-core@^4.17.18": version "4.17.28" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" @@ -255,6 +264,15 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/express-ws@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" + integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core" "*" + "@types/ws" "*" + "@types/express@*", "@types/express@^4.17.13": version "4.17.13" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" @@ -377,6 +395,13 @@ "@types/node" "*" "@types/webidl-conversions" "*" +"@types/ws@*": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + "@types/yamljs@^0.2.31": version "0.2.31" resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.31.tgz#b1a620b115c96db7b3bfdf0cf54aee0c57139245" @@ -1431,6 +1456,13 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +express-ws@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" + integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== + dependencies: + ws "^7.4.6" + express@^4.17.2: version "4.17.2" resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" @@ -3698,6 +3730,11 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" diff --git a/package.json b/package.json index 31b3c616..e8ff6afc 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "description": "Note Rack", "main": "index.js", "devDependencies": { - "@types/express-ws": "^3.0.1", "cypress": "^11.2.0" }, "scripts": { @@ -29,7 +28,5 @@ "url": "https://github.com/Eroxl/Note-Rack/issues" }, "homepage": "https://github.com/Eroxl/Note-Rack#readme", - "dependencies": { - "express-ws": "^5.0.2" - } + "dependencies": {} } diff --git a/yarn.lock b/yarn.lock index 3087f13c..ea2a6168 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,54 +39,6 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.31": - version "4.17.32" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" - integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express-ws@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" - integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== - dependencies: - "@types/express" "*" - "@types/express-serve-static-core" "*" - "@types/ws" "*" - -"@types/express@*": - version "4.17.15" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" - integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.31" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - "@types/node@*": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" @@ -97,24 +49,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.33.tgz#8c29a0036771569662e4635790ffa9e057db379b" integrity sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg== -"@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/serve-static@*": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== - dependencies: - "@types/mime" "*" - "@types/node" "*" - "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -125,13 +59,6 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== -"@types/ws@*": - version "8.5.4" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" - integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== - dependencies: - "@types/node" "*" - "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" @@ -514,13 +441,6 @@ executable@^4.1.1: dependencies: pify "^2.2.0" -express-ws@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" - integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== - dependencies: - ws "^7.4.6" - extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1192,11 +1112,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^7.4.6: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From fb207b54de8b89d51576c017e7df641a1044221f Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sun, 26 Feb 2023 15:46:14 -0800 Subject: [PATCH 05/54] Add page permissions field to page model --- backend/src/models/pageModel.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/src/models/pageModel.ts b/backend/src/models/pageModel.ts index 869506de..791d4657 100644 --- a/backend/src/models/pageModel.ts +++ b/backend/src/models/pageModel.ts @@ -2,6 +2,13 @@ import mongoose, { Schema } from 'mongoose'; const PageSchema = new Schema({ user: String, + permissions: [ + { + read: [String], + write: [String], + usernameRegex: String + } + ], style: {}, data: [ { From 9feaee940598c8571b71f21ed3d1cc3a0a12b6ce Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sun, 26 Feb 2023 15:47:39 -0800 Subject: [PATCH 06/54] Add routes and websocket support to Index.ts --- backend/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 890b38fd..d29b6496 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -10,7 +10,6 @@ import dotenv from 'dotenv'; import cors from 'cors'; import setupAuth from './setupAuth'; -import routes from './routes/index'; // -=- Connect to MongoDB with dotenv file -=- dotenv.config(); @@ -48,6 +47,8 @@ app.use(middleware()); // -=- Add Websocket Support -=- expressWs(app); +import routes from './routes/index'; + // -=- Add API Routes -=- app.use('/', routes); From b864b709bb8552de771c1b70a50275506a69ee2c Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sun, 26 Feb 2023 16:20:39 -0800 Subject: [PATCH 07/54] Add admin, username field to pageModel.ts --- backend/src/models/pageModel.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/models/pageModel.ts b/backend/src/models/pageModel.ts index 791d4657..b9dead58 100644 --- a/backend/src/models/pageModel.ts +++ b/backend/src/models/pageModel.ts @@ -4,9 +4,10 @@ const PageSchema = new Schema({ user: String, permissions: [ { - read: [String], - write: [String], - usernameRegex: String + read: Boolean, + write: Boolean, + admin: Boolean, + username: String } ], style: {}, From 609d77245139699d75b15858b96a4e44ceb3e3ec Mon Sep 17 00:00:00 2001 From: Eroxl Date: Mon, 27 Feb 2023 11:19:21 -0800 Subject: [PATCH 08/54] Add verifyPermissions middleware for managing user permissions on pages. --- backend/src/middleware/verifyPermissions.ts | 100 ++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 backend/src/middleware/verifyPermissions.ts diff --git a/backend/src/middleware/verifyPermissions.ts b/backend/src/middleware/verifyPermissions.ts new file mode 100644 index 00000000..d39d97a4 --- /dev/null +++ b/backend/src/middleware/verifyPermissions.ts @@ -0,0 +1,100 @@ +import { Response, NextFunction } from 'express'; +import { SessionRequest } from 'supertokens-node/framework/express'; +import { verifySession } from 'supertokens-node/recipe/session/framework/express'; + +import PageModel from '../models/pageModel'; + +type ValidPermissions = 'read' | 'write' | 'admin'; + +export interface PageRequest extends SessionRequest { + pageData?: any; +} + +const verifyPermissions = (permissions: ValidPermissions[]) => { + const verifyPermissionsMiddleware = async (req: PageRequest, res: Response, next: NextFunction) => { + const { page } = req.params; + + if (!req.session) { + throw new Error('No session provided to middleware!'); + } + + if (!page) { + res.statusCode = 400; + res.json({ + status: 'error', + message: 'No page was provided...', + }); + return; + } + + const username = req.session.getUserId(); + + if (!username) { + res.statusCode = 401; + res.json({ + status: 'error', + message: 'Please login to modify this page!', + }); + return; + } + + const pageData = await PageModel.findOne({ _id: page }).lean(); + + if (!pageData) { + res.statusCode = 404; + res.json({ + status: 'error', + message: 'This page does not exist...', + }); + return; + } + + if (pageData.user === username) { + req.pageData = pageData; + + next(); + + return; + } + + const userPermissionsOnPage = pageData.permissions + .find((permission: any) => permission.username === username) + .permissions as { [key in ValidPermissions]: boolean }; + + if (!userPermissionsOnPage) { + res.statusCode = 403; + res.json({ + status: 'error', + message: 'You do not have access to this page...', + }); + return; + } + + const hasPermission = permissions.every((permission) => userPermissionsOnPage[permission]); + + if (!hasPermission) { + res.statusCode = 403; + res.json({ + status: 'error', + message: 'You do not have access to this page...', + }); + return; + } + + req.pageData = pageData; + + next(); + }; + + const middleware = async (req: SessionRequest, res: Response, next: NextFunction) => { + verifySession()( + req, + res, + () => verifyPermissionsMiddleware(req, res, next), + ); + }; + + return middleware; +} + +export default verifyPermissions; From e1241a34178ef6bda119be886f16876cd3352964 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Fri, 13 Jan 2023 22:43:07 -0800 Subject: [PATCH 09/54] Added websocket endpoint support to express server --- backend/src/index.ts | 12 ++++--- package.json | 7 ++-- yarn.lock | 85 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index ecf3b8fc..890b38fd 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,12 +1,13 @@ -import bodyParser from 'body-parser'; +import { middleware, errorHandler } from 'supertokens-node/framework/express'; +import supertokens from 'supertokens-node'; import swaggerJSDoc from 'swagger-jsdoc'; import swaggerUI from 'swagger-ui-express'; -import mongoose from 'mongoose'; import express from 'express'; +import expressWs from 'express-ws'; +import bodyParser from 'body-parser'; +import mongoose from 'mongoose'; import dotenv from 'dotenv'; import cors from 'cors'; -import { middleware, errorHandler } from 'supertokens-node/framework/express'; -import supertokens from 'supertokens-node'; import setupAuth from './setupAuth'; import routes from './routes/index'; @@ -44,6 +45,9 @@ app.use(cors({ // -=- Add Super Tokens Middleware -=- app.use(middleware()); +// -=- Add Websocket Support -=- +expressWs(app); + // -=- Add API Routes -=- app.use('/', routes); diff --git a/package.json b/package.json index 9f3fc342..31b3c616 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Note Rack", "main": "index.js", "devDependencies": { + "@types/express-ws": "^3.0.1", "cypress": "^11.2.0" }, "scripts": { @@ -27,6 +28,8 @@ "bugs": { "url": "https://github.com/Eroxl/Note-Rack/issues" }, - "homepage": "https://github.com/Eroxl/Note-Rack#readme" + "homepage": "https://github.com/Eroxl/Note-Rack#readme", + "dependencies": { + "express-ws": "^5.0.2" + } } - \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ea2a6168..3087f13c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,6 +39,54 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.31": + version "4.17.32" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" + integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-ws@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" + integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core" "*" + "@types/ws" "*" + +"@types/express@*": + version "4.17.15" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" + integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.31" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + "@types/node@*": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" @@ -49,6 +97,24 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.33.tgz#8c29a0036771569662e4635790ffa9e057db379b" integrity sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg== +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/serve-static@*": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" + "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -59,6 +125,13 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== +"@types/ws@*": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" @@ -441,6 +514,13 @@ executable@^4.1.1: dependencies: pify "^2.2.0" +express-ws@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" + integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== + dependencies: + ws "^7.4.6" + extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1112,6 +1192,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From 1faf1da68a3614c0746a722a0110023a2ec209ee Mon Sep 17 00:00:00 2001 From: Eroxl Date: Fri, 13 Jan 2023 22:43:49 -0800 Subject: [PATCH 10/54] Install express-ws deps in correct directory --- backend/package.json | 2 ++ backend/yarn.lock | 37 +++++++++++++++++++ package.json | 5 +-- yarn.lock | 85 -------------------------------------------- 4 files changed, 40 insertions(+), 89 deletions(-) diff --git a/backend/package.json b/backend/package.json index 1ef8432c..130be483 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,6 +7,7 @@ "devDependencies": { "@types/cors": "^2.8.12", "@types/express": "^4.17.13", + "@types/express-ws": "^3.0.1", "@types/redis": "^4.0.11", "@types/swagger-jsdoc": "^6.0.1", "@types/swagger-ui-express": "^4.1.3", @@ -42,6 +43,7 @@ "cors": "^2.8.5", "dotenv": "^14.2.0", "express": "^4.17.2", + "express-ws": "^5.0.2", "ioredis": "^4.28.5", "jsonwebtoken": "^8.5.1", "mongoose": "^6.1.7", diff --git a/backend/yarn.lock b/backend/yarn.lock index d8320541..5d099f18 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -237,6 +237,15 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== +"@types/express-serve-static-core@*": + version "4.17.32" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" + integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/express-serve-static-core@^4.17.18": version "4.17.28" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" @@ -255,6 +264,15 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/express-ws@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" + integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core" "*" + "@types/ws" "*" + "@types/express@*", "@types/express@^4.17.13": version "4.17.13" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" @@ -377,6 +395,13 @@ "@types/node" "*" "@types/webidl-conversions" "*" +"@types/ws@*": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + "@types/yamljs@^0.2.31": version "0.2.31" resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.31.tgz#b1a620b115c96db7b3bfdf0cf54aee0c57139245" @@ -1431,6 +1456,13 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +express-ws@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" + integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== + dependencies: + ws "^7.4.6" + express@^4.17.2: version "4.17.2" resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" @@ -3698,6 +3730,11 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" diff --git a/package.json b/package.json index 31b3c616..e8ff6afc 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "description": "Note Rack", "main": "index.js", "devDependencies": { - "@types/express-ws": "^3.0.1", "cypress": "^11.2.0" }, "scripts": { @@ -29,7 +28,5 @@ "url": "https://github.com/Eroxl/Note-Rack/issues" }, "homepage": "https://github.com/Eroxl/Note-Rack#readme", - "dependencies": { - "express-ws": "^5.0.2" - } + "dependencies": {} } diff --git a/yarn.lock b/yarn.lock index 3087f13c..ea2a6168 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,54 +39,6 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.31": - version "4.17.32" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" - integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express-ws@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" - integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== - dependencies: - "@types/express" "*" - "@types/express-serve-static-core" "*" - "@types/ws" "*" - -"@types/express@*": - version "4.17.15" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" - integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.31" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - "@types/node@*": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" @@ -97,24 +49,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.33.tgz#8c29a0036771569662e4635790ffa9e057db379b" integrity sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg== -"@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/serve-static@*": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== - dependencies: - "@types/mime" "*" - "@types/node" "*" - "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -125,13 +59,6 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== -"@types/ws@*": - version "8.5.4" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" - integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== - dependencies: - "@types/node" "*" - "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" @@ -514,13 +441,6 @@ executable@^4.1.1: dependencies: pify "^2.2.0" -express-ws@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" - integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== - dependencies: - ws "^7.4.6" - extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1192,11 +1112,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^7.4.6: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From 58cc0db5cc541b988448769bc58d6444b335b3f2 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Fri, 13 Jan 2023 22:43:07 -0800 Subject: [PATCH 11/54] Added websocket endpoint support to express server --- package.json | 5 +++- yarn.lock | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e8ff6afc..31b3c616 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Note Rack", "main": "index.js", "devDependencies": { + "@types/express-ws": "^3.0.1", "cypress": "^11.2.0" }, "scripts": { @@ -28,5 +29,7 @@ "url": "https://github.com/Eroxl/Note-Rack/issues" }, "homepage": "https://github.com/Eroxl/Note-Rack#readme", - "dependencies": {} + "dependencies": { + "express-ws": "^5.0.2" + } } diff --git a/yarn.lock b/yarn.lock index ea2a6168..3087f13c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,6 +39,54 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.31": + version "4.17.32" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" + integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-ws@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" + integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core" "*" + "@types/ws" "*" + +"@types/express@*": + version "4.17.15" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" + integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.31" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + "@types/node@*": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" @@ -49,6 +97,24 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.33.tgz#8c29a0036771569662e4635790ffa9e057db379b" integrity sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg== +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/serve-static@*": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" + "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -59,6 +125,13 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== +"@types/ws@*": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" @@ -441,6 +514,13 @@ executable@^4.1.1: dependencies: pify "^2.2.0" +express-ws@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" + integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== + dependencies: + ws "^7.4.6" + extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1112,6 +1192,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From 203253ea2fcfdcc03b43206d8c13985a8c612b29 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Fri, 13 Jan 2023 22:43:49 -0800 Subject: [PATCH 12/54] Install express-ws deps in correct directory --- package.json | 5 +--- yarn.lock | 85 ---------------------------------------------------- 2 files changed, 1 insertion(+), 89 deletions(-) diff --git a/package.json b/package.json index 31b3c616..e8ff6afc 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "description": "Note Rack", "main": "index.js", "devDependencies": { - "@types/express-ws": "^3.0.1", "cypress": "^11.2.0" }, "scripts": { @@ -29,7 +28,5 @@ "url": "https://github.com/Eroxl/Note-Rack/issues" }, "homepage": "https://github.com/Eroxl/Note-Rack#readme", - "dependencies": { - "express-ws": "^5.0.2" - } + "dependencies": {} } diff --git a/yarn.lock b/yarn.lock index 3087f13c..ea2a6168 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,54 +39,6 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.31": - version "4.17.32" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" - integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express-ws@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" - integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== - dependencies: - "@types/express" "*" - "@types/express-serve-static-core" "*" - "@types/ws" "*" - -"@types/express@*": - version "4.17.15" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" - integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.31" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - "@types/node@*": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" @@ -97,24 +49,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.33.tgz#8c29a0036771569662e4635790ffa9e057db379b" integrity sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg== -"@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/serve-static@*": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== - dependencies: - "@types/mime" "*" - "@types/node" "*" - "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -125,13 +59,6 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== -"@types/ws@*": - version "8.5.4" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" - integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== - dependencies: - "@types/node" "*" - "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" @@ -514,13 +441,6 @@ executable@^4.1.1: dependencies: pify "^2.2.0" -express-ws@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-5.0.2.tgz#5b02d41b937d05199c6c266d7cc931c823bda8eb" - integrity sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ== - dependencies: - ws "^7.4.6" - extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1192,11 +1112,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^7.4.6: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From 5ba18241a74f57b5157fe63aaaa78a66516833ae Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sun, 26 Feb 2023 15:46:14 -0800 Subject: [PATCH 13/54] Add page permissions field to page model --- backend/src/models/pageModel.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/src/models/pageModel.ts b/backend/src/models/pageModel.ts index 869506de..791d4657 100644 --- a/backend/src/models/pageModel.ts +++ b/backend/src/models/pageModel.ts @@ -2,6 +2,13 @@ import mongoose, { Schema } from 'mongoose'; const PageSchema = new Schema({ user: String, + permissions: [ + { + read: [String], + write: [String], + usernameRegex: String + } + ], style: {}, data: [ { From 86eca283ec6c81dac5a5a9d46aeffc7cbcce5d31 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sun, 26 Feb 2023 15:47:39 -0800 Subject: [PATCH 14/54] Add routes and websocket support to Index.ts --- backend/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 890b38fd..d29b6496 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -10,7 +10,6 @@ import dotenv from 'dotenv'; import cors from 'cors'; import setupAuth from './setupAuth'; -import routes from './routes/index'; // -=- Connect to MongoDB with dotenv file -=- dotenv.config(); @@ -48,6 +47,8 @@ app.use(middleware()); // -=- Add Websocket Support -=- expressWs(app); +import routes from './routes/index'; + // -=- Add API Routes -=- app.use('/', routes); From 426ba1b881678277fc6f5bb502bd73e98fab557c Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sun, 26 Feb 2023 16:20:39 -0800 Subject: [PATCH 15/54] Add admin, username field to pageModel.ts --- backend/src/models/pageModel.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/models/pageModel.ts b/backend/src/models/pageModel.ts index 791d4657..b9dead58 100644 --- a/backend/src/models/pageModel.ts +++ b/backend/src/models/pageModel.ts @@ -4,9 +4,10 @@ const PageSchema = new Schema({ user: String, permissions: [ { - read: [String], - write: [String], - usernameRegex: String + read: Boolean, + write: Boolean, + admin: Boolean, + username: String } ], style: {}, From c769d12bb776188f039b06b5e3fe93d0a3edfc55 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Mon, 27 Feb 2023 11:19:21 -0800 Subject: [PATCH 16/54] Add verifyPermissions middleware for managing user permissions on pages. --- backend/src/middleware/verifyPermissions.ts | 100 ++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 backend/src/middleware/verifyPermissions.ts diff --git a/backend/src/middleware/verifyPermissions.ts b/backend/src/middleware/verifyPermissions.ts new file mode 100644 index 00000000..d39d97a4 --- /dev/null +++ b/backend/src/middleware/verifyPermissions.ts @@ -0,0 +1,100 @@ +import { Response, NextFunction } from 'express'; +import { SessionRequest } from 'supertokens-node/framework/express'; +import { verifySession } from 'supertokens-node/recipe/session/framework/express'; + +import PageModel from '../models/pageModel'; + +type ValidPermissions = 'read' | 'write' | 'admin'; + +export interface PageRequest extends SessionRequest { + pageData?: any; +} + +const verifyPermissions = (permissions: ValidPermissions[]) => { + const verifyPermissionsMiddleware = async (req: PageRequest, res: Response, next: NextFunction) => { + const { page } = req.params; + + if (!req.session) { + throw new Error('No session provided to middleware!'); + } + + if (!page) { + res.statusCode = 400; + res.json({ + status: 'error', + message: 'No page was provided...', + }); + return; + } + + const username = req.session.getUserId(); + + if (!username) { + res.statusCode = 401; + res.json({ + status: 'error', + message: 'Please login to modify this page!', + }); + return; + } + + const pageData = await PageModel.findOne({ _id: page }).lean(); + + if (!pageData) { + res.statusCode = 404; + res.json({ + status: 'error', + message: 'This page does not exist...', + }); + return; + } + + if (pageData.user === username) { + req.pageData = pageData; + + next(); + + return; + } + + const userPermissionsOnPage = pageData.permissions + .find((permission: any) => permission.username === username) + .permissions as { [key in ValidPermissions]: boolean }; + + if (!userPermissionsOnPage) { + res.statusCode = 403; + res.json({ + status: 'error', + message: 'You do not have access to this page...', + }); + return; + } + + const hasPermission = permissions.every((permission) => userPermissionsOnPage[permission]); + + if (!hasPermission) { + res.statusCode = 403; + res.json({ + status: 'error', + message: 'You do not have access to this page...', + }); + return; + } + + req.pageData = pageData; + + next(); + }; + + const middleware = async (req: SessionRequest, res: Response, next: NextFunction) => { + verifySession()( + req, + res, + () => verifyPermissionsMiddleware(req, res, next), + ); + }; + + return middleware; +} + +export default verifyPermissions; From 2fed01f0d6a2cc13ab6fa20f30d4ef4108c4035d Mon Sep 17 00:00:00 2001 From: Eroxl Date: Wed, 1 Mar 2023 11:36:16 -0800 Subject: [PATCH 17/54] Allow multiple users to edit pages --- backend/src/helpers/addPage.ts | 5 +++ backend/src/routes/page/getPage.ts | 41 +++---------------- backend/src/routes/page/modifyPage/addPage.ts | 32 +++------------ .../src/routes/page/modifyPage/deletePage.ts | 37 ++++------------- .../src/routes/page/modifyPage/editPage.ts | 34 ++++----------- 5 files changed, 33 insertions(+), 116 deletions(-) diff --git a/backend/src/helpers/addPage.ts b/backend/src/helpers/addPage.ts index a76bd532..de47818e 100644 --- a/backend/src/helpers/addPage.ts +++ b/backend/src/helpers/addPage.ts @@ -10,6 +10,11 @@ const addPage = async ( ) => { const pageMap = await PageMapModel.findById(page).lean(); + if (!pageMap) { + // NOTE:EROXL: Should never happen + throw new Error('Page not found'); + } + const arrayFilters: Record[] = []; let queryString = 'subPages'; diff --git a/backend/src/routes/page/getPage.ts b/backend/src/routes/page/getPage.ts index b009bc62..0b81661f 100644 --- a/backend/src/routes/page/getPage.ts +++ b/backend/src/routes/page/getPage.ts @@ -1,46 +1,15 @@ import express from 'express'; -import { SessionRequest } from 'supertokens-node/framework/express'; -import { verifySession } from 'supertokens-node/recipe/session/framework/express'; -import PageModel from '../../models/pageModel'; +import verifyPermissions from '../../middleware/verifyPermissions'; +import type { PageRequest } from '../../middleware/verifyPermissions'; const router = express.Router(); router.get( '/get-page/:page', - verifySession(), - async (req: SessionRequest, res) => { - const { page } = req.params; - const username = req.session!.getUserId(); - - if (!username) { - res.statusCode = 401; - res.json({ - status: 'error', - message: 'Please login to view this page!', - }); - return; - } - - const pageData = await PageModel.findById(page).lean(); - - if (!pageData) { - res.statusCode = 404; - res.json({ - status: 'error', - message: 'Page not found please try another page...', - }); - return; - } - - if (pageData.user !== username) { - res.statusCode = 403; - res.json({ - status: 'error', - message: 'You do not have access to this file please login with a different account to view it...', - }); - return; - } + verifyPermissions(['read']), + async (req: PageRequest, res) => { + const pageData = req.pageData; res.statusCode = 200; res.json({ diff --git a/backend/src/routes/page/modifyPage/addPage.ts b/backend/src/routes/page/modifyPage/addPage.ts index 3768bf63..72470035 100644 --- a/backend/src/routes/page/modifyPage/addPage.ts +++ b/backend/src/routes/page/modifyPage/addPage.ts @@ -1,36 +1,16 @@ import express from 'express'; -import { SessionRequest } from 'supertokens-node/framework/express'; -import { verifySession } from 'supertokens-node/recipe/session/framework/express'; import addPage from '../../../helpers/addPage'; -import PageTreeModel from '../../../models/pageTreeModel'; +import verifyPermissions from '../../../middleware/verifyPermissions'; +import type { PageRequest } from '../../../middleware/verifyPermissions'; const router = express.Router(); router.post( '/:page/', - verifySession(), - async (req: SessionRequest, res) => { - const username = req.session!.getUserId(); - - if (!username) { - res.statusCode = 401; - res.json({ - status: 'error', - message: 'Please login to view this page!', - }); - return; - } - - const pageTree = await PageTreeModel.findOne({ user: username }, 'user').lean(); - - if (!pageTree) { - res.statusCode = 500; - res.json({ - status: 'error', - message: 'You do not have any pages to add to!', - }); - } + verifyPermissions(['write']), + async (req: PageRequest, res) => { + const pageOwner = req.pageData.user; const { page } = req.params; const { @@ -40,7 +20,7 @@ router.post( await addPage( page, - username, + pageOwner, newPageId, newPageName, ); diff --git a/backend/src/routes/page/modifyPage/deletePage.ts b/backend/src/routes/page/modifyPage/deletePage.ts index b2dd6942..d7e15d8d 100644 --- a/backend/src/routes/page/modifyPage/deletePage.ts +++ b/backend/src/routes/page/modifyPage/deletePage.ts @@ -1,41 +1,22 @@ import express from 'express'; -import { SessionRequest } from 'supertokens-node/framework/express'; -import { verifySession } from 'supertokens-node/recipe/session/framework/express'; import deletePage from '../../../helpers/deletePage'; +import verifyPermissions from '../../../middleware/verifyPermissions'; +import type { PageRequest } from '../../../middleware/verifyPermissions'; const router = express.Router(); router.delete( '/:page/', - verifySession(), - async (req: SessionRequest, res) => { - const username = req.session!.getUserId(); - - if (!username) { - res.statusCode = 401; - res.json({ - status: 'error', - message: 'Please login to view this page!', - }); - return; - } - + verifyPermissions(['admin']), + async (req: PageRequest, res) => { const { page } = req.params; + const pageOwner = req.pageData.user; - try { - await deletePage( - page, - username, - ); - } catch (error) { - res.statusCode = 500; - res.json({ - status: 'error', - message: `Something went wrong and the page could not be deleted, error: ${(error as Error).message}}`, - }); - return; - } + await deletePage( + page, + pageOwner, + ); res.statusCode = 200; res.json({ diff --git a/backend/src/routes/page/modifyPage/editPage.ts b/backend/src/routes/page/modifyPage/editPage.ts index e4b1c8d8..9576de27 100644 --- a/backend/src/routes/page/modifyPage/editPage.ts +++ b/backend/src/routes/page/modifyPage/editPage.ts @@ -1,40 +1,20 @@ import express from 'express'; -import { SessionRequest } from 'supertokens-node/framework/express'; -import { verifySession } from 'supertokens-node/recipe/session/framework/express'; import PageTreeModel from '../../../models/pageTreeModel'; import PageMapModel from '../../../models/pageMap'; import PageModel from '../../../models/pageModel'; +import verifyPermissions from '../../../middleware/verifyPermissions'; +import type { PageRequest } from '../../../middleware/verifyPermissions'; const router = express.Router(); router.patch( '/:page/', - verifySession(), - async (req: SessionRequest, res) => { - const username = req.session!.getUserId(); + verifyPermissions(['write']), + async (req: PageRequest, res) => { const { style } = req.body; const { page } = req.params; - - if (!username) { - res.statusCode = 401; - res.json({ - status: 'error', - message: 'Please login to view this page!', - }); - return; - } - - const pageData = await PageModel.findOne({ _id: page, user: username }, 'user').lean(); - - if (!pageData) { - res.statusCode = 404; - res.json({ - status: 'error', - message: 'You do not have access to this page or it does not exist...', - }); - return; - } + const pageData = req.pageData; const formattedStyleProps = Object.fromEntries(Object.keys(style).map((styleElement) => [`style.${styleElement}`, style[styleElement]])); @@ -72,9 +52,11 @@ router.patch( const formattedStylePropsForTree = Object.fromEntries(Object.keys(style).map((styleElement) => [`${queryString}.style.${styleElement}`, style[styleElement]])); + const pageOwner = pageData.username; + await PageTreeModel.updateOne( { - _id: username, + _id: pageOwner, }, { $set: { From 26672d3726758bc5d23d8129fa3df495983c9b42 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Thu, 2 Mar 2023 10:05:53 -0800 Subject: [PATCH 18/54] Switch page permissions to new middleware --- backend/src/routes/page/getPageInfo.ts | 32 +++---------------- backend/src/routes/page/modify.ts | 31 +++--------------- .../src/routes/page/modifyPage/editPage.ts | 2 +- 3 files changed, 10 insertions(+), 55 deletions(-) diff --git a/backend/src/routes/page/getPageInfo.ts b/backend/src/routes/page/getPageInfo.ts index 82ed82aa..f4d46945 100644 --- a/backend/src/routes/page/getPageInfo.ts +++ b/backend/src/routes/page/getPageInfo.ts @@ -1,37 +1,15 @@ import express from 'express'; -import { SessionRequest } from 'supertokens-node/framework/express'; -import { verifySession } from 'supertokens-node/recipe/session/framework/express'; -import PageModel from '../../models/pageModel'; +import verifyPermissions from '../../middleware/verifyPermissions'; +import type { PageRequest } from '../../middleware/verifyPermissions'; const router = express.Router(); router.get( '/get-page-info/:page', - verifySession(), - async (req: SessionRequest, res) => { - const { page } = req.params; - const username = req.session!.getUserId(); - - if (!username) { - res.statusCode = 401; - res.json({ - status: 'error', - message: 'Please login to view this page!', - }); - return; - } - - const pageData = await PageModel.findById(page, 'style').lean(); - - if (!pageData) { - res.statusCode = 404; - res.json({ - status: 'error', - message: 'Page not found please try another page...', - }); - return; - } + verifyPermissions(['read']), + async (req: PageRequest, res) => { + const pageData = req.pageData; res.statusCode = 200; res.json({ diff --git a/backend/src/routes/page/modify.ts b/backend/src/routes/page/modify.ts index f7dfd0e9..620c988c 100644 --- a/backend/src/routes/page/modify.ts +++ b/backend/src/routes/page/modify.ts @@ -1,40 +1,17 @@ import express from 'express'; -import { SessionRequest } from 'supertokens-node/framework/express'; -import { verifySession } from 'supertokens-node/recipe/session/framework/express'; +import verifyPermissions from '../../middleware/verifyPermissions'; +import type { PageRequest } from '../../middleware/verifyPermissions'; import queryAggregator from '../../helpers/operations/queryAggregator'; import queryGenerator from '../../helpers/operations/queryGenerators'; -import PageModel from '../../models/pageModel'; const router = express.Router(); router.post( '/modify/:page', - verifySession(), - async (req: SessionRequest, res) => { + verifyPermissions(['write']), + async (req: PageRequest, res) => { const { page } = req.params; - const username = req.session!.getUserId(); - - // -=- Check if user is logged in -=- - if (!username) { - res.statusCode = 401; - res.json({ - status: 'error', - message: 'Please login to modify this page!', - }); - return; - } - - // -=- Check if user has permissions to access this page -=- - const pageData = await PageModel.findOne({ _id: page, user: username }, 'user').lean(); - - if (!pageData) { - res.statusCode = 404; - res.json({ - status: 'error', - message: 'You do not have access to this page or it does not exist...', - }); - } // -=- If the user has permissions start updating the page -=- const { operations } = req.body; diff --git a/backend/src/routes/page/modifyPage/editPage.ts b/backend/src/routes/page/modifyPage/editPage.ts index 9576de27..c0805f0e 100644 --- a/backend/src/routes/page/modifyPage/editPage.ts +++ b/backend/src/routes/page/modifyPage/editPage.ts @@ -10,7 +10,7 @@ const router = express.Router(); router.patch( '/:page/', - verifyPermissions(['write']), + verifyPermissions(['admin']), async (req: PageRequest, res) => { const { style } = req.body; const { page } = req.params; From e502963b3be918a81967b4282dd0e0663e438c36 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Thu, 2 Mar 2023 18:42:45 -0800 Subject: [PATCH 19/54] Ensure page permissions are propagated to new sub pages --- backend/src/helpers/addPage.ts | 7 +++++++ backend/src/routes/page/modifyPage/addPage.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/backend/src/helpers/addPage.ts b/backend/src/helpers/addPage.ts index de47818e..a3cc02b7 100644 --- a/backend/src/helpers/addPage.ts +++ b/backend/src/helpers/addPage.ts @@ -7,6 +7,12 @@ const addPage = async ( username: string, newPageId?: string, newPageName?: string, + pagePermissions?: { + read: boolean, + write: boolean, + admin: boolean, + username: string, + }[], ) => { const pageMap = await PageMapModel.findById(page).lean(); @@ -77,6 +83,7 @@ const addPage = async ( icon: '📝', name: newPageName || 'New Notebook', }, + permissions: pagePermissions || [], data: [], }); }; diff --git a/backend/src/routes/page/modifyPage/addPage.ts b/backend/src/routes/page/modifyPage/addPage.ts index 72470035..edd51811 100644 --- a/backend/src/routes/page/modifyPage/addPage.ts +++ b/backend/src/routes/page/modifyPage/addPage.ts @@ -11,6 +11,7 @@ router.post( verifyPermissions(['write']), async (req: PageRequest, res) => { const pageOwner = req.pageData.user; + const pageData = req.pageData; const { page } = req.params; const { From 4dfdd6d3c297f9292113e1a2c64dd50673c7fa67 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Fri, 3 Mar 2023 16:39:41 -0800 Subject: [PATCH 20/54] Fix issue with selection data being consumed twice --- web/src/components/Editor.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/src/components/Editor.tsx b/web/src/components/Editor.tsx index 4c9b90fd..16e684a1 100644 --- a/web/src/components/Editor.tsx +++ b/web/src/components/Editor.tsx @@ -32,11 +32,15 @@ const Editor = (props: EditorProps) => { // -=- Setup Selection -=- const selectionData = useSelectionCollector('blocks'); + const [selectionDataConsumed, setSelectionDataConsumed] = useState(false); useEffect(() => { const handleSelectionEvents = (event: KeyboardEvent) => { // ~ If the user is not pressing the backspace key, return if (event.key !== 'Backspace') return; + + // ~ If the selection data has already been consumed, return + if (selectionDataConsumed) return; // ~ Iterate over all the selected blocks for (let i = 0; i < selectionData.length; i += 1) { @@ -57,6 +61,9 @@ const Editor = (props: EditorProps) => { // ~ Remove the block from the page removeBlock(index - i, [blockID], page as string, pageData, setPageData); } + + // ~ Reset the selection data + setSelectionDataConsumed(true); }; // ~ Add the event listener @@ -66,6 +73,11 @@ const Editor = (props: EditorProps) => { return () => { document.removeEventListener('keydown', handleSelectionEvents); }; + }, [selectionData, selectionDataConsumed]); + + useEffect(() => { + // ~ Reset the selection data, when the selection changes + setSelectionDataConsumed(false); }, [selectionData]); // -=- Render -=- From b424405f8d4adde6c2255982c6cc7c9009929ff0 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Mon, 6 Mar 2023 12:39:46 -0800 Subject: [PATCH 21/54] Add buttons for inviting users --- web/src/components/menus/EmailShareMenu.tsx | 174 ++++++++++++++++++ web/src/components/menus/ShareMenu.tsx | 128 +++++++++++++ .../pageCustomization/ShareButton.tsx | 39 ++++ web/src/pages/note-rack/[page].tsx | 8 +- web/src/public/Globe.svg | 13 ++ 5 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 web/src/components/menus/EmailShareMenu.tsx create mode 100644 web/src/components/menus/ShareMenu.tsx create mode 100644 web/src/components/pageCustomization/ShareButton.tsx create mode 100644 web/src/public/Globe.svg diff --git a/web/src/components/menus/EmailShareMenu.tsx b/web/src/components/menus/EmailShareMenu.tsx new file mode 100644 index 00000000..e7270b65 --- /dev/null +++ b/web/src/components/menus/EmailShareMenu.tsx @@ -0,0 +1,174 @@ +import React, { useState, useRef, useEffect } from 'react'; + +interface ShareMenuProps { + page: string, + setIsEditingEmails: (isEditingEmails: boolean) => void, +} + +enum DropdownOptions { + FullAccess, + EditOnly, + ViewOnly, +} + +const EmailShareMenu = (props: ShareMenuProps) => { + const { page, setIsEditingEmails } = props; + + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [selectedDropdownOption, setSelectedDropdownOption] = useState(DropdownOptions.FullAccess); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current + && !dropdownRef.current.contains(event.target as Node) + ) { + setIsDropdownOpen(false); + } + } + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + } + }); + + const dropdownInfo: { + [key: number]: { + title: string, + description: string, + } + } = { + [DropdownOptions.FullAccess]: { + title: 'Full access', + description: 'Can edit, delete, and share' + }, + [DropdownOptions.EditOnly]: { + title: 'Edit only', + description: 'Can edit, but not delete or share' + }, + [DropdownOptions.ViewOnly]: { + title: 'View only', + description: 'Cannot edit, delete, or share' + }, + } + + return ( + <> +
+ + {/* Dropdown */} +
{ + setIsDropdownOpen(!isDropdownOpen); + }} + ref={dropdownRef} + > + { + dropdownInfo[selectedDropdownOption].title + } + + + + { + isDropdownOpen && ( +
+
    + { + Object.values(dropdownInfo).map((_, option) => ( +
  • { + setSelectedDropdownOption(option); + setIsDropdownOpen(false); + }} + > +
    +
    + { + dropdownInfo[option].title + } +
    +
    + { + dropdownInfo[option].description + } +
    +
    + { + selectedDropdownOption === option && ( + + + + ) + } +
  • + )) + } +
+
+ ) + } +
+
+
+ + +
+ + ); +} + +export default EmailShareMenu; diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx new file mode 100644 index 00000000..3458ca82 --- /dev/null +++ b/web/src/components/menus/ShareMenu.tsx @@ -0,0 +1,128 @@ +import React, { useEffect, useState } from 'react'; +import Image from 'next/image'; + +import EmailShareMenu from './EmailShareMenu'; +import Globe from '../../public/Globe.svg'; + +interface ShareMenuProps { + page: string, + setIsMenuOpen: (isMenuOpen: boolean) => void, + buttonRef: React.RefObject, +} + +const ShareMenu = (props: ShareMenuProps) => { + const { page, setIsMenuOpen, buttonRef } = props; + const [isPagePublic, setIsPagePublic] = useState(false); + const [isEditingEmails, setIsEditingEmails] = useState(false); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + buttonRef.current + && !buttonRef.current.contains(event.target as Node) + ) { + setIsMenuOpen(false); + } + } + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + } + }); + + return ( +
+ {isEditingEmails + ? ( + + ) + : ( + <> +
{ + setIsEditingEmails(true); + }} + > +
+ Add email +
+
+ Invite +
+
+
+
{ + setIsPagePublic(!isPagePublic); + } } + > + Globe +
+ Share to Web + + {isPagePublic + ? 'Anyone with the link can view this page.' + : 'Only people you share the link with can view this page.'} + +
+ +
+
{ + navigator.clipboard.writeText(document.location.href); + } } + > + Copy Link +
+ + ) + } +
+ ); +} + +export default ShareMenu; diff --git a/web/src/components/pageCustomization/ShareButton.tsx b/web/src/components/pageCustomization/ShareButton.tsx new file mode 100644 index 00000000..5e7df9ff --- /dev/null +++ b/web/src/components/pageCustomization/ShareButton.tsx @@ -0,0 +1,39 @@ +import React, { useState, useRef } from 'react'; +import { useRouter } from 'next/router'; + +import ShareMenu from '../menus/ShareMenu'; + +const ShareButton = () => { + const { page } = useRouter().query; + const [isMenuOpen, setIsMenuOpen] = useState(false); + const buttonRef = useRef(null); + + return ( +
+
+
{ + setIsMenuOpen(!isMenuOpen); + }} + > + Share +
+
+ { + isMenuOpen && ( + + ) + } +
+ ) +} + + +export default ShareButton; diff --git a/web/src/pages/note-rack/[page].tsx b/web/src/pages/note-rack/[page].tsx index ee68168a..e92ed7f6 100644 --- a/web/src/pages/note-rack/[page].tsx +++ b/web/src/pages/note-rack/[page].tsx @@ -10,6 +10,7 @@ import PageSidebar from '../../components/pageInfo/PageSidebar'; import Editor from '../../components/Editor'; import LoadingPage from '../../components/LoadingPage'; import SaveManager from '../../classes/SaveManager'; +import ShareButton from '../../components/pageCustomization/ShareButton'; const NoteRackPage = (props: {pageDataReq: Promise}) => { const [pageData, setPageData] = useState>({}); @@ -56,8 +57,11 @@ const NoteRackPage = (props: {pageDataReq: Promise}) => { }
-
- +
+
+ + +
diff --git a/web/src/public/Globe.svg b/web/src/public/Globe.svg new file mode 100644 index 00000000..0425d11d --- /dev/null +++ b/web/src/public/Globe.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + From ff035e05b6a50392804e3f8be2fe32bce0377456 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sat, 11 Mar 2023 10:15:57 -0800 Subject: [PATCH 22/54] Refactor middleware to use typed models and interfaces. --- backend/src/middleware/verifyPermissions.ts | 5 +++-- backend/src/models/pageMap.ts | 9 +++++++-- backend/src/models/pageModel.ts | 20 +++++++++++++++++-- backend/src/models/pageTreeModel.ts | 11 ++++++++-- backend/src/models/userModel.ts | 9 +++++++-- backend/src/routes/page/getHomePage.ts | 2 +- backend/src/routes/page/getPage.ts | 2 +- backend/src/routes/page/getPageInfo.ts | 2 +- backend/src/routes/page/modifyPage/addPage.ts | 3 +-- .../src/routes/page/modifyPage/deletePage.ts | 2 +- .../src/routes/page/modifyPage/editPage.ts | 4 ++-- 11 files changed, 51 insertions(+), 18 deletions(-) diff --git a/backend/src/middleware/verifyPermissions.ts b/backend/src/middleware/verifyPermissions.ts index d39d97a4..bd765b3c 100644 --- a/backend/src/middleware/verifyPermissions.ts +++ b/backend/src/middleware/verifyPermissions.ts @@ -3,11 +3,13 @@ import { SessionRequest } from 'supertokens-node/framework/express'; import { verifySession } from 'supertokens-node/recipe/session/framework/express'; import PageModel from '../models/pageModel'; +import type { IPage } from '../models/pageModel'; type ValidPermissions = 'read' | 'write' | 'admin'; export interface PageRequest extends SessionRequest { - pageData?: any; + pageData?: IPage, + permissions?: ValidPermissions[], } const verifyPermissions = (permissions: ValidPermissions[]) => { @@ -59,7 +61,6 @@ const verifyPermissions = (permissions: ValidPermissions[]) => { const userPermissionsOnPage = pageData.permissions .find((permission: any) => permission.username === username) - .permissions as { [key in ValidPermissions]: boolean }; if (!userPermissionsOnPage) { res.statusCode = 403; diff --git a/backend/src/models/pageMap.ts b/backend/src/models/pageMap.ts index eceeaa43..2618cc60 100644 --- a/backend/src/models/pageMap.ts +++ b/backend/src/models/pageMap.ts @@ -1,10 +1,15 @@ import mongoose, { Schema } from 'mongoose'; -const PageMapScheme = new Schema({ +export interface IPageMap { + _id: string; + pathToPage: string[]; +} + +const PageMapScheme = new Schema({ _id: Schema.Types.String, pathToPage: [Schema.Types.String], }); -const PageMapModel = mongoose.models.pageMap || mongoose.model('pageMap', PageMapScheme, 'PageMap'); +const PageMapModel = mongoose.models.pageMap as mongoose.Model || mongoose.model('pageMap', PageMapScheme); export default PageMapModel; diff --git a/backend/src/models/pageModel.ts b/backend/src/models/pageModel.ts index b9dead58..c5c46eeb 100644 --- a/backend/src/models/pageModel.ts +++ b/backend/src/models/pageModel.ts @@ -1,6 +1,22 @@ import mongoose, { Schema } from 'mongoose'; -const PageSchema = new Schema({ +export interface IPage { + user: string; + permissions: { + read: boolean; + write: boolean; + admin: boolean; + username: string; + }[]; + style: {}; + data: { + blockType: string; + properties: {}; + children: []; + }[]; +} + +const PageSchema = new Schema({ user: String, permissions: [ { @@ -20,6 +36,6 @@ const PageSchema = new Schema({ ], }); -const PageModel = mongoose.models.page || mongoose.model('page', PageSchema, 'Page'); +const PageModel = mongoose.models.page as mongoose.Model || mongoose.model('page', PageSchema); export default PageModel; diff --git a/backend/src/models/pageTreeModel.ts b/backend/src/models/pageTreeModel.ts index f152bea0..c89c8f73 100644 --- a/backend/src/models/pageTreeModel.ts +++ b/backend/src/models/pageTreeModel.ts @@ -1,6 +1,13 @@ import mongoose, { Schema } from 'mongoose'; -const PageTreeSchema = new Schema({}); +interface IPageTree { + _id: string; + expanded: boolean; + style: {}; + subPages: IPageTree[]; +} + +const PageTreeSchema = new Schema({}); PageTreeSchema.add({ _id: Schema.Types.String, @@ -9,6 +16,6 @@ PageTreeSchema.add({ subPages: [PageTreeSchema], }); -const PageTreeModel = mongoose.models.pageTree || mongoose.model('pageTree', PageTreeSchema, 'PageTree'); +const PageTreeModel = mongoose.models.pageTree as mongoose.Model || mongoose.model('pageTree', PageTreeSchema); export default PageTreeModel; diff --git a/backend/src/models/userModel.ts b/backend/src/models/userModel.ts index 5d7eb6ad..f1ef3a6c 100644 --- a/backend/src/models/userModel.ts +++ b/backend/src/models/userModel.ts @@ -1,6 +1,11 @@ import mongoose, { Schema } from 'mongoose'; -const UserScheme = new Schema({ +interface IUser { + username: string; + homePage: string; +} + +const UserScheme = new Schema({ username: { type: Schema.Types.String, required: true, @@ -9,6 +14,6 @@ const UserScheme = new Schema({ homePage: Schema.Types.String, }); -const UserModel = mongoose.models.user || mongoose.model('user', UserScheme, 'User'); +const UserModel = mongoose.models.user as mongoose.Model || mongoose.model('user', UserScheme); export default UserModel; diff --git a/backend/src/routes/page/getHomePage.ts b/backend/src/routes/page/getHomePage.ts index e2048981..f814a770 100644 --- a/backend/src/routes/page/getHomePage.ts +++ b/backend/src/routes/page/getHomePage.ts @@ -10,7 +10,7 @@ router.post( '/get-home-page', verifySession(), async (req: SessionRequest, res) => { - const username = req.session!.getUserId(); + const username = req.session?.getUserId(); if (!username) { res.statusCode = 401; diff --git a/backend/src/routes/page/getPage.ts b/backend/src/routes/page/getPage.ts index 0b81661f..9a6ab54e 100644 --- a/backend/src/routes/page/getPage.ts +++ b/backend/src/routes/page/getPage.ts @@ -9,7 +9,7 @@ router.get( '/get-page/:page', verifyPermissions(['read']), async (req: PageRequest, res) => { - const pageData = req.pageData; + const pageData = req.pageData!; res.statusCode = 200; res.json({ diff --git a/backend/src/routes/page/getPageInfo.ts b/backend/src/routes/page/getPageInfo.ts index f4d46945..846f967a 100644 --- a/backend/src/routes/page/getPageInfo.ts +++ b/backend/src/routes/page/getPageInfo.ts @@ -9,7 +9,7 @@ router.get( '/get-page-info/:page', verifyPermissions(['read']), async (req: PageRequest, res) => { - const pageData = req.pageData; + const pageData = req.pageData!; res.statusCode = 200; res.json({ diff --git a/backend/src/routes/page/modifyPage/addPage.ts b/backend/src/routes/page/modifyPage/addPage.ts index edd51811..f4739cf6 100644 --- a/backend/src/routes/page/modifyPage/addPage.ts +++ b/backend/src/routes/page/modifyPage/addPage.ts @@ -10,8 +10,7 @@ router.post( '/:page/', verifyPermissions(['write']), async (req: PageRequest, res) => { - const pageOwner = req.pageData.user; - const pageData = req.pageData; + const pageOwner = req.pageData!.user; const { page } = req.params; const { diff --git a/backend/src/routes/page/modifyPage/deletePage.ts b/backend/src/routes/page/modifyPage/deletePage.ts index d7e15d8d..8f0da251 100644 --- a/backend/src/routes/page/modifyPage/deletePage.ts +++ b/backend/src/routes/page/modifyPage/deletePage.ts @@ -11,7 +11,7 @@ router.delete( verifyPermissions(['admin']), async (req: PageRequest, res) => { const { page } = req.params; - const pageOwner = req.pageData.user; + const pageOwner = req.pageData!.user; await deletePage( page, diff --git a/backend/src/routes/page/modifyPage/editPage.ts b/backend/src/routes/page/modifyPage/editPage.ts index c0805f0e..a9c3e99e 100644 --- a/backend/src/routes/page/modifyPage/editPage.ts +++ b/backend/src/routes/page/modifyPage/editPage.ts @@ -14,7 +14,7 @@ router.patch( async (req: PageRequest, res) => { const { style } = req.body; const { page } = req.params; - const pageData = req.pageData; + const pageData = req.pageData!; const formattedStyleProps = Object.fromEntries(Object.keys(style).map((styleElement) => [`style.${styleElement}`, style[styleElement]])); @@ -52,7 +52,7 @@ router.patch( const formattedStylePropsForTree = Object.fromEntries(Object.keys(style).map((styleElement) => [`${queryString}.style.${styleElement}`, style[styleElement]])); - const pageOwner = pageData.username; + const pageOwner = pageData.user; await PageTreeModel.updateOne( { From 39cfbca01699f0b0832066b56201a7b3ef99185c Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sat, 11 Mar 2023 10:29:09 -0800 Subject: [PATCH 23/54] Add permissions to pageData --- backend/src/middleware/verifyPermissions.ts | 5 +++++ backend/src/routes/page/getPage.ts | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/backend/src/middleware/verifyPermissions.ts b/backend/src/middleware/verifyPermissions.ts index bd765b3c..a6cf45fe 100644 --- a/backend/src/middleware/verifyPermissions.ts +++ b/backend/src/middleware/verifyPermissions.ts @@ -54,6 +54,7 @@ const verifyPermissions = (permissions: ValidPermissions[]) => { if (pageData.user === username) { req.pageData = pageData; + req.permissions = ['admin', 'write', 'read']; next(); return; @@ -83,6 +84,10 @@ const verifyPermissions = (permissions: ValidPermissions[]) => { } req.pageData = pageData; + req.permissions = Object + .entries(userPermissionsOnPage) + .filter(([_, value]) => value === true) + .map(([key, _]) => key as ValidPermissions); next(); }; diff --git a/backend/src/routes/page/getPage.ts b/backend/src/routes/page/getPage.ts index 9a6ab54e..e4e6bcfe 100644 --- a/backend/src/routes/page/getPage.ts +++ b/backend/src/routes/page/getPage.ts @@ -10,6 +10,7 @@ router.get( verifyPermissions(['read']), async (req: PageRequest, res) => { const pageData = req.pageData!; + const permissions = req.permissions!; res.statusCode = 200; res.json({ @@ -17,6 +18,11 @@ router.get( message: { style: pageData.style, data: pageData.data, + ...( + permissions.includes('admin') + ? { permissions: pageData.permissions } + : {} + ) }, }); }, From cda897522eb40c6b5354d11ac8a5acf943266868 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sat, 11 Mar 2023 12:48:17 -0800 Subject: [PATCH 24/54] Implement ability to update page permissions --- backend/src/middleware/verifyPermissions.ts | 5 +- backend/src/models/pageModel.ts | 20 +++---- backend/src/routes/page/index.ts | 7 +++ backend/src/routes/page/updatePermissions.ts | 55 ++++++++++++++++++++ web/src/components/menus/EmailShareMenu.tsx | 47 +++++++++++++++-- 5 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 backend/src/routes/page/updatePermissions.ts diff --git a/backend/src/middleware/verifyPermissions.ts b/backend/src/middleware/verifyPermissions.ts index a6cf45fe..d2a46c7a 100644 --- a/backend/src/middleware/verifyPermissions.ts +++ b/backend/src/middleware/verifyPermissions.ts @@ -60,8 +60,9 @@ const verifyPermissions = (permissions: ValidPermissions[]) => { return; } - const userPermissionsOnPage = pageData.permissions - .find((permission: any) => permission.username === username) + console.log(pageData.permissions); + + const userPermissionsOnPage = pageData.permissions[username] || undefined; if (!userPermissionsOnPage) { res.statusCode = 403; diff --git a/backend/src/models/pageModel.ts b/backend/src/models/pageModel.ts index c5c46eeb..d4a90b60 100644 --- a/backend/src/models/pageModel.ts +++ b/backend/src/models/pageModel.ts @@ -3,11 +3,12 @@ import mongoose, { Schema } from 'mongoose'; export interface IPage { user: string; permissions: { - read: boolean; - write: boolean; - admin: boolean; - username: string; - }[]; + [key: string]: { + read: boolean; + write: boolean; + admin: boolean; + }; + }; style: {}; data: { blockType: string; @@ -18,14 +19,7 @@ export interface IPage { const PageSchema = new Schema({ user: String, - permissions: [ - { - read: Boolean, - write: Boolean, - admin: Boolean, - username: String - } - ], + permissions: {}, style: {}, data: [ { diff --git a/backend/src/routes/page/index.ts b/backend/src/routes/page/index.ts index 2faab52b..9f6fb574 100644 --- a/backend/src/routes/page/index.ts +++ b/backend/src/routes/page/index.ts @@ -5,6 +5,7 @@ import modify from './modify'; import getHomePage from './getHomePage'; import getPage from './getPage'; import getPageInfo from './getPageInfo'; +import updatePermissions from './updatePermissions'; const router = express.Router(); @@ -32,6 +33,12 @@ router.use( modify, ); +// -=- Create Update Permissions API -=- +router.use( + '/', + updatePermissions, +); + // -=- Create Modify Page API -=- router.use( '/modify-page/', diff --git a/backend/src/routes/page/updatePermissions.ts b/backend/src/routes/page/updatePermissions.ts new file mode 100644 index 00000000..f1007d32 --- /dev/null +++ b/backend/src/routes/page/updatePermissions.ts @@ -0,0 +1,55 @@ +import express from 'express'; +import ThirdParty from 'supertokens-node/recipe/thirdparty'; + +import PageModel from '../../models/pageModel'; +import verifyPermissions from '../../middleware/verifyPermissions'; + +const router = express.Router(); + +router.post( + '/update-permissions/:page/', + verifyPermissions(['admin']), + async (req, res) => { + const { page } = req.params; + const { email, permissions } = req.body; + + // -=- If the user has permissions start updating the page -=- + if (!email || !permissions) { + res.statusCode = 400; + res.json({ + status: 'error', + message: 'No email or permissions were provided...', + }); + return; + } + + // ~ Get the user ID from the email + const userId = (await ThirdParty.getUsersByEmail(email)).map((user) => user.id); + + if (!userId || userId.length === 0) { + res.statusCode = 404; + res.json({ + status: 'error', + message: 'This user does not exist...', + }); + return; + } + + await PageModel.findByIdAndUpdate( + page, + { + $set: { + [`permissions.${userId[0]}`]: permissions, + }, + }, + ); + + res.statusCode = 200; + res.json({ + status: 'success', + message: 'Page updated successfully...', + }); + }, +); + +export default router; diff --git a/web/src/components/menus/EmailShareMenu.tsx b/web/src/components/menus/EmailShareMenu.tsx index e7270b65..a824935e 100644 --- a/web/src/components/menus/EmailShareMenu.tsx +++ b/web/src/components/menus/EmailShareMenu.tsx @@ -17,6 +17,7 @@ const EmailShareMenu = (props: ShareMenuProps) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [selectedDropdownOption, setSelectedDropdownOption] = useState(DropdownOptions.FullAccess); const dropdownRef = useRef(null); + const emailInputRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -39,19 +40,39 @@ const EmailShareMenu = (props: ShareMenuProps) => { [key: number]: { title: string, description: string, + permissions: { + admin: boolean, + edit: boolean, + read: boolean, + } } } = { [DropdownOptions.FullAccess]: { title: 'Full access', - description: 'Can edit, delete, and share' + description: 'Can edit, delete, and share', + permissions: { + admin: true, + edit: true, + read: true, + } }, [DropdownOptions.EditOnly]: { title: 'Edit only', - description: 'Can edit, but not delete or share' + description: 'Can edit, but not delete or share', + permissions: { + admin: false, + edit: true, + read: true, + }, }, [DropdownOptions.ViewOnly]: { title: 'View only', - description: 'Cannot edit, delete, or share' + description: 'Cannot edit, delete, or share', + permissions: { + admin: false, + edit: false, + read: true, + } }, } @@ -65,6 +86,7 @@ const EmailShareMenu = (props: ShareMenuProps) => { placeholder="Add email" type="email" autoFocus + ref={emailInputRef} /> {/* Dropdown */}
{ className="px-2 bg-blue-500 border border-blue-600 rounded text-amber-50" onClick={() => { setIsEditingEmails(false); - // TODO: Send email invite + const email = emailInputRef.current?.value; + if (!email) return; + + (async () => { + await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/page/update-permissions/${page}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email, + permissions: dropdownInfo[selectedDropdownOption].permissions, + }), + } + ) + })(); }} > Invite From 0b9cfa1adafe71c31554621668400ecfe047d838 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sun, 12 Mar 2023 14:41:47 -0700 Subject: [PATCH 25/54] Allow solely admin users to update page permissions --- backend/src/middleware/verifyPermissions.ts | 11 +- backend/src/models/pageModel.ts | 1 + backend/src/routes/page/getPage.ts | 5 + backend/src/routes/page/updatePermissions.ts | 11 +- web/src/components/menus/EmailShareMenu.tsx | 9 +- web/src/components/menus/ShareMenu.tsx | 190 +++++++++++++----- .../pageCustomization/ShareButton.tsx | 11 +- web/src/lib/helpers/focusHelpers.ts | 6 +- web/src/lib/pages/updatePage.ts | 24 ++- web/src/pages/note-rack/[page].tsx | 9 +- web/src/types/pageTypes.ts | 21 +- 11 files changed, 218 insertions(+), 80 deletions(-) diff --git a/backend/src/middleware/verifyPermissions.ts b/backend/src/middleware/verifyPermissions.ts index d2a46c7a..9e9f4c1f 100644 --- a/backend/src/middleware/verifyPermissions.ts +++ b/backend/src/middleware/verifyPermissions.ts @@ -60,10 +60,17 @@ const verifyPermissions = (permissions: ValidPermissions[]) => { return; } - console.log(pageData.permissions); - const userPermissionsOnPage = pageData.permissions[username] || undefined; + // Merge pageData.permissions['*'] with userPermissionsOnPage + if (pageData.permissions['*']) { + Object.entries(pageData.permissions['*']).forEach(([key, value]) => { + if (userPermissionsOnPage && key !== 'email') { + userPermissionsOnPage[key as ValidPermissions] = (userPermissionsOnPage[key as ValidPermissions] || value) as boolean; + } + }); + } + if (!userPermissionsOnPage) { res.statusCode = 403; res.json({ diff --git a/backend/src/models/pageModel.ts b/backend/src/models/pageModel.ts index d4a90b60..4b12dc47 100644 --- a/backend/src/models/pageModel.ts +++ b/backend/src/models/pageModel.ts @@ -7,6 +7,7 @@ export interface IPage { read: boolean; write: boolean; admin: boolean; + email: string; }; }; style: {}; diff --git a/backend/src/routes/page/getPage.ts b/backend/src/routes/page/getPage.ts index e4e6bcfe..5c210255 100644 --- a/backend/src/routes/page/getPage.ts +++ b/backend/src/routes/page/getPage.ts @@ -18,6 +18,11 @@ router.get( message: { style: pageData.style, data: pageData.data, + userPermissions: { + read: true, + write: permissions.includes('write'), + admin: permissions.includes('admin'), + }, ...( permissions.includes('admin') ? { permissions: pageData.permissions } diff --git a/backend/src/routes/page/updatePermissions.ts b/backend/src/routes/page/updatePermissions.ts index f1007d32..3dfed029 100644 --- a/backend/src/routes/page/updatePermissions.ts +++ b/backend/src/routes/page/updatePermissions.ts @@ -23,8 +23,10 @@ router.post( return; } - // ~ Get the user ID from the email - const userId = (await ThirdParty.getUsersByEmail(email)).map((user) => user.id); + // ~ Get the user ID from the email (or * if it's a wildcard) + const userId = email === '*' + ? ['*'] + : (await ThirdParty.getUsersByEmail(email)).map((user) => user.id) if (!userId || userId.length === 0) { res.statusCode = 404; @@ -39,7 +41,10 @@ router.post( page, { $set: { - [`permissions.${userId[0]}`]: permissions, + [`permissions.${userId[0]}`]: { + ...permissions, + email, + }, }, }, ); diff --git a/web/src/components/menus/EmailShareMenu.tsx b/web/src/components/menus/EmailShareMenu.tsx index a824935e..1d95d15b 100644 --- a/web/src/components/menus/EmailShareMenu.tsx +++ b/web/src/components/menus/EmailShareMenu.tsx @@ -3,6 +3,7 @@ import React, { useState, useRef, useEffect } from 'react'; interface ShareMenuProps { page: string, setIsEditingEmails: (isEditingEmails: boolean) => void, + addPermissions: (email: string, currentPermissions: any) => void, } enum DropdownOptions { @@ -12,7 +13,7 @@ enum DropdownOptions { } const EmailShareMenu = (props: ShareMenuProps) => { - const { page, setIsEditingEmails } = props; + const { page, setIsEditingEmails, addPermissions } = props; const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [selectedDropdownOption, setSelectedDropdownOption] = useState(DropdownOptions.FullAccess); @@ -187,7 +188,7 @@ const EmailShareMenu = (props: ShareMenuProps) => { if (!email) return; (async () => { - await fetch( + const status = await fetch( `${process.env.NEXT_PUBLIC_API_URL}/page/update-permissions/${page}`, { method: 'POST', @@ -200,6 +201,10 @@ const EmailShareMenu = (props: ShareMenuProps) => { }), } ) + + if (status.ok) { + addPermissions(email, dropdownInfo[selectedDropdownOption].permissions); + } })(); }} > diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx index 3458ca82..1e22911a 100644 --- a/web/src/components/menus/ShareMenu.tsx +++ b/web/src/components/menus/ShareMenu.tsx @@ -3,17 +3,39 @@ import Image from 'next/image'; import EmailShareMenu from './EmailShareMenu'; import Globe from '../../public/Globe.svg'; +import type PageDataInterface from '../../types/pageTypes'; interface ShareMenuProps { page: string, setIsMenuOpen: (isMenuOpen: boolean) => void, buttonRef: React.RefObject, + pagePermissions?: PageDataInterface['message']['permissions'], + permissionsOnPage?: PageDataInterface['message']['userPermissions'], } const ShareMenu = (props: ShareMenuProps) => { - const { page, setIsMenuOpen, buttonRef } = props; + const { + page, + setIsMenuOpen, + buttonRef, + pagePermissions, + permissionsOnPage, + } = props; const [isPagePublic, setIsPagePublic] = useState(false); const [isEditingEmails, setIsEditingEmails] = useState(false); + const [currentPermissions, setCurrentPermissions] = useState({}); + + useEffect(() => { + if (pagePermissions) { + setCurrentPermissions(pagePermissions); + } + }, [pagePermissions]); + + useEffect(() => { + if (currentPermissions && currentPermissions['*']?.read) { + setIsPagePublic(true); + } + }, [currentPermissions, isPagePublic]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -32,6 +54,27 @@ const ShareMenu = (props: ShareMenuProps) => { } }); + const addPermissions = (email: string, newPermissions: any) => { + if (!currentPermissions) { + setCurrentPermissions({ + [email]: { + ...newPermissions, + }, + }); + return; + } + + const key = Object.entries(currentPermissions).find(([, value]) => value.email === email)?.[0] || email; + + setCurrentPermissions({ + ...currentPermissions, + [key]: { + ...newPermissions, + email, + }, + }); + } + return (
{ ) : ( @@ -54,70 +98,108 @@ const ShareMenu = (props: ShareMenuProps) => {
{ + if (!permissionsOnPage?.admin) return; + setIsEditingEmails(true); }} - > + >
Add email
Invite
-
-
{ - setIsPagePublic(!isPagePublic); - } } - > - Globe -
- Share to Web - - {isPagePublic - ? 'Anyone with the link can view this page.' - : 'Only people you share the link with can view this page.'} - -
- -
-
{ - navigator.clipboard.writeText(document.location.href); - } } - > - Copy Link -
+ { + permissionsOnPage?.admin && ( + <> + { + (currentPermissions && !isPagePublic) && ( +
+ Shared with + { + Object.values(currentPermissions).map((permission) => ( +
+ {permission.email} +
+ )) + } +
+ ) + } +
+
{ + setIsPagePublic(!isPagePublic); + }} + > + Globe +
+ Share to Web + + {isPagePublic + ? 'Anyone with the link can view this page.' + : 'Only people you share the link with can view this page.'} + +
+ +
+
+
{ + navigator.clipboard.writeText(document.location.href); + }} + > + Copy Link +
+ + ) + } ) } diff --git a/web/src/components/pageCustomization/ShareButton.tsx b/web/src/components/pageCustomization/ShareButton.tsx index 5e7df9ff..ef7d3663 100644 --- a/web/src/components/pageCustomization/ShareButton.tsx +++ b/web/src/components/pageCustomization/ShareButton.tsx @@ -1,9 +1,16 @@ import React, { useState, useRef } from 'react'; import { useRouter } from 'next/router'; +import type { Permissions, UserPermissions } from '../../types/pageTypes'; import ShareMenu from '../menus/ShareMenu'; -const ShareButton = () => { +interface ShareButtonProps { + pagePermissions?: Permissions + permissionsOnPage?: UserPermissions +} + +const ShareButton = (props: ShareButtonProps) => { + const { pagePermissions, permissionsOnPage } = props; const { page } = useRouter().query; const [isMenuOpen, setIsMenuOpen] = useState(false); const buttonRef = useRef(null); @@ -28,6 +35,8 @@ const ShareButton = () => { page={page as string} setIsMenuOpen={setIsMenuOpen} buttonRef={buttonRef} + pagePermissions={pagePermissions} + permissionsOnPage={permissionsOnPage} /> ) } diff --git a/web/src/lib/helpers/focusHelpers.ts b/web/src/lib/helpers/focusHelpers.ts index d46dab3c..195add42 100644 --- a/web/src/lib/helpers/focusHelpers.ts +++ b/web/src/lib/helpers/focusHelpers.ts @@ -59,7 +59,7 @@ const getNextEditableBlock = ( while (index > 0) { index -= 1; - const block = document.getElementById(pageData.message.data[index]._id) + const block = document.getElementById(pageData.message!.data[index]._id) if (block?.getAttribute('contenteditable') === 'true') { return block; } @@ -73,10 +73,10 @@ const getNextEditableBlock = ( } // ~ Find the next editable block - while (index < pageData.message.data.length - 1) { + while (index < pageData.message!.data.length - 1) { index += 1; - const block = document.getElementById(pageData.message.data[index]._id) + const block = document.getElementById(pageData.message!.data[index]._id) if (block?.getAttribute('contenteditable') === 'true') { return block; } diff --git a/web/src/lib/pages/updatePage.ts b/web/src/lib/pages/updatePage.ts index d0932742..b0e73f84 100644 --- a/web/src/lib/pages/updatePage.ts +++ b/web/src/lib/pages/updatePage.ts @@ -33,7 +33,7 @@ const addBlockAtIndex = async ( // ~ Add the block to the page const tempPageData = pageData as PageDataInterface; - tempPageData.message.data.splice(index, 0, { + tempPageData.message!.data.splice(index, 0, { _id: objectID, blockType: blockType || 'text', properties: blockProperties || { @@ -46,8 +46,10 @@ const addBlockAtIndex = async ( setPageData({ status: 'Success', message: { - style: tempPageData.message.style, - data: [...tempPageData.message.data], + style: tempPageData.message!.style, + data: [...tempPageData.message!.data], + userPermissions: tempPageData.message!.userPermissions, + permissions: tempPageData.message!.permissions, }, }); @@ -75,14 +77,16 @@ const removeBlock = async ( // ~ Remove the block from the page const tempPageData = pageData as PageDataInterface; - tempPageData.message.data.splice(index, 1); + tempPageData.message!.data.splice(index, 1); // ~ Update the page setPageData({ status: 'Success', message: { - style: tempPageData.message.style, - data: [...tempPageData.message.data], + style: tempPageData.message!.style, + data: [...tempPageData.message!.data], + userPermissions: tempPageData.message!.userPermissions, + permissions: tempPageData.message!.permissions, }, }); @@ -141,8 +145,8 @@ const moveBlock = async ( 'doc-ids': blockIDs.length > 1 ? blockIDs : undefined, 'new-block-index': newIndex + offset, 'new-block-id': blockIDs[blockIDs.length - 1], - 'new-block-type': pageData.message.data[currentIndex].blockType, - 'new-block-properties': pageData.message.data[currentIndex].properties, + 'new-block-type': pageData.message!.data[currentIndex].blockType, + 'new-block-properties': pageData.message!.data[currentIndex].properties, }, page, ); @@ -150,8 +154,8 @@ const moveBlock = async ( // -=- Update page data -=- const pageDataCopy = { ...pageData }; - pageDataCopy.message.data.splice(newIndex + 1, 0, pageData.message.data[currentIndex]); - pageDataCopy.message.data.splice(currentIndex + offset, 1); + pageDataCopy.message!.data.splice(newIndex + 1, 0, pageData.message!.data[currentIndex]); + pageDataCopy.message!.data.splice(currentIndex + offset, 1); setPageData(pageDataCopy); }; diff --git a/web/src/pages/note-rack/[page].tsx b/web/src/pages/note-rack/[page].tsx index e92ed7f6..752d66ec 100644 --- a/web/src/pages/note-rack/[page].tsx +++ b/web/src/pages/note-rack/[page].tsx @@ -39,14 +39,14 @@ const NoteRackPage = (props: {pageDataReq: Promise}) => { ) : ( <> - {(pageData as PageDataInterface).message.style.name} + {(pageData as PageDataInterface).message!.style.name} - ${(pageData as PageDataInterface).message.style.icon} + ${(pageData as PageDataInterface).message!.style.icon} `} @@ -60,7 +60,10 @@ const NoteRackPage = (props: {pageDataReq: Promise}) => {
- +
diff --git a/web/src/types/pageTypes.ts b/web/src/types/pageTypes.ts index b13e63ec..b5110dd5 100644 --- a/web/src/types/pageTypes.ts +++ b/web/src/types/pageTypes.ts @@ -5,9 +5,24 @@ interface Block { children: Block[] } +export interface UserPermissions { + read: boolean, + write: boolean, + admin: boolean, +} + +export interface Permissions { + [key: string]: { + read: boolean, + write: boolean, + admin: boolean, + email: string, + } +} + interface PageDataInterface { status: string, - message: { + message?: { style: { colour: { r: number, @@ -17,7 +32,9 @@ interface PageDataInterface { name: string, icon: string, }, - data: Block[] + data: Block[], + userPermissions: UserPermissions, + permissions?: Permissions, }, } From dc1fa2ae0554aa2f40de369f77db62990af0fe2d Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sun, 12 Mar 2023 16:05:44 -0700 Subject: [PATCH 26/54] Remove broken tests and move constant files --- web/package.json | 3 +- .../__tests__/components/blocks/Icon.spec.tsx | 71 --------- .../__tests__/components/blocks/Text.spec.tsx | 139 ------------------ .../components/blocks/Title.spec.tsx | 71 --------- web/src/__tests__/setupTests.ts | 3 - web/src/components/blocks/BaseBlock.tsx | 2 +- web/src/components/blocks/TextBlock.tsx | 2 +- .../pageCustomization/ColoursPicker.tsx | 2 +- web/src/{ => lib}/constants/BlockTypes.ts | 0 web/src/{ => lib}/constants/Colours.ts | 0 web/src/{ => lib}/constants/TextStyles.ts | 0 11 files changed, 4 insertions(+), 289 deletions(-) delete mode 100644 web/src/__tests__/components/blocks/Icon.spec.tsx delete mode 100644 web/src/__tests__/components/blocks/Text.spec.tsx delete mode 100644 web/src/__tests__/components/blocks/Title.spec.tsx delete mode 100644 web/src/__tests__/setupTests.ts rename web/src/{ => lib}/constants/BlockTypes.ts (100%) rename web/src/{ => lib}/constants/Colours.ts (100%) rename web/src/{ => lib}/constants/TextStyles.ts (100%) diff --git a/web/package.json b/web/package.json index ac1ac5b0..9a0f0555 100644 --- a/web/package.json +++ b/web/package.json @@ -5,8 +5,7 @@ "dev": "next dev ./src/", "build": "next build ./src/", "start": "next start ./src/", - "lint": "next lint ./src/", - "test": "jest" + "lint": "next lint ./src/" }, "dependencies": { "dotenv": "^14.2.0", diff --git a/web/src/__tests__/components/blocks/Icon.spec.tsx b/web/src/__tests__/components/blocks/Icon.spec.tsx deleted file mode 100644 index 8903e955..00000000 --- a/web/src/__tests__/components/blocks/Icon.spec.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import '@testing-library/jest-dom'; -import { render, fireEvent } from '@testing-library/react'; - -import Icon from '../../../components/blocks/Icon'; - -describe('Icon', () => { - let expectedProps: { - value: string, - blockID: string, - page: string, - }; - - beforeEach(() => { - expectedProps = { - value: '📘', - blockID: 'testingIconBlock', - page: 'page', - }; - - fetchMock.resetMocks(); - }); - - test('Should render the "icon" block and not render the "picker menu"', async () => { - const { value, page } = expectedProps; - - const { findByText, findByLabelText } = render( - , - ); - - const iconText = await findByText(value); - const emojiSelectorMenu = (await findByLabelText('Emoji Mart™')).closest('div'); - - expect(iconText).toBeVisible(); - - // EROXL: Couldn't figure out a way to load tailwind css into jest - expect(emojiSelectorMenu).toHaveClass('hidden'); - }); - - test('Emoji picker menu should be toggleable', async () => { - const { value, page } = expectedProps; - - const { findByText, findByLabelText } = render( - , - ); - - const iconButton = await findByText(value); - const emojiSelectorMenu = (await findByLabelText('Emoji Mart™')).closest('div'); - - expect(emojiSelectorMenu).toHaveClass('hidden'); - fireEvent.click(iconButton); - expect(emojiSelectorMenu).not.toHaveClass('hidden'); - }); - - test('Emoji picker menu should change the emoji', async () => { - const { value, page } = expectedProps; - - fetchMock.mockOnce('{}'); - - const { findByText, findByLabelText } = render( - , - ); - - const iconButton = await findByText(value); - fireEvent.click(iconButton); - const emojiSelectionButton = await findByLabelText('😞, disappointed'); - fireEvent.click(emojiSelectionButton); - - expect(iconButton).toHaveTextContent('😞'); - }); -}); diff --git a/web/src/__tests__/components/blocks/Text.spec.tsx b/web/src/__tests__/components/blocks/Text.spec.tsx deleted file mode 100644 index 9af9111e..00000000 --- a/web/src/__tests__/components/blocks/Text.spec.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/* eslint-disable react/no-children-prop */ -/* eslint-disable @typescript-eslint/no-empty-function */ -import React from 'react'; -import '@testing-library/jest-dom'; -import userEvent from '@testing-library/user-event'; -import { render } from '@testing-library/react'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import { act } from 'react-dom/test-utils'; - -import TextStyles from '../../../constants/TextStyles'; -import textKeybinds from '../../../lib/textKeybinds'; -import TextBlock from '../../../components/blocks/TextBlock'; -import PageDataInterface from '../../../types/pageTypes'; -import BaseBlock from '../../../components/blocks/BaseBlock'; - -describe('Text', () => { - let expectedProps: { - properties: { value: string }, - blockID: string, - page: string, - pageData: PageDataInterface - setPageData: () => void, - setCurrentBlockType: () => void, - }; - - beforeEach(() => { - expectedProps = { - properties: { - value: 'Test text', - }, - blockID: 'testingTextBlock', - page: 'page', - pageData: { - status: '', - message: { - style: { - colour: { - r: 1, - g: 2, - b: 3, - }, - name: 'Page', - icon: '🌳', - }, - data: [], - }, - }, - setPageData: (): void => {}, - setCurrentBlockType: () => {}, - }; - - fetchMock.resetMocks(); - }); - - ['h1', 'h2', 'h3', 'h4', 'h5', 'quote', 'callout'].forEach((textType) => { - test(`Should render ${textType}`, async () => { - const { - properties, - blockID, - page, - pageData, - setPageData, - setCurrentBlockType, - } = expectedProps; - - const { findByText } = render( - , - ); - - const textElement = await findByText(properties.value); - - expect(textElement).toBeVisible(); - expect(textElement).toHaveAttribute('contentEditable', 'true'); - expect(textElement).toHaveClass(TextStyles[textType]); - }); - }); - - textKeybinds.forEach((textObject) => { - if (textObject.customFunc) return; - test(`Should allow style change from normal text to ${textObject.type}`, async () => { - const { - properties, - blockID, - page, - pageData, - setPageData, - } = expectedProps; - fetchMock.mockOnce('{}'); - - if (TextStyles[textObject.type]) { - const { findByText } = render( - - {}} - /> - , - ); - - const textElement = await findByText(properties.value); - - expect(textElement).toBeVisible(); - expect(textElement).toHaveAttribute('contentEditable', 'true'); - - await act(async () => { - textElement.innerHTML = ''; - - userEvent.type(textElement, `${textObject.plainTextKeybind}{space}`); - - // Wait 100ms for the change to be applied - // eslint-disable-next-line no-promise-executor-return - await new Promise((r) => setTimeout(r, 100)); - }); - - expect(textElement).toBeEmptyDOMElement(); - - expect(textElement).toHaveClass(TextStyles[textObject.type]); - } - }); - }); -}); diff --git a/web/src/__tests__/components/blocks/Title.spec.tsx b/web/src/__tests__/components/blocks/Title.spec.tsx deleted file mode 100644 index 69bfd15d..00000000 --- a/web/src/__tests__/components/blocks/Title.spec.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; - -import Title from '../../../components/blocks/Title'; -import PageDataInterface from '../../../types/pageTypes'; - -describe('Title', () => { - let expectedProps: { - value: string, - blockID: string, - page: string, - type: string, - setPageData: () => void, - pageData: PageDataInterface - }; - - beforeEach(() => { - expectedProps = { - value: 'Example text', - blockID: 'testingIconBlock', - page: 'page', - type: '', - setPageData: (): void => { - throw new Error('Remove block function not implemented.'); - }, - pageData: { - status: '', - message: { - style: { - colour: { - r: 1, - g: 2, - b: 3, - }, - name: 'Page', - icon: '🌳', - }, - data: [], - }, - }, - }; - }); - - test('Should render the title', async () => { - const { - value, - pageData, - setPageData, - } = expectedProps; - - const { findByText } = render( - - - </DndProvider>, - ); - - const titleText = await findByText(value); - - expect(titleText).toBeVisible(); - expect(titleText).toHaveAttribute('contentEditable', 'true'); - }); -}); diff --git a/web/src/__tests__/setupTests.ts b/web/src/__tests__/setupTests.ts deleted file mode 100644 index 9e92f7c1..00000000 --- a/web/src/__tests__/setupTests.ts +++ /dev/null @@ -1,3 +0,0 @@ -import fetchMock from 'jest-fetch-mock'; - -fetchMock.enableMocks(); diff --git a/web/src/components/blocks/BaseBlock.tsx b/web/src/components/blocks/BaseBlock.tsx index 0410d216..8bfcfa0e 100644 --- a/web/src/components/blocks/BaseBlock.tsx +++ b/web/src/components/blocks/BaseBlock.tsx @@ -5,7 +5,7 @@ import { useSelectable, SelectionManager } from 'react-virtual-selection'; import { moveBlock } from '../../lib/pages/updatePage'; import BlockHandle from './BlockHandle'; -import BlockTypes from '../../constants/BlockTypes'; +import BlockTypes from '../../lib/constants/BlockTypes'; import type { BaseBlockProps } from '../../types/blockTypes'; const BaseBlock = (props: BaseBlockProps) => { diff --git a/web/src/components/blocks/TextBlock.tsx b/web/src/components/blocks/TextBlock.tsx index fc123284..8cdd3002 100644 --- a/web/src/components/blocks/TextBlock.tsx +++ b/web/src/components/blocks/TextBlock.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { isCaretAtTop, isCaretAtBottom } from '../../lib/helpers/caretHelpers'; import { editBlock, addBlockAtIndex, removeBlock } from '../../lib/pages/updatePage'; -import TextStyles from '../../constants/TextStyles'; +import TextStyles from '../../lib/constants/TextStyles'; import textKeybinds from '../../lib/textKeybinds'; import type { EditableText } from '../../types/blockTypes'; import handleKeyDown from '../../lib/blockNavigation/handleKeyDown'; diff --git a/web/src/components/pageCustomization/ColoursPicker.tsx b/web/src/components/pageCustomization/ColoursPicker.tsx index c22a0b37..0c804d1f 100644 --- a/web/src/components/pageCustomization/ColoursPicker.tsx +++ b/web/src/components/pageCustomization/ColoursPicker.tsx @@ -1,7 +1,7 @@ import React from 'react'; import editStyle from '../../lib/pages/editStyle'; -import Colours from '../../constants/Colours'; +import Colours from '../../lib/constants/Colours'; interface ColourPickerProps { page: string, diff --git a/web/src/constants/BlockTypes.ts b/web/src/lib/constants/BlockTypes.ts similarity index 100% rename from web/src/constants/BlockTypes.ts rename to web/src/lib/constants/BlockTypes.ts diff --git a/web/src/constants/Colours.ts b/web/src/lib/constants/Colours.ts similarity index 100% rename from web/src/constants/Colours.ts rename to web/src/lib/constants/Colours.ts diff --git a/web/src/constants/TextStyles.ts b/web/src/lib/constants/TextStyles.ts similarity index 100% rename from web/src/constants/TextStyles.ts rename to web/src/lib/constants/TextStyles.ts From d802d5e12d90b0f6e635e0b4ae274adc7e2ca008 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 16:07:59 -0700 Subject: [PATCH 27/54] Simplify file structure --- web/src/components/Editor.tsx | 10 +++++----- web/src/components/blocks/BaseBlock.tsx | 2 +- web/src/components/blocks/Icon.tsx | 2 +- web/src/components/blocks/MathBlock.tsx | 2 +- web/src/components/blocks/TextBlock.tsx | 2 +- web/src/components/blocks/Title.tsx | 2 +- web/src/components/menus/ShareMenu.tsx | 6 +++--- web/src/components/pageCustomization/ShareButton.tsx | 2 +- web/src/lib/blockNavigation/handleKeyDown.ts | 2 +- web/src/lib/blockNavigation/handleKeyUp.ts | 2 +- web/src/{ => lib}/classes/SaveManager.ts | 0 web/src/lib/helpers/focusHelpers.ts | 2 +- web/src/lib/pages/updatePage.ts | 4 ++-- web/src/{config => lib}/superTokensConfig.ts | 0 .../{types/blockTypes.ts => lib/types/blockTypes.d.ts} | 0 .../{types/pageTypes.ts => lib/types/pageTypes.d.ts} | 0 web/src/pages/_app.tsx | 2 +- web/src/pages/note-rack/[page].tsx | 4 ++-- 18 files changed, 22 insertions(+), 22 deletions(-) rename web/src/{ => lib}/classes/SaveManager.ts (100%) rename web/src/{config => lib}/superTokensConfig.ts (100%) rename web/src/{types/blockTypes.ts => lib/types/blockTypes.d.ts} (100%) rename web/src/{types/pageTypes.ts => lib/types/pageTypes.d.ts} (100%) diff --git a/web/src/components/Editor.tsx b/web/src/components/Editor.tsx index 16e684a1..70fe1f58 100644 --- a/web/src/components/Editor.tsx +++ b/web/src/components/Editor.tsx @@ -9,7 +9,7 @@ import React, { import { useRouter } from 'next/router'; import { Selectable, useSelectionCollector } from 'react-virtual-selection'; -import type PageDataInterface from '../types/pageTypes'; +import type PageDataInterface from '../lib/types/pageTypes'; import BaseBlock from './blocks/BaseBlock'; import PageThumbnail from './pageCustomization/PageThumbnail'; import Title from './blocks/Title'; @@ -91,7 +91,7 @@ const Editor = (props: EditorProps) => { className="flex flex-col items-center w-full h-max bg-amber-50 dark:bg-zinc-700 print:dark:bg-white print:bg-white bg-" > {/* ~ Render the page thumbnail */} - <PageThumbnail colour={pageData.message.style.colour} page={page as string} /> + <PageThumbnail colour={pageData.message!.style.colour} page={page as string} /> {/* ~ Render the main interactive editor */} <div className="flex flex-col w-full max-w-4xl gap-3 px-20 pb-56 mx-auto break-words select-none print:p-0 text-zinc-700 dark:text-amber-50 print:dark:text-zinc-700 h-max editor" @@ -99,7 +99,7 @@ const Editor = (props: EditorProps) => { {/* ~ Render the page icon */} <Icon page={page as string} - icon={pageData.message.style.icon} + icon={pageData.message!.style.icon} /> {/* ~ Render the title */} <Title @@ -107,11 +107,11 @@ const Editor = (props: EditorProps) => { pageData={pageData} index={0} setPageData={setPageData} - title={pageData.message.style.name} + title={pageData.message!.style.name} /> {/* ~ Render the blocks */} - {(pageData as PageDataInterface).message.data.map((block, index) => ( + {(pageData as PageDataInterface).message!.data.map((block, index) => ( <BaseBlock blockType={block.blockType} blockID={block._id} diff --git a/web/src/components/blocks/BaseBlock.tsx b/web/src/components/blocks/BaseBlock.tsx index 8bfcfa0e..bc1da19b 100644 --- a/web/src/components/blocks/BaseBlock.tsx +++ b/web/src/components/blocks/BaseBlock.tsx @@ -6,7 +6,7 @@ import { useSelectable, SelectionManager } from 'react-virtual-selection'; import { moveBlock } from '../../lib/pages/updatePage'; import BlockHandle from './BlockHandle'; import BlockTypes from '../../lib/constants/BlockTypes'; -import type { BaseBlockProps } from '../../types/blockTypes'; +import type { BaseBlockProps } from '../../lib/types/blockTypes'; const BaseBlock = (props: BaseBlockProps) => { const { diff --git a/web/src/components/blocks/Icon.tsx b/web/src/components/blocks/Icon.tsx index 5a85ef05..5f081ebc 100644 --- a/web/src/components/blocks/Icon.tsx +++ b/web/src/components/blocks/Icon.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'; import 'emoji-mart/css/emoji-mart.css'; import { Picker, BaseEmoji } from 'emoji-mart'; -import type { PermanentBlock } from '../../types/blockTypes'; +import type { PermanentBlock } from '../../lib/types/blockTypes'; import editStyle from '../../lib/pages/editStyle'; interface IconProps extends PermanentBlock { diff --git a/web/src/components/blocks/MathBlock.tsx b/web/src/components/blocks/MathBlock.tsx index 7171f202..60b130c9 100644 --- a/web/src/components/blocks/MathBlock.tsx +++ b/web/src/components/blocks/MathBlock.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import katex from 'katex'; import 'katex/dist/katex.min.css'; -import type { EditableText } from '../../types/blockTypes'; +import type { EditableText } from '../../lib/types/blockTypes'; import { addBlockAtIndex, editBlock } from '../../lib/pages/updatePage'; const MathBlock = (props: EditableText) => { diff --git a/web/src/components/blocks/TextBlock.tsx b/web/src/components/blocks/TextBlock.tsx index 8cdd3002..97e925f5 100644 --- a/web/src/components/blocks/TextBlock.tsx +++ b/web/src/components/blocks/TextBlock.tsx @@ -4,7 +4,7 @@ import { isCaretAtTop, isCaretAtBottom } from '../../lib/helpers/caretHelpers'; import { editBlock, addBlockAtIndex, removeBlock } from '../../lib/pages/updatePage'; import TextStyles from '../../lib/constants/TextStyles'; import textKeybinds from '../../lib/textKeybinds'; -import type { EditableText } from '../../types/blockTypes'; +import type { EditableText } from '../../lib/types/blockTypes'; import handleKeyDown from '../../lib/blockNavigation/handleKeyDown'; import handleKeyUp from '../../lib/blockNavigation/handleKeyUp'; diff --git a/web/src/components/blocks/Title.tsx b/web/src/components/blocks/Title.tsx index 456ff9a3..276cea67 100644 --- a/web/src/components/blocks/Title.tsx +++ b/web/src/components/blocks/Title.tsx @@ -4,7 +4,7 @@ import { useDrop } from 'react-dnd'; import TitleBreaker from './TitleBreaker'; import { addBlockAtIndex, moveBlock } from '../../lib/pages/updatePage'; -import type { PermanentEditableText } from '../../types/blockTypes'; +import type { PermanentEditableText } from '../../lib/types/blockTypes'; import editStyle from '../../lib/pages/editStyle'; import { isCaretAtBottom, isCaretAtTop } from '../../lib/helpers/caretHelpers'; import handleKeyDown from '../../lib/blockNavigation/handleKeyDown'; diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx index 1e22911a..b141134b 100644 --- a/web/src/components/menus/ShareMenu.tsx +++ b/web/src/components/menus/ShareMenu.tsx @@ -3,14 +3,14 @@ import Image from 'next/image'; import EmailShareMenu from './EmailShareMenu'; import Globe from '../../public/Globe.svg'; -import type PageDataInterface from '../../types/pageTypes'; +import type { Permissions, UserPermissions } from '../../lib/types/pageTypes'; interface ShareMenuProps { page: string, setIsMenuOpen: (isMenuOpen: boolean) => void, buttonRef: React.RefObject<HTMLDivElement>, - pagePermissions?: PageDataInterface['message']['permissions'], - permissionsOnPage?: PageDataInterface['message']['userPermissions'], + pagePermissions?: Permissions, + permissionsOnPage?: UserPermissions } const ShareMenu = (props: ShareMenuProps) => { diff --git a/web/src/components/pageCustomization/ShareButton.tsx b/web/src/components/pageCustomization/ShareButton.tsx index ef7d3663..4ebac7d8 100644 --- a/web/src/components/pageCustomization/ShareButton.tsx +++ b/web/src/components/pageCustomization/ShareButton.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef } from 'react'; import { useRouter } from 'next/router'; -import type { Permissions, UserPermissions } from '../../types/pageTypes'; +import type { Permissions, UserPermissions } from '../../lib/types/pageTypes'; import ShareMenu from '../menus/ShareMenu'; interface ShareButtonProps { diff --git a/web/src/lib/blockNavigation/handleKeyDown.ts b/web/src/lib/blockNavigation/handleKeyDown.ts index 07f9c2f4..fc9fbb1c 100644 --- a/web/src/lib/blockNavigation/handleKeyDown.ts +++ b/web/src/lib/blockNavigation/handleKeyDown.ts @@ -1,5 +1,5 @@ import { focusBlockAtIndexRelativeToTop, getLengthExcludingLastLine } from '../helpers/focusHelpers'; -import type PageDataInterface from '../../types/pageTypes'; +import type PageDataInterface from '../types/pageTypes'; const handleKeyDown = ( e: React.KeyboardEvent<HTMLSpanElement>, diff --git a/web/src/lib/blockNavigation/handleKeyUp.ts b/web/src/lib/blockNavigation/handleKeyUp.ts index 5db1762a..542c778c 100644 --- a/web/src/lib/blockNavigation/handleKeyUp.ts +++ b/web/src/lib/blockNavigation/handleKeyUp.ts @@ -1,4 +1,4 @@ -import PageDataInterface from '../../types/pageTypes'; +import PageDataInterface from '../types/pageTypes'; import { focusBlockAtIndexRelativeToBottom } from '../helpers/focusHelpers'; const handleKeyUp = ( diff --git a/web/src/classes/SaveManager.ts b/web/src/lib/classes/SaveManager.ts similarity index 100% rename from web/src/classes/SaveManager.ts rename to web/src/lib/classes/SaveManager.ts diff --git a/web/src/lib/helpers/focusHelpers.ts b/web/src/lib/helpers/focusHelpers.ts index 195add42..904ee11e 100644 --- a/web/src/lib/helpers/focusHelpers.ts +++ b/web/src/lib/helpers/focusHelpers.ts @@ -1,4 +1,4 @@ -import type PageDataInterface from '../../types/pageTypes'; +import type PageDataInterface from '../types/pageTypes'; const getFirstLineLength = (node: HTMLElement): number => { // ~ Get the first newline character diff --git a/web/src/lib/pages/updatePage.ts b/web/src/lib/pages/updatePage.ts index b0e73f84..0e06413c 100644 --- a/web/src/lib/pages/updatePage.ts +++ b/web/src/lib/pages/updatePage.ts @@ -3,8 +3,8 @@ import crypto from 'crypto'; import { focusBlockAtIndex } from '../helpers/focusHelpers'; -import type PageDataInterface from '../../types/pageTypes'; -import SaveManager from '../../classes/SaveManager'; +import type PageDataInterface from '../types/pageTypes'; +import SaveManager from '../classes/SaveManager'; const addBlockAtIndex = async ( index: number, diff --git a/web/src/config/superTokensConfig.ts b/web/src/lib/superTokensConfig.ts similarity index 100% rename from web/src/config/superTokensConfig.ts rename to web/src/lib/superTokensConfig.ts diff --git a/web/src/types/blockTypes.ts b/web/src/lib/types/blockTypes.d.ts similarity index 100% rename from web/src/types/blockTypes.ts rename to web/src/lib/types/blockTypes.d.ts diff --git a/web/src/types/pageTypes.ts b/web/src/lib/types/pageTypes.d.ts similarity index 100% rename from web/src/types/pageTypes.ts rename to web/src/lib/types/pageTypes.d.ts diff --git a/web/src/pages/_app.tsx b/web/src/pages/_app.tsx index 37cba662..2617af70 100644 --- a/web/src/pages/_app.tsx +++ b/web/src/pages/_app.tsx @@ -5,7 +5,7 @@ import SuperTokensReact, { SuperTokensWrapper } from 'supertokens-auth-react'; import '../styles/globals.css'; import '../styles/emojiPicker.css'; -import superTokensConfig from '../config/superTokensConfig'; +import superTokensConfig from '../lib/superTokensConfig'; // -=- Initialization -=- // ~ If we're in the browser, initialize SuperTokens diff --git a/web/src/pages/note-rack/[page].tsx b/web/src/pages/note-rack/[page].tsx index 752d66ec..96307553 100644 --- a/web/src/pages/note-rack/[page].tsx +++ b/web/src/pages/note-rack/[page].tsx @@ -5,11 +5,11 @@ import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import PagePath from '../../components/pageInfo/PagePath'; -import type PageDataInterface from '../../types/pageTypes'; +import type PageDataInterface from '../../lib/types/pageTypes'; import PageSidebar from '../../components/pageInfo/PageSidebar'; import Editor from '../../components/Editor'; import LoadingPage from '../../components/LoadingPage'; -import SaveManager from '../../classes/SaveManager'; +import SaveManager from '../../lib/classes/SaveManager'; import ShareButton from '../../components/pageCustomization/ShareButton'; const NoteRackPage = (props: {pageDataReq: Promise<PageDataInterface>}) => { From 28d82061a024afd147e7be1bf913ef9a651cfa82 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 16:08:30 -0700 Subject: [PATCH 28/54] Move superTokens config file --- web/src/lib/{ => config}/superTokensConfig.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web/src/lib/{ => config}/superTokensConfig.ts (100%) diff --git a/web/src/lib/superTokensConfig.ts b/web/src/lib/config/superTokensConfig.ts similarity index 100% rename from web/src/lib/superTokensConfig.ts rename to web/src/lib/config/superTokensConfig.ts From 8848031680b1b3f40b19a9f62d787d6ecb4c4dc7 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 16:08:43 -0700 Subject: [PATCH 29/54] Update imports --- web/src/pages/_app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/_app.tsx b/web/src/pages/_app.tsx index 2617af70..f5bd20f9 100644 --- a/web/src/pages/_app.tsx +++ b/web/src/pages/_app.tsx @@ -5,7 +5,7 @@ import SuperTokensReact, { SuperTokensWrapper } from 'supertokens-auth-react'; import '../styles/globals.css'; import '../styles/emojiPicker.css'; -import superTokensConfig from '../lib/superTokensConfig'; +import superTokensConfig from '../lib/config/superTokensConfig'; // -=- Initialization -=- // ~ If we're in the browser, initialize SuperTokens From 431a87b9ae051452ee3182e177c86ed19af448cd Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 16:10:12 -0700 Subject: [PATCH 30/54] Remove redundant file --- web/src/components/pageInfo/PagePath.tsx | 4 +--- web/src/components/pageInfo/PageSidebar.tsx | 9 ++++++++- web/src/components/pageInfo/PageSidebarItem.tsx | 2 +- web/src/components/pageInfo/PageSidebarItemProps.ts | 10 ---------- 4 files changed, 10 insertions(+), 15 deletions(-) delete mode 100644 web/src/components/pageInfo/PageSidebarItemProps.ts diff --git a/web/src/components/pageInfo/PagePath.tsx b/web/src/components/pageInfo/PagePath.tsx index 1b1ffad2..680dbc2c 100644 --- a/web/src/components/pageInfo/PagePath.tsx +++ b/web/src/components/pageInfo/PagePath.tsx @@ -1,7 +1,7 @@ import Link from 'next/link'; import React, { useEffect, useState } from 'react'; -interface PagePath { +export interface PagePath { name: string; icon: string; pageID: string; @@ -72,6 +72,4 @@ const PagePath = () => { ); }; -export type { PagePath }; - export default PagePath; diff --git a/web/src/components/pageInfo/PageSidebar.tsx b/web/src/components/pageInfo/PageSidebar.tsx index 7c640fb6..68bed180 100644 --- a/web/src/components/pageInfo/PageSidebar.tsx +++ b/web/src/components/pageInfo/PageSidebar.tsx @@ -1,8 +1,15 @@ import React, { useState, useEffect } from 'react'; -import type { PageSidebarItemProps } from './PageSidebarItemProps'; import PageSidebarItem from './PageSidebarItem'; +export interface PageSidebarItemProps { + _id: string, + style: Record<string, unknown>, + expanded: boolean, + parentExpanded: boolean, + subPages: PageSidebarItemProps[] +} + const PageSidebar = () => { const [isLoading, setIsLoading] = useState(true); const [pageTree, setPageTree] = useState<PageSidebarItemProps[] | undefined>(undefined); diff --git a/web/src/components/pageInfo/PageSidebarItem.tsx b/web/src/components/pageInfo/PageSidebarItem.tsx index 49ec363c..a1678e1c 100644 --- a/web/src/components/pageInfo/PageSidebarItem.tsx +++ b/web/src/components/pageInfo/PageSidebarItem.tsx @@ -3,7 +3,7 @@ import Link from 'next/link'; import { useRouter } from 'next/router'; import type { PagePath } from './PagePath'; -import type { PageSidebarItemProps } from './PageSidebarItemProps'; +import type { PageSidebarItemProps } from './PageSidebar'; import editPageTree from '../../lib/pageTrees/editPageTree'; const PageSidebarItem = (props: PageSidebarItemProps & { pagePath: PagePath[] }) => { diff --git a/web/src/components/pageInfo/PageSidebarItemProps.ts b/web/src/components/pageInfo/PageSidebarItemProps.ts deleted file mode 100644 index 684ab7ba..00000000 --- a/web/src/components/pageInfo/PageSidebarItemProps.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -interface PageSidebarItemProps { - _id: string, - style: Record<string, unknown>, - expanded: boolean, - parentExpanded: boolean, - subPages: PageSidebarItemProps[] -} - -export type { PageSidebarItemProps }; From c01d4d3b25af2c3e02ac8b961e690c46bbf45f9f Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 16:12:00 -0700 Subject: [PATCH 31/54] Fix broken imports --- web/src/lib/constants/BlockTypes.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/lib/constants/BlockTypes.ts b/web/src/lib/constants/BlockTypes.ts index 44bb4aa5..3416eb28 100644 --- a/web/src/lib/constants/BlockTypes.ts +++ b/web/src/lib/constants/BlockTypes.ts @@ -1,8 +1,8 @@ -import Icon from '../components/blocks/Icon'; -import Title from '../components/blocks/Title'; -import TextBlock from '../components/blocks/TextBlock'; -import PageBlock from '../components/blocks/PageBlock'; -import MathBlock from '../components/blocks/MathBlock'; +import Icon from '../../components/blocks/Icon'; +import Title from '../../components/blocks/Title'; +import TextBlock from '../../components/blocks/TextBlock'; +import PageBlock from '../../components/blocks/PageBlock'; +import MathBlock from '../../components/blocks/MathBlock'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const BlockTypes: {[key: string]: any} = { From 313a40e84ae1c150e9769aaf57096edd927af2e6 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 16:56:49 -0700 Subject: [PATCH 32/54] Extract EmailShareMenu dropdown into separate components --- web/src/components/menus/EmailShareMenu.tsx | 108 ++-------------- web/src/components/menus/ShareMenu.tsx | 13 +- .../components/menus/ShareOptionsDropdown.tsx | 80 ++++++++++++ web/src/components/menus/UserPermission.tsx | 120 ++++++++++++++++++ web/src/lib/constants/ShareOptions.ts | 47 +++++++ 5 files changed, 263 insertions(+), 105 deletions(-) create mode 100644 web/src/components/menus/ShareOptionsDropdown.tsx create mode 100644 web/src/components/menus/UserPermission.tsx create mode 100644 web/src/lib/constants/ShareOptions.ts diff --git a/web/src/components/menus/EmailShareMenu.tsx b/web/src/components/menus/EmailShareMenu.tsx index 1d95d15b..0ff6d532 100644 --- a/web/src/components/menus/EmailShareMenu.tsx +++ b/web/src/components/menus/EmailShareMenu.tsx @@ -1,17 +1,14 @@ import React, { useState, useRef, useEffect } from 'react'; +import { DropdownOptions, dropdownInfo } from '../../lib/constants/ShareOptions'; +import ShareOptionsDropdown from './ShareOptionsDropdown'; + interface ShareMenuProps { page: string, setIsEditingEmails: (isEditingEmails: boolean) => void, addPermissions: (email: string, currentPermissions: any) => void, } -enum DropdownOptions { - FullAccess, - EditOnly, - ViewOnly, -} - const EmailShareMenu = (props: ShareMenuProps) => { const { page, setIsEditingEmails, addPermissions } = props; @@ -37,46 +34,6 @@ const EmailShareMenu = (props: ShareMenuProps) => { } }); - const dropdownInfo: { - [key: number]: { - title: string, - description: string, - permissions: { - admin: boolean, - edit: boolean, - read: boolean, - } - } - } = { - [DropdownOptions.FullAccess]: { - title: 'Full access', - description: 'Can edit, delete, and share', - permissions: { - admin: true, - edit: true, - read: true, - } - }, - [DropdownOptions.EditOnly]: { - title: 'Edit only', - description: 'Can edit, but not delete or share', - permissions: { - admin: false, - edit: true, - read: true, - }, - }, - [DropdownOptions.ViewOnly]: { - title: 'View only', - description: 'Cannot edit, delete, or share', - permissions: { - admin: false, - edit: false, - read: true, - } - }, - } - return ( <> <div @@ -113,60 +70,11 @@ const EmailShareMenu = (props: ShareMenuProps) => { </svg> { isDropdownOpen && ( - <div - className="absolute right-0 z-10 mt-1 -translate-y-2 border rounded-md shadow-lg dark:border-neutral-600 border-stone-200 w-max top-full bg-stone-100 dark:bg-neutral-600" - > - <ul - className="flex flex-col w-full gap-1 p-1 text-zinc-700 dark:text-amber-50" - > - { - Object.values(dropdownInfo).map((_, option) => ( - <li - key={option} - className="flex flex-row items-center justify-between w-full px-2 py-1 rounded cursor-pointer hover:bg-black/5 hover:dark:bg-white/5" - onClick={() => { - setSelectedDropdownOption(option); - setIsDropdownOpen(false); - }} - > - <div - className="flex flex-col" - > - <div - className="font-medium" - > - { - dropdownInfo[option].title - } - </div> - <div - className="text-sm" - > - { - dropdownInfo[option].description - } - </div> - </div> - { - selectedDropdownOption === option && ( - <svg - className="w-4 h-4" - viewBox="0 0 20 20" - fill="currentColor" - > - <path - fillRule="evenodd" - d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" - clipRule="evenodd" - /> - </svg> - ) - } - </li> - )) - } - </ul> - </div> + <ShareOptionsDropdown + selectedDropdownOption={selectedDropdownOption} + setSelectedDropdownOption={setSelectedDropdownOption} + setIsDropdownOpen={setIsDropdownOpen} + /> ) } </div> diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx index b141134b..b74407e4 100644 --- a/web/src/components/menus/ShareMenu.tsx +++ b/web/src/components/menus/ShareMenu.tsx @@ -4,6 +4,7 @@ import Image from 'next/image'; import EmailShareMenu from './EmailShareMenu'; import Globe from '../../public/Globe.svg'; import type { Permissions, UserPermissions } from '../../lib/types/pageTypes'; +import UserPermission from './UserPermission'; interface ShareMenuProps { page: string, @@ -136,11 +137,13 @@ const ShareMenu = (props: ShareMenuProps) => { <span className="font-bold">Shared with</span> { Object.values(currentPermissions).map((permission) => ( - <div - className="p-2 rounded-sm hover:cursor-pointer dark:hover:bg-white/10 hover:bg-black/10" - > - {permission.email} - </div> + <UserPermission + email={permission.email} + permissions={permission as UserPermissions} + pagePermissions={currentPermissions} + setCurrentPermissions={setCurrentPermissions} + key={permission.email} + /> )) } </div> diff --git a/web/src/components/menus/ShareOptionsDropdown.tsx b/web/src/components/menus/ShareOptionsDropdown.tsx new file mode 100644 index 00000000..c5de7f5b --- /dev/null +++ b/web/src/components/menus/ShareOptionsDropdown.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { DropdownOptions, dropdownInfo } from '../../lib/constants/ShareOptions'; + +interface ShareOptionsDropdownProps { + setIsDropdownOpen: React.Dispatch<React.SetStateAction<boolean>>, + selectedDropdownOption: DropdownOptions, + setSelectedDropdownOption: (option: DropdownOptions) => void, + children?: React.ReactNode, +} + +const ShareOptionsDropdown = (props: ShareOptionsDropdownProps) => { + const { + children, + setIsDropdownOpen, + selectedDropdownOption, + setSelectedDropdownOption, + } = props; + + return ( + <div + className="absolute right-0 z-10 mt-1 -translate-y-2 border rounded-md shadow-lg dark:border-neutral-600 border-stone-200 w-max top-full bg-stone-100 dark:bg-neutral-600" + > + <ul + className="flex flex-col w-full gap-1 p-1 text-zinc-700 dark:text-amber-50" + > + { + Object.values(dropdownInfo).map((_, option) => ( + <li + key={option} + className="flex flex-row items-center justify-between w-full px-2 py-1 rounded cursor-pointer hover:bg-black/5 hover:dark:bg-white/5" + onClick={() => { + setSelectedDropdownOption(option); + setIsDropdownOpen(false); + }} + > + <div + className="flex flex-col" + > + <div + className="font-medium" + > + { + dropdownInfo[option].title + } + </div> + <div + className="text-sm" + > + { + dropdownInfo[option].description + } + </div> + </div> + { + selectedDropdownOption === option && ( + <svg + className="w-4 h-4" + viewBox="0 0 20 20" + fill="currentColor" + > + <path + fillRule="evenodd" + d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" + clipRule="evenodd" + /> + </svg> + ) + } + </li> + )) + } + { + children + } + </ul> + </div> + ); +} + +export default ShareOptionsDropdown; diff --git a/web/src/components/menus/UserPermission.tsx b/web/src/components/menus/UserPermission.tsx new file mode 100644 index 00000000..9340b8bd --- /dev/null +++ b/web/src/components/menus/UserPermission.tsx @@ -0,0 +1,120 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { UserPermissions, Permissions } from '../../lib/types/pageTypes'; + +import { DropdownOptions, dropdownInfo } from '../../lib/constants/ShareOptions'; +import ShareOptionsDropdown from './ShareOptionsDropdown'; + +interface UserPermissionProps { + email: string, + permissions: UserPermissions, + pagePermissions: Permissions, + setCurrentPermissions: React.Dispatch<React.SetStateAction<Permissions | undefined>>, +} + +const UserPermission = (props: UserPermissionProps) => { + const { + email, + permissions, + setCurrentPermissions, + pagePermissions + } = props; + + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [selectedDropdownOption, setSelectedDropdownOption] = useState<DropdownOptions>(DropdownOptions.ViewOnly); + const dropdownRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + const defaultDropdownOption = Object.keys(dropdownInfo).find((key) => { + let keyPermissions = dropdownInfo[+key].permissions; + + return Object.entries(keyPermissions).every(([key, value]) => { + return permissions[key as keyof UserPermissions] === value; + }); + }) as (DropdownOptions | undefined) || DropdownOptions.ViewOnly; + + setSelectedDropdownOption(defaultDropdownOption); + }, [permissions]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current + && !dropdownRef.current.contains(event.target as Node) + ) { + setIsDropdownOpen(false); + } + } + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + } + }); + + const setSelectDropdownOption = (option: DropdownOptions) => { + let key = Object.keys(pagePermissions).find((key) => pagePermissions[key].email === email) || email; + + setCurrentPermissions({ + ...pagePermissions, + [key]: { + ...dropdownInfo[option].permissions, + email: email, + } + } as Permissions) + } + + return ( + <div + className="relative flex w-full gap-2 p-2 px-2 rounded-sm hover:cursor-pointer dark:hover:bg-white/10 hover:bg-black/10" + onClick={() => { + setIsDropdownOpen(!isDropdownOpen); + }} + ref={dropdownRef} + > + <p className="w-full my-auto overflow-hidden text-ellipsis"> + {email} + </p> + <div + className="relative flex flex-row items-center justify-center px-2 py-1 my-1 rounded cursor-pointer select-none text-amber-50/70 w-max whitespace-nowrap hover:bg-black/5 hover:dark:bg-white/5" + > + { + dropdownInfo[selectedDropdownOption].title + } + <svg + className="w-4 h-4" + viewBox="0 0 20 20" + fill="currentColor" + > + <path + fillRule="evenodd" + d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" + clipRule="evenodd" + /> + </svg> + { + isDropdownOpen && ( + <ShareOptionsDropdown + setIsDropdownOpen={setIsDropdownOpen} + selectedDropdownOption={selectedDropdownOption} + setSelectedDropdownOption={setSelectDropdownOption} + > + <p + onClick={() => { + setCurrentPermissions({ + ...Object.fromEntries(Object.entries(pagePermissions).filter(([_, value]) => value.email !== email)) + } as Permissions); + }} + className="flex flex-row items-center justify-between w-full p-2 px-2 py-1 font-bold text-center text-red-400 rounded cursor-pointer hover:bg-black/5 hover:dark:bg-white/5" + > + Remove + </p> + </ShareOptionsDropdown> + ) + } + </div> + </div> + ) +}; + +export default UserPermission; diff --git a/web/src/lib/constants/ShareOptions.ts b/web/src/lib/constants/ShareOptions.ts new file mode 100644 index 00000000..055ded61 --- /dev/null +++ b/web/src/lib/constants/ShareOptions.ts @@ -0,0 +1,47 @@ +enum DropdownOptions { + FullAccess, + EditOnly, + ViewOnly, +} + +const dropdownInfo: { + [key: number]: { + title: string, + description: string, + permissions: { + admin: boolean, + edit: boolean, + read: boolean, + } + } +} = { + [DropdownOptions.FullAccess]: { + title: 'Full access', + description: 'Can edit, delete, and share', + permissions: { + admin: true, + edit: true, + read: true, + } + }, + [DropdownOptions.EditOnly]: { + title: 'Edit only', + description: 'Can edit, but not delete or share', + permissions: { + admin: false, + edit: true, + read: true, + }, + }, + [DropdownOptions.ViewOnly]: { + title: 'View only', + description: 'Cannot edit, delete, or share', + permissions: { + admin: false, + edit: false, + read: true, + } + }, +} + +export { DropdownOptions, dropdownInfo } From 43cd04cc136b4200481d061327562e227610f487 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 17:06:08 -0700 Subject: [PATCH 33/54] Add functionality to update user permissions via API call --- web/src/components/menus/UserPermission.tsx | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/web/src/components/menus/UserPermission.tsx b/web/src/components/menus/UserPermission.tsx index 9340b8bd..9f8c5548 100644 --- a/web/src/components/menus/UserPermission.tsx +++ b/web/src/components/menus/UserPermission.tsx @@ -3,6 +3,7 @@ import { UserPermissions, Permissions } from '../../lib/types/pageTypes'; import { DropdownOptions, dropdownInfo } from '../../lib/constants/ShareOptions'; import ShareOptionsDropdown from './ShareOptionsDropdown'; +import { useRouter } from 'next/router'; interface UserPermissionProps { email: string, @@ -23,6 +24,8 @@ const UserPermission = (props: UserPermissionProps) => { const [selectedDropdownOption, setSelectedDropdownOption] = useState<DropdownOptions>(DropdownOptions.ViewOnly); const dropdownRef = useRef<HTMLDivElement>(null); + const page = useRouter().query.page as string; + useEffect(() => { const defaultDropdownOption = Object.keys(dropdownInfo).find((key) => { let keyPermissions = dropdownInfo[+key].permissions; @@ -66,7 +69,13 @@ const UserPermission = (props: UserPermissionProps) => { return ( <div - className="relative flex w-full gap-2 p-2 px-2 rounded-sm hover:cursor-pointer dark:hover:bg-white/10 hover:bg-black/10" + className={` + relative flex w-full gap-2 p-2 px-2 rounded-sm hover:cursor-pointer + ${isDropdownOpen + ? 'bg-black/10 dark:bg-white/10' + : 'dark:hover:bg-white/10 hover:bg-black/10' + } + `} onClick={() => { setIsDropdownOpen(!isDropdownOpen); }} @@ -104,6 +113,26 @@ const UserPermission = (props: UserPermissionProps) => { setCurrentPermissions({ ...Object.fromEntries(Object.entries(pagePermissions).filter(([_, value]) => value.email !== email)) } as Permissions); + + (async () => { + await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/page/update-permissions/${page}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email, + permissions: { + read: false, + write: false, + admin: false, + } + }), + } + ); + })(); }} className="flex flex-row items-center justify-between w-full p-2 px-2 py-1 font-bold text-center text-red-400 rounded cursor-pointer hover:bg-black/5 hover:dark:bg-white/5" > From c1c3255b87a5b8fe2caa9ee97a392aa7f86594ee Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 17:06:32 -0700 Subject: [PATCH 34/54] Update permissions with conditional unset --- backend/src/routes/page/updatePermissions.ts | 29 ++++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/backend/src/routes/page/updatePermissions.ts b/backend/src/routes/page/updatePermissions.ts index 3dfed029..64d8171c 100644 --- a/backend/src/routes/page/updatePermissions.ts +++ b/backend/src/routes/page/updatePermissions.ts @@ -37,17 +37,28 @@ router.post( return; } - await PageModel.findByIdAndUpdate( - page, - { - $set: { - [`permissions.${userId[0]}`]: { - ...permissions, - email, + if (Object.values(permissions).some((value) => value === true)) { + await PageModel.findByIdAndUpdate( + page, + { + $set: { + [`permissions.${userId[0]}`]: { + ...permissions, + email, + }, }, }, - }, - ); + ); + } else { + await PageModel.findByIdAndUpdate( + page, + { + $unset: { + [`permissions.${userId[0]}`]: '', + }, + }, + ); + } res.statusCode = 200; res.json({ From ad9d934fb185b95deeb190abe8bbaa4a7426c120 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 17:07:45 -0700 Subject: [PATCH 35/54] Update ShareMenu link text --- web/src/components/menus/ShareMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx index b74407e4..e7e33550 100644 --- a/web/src/components/menus/ShareMenu.tsx +++ b/web/src/components/menus/ShareMenu.tsx @@ -167,7 +167,7 @@ const ShareMenu = (props: ShareMenuProps) => { <span className="block text-sm text-zinc-700 dark:text-amber-50"> {isPagePublic ? 'Anyone with the link can view this page.' - : 'Only people you share the link with can view this page.'} + : 'Only people you invite can view this page.'} </span> </div> <input From e6227403bf722120fc54c4fa4fa0b935091b102d Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 17:09:33 -0700 Subject: [PATCH 36/54] Add API call to update page permissions on share settings change --- web/src/components/menus/ShareMenu.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx index e7e33550..9ed79bb5 100644 --- a/web/src/components/menus/ShareMenu.tsx +++ b/web/src/components/menus/ShareMenu.tsx @@ -154,6 +154,26 @@ const ShareMenu = (props: ShareMenuProps) => { className="flex flex-row w-full gap-5 p-2 text-left rounded-sm cursor-pointer select-none dark:hover:bg-white/10 hover:bg-black/10" onClick={() => { setIsPagePublic(!isPagePublic); + + (async () => { + await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/page/update-permissions/${page}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: '*', + permissions: { + read: !isPagePublic, + write: false, + admin: false, + } + }), + } + ); + })(); }} > <Image From 43839fc93f89b73db95056b5be28b8dd1ecd19ac Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 18:47:35 -0700 Subject: [PATCH 37/54] Fix issues with making a page public --- backend/src/middleware/verifyPermissions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/middleware/verifyPermissions.ts b/backend/src/middleware/verifyPermissions.ts index 9e9f4c1f..422394ca 100644 --- a/backend/src/middleware/verifyPermissions.ts +++ b/backend/src/middleware/verifyPermissions.ts @@ -60,7 +60,9 @@ const verifyPermissions = (permissions: ValidPermissions[]) => { return; } - const userPermissionsOnPage = pageData.permissions[username] || undefined; + console.log(pageData.permissions); + + const userPermissionsOnPage = pageData.permissions[username] || {}; // Merge pageData.permissions['*'] with userPermissionsOnPage if (pageData.permissions['*']) { From 3198721a3142cdc6348874e3271906b1c0beb0a5 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 18:57:34 -0700 Subject: [PATCH 38/54] Refactor share component and add handling for setting page public. --- backend/src/middleware/verifyPermissions.ts | 2 -- web/src/components/menus/ShareMenu.tsx | 15 ++++++--------- web/src/components/menus/ShareOptionsDropdown.tsx | 2 +- .../components/pageCustomization/ShareButton.tsx | 11 ++++++++++- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/backend/src/middleware/verifyPermissions.ts b/backend/src/middleware/verifyPermissions.ts index 422394ca..583c7d08 100644 --- a/backend/src/middleware/verifyPermissions.ts +++ b/backend/src/middleware/verifyPermissions.ts @@ -60,8 +60,6 @@ const verifyPermissions = (permissions: ValidPermissions[]) => { return; } - console.log(pageData.permissions); - const userPermissionsOnPage = pageData.permissions[username] || {}; // Merge pageData.permissions['*'] with userPermissionsOnPage diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx index 9ed79bb5..5aef2dd0 100644 --- a/web/src/components/menus/ShareMenu.tsx +++ b/web/src/components/menus/ShareMenu.tsx @@ -10,8 +10,10 @@ interface ShareMenuProps { page: string, setIsMenuOpen: (isMenuOpen: boolean) => void, buttonRef: React.RefObject<HTMLDivElement>, + isPagePublic: boolean, + setIsPagePublic: (isPagePublic: boolean) => void, pagePermissions?: Permissions, - permissionsOnPage?: UserPermissions + permissionsOnPage?: UserPermissions, } const ShareMenu = (props: ShareMenuProps) => { @@ -21,8 +23,9 @@ const ShareMenu = (props: ShareMenuProps) => { buttonRef, pagePermissions, permissionsOnPage, + isPagePublic, + setIsPagePublic, } = props; - const [isPagePublic, setIsPagePublic] = useState(false); const [isEditingEmails, setIsEditingEmails] = useState(false); const [currentPermissions, setCurrentPermissions] = useState<typeof pagePermissions>({}); @@ -32,12 +35,6 @@ const ShareMenu = (props: ShareMenuProps) => { } }, [pagePermissions]); - useEffect(() => { - if (currentPermissions && currentPermissions['*']?.read) { - setIsPagePublic(true); - } - }, [currentPermissions, isPagePublic]); - useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( @@ -137,7 +134,7 @@ const ShareMenu = (props: ShareMenuProps) => { <span className="font-bold">Shared with</span> { Object.values(currentPermissions).map((permission) => ( - <UserPermission + permission.email !== '*' && <UserPermission email={permission.email} permissions={permission as UserPermissions} pagePermissions={currentPermissions} diff --git a/web/src/components/menus/ShareOptionsDropdown.tsx b/web/src/components/menus/ShareOptionsDropdown.tsx index c5de7f5b..76206ea4 100644 --- a/web/src/components/menus/ShareOptionsDropdown.tsx +++ b/web/src/components/menus/ShareOptionsDropdown.tsx @@ -52,7 +52,7 @@ const ShareOptionsDropdown = (props: ShareOptionsDropdownProps) => { </div> </div> { - selectedDropdownOption === option && ( + +selectedDropdownOption.toString() === option && ( <svg className="w-4 h-4" viewBox="0 0 20 20" diff --git a/web/src/components/pageCustomization/ShareButton.tsx b/web/src/components/pageCustomization/ShareButton.tsx index 4ebac7d8..e7814673 100644 --- a/web/src/components/pageCustomization/ShareButton.tsx +++ b/web/src/components/pageCustomization/ShareButton.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { useRouter } from 'next/router'; import type { Permissions, UserPermissions } from '../../lib/types/pageTypes'; @@ -14,6 +14,13 @@ const ShareButton = (props: ShareButtonProps) => { const { page } = useRouter().query; const [isMenuOpen, setIsMenuOpen] = useState(false); const buttonRef = useRef<HTMLDivElement>(null); + const [isPagePublic, setIsPagePublic] = useState(false); + + useEffect(() => { + if (pagePermissions && pagePermissions['*']?.read) { + setIsPagePublic(true); + } + }, [pagePermissions]); return ( <div @@ -37,6 +44,8 @@ const ShareButton = (props: ShareButtonProps) => { buttonRef={buttonRef} pagePermissions={pagePermissions} permissionsOnPage={permissionsOnPage} + isPagePublic={isPagePublic} + setIsPagePublic={setIsPagePublic} /> ) } From 512703a87e6d2a8f23749582582ded602e93a616 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sun, 12 Mar 2023 19:11:43 -0700 Subject: [PATCH 39/54] Modify ShareMenu permission check --- web/src/components/menus/ShareMenu.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx index 5aef2dd0..c757fa97 100644 --- a/web/src/components/menus/ShareMenu.tsx +++ b/web/src/components/menus/ShareMenu.tsx @@ -129,7 +129,11 @@ const ShareMenu = (props: ShareMenuProps) => { permissionsOnPage?.admin && ( <> { - (currentPermissions && !isPagePublic) && ( + ( + currentPermissions + && Object.keys(currentPermissions).length > 0 + && !isPagePublic + ) && ( <div> <span className="font-bold">Shared with</span> { From aac0d26dcdbc084fcb48a600b3dfaaf44293296b Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Thu, 16 Mar 2023 09:37:36 -0700 Subject: [PATCH 40/54] Fix permissions object not being set on addPage function. --- backend/src/helpers/addPage.ts | 2 +- backend/src/routes/page/updatePermissions.ts | 82 ++++++++++++++------ backend/src/setupAuth.ts | 1 + 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/backend/src/helpers/addPage.ts b/backend/src/helpers/addPage.ts index a3cc02b7..27590c0d 100644 --- a/backend/src/helpers/addPage.ts +++ b/backend/src/helpers/addPage.ts @@ -83,7 +83,7 @@ const addPage = async ( icon: '📝', name: newPageName || 'New Notebook', }, - permissions: pagePermissions || [], + permissions: pagePermissions || {}, data: [], }); }; diff --git a/backend/src/routes/page/updatePermissions.ts b/backend/src/routes/page/updatePermissions.ts index 64d8171c..9a4a2aa4 100644 --- a/backend/src/routes/page/updatePermissions.ts +++ b/backend/src/routes/page/updatePermissions.ts @@ -2,14 +2,17 @@ import express from 'express'; import ThirdParty from 'supertokens-node/recipe/thirdparty'; import PageModel from '../../models/pageModel'; -import verifyPermissions from '../../middleware/verifyPermissions'; +import verifyPermissions, { PageRequest } from '../../middleware/verifyPermissions'; +import PageTreeModel from '../../models/pageTreeModel'; +import PageMapModel from '../../models/pageMap'; const router = express.Router(); router.post( '/update-permissions/:page/', verifyPermissions(['admin']), - async (req, res) => { + async (req: PageRequest, res) => { + const username = req.pageData!.user; const { page } = req.params; const { email, permissions } = req.body; @@ -37,28 +40,61 @@ router.post( return; } - if (Object.values(permissions).some((value) => value === true)) { - await PageModel.findByIdAndUpdate( - page, - { - $set: { - [`permissions.${userId[0]}`]: { - ...permissions, - email, - }, - }, - }, - ); - } else { - await PageModel.findByIdAndUpdate( - page, - { - $unset: { - [`permissions.${userId[0]}`]: '', - }, - }, - ); + // ~ Get all sub pages and update their permissions + const pageMap = await PageMapModel.findById(page).lean(); + const pageTree = await PageTreeModel.findById(username).lean(); + + if (!pageMap || !pageTree) { + res.statusCode = 404; + res.json({ + status: 'error', + message: 'This page does not exist...', + }); + return; } + + pageMap.pathToPage.push(page); + + // Traverse the page tree to get all sub pages + const startingPageTree = (() => { + let subPageTree = pageTree; + let subPageMap = pageMap; + while (subPageMap.pathToPage.length > 0) { + const pageID = subPageMap.pathToPage.shift()!; + subPageTree = subPageTree.subPages.find((subPage) => subPage._id === pageID)!; + } + + return subPageTree; + })(); + + let subPages: string[] = (() => { + const subPages: string[] = []; + const traverse = (pageTree: any) => { + subPages.push(pageTree._id); + pageTree.subPages.forEach((subPage: any) => traverse(subPage)); + }; + + traverse(startingPageTree); + return subPages; + })(); + + console.log(subPages); + + await PageModel.updateMany( + { + _id: { + $in: subPages, + } + }, + { + $set: { + [`permissions.${userId[0]}`]: { + ...permissions, + email, + }, + } + }, + ); res.statusCode = 200; res.json({ diff --git a/backend/src/setupAuth.ts b/backend/src/setupAuth.ts index 1f71224f..6cb83dca 100644 --- a/backend/src/setupAuth.ts +++ b/backend/src/setupAuth.ts @@ -97,6 +97,7 @@ const setupAuth = () => { icon: '📝', name: 'New Notebook', }, + permissions: {}, data: [], }, ); From 48c22a07b67362d7a0facd7bc175888d6c572942 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Fri, 17 Mar 2023 11:08:28 -0700 Subject: [PATCH 41/54] Add PagePermissionsContext, and fix issues with state --- web/src/components/menus/EmailShareMenu.tsx | 32 ++++++++- web/src/components/menus/ShareMenu.tsx | 56 +++------------- web/src/components/menus/UserPermission.tsx | 27 ++++---- .../pageCustomization/ShareButton.tsx | 65 ++++++++++++------- web/src/contexts/PagePermissionsContext.tsx | 25 +++++++ 5 files changed, 120 insertions(+), 85 deletions(-) create mode 100644 web/src/contexts/PagePermissionsContext.tsx diff --git a/web/src/components/menus/EmailShareMenu.tsx b/web/src/components/menus/EmailShareMenu.tsx index 0ff6d532..1574a144 100644 --- a/web/src/components/menus/EmailShareMenu.tsx +++ b/web/src/components/menus/EmailShareMenu.tsx @@ -1,16 +1,21 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, useContext } from 'react'; import { DropdownOptions, dropdownInfo } from '../../lib/constants/ShareOptions'; import ShareOptionsDropdown from './ShareOptionsDropdown'; +import PagePermissionContext from '../../contexts/PagePermissionsContext'; interface ShareMenuProps { page: string, setIsEditingEmails: (isEditingEmails: boolean) => void, - addPermissions: (email: string, currentPermissions: any) => void, } const EmailShareMenu = (props: ShareMenuProps) => { - const { page, setIsEditingEmails, addPermissions } = props; + const { page, setIsEditingEmails } = props; + + const { + currentPermissions, + setCurrentPermissions, + } = useContext(PagePermissionContext); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [selectedDropdownOption, setSelectedDropdownOption] = useState(DropdownOptions.FullAccess); @@ -34,6 +39,27 @@ const EmailShareMenu = (props: ShareMenuProps) => { } }); + const addPermissions = (email: string, newPermissions: any) => { + if (!currentPermissions) { + setCurrentPermissions({ + [email]: { + ...newPermissions, + }, + }); + return; + } + + const key = Object.entries(currentPermissions).find(([, value]) => value.email === email)?.[0] || email; + + setCurrentPermissions({ + ...currentPermissions, + [key]: { + ...newPermissions, + email, + }, + }); + } + return ( <> <div diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx index c757fa97..e57ab6d3 100644 --- a/web/src/components/menus/ShareMenu.tsx +++ b/web/src/components/menus/ShareMenu.tsx @@ -1,39 +1,28 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import Image from 'next/image'; import EmailShareMenu from './EmailShareMenu'; import Globe from '../../public/Globe.svg'; -import type { Permissions, UserPermissions } from '../../lib/types/pageTypes'; +import type { Permissions } from '../../lib/types/pageTypes'; import UserPermission from './UserPermission'; +import PagePermissionsContext from '../../contexts/PagePermissionsContext'; interface ShareMenuProps { page: string, - setIsMenuOpen: (isMenuOpen: boolean) => void, buttonRef: React.RefObject<HTMLDivElement>, - isPagePublic: boolean, - setIsPagePublic: (isPagePublic: boolean) => void, - pagePermissions?: Permissions, - permissionsOnPage?: UserPermissions, } const ShareMenu = (props: ShareMenuProps) => { + const { page, buttonRef } = props; + const [isEditingEmails, setIsEditingEmails] = useState(false); + const { - page, - setIsMenuOpen, - buttonRef, - pagePermissions, permissionsOnPage, + setIsMenuOpen, isPagePublic, setIsPagePublic, - } = props; - const [isEditingEmails, setIsEditingEmails] = useState(false); - const [currentPermissions, setCurrentPermissions] = useState<typeof pagePermissions>({}); - - useEffect(() => { - if (pagePermissions) { - setCurrentPermissions(pagePermissions); - } - }, [pagePermissions]); + currentPermissions, + } = useContext(PagePermissionsContext); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -52,27 +41,6 @@ const ShareMenu = (props: ShareMenuProps) => { } }); - const addPermissions = (email: string, newPermissions: any) => { - if (!currentPermissions) { - setCurrentPermissions({ - [email]: { - ...newPermissions, - }, - }); - return; - } - - const key = Object.entries(currentPermissions).find(([, value]) => value.email === email)?.[0] || email; - - setCurrentPermissions({ - ...currentPermissions, - [key]: { - ...newPermissions, - email, - }, - }); - } - return ( <div className={` @@ -88,7 +56,6 @@ const ShareMenu = (props: ShareMenuProps) => { <EmailShareMenu page={page} setIsEditingEmails={setIsEditingEmails} - addPermissions={addPermissions} /> ) : ( @@ -131,7 +98,7 @@ const ShareMenu = (props: ShareMenuProps) => { { ( currentPermissions - && Object.keys(currentPermissions).length > 0 + && Object.keys(currentPermissions).filter((key) => key !== '*').length > 0 && !isPagePublic ) && ( <div> @@ -140,9 +107,6 @@ const ShareMenu = (props: ShareMenuProps) => { Object.values(currentPermissions).map((permission) => ( permission.email !== '*' && <UserPermission email={permission.email} - permissions={permission as UserPermissions} - pagePermissions={currentPermissions} - setCurrentPermissions={setCurrentPermissions} key={permission.email} /> )) diff --git a/web/src/components/menus/UserPermission.tsx b/web/src/components/menus/UserPermission.tsx index 9f8c5548..93478c5c 100644 --- a/web/src/components/menus/UserPermission.tsx +++ b/web/src/components/menus/UserPermission.tsx @@ -1,24 +1,17 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useContext } from 'react'; import { UserPermissions, Permissions } from '../../lib/types/pageTypes'; import { DropdownOptions, dropdownInfo } from '../../lib/constants/ShareOptions'; import ShareOptionsDropdown from './ShareOptionsDropdown'; import { useRouter } from 'next/router'; +import PagePermissionsContext from '../../contexts/PagePermissionsContext'; interface UserPermissionProps { email: string, - permissions: UserPermissions, - pagePermissions: Permissions, - setCurrentPermissions: React.Dispatch<React.SetStateAction<Permissions | undefined>>, } const UserPermission = (props: UserPermissionProps) => { - const { - email, - permissions, - setCurrentPermissions, - pagePermissions - } = props; + const { email } = props; const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [selectedDropdownOption, setSelectedDropdownOption] = useState<DropdownOptions>(DropdownOptions.ViewOnly); @@ -26,8 +19,18 @@ const UserPermission = (props: UserPermissionProps) => { const page = useRouter().query.page as string; + const { + pagePermissions, + permissionsOnPage: permissions, + setCurrentPermissions, + } = useContext(PagePermissionsContext); + useEffect(() => { const defaultDropdownOption = Object.keys(dropdownInfo).find((key) => { + if (!permissions) { + return false; + } + let keyPermissions = dropdownInfo[+key].permissions; return Object.entries(keyPermissions).every(([key, value]) => { @@ -56,7 +59,7 @@ const UserPermission = (props: UserPermissionProps) => { }); const setSelectDropdownOption = (option: DropdownOptions) => { - let key = Object.keys(pagePermissions).find((key) => pagePermissions[key].email === email) || email; + let key = Object.keys(pagePermissions!).find((key) => pagePermissions![key].email === email) || email; setCurrentPermissions({ ...pagePermissions, @@ -111,7 +114,7 @@ const UserPermission = (props: UserPermissionProps) => { <p onClick={() => { setCurrentPermissions({ - ...Object.fromEntries(Object.entries(pagePermissions).filter(([_, value]) => value.email !== email)) + ...Object.fromEntries(Object.entries(pagePermissions!).filter(([_, value]) => value.email !== email)) } as Permissions); (async () => { diff --git a/web/src/components/pageCustomization/ShareButton.tsx b/web/src/components/pageCustomization/ShareButton.tsx index e7814673..56bbc5f5 100644 --- a/web/src/components/pageCustomization/ShareButton.tsx +++ b/web/src/components/pageCustomization/ShareButton.tsx @@ -3,6 +3,7 @@ import { useRouter } from 'next/router'; import type { Permissions, UserPermissions } from '../../lib/types/pageTypes'; import ShareMenu from '../menus/ShareMenu'; +import PagePermissionsContext from '../../contexts/PagePermissionsContext'; interface ShareButtonProps { pagePermissions?: Permissions @@ -15,41 +16,57 @@ const ShareButton = (props: ShareButtonProps) => { const [isMenuOpen, setIsMenuOpen] = useState(false); const buttonRef = useRef<HTMLDivElement>(null); const [isPagePublic, setIsPagePublic] = useState(false); + const [currentPermissions, setCurrentPermissions] = useState<Permissions>({}); useEffect(() => { if (pagePermissions && pagePermissions['*']?.read) { setIsPagePublic(true); + } else { + setIsPagePublic(false); + } + }, [pagePermissions]); + + useEffect(() => { + if (pagePermissions) { + setCurrentPermissions(pagePermissions); } }, [pagePermissions]); return ( - <div - ref={buttonRef} + <PagePermissionsContext.Provider + value={ + { + pagePermissions, + permissionsOnPage, + isMenuOpen, + setIsMenuOpen, + isPagePublic, + setIsPagePublic, + currentPermissions, + setCurrentPermissions, + } + } > - <div className="flex flex-row h-full gap-1 p-2 select-none w-max"> - <div - className="w-full h-full text-center rounded cursor-pointer text-zinc-700 dark:text-amber-50" - onClick={() => { - setIsMenuOpen(!isMenuOpen); - }} - > - Share + <div + ref={buttonRef} + > + <div className="flex flex-row h-full gap-1 p-2 select-none w-max"> + <div + className="w-full h-full text-center rounded cursor-pointer text-zinc-700 dark:text-amber-50" + onClick={() => { + setIsMenuOpen(!isMenuOpen); + }} + > + Share + </div> </div> + { + isMenuOpen && ( + <ShareMenu page={page as string} buttonRef={buttonRef} /> + ) + } </div> - { - isMenuOpen && ( - <ShareMenu - page={page as string} - setIsMenuOpen={setIsMenuOpen} - buttonRef={buttonRef} - pagePermissions={pagePermissions} - permissionsOnPage={permissionsOnPage} - isPagePublic={isPagePublic} - setIsPagePublic={setIsPagePublic} - /> - ) - } - </div> + </PagePermissionsContext.Provider> ) } diff --git a/web/src/contexts/PagePermissionsContext.tsx b/web/src/contexts/PagePermissionsContext.tsx new file mode 100644 index 00000000..b53938f6 --- /dev/null +++ b/web/src/contexts/PagePermissionsContext.tsx @@ -0,0 +1,25 @@ +import { createContext } from 'react'; + +import type { UserPermissions, Permissions } from '../lib/types/pageTypes'; + +interface PagePermissionsContextProps { + pagePermissions?: Permissions, + permissionsOnPage?: UserPermissions, + isMenuOpen: boolean, + setIsMenuOpen: (isMenuOpen: boolean) => void, + isPagePublic: boolean, + setIsPagePublic: (isPagePublic: boolean) => void, + currentPermissions: Permissions, + setCurrentPermissions: (currentPermissions: Permissions) => void, +} + +const PagePermissionContext = createContext<PagePermissionsContextProps>({ + isMenuOpen: false, + setIsMenuOpen: () => {}, + isPagePublic: false, + setIsPagePublic: () => {}, + currentPermissions: {}, + setCurrentPermissions: () => {}, +}); + +export default PagePermissionContext; From 4badf3b964210a2be44c274e32c71a5c3d1739d1 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Fri, 17 Mar 2023 11:13:05 -0700 Subject: [PATCH 42/54] Add logic to prevent users from updating their own permissions --- backend/src/routes/page/updatePermissions.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/src/routes/page/updatePermissions.ts b/backend/src/routes/page/updatePermissions.ts index 9a4a2aa4..07dc3a32 100644 --- a/backend/src/routes/page/updatePermissions.ts +++ b/backend/src/routes/page/updatePermissions.ts @@ -26,6 +26,17 @@ router.post( return; } + const updaterEmail = (await ThirdParty.getUserById(req.session!.getUserId()))?.email; + + if (updaterEmail === email) { + res.statusCode = 400; + res.json({ + status: 'error', + message: 'You cannot update your own permissions...', + }); + return; + } + // ~ Get the user ID from the email (or * if it's a wildcard) const userId = email === '*' ? ['*'] @@ -78,8 +89,6 @@ router.post( return subPages; })(); - console.log(subPages); - await PageModel.updateMany( { _id: { From 1f0b907a32c673fc61a8e9dc71f231c0982981df Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Fri, 17 Mar 2023 11:59:40 -0700 Subject: [PATCH 43/54] Disallow non-owner users from editing admin permissions. --- backend/src/routes/page/updatePermissions.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/src/routes/page/updatePermissions.ts b/backend/src/routes/page/updatePermissions.ts index 07dc3a32..ffa7498a 100644 --- a/backend/src/routes/page/updatePermissions.ts +++ b/backend/src/routes/page/updatePermissions.ts @@ -26,6 +26,15 @@ router.post( return; } + if (permissions.admin && username !== req.session!.getUserId()) { + res.statusCode = 400; + res.json({ + status: 'error', + message: 'You cannot edit admin permissions...', + }); + return; + } + const updaterEmail = (await ThirdParty.getUserById(req.session!.getUserId()))?.email; if (updaterEmail === email) { From 7d42f80488327e1ccdbb1f01261a9bd01af23c8d Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Fri, 17 Mar 2023 12:21:14 -0700 Subject: [PATCH 44/54] Remove old api documentation --- .../documentation/account/editPageTree.yaml | 30 ----- .../documentation/account/getPageTree.yaml | 12 -- backend/documentation/account/login.yaml | 30 ----- backend/documentation/account/register.yaml | 32 ------ backend/documentation/page/getHomePage.yaml | 24 ---- backend/documentation/page/getPage.yaml | 46 -------- backend/documentation/page/getPageInfo.yaml | 35 ------ backend/documentation/page/modify.yaml | 108 ------------------ .../page/modifyPage/modifyPage.yaml | 86 -------------- 9 files changed, 403 deletions(-) delete mode 100644 backend/documentation/account/editPageTree.yaml delete mode 100644 backend/documentation/account/getPageTree.yaml delete mode 100644 backend/documentation/account/login.yaml delete mode 100644 backend/documentation/account/register.yaml delete mode 100644 backend/documentation/page/getHomePage.yaml delete mode 100644 backend/documentation/page/getPage.yaml delete mode 100644 backend/documentation/page/getPageInfo.yaml delete mode 100644 backend/documentation/page/modify.yaml delete mode 100644 backend/documentation/page/modifyPage/modifyPage.yaml diff --git a/backend/documentation/account/editPageTree.yaml b/backend/documentation/account/editPageTree.yaml deleted file mode 100644 index 37694b55..00000000 --- a/backend/documentation/account/editPageTree.yaml +++ /dev/null @@ -1,30 +0,0 @@ -paths: - /account/edit-page-tree/{page}: - post: - description: Edit a users page tree - tags: - - Account - - Pages - parameters: - - in: path - name: page - description: The page to edit - required: true - schema: - type: string - example: 02a80903359f073357d677a3 - - in: query - name: body - description: The information about page trees nodes state - required: true - schema: - type: object - properties: - new-exansion-state: - type: boolean - description: The new expansion state of the page tree node - responses: - '200': - description: Returns the page tree - '400': - description: The user isn't logged in \ No newline at end of file diff --git a/backend/documentation/account/getPageTree.yaml b/backend/documentation/account/getPageTree.yaml deleted file mode 100644 index 5d5b324f..00000000 --- a/backend/documentation/account/getPageTree.yaml +++ /dev/null @@ -1,12 +0,0 @@ -paths: - /account/get-page-tree: - get: - description: Get a users page tree - tags: - - Account - - Pages - responses: - '200': - description: Returns the page tree - '400': - description: The user isn't logged in \ No newline at end of file diff --git a/backend/documentation/account/login.yaml b/backend/documentation/account/login.yaml deleted file mode 100644 index f9f045ca..00000000 --- a/backend/documentation/account/login.yaml +++ /dev/null @@ -1,30 +0,0 @@ -paths: - /account/login: - post: - description: Login to an account - tags: - - Account - parameters: - - in: query - name: body - description: The email and password to login with - required: true - schema: - type: object - properties: - email: - type: string - description: The email of the user - password: - type: string - description: The password of the user - - in: query - responses: - '200': - description: Succesfully logged in - '400': - description: The user didn't provide an email or password - '403': - description: Invalid email or password - '429': - description: The user has been timed out for too many failed login attempts \ No newline at end of file diff --git a/backend/documentation/account/register.yaml b/backend/documentation/account/register.yaml deleted file mode 100644 index fd9e9013..00000000 --- a/backend/documentation/account/register.yaml +++ /dev/null @@ -1,32 +0,0 @@ -paths: - /account/register: - post: - description: Register a new account - tags: - - Account - parameters: - - in: query - name: body - description: The email, username and password to create the account with - required: true - schema: - type: object - properties: - email: - type: string - description: The email of the user - password: - type: string - description: The password of the user - username: - type: string - description: The username of the user - responses: - '200': - description: Successfully created the account - '400': - description: The user didn't provide a valid email or password or username - '409': - description: The user already exists - '500': - description: The user couldn't be created \ No newline at end of file diff --git a/backend/documentation/page/getHomePage.yaml b/backend/documentation/page/getHomePage.yaml deleted file mode 100644 index e9e05b31..00000000 --- a/backend/documentation/page/getHomePage.yaml +++ /dev/null @@ -1,24 +0,0 @@ -paths: - /page/get-home-page: - get: - description: Get a users home page - tags: - - Pages - responses: - '200': - description: Returns the home page ID - content: - 'application/json': - schema: - type: object - properties: - status: - type: string - description: The status of the request - example: success - message: - type: string - example: 02a80903359f073357d677a3 - description: The ID of the users home page - '401': - description: The user is not logged in or doesn't have a home page \ No newline at end of file diff --git a/backend/documentation/page/getPage.yaml b/backend/documentation/page/getPage.yaml deleted file mode 100644 index e0b74439..00000000 --- a/backend/documentation/page/getPage.yaml +++ /dev/null @@ -1,46 +0,0 @@ -paths: - /page/get-page/{page}: - get: - description: Get a page - tags: - - Pages - parameters: - - in: path - name: page - description: The page to get - required: true - schema: - type: string - example: 02a80903359f073357d677a3 - pattern: ^[a-f\d]{24}$ - responses: - '200': - description: Returns the page - content: - 'application/json': - schema: - type: object - properties: - status: - type: string - description: The status of the request - example: success - message: - style: - type: object - data: - type: array - items: - type: object - properties: - properties: - type: object - children: - type: array - - '401': - description: The user is not logged in - '403': - description: The user doesn't have access to the page - '404': - description: The page doesn't exist \ No newline at end of file diff --git a/backend/documentation/page/getPageInfo.yaml b/backend/documentation/page/getPageInfo.yaml deleted file mode 100644 index 126e4f11..00000000 --- a/backend/documentation/page/getPageInfo.yaml +++ /dev/null @@ -1,35 +0,0 @@ -paths: - /page/get-page-info/{page}: - get: - description: Get a pages style data - tags: - - Pages - parameters: - - in: path - name: page - description: The page to get - required: true - schema: - type: string - example: 02a80903359f073357d677a3 - pattern: ^[a-f\d]{24}$ - responses: - '200': - description: Returns the page - content: - 'application/json': - schema: - type: object - properties: - status: - type: string - description: The status of the request - example: success - message: - style: - type: object - - '401': - description: The user is not logged in - '404': - description: The page doesn't exist \ No newline at end of file diff --git a/backend/documentation/page/modify.yaml b/backend/documentation/page/modify.yaml deleted file mode 100644 index 0e51afec..00000000 --- a/backend/documentation/page/modify.yaml +++ /dev/null @@ -1,108 +0,0 @@ -paths: - /page/modify/{page}: - post: - description: Modify a group of blocks on a page using a set of operations - tags: - - Pages - - Blocks - parameters: - - in: path - name: page - description: The page to modify - required: true - schema: - type: string - example: 02a80903359f073357d677a3 - pattern: ^[a-f\d]{24}$ - - in: query - name: body - description: The operations to perform on the page - required: true - schema: - type: object - properties: - operations: - type: array - items: - oneOf: - - type: object - properties: - operation: - type: string - description: The type of operation to perform - example: addBlock - data: - type: object - description: The data to perform the operation with - properties: - doc-ids: - type: array - description: The IDs of the documents to perform the operation on - items: - type: string - pattern: ^[a-f\d]{24}$ - new-block-type: - type: string - description: The type of block to add - example: text - new-block-index: - type: number - description: The index of the block to add - new-block-properties: - type: object - description: The properties of the block to add - new-block-id: - type: string - description: The ID of the block to add - pattern: ^[a-f\d]{24}$ - - type: object - properties: - operation: - type: string - description: The type of operation to perform - example: deleteBlock - data: - type: object - description: The data to perform the operation with - properties: - doc-ids: - type: array - description: The IDs of the documents to perform the operation on - items: - type: string - pattern: ^[a-f\d]{24}$ - minItems: 1 - - type: object - properties: - operation: - type: string - description: The type of operation to perform - example: updateBlock - data: - type: object - description: The data to perform the operation with - properties: - doc-ids: - type: array - description: The IDs of the documents to perform the operation on - items: - type: string - pattern: ^[a-f\d]{24}$ - block-type: - type: string - description: The type to update the block to - example: text - block-properties: - type: object - description: The properties to update the block with - example: - text: Hello World - responses: - '200': - description: Successfully modified page - '400': - description: The user didn't provide any operations to perform - '401': - description: User not logged in - '404': - description: User doesn't have access to the page or page doesn't exist \ No newline at end of file diff --git a/backend/documentation/page/modifyPage/modifyPage.yaml b/backend/documentation/page/modifyPage/modifyPage.yaml deleted file mode 100644 index 88dcbdec..00000000 --- a/backend/documentation/page/modifyPage/modifyPage.yaml +++ /dev/null @@ -1,86 +0,0 @@ -paths: - /page/modify-page/{page}: - post: - description: Create a page - tags: - - Pages - parameters: - - in: path - name: page - description: The page to create the new page under - required: true - schema: - type: string - example: 02a80903359f073357d677a3 - pattern: ^[a-f\d]{24}$ - - in: query - name: body - description: The information about the new page - required: true - schema: - type: object - properties: - new-page-id: - type: string - description: The ID of the new page - pattern: ^[a-f\d]{24}$ - new-page-name: - type: string - description: The name of the new page - responses: - '200': - description: Created the page - '401': - description: The user is not logged in or doesn't have access to the page - '500': - description: The page couldn't be created - delete: - description: Delete a page - tags: - - Pages - parameters: - - in: path - name: page - description: The page to delete - required: true - schema: - type: string - example: 02a80903359f073357d677a3 - pattern: ^[a-f\d]{24}$ - responses: - '200': - description: Deleted the page - '401': - description: The user is not logged in or doesn't have access to the page - '500': - description: The page couldn't be deleted - patch: - description: Edit a pages style - tags: - - Pages - parameters: - - in: path - name: page - description: The page to edit - required: true - schema: - type: string - example: 02a80903359f073357d677a3 - pattern: ^[a-f\d]{24}$ - - in: query - name: body - description: The information about the new page - required: true - schema: - type: object - properties: - style: - type: object - description: The new style of the page - responses: - '200': - description: Edited the page - '401': - description: The user is not logged in or doesn't have access to the page - '500': - description: The page couldn't be edited \ No newline at end of file From f75aa3433629e2340fc0af6d21f378fd3399ab71 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sat, 18 Mar 2023 17:05:13 -0700 Subject: [PATCH 45/54] Refactor permission verification and page sidebar rendering. --- backend/src/middleware/verifyPermissions.ts | 19 ++++--------------- web/src/components/pageInfo/PageSidebar.tsx | 13 ++++++++++++- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/backend/src/middleware/verifyPermissions.ts b/backend/src/middleware/verifyPermissions.ts index 583c7d08..79136d6b 100644 --- a/backend/src/middleware/verifyPermissions.ts +++ b/backend/src/middleware/verifyPermissions.ts @@ -15,10 +15,6 @@ export interface PageRequest extends SessionRequest { const verifyPermissions = (permissions: ValidPermissions[]) => { const verifyPermissionsMiddleware = async (req: PageRequest, res: Response, next: NextFunction) => { const { page } = req.params; - - if (!req.session) { - throw new Error('No session provided to middleware!'); - } if (!page) { res.statusCode = 400; @@ -29,16 +25,7 @@ const verifyPermissions = (permissions: ValidPermissions[]) => { return; } - const username = req.session.getUserId(); - - if (!username) { - res.statusCode = 401; - res.json({ - status: 'error', - message: 'Please login to modify this page!', - }); - return; - } + const username = req.session?.getUserId() || ''; const pageData = await PageModel.findOne({ _id: page }).lean(); @@ -101,7 +88,9 @@ const verifyPermissions = (permissions: ValidPermissions[]) => { }; const middleware = async (req: SessionRequest, res: Response, next: NextFunction) => { - verifySession()( + verifySession({ + sessionRequired: false, + })( req, res, () => verifyPermissionsMiddleware(req, res, next), diff --git a/web/src/components/pageInfo/PageSidebar.tsx b/web/src/components/pageInfo/PageSidebar.tsx index 68bed180..74f6e141 100644 --- a/web/src/components/pageInfo/PageSidebar.tsx +++ b/web/src/components/pageInfo/PageSidebar.tsx @@ -29,13 +29,24 @@ const PageSidebar = () => { // -=- Setting -=- // ~ Set the page tree, if it exists - setPageTree(pageTreeObject.message.subPages); + setPageTree(pageTreeObject.message.subPages || []); // ~ Set the loading state to false setIsLoading(false); })(); }, []); + + if (!pageTree?.length) { + return ( + <div className="absolute h-screen p-3 pt-12 select-none print:h-max w-52 print:w-0 bg-amber-400/10 no-scrollbar dark:bg-white/10"> + <p className="text-white"> + Log in to see your pages + </p> + </div> + ); + } + return ( <div className="absolute h-screen pt-12 pr-3 select-none print:h-max w-52 print:w-0 bg-amber-400/10 no-scrollbar dark:bg-white/10"> {/* ~ Render the page tree after it has been loaded */} From 4559c4363f5d94ee8e1ad1ae2eedc52fa7939112 Mon Sep 17 00:00:00 2001 From: Eroxl <InfinityWaffles@outlook.com> Date: Sat, 18 Mar 2023 17:48:45 -0700 Subject: [PATCH 46/54] Switch to useContext --- web/src/components/Editor.tsx | 40 ++++---------- web/src/components/blocks/BaseBlock.tsx | 6 +-- web/src/components/blocks/MathBlock.tsx | 7 +-- web/src/components/blocks/TextBlock.tsx | 7 +-- web/src/components/blocks/Title.tsx | 7 +-- .../pageCustomization/ShareButton.tsx | 15 +++--- web/src/contexts/PageContext.ts | 14 +++++ ...sContext.tsx => PagePermissionsContext.ts} | 0 web/src/lib/blockNavigation/handleKeyDown.ts | 2 +- web/src/lib/blockNavigation/handleKeyUp.ts | 2 +- web/src/lib/helpers/focusHelpers.ts | 14 ++--- web/src/lib/pages/updatePage.ts | 53 +++++++++---------- web/src/lib/types/blockTypes.d.ts | 4 -- web/src/pages/note-rack/[page].tsx | 49 +++++++++-------- 14 files changed, 106 insertions(+), 114 deletions(-) create mode 100644 web/src/contexts/PageContext.ts rename web/src/contexts/{PagePermissionsContext.tsx => PagePermissionsContext.ts} (100%) diff --git a/web/src/components/Editor.tsx b/web/src/components/Editor.tsx index 70fe1f58..a1962fda 100644 --- a/web/src/components/Editor.tsx +++ b/web/src/components/Editor.tsx @@ -1,29 +1,23 @@ /* eslint-disable react/no-children-prop */ /* eslint-disable no-underscore-dangle */ import React, { - Dispatch, - SetStateAction, useState, useEffect, + useContext, } from 'react'; import { useRouter } from 'next/router'; import { Selectable, useSelectionCollector } from 'react-virtual-selection'; -import type PageDataInterface from '../lib/types/pageTypes'; import BaseBlock from './blocks/BaseBlock'; import PageThumbnail from './pageCustomization/PageThumbnail'; import Title from './blocks/Title'; import Icon from './blocks/Icon'; import { removeBlock } from '../lib/pages/updatePage'; import deletePage from '../lib/deletePage'; +import PageContext from '../contexts/PageContext'; -interface EditorProps { - pageData: PageDataInterface, - setPageData: Dispatch<SetStateAction<PageDataInterface | Record<string, unknown>>> -} - -const Editor = (props: EditorProps) => { - const { pageData, setPageData } = props; +const Editor = () => { + const { pageData, setPageData } = useContext(PageContext); const [isMenuOpen, setIsMenuOpen] = useState(false); // -=- Setup Page Data -=- @@ -32,15 +26,11 @@ const Editor = (props: EditorProps) => { // -=- Setup Selection -=- const selectionData = useSelectionCollector('blocks'); - const [selectionDataConsumed, setSelectionDataConsumed] = useState(false); useEffect(() => { const handleSelectionEvents = (event: KeyboardEvent) => { // ~ If the user is not pressing the backspace key, return if (event.key !== 'Backspace') return; - - // ~ If the selection data has already been consumed, return - if (selectionDataConsumed) return; // ~ Iterate over all the selected blocks for (let i = 0; i < selectionData.length; i += 1) { @@ -61,9 +51,6 @@ const Editor = (props: EditorProps) => { // ~ Remove the block from the page removeBlock(index - i, [blockID], page as string, pageData, setPageData); } - - // ~ Reset the selection data - setSelectionDataConsumed(true); }; // ~ Add the event listener @@ -73,13 +60,10 @@ const Editor = (props: EditorProps) => { return () => { document.removeEventListener('keydown', handleSelectionEvents); }; - }, [selectionData, selectionDataConsumed]); - - useEffect(() => { - // ~ Reset the selection data, when the selection changes - setSelectionDataConsumed(false); }, [selectionData]); + if (!pageData) return null; + // -=- Render -=- return ( <Selectable @@ -91,7 +75,7 @@ const Editor = (props: EditorProps) => { className="flex flex-col items-center w-full h-max bg-amber-50 dark:bg-zinc-700 print:dark:bg-white print:bg-white bg-" > {/* ~ Render the page thumbnail */} - <PageThumbnail colour={pageData.message!.style.colour} page={page as string} /> + <PageThumbnail colour={pageData.style.colour} page={page as string} /> {/* ~ Render the main interactive editor */} <div className="flex flex-col w-full max-w-4xl gap-3 px-20 pb-56 mx-auto break-words select-none print:p-0 text-zinc-700 dark:text-amber-50 print:dark:text-zinc-700 h-max editor" @@ -99,19 +83,17 @@ const Editor = (props: EditorProps) => { {/* ~ Render the page icon */} <Icon page={page as string} - icon={pageData.message!.style.icon} + icon={pageData!.style.icon} /> {/* ~ Render the title */} <Title page={page as string} - pageData={pageData} index={0} - setPageData={setPageData} - title={pageData.message!.style.name} + title={pageData!.style.name} /> {/* ~ Render the blocks */} - {(pageData as PageDataInterface).message!.data.map((block, index) => ( + {pageData.data.map((block, index) => ( <BaseBlock blockType={block.blockType} blockID={block._id} @@ -120,8 +102,6 @@ const Editor = (props: EditorProps) => { page={page as string} properties={block.properties} children={block.children} - pageData={pageData} - setPageData={setPageData} isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} /> diff --git a/web/src/components/blocks/BaseBlock.tsx b/web/src/components/blocks/BaseBlock.tsx index bc1da19b..b8e540de 100644 --- a/web/src/components/blocks/BaseBlock.tsx +++ b/web/src/components/blocks/BaseBlock.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-children-prop */ -import React, { useState } from 'react'; +import React, { useState, useContext } from 'react'; import { useDrag, useDrop } from 'react-dnd'; import { useSelectable, SelectionManager } from 'react-virtual-selection'; @@ -7,22 +7,22 @@ import { moveBlock } from '../../lib/pages/updatePage'; import BlockHandle from './BlockHandle'; import BlockTypes from '../../lib/constants/BlockTypes'; import type { BaseBlockProps } from '../../lib/types/blockTypes'; +import PageContext from '../../contexts/PageContext'; const BaseBlock = (props: BaseBlockProps) => { const { page, index, blockID, - pageData, children, blockType, properties, - setPageData, isMenuOpen, setIsMenuOpen, } = props; const [currentBlockType, setCurrentBlockType] = useState(blockType); + const { pageData, setPageData } = useContext(PageContext); // -=- Setup Selection -=- const [selected, selectableRef] = useSelectable( diff --git a/web/src/components/blocks/MathBlock.tsx b/web/src/components/blocks/MathBlock.tsx index 60b130c9..e2cf0738 100644 --- a/web/src/components/blocks/MathBlock.tsx +++ b/web/src/components/blocks/MathBlock.tsx @@ -1,9 +1,10 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useContext } from 'react'; import katex from 'katex'; import 'katex/dist/katex.min.css'; import type { EditableText } from '../../lib/types/blockTypes'; import { addBlockAtIndex, editBlock } from '../../lib/pages/updatePage'; +import PageContext from '../../contexts/PageContext'; const MathBlock = (props: EditableText) => { const { @@ -11,12 +12,12 @@ const MathBlock = (props: EditableText) => { properties, page, index, - pageData, - setPageData, setCurrentBlockType, } = props; const { value } = properties; + const { pageData, setPageData } = useContext(PageContext); + const [currentValue, setCurrentValue] = useState(value || ''); const [isEditing, setIsEditing] = useState(false); diff --git a/web/src/components/blocks/TextBlock.tsx b/web/src/components/blocks/TextBlock.tsx index 97e925f5..e212d680 100644 --- a/web/src/components/blocks/TextBlock.tsx +++ b/web/src/components/blocks/TextBlock.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { isCaretAtTop, isCaretAtBottom } from '../../lib/helpers/caretHelpers'; import { editBlock, addBlockAtIndex, removeBlock } from '../../lib/pages/updatePage'; @@ -7,6 +7,7 @@ import textKeybinds from '../../lib/textKeybinds'; import type { EditableText } from '../../lib/types/blockTypes'; import handleKeyDown from '../../lib/blockNavigation/handleKeyDown'; import handleKeyUp from '../../lib/blockNavigation/handleKeyUp'; +import PageContext from '../../contexts/PageContext'; const TextBlock = (props: EditableText) => { const { @@ -15,12 +16,12 @@ const TextBlock = (props: EditableText) => { type, index, blockID, - pageData, - setPageData, setCurrentBlockType, } = props; const { value } = properties; + const { pageData, setPageData } = useContext(PageContext); + const handlePotentialTypeChange = async (element: HTMLSpanElement) => { textKeybinds.forEach(async (bind) => { const regexSearch = bind.keybind.exec(element.textContent || ''); diff --git a/web/src/components/blocks/Title.tsx b/web/src/components/blocks/Title.tsx index 276cea67..6ed855d1 100644 --- a/web/src/components/blocks/Title.tsx +++ b/web/src/components/blocks/Title.tsx @@ -1,5 +1,5 @@ /* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role */ -import React from 'react'; +import React, { useContext } from 'react'; import { useDrop } from 'react-dnd'; import TitleBreaker from './TitleBreaker'; @@ -8,6 +8,7 @@ import type { PermanentEditableText } from '../../lib/types/blockTypes'; import editStyle from '../../lib/pages/editStyle'; import { isCaretAtBottom, isCaretAtTop } from '../../lib/helpers/caretHelpers'; import handleKeyDown from '../../lib/blockNavigation/handleKeyDown'; +import PageContext from '../../contexts/PageContext'; interface TitleProps extends PermanentEditableText { title: string, @@ -18,10 +19,10 @@ const Title = (props: TitleProps) => { page, index, title, - pageData, - setPageData, } = props; + const { pageData, setPageData } = useContext(PageContext); + const onTitleChanged = (text: string) => { editStyle({ name: text }, page); }; diff --git a/web/src/components/pageCustomization/ShareButton.tsx b/web/src/components/pageCustomization/ShareButton.tsx index 56bbc5f5..46a937b9 100644 --- a/web/src/components/pageCustomization/ShareButton.tsx +++ b/web/src/components/pageCustomization/ShareButton.tsx @@ -1,17 +1,16 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, useContext } from 'react'; import { useRouter } from 'next/router'; import type { Permissions, UserPermissions } from '../../lib/types/pageTypes'; import ShareMenu from '../menus/ShareMenu'; import PagePermissionsContext from '../../contexts/PagePermissionsContext'; +import PageContext from '../../contexts/PageContext'; -interface ShareButtonProps { - pagePermissions?: Permissions - permissionsOnPage?: UserPermissions -} - -const ShareButton = (props: ShareButtonProps) => { - const { pagePermissions, permissionsOnPage } = props; +const ShareButton = () => { + const { + userPermissions: permissionsOnPage, + permissions: pagePermissions + } = useContext(PageContext).pageData || {}; const { page } = useRouter().query; const [isMenuOpen, setIsMenuOpen] = useState(false); const buttonRef = useRef<HTMLDivElement>(null); diff --git a/web/src/contexts/PageContext.ts b/web/src/contexts/PageContext.ts new file mode 100644 index 00000000..4a2bbfb4 --- /dev/null +++ b/web/src/contexts/PageContext.ts @@ -0,0 +1,14 @@ +import { createContext } from 'react'; + +import PageDataInterface from '../lib/types/pageTypes'; + +interface PageContextProps { + pageData?: PageDataInterface['message'], + setPageData: React.Dispatch<React.SetStateAction<PageDataInterface['message']>>, +} + +const PageContext = createContext<PageContextProps>({ + setPageData: (_) => {}, +}); + +export default PageContext; diff --git a/web/src/contexts/PagePermissionsContext.tsx b/web/src/contexts/PagePermissionsContext.ts similarity index 100% rename from web/src/contexts/PagePermissionsContext.tsx rename to web/src/contexts/PagePermissionsContext.ts diff --git a/web/src/lib/blockNavigation/handleKeyDown.ts b/web/src/lib/blockNavigation/handleKeyDown.ts index fc9fbb1c..292bb51d 100644 --- a/web/src/lib/blockNavigation/handleKeyDown.ts +++ b/web/src/lib/blockNavigation/handleKeyDown.ts @@ -4,7 +4,7 @@ import type PageDataInterface from '../types/pageTypes'; const handleKeyDown = ( e: React.KeyboardEvent<HTMLSpanElement>, index: number, - pageData: PageDataInterface, + pageData: PageDataInterface['message'], ) => { e.preventDefault(); diff --git a/web/src/lib/blockNavigation/handleKeyUp.ts b/web/src/lib/blockNavigation/handleKeyUp.ts index 542c778c..016ada2f 100644 --- a/web/src/lib/blockNavigation/handleKeyUp.ts +++ b/web/src/lib/blockNavigation/handleKeyUp.ts @@ -4,7 +4,7 @@ import { focusBlockAtIndexRelativeToBottom } from '../helpers/focusHelpers'; const handleKeyUp = ( e: React.KeyboardEvent<HTMLSpanElement>, index: number, - pageData: PageDataInterface, + pageData: PageDataInterface['message'], ) => { e.preventDefault(); diff --git a/web/src/lib/helpers/focusHelpers.ts b/web/src/lib/helpers/focusHelpers.ts index 904ee11e..e3d792e5 100644 --- a/web/src/lib/helpers/focusHelpers.ts +++ b/web/src/lib/helpers/focusHelpers.ts @@ -51,7 +51,7 @@ const getClosestTextNode = (node: Node): Node[] => { const getNextEditableBlock = ( index: number, - pageData: PageDataInterface, + pageData: PageDataInterface['message'], direction: 'up' | 'down' = 'up' ): HTMLElement | undefined => { if (direction === 'up') { @@ -59,7 +59,7 @@ const getNextEditableBlock = ( while (index > 0) { index -= 1; - const block = document.getElementById(pageData.message!.data[index]._id) + const block = document.getElementById(pageData!.data[index]._id) if (block?.getAttribute('contenteditable') === 'true') { return block; } @@ -73,10 +73,10 @@ const getNextEditableBlock = ( } // ~ Find the next editable block - while (index < pageData.message!.data.length - 1) { + while (index < pageData!.data.length - 1) { index += 1; - const block = document.getElementById(pageData.message!.data[index]._id) + const block = document.getElementById(pageData!.data[index]._id) if (block?.getAttribute('contenteditable') === 'true') { return block; } @@ -85,7 +85,7 @@ const getNextEditableBlock = ( const focusBlockAtIndex = ( index: number, - pageData: PageDataInterface, + pageData: PageDataInterface['message'], ) => { const block = getNextEditableBlock(index, pageData); if (!block) return; @@ -97,7 +97,7 @@ const focusBlockAtIndex = ( const focusBlockAtIndexRelativeToTop = ( index: number, - pageData: PageDataInterface, + pageData: PageDataInterface['message'], position: number, ) => { const block = getNextEditableBlock(index, pageData, 'down'); @@ -111,7 +111,7 @@ const focusBlockAtIndexRelativeToTop = ( const focusBlockAtIndexRelativeToBottom = ( index: number, - pageData: PageDataInterface, + pageData: PageDataInterface['message'], position: number, ) => { const block = getNextEditableBlock(index, pageData); diff --git a/web/src/lib/pages/updatePage.ts b/web/src/lib/pages/updatePage.ts index 0e06413c..34868339 100644 --- a/web/src/lib/pages/updatePage.ts +++ b/web/src/lib/pages/updatePage.ts @@ -9,8 +9,8 @@ import SaveManager from '../classes/SaveManager'; const addBlockAtIndex = async ( index: number, page: string, - pageData: PageDataInterface, - setPageData: (value: Record<string, unknown>) => void, + pageData: PageDataInterface['message'], + setPageData: React.Dispatch<React.SetStateAction<PageDataInterface['message']>>, blockIDs?: string[], blockType?: string, blockProperties?: Record<string, unknown>, @@ -32,8 +32,9 @@ const addBlockAtIndex = async ( ); // ~ Add the block to the page - const tempPageData = pageData as PageDataInterface; - tempPageData.message!.data.splice(index, 0, { + const tempPageData = pageData; + + tempPageData!.data.splice(index, 0, { _id: objectID, blockType: blockType || 'text', properties: blockProperties || { @@ -44,13 +45,10 @@ const addBlockAtIndex = async ( // ~ Update the page setPageData({ - status: 'Success', - message: { - style: tempPageData.message!.style, - data: [...tempPageData.message!.data], - userPermissions: tempPageData.message!.userPermissions, - permissions: tempPageData.message!.permissions, - }, + style: tempPageData!.style, + data: [...tempPageData!.data], + userPermissions: tempPageData!.userPermissions, + permissions: tempPageData!.permissions, }); // ~ Wait for page to update before adding the block @@ -62,8 +60,8 @@ const removeBlock = async ( index: number, blockIDs: string[], page: string, - pageData: PageDataInterface, - setPageData: (value: Record<string, unknown>) => void, + pageData: PageDataInterface['message'], + setPageData: React.Dispatch<React.SetStateAction<PageDataInterface['message']>>, moveFocusToPreviousBlock = false, ) => { // ~ Save the change to the server @@ -76,18 +74,15 @@ const removeBlock = async ( ); // ~ Remove the block from the page - const tempPageData = pageData as PageDataInterface; - tempPageData.message!.data.splice(index, 1); + const tempPageData = pageData; + tempPageData!.data.splice(index, 1); // ~ Update the page setPageData({ - status: 'Success', - message: { - style: tempPageData.message!.style, - data: [...tempPageData.message!.data], - userPermissions: tempPageData.message!.userPermissions, - permissions: tempPageData.message!.permissions, - }, + style: tempPageData!.style, + data: [...tempPageData!.data], + userPermissions: tempPageData!.userPermissions, + permissions: tempPageData!.permissions, }); if (!moveFocusToPreviousBlock) return; @@ -122,8 +117,8 @@ const moveBlock = async ( currentIndex: number, newIndex: number, page: string, - pageData: PageDataInterface, - setPageData: (value: Record<string, unknown>) => void, + pageData: PageDataInterface['message'], + setPageData: React.Dispatch<React.SetStateAction<PageDataInterface['message']>>, ) => { // ~ Figure out if the block is being moved up or down const offset = currentIndex > newIndex ? 1 : 0; @@ -145,17 +140,17 @@ const moveBlock = async ( 'doc-ids': blockIDs.length > 1 ? blockIDs : undefined, 'new-block-index': newIndex + offset, 'new-block-id': blockIDs[blockIDs.length - 1], - 'new-block-type': pageData.message!.data[currentIndex].blockType, - 'new-block-properties': pageData.message!.data[currentIndex].properties, + 'new-block-type': pageData!.data[currentIndex].blockType, + 'new-block-properties': pageData!.data[currentIndex].properties, }, page, ); // -=- Update page data -=- - const pageDataCopy = { ...pageData }; + const pageDataCopy = { ...pageData! }; - pageDataCopy.message!.data.splice(newIndex + 1, 0, pageData.message!.data[currentIndex]); - pageDataCopy.message!.data.splice(currentIndex + offset, 1); + pageDataCopy.data.splice(newIndex + 1, 0, pageData!.data[currentIndex]); + pageDataCopy.data.splice(currentIndex + offset, 1); setPageData(pageDataCopy); }; diff --git a/web/src/lib/types/blockTypes.d.ts b/web/src/lib/types/blockTypes.d.ts index b575a511..f47b2e05 100644 --- a/web/src/lib/types/blockTypes.d.ts +++ b/web/src/lib/types/blockTypes.d.ts @@ -10,8 +10,6 @@ interface BaseBlockProps { children: unknown[] page: string, index: number, - pageData: PageDataInterface, - setPageData: Dispatch<SetStateAction<PageDataInterface | Record<string, unknown>>>, isMenuOpen: boolean, setIsMenuOpen: Dispatch<SetStateAction<boolean>>, } @@ -24,8 +22,6 @@ interface PermanentBlock { // -=- Used for for blocks that can't be deleted but can be edited -=- interface PermanentEditableText extends PermanentBlock { index: number, - pageData: PageDataInterface, - setPageData: Dispatch<SetStateAction<PageDataInterface | Record<string, unknown>>>, } // -=- Used for p through h1 -=- diff --git a/web/src/pages/note-rack/[page].tsx b/web/src/pages/note-rack/[page].tsx index 96307553..9b0eaec4 100644 --- a/web/src/pages/note-rack/[page].tsx +++ b/web/src/pages/note-rack/[page].tsx @@ -11,9 +11,10 @@ import Editor from '../../components/Editor'; import LoadingPage from '../../components/LoadingPage'; import SaveManager from '../../lib/classes/SaveManager'; import ShareButton from '../../components/pageCustomization/ShareButton'; +import PageContext from '../../contexts/PageContext'; const NoteRackPage = (props: {pageDataReq: Promise<PageDataInterface>}) => { - const [pageData, setPageData] = useState<PageDataInterface | Record<string, unknown>>({}); + const [pageData, setPageData] = useState<PageDataInterface['message']>(); const { pageDataReq } = props; // TODO:EROXL: Add error handling here... @@ -21,7 +22,7 @@ const NoteRackPage = (props: {pageDataReq: Promise<PageDataInterface>}) => { // -=- Setup Page Data -=- // ~ Get the page data (async () => { - setPageData(await pageDataReq); + setPageData((await pageDataReq).message); })(); // -=- Setup Auto Saving -=- @@ -33,20 +34,20 @@ const NoteRackPage = (props: {pageDataReq: Promise<PageDataInterface>}) => { <> <Head> { - !pageData.message + !pageData ? ( <title>Loading... ) : ( <> - {(pageData as PageDataInterface).message!.style.name} + {pageData.style.name} - ${(pageData as PageDataInterface).message!.style.icon} + ${pageData.style.icon} `} @@ -56,25 +57,29 @@ const NoteRackPage = (props: {pageDataReq: Promise}) => { ) } -
-
-
- - + +
+
+
+ + +
+ + + { + !pageData + ? + : + } +
- - - { - !pageData.message - ? - : - } - -
+ ); }; From 6de5b9d40db3696db0e714f12d7ab632a8989c99 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sat, 18 Mar 2023 19:13:33 -0700 Subject: [PATCH 47/54] Don't allow non editor users to edit --- web/src/components/Editor.tsx | 5 +++- web/src/components/blocks/BaseBlock.tsx | 16 +++++++++---- web/src/components/blocks/Icon.tsx | 24 +++++++++++++++---- web/src/components/blocks/MathBlock.tsx | 8 ++++++- web/src/components/blocks/TextBlock.tsx | 6 ++++- web/src/components/blocks/Title.tsx | 6 ++++- .../pageCustomization/PageThumbnail.tsx | 23 +++++++++++------- 7 files changed, 66 insertions(+), 22 deletions(-) diff --git a/web/src/components/Editor.tsx b/web/src/components/Editor.tsx index a1962fda..61194521 100644 --- a/web/src/components/Editor.tsx +++ b/web/src/components/Editor.tsx @@ -20,12 +20,15 @@ const Editor = () => { const { pageData, setPageData } = useContext(PageContext); const [isMenuOpen, setIsMenuOpen] = useState(false); + const isAllowedToEdit = pageData?.userPermissions.write; + // -=- Setup Page Data -=- // ~ Get the page ID const { page } = useRouter().query; // -=- Setup Selection -=- const selectionData = useSelectionCollector('blocks'); + useEffect(() => { const handleSelectionEvents = (event: KeyboardEvent) => { @@ -67,7 +70,7 @@ const Editor = () => { // -=- Render -=- return (
diff --git a/web/src/components/blocks/BaseBlock.tsx b/web/src/components/blocks/BaseBlock.tsx index b8e540de..f5e795a7 100644 --- a/web/src/components/blocks/BaseBlock.tsx +++ b/web/src/components/blocks/BaseBlock.tsx @@ -24,6 +24,8 @@ const BaseBlock = (props: BaseBlockProps) => { const [currentBlockType, setCurrentBlockType] = useState(blockType); const { pageData, setPageData } = useContext(PageContext); + const isAllowedToEdit = pageData?.userPermissions.write; + // -=- Setup Selection -=- const [selected, selectableRef] = useSelectable( 'blocks', @@ -84,11 +86,15 @@ const BaseBlock = (props: BaseBlockProps) => { }, 'blocks'); }} > - + { + isAllowedToEdit && ( + + ) + } { React.createElement( BlockTypes[currentBlockType] ?? BlockTypes.text, diff --git a/web/src/components/blocks/Icon.tsx b/web/src/components/blocks/Icon.tsx index 5f081ebc..b9299c2a 100644 --- a/web/src/components/blocks/Icon.tsx +++ b/web/src/components/blocks/Icon.tsx @@ -1,9 +1,10 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useContext } from 'react'; import 'emoji-mart/css/emoji-mart.css'; import { Picker, BaseEmoji } from 'emoji-mart'; import type { PermanentBlock } from '../../lib/types/blockTypes'; import editStyle from '../../lib/pages/editStyle'; +import PageContext from '../../contexts/PageContext'; interface IconProps extends PermanentBlock { icon: string, @@ -14,6 +15,9 @@ const Icon = (props: IconProps) => { const [currentIcon, setCurrentIcon] = useState(icon || '📝'); const [isEmojiSelectorActive, setIsEmojiSelectorActive] = useState(false); + const { pageData } = useContext(PageContext); + + const isAllowedToEdit = pageData?.userPermissions.write; const emojiPickerMenuRef = useRef(null); @@ -42,10 +46,20 @@ const Icon = (props: IconProps) => { return (
- - + + {isAllowedToEdit + ? ( + + ) + : ( + + {currentIcon} + + )}
diff --git a/web/src/components/blocks/MathBlock.tsx b/web/src/components/blocks/MathBlock.tsx index e2cf0738..f67962f2 100644 --- a/web/src/components/blocks/MathBlock.tsx +++ b/web/src/components/blocks/MathBlock.tsx @@ -18,6 +18,8 @@ const MathBlock = (props: EditableText) => { const { pageData, setPageData } = useContext(PageContext); + const isAllowedToEdit = pageData!.userPermissions.write; + const [currentValue, setCurrentValue] = useState(value || ''); const [isEditing, setIsEditing] = useState(false); @@ -26,6 +28,8 @@ const MathBlock = (props: EditableText) => { }, [value]); const switchToEditing = () => { + if (!isAllowedToEdit) return; + setIsEditing(true); setTimeout(() => { @@ -51,7 +55,7 @@ const MathBlock = (props: EditableText) => { className="min-h-[1.2em] w-full h-full flex items-center justify-center" onClick={(isEditing || !currentValue) ? undefined : switchToEditing} id={`${blockID}-container`} - role={(isEditing || !currentValue) ? 'textbox' : 'button'} + role={(isEditing || !currentValue || !isAllowedToEdit) ? 'textbox' : 'button'} tabIndex={0} > {isEditing @@ -66,6 +70,8 @@ const MathBlock = (props: EditableText) => { key={blockID} onBlur={ (e) => { + if (!isAllowedToEdit) return; + setCurrentValue(e.currentTarget.innerText); editBlock([blockID], undefined, { value: e.currentTarget.innerText }, page); setIsEditing(false); diff --git a/web/src/components/blocks/TextBlock.tsx b/web/src/components/blocks/TextBlock.tsx index e212d680..364f48d1 100644 --- a/web/src/components/blocks/TextBlock.tsx +++ b/web/src/components/blocks/TextBlock.tsx @@ -22,6 +22,8 @@ const TextBlock = (props: EditableText) => { const { pageData, setPageData } = useContext(PageContext); + const isAllowedToEdit = pageData!.userPermissions.write; + const handlePotentialTypeChange = async (element: HTMLSpanElement) => { textKeybinds.forEach(async (bind) => { const regexSearch = bind.keybind.exec(element.textContent || ''); @@ -54,7 +56,7 @@ const TextBlock = (props: EditableText) => { className={`min-h-[1.2em] outline-none whitespace-pre-wrap w-full ${TextStyles[type]}`} role="textbox" tabIndex={0} - contentEditable + contentEditable={isAllowedToEdit} suppressContentEditableWarning id={blockID} onInput={(e) => { @@ -62,6 +64,8 @@ const TextBlock = (props: EditableText) => { }} onBlur={ (e) => { + if (!isAllowedToEdit) return; + editBlock([blockID], undefined, { value: e.currentTarget.innerText }, page); } } diff --git a/web/src/components/blocks/Title.tsx b/web/src/components/blocks/Title.tsx index 6ed855d1..6f0ce625 100644 --- a/web/src/components/blocks/Title.tsx +++ b/web/src/components/blocks/Title.tsx @@ -23,7 +23,11 @@ const Title = (props: TitleProps) => { const { pageData, setPageData } = useContext(PageContext); + const isAllowedToEdit = pageData?.userPermissions.write; + const onTitleChanged = (text: string) => { + if (!isAllowedToEdit) return; + editStyle({ name: text }, page); }; @@ -55,7 +59,7 @@ const Title = (props: TitleProps) => {
{ const [isColourMenuActive, setIsColourMenuActive] = useState(false); const [activeColour, setActiveColour] = useState({}); + const { pageData } = useContext(PageContext); + + const isAllowedToEdit = pageData?.userPermissions.write; + useEffect(() => { setActiveColour({ '--forced-background-colour': `#${colour.r.toString(16).padStart(2, '0')}${colour.g.toString(16).padStart(2, '0')}${colour.b.toString(16).padStart(2, '0')}`, @@ -44,13 +49,15 @@ const PageThumbnail = (props: PageThumbnailProps) => {
}>
- + {isAllowedToEdit && ( + + )}
Date: Sat, 18 Mar 2023 19:22:53 -0700 Subject: [PATCH 48/54] Clean up UI if user is not logged in --- web/src/components/Editor.tsx | 6 +++++- web/src/pages/note-rack/[page].tsx | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/web/src/components/Editor.tsx b/web/src/components/Editor.tsx index 61194521..b90c3e8b 100644 --- a/web/src/components/Editor.tsx +++ b/web/src/components/Editor.tsx @@ -7,6 +7,7 @@ import React, { } from 'react'; import { useRouter } from 'next/router'; import { Selectable, useSelectionCollector } from 'react-virtual-selection'; +import { useSessionContext } from 'supertokens-auth-react/recipe/session'; import BaseBlock from './blocks/BaseBlock'; import PageThumbnail from './pageCustomization/PageThumbnail'; @@ -21,6 +22,9 @@ const Editor = () => { const [isMenuOpen, setIsMenuOpen] = useState(false); const isAllowedToEdit = pageData?.userPermissions.write; + const session = useSessionContext(); + + const isLoggedIn = session?.loading === false && session?.doesSessionExist === true; // -=- Setup Page Data -=- // ~ Get the page ID @@ -73,7 +77,7 @@ const Editor = () => { accepts={isAllowedToEdit ? "blocks" : ""} selectionClassName="bg-sky-300 opacity-20" > -
+
diff --git a/web/src/pages/note-rack/[page].tsx b/web/src/pages/note-rack/[page].tsx index 9b0eaec4..3e16ef9c 100644 --- a/web/src/pages/note-rack/[page].tsx +++ b/web/src/pages/note-rack/[page].tsx @@ -3,6 +3,7 @@ import { GetServerSidePropsContext } from 'next'; import Head from 'next/head'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; +import { useSessionContext } from 'supertokens-auth-react/recipe/session'; import PagePath from '../../components/pageInfo/PagePath'; import type PageDataInterface from '../../lib/types/pageTypes'; @@ -17,6 +18,10 @@ const NoteRackPage = (props: {pageDataReq: Promise}) => { const [pageData, setPageData] = useState(); const { pageDataReq } = props; + const session = useSessionContext(); + + const isLoggedIn = session?.loading === false && session?.doesSessionExist === true; + // TODO:EROXL: Add error handling here... useEffect(() => { // -=- Setup Page Data -=- @@ -66,11 +71,17 @@ const NoteRackPage = (props: {pageDataReq: Promise}) => {
- - + {isLoggedIn && ( + <> + + + + )}
- + {isLoggedIn && ( + + )} { !pageData From 7b4f5fb01a0ba7430fa70068b0e6b1358b59bfdb Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sat, 18 Mar 2023 19:25:50 -0700 Subject: [PATCH 49/54] Bump react virtual selection version to current --- web/package.json | 2 +- web/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/package.json b/web/package.json index 9a0f0555..00f6bec5 100644 --- a/web/package.json +++ b/web/package.json @@ -17,7 +17,7 @@ "react-dnd": "^15.1.2", "react-dnd-html5-backend": "^15.1.3", "react-dom": "17.0.2", - "react-virtual-selection": "^1.0.17", + "react-virtual-selection": "^1.0.25", "supertokens-auth-react": "^0.28.1" }, "devDependencies": { diff --git a/web/yarn.lock b/web/yarn.lock index 29c65bd2..690527ac 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -4436,10 +4436,10 @@ react-use@^15.3.3: ts-easing "^0.2.0" tslib "^2.0.0" -react-virtual-selection@^1.0.17: - version "1.0.21" - resolved "https://registry.yarnpkg.com/react-virtual-selection/-/react-virtual-selection-1.0.21.tgz#99ac69120778555dca8594267a332e3c5e6ca0e7" - integrity sha512-m4HMmaqt7isrO8I4OROOclJD8Tgl3ECTt/+qRNcjUaiJrcpAFSftXCcrMvEAdyJ18bCtdqzNeKIb9I2CXOnNsQ== +react-virtual-selection@^1.0.25: + version "1.0.25" + resolved "https://registry.yarnpkg.com/react-virtual-selection/-/react-virtual-selection-1.0.25.tgz#50882f493151ad74385780eb7611a88c3aba2efb" + integrity sha512-OKQ1gtxy2pQRCcBqg7oEDhVPTlMjHvFNswhqAJ4+O4FhGtEdie8aLRJspCLk4o9bdCV7jZoNFT0VWcVTzvTmRA== react@17.0.2: version "17.0.2" From 7372e25a2af757c0a3e6d1a931471601e6bf4d72 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sat, 18 Mar 2023 20:35:02 -0700 Subject: [PATCH 50/54] Fix issues with user permissions state --- web/src/components/menus/ShareMenu.tsx | 1 - web/src/components/menus/UserPermission.tsx | 41 +++++++++++++------ .../pageCustomization/ShareButton.tsx | 1 - web/src/contexts/PagePermissionsContext.ts | 1 - 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx index e57ab6d3..fec16ada 100644 --- a/web/src/components/menus/ShareMenu.tsx +++ b/web/src/components/menus/ShareMenu.tsx @@ -3,7 +3,6 @@ import Image from 'next/image'; import EmailShareMenu from './EmailShareMenu'; import Globe from '../../public/Globe.svg'; -import type { Permissions } from '../../lib/types/pageTypes'; import UserPermission from './UserPermission'; import PagePermissionsContext from '../../contexts/PagePermissionsContext'; diff --git a/web/src/components/menus/UserPermission.tsx b/web/src/components/menus/UserPermission.tsx index 93478c5c..e3dec676 100644 --- a/web/src/components/menus/UserPermission.tsx +++ b/web/src/components/menus/UserPermission.tsx @@ -20,26 +20,25 @@ const UserPermission = (props: UserPermissionProps) => { const page = useRouter().query.page as string; const { - pagePermissions, - permissionsOnPage: permissions, + currentPermissions, setCurrentPermissions, } = useContext(PagePermissionsContext); useEffect(() => { - const defaultDropdownOption = Object.keys(dropdownInfo).find((key) => { - if (!permissions) { - return false; - } + const permissions = Object.entries(currentPermissions).find(([, value]) => value.email === email)?.[1]; - let keyPermissions = dropdownInfo[+key].permissions; + const defaultDropdownOption = Object.keys(dropdownInfo).find((uuid) => { + let keyPermissions = dropdownInfo[+uuid].permissions; return Object.entries(keyPermissions).every(([key, value]) => { + if (!permissions) return false; + return permissions[key as keyof UserPermissions] === value; }); }) as (DropdownOptions | undefined) || DropdownOptions.ViewOnly; setSelectedDropdownOption(defaultDropdownOption); - }, [permissions]); + }, [currentPermissions]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -59,15 +58,33 @@ const UserPermission = (props: UserPermissionProps) => { }); const setSelectDropdownOption = (option: DropdownOptions) => { - let key = Object.keys(pagePermissions!).find((key) => pagePermissions![key].email === email) || email; + let key = Object.keys(currentPermissions).find((key) => currentPermissions[key].email === email) || email; setCurrentPermissions({ - ...pagePermissions, + ...currentPermissions, [key]: { ...dropdownInfo[option].permissions, email: email, } - } as Permissions) + } as Permissions); + + (async () => { + await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/page/update-permissions/${page}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email, + permissions: dropdownInfo[option].permissions + }), + } + ); + })(); + + setSelectedDropdownOption(option); } return ( @@ -114,7 +131,7 @@ const UserPermission = (props: UserPermissionProps) => {

{ setCurrentPermissions({ - ...Object.fromEntries(Object.entries(pagePermissions!).filter(([_, value]) => value.email !== email)) + ...Object.fromEntries(Object.entries(currentPermissions).filter(([_, value]) => value.email !== email)) } as Permissions); (async () => { diff --git a/web/src/components/pageCustomization/ShareButton.tsx b/web/src/components/pageCustomization/ShareButton.tsx index 46a937b9..6e6249bf 100644 --- a/web/src/components/pageCustomization/ShareButton.tsx +++ b/web/src/components/pageCustomization/ShareButton.tsx @@ -35,7 +35,6 @@ const ShareButton = () => { void, From 6eb96d37030f7c73fa4eb053afc91382252fba91 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sat, 18 Mar 2023 20:42:13 -0700 Subject: [PATCH 51/54] Update permissions to use write instead of edit in ShareOptions --- web/src/lib/constants/ShareOptions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/src/lib/constants/ShareOptions.ts b/web/src/lib/constants/ShareOptions.ts index 055ded61..8c96f974 100644 --- a/web/src/lib/constants/ShareOptions.ts +++ b/web/src/lib/constants/ShareOptions.ts @@ -10,7 +10,7 @@ const dropdownInfo: { description: string, permissions: { admin: boolean, - edit: boolean, + write: boolean, read: boolean, } } @@ -20,7 +20,7 @@ const dropdownInfo: { description: 'Can edit, delete, and share', permissions: { admin: true, - edit: true, + write: true, read: true, } }, @@ -29,7 +29,7 @@ const dropdownInfo: { description: 'Can edit, but not delete or share', permissions: { admin: false, - edit: true, + write: true, read: true, }, }, @@ -38,7 +38,7 @@ const dropdownInfo: { description: 'Cannot edit, delete, or share', permissions: { admin: false, - edit: false, + write: false, read: true, } }, From e592e6f2f80285f00c2b6e1b41b04842b5a4476d Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sat, 18 Mar 2023 20:43:24 -0700 Subject: [PATCH 52/54] Change write to admin in Icon and Title components. --- web/src/components/blocks/Icon.tsx | 2 +- web/src/components/blocks/Title.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/components/blocks/Icon.tsx b/web/src/components/blocks/Icon.tsx index b9299c2a..c128de93 100644 --- a/web/src/components/blocks/Icon.tsx +++ b/web/src/components/blocks/Icon.tsx @@ -17,7 +17,7 @@ const Icon = (props: IconProps) => { const [isEmojiSelectorActive, setIsEmojiSelectorActive] = useState(false); const { pageData } = useContext(PageContext); - const isAllowedToEdit = pageData?.userPermissions.write; + const isAllowedToEdit = pageData?.userPermissions.admin; const emojiPickerMenuRef = useRef(null); diff --git a/web/src/components/blocks/Title.tsx b/web/src/components/blocks/Title.tsx index 6f0ce625..f91c2360 100644 --- a/web/src/components/blocks/Title.tsx +++ b/web/src/components/blocks/Title.tsx @@ -23,7 +23,7 @@ const Title = (props: TitleProps) => { const { pageData, setPageData } = useContext(PageContext); - const isAllowedToEdit = pageData?.userPermissions.write; + const isAllowedToEdit = pageData?.userPermissions.admin; const onTitleChanged = (text: string) => { if (!isAllowedToEdit) return; From 2454bd2e7b8be22493dc1fc2e0acc34901731a31 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sat, 18 Mar 2023 20:44:38 -0700 Subject: [PATCH 53/54] Fix the incorrect user permission check in PageThumbnail component. --- web/src/components/pageCustomization/PageThumbnail.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/pageCustomization/PageThumbnail.tsx b/web/src/components/pageCustomization/PageThumbnail.tsx index 8c1e4eb3..8812be70 100644 --- a/web/src/components/pageCustomization/PageThumbnail.tsx +++ b/web/src/components/pageCustomization/PageThumbnail.tsx @@ -20,7 +20,7 @@ const PageThumbnail = (props: PageThumbnailProps) => { const { pageData } = useContext(PageContext); - const isAllowedToEdit = pageData?.userPermissions.write; + const isAllowedToEdit = pageData?.userPermissions.admin; useEffect(() => { setActiveColour({ From e63a27d66511dec4d3c76a3b95d871c62f477403 Mon Sep 17 00:00:00 2001 From: Eroxl Date: Sun, 19 Mar 2023 09:37:04 -0700 Subject: [PATCH 54/54] Stop 0 perm users from being displayed --- web/src/components/menus/ShareMenu.tsx | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/web/src/components/menus/ShareMenu.tsx b/web/src/components/menus/ShareMenu.tsx index fec16ada..38d5b4a4 100644 --- a/web/src/components/menus/ShareMenu.tsx +++ b/web/src/components/menus/ShareMenu.tsx @@ -97,18 +97,27 @@ const ShareMenu = (props: ShareMenuProps) => { { ( currentPermissions - && Object.keys(currentPermissions).filter((key) => key !== '*').length > 0 + && Object.values(currentPermissions).filter( + (permission) => { + const hasAnyPermissions = permission.read || permission.write || permission.admin; + return permission.email !== '*' && hasAnyPermissions; + } + ).length > 0 && !isPagePublic ) && (

Shared with { - Object.values(currentPermissions).map((permission) => ( - permission.email !== '*' && - )) + Object.values(currentPermissions) + .filter((permission) => permission.read || permission.write || permission.admin) + .map((permission) => ( + permission.email !== '*' && ( + + ) + )) }
)