From 095d13f8bbfe543fcf086840e1a985447a6c51ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Tue, 11 Oct 2022 15:22:34 +0200 Subject: [PATCH] feat: add listin worspaces and workspace users --- .pnp.cjs | 97 +++++++++------- ...-api-npm-1.15.0-12a67ff9b7-88ae0a340e.zip} | Bin 72491 -> 80066 bytes ...ommon-npm-1.39.0-1e36e2ef74-92cfad04a6.zip | Bin 0 -> 41393 bytes ...tion-npm-1.17.0-587d631df2-587516dfed.zip} | Bin 57529 -> 57524 bytes ...ures-npm-1.53.0-8ea4a2d559-a856e815a3.zip} | Bin 26275 -> 26274 bytes ...dels-npm-1.26.0-dade8919ab-f595a3de88.zip} | Bin 225824 -> 227483 bytes ...nses-npm-1.11.0-d066ddbbb6-46d6a47980.zip} | Bin 46437 -> 46437 bytes ...tils-npm-1.10.0-0dc2ade40b-c02d54ca8a.zip} | Bin 16984 -> 16984 bytes packages/api-gateway/bin/server.ts | 1 + .../src/Controller/v1/InvitesController.ts | 23 ++++ .../src/Controller/v1/WorkspacesController.ts | 17 ++- packages/auth/package.json | 2 +- packages/workspace/bin/server.ts | 1 + packages/workspace/package.json | 3 +- packages/workspace/src/Bootstrap/Container.ts | 16 +++ packages/workspace/src/Bootstrap/Types.ts | 6 + .../Controller/WorkspacesController.spec.ts | 108 +++++++++++++++++- .../src/Controller/WorkspacesController.ts | 85 ++++++++++++++ .../Domain/Projection/ProjectorInterface.ts | 3 + .../Domain/Projection/WorkspaceProjection.ts | 3 + .../Projection/WorkspaceProjector.spec.ts | 30 +++++ .../Domain/Projection/WorkspaceProjector.ts | 19 +++ .../Projection/WorkspaceUserProjection.ts | 3 + .../Projection/WorkspaceUserProjector.spec.ts | 42 +++++++ .../Projection/WorkspaceUserProjector.ts | 25 ++++ .../ListWorkspaceUsers.spec.ts | 84 ++++++++++++++ .../ListWorkspaceUsers/ListWorkspaceUsers.ts | 50 ++++++++ .../ListWorkspaceUsersDTO.ts | 6 + .../ListWorkspaceUsersResponse.ts | 6 + .../ListWorkspaces/ListWorkspaces.spec.ts | 47 ++++++++ .../UseCase/ListWorkspaces/ListWorkspaces.ts | 40 +++++++ .../ListWorkspaces/ListWorkspacesDTO.ts | 5 + .../ListWorkspaces/ListWorkspacesResponse.ts | 6 + .../Workspace/WorkspaceRepositoryInterface.ts | 3 + .../WorkspaceUserRepositoryInterface.ts | 3 + .../InversifyExpressInvitesController.ts | 23 ++++ .../InversifyExpressWorkspacesController.ts | 21 +++- .../MySQL/MySQLWorkspaceRepository.spec.ts | 18 +++ .../Infra/MySQL/MySQLWorkspaceRepository.ts | 8 ++ .../MySQLWorkspaceUserRepository.spec.ts | 18 +++ .../MySQL/MySQLWorkspaceUserRepository.ts | 8 ++ yarn.lock | 98 +++++++++------- 42 files changed, 835 insertions(+), 93 deletions(-) rename .yarn/cache/{@standardnotes-api-npm-1.12.1-6b8bfe4ccf-8623fc82de.zip => @standardnotes-api-npm-1.15.0-12a67ff9b7-88ae0a340e.zip} (66%) create mode 100644 .yarn/cache/@standardnotes-common-npm-1.39.0-1e36e2ef74-92cfad04a6.zip rename .yarn/cache/{@standardnotes-encryption-npm-1.16.2-9e53125abe-50efc1b201.zip => @standardnotes-encryption-npm-1.17.0-587d631df2-587516dfed.zip} (92%) rename .yarn/cache/{@standardnotes-features-npm-1.52.4-05c59084e4-aea7b48627.zip => @standardnotes-features-npm-1.53.0-8ea4a2d559-a856e815a3.zip} (90%) rename .yarn/cache/{@standardnotes-models-npm-1.24.2-8c2c157efa-17b3cfba39.zip => @standardnotes-models-npm-1.26.0-dade8919ab-f595a3de88.zip} (87%) rename .yarn/cache/{@standardnotes-responses-npm-1.10.6-f636794f47-0583e2cb77.zip => @standardnotes-responses-npm-1.11.0-d066ddbbb6-46d6a47980.zip} (98%) rename .yarn/cache/{@standardnotes-utils-npm-1.9.1-e48d87ffc7-f775bb3744.zip => @standardnotes-utils-npm-1.10.0-0dc2ade40b-c02d54ca8a.zip} (94%) create mode 100644 packages/api-gateway/src/Controller/v1/InvitesController.ts create mode 100644 packages/workspace/src/Domain/Projection/ProjectorInterface.ts create mode 100644 packages/workspace/src/Domain/Projection/WorkspaceProjection.ts create mode 100644 packages/workspace/src/Domain/Projection/WorkspaceProjector.spec.ts create mode 100644 packages/workspace/src/Domain/Projection/WorkspaceProjector.ts create mode 100644 packages/workspace/src/Domain/Projection/WorkspaceUserProjection.ts create mode 100644 packages/workspace/src/Domain/Projection/WorkspaceUserProjector.spec.ts create mode 100644 packages/workspace/src/Domain/Projection/WorkspaceUserProjector.ts create mode 100644 packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers.spec.ts create mode 100644 packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers.ts create mode 100644 packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsersDTO.ts create mode 100644 packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsersResponse.ts create mode 100644 packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspaces.spec.ts create mode 100644 packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspaces.ts create mode 100644 packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspacesDTO.ts create mode 100644 packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspacesResponse.ts create mode 100644 packages/workspace/src/Infra/InversifyExpressUtils/InversifyExpressInvitesController.ts diff --git a/.pnp.cjs b/.pnp.cjs index 6613566f9..9d125c881 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -2521,16 +2521,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@standardnotes/api", [\ - ["npm:1.12.1", {\ - "packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.12.1-6b8bfe4ccf-8623fc82de.zip/node_modules/@standardnotes/api/",\ + ["npm:1.15.0", {\ + "packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.15.0-12a67ff9b7-88ae0a340e.zip/node_modules/@standardnotes/api/",\ "packageDependencies": [\ - ["@standardnotes/api", "npm:1.12.1"],\ - ["@standardnotes/common", "workspace:packages/common"],\ - ["@standardnotes/encryption", "npm:1.16.2"],\ - ["@standardnotes/models", "npm:1.24.2"],\ - ["@standardnotes/responses", "npm:1.10.6"],\ + ["@standardnotes/api", "npm:1.15.0"],\ + ["@standardnotes/common", "npm:1.39.0"],\ + ["@standardnotes/encryption", "npm:1.17.0"],\ + ["@standardnotes/models", "npm:1.26.0"],\ + ["@standardnotes/responses", "npm:1.11.0"],\ ["@standardnotes/security", "workspace:packages/security"],\ - ["@standardnotes/utils", "npm:1.9.1"],\ + ["@standardnotes/utils", "npm:1.10.0"],\ ["reflect-metadata", "npm:0.1.13"]\ ],\ "linkType": "HARD"\ @@ -2600,7 +2600,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\ ["@sentry/node", "npm:7.5.0"],\ ["@standardnotes/analytics", "workspace:packages/analytics"],\ - ["@standardnotes/api", "npm:1.12.1"],\ + ["@standardnotes/api", "npm:1.15.0"],\ ["@standardnotes/common", "workspace:packages/common"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ @@ -2653,6 +2653,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@standardnotes/common", [\ + ["npm:1.39.0", {\ + "packageLocation": "./.yarn/cache/@standardnotes-common-npm-1.39.0-1e36e2ef74-92cfad04a6.zip/node_modules/@standardnotes/common/",\ + "packageDependencies": [\ + ["@standardnotes/common", "npm:1.39.0"],\ + ["reflect-metadata", "npm:0.1.13"]\ + ],\ + "linkType": "HARD"\ + }],\ ["workspace:packages/common", {\ "packageLocation": "./packages/common/",\ "packageDependencies": [\ @@ -2725,15 +2733,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@standardnotes/encryption", [\ - ["npm:1.16.2", {\ - "packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.16.2-9e53125abe-50efc1b201.zip/node_modules/@standardnotes/encryption/",\ + ["npm:1.17.0", {\ + "packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.17.0-587d631df2-587516dfed.zip/node_modules/@standardnotes/encryption/",\ "packageDependencies": [\ - ["@standardnotes/encryption", "npm:1.16.2"],\ - ["@standardnotes/common", "workspace:packages/common"],\ - ["@standardnotes/models", "npm:1.24.2"],\ - ["@standardnotes/responses", "npm:1.10.6"],\ + ["@standardnotes/encryption", "npm:1.17.0"],\ + ["@standardnotes/common", "npm:1.39.0"],\ + ["@standardnotes/models", "npm:1.26.0"],\ + ["@standardnotes/responses", "npm:1.11.0"],\ ["@standardnotes/sncrypto-common", "npm:1.13.0"],\ - ["@standardnotes/utils", "npm:1.9.1"],\ + ["@standardnotes/utils", "npm:1.10.0"],\ ["reflect-metadata", "npm:0.1.13"]\ ],\ "linkType": "HARD"\ @@ -2791,12 +2799,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ - ["npm:1.52.4", {\ - "packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.52.4-05c59084e4-aea7b48627.zip/node_modules/@standardnotes/features/",\ + ["npm:1.53.0", {\ + "packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.53.0-8ea4a2d559-a856e815a3.zip/node_modules/@standardnotes/features/",\ "packageDependencies": [\ - ["@standardnotes/features", "npm:1.52.4"],\ + ["@standardnotes/features", "npm:1.53.0"],\ ["@standardnotes/auth", "npm:3.19.4"],\ - ["@standardnotes/common", "workspace:packages/common"],\ + ["@standardnotes/common", "npm:1.39.0"],\ ["@standardnotes/security", "workspace:packages/security"],\ ["reflect-metadata", "npm:0.1.13"]\ ],\ @@ -2856,14 +2864,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@standardnotes/models", [\ - ["npm:1.24.2", {\ - "packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.24.2-8c2c157efa-17b3cfba39.zip/node_modules/@standardnotes/models/",\ + ["npm:1.26.0", {\ + "packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.26.0-dade8919ab-f595a3de88.zip/node_modules/@standardnotes/models/",\ "packageDependencies": [\ - ["@standardnotes/models", "npm:1.24.2"],\ - ["@standardnotes/common", "workspace:packages/common"],\ - ["@standardnotes/features", "npm:1.52.4"],\ - ["@standardnotes/responses", "npm:1.10.6"],\ - ["@standardnotes/utils", "npm:1.9.1"],\ + ["@standardnotes/models", "npm:1.26.0"],\ + ["@standardnotes/common", "npm:1.39.0"],\ + ["@standardnotes/features", "npm:1.53.0"],\ + ["@standardnotes/responses", "npm:1.11.0"],\ + ["@standardnotes/utils", "npm:1.10.0"],\ ["lodash", "npm:4.17.21"],\ ["reflect-metadata", "npm:0.1.13"]\ ],\ @@ -2899,12 +2907,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@standardnotes/responses", [\ - ["npm:1.10.6", {\ - "packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.10.6-f636794f47-0583e2cb77.zip/node_modules/@standardnotes/responses/",\ + ["npm:1.11.0", {\ + "packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.11.0-d066ddbbb6-46d6a47980.zip/node_modules/@standardnotes/responses/",\ "packageDependencies": [\ - ["@standardnotes/responses", "npm:1.10.6"],\ - ["@standardnotes/common", "workspace:packages/common"],\ - ["@standardnotes/features", "npm:1.52.4"],\ + ["@standardnotes/responses", "npm:1.11.0"],\ + ["@standardnotes/common", "npm:1.39.0"],\ + ["@standardnotes/features", "npm:1.53.0"],\ ["@standardnotes/security", "workspace:packages/security"],\ ["reflect-metadata", "npm:0.1.13"]\ ],\ @@ -3125,6 +3133,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@standardnotes/utils", [\ + ["npm:1.10.0", {\ + "packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.10.0-0dc2ade40b-c02d54ca8a.zip/node_modules/@standardnotes/utils/",\ + "packageDependencies": [\ + ["@standardnotes/utils", "npm:1.10.0"],\ + ["@standardnotes/common", "npm:1.39.0"],\ + ["dompurify", "npm:2.4.0"],\ + ["lodash", "npm:4.17.21"],\ + ["reflect-metadata", "npm:0.1.13"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:1.6.12", {\ "packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.6.12-8fa8d7d09b-e177b1fa51.zip/node_modules/@standardnotes/utils/",\ "packageDependencies": [\ @@ -3134,17 +3153,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["lodash", "npm:4.17.21"]\ ],\ "linkType": "HARD"\ - }],\ - ["npm:1.9.1", {\ - "packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.9.1-e48d87ffc7-f775bb3744.zip/node_modules/@standardnotes/utils/",\ - "packageDependencies": [\ - ["@standardnotes/utils", "npm:1.9.1"],\ - ["@standardnotes/common", "workspace:packages/common"],\ - ["dompurify", "npm:2.4.0"],\ - ["lodash", "npm:4.17.21"],\ - ["reflect-metadata", "npm:0.1.13"]\ - ],\ - "linkType": "HARD"\ }]\ ]],\ ["@standardnotes/workspace-server", [\ @@ -3154,10 +3162,11 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@standardnotes/workspace-server", "workspace:packages/workspace"],\ ["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\ ["@sentry/node", "npm:7.5.0"],\ - ["@standardnotes/api", "npm:1.12.1"],\ + ["@standardnotes/api", "npm:1.15.0"],\ ["@standardnotes/common", "workspace:packages/common"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ + ["@standardnotes/models", "npm:1.26.0"],\ ["@standardnotes/security", "workspace:packages/security"],\ ["@standardnotes/time", "workspace:packages/time"],\ ["@types/cors", "npm:2.8.12"],\ diff --git a/.yarn/cache/@standardnotes-api-npm-1.12.1-6b8bfe4ccf-8623fc82de.zip b/.yarn/cache/@standardnotes-api-npm-1.15.0-12a67ff9b7-88ae0a340e.zip similarity index 66% rename from .yarn/cache/@standardnotes-api-npm-1.12.1-6b8bfe4ccf-8623fc82de.zip rename to .yarn/cache/@standardnotes-api-npm-1.15.0-12a67ff9b7-88ae0a340e.zip index d19d36327f3f2888c0f139e0b3aff1faa9c5178c..396012680e4645b2a669f75eaf89999bfc2082c6 100644 GIT binary patch delta 11918 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 delta 8484 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 yh%652V{0Qofd}T$t^W{5Z2Ld3U?Nc)2)+OHn diff --git a/.yarn/cache/@standardnotes-common-npm-1.39.0-1e36e2ef74-92cfad04a6.zip b/.yarn/cache/@standardnotes-common-npm-1.39.0-1e36e2ef74-92cfad04a6.zip new file mode 100644 index 0000000000000000000000000000000000000000..85ed6ebe6439d2865c76d4c3517480fb97c3b0b7 GIT binary patch literal 41393 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%0D8^y{P(@$3GgUH(cUM6q(Lxg&AI0I?VJ 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 literal 0 HcmV?d00001 diff --git a/.yarn/cache/@standardnotes-encryption-npm-1.16.2-9e53125abe-50efc1b201.zip b/.yarn/cache/@standardnotes-encryption-npm-1.17.0-587d631df2-587516dfed.zip similarity index 92% rename from .yarn/cache/@standardnotes-encryption-npm-1.16.2-9e53125abe-50efc1b201.zip rename to .yarn/cache/@standardnotes-encryption-npm-1.17.0-587d631df2-587516dfed.zip index 415ba6e7e9c18a187be0a4cbe4e4a80cd307727c..2edd0609581d0157d4aedd2e0fab66f9bb8e39fd 100644 GIT binary patch delta 1159 zcmYjPeK6a180V{q_aybwMn-5=m##l9t*jW5bdEpT4Xt6K9C7V7Z$mj$LwDZRc*`?( zAzI3qcJVTF^hG(lYcnSz2=NlG>PmB0FD>p?YCoCV`u*$qJkR&}JfG+D{r2?hb@uDE z86+50Rk%8nk&ezBSS0xJGpH5OPp{4h;!|BG57k?)hgx@+w_MN&y=iqAR0rrRkCA*&DZU2y4(%Uvxop;K>AYj&kO$j+)`zb?^}m&2x9rl z3V|*SoId_av-K{rGwN`>S@68PVYT5h4}}ogzZ@lgZA(6Q$?3OKOEpZa&(O8%XmV*U zTb9i6d|vw11V335n;CO_N3X=EBq)9}sOU|zx;CZRKq&O6JZsxac;mFHy8ZZ6f3@;7 z(Y;rkLOy?Gz`x*6ig(+ik@AiK-mL4Zro_p*^zamSwJ?3Kl47!!k~Z|C!*aZMDsJNC zrQyU-*+>M*WMtR!dAo`}2#DEgh-n zhD#VF*^N2n8M}+z(T4O+Sy(%D*=NyB?G!tzsd2#++X-os$jw`#mo_4%*F>eA=j!%z zNB+u-03Z8eX_TbQbE;NP*Zhu9kay9~`MzR@Rrrv*7F$!~_yn^KjpcFW#e$s3a-(gYUr@N=D&Hz%KBF*$xua&sl=#d!mWX{%Od~2M1Y# zZYz|@ve0rWkS=?I`F%^^4r54^hW9aHHH`s9GekHbr=Y+LEtMq05)$QfbT4xs8pFK& z464rpwi5^B7RErlb^z=rh>$r#LCR~;PWA`&2!^(^_bCm5_Rtt?CoDm8ooM>MoXP82 z#y-Uvq~e3_Ay04{#nA7$KtdS6=xIc|0qh&WU^hlbgdgy69>Q@FRE)WxBYFQKKje~+ zbFiQ+e@`S4B;yoBE5K71weX~pf=Gp0898$nP@-fa!y>#j7*~TK+D%~J41vudB77_O zP*{jS>rK>&gFv0cP**X&lQ&R5F#^CLQwStK7GNjMVSUIFoKzIuIAE!=k)cRi*j0Uo zva9is1M}5HD4n9C#5!$7UPlJAX*vq6-zOwM?Nb62Ok-%U;lI?*G!rozaTDBNHrxbu zo)IB$21DLWTEnxYRG`cm!$-46(dWPJ*#N9)Yk*k{J=udC;KvZaOpT%Ld-xU+P=$x4 r5^W_>LI%4zI{Kq!-;zL-)~XGMTCKoto{l(e+KYmDXM@A-doT45bJ7AR delta 1123 zcmYjPX;70_6eSOo9g-1QzF4)`4ErZl)*@3@9kgt+NJ+p{8L(O-LQo`yKx6>f!wnz< zrtC`*h%FYe!4hQ@goK2x#IQ{}R;Xx^Ex7zfo?hI*D2)HC%Pwo=yxXDtTlf!`%b z{&jf~-4j5x*6XNAF3M%S=;lrxA5D%Av46Oi+JS{k`8+!uVZug03g;d=eO+#)j?;e1 zn!VZ1wVG}!%J9+l-8X5@_f4NYnWJHG`)P~%GIw9nBMKYSslBd#gXh_ry=YTE{y2d( z^!$AL$>LB+U@3RWYYq3_nEM7Ji^424+zB=;qJnN!5+Lk zldI6}JbllR+UV})JxY%^b{1ijUp zj$6b_F%;J}O1ph@|GtB8=MVj};j13@D=nod()J;*kIX&DjZBN&=t z1Hse{>_>6bm7&yxL*b|cqHusK3r%U4;Y+w4-ZKmdrSzo>rxWC{;LE+xwbHZU47ug2AjK61FST l_#F7NIAV1up%bJHkzn^6j*babL#qWQz@H=T+;>m)d8ubN<$=QkNsd`z(`FXiPr}J(b2<-W- z%^}-8VVchARn6NnE-ui$ut;Modza*ml7y^f#um^j(w5AP?1B~R}xO8@=xNYuNd zcC}~BR8;FVe@&SB&R6HpBX5h0WiIK9Zf(E8cK7~Ym(bN0YD*gHtff~3zvZ0KX(pvr zBPxDNr}fDP7eB#8do8z}YMjoca^TsW%K4vz{Mhb#U6K1My{tgy`HzFmvHRDz+STQz zs`6c4F~#sg+$G@~CX4hRzLSBd~&8UBRENelX(N%>Q%_p*V zSk`R*D6y)i`Fx0{$O%DN#l@0`WH?_jVHam7B12B71Qr#{J%Na zEw;E?vu~4vQ55}C^C6!tQ6y=$s1y;n5LQo zCEO+##ThfXS%8^u<1Cr%EGKW|@|j!`ugAn@1!P)Ijt}9O{1nLD3*q`DI5V|cPZmsc znmk{OXYvPYmdV!=1txz@kYY5NERa~kw8sf7c_Gn&@zi9-q&~)vlP4tgGRe3BO*5XH zQ toUE0q#U$wi6t+UPr(QO{v=XPO9GnjZrY00416aZUgL delta 860 zcmZ2oWm_qA4`(rwFiR(I=<$L9uX_~%(|27=#4jw>$g zX??^eD!=A&RFQJ&^8iO(t{)OnJw$J(Y{y492?WD%H zf3mANRO?0drk?&FCRf+1DzwI6Re9vw;=E?L_xl&_GJavW+kyWe+biGg><>5fPFo$J zI<+)-sjAK7+O{7L%I0!DSjyzN!TQU^=Q~0sEzVmztK*+fFpFQoAMK{U8#1Tdt$kIp zcH8s7odOAqQ&c)Wcl=}C(ra1s=hI{Bd4gTm&C>4Pt2LPGCh`B7-y64}*^Z{4c3t7zrBUF#< z;jq3-4R^)2{5~U>w{O{w$pv*C-~0VR@O7u&_fOX^=Jw`U`dTt^`_B{qQajh>UjDj9 za%O1D-}ZDz9=Dy&Y197I^B7-rxcg)EAzcf-osTCvaw_MTmHjEHzguFn?4{lLoqFN> ziVBScFYZW~<1}j-`}eaOe82CGn;>a>d*^|T@so`yIpr@okKZUY z6>s`dIWO(D)_2j07&GBiO^N#fieihRbQ_Jru6kSDKjBprRXOXNnar5vK6#bA_~e8n zk;z(a$ckm%fHoOV&UWLP9G$Gi^cTd{nY=mKgelV<=rY^MIgy-`^;7hid_2Gk9iqf0 zZ}4E5ye379N!t@DBsy6k)tE`j2gtRY+?Q(0bOXo)>NW5cnk5E Jwsb#`I{}vlbhrQj diff --git a/.yarn/cache/@standardnotes-models-npm-1.24.2-8c2c157efa-17b3cfba39.zip b/.yarn/cache/@standardnotes-models-npm-1.26.0-dade8919ab-f595a3de88.zip similarity index 87% rename from .yarn/cache/@standardnotes-models-npm-1.24.2-8c2c157efa-17b3cfba39.zip rename to .yarn/cache/@standardnotes-models-npm-1.26.0-dade8919ab-f595a3de88.zip index 371848783b462f1401bce1898c8dd22345d591eb..5e9154a59e55e9be3e6c105a2c81022e5f215e0a 100644 GIT binary patch delta 9710 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�qK^n8Zb%R@8ugE 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 diff --git a/.yarn/cache/@standardnotes-responses-npm-1.10.6-f636794f47-0583e2cb77.zip b/.yarn/cache/@standardnotes-responses-npm-1.11.0-d066ddbbb6-46d6a47980.zip similarity index 98% rename from .yarn/cache/@standardnotes-responses-npm-1.10.6-f636794f47-0583e2cb77.zip rename to .yarn/cache/@standardnotes-responses-npm-1.11.0-d066ddbbb6-46d6a47980.zip index 18487c21119991c2f809b5eb70112444f892afa1..2ac32d56f4382b08b3e4ff19236b02b027f64246 100644 GIT binary patch delta 562 zcmV-20?qy9>H_8J0A(-Y_29FMz`6(LO>YCS5Je&KbPaZEEOD;bGkpfNUPrZ$ABvU8Mb6$A>ku=7U*Z!D zUO6-1q;}JHU2oYAabSPPoNV-LXA{RH9D5M9nOEpS2`(?;}nsv&TK-#-x?#=13rchlR(Y)^$srNBqYNKmH&=>w>U z0Db!M{TnYu&U?pOiQrPc)PPynR0}d%cOB!6W6{Ay!UpOJN{4>|ev;0zM4r$igkCW| zFbdHqRZd;y{6V7H&7|tlwEDc%5jrPzAhpKEbl}5@2}%Kv6sJ~(R+HVp*i&SF0Mg>B zrzy!T&~cF7|1!9{?SB%%&-dc~GYmkZvC7O>>N*Z@xu10JF5ihlREKm4jSc0=)G`G= zwBt4{#9bxId|H25cJMyooSK4YXf#B8bD+FVc6qGL^Er}}jhX+m=3oC+DsFpxUx;#Z z^7Id{0g0naD@(9x>}WMDPb*hnJr!xe*sWS0|c|HI@(+W0cG=jvx%479bj1w A_W%F@ delta 562 zcmV-20?qy9>H_8J0=>OGN&5IZdi63P&uQ#aV^?eUy0+jjFuG&`d~`OLX_QTp#s-{Q zXW&`vM+jfK(_-%1_-~J*(nC`{n7wCXSU6v@fP#Q#O}=YN$>+n(M_u`Gl< zmT}IjKwB6h`F*(M-9?-)FP8**{QA0|c|HI@(+WxR?yHvx%479X%=* AV*mgE diff --git a/.yarn/cache/@standardnotes-utils-npm-1.9.1-e48d87ffc7-f775bb3744.zip b/.yarn/cache/@standardnotes-utils-npm-1.10.0-0dc2ade40b-c02d54ca8a.zip similarity index 94% rename from .yarn/cache/@standardnotes-utils-npm-1.9.1-e48d87ffc7-f775bb3744.zip rename to .yarn/cache/@standardnotes-utils-npm-1.10.0-0dc2ade40b-c02d54ca8a.zip index af6ae41f5af2a464787cfea5b6a47c1dbcb5631e..9177dddde5c4dea53b1bce97367b54dfe1c3caa0 100644 GIT binary patch delta 601 zcmV-f0;c`ggaO!u0kDB43eLD$!BA! ziY0=q#LG4kst&+$eC9D90YNgTBU<3X1K2WFC378$Mv{ZyjE{t6Y$>D{Z28j0d zsI&*K6(VZSU9dW@-UW?$44b=akz9#83Pj}1A$$-bUz?X z7{!-wzkc9?q*!U(PzySq7pE{A9czGw#&nBttx<4)@DQ<1gS#4SJ$$BGqDUUl39^PE z+#$CFqi!y4v>5-TmK|qO`(8BszSIQWlNw2lkl{Em{tFRz1iZL)7^NSzv+Efrw<6O4 zo8Ny2ZuF9gd2Vi!$3iN+!Y(7lc8Hs=R&Cd7QJ({rlY(p}SBg_}4ErnMQkQ2VqM>eF zesBwamU3!o;H1O_xtRNU{(9e9$j$QqlZ#P*V4Q}gclpL?m$#^K3bCU#$w{`l!LRN6 z4z@Rp@onw04M{H}iD*a@jyJN53+|f!5Z}tKB*m=c3db%H);+!OZr+WQ>t^z4d+H)j z=O(Pd^NQ59KTY-^?029cRTbOTDKD~Bv99VIy4bJFvaCyBu;<%-xh^V7p`s~G7H^Ax nP)h>@lUhF-lLP`TldwM)vo$Hx6$#F`VsaD$005e^5kE%*qEH;W delta 601 zcmV-f0;c`ggaO!u0kDB43ah?2z!U-i0GR}n{sAbHNIwjJZBtE;(=ZUd_g7dsqO}_* z={7}xK#K$igpl@#sN~v{)>UFhwzF)t+W(F}(rj9Kn#AwTn>XX}%L1WHu@18LDEn+3 zQ-T?xv;(^woNR4&7Z^`qtkf!yDA^F2PC@OZ0_#s-{BID_A_{%_NbgBba`b_&L(Dw< z8dC&VZe;I&MzIIoby}fNNjeD@HQJ!Z(FOV$Y@pugBXBo5>6na}LRzN&#yQw1843KYOaBAP z#!-Cv_Ui{ONshI_EpuQ}y)=c{=y(e(HfB168-t>MgS&+F2K>z!7vK{!3MKN0PLQ)4 z;U4)AjrzIx(PI3UIewT)?FQM3+foyBOX^H&rHbXihA%{X33&E#9Ay}_w;LEoACc{W z&+oqnKYGRGd~dE6kH@VD3R_Cb`*Gbuw(k1D$mSHWf|R75Txr4VA)Z&nZ(T7Z8E9I+ zKez*b#{_dMa&kfmE$6Y~W! zCY;6djNG?BO?EHccVKa=1mD$pQ3=>lC|BzobtB46-mrR8?baJn)fCosP4g;Sye|F$ nP)h>@lUhF-lLP`TldwM)vo$Hx6$z`pIKUJF005b@5kE%*uxlo* diff --git a/packages/api-gateway/bin/server.ts b/packages/api-gateway/bin/server.ts index dc87004cf..e28841560 100644 --- a/packages/api-gateway/bin/server.ts +++ b/packages/api-gateway/bin/server.ts @@ -20,6 +20,7 @@ import '../src/Controller/v1/OfflineController' import '../src/Controller/v1/FilesController' import '../src/Controller/v1/SubscriptionInvitesController' import '../src/Controller/v1/WorkspacesController' +import '../src/Controller/v1/InvitesController' import '../src/Controller/v2/PaymentsControllerV2' import '../src/Controller/v2/ActionsControllerV2' diff --git a/packages/api-gateway/src/Controller/v1/InvitesController.ts b/packages/api-gateway/src/Controller/v1/InvitesController.ts new file mode 100644 index 000000000..69cec3fb6 --- /dev/null +++ b/packages/api-gateway/src/Controller/v1/InvitesController.ts @@ -0,0 +1,23 @@ +import { inject } from 'inversify' +import { Request, Response } from 'express' +import { controller, BaseHttpController, httpPost } from 'inversify-express-utils' + +import TYPES from '../../Bootstrap/Types' +import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface' + +@controller('/v1/invites', TYPES.AuthMiddleware) +export class InvitesController extends BaseHttpController { + constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) { + super() + } + + @httpPost('/:inviteUuid/accept') + async accept(request: Request, response: Response): Promise { + await this.httpService.callWorkspaceServer( + request, + response, + `invites/${request.params.inviteUuid}/accept`, + request.body, + ) + } +} diff --git a/packages/api-gateway/src/Controller/v1/WorkspacesController.ts b/packages/api-gateway/src/Controller/v1/WorkspacesController.ts index 0bb93908e..c08ae8c7f 100644 --- a/packages/api-gateway/src/Controller/v1/WorkspacesController.ts +++ b/packages/api-gateway/src/Controller/v1/WorkspacesController.ts @@ -1,6 +1,6 @@ import { inject } from 'inversify' import { Request, Response } from 'express' -import { controller, BaseHttpController, httpPost } from 'inversify-express-utils' +import { controller, BaseHttpController, httpPost, httpGet } from 'inversify-express-utils' import TYPES from '../../Bootstrap/Types' import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface' @@ -16,6 +16,21 @@ export class WorkspacesController extends BaseHttpController { await this.httpService.callWorkspaceServer(request, response, 'workspaces', request.body) } + @httpGet('/:workspaceUuid/users') + async listWorkspaceUsers(request: Request, response: Response): Promise { + await this.httpService.callWorkspaceServer( + request, + response, + `workspaces/${request.params.workspaceUuid}/users`, + request.body, + ) + } + + @httpGet('/') + async listWorkspaces(request: Request, response: Response): Promise { + await this.httpService.callWorkspaceServer(request, response, 'workspaces', request.body) + } + @httpPost('/:workspaceUuid/invites') async invite(request: Request, response: Response): Promise { await this.httpService.callWorkspaceServer( diff --git a/packages/auth/package.json b/packages/auth/package.json index 16cd9657d..65f006f33 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -34,7 +34,7 @@ "@newrelic/winston-enricher": "^4.0.0", "@sentry/node": "^7.3.0", "@standardnotes/analytics": "workspace:*", - "@standardnotes/api": "^1.12.1", + "@standardnotes/api": "^1.15.0", "@standardnotes/common": "workspace:*", "@standardnotes/domain-events": "workspace:*", "@standardnotes/domain-events-infra": "workspace:*", diff --git a/packages/workspace/bin/server.ts b/packages/workspace/bin/server.ts index 91b421471..e1b25b03a 100644 --- a/packages/workspace/bin/server.ts +++ b/packages/workspace/bin/server.ts @@ -5,6 +5,7 @@ import 'newrelic' import * as Sentry from '@sentry/node' import '../src/Infra/InversifyExpressUtils/InversifyExpressHealthCheckController' +import '../src/Infra/InversifyExpressUtils/InversifyExpressInvitesController' import '../src/Infra/InversifyExpressUtils/InversifyExpressWorkspacesController' import * as cors from 'cors' diff --git a/packages/workspace/package.json b/packages/workspace/package.json index 9f46822e1..a3abe30ee 100644 --- a/packages/workspace/package.json +++ b/packages/workspace/package.json @@ -25,10 +25,11 @@ "dependencies": { "@newrelic/winston-enricher": "^4.0.0", "@sentry/node": "^7.3.0", - "@standardnotes/api": "^1.12.1", + "@standardnotes/api": "^1.15.0", "@standardnotes/common": "workspace:*", "@standardnotes/domain-events": "workspace:^", "@standardnotes/domain-events-infra": "workspace:^", + "@standardnotes/models": "^1.26.0", "@standardnotes/security": "workspace:*", "@standardnotes/time": "workspace:^", "aws-sdk": "^2.1159.0", diff --git a/packages/workspace/src/Bootstrap/Container.ts b/packages/workspace/src/Bootstrap/Container.ts index 502d09628..a8b0d2be6 100644 --- a/packages/workspace/src/Bootstrap/Container.ts +++ b/packages/workspace/src/Bootstrap/Container.ts @@ -38,6 +38,14 @@ import { WorkspaceInvite } from '../Domain/Invite/WorkspaceInvite' import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace' import { DomainEventFactory } from '../Domain/Event/DomainEventFactory' import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface' +import { WorkspaceProjection } from '../Domain/Projection/WorkspaceProjection' +import { WorkspaceProjector } from '../Domain/Projection/WorkspaceProjector' +import { ProjectorInterface } from '../Domain/Projection/ProjectorInterface' +import { WorkspaceUserProjection } from '../Domain/Projection/WorkspaceUserProjection' +import { WorkspaceUserProjector } from '../Domain/Projection/WorkspaceUserProjector' +import { AcceptInvitation } from '../Domain/UseCase/AcceptInvitation/AcceptInvitation' +import { ListWorkspaces } from '../Domain/UseCase/ListWorkspaces/ListWorkspaces' +import { ListWorkspaceUsers } from '../Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers' // eslint-disable-next-line @typescript-eslint/no-var-requires const newrelicFormatter = require('@newrelic/winston-enricher') @@ -131,8 +139,16 @@ export class ContainerConfigLoader { // use cases container.bind(TYPES.CreateWorkspace).to(CreateWorkspace) container.bind(TYPES.InviteToWorkspace).to(InviteToWorkspace) + container.bind(TYPES.AcceptInvitation).to(AcceptInvitation) + container.bind(TYPES.ListWorkspaces).to(ListWorkspaces) + container.bind(TYPES.ListWorkspaceUsers).to(ListWorkspaceUsers) // Handlers container.bind(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler) + // Projection + container.bind>(TYPES.WorkspaceProjector).to(WorkspaceProjector) + container + .bind>(TYPES.WorkspaceUserProjector) + .to(WorkspaceUserProjector) // Services container.bind(TYPES.DomainEventFactory).to(DomainEventFactory) container.bind(TYPES.Timer).toConstantValue(new Timer()) diff --git a/packages/workspace/src/Bootstrap/Types.ts b/packages/workspace/src/Bootstrap/Types.ts index ba7c9afa0..5f9c0f9e2 100644 --- a/packages/workspace/src/Bootstrap/Types.ts +++ b/packages/workspace/src/Bootstrap/Types.ts @@ -28,8 +28,14 @@ const TYPES = { // use cases CreateWorkspace: Symbol.for('CreateWorkspace'), InviteToWorkspace: Symbol.for('InviteToWorkspace'), + AcceptInvitation: Symbol.for('AcceptInvitation'), + ListWorkspaces: Symbol.for('ListWorkspaces'), + ListWorkspaceUsers: Symbol.for('ListWorkspaceUsers'), // Handlers UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'), + // Projection + WorkspaceProjector: Symbol.for('WorkspaceProjector'), + WorkspaceUserProjector: Symbol.for('WorkspaceUserProjector'), // Services Timer: Symbol.for('Timer'), CrossServiceTokenDecoder: Symbol.for('CrossServiceTokenDecoder'), diff --git a/packages/workspace/src/Controller/WorkspacesController.spec.ts b/packages/workspace/src/Controller/WorkspacesController.spec.ts index b8ca9d3ce..f42eadbe4 100644 --- a/packages/workspace/src/Controller/WorkspacesController.spec.ts +++ b/packages/workspace/src/Controller/WorkspacesController.spec.ts @@ -1,16 +1,42 @@ import { WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common' import 'reflect-metadata' +import { ProjectorInterface } from '../Domain/Projection/ProjectorInterface' +import { WorkspaceProjection } from '../Domain/Projection/WorkspaceProjection' +import { WorkspaceUserProjection } from '../Domain/Projection/WorkspaceUserProjection' +import { AcceptInvitation } from '../Domain/UseCase/AcceptInvitation/AcceptInvitation' import { CreateWorkspace } from '../Domain/UseCase/CreateWorkspace/CreateWorkspace' import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace' +import { ListWorkspaces } from '../Domain/UseCase/ListWorkspaces/ListWorkspaces' +import { ListWorkspaceUsers } from '../Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers' +import { Workspace } from '../Domain/Workspace/Workspace' +import { WorkspaceUser } from '../Domain/Workspace/WorkspaceUser' import { WorkspacesController } from './WorkspacesController' describe('WorkspacesController', () => { let doCreateWorkspace: CreateWorkspace let doInviteToWorkspace: InviteToWorkspace + let doAcceptInvitation: AcceptInvitation + let doListWorkspaces: ListWorkspaces + let doListWorkspaceUsers: ListWorkspaceUsers + let workspacesProject: ProjectorInterface + let workspaceUsersProjector: ProjectorInterface + let workspace1: Workspace + let workspace2: Workspace + let workspaceUser1: WorkspaceUser + let workspaceUser2: WorkspaceUser - const createController = () => new WorkspacesController(doCreateWorkspace, doInviteToWorkspace) + const createController = () => + new WorkspacesController( + doCreateWorkspace, + doInviteToWorkspace, + doListWorkspaces, + doListWorkspaceUsers, + doAcceptInvitation, + workspacesProject, + workspaceUsersProjector, + ) beforeEach(() => { doCreateWorkspace = {} as jest.Mocked @@ -18,6 +44,23 @@ describe('WorkspacesController', () => { doInviteToWorkspace = {} as jest.Mocked doInviteToWorkspace.execute = jest.fn().mockReturnValue({ invite: { uuid: 'i-1-2-3' } }) + + doListWorkspaces = {} as jest.Mocked + doListWorkspaces.execute = jest + .fn() + .mockReturnValue({ ownedWorkspaces: [workspace1], joinedWorkspaces: [workspace2] }) + + doListWorkspaceUsers = {} as jest.Mocked + doListWorkspaceUsers.execute = jest.fn().mockReturnValue({ workspaceUsers: [workspaceUser1, workspaceUser2] }) + + doAcceptInvitation = {} as jest.Mocked + doAcceptInvitation.execute = jest.fn().mockReturnValue({ success: true }) + + workspacesProject = {} as jest.Mocked> + workspacesProject.project = jest.fn().mockReturnValue({ foo: 'bar' }) + + workspaceUsersProjector = {} as jest.Mocked> + workspaceUsersProjector.project = jest.fn().mockReturnValue({ bar: 'buzz' }) }) it('should create a workspace', async () => { @@ -52,4 +95,67 @@ describe('WorkspacesController', () => { status: 200, }) }) + + it('should accept an invite', async () => { + const result = await createController().acceptInvite({ + userUuid: '1-2-3', + encryptedPrivateKey: 'foo', + inviteUuid: 'i-1-2-3', + publicKey: 'bar', + }) + + expect(result).toEqual({ + data: { + success: true, + }, + status: 200, + }) + }) + + it('should not accept an invite if it fails', async () => { + doAcceptInvitation.execute = jest.fn().mockReturnValue({ success: false }) + const result = await createController().acceptInvite({ + userUuid: '1-2-3', + encryptedPrivateKey: 'foo', + inviteUuid: 'i-1-2-3', + publicKey: 'bar', + }) + + expect(result).toEqual({ + data: { + error: { + message: 'Could not accept invite', + }, + }, + status: 400, + }) + }) + + it('should list workspaces', async () => { + const result = await createController().listWorkspaces({ + userUuid: '1-2-3', + }) + + expect(result).toEqual({ + data: { + ownedWorkspaces: [{ foo: 'bar' }], + joinedWorkspaces: [{ foo: 'bar' }], + }, + status: 200, + }) + }) + + it('should list workspace users', async () => { + const result = await createController().listWorkspaceUsers({ + userUuid: '1-2-3', + workspaceUuid: 'w-1-2-3', + }) + + expect(result).toEqual({ + data: { + users: [{ bar: 'buzz' }, { bar: 'buzz' }], + }, + status: 200, + }) + }) }) diff --git a/packages/workspace/src/Controller/WorkspacesController.ts b/packages/workspace/src/Controller/WorkspacesController.ts index ddd25edaf..62d700c09 100644 --- a/packages/workspace/src/Controller/WorkspacesController.ts +++ b/packages/workspace/src/Controller/WorkspacesController.ts @@ -6,18 +6,38 @@ import { WorkspaceInvitationRequestParams, WorkspaceInvitationResponse, WorkspaceServerInterface, + WorkspaceListRequestParams, + WorkspaceListResponse, + WorkspaceInvitationAcceptingRequestParams, + WorkspaceInvitationAcceptingResponse, + WorkspaceUserListRequestParams, } from '@standardnotes/api' import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common' import TYPES from '../Bootstrap/Types' import { CreateWorkspace } from '../Domain/UseCase/CreateWorkspace/CreateWorkspace' import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace' +import { ProjectorInterface } from '../Domain/Projection/ProjectorInterface' +import { WorkspaceProjection } from '../Domain/Projection/WorkspaceProjection' +import { Workspace } from '../Domain/Workspace/Workspace' +import { ListWorkspaces } from '../Domain/UseCase/ListWorkspaces/ListWorkspaces' +import { WorkspaceUserListResponse } from '@standardnotes/api/dist/Domain/Response/Workspace/WorkspaceUserListResponse' +import { AcceptInvitation } from '../Domain/UseCase/AcceptInvitation/AcceptInvitation' +import { WorkspaceUser } from '../Domain/Workspace/WorkspaceUser' +import { WorkspaceUserProjection } from '../Domain/Projection/WorkspaceUserProjection' +import { ListWorkspaceUsers } from '../Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers' @injectable() export class WorkspacesController implements WorkspaceServerInterface { constructor( @inject(TYPES.CreateWorkspace) private doCreateWorkspace: CreateWorkspace, @inject(TYPES.InviteToWorkspace) private doInviteToWorkspace: InviteToWorkspace, + @inject(TYPES.ListWorkspaces) private doListWorkspaces: ListWorkspaces, + @inject(TYPES.ListWorkspaceUsers) private doListWorkspaceUsers: ListWorkspaceUsers, + @inject(TYPES.AcceptInvitation) private doAcceptInvite: AcceptInvitation, + @inject(TYPES.WorkspaceProjector) private workspaceProjector: ProjectorInterface, + @inject(TYPES.WorkspaceUserProjector) + private workspaceUserProjector: ProjectorInterface, ) {} async inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise { @@ -49,4 +69,69 @@ export class WorkspacesController implements WorkspaceServerInterface { data: { uuid: workspace.uuid }, } } + + async listWorkspaces(params: WorkspaceListRequestParams): Promise { + const { ownedWorkspaces, joinedWorkspaces } = await this.doListWorkspaces.execute({ + userUuid: params.userUuid as Uuid, + }) + + const ownedWorkspacesProjections = [] + for (const ownedWorkspace of ownedWorkspaces) { + ownedWorkspacesProjections.push(await this.workspaceProjector.project(ownedWorkspace)) + } + + const joinedWorkspacesProjections = [] + for (const joinedWorkspace of joinedWorkspaces) { + joinedWorkspacesProjections.push(await this.workspaceProjector.project(joinedWorkspace)) + } + + return { + status: HttpStatusCode.Success, + data: { ownedWorkspaces: ownedWorkspacesProjections, joinedWorkspaces: joinedWorkspacesProjections }, + } + } + + async listWorkspaceUsers(params: WorkspaceUserListRequestParams): Promise { + const { workspaceUsers } = await this.doListWorkspaceUsers.execute({ + userUuid: params.userUuid as Uuid, + workspaceUuid: params.workspaceUuid, + }) + + const workspaceUserProjections = [] + for (const workspaceUser of workspaceUsers) { + workspaceUserProjections.push(await this.workspaceUserProjector.project(workspaceUser)) + } + + return { + status: HttpStatusCode.Success, + data: { users: workspaceUserProjections }, + } + } + + async acceptInvite(params: WorkspaceInvitationAcceptingRequestParams): Promise { + const result = await this.doAcceptInvite.execute({ + acceptingUserUuid: params.userUuid, + encryptedPrivateKey: params.encryptedPrivateKey, + invitationUuid: params.inviteUuid, + publicKey: params.publicKey, + }) + + if (!result.success) { + return { + status: HttpStatusCode.BadRequest, + data: { + error: { + message: 'Could not accept invite', + }, + }, + } + } + + return { + status: HttpStatusCode.Success, + data: { + success: true, + }, + } + } } diff --git a/packages/workspace/src/Domain/Projection/ProjectorInterface.ts b/packages/workspace/src/Domain/Projection/ProjectorInterface.ts new file mode 100644 index 000000000..a987e78be --- /dev/null +++ b/packages/workspace/src/Domain/Projection/ProjectorInterface.ts @@ -0,0 +1,3 @@ +export interface ProjectorInterface { + project(object: T): Promise +} diff --git a/packages/workspace/src/Domain/Projection/WorkspaceProjection.ts b/packages/workspace/src/Domain/Projection/WorkspaceProjection.ts new file mode 100644 index 000000000..4ddd29fd5 --- /dev/null +++ b/packages/workspace/src/Domain/Projection/WorkspaceProjection.ts @@ -0,0 +1,3 @@ +import { Workspace } from '@standardnotes/models' + +export type WorkspaceProjection = Workspace diff --git a/packages/workspace/src/Domain/Projection/WorkspaceProjector.spec.ts b/packages/workspace/src/Domain/Projection/WorkspaceProjector.spec.ts new file mode 100644 index 000000000..103c95406 --- /dev/null +++ b/packages/workspace/src/Domain/Projection/WorkspaceProjector.spec.ts @@ -0,0 +1,30 @@ +import 'reflect-metadata' + +import { WorkspaceType } from '@standardnotes/common' +import { Workspace } from '../Workspace/Workspace' + +import { WorkspaceProjector } from './WorkspaceProjector' + +describe('WorkspaceProjector', () => { + const createProjector = () => new WorkspaceProjector() + + it('should project a workspace', async () => { + expect( + await createProjector().project({ + uuid: 'w-1-2-3', + type: WorkspaceType.Private, + name: 'test', + keyRotationIndex: 0, + createdAt: 1, + updatedAt: 2, + } as jest.Mocked), + ).toEqual({ + uuid: 'w-1-2-3', + type: 'private', + name: 'test', + keyRotationIndex: 0, + createdAt: 1, + updatedAt: 2, + }) + }) +}) diff --git a/packages/workspace/src/Domain/Projection/WorkspaceProjector.ts b/packages/workspace/src/Domain/Projection/WorkspaceProjector.ts new file mode 100644 index 000000000..2d798b360 --- /dev/null +++ b/packages/workspace/src/Domain/Projection/WorkspaceProjector.ts @@ -0,0 +1,19 @@ +import { injectable } from 'inversify' +import { ProjectorInterface } from './ProjectorInterface' + +import { WorkspaceProjection } from './WorkspaceProjection' +import { Workspace } from '../Workspace/Workspace' + +@injectable() +export class WorkspaceProjector implements ProjectorInterface { + async project(workspace: Workspace): Promise { + return { + uuid: workspace.uuid, + type: workspace.type, + name: workspace.name, + keyRotationIndex: workspace.keyRotationIndex, + createdAt: workspace.createdAt, + updatedAt: workspace.updatedAt, + } + } +} diff --git a/packages/workspace/src/Domain/Projection/WorkspaceUserProjection.ts b/packages/workspace/src/Domain/Projection/WorkspaceUserProjection.ts new file mode 100644 index 000000000..966f22d84 --- /dev/null +++ b/packages/workspace/src/Domain/Projection/WorkspaceUserProjection.ts @@ -0,0 +1,3 @@ +import { WorkspaceUser } from '@standardnotes/models' + +export type WorkspaceUserProjection = WorkspaceUser diff --git a/packages/workspace/src/Domain/Projection/WorkspaceUserProjector.spec.ts b/packages/workspace/src/Domain/Projection/WorkspaceUserProjector.spec.ts new file mode 100644 index 000000000..35a54b4c6 --- /dev/null +++ b/packages/workspace/src/Domain/Projection/WorkspaceUserProjector.spec.ts @@ -0,0 +1,42 @@ +import 'reflect-metadata' + +import { WorkspaceAccessLevel, WorkspaceUserStatus } from '@standardnotes/common' +import { WorkspaceUser } from '../Workspace/WorkspaceUser' + +import { WorkspaceUserProjector } from './WorkspaceUserProjector' + +describe('WorkspaceUserProjector', () => { + const createProjector = () => new WorkspaceUserProjector() + + it('should project a workspace user', async () => { + expect( + await createProjector().project({ + uuid: '1-2-3', + accessLevel: WorkspaceAccessLevel.Owner, + userUuid: 'u-1-2-3', + userDisplayName: 'foobar', + workspaceUuid: 'w-1-2-3', + encryptedWorkspaceKey: 'foo', + publicKey: 'bar', + encryptedPrivateKey: 'buzz', + status: WorkspaceUserStatus.PendingKeyshare, + keyRotationIndex: 0, + createdAt: 1, + updatedAt: 2, + } as jest.Mocked), + ).toEqual({ + uuid: '1-2-3', + accessLevel: 'owner', + userUuid: 'u-1-2-3', + userDisplayName: 'foobar', + workspaceUuid: 'w-1-2-3', + encryptedWorkspaceKey: 'foo', + publicKey: 'bar', + encryptedPrivateKey: 'buzz', + status: 'pending-keyshare', + keyRotationIndex: 0, + createdAt: 1, + updatedAt: 2, + }) + }) +}) diff --git a/packages/workspace/src/Domain/Projection/WorkspaceUserProjector.ts b/packages/workspace/src/Domain/Projection/WorkspaceUserProjector.ts new file mode 100644 index 000000000..613b88160 --- /dev/null +++ b/packages/workspace/src/Domain/Projection/WorkspaceUserProjector.ts @@ -0,0 +1,25 @@ +import { injectable } from 'inversify' +import { ProjectorInterface } from './ProjectorInterface' + +import { WorkspaceUserProjection } from './WorkspaceUserProjection' +import { WorkspaceUser } from '../Workspace/WorkspaceUser' + +@injectable() +export class WorkspaceUserProjector implements ProjectorInterface { + async project(workspaceUser: WorkspaceUser): Promise { + return { + uuid: workspaceUser.uuid, + accessLevel: workspaceUser.accessLevel, + userUuid: workspaceUser.userUuid, + userDisplayName: workspaceUser.userDisplayName, + workspaceUuid: workspaceUser.workspaceUuid, + encryptedWorkspaceKey: workspaceUser.encryptedWorkspaceKey, + publicKey: workspaceUser.publicKey, + encryptedPrivateKey: workspaceUser.encryptedPrivateKey, + status: workspaceUser.status, + keyRotationIndex: workspaceUser.keyRotationIndex, + createdAt: workspaceUser.createdAt, + updatedAt: workspaceUser.updatedAt, + } + } +} diff --git a/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers.spec.ts b/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers.spec.ts new file mode 100644 index 000000000..dbc73f058 --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers.spec.ts @@ -0,0 +1,84 @@ +import { WorkspaceAccessLevel } from '@standardnotes/common' +import 'reflect-metadata' +import { Workspace } from '../../Workspace/Workspace' +import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface' +import { WorkspaceUser } from '../../Workspace/WorkspaceUser' +import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface' + +import { ListWorkspaceUsers } from './ListWorkspaceUsers' + +describe('ListWorkspaceUsers', () => { + let workspaceRepository: WorkspaceRepositoryInterface + let workspaceUserRepository: WorkspaceUserRepositoryInterface + let workspace: Workspace + let workspaceUser1: WorkspaceUser + let workspaceUser2: WorkspaceUser + + const createUseCase = () => new ListWorkspaceUsers(workspaceRepository, workspaceUserRepository) + + beforeEach(() => { + workspace = { uuid: 'j-1-2-3' } as jest.Mocked + + workspaceUser1 = { userUuid: 'u-1-2-3', accessLevel: WorkspaceAccessLevel.Owner } as jest.Mocked + workspaceUser2 = { + userUuid: 'u-2-3-4', + accessLevel: WorkspaceAccessLevel.WriteAndRead, + } as jest.Mocked + + workspaceRepository = {} as jest.Mocked + workspaceRepository.findOneByUuid = jest.fn().mockReturnValue(workspace) + + workspaceUserRepository = {} as jest.Mocked + workspaceUserRepository.findByWorkspaceUuid = jest.fn().mockReturnValue([workspaceUser1, workspaceUser2]) + }) + + it('should list users in a workspace where the user is owner or admin', async () => { + const result = await createUseCase().execute({ + userUuid: 'u-1-2-3', + workspaceUuid: 'j-1-2-3', + }) + + expect(result).toEqual({ + workspaceUsers: [workspaceUser1, workspaceUser2], + userIsOwnerOrAdmin: true, + }) + }) + + it('should list users in a workspace where the user is not the owner or admin with indiciation', async () => { + const result = await createUseCase().execute({ + userUuid: 'u-2-3-4', + workspaceUuid: 'j-1-2-3', + }) + + expect(result).toEqual({ + workspaceUsers: [workspaceUser1, workspaceUser2], + userIsOwnerOrAdmin: false, + }) + }) + + it('should not list users in a workspace where the user does not belong', async () => { + const result = await createUseCase().execute({ + userUuid: 'z-1-2-3', + workspaceUuid: 'j-1-2-3', + }) + + expect(result).toEqual({ + workspaceUsers: [], + userIsOwnerOrAdmin: false, + }) + }) + + it('should not list users in a workspace that does not exist', async () => { + workspaceRepository.findOneByUuid = jest.fn().mockReturnValue(null) + + const result = await createUseCase().execute({ + userUuid: 'u-1-2-3', + workspaceUuid: 'j-1-2-3', + }) + + expect(result).toEqual({ + workspaceUsers: [], + userIsOwnerOrAdmin: false, + }) + }) +}) diff --git a/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers.ts b/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers.ts new file mode 100644 index 000000000..f364db9ab --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers.ts @@ -0,0 +1,50 @@ +import { WorkspaceAccessLevel } from '@standardnotes/common' +import { inject, injectable } from 'inversify' +import TYPES from '../../../Bootstrap/Types' +import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface' +import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface' +import { UseCaseInterface } from '../UseCaseInterface' +import { ListWorkspaceUsersDTO } from './ListWorkspaceUsersDTO' +import { ListWorkspaceUsersResponse } from './ListWorkspaceUsersResponse' + +@injectable() +export class ListWorkspaceUsers implements UseCaseInterface { + constructor( + @inject(TYPES.WorkspaceRepository) private workspaceRepository: WorkspaceRepositoryInterface, + @inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface, + ) {} + + async execute(dto: ListWorkspaceUsersDTO): Promise { + const workspace = await this.workspaceRepository.findOneByUuid(dto.workspaceUuid) + if (workspace === null) { + return { + workspaceUsers: [], + userIsOwnerOrAdmin: false, + } + } + + const workspaceUsers = await this.workspaceUserRepository.findByWorkspaceUuid(dto.workspaceUuid) + let userIsOwnerOrAdmin = false + let userIsInWorkspace = false + for (const workspaceUser of workspaceUsers) { + if (workspaceUser.userUuid === dto.userUuid) { + userIsInWorkspace = true + if ([WorkspaceAccessLevel.Admin, WorkspaceAccessLevel.Owner].includes(workspaceUser.accessLevel)) { + userIsOwnerOrAdmin = true + } + } + } + + if (!userIsInWorkspace) { + return { + workspaceUsers: [], + userIsOwnerOrAdmin: false, + } + } + + return { + workspaceUsers, + userIsOwnerOrAdmin, + } + } +} diff --git a/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsersDTO.ts b/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsersDTO.ts new file mode 100644 index 000000000..bff27c6d5 --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsersDTO.ts @@ -0,0 +1,6 @@ +import { Uuid } from '@standardnotes/common' + +export type ListWorkspaceUsersDTO = { + workspaceUuid: Uuid + userUuid: Uuid +} diff --git a/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsersResponse.ts b/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsersResponse.ts new file mode 100644 index 000000000..2e51113f4 --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsersResponse.ts @@ -0,0 +1,6 @@ +import { WorkspaceUser } from '../../Workspace/WorkspaceUser' + +export type ListWorkspaceUsersResponse = { + workspaceUsers: WorkspaceUser[] + userIsOwnerOrAdmin: boolean +} diff --git a/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspaces.spec.ts b/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspaces.spec.ts new file mode 100644 index 000000000..c0a88d98e --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspaces.spec.ts @@ -0,0 +1,47 @@ +import { WorkspaceAccessLevel } from '@standardnotes/common' +import 'reflect-metadata' +import { Workspace } from '../../Workspace/Workspace' +import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface' +import { WorkspaceUser } from '../../Workspace/WorkspaceUser' +import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface' + +import { ListWorkspaces } from './ListWorkspaces' + +describe('ListWorkspaces', () => { + let workspaceRepository: WorkspaceRepositoryInterface + let workspaceUserRepository: WorkspaceUserRepositoryInterface + let ownedWorkspace: Workspace + let joinedWorkspace: Workspace + let workspaceUser1: WorkspaceUser + let workspaceUser2: WorkspaceUser + + const createUseCase = () => new ListWorkspaces(workspaceRepository, workspaceUserRepository) + + beforeEach(() => { + ownedWorkspace = { uuid: 'o-1-2-3' } as jest.Mocked + joinedWorkspace = { uuid: 'j-1-2-3' } as jest.Mocked + + workspaceUser1 = { accessLevel: WorkspaceAccessLevel.Owner } as jest.Mocked + workspaceUser2 = { accessLevel: WorkspaceAccessLevel.WriteAndRead } as jest.Mocked + + workspaceRepository = {} as jest.Mocked + workspaceRepository.findByUuids = jest + .fn() + .mockReturnValueOnce([ownedWorkspace]) + .mockReturnValueOnce([joinedWorkspace]) + + workspaceUserRepository = {} as jest.Mocked + workspaceUserRepository.findByUserUuid = jest.fn().mockReturnValue([workspaceUser1, workspaceUser2]) + }) + + it('should list owned and joined workspaces for a user', async () => { + const result = await createUseCase().execute({ + userUuid: 'u-1-2-3', + }) + + expect(result).toEqual({ + ownedWorkspaces: [ownedWorkspace], + joinedWorkspaces: [joinedWorkspace], + }) + }) +}) diff --git a/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspaces.ts b/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspaces.ts new file mode 100644 index 000000000..00c18bb97 --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspaces.ts @@ -0,0 +1,40 @@ +import { WorkspaceAccessLevel } from '@standardnotes/common' +import { inject, injectable } from 'inversify' + +import TYPES from '../../../Bootstrap/Types' +import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositoryInterface' +import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface' +import { UseCaseInterface } from '../UseCaseInterface' + +import { ListWorkspacesDTO } from './ListWorkspacesDTO' +import { ListWorkspacesResponse } from './ListWorkspacesResponse' + +@injectable() +export class ListWorkspaces implements UseCaseInterface { + constructor( + @inject(TYPES.WorkspaceRepository) private workspaceRepository: WorkspaceRepositoryInterface, + @inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface, + ) {} + + async execute(dto: ListWorkspacesDTO): Promise { + const workspaceAssociations = await this.workspaceUserRepository.findByUserUuid(dto.userUuid) + + const ownedWorkspacesUuids = [] + const joinedWorkspacesUuids = [] + for (const workspaceAssociation of workspaceAssociations) { + if ([WorkspaceAccessLevel.Admin, WorkspaceAccessLevel.Owner].includes(workspaceAssociation.accessLevel)) { + ownedWorkspacesUuids.push(workspaceAssociation.uuid) + } else { + joinedWorkspacesUuids.push(workspaceAssociation.uuid) + } + } + + const ownedWorkspaces = await this.workspaceRepository.findByUuids(ownedWorkspacesUuids) + const joinedWorkspaces = await this.workspaceRepository.findByUuids(joinedWorkspacesUuids) + + return { + ownedWorkspaces, + joinedWorkspaces, + } + } +} diff --git a/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspacesDTO.ts b/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspacesDTO.ts new file mode 100644 index 000000000..b70037c69 --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspacesDTO.ts @@ -0,0 +1,5 @@ +import { Uuid } from '@standardnotes/common' + +export type ListWorkspacesDTO = { + userUuid: Uuid +} diff --git a/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspacesResponse.ts b/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspacesResponse.ts new file mode 100644 index 000000000..8c860d8d0 --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/ListWorkspaces/ListWorkspacesResponse.ts @@ -0,0 +1,6 @@ +import { Workspace } from '../../Workspace/Workspace' + +export type ListWorkspacesResponse = { + ownedWorkspaces: Array + joinedWorkspaces: Array +} diff --git a/packages/workspace/src/Domain/Workspace/WorkspaceRepositoryInterface.ts b/packages/workspace/src/Domain/Workspace/WorkspaceRepositoryInterface.ts index 5ded5fee6..ce389c721 100644 --- a/packages/workspace/src/Domain/Workspace/WorkspaceRepositoryInterface.ts +++ b/packages/workspace/src/Domain/Workspace/WorkspaceRepositoryInterface.ts @@ -1,5 +1,8 @@ +import { Uuid } from '@standardnotes/common' import { Workspace } from './Workspace' export interface WorkspaceRepositoryInterface { save(workspace: Workspace): Promise + findByUuids(uuids: Uuid[]): Promise + findOneByUuid(uuid: Uuid): Promise } diff --git a/packages/workspace/src/Domain/Workspace/WorkspaceUserRepositoryInterface.ts b/packages/workspace/src/Domain/Workspace/WorkspaceUserRepositoryInterface.ts index bad097e18..c3594d4e0 100644 --- a/packages/workspace/src/Domain/Workspace/WorkspaceUserRepositoryInterface.ts +++ b/packages/workspace/src/Domain/Workspace/WorkspaceUserRepositoryInterface.ts @@ -1,5 +1,8 @@ +import { Uuid } from '@standardnotes/common' import { WorkspaceUser } from './WorkspaceUser' export interface WorkspaceUserRepositoryInterface { save(workspace: WorkspaceUser): Promise + findByUserUuid(userUuid: Uuid): Promise + findByWorkspaceUuid(workspaceUuid: Uuid): Promise } diff --git a/packages/workspace/src/Infra/InversifyExpressUtils/InversifyExpressInvitesController.ts b/packages/workspace/src/Infra/InversifyExpressUtils/InversifyExpressInvitesController.ts new file mode 100644 index 000000000..90f327817 --- /dev/null +++ b/packages/workspace/src/Infra/InversifyExpressUtils/InversifyExpressInvitesController.ts @@ -0,0 +1,23 @@ +import { Request, Response } from 'express' +import { inject } from 'inversify' +import { BaseHttpController, controller, httpPost, results } from 'inversify-express-utils' +import TYPES from '../../Bootstrap/Types' +import { WorkspacesController } from '../../Controller/WorkspacesController' + +@controller('/invites', TYPES.ApiGatewayAuthMiddleware) +export class InversifyExpressInvitesController extends BaseHttpController { + constructor(@inject(TYPES.WorkspacesController) private workspacesController: WorkspacesController) { + super() + } + + @httpPost('/:inviteUuid/accept') + async acceptInvite(request: Request, response: Response): Promise { + const result = await this.workspacesController.acceptInvite({ + ...request.body, + inviteUuid: request.params.inviteUuid, + userUuid: response.locals.user.uuid, + }) + + return this.json(result.data, result.status) + } +} diff --git a/packages/workspace/src/Infra/InversifyExpressUtils/InversifyExpressWorkspacesController.ts b/packages/workspace/src/Infra/InversifyExpressUtils/InversifyExpressWorkspacesController.ts index fb0cfb478..f33696d76 100644 --- a/packages/workspace/src/Infra/InversifyExpressUtils/InversifyExpressWorkspacesController.ts +++ b/packages/workspace/src/Infra/InversifyExpressUtils/InversifyExpressWorkspacesController.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express' import { inject } from 'inversify' -import { BaseHttpController, controller, httpPost, results } from 'inversify-express-utils' +import { BaseHttpController, controller, httpGet, httpPost, results } from 'inversify-express-utils' import TYPES from '../../Bootstrap/Types' import { WorkspacesController } from '../../Controller/WorkspacesController' @@ -20,6 +20,25 @@ export class InversifyExpressWorkspacesController extends BaseHttpController { return this.json(result.data, result.status) } + @httpGet('/') + async listWorkspaces(response: Response): Promise { + const result = await this.workspacesController.listWorkspaces({ + userUuid: response.locals.user.uuid, + }) + + return this.json(result.data, result.status) + } + + @httpGet('/:workspaceUuid/users') + async listWorkspaceUsers(request: Request, response: Response): Promise { + const result = await this.workspacesController.listWorkspaceUsers({ + userUuid: response.locals.user.uuid, + workspaceUuid: request.params.workspaceUuid, + }) + + return this.json(result.data, result.status) + } + @httpPost('/:workspaceUuid/invites') async inviteToWorkspace(request: Request, response: Response): Promise { if (request.params.workspaceUuid !== request.body.workspaceUuid) { diff --git a/packages/workspace/src/Infra/MySQL/MySQLWorkspaceRepository.spec.ts b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceRepository.spec.ts index cd5ec4ae2..f373c643b 100644 --- a/packages/workspace/src/Infra/MySQL/MySQLWorkspaceRepository.spec.ts +++ b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceRepository.spec.ts @@ -28,4 +28,22 @@ describe('MySQLWorkspaceRepository', () => { expect(ormRepository.save).toHaveBeenCalledWith(workspace) }) + + it('should find many by uuids', async () => { + queryBuilder.where = jest.fn().mockReturnThis() + queryBuilder.getMany = jest.fn().mockReturnValue([]) + + await createRepository().findByUuids(['i-1-2-3']) + + expect(queryBuilder.where).toHaveBeenCalledWith('uuid IN (:...uuids)', { uuids: ['i-1-2-3'] }) + }) + + it('should find one by uuid', async () => { + queryBuilder.where = jest.fn().mockReturnThis() + queryBuilder.getOne = jest.fn().mockReturnValue(null) + + await createRepository().findOneByUuid('i-1-2-3') + + expect(queryBuilder.where).toHaveBeenCalledWith('uuid = :uuid', { uuid: 'i-1-2-3' }) + }) }) diff --git a/packages/workspace/src/Infra/MySQL/MySQLWorkspaceRepository.ts b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceRepository.ts index 17547ce33..6cbceaf07 100644 --- a/packages/workspace/src/Infra/MySQL/MySQLWorkspaceRepository.ts +++ b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceRepository.ts @@ -11,6 +11,14 @@ export class MySQLWorkspaceRepository implements WorkspaceRepositoryInterface { private ormRepository: Repository, ) {} + async findOneByUuid(uuid: string): Promise { + return this.ormRepository.createQueryBuilder().where('uuid = :uuid', { uuid }).getOne() + } + + async findByUuids(uuids: string[]): Promise { + return this.ormRepository.createQueryBuilder().where('uuid IN (:...uuids)', { uuids }).getMany() + } + async save(workspace: Workspace): Promise { return this.ormRepository.save(workspace) } diff --git a/packages/workspace/src/Infra/MySQL/MySQLWorkspaceUserRepository.spec.ts b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceUserRepository.spec.ts index 44dbcf215..7c596b133 100644 --- a/packages/workspace/src/Infra/MySQL/MySQLWorkspaceUserRepository.spec.ts +++ b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceUserRepository.spec.ts @@ -27,4 +27,22 @@ describe('MySQLWorkspaceUserRepository', () => { expect(ormRepository.save).toHaveBeenCalledWith(workspace) }) + + it('should find many by user uuid', async () => { + queryBuilder.where = jest.fn().mockReturnThis() + queryBuilder.getMany = jest.fn().mockReturnValue([]) + + await createRepository().findByUserUuid('i-1-2-3') + + expect(queryBuilder.where).toHaveBeenCalledWith('user_uuid = :userUuid', { userUuid: 'i-1-2-3' }) + }) + + it('should find many by workspace uuid', async () => { + queryBuilder.where = jest.fn().mockReturnThis() + queryBuilder.getMany = jest.fn().mockReturnValue([]) + + await createRepository().findByWorkspaceUuid('i-1-2-3') + + expect(queryBuilder.where).toHaveBeenCalledWith('workspace_uuid = :workspaceUuid', { workspaceUuid: 'i-1-2-3' }) + }) }) diff --git a/packages/workspace/src/Infra/MySQL/MySQLWorkspaceUserRepository.ts b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceUserRepository.ts index 1ccfcf4f7..1a7120e4c 100644 --- a/packages/workspace/src/Infra/MySQL/MySQLWorkspaceUserRepository.ts +++ b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceUserRepository.ts @@ -12,6 +12,14 @@ export class MySQLWorkspaceUserRepository implements WorkspaceUserRepositoryInte private ormRepository: Repository, ) {} + async findByWorkspaceUuid(workspaceUuid: string): Promise { + return this.ormRepository.createQueryBuilder().where('workspace_uuid = :workspaceUuid', { workspaceUuid }).getMany() + } + + async findByUserUuid(userUuid: string): Promise { + return this.ormRepository.createQueryBuilder().where('user_uuid = :userUuid', { userUuid }).getMany() + } + async save(workspaceUser: WorkspaceUser): Promise { return this.ormRepository.save(workspaceUser) } diff --git a/yarn.lock b/yarn.lock index 9f8a0971a..412f516c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1824,18 +1824,18 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/api@npm:^1.12.1": - version: 1.12.1 - resolution: "@standardnotes/api@npm:1.12.1" +"@standardnotes/api@npm:^1.15.0": + version: 1.15.0 + resolution: "@standardnotes/api@npm:1.15.0" dependencies: - "@standardnotes/common": ^1.36.1 - "@standardnotes/encryption": 1.16.2 - "@standardnotes/models": 1.24.2 - "@standardnotes/responses": 1.10.6 + "@standardnotes/common": ^1.39.0 + "@standardnotes/encryption": 1.17.0 + "@standardnotes/models": 1.26.0 + "@standardnotes/responses": 1.11.0 "@standardnotes/security": ^1.1.0 - "@standardnotes/utils": 1.9.1 + "@standardnotes/utils": 1.10.0 reflect-metadata: ^0.1.13 - checksum: 8623fc82de0cbe6793691bc50bf168d1ab2535516f71ffc10ac642abe6ab9ac2faef6cfe406350c2a1b6ea31e0ad34ad29cab804721a49500f6a1d3498cdd46e + checksum: 88ae0a340e175b8251e4960f377998a9e21ec5c7967be85c813ff2103d281faea8b13faf787ac51dab4d1737521df136748c9dbd34f0eee2f61d860085bca840 languageName: node linkType: hard @@ -1846,7 +1846,7 @@ __metadata: "@newrelic/winston-enricher": ^4.0.0 "@sentry/node": ^7.3.0 "@standardnotes/analytics": "workspace:*" - "@standardnotes/api": ^1.12.1 + "@standardnotes/api": ^1.15.0 "@standardnotes/common": "workspace:*" "@standardnotes/domain-events": "workspace:*" "@standardnotes/domain-events-infra": "workspace:*" @@ -1907,7 +1907,7 @@ __metadata: languageName: node linkType: hard -"@standardnotes/common@^1.19.1, @standardnotes/common@^1.23.1, @standardnotes/common@^1.32.0, @standardnotes/common@^1.36.1, @standardnotes/common@workspace:*, @standardnotes/common@workspace:^, @standardnotes/common@workspace:packages/common": +"@standardnotes/common@^1.19.1, @standardnotes/common@^1.23.1, @standardnotes/common@^1.32.0, @standardnotes/common@workspace:*, @standardnotes/common@workspace:^, @standardnotes/common@workspace:packages/common": version: 0.0.0-use.local resolution: "@standardnotes/common@workspace:packages/common" dependencies: @@ -1921,6 +1921,15 @@ __metadata: languageName: unknown linkType: soft +"@standardnotes/common@npm:^1.39.0": + version: 1.39.0 + resolution: "@standardnotes/common@npm:1.39.0" + dependencies: + reflect-metadata: ^0.1.13 + checksum: 92cfad04a631cdcf971516be4bf0e066e9cb6e894017102ac406cd8964282e088653ebed282d7624f371d617418edce124d12a62964723697ed7fa33b6d6e4d0 + languageName: node + linkType: hard + "@standardnotes/config@npm:2.4.3": version: 2.4.3 resolution: "@standardnotes/config@npm:2.4.3" @@ -1972,17 +1981,17 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/encryption@npm:1.16.2": - version: 1.16.2 - resolution: "@standardnotes/encryption@npm:1.16.2" +"@standardnotes/encryption@npm:1.17.0": + version: 1.17.0 + resolution: "@standardnotes/encryption@npm:1.17.0" dependencies: - "@standardnotes/common": ^1.36.1 - "@standardnotes/models": 1.24.2 - "@standardnotes/responses": 1.10.6 + "@standardnotes/common": ^1.39.0 + "@standardnotes/models": 1.26.0 + "@standardnotes/responses": 1.11.0 "@standardnotes/sncrypto-common": 1.13.0 - "@standardnotes/utils": 1.9.1 + "@standardnotes/utils": 1.10.0 reflect-metadata: ^0.1.13 - checksum: 50efc1b20105b06be2325d17440a0952e3fd47a596f99ea937446e6da895c349cb5ae449398c390829c2379c03a7cfa160e7f6b2d9bf3339ccb53fc34fa81c86 + checksum: 587516dfed87ba0bc46fef9ddcc6edb42757c140f852096d6bf6f7911418a7aa1eb0b370c5a0c56eeab3e6b13117ec055374b6e023c64e67cdf2362a584ac06e languageName: node linkType: hard @@ -2014,15 +2023,15 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/features@npm:1.52.4": - version: 1.52.4 - resolution: "@standardnotes/features@npm:1.52.4" +"@standardnotes/features@npm:1.53.0": + version: 1.53.0 + resolution: "@standardnotes/features@npm:1.53.0" dependencies: "@standardnotes/auth": ^3.19.4 - "@standardnotes/common": ^1.36.1 + "@standardnotes/common": ^1.39.0 "@standardnotes/security": ^1.2.0 reflect-metadata: ^0.1.13 - checksum: aea7b486275e9485c3d87b3db334c2b955f3ddd160054282e769aa59f026f6daffcb15edd8a3a4959a861552294df54437edb8d5b636f4e4f1c59eb74e75424b + checksum: a856e815a313d42706836b1e4c8bbaeb987ee86f2e53bf4b928ed401de483ab11ecd31a8bcb13b23cb7b6a53f72a9f481da2acf68d5e0e970af4ce8c8f2e7c65 languageName: node linkType: hard @@ -2099,17 +2108,17 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/models@npm:1.24.2": - version: 1.24.2 - resolution: "@standardnotes/models@npm:1.24.2" +"@standardnotes/models@npm:1.26.0, @standardnotes/models@npm:^1.26.0": + version: 1.26.0 + resolution: "@standardnotes/models@npm:1.26.0" dependencies: - "@standardnotes/common": ^1.36.1 - "@standardnotes/features": 1.52.4 - "@standardnotes/responses": 1.10.6 - "@standardnotes/utils": 1.9.1 + "@standardnotes/common": ^1.39.0 + "@standardnotes/features": 1.53.0 + "@standardnotes/responses": 1.11.0 + "@standardnotes/utils": 1.10.0 lodash: ^4.17.21 reflect-metadata: ^0.1.13 - checksum: 17b3cfba39c97f9e7b1960fe5cc0e6edd0ac0fbc492e266d7814972a6619465f8fb7ca6c90ac082de9592c83e2bbc2d883d9eaa076d4b283d93092e779a1af5c + checksum: f595a3de88ca815d04190ff188c1355ac1848369f94b81f7fc7be3a03918eaaa964eb078bb77622efd1ba7e2db1a47595db94e3ecc90c3544a54838a3980d6b0 languageName: node linkType: hard @@ -2138,15 +2147,15 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/responses@npm:1.10.6": - version: 1.10.6 - resolution: "@standardnotes/responses@npm:1.10.6" +"@standardnotes/responses@npm:1.11.0": + version: 1.11.0 + resolution: "@standardnotes/responses@npm:1.11.0" dependencies: - "@standardnotes/common": ^1.36.1 - "@standardnotes/features": 1.52.4 + "@standardnotes/common": ^1.39.0 + "@standardnotes/features": 1.53.0 "@standardnotes/security": ^1.1.0 reflect-metadata: ^0.1.13 - checksum: 0583e2cb77a23c22c7aa90e912c87b41fc8b8d236256cfa672e2a1272138974da0ebbbfa284049860adfed818e9c45a7f8ba169ff137b6b5c46a32a689320e6a + checksum: 46d6a479806507b15dc6f949e8863915f2cac81964aac715aab0bfc063e7bd7961767fcc6488e08397a0e1a52ccf2808429d8660b98a79415e25f601719125dc languageName: node linkType: hard @@ -2349,15 +2358,15 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/utils@npm:1.9.1": - version: 1.9.1 - resolution: "@standardnotes/utils@npm:1.9.1" +"@standardnotes/utils@npm:1.10.0": + version: 1.10.0 + resolution: "@standardnotes/utils@npm:1.10.0" dependencies: - "@standardnotes/common": ^1.36.1 + "@standardnotes/common": ^1.39.0 dompurify: ^2.3.8 lodash: ^4.17.21 reflect-metadata: ^0.1.13 - checksum: f775bb37447300bef1c02d911f7d132538bf52d8b65573129def50b0545b1a69af785b1d09932734ecc14a37e07cf1913e5e7b25866249a059bcea9fa7e9b18c + checksum: c02d54ca8a4debb62ecf6642825e1c313b3aad11767b1f0e6d36a976ec3058eba8592ab3194544042825296357676bc821e168489b638b0514bd1650dc9d0e0d languageName: node linkType: hard @@ -2378,10 +2387,11 @@ __metadata: dependencies: "@newrelic/winston-enricher": ^4.0.0 "@sentry/node": ^7.3.0 - "@standardnotes/api": ^1.12.1 + "@standardnotes/api": ^1.15.0 "@standardnotes/common": "workspace:*" "@standardnotes/domain-events": "workspace:^" "@standardnotes/domain-events-infra": "workspace:^" + "@standardnotes/models": ^1.26.0 "@standardnotes/security": "workspace:*" "@standardnotes/time": "workspace:^" "@types/cors": ^2.8.9