From 6bbccb21f8afefe4083e0f412c99d1708d6b54dd Mon Sep 17 00:00:00 2001 From: Gearonix Date: Thu, 27 Jul 2023 20:05:31 +0300 Subject: [PATCH 01/10] feat: added new sign-in-modal --- _templates/fsd-module/README.md | 4 ++ apps/client/src/app/styles/app.styles.ts | 1 + .../entities/sign-in-modal-template/index.ts | 1 + .../sign-in-modal-template/lib/helpers.ts | 1 + .../ui/sign-in-modal-template.styles.ts | 26 ++++++++++ .../ui/sign-in-modal-template.tsx | 46 ++++++++++++++++++ .../{hooks/index.ts => lib/helpers.ts} | 0 .../src/widgets/sign-in-modal/lib/index.ts | 0 .../client/src/widgets/sign-in-modal/types.ts | 4 ++ .../sign-in-modal/ui/sign-in-modal.styles.ts | 3 -- .../sign-in-modal/ui/sign-in-modal.tsx | 11 ++++- apps/client/tsconfig.app.json | 3 +- libs/client-shared/src/assets/index.ts | 1 + libs/client-shared/src/assets/logo.png | Bin 0 -> 5168 bytes libs/client-shared/src/ui/index.ts | 2 +- libs/client-shared/src/ui/modal/index.ts | 1 + .../src/ui/modal/ui/modal.styles.ts | 35 +++++++++++-- libs/client-shared/src/ui/modal/ui/modal.tsx | 14 +++++- .../widgets/settings/ui/settings.styles.ts | 14 ------ .../src/widgets/settings/ui/settings.tsx | 14 +++--- 20 files changed, 148 insertions(+), 33 deletions(-) create mode 100644 apps/client/src/entities/sign-in-modal-template/index.ts create mode 100644 apps/client/src/entities/sign-in-modal-template/lib/helpers.ts create mode 100644 apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts create mode 100644 apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.tsx rename apps/client/src/widgets/sign-in-modal/{hooks/index.ts => lib/helpers.ts} (100%) delete mode 100644 apps/client/src/widgets/sign-in-modal/lib/index.ts create mode 100644 apps/client/src/widgets/sign-in-modal/types.ts delete mode 100644 apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.styles.ts create mode 100644 libs/client-shared/src/assets/index.ts create mode 100644 libs/client-shared/src/assets/logo.png diff --git a/_templates/fsd-module/README.md b/_templates/fsd-module/README.md index 4e43f176..ed7e8c4a 100644 --- a/_templates/fsd-module/README.md +++ b/_templates/fsd-module/README.md @@ -6,3 +6,7 @@ This generator creates a module using the --- - new (General case) + +### Run + +`hygen fsd-module [method]` diff --git a/apps/client/src/app/styles/app.styles.ts b/apps/client/src/app/styles/app.styles.ts index 692e5dee..4e607800 100644 --- a/apps/client/src/app/styles/app.styles.ts +++ b/apps/client/src/app/styles/app.styles.ts @@ -18,6 +18,7 @@ export const GlobalStyles = createGlobalStyle` margin: 0; } + ${customScrollbar('body')} .ant-popconfirm { diff --git a/apps/client/src/entities/sign-in-modal-template/index.ts b/apps/client/src/entities/sign-in-modal-template/index.ts new file mode 100644 index 00000000..719fbb35 --- /dev/null +++ b/apps/client/src/entities/sign-in-modal-template/index.ts @@ -0,0 +1 @@ +export { SignInModalTemplate } from './ui/sign-in-modal-template' diff --git a/apps/client/src/entities/sign-in-modal-template/lib/helpers.ts b/apps/client/src/entities/sign-in-modal-template/lib/helpers.ts new file mode 100644 index 00000000..41d86c31 --- /dev/null +++ b/apps/client/src/entities/sign-in-modal-template/lib/helpers.ts @@ -0,0 +1 @@ +export const getFormItemRules = () => [{ required: true, min: 6, max: 14 }] diff --git a/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts b/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts new file mode 100644 index 00000000..857b04d0 --- /dev/null +++ b/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts @@ -0,0 +1,26 @@ +import { Form } from 'antd' +import styled from 'styled-components' + +import { ColorButton } from '$/client-shared' +import { wh } from '$/styles' + +export const SignInModalStyles = styled(Form)` + width: 84%; + margin: 0 auto; +` + +export const SubmitButton = styled(ColorButton)` + height: 40px; + width: 100%; + cursor: pointer; + font-size: ${({ theme }) => theme.fz7}; + margin: 0 auto; + margin-top: 80px; + display: block; +` + +export const LogoWrapper = styled.img` + ${wh('68px', '92px')} + margin: 10px auto; + display: block; +` diff --git a/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.tsx b/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.tsx new file mode 100644 index 00000000..fc122fd4 --- /dev/null +++ b/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.tsx @@ -0,0 +1,46 @@ +import { Form, Input } from 'antd' + +import { getFormItemRules } from '../lib/helpers' + +import { + LogoWrapper, + SignInModalStyles, + SubmitButton +} from './sign-in-modal-template.styles' + +import { Logo } from '$/assets' +import { ModalTitle } from '$/client-shared' + +interface SignInModalTemplateProps { + onSubmit: (data: T) => void +} + +export const SignInModalTemplate = ({ + onSubmit +}: SignInModalTemplateProps) => { + return ( + + + CodeGear + + + + + + + + + + + Sign In + + + ) +} diff --git a/apps/client/src/widgets/sign-in-modal/hooks/index.ts b/apps/client/src/widgets/sign-in-modal/lib/helpers.ts similarity index 100% rename from apps/client/src/widgets/sign-in-modal/hooks/index.ts rename to apps/client/src/widgets/sign-in-modal/lib/helpers.ts diff --git a/apps/client/src/widgets/sign-in-modal/lib/index.ts b/apps/client/src/widgets/sign-in-modal/lib/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/client/src/widgets/sign-in-modal/types.ts b/apps/client/src/widgets/sign-in-modal/types.ts new file mode 100644 index 00000000..f256c480 --- /dev/null +++ b/apps/client/src/widgets/sign-in-modal/types.ts @@ -0,0 +1,4 @@ +export interface SignInForm { + username: string + password: string +} diff --git a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.styles.ts b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.styles.ts deleted file mode 100644 index 300f5397..00000000 --- a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.styles.ts +++ /dev/null @@ -1,3 +0,0 @@ -import styled from 'styled-components' - -export const SignInModalStyles = styled.div`` diff --git a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx index 0f064d96..5859dce9 100644 --- a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx +++ b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx @@ -1,3 +1,6 @@ +import { SignInModalTemplate } from '@/entities/sign-in-modal-template' +import { SignInForm } from '@/widgets/sign-in-modal/types' + import { Modal, VoidFunction } from '$/client-shared' interface SignInModalProps { @@ -6,9 +9,13 @@ interface SignInModalProps { } export const SignInModal = ({ isOpen, onClose }: SignInModalProps) => { + const onSubmit = (data: SignInForm) => { + console.log(data) + } + return ( - - test + + onSubmit={onSubmit} /> ) } diff --git a/apps/client/tsconfig.app.json b/apps/client/tsconfig.app.json index a973aff1..9c964a27 100644 --- a/apps/client/tsconfig.app.json +++ b/apps/client/tsconfig.app.json @@ -9,10 +9,11 @@ "paths": { "@/*": ["./src/*"], "$/client-shared": ["../../libs/client-shared/src/index.ts"], + "$/assets": ["../../libs/client-shared/src/assets/index.ts"], "$/styles": ["../../libs/client-shared/src/styles/index.ts"], "$/editor": ["../../libs/editor/src/app/index.ts"], "react": ["../../node_modules/preact/compat"], - "react-dom": ["../../node_modules/preact/compat"] + "react-dom": ["../../node_modules/preact/compat"], } }, "exclude": [ diff --git a/libs/client-shared/src/assets/index.ts b/libs/client-shared/src/assets/index.ts new file mode 100644 index 00000000..2b619035 --- /dev/null +++ b/libs/client-shared/src/assets/index.ts @@ -0,0 +1 @@ +export { default as Logo } from './logo.png' diff --git a/libs/client-shared/src/assets/logo.png b/libs/client-shared/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5a30a9faa6f41e8a1b2d8dc86b15071455d24ec7 GIT binary patch literal 5168 zcmV-06wm94P)K3y_WMHfPflz;eH5%ZAl_11F`gcy_|3+hfqDg6Y|T|{teAP6x~kTUvq zYYhRm=qt_Brybl|SpY$ZL8_Hi-`RhFhzOi zYL5;GLIRT80NMfy0qXL4EfaqdvIP>IF9f%6TA_zhPB8yd@mT35oUL`4m z{TKux0nwIQd$a%8`%w|xCD^OAK@egPsj_PDs%mUh!uJcsCVXY3NmV~nO@vfeAugdS zD^=AMFo0B7Aui!6YeKL3))z&!<9_w&ItW4pLRD66xEi9u2Ha^GsnkMo2qjjGvQiZi zh0qki3Q?FW*3X?~NNM$n1#iGB@tza0@ZY(yG>ba2Lxe&@s-uhe)EY}Who}}yevg#AP9Z* z_$NzK+kC=amRHQUQHF?CV8)6Sr@sfa!W~_Dm6j%9BHBVT*0ZYHtydEd0%neCqw4O$ z!`cA|g(6t2tIoWd)33l@I$rOry;>85LJ%z0DI@_g=}>~umIPt;3HGe{IgMI*lOTA$ zwhF>bGH!xZft=A7?V-BU;su6P5N3*C8$jReSO){`qSNmQ+ZKzEf6R6fv=82g>qzu9 z8_c>`FYtY>s_!4d9d9R@kTbHm0K+PD*}CA+mgm} z=`gX^6-}~OtQ8QVAREI|U~*%&y0b&obq$0#Nc3fpFw3oS)+s&}bln}<^<{=oEib*e zCWFo_#Xz;O=WClFL?Uc|)>+tyZ5tAOMI>~acA03Uh8%bdN$4$t{o^5md1TjDNFrS= zq;9v4=pAzTTKY)zHG-)3U{y`r*dl~RqOSp>iItJubt=fNuQ+1mwnA5_x{4i`62*}%RqC<@mTA)Z(n@vpW}nCL6MSeZLcgDUI;5UgOc(N}V@G9p-8A&a1?+Mc&&!6KLuD>`XNKdRxZyzjAyec4AkL}S{x?*L#qa2#j z0+9X#`N7KQD}Ay0NR`ba9nT_Qch?4skeyh4IFm*4va+urSJ)7JWiM7ntU->MUi38` zvHFN;^GxQxcDiEq5z*$E%tUL4Sbapad4@&1l%}V(ORPRDS{Ig;_7)W?AmoNLCt`Mr zbrA0Dk7|t~6gftdA3CVqU3ggA0fX2tRv+&?yg9ILbc<*OI|iXU$4x?`qNJ~9Mk}3XKMT8;BE&@z!stBYvp1J zyGfK7WGdF5-u&ezg4!B*sI3rpfB5FUnI5pdML@VA%emGPTdKUOxJtHkd$G$E>7p z3EE*^n~GSk-TGn?T>XUp^a>>B8MyB5@#9}L!3458W)~4GfpM{3yS1@~2wcI;PH--K zZK-Y4BFO5P4TPF>851iH`|p&lKJa`<+K@#s9(~E`*mP0w46l-4$Kwf zH11F1l1}th6^oE59h`HokFo=?#mW`6T*f17ggQ)m(U(|+ltF-P*jw2eems^~tr+^l z)28UFWiuV_i63&V-cY>er~!}g*;E9DPwz5i9 z9gk9Q@Z;HO&B(birs2%PYgiVGa2Z<7dJ13FmdoQ`rDb>uf^30{YS)-hnO;QGq$=Bl z&z)t^YE7aqE(-c1gfW$MrgPN<(0>{U3TYwXzR^BTfynBZ2d`!Yde$pfx>XP^gRpyk ze!7sEP#NCLap7xSj&M3qM9$v%yTHo&;jN7)3PC!YfLBf1Gzd{Oz1s#G;OMJ&FGFF@ zj&ULoEY_PFudLmxnib?rm5b}fj?EZG-QIr!3JXzCcvKl_1Lkjiu~=lGvM)udq-STR z*KHLlgQwjZ-w+4|VPqOma#bb%8G?MnLw@;O)xI|s{HIBi(u4XwEeB&23P3<~RlvA} zhI~X-v&NT(1flzaa#<(>Bmb3J!ADReXqL}Ie%^#yG)7id1e{j|@B!>iHDb+XUpff| zrJs5Kk^&H911d1%uuzq9mwtF;Xba#p@D>b;;a9Q;1%SNx>HrJ^-)n)tu9cs@Uc@nDWq7*<}GuJPRI*XP4%7q2b8CB&xQ&V z49dT~xkWL>N)yO_3witDO)FGQ5iwr7`W9NId=HO$2V*SY{ z8#`LT8VHj~rx|gjz|Ae*s;bPGqu5_o72qKV;YsHnt_l>a^*bu}7x1w5*6EG0XIG?m zKmyW;z6#L#eXx1(Fr{&CeDCM~@~$)ni_IbltN#Aa|F!!5Km4=$%kO`iT(rM?`V4+| z(1L%u@hv-M1vA%?r9*P9(Z6$KFNCp`mABEA%U7REbu~ab&4??rbnD@`ds~@_M_50+ z`AhoGP|{G?B&P4-VsajR1zmwTNQgTHq`Ibv*?YK{9QJi3q3)7UC+^n`)f_;oYswTj z`tnxr_EuKIyjJePvWbF4Ur~h0gchsht{G$c3VMd4FC>eg)YMais6u5T^#~$6&>%bT z0E8?_;~uW+G_N!Y>rsb^+|X(!ZpF?cGZKB#>eM=|PQ_|-A6cxN4qPc+Jre&qMNCIu z*<5MV>2FSHLrh||3iP%R7W;}!TWBl9U87wPQ;5D0@c`bXm8t&i)^Uj?Rz?JC5w=EO z2q&oB?YP7KFtNqTh+r)vt?0`oi=Z`c7p;VSo3|ll4%d@b(?Jr6l@Y;O%JibI#NITB z#A0PcuvYVoqOaX7qAwEZHlHnnlrSPzrk4`Dvja*Mac8^zq!@kC4C*7$xIJpav`LJK z)rVHP$dse(Be0n?c70`laj`OD3vz?`=qp815i29MAV*wSgtG&qSJE3MHL?0!yY=NN zxMT~Y%GysF(U(C|7psqW*_@F^^p%jz#LD!%EcbU3Ymg(%M_)0?RIEPU-dIFOtU-?8 z>+BJmKc4H>v@F6@#Ofn{Hm6J}`kJa(eMGdmWhcwV%T|chhwT9^+HES?+?^lD9%#yH z|M=*>k)2iJzm$c@L@bARv6uUc)H;QOrDFAA(VmsCp#g4)MaT&$`dO>RI*9n%96?nU zQPo~Py4wbWt}% zoa?TNMX-(&Q0`J(!A?i4e3>qkHs88P7x~&Ab>KpG+k|L+UyDlJkd~d+5L>X>ij}W` zJS}Y^Vu)Uxs_F8jT?S4`dZN8T>48`TTR5R*+Urq0Y-VB|81kc4I0erT-QD3kk>bL> zmVQ`PB>I{%F(zH- zNU-Z+UYLqlV-NWe@qBH!y9>XrCEh&Y9dNudw=BLkdl1x8n59{>Vl_A9M?~>O5-sty zImPp@r`IQ)tTiK69`aw89w6WU>ca0=gUH=V5QEtPsAwMdeerv`i7(jfjS;l$SaANgl`Pu=PsiCjq z?L)xst|?zDTNRB!-t}~0RCOIM*4z&H(GXv27eTq}Xt8=xRC#R%hWv<-uT@lO4K>A; zCEb_=Otjt>>ue7BQAx$u%2vhDoX`^N2g47(5i1Y*P;#Do?uzTNQQx_a+Dv;anBx*qhoR z)IkP)tv4B)0bi@Ms$BZ~;)DABq3jD74NsuRK&_iN@|_j4u)lVABX;@C6WwjG0z+gx zfig*dGRA-JomrWT>5GqgLt6&~5BjNYPICff5`QvAezuk~dvGZR)PtY&dr1`?oE{I& zE&bNC^;o%5-Ak2q^ck$WvqOSigIOoX6R0$>O|-+iuJp)Py@oBA@yQkqf4j3XfieXz zF!071OIF3+iV1^{VtdE1_0bG&%0nk zFh!U^nSv|r)Y5~l`gZuC$sJklI1Q?>4r^Y^dNh%MfPANMbSQ^$<_7Y2X&~o?$x&PwqS5EffAt_hwQ;tsjHUKI=w(6 z;mAKXTFcANon@u8WBH^@CQupD+c1NU$+&Bj1z@6ymGL^a=tHor*FbN`=kxqqAY_gy zKg*HX%Yf^K!r6d9I3?IyS+K?y__O@pkiP}ORN&RHv+n72%4iemLL=@5Jr}*Q4l>Ou z>sQh_*qOBrCwZrLY5@-;SJ@GbEiON8g1WQXPYWuLHX_p8;H3t%d>i@=uBshB_Vz1` z#Ee*d>P{6QhJK|jpr zm5inY`uS#3l?_S&21!S(!*IoQVG*>hP&W6oo_JN|@{6zoym`jO@4VmNgSJ%A*&?XQ z@}!Kj+ez=wMy!G`z)wEe*wG67EvOTRf1eTvLdN{{_y7OD@Bib!y`W5WzI?9!` display: grid; position: relative; - ${wh('50vw', '60vh')}; + width: ${({ $width }) => $width ?? 50}vw; + height: ${({ $height }) => $height ?? 60}vh; background: ${color('grey')}; border: 2px solid ${color('lightGrey')}; min-height: 300px; @@ -38,3 +52,18 @@ export const ModalContainer = styled.div` color: ${color('light')}; } ` + +export const ModalTitle = styled.h1` + text-align: center; + padding-bottom: 18px; + margin-bottom: 30px; + font-size: ${({ theme }) => theme.fz10}; + border-bottom: ${br} ${color('lightGrey')}; +` + +export const ModalSeparator = styled.div` + background: ${color('lightGrey')}; + ${wh('100%', '2px')}; + margin-bottom: 30px; + margin-top: -8px; +` diff --git a/libs/client-shared/src/ui/modal/ui/modal.tsx b/libs/client-shared/src/ui/modal/ui/modal.tsx index 3cb84955..c07d155a 100644 --- a/libs/client-shared/src/ui/modal/ui/modal.tsx +++ b/libs/client-shared/src/ui/modal/ui/modal.tsx @@ -12,9 +12,17 @@ import { ModalBackground, ModalContainer, ModalStyles } from './modal.styles' type ModalProps = WithChildren<{ isOpen: boolean onClose: () => void + width?: number + height?: number }> -export const Modal = ({ onClose, isOpen, children }: ModalProps) => { +export const Modal = ({ + onClose, + isOpen, + children, + width, + height +}: ModalProps) => { const { Spring, Gesture } = useAnimations() const { opacity, transform } = useModalTransitions() @@ -65,7 +73,9 @@ export const Modal = ({ onClose, isOpen, children }: ModalProps) => { style={{ ...modalStyle, x, y, scale }} {...bind()} onClick={stopPropagation} - as={Spring.a.div}> + as={Spring.a.div} + $width={width} + $height={height}> theme.fz10}; - border-bottom: ${br} ${color('lightGrey')}; -` diff --git a/libs/editor/src/widgets/settings/ui/settings.tsx b/libs/editor/src/widgets/settings/ui/settings.tsx index 263ec6c8..26587abc 100644 --- a/libs/editor/src/widgets/settings/ui/settings.tsx +++ b/libs/editor/src/widgets/settings/ui/settings.tsx @@ -10,9 +10,9 @@ import { useActions, useModalsContext, useStore } from '@/shared/hooks' import { useColorCallback } from '../hooks' -import { Separator, SettingsItem, SettingsText, Title } from './settings.styles' +import { SettingsItem, SettingsText } from './settings.styles' -import { Modal } from '$/client-shared' +import { ModalSeparator, ModalTitle, Modal } from '$/client-shared' const Settings = observer(() => { const modalsContext = useModalsContext() @@ -40,7 +40,7 @@ const Settings = observer(() => { return ( - Editor settings + Editor settings

Editor Theme

@@ -69,7 +69,7 @@ const Settings = observer(() => {
- +

Custom Editor Background

@@ -94,7 +94,7 @@ const Settings = observer(() => { size="large" />
- +

Save editor settings to Local Storage

@@ -106,9 +106,9 @@ const Settings = observer(() => { style={{ marginRight: 15, marginTop: 0 }} />
- Key buildings + Key buildings - +
) }) From 97ed032710231dc16490291ba176a7b50a4a0493 Mon Sep 17 00:00:00 2001 From: Gearonix Date: Thu, 27 Jul 2023 21:03:44 +0300 Subject: [PATCH 02/10] chore: initialized AuthStore and AuthServices --- apps/client/src/app/index.ts | 1 + .../src/app/providers/router/config/router.tsx | 10 ++-------- .../src/app/providers/store/config/store.ts | 4 ++++ apps/client/src/app/providers/store/index.ts | 3 ++- apps/client/src/entities/.gitkeep | 0 apps/client/src/pages/edit/index.ts | 1 + apps/client/src/pages/edit/ui/EditPage.tsx | 15 +++++++++++++++ apps/client/src/shared/hooks/index.ts | 1 + apps/client/src/shared/hooks/useStore.ts | 7 +++++++ apps/client/src/widgets/sign-in-modal/index.ts | 1 + .../src/widgets/sign-in-modal/lib/helpers.ts | 0 .../src/widgets/sign-in-modal/store/.gitkeep | 0 .../widgets/sign-in-modal/store/auth.services.ts | 13 +++++++++++++ .../src/widgets/sign-in-modal/store/auth.store.ts | 14 ++++++++++++++ .../widgets/sign-in-modal/ui/sign-in-modal.tsx | 11 ++++++++--- 15 files changed, 69 insertions(+), 12 deletions(-) delete mode 100644 apps/client/src/entities/.gitkeep create mode 100644 apps/client/src/pages/edit/index.ts create mode 100644 apps/client/src/pages/edit/ui/EditPage.tsx create mode 100644 apps/client/src/shared/hooks/index.ts create mode 100644 apps/client/src/shared/hooks/useStore.ts delete mode 100644 apps/client/src/widgets/sign-in-modal/lib/helpers.ts delete mode 100644 apps/client/src/widgets/sign-in-modal/store/.gitkeep create mode 100644 apps/client/src/widgets/sign-in-modal/store/auth.services.ts create mode 100644 apps/client/src/widgets/sign-in-modal/store/auth.store.ts diff --git a/apps/client/src/app/index.ts b/apps/client/src/app/index.ts index f9379e42..d531cba8 100644 --- a/apps/client/src/app/index.ts +++ b/apps/client/src/app/index.ts @@ -1 +1,2 @@ export { default as App } from './app' +export { RootStore, StoreContext } from './providers/store' diff --git a/apps/client/src/app/providers/router/config/router.tsx b/apps/client/src/app/providers/router/config/router.tsx index 2c95f2ae..94a35de4 100644 --- a/apps/client/src/app/providers/router/config/router.tsx +++ b/apps/client/src/app/providers/router/config/router.tsx @@ -1,13 +1,11 @@ -import { Suspense } from 'react' import { createBrowserRouter } from 'react-router-dom' import { About } from '@/pages/about' +import { EditPage } from '@/pages/edit' import { Main } from '@/pages/main' import { NotFound } from '@/pages/not-found' -import { SignInModal } from '@/widgets/sign-in-modal' import { RoutePaths } from '$/client-shared' -import { Editor } from '$/editor' const router = createBrowserRouter([ { @@ -16,11 +14,7 @@ const router = createBrowserRouter([ }, { path: RoutePaths.EDITOR, - element: ( - - - - ) + element: }, { path: RoutePaths.ABOUT, diff --git a/apps/client/src/app/providers/store/config/store.ts b/apps/client/src/app/providers/store/config/store.ts index 944d6f77..0771eb9c 100644 --- a/apps/client/src/app/providers/store/config/store.ts +++ b/apps/client/src/app/providers/store/config/store.ts @@ -1,13 +1,17 @@ import { makeAutoObservable } from 'mobx' +import { AuthStore } from '@/widgets/sign-in-modal' + import { EditorStore } from '$/editor' class RootStore { editor: EditorStore + auth: AuthStore constructor() { makeAutoObservable(this) this.editor = new EditorStore() + this.auth = new AuthStore() } } diff --git a/apps/client/src/app/providers/store/index.ts b/apps/client/src/app/providers/store/index.ts index 6abf2234..94b6a9d8 100644 --- a/apps/client/src/app/providers/store/index.ts +++ b/apps/client/src/app/providers/store/index.ts @@ -1 +1,2 @@ -export { default as StoreProvider } from './ui/store-provider' +export { default as RootStore } from './config/store' +export { StoreContext, default as StoreProvider } from './ui/store-provider' diff --git a/apps/client/src/entities/.gitkeep b/apps/client/src/entities/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/client/src/pages/edit/index.ts b/apps/client/src/pages/edit/index.ts new file mode 100644 index 00000000..1822a0ee --- /dev/null +++ b/apps/client/src/pages/edit/index.ts @@ -0,0 +1 @@ +export { default as EditPage } from './ui/EditPage' diff --git a/apps/client/src/pages/edit/ui/EditPage.tsx b/apps/client/src/pages/edit/ui/EditPage.tsx new file mode 100644 index 00000000..7f70b72c --- /dev/null +++ b/apps/client/src/pages/edit/ui/EditPage.tsx @@ -0,0 +1,15 @@ +import { Suspense } from 'react' + +import { SignInModal } from '@/widgets/sign-in-modal' + +import { Editor } from '$/editor' + +const EditPage = () => { + return ( + + + + ) +} + +export default EditPage diff --git a/apps/client/src/shared/hooks/index.ts b/apps/client/src/shared/hooks/index.ts new file mode 100644 index 00000000..48b0d364 --- /dev/null +++ b/apps/client/src/shared/hooks/index.ts @@ -0,0 +1 @@ +export { useStore } from './useStore' diff --git a/apps/client/src/shared/hooks/useStore.ts b/apps/client/src/shared/hooks/useStore.ts new file mode 100644 index 00000000..92685dd9 --- /dev/null +++ b/apps/client/src/shared/hooks/useStore.ts @@ -0,0 +1,7 @@ +import { useContext } from 'react' + +import { RootStore, StoreContext } from '@/app' + +export const useStore = (name: T) => { + return useContext(StoreContext)[name] +} diff --git a/apps/client/src/widgets/sign-in-modal/index.ts b/apps/client/src/widgets/sign-in-modal/index.ts index 697de78b..430ac96a 100644 --- a/apps/client/src/widgets/sign-in-modal/index.ts +++ b/apps/client/src/widgets/sign-in-modal/index.ts @@ -1 +1,2 @@ +export { AuthStore } from './store/auth.store' export { SignInModal } from './ui/sign-in-modal' diff --git a/apps/client/src/widgets/sign-in-modal/lib/helpers.ts b/apps/client/src/widgets/sign-in-modal/lib/helpers.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/client/src/widgets/sign-in-modal/store/.gitkeep b/apps/client/src/widgets/sign-in-modal/store/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/client/src/widgets/sign-in-modal/store/auth.services.ts b/apps/client/src/widgets/sign-in-modal/store/auth.services.ts new file mode 100644 index 00000000..4017954b --- /dev/null +++ b/apps/client/src/widgets/sign-in-modal/store/auth.services.ts @@ -0,0 +1,13 @@ +import { makeAutoObservable } from 'mobx' + +import { SignInForm } from '@/widgets/sign-in-modal/types' + +export class AuthServices { + constructor() { + makeAutoObservable(this) + } + + signIn(payload: SignInForm) { + console.log(payload) + } +} diff --git a/apps/client/src/widgets/sign-in-modal/store/auth.store.ts b/apps/client/src/widgets/sign-in-modal/store/auth.store.ts new file mode 100644 index 00000000..b2291dc5 --- /dev/null +++ b/apps/client/src/widgets/sign-in-modal/store/auth.store.ts @@ -0,0 +1,14 @@ +import { makeAutoObservable } from 'mobx' + +import { AuthServices } from './auth.services' + +export class AuthStore { + username: string + isAuthorized: boolean + readonly services: AuthServices + + constructor() { + makeAutoObservable(this) + this.services = new AuthServices() + } +} diff --git a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx index 5859dce9..0931d5bb 100644 --- a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx +++ b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx @@ -1,4 +1,7 @@ +import { observer } from 'mobx-react-lite' + import { SignInModalTemplate } from '@/entities/sign-in-modal-template' +import { useStore } from '@/shared/hooks' import { SignInForm } from '@/widgets/sign-in-modal/types' import { Modal, VoidFunction } from '$/client-shared' @@ -8,9 +11,11 @@ interface SignInModalProps { onClose: VoidFunction } -export const SignInModal = ({ isOpen, onClose }: SignInModalProps) => { +export const SignInModal = observer(({ isOpen, onClose }: SignInModalProps) => { + const auth = useStore('auth') + const onSubmit = (data: SignInForm) => { - console.log(data) + auth.services.signIn(data) } return ( @@ -18,4 +23,4 @@ export const SignInModal = ({ isOpen, onClose }: SignInModalProps) => { onSubmit={onSubmit} />
) -} +}) From 491971d7dbb2fe4c9a7915e1c0bfef8d21acc215 Mon Sep 17 00:00:00 2001 From: Gearonix Date: Fri, 28 Jul 2023 16:06:42 +0300 Subject: [PATCH 03/10] chore: initialized GraphQL Nest.js config, made first Apollo request to server from client --- .eslintignore | 1 - .eslintrc.js | 6 +++-- .nxignore | 2 ++ .prettierignore | 3 --- .../sign-in-modal/graphql/sign-in.mutation.ts | 15 +++++++++++ .../sign-in-modal/store/auth.services.ts | 10 +++++--- .../client/src/widgets/sign-in-modal/types.ts | 5 ++++ .../sign-in-modal/ui/sign-in-modal.tsx | 2 +- apps/server/src/_schema.gql | 21 ++++++++++++++++ apps/server/src/app.module.ts | 17 ++++++++++++- apps/server/src/decorators/index.ts | 1 + .../src/decorators/string-field.decorator.ts | 25 +++++++++++++++++++ .../http-exception.filter.ts | 6 +++++ .../src/modules/authorization/auth.module.ts | 9 +++++++ .../modules/authorization/auth.resolver.ts | 24 ++++++++++++++++++ .../src/modules/authorization/auth.service.ts | 0 .../src/modules/authorization/dto/index.ts | 2 ++ .../authorization/dto/sign-in.input.ts | 11 ++++++++ .../modules/authorization/dto/user.entity.ts | 13 ++++++++++ .../server/src/modules/authorization/index.ts | 1 + .../client-shared/src/config/apollo-client.ts | 12 +++++++++ libs/client-shared/src/config/index.ts | 1 + libs/client-shared/src/index.ts | 1 + libs/client-shared/src/lib/apollo/index.ts | 1 + libs/client-shared/src/lib/apollo/mutate.ts | 20 +++++++++++++++ .../client-shared/src/types/common/graphql.ts | 6 +++++ libs/client-shared/src/types/index.ts | 1 + libs/config/src/lib/server.ts | 5 +++- package.json | 8 ++++++ 29 files changed, 217 insertions(+), 12 deletions(-) delete mode 100644 .eslintignore create mode 100644 .nxignore delete mode 100644 .prettierignore create mode 100644 apps/client/src/widgets/sign-in-modal/graphql/sign-in.mutation.ts create mode 100644 apps/server/src/_schema.gql create mode 100644 apps/server/src/decorators/index.ts create mode 100644 apps/server/src/decorators/string-field.decorator.ts create mode 100644 apps/server/src/modules/authorization/auth.module.ts create mode 100644 apps/server/src/modules/authorization/auth.resolver.ts create mode 100644 apps/server/src/modules/authorization/auth.service.ts create mode 100644 apps/server/src/modules/authorization/dto/index.ts create mode 100644 apps/server/src/modules/authorization/dto/sign-in.input.ts create mode 100644 apps/server/src/modules/authorization/dto/user.entity.ts create mode 100644 apps/server/src/modules/authorization/index.ts create mode 100644 libs/client-shared/src/config/apollo-client.ts create mode 100644 libs/client-shared/src/lib/apollo/index.ts create mode 100644 libs/client-shared/src/lib/apollo/mutate.ts create mode 100644 libs/client-shared/src/types/common/graphql.ts diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 3c3629e6..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/.eslintrc.js b/.eslintrc.js index ad06826f..9e094e62 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,7 +21,7 @@ module.exports = configure({ extend: { root: true, ignorePatterns: ["**/*"], - plugins: ["@nx"], + plugins: ["@nx", "prefer-arrow"], // My custom config extends: ["gearonix"], rules: { @@ -35,7 +35,9 @@ module.exports = configure({ "react-hooks/exhaustive-deps": "warn", "react/no-array-index-key": "warn", "@typescript-eslint/no-explicit-any": "warn", - "dot-notation": "off" + "dot-notation": "off", + "prefer-arrow/prefer-arrow-functions": "error", + "@typescript-eslint/no-unused-vars": "warn" } } }) diff --git a/.nxignore b/.nxignore new file mode 100644 index 00000000..6a38120c --- /dev/null +++ b/.nxignore @@ -0,0 +1,2 @@ +# Added to avoid causing additional problems when serving nestjs applications. +_schema.gql diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 9481e77e..00000000 --- a/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -# Add files here to ignore them from prettier formatting -/dist -/coverage \ No newline at end of file diff --git a/apps/client/src/widgets/sign-in-modal/graphql/sign-in.mutation.ts b/apps/client/src/widgets/sign-in-modal/graphql/sign-in.mutation.ts new file mode 100644 index 00000000..7b600d9a --- /dev/null +++ b/apps/client/src/widgets/sign-in-modal/graphql/sign-in.mutation.ts @@ -0,0 +1,15 @@ +import { gql } from '@apollo/react-hooks' + +import { ApolloMutation } from '$/client-shared' + +export const SignInMutation: ApolloMutation = { + gql: gql` + mutation SignIn($payload: SignIn!) { + signIn(_graphql: $payload) { + username + avatarUrl + } + } + `, + method: 'signIn' +} diff --git a/apps/client/src/widgets/sign-in-modal/store/auth.services.ts b/apps/client/src/widgets/sign-in-modal/store/auth.services.ts index 4017954b..b5c9422a 100644 --- a/apps/client/src/widgets/sign-in-modal/store/auth.services.ts +++ b/apps/client/src/widgets/sign-in-modal/store/auth.services.ts @@ -1,13 +1,17 @@ import { makeAutoObservable } from 'mobx' -import { SignInForm } from '@/widgets/sign-in-modal/types' +import { SignInMutation } from '../graphql/sign-in.mutation' +import { SignInForm, SignInResponse } from '../types' + +import { mutate } from '$/client-shared' export class AuthServices { constructor() { makeAutoObservable(this) } - signIn(payload: SignInForm) { - console.log(payload) + async signIn(form: SignInForm) { + const res = await mutate(SignInMutation, form) + console.log(res) } } diff --git a/apps/client/src/widgets/sign-in-modal/types.ts b/apps/client/src/widgets/sign-in-modal/types.ts index f256c480..9879f2ef 100644 --- a/apps/client/src/widgets/sign-in-modal/types.ts +++ b/apps/client/src/widgets/sign-in-modal/types.ts @@ -2,3 +2,8 @@ export interface SignInForm { username: string password: string } + +export interface SignInResponse { + username: string + avatarUrl: string +} diff --git a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx index 0931d5bb..ae586a70 100644 --- a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx +++ b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx @@ -19,7 +19,7 @@ export const SignInModal = observer(({ isOpen, onClose }: SignInModalProps) => { } return ( - + onSubmit={onSubmit} /> ) diff --git a/apps/server/src/_schema.gql b/apps/server/src/_schema.gql new file mode 100644 index 00000000..8ad00bd7 --- /dev/null +++ b/apps/server/src/_schema.gql @@ -0,0 +1,21 @@ +# ------------------------------------------------------ +# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) +# ------------------------------------------------------ + +type User { + username: String! + avatarUrl: String! +} + +type Query { + sayHello: String! +} + +type Mutation { + signIn(_graphql: SignIn!): User! +} + +input SignIn { + username: String! + password: String! +} \ No newline at end of file diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index b4aad00f..4bb7b32c 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -1,10 +1,25 @@ +import { join } from 'path' + +import { AuthModule } from '@/modules/authorization' +import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo' import { Module } from '@nestjs/common' import { ConfigModule } from '@nestjs/config' +import { GraphQLModule } from '@nestjs/graphql' import { CodeExecutorModule } from './modules/code-executor-api' +import { clientUrl } from '$/config' + @Module({ - imports: [ConfigModule.forRoot(), CodeExecutorModule], + imports: [ + CodeExecutorModule, + AuthModule, + ConfigModule.forRoot(), + GraphQLModule.forRoot({ + driver: ApolloDriver, + autoSchemaFile: join(process.cwd(), 'apps/server/src/_schema.gql') + }) + ], controllers: [], providers: [] }) diff --git a/apps/server/src/decorators/index.ts b/apps/server/src/decorators/index.ts new file mode 100644 index 00000000..f7dc16d1 --- /dev/null +++ b/apps/server/src/decorators/index.ts @@ -0,0 +1 @@ +export { StringField } from './string-field.decorator' diff --git a/apps/server/src/decorators/string-field.decorator.ts b/apps/server/src/decorators/string-field.decorator.ts new file mode 100644 index 00000000..000cd720 --- /dev/null +++ b/apps/server/src/decorators/string-field.decorator.ts @@ -0,0 +1,25 @@ +import { IsString, MaxLength, MinLength } from 'class-validator'; + +import { applyDecorators } from '@nestjs/common' +import { Field } from '@nestjs/graphql' +import { ApiProperty } from '@nestjs/swagger' + +type StringFieldPayload = Partial<{ + min: number + max: number + example: string +}> + +export const StringField = ({ + min = 6, + max = 14, + example +}: StringFieldPayload) => { + return applyDecorators( + Field(), + IsString(), + MinLength(min), + MaxLength(max), + ApiProperty({ example }) + ) +} diff --git a/apps/server/src/exception-filters/http-exception.filter.ts b/apps/server/src/exception-filters/http-exception.filter.ts index f288b3e0..88ed585c 100644 --- a/apps/server/src/exception-filters/http-exception.filter.ts +++ b/apps/server/src/exception-filters/http-exception.filter.ts @@ -7,6 +7,8 @@ import { HttpException } from '@nestjs/common' +import { graphqlArg } from '$/config' + @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { @@ -15,6 +17,10 @@ export class HttpExceptionFilter implements ExceptionFilter { const request = ctx.getRequest() const status = exception.getStatus() + if (graphqlArg in response) { + return + } + response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), diff --git a/apps/server/src/modules/authorization/auth.module.ts b/apps/server/src/modules/authorization/auth.module.ts new file mode 100644 index 00000000..ce40e82e --- /dev/null +++ b/apps/server/src/modules/authorization/auth.module.ts @@ -0,0 +1,9 @@ +import { AuthResolver } from '@/modules/authorization/auth.resolver' +import { Module } from '@nestjs/common' + +@Module({ + controllers: [], + providers: [AuthResolver], + imports: [] +}) +export class AuthModule {} diff --git a/apps/server/src/modules/authorization/auth.resolver.ts b/apps/server/src/modules/authorization/auth.resolver.ts new file mode 100644 index 00000000..884209c0 --- /dev/null +++ b/apps/server/src/modules/authorization/auth.resolver.ts @@ -0,0 +1,24 @@ +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql' + +import { SignIn, User } from './dto' + +import { graphqlArg } from '$/config' + +@Resolver(() => User) +export class AuthResolver { + @Mutation(() => User) + async signIn(@Args(graphqlArg) payload: SignIn): Promise { + console.log(payload) + return { + avatarUrl: 'test', + username: 'test2' + } + } + + // Graphql requires at least one @Query to + // work so for now this will be here lol + @Query(() => String) + sayHello(): string { + return 'Hello World!' + } +} diff --git a/apps/server/src/modules/authorization/auth.service.ts b/apps/server/src/modules/authorization/auth.service.ts new file mode 100644 index 00000000..e69de29b diff --git a/apps/server/src/modules/authorization/dto/index.ts b/apps/server/src/modules/authorization/dto/index.ts new file mode 100644 index 00000000..55707104 --- /dev/null +++ b/apps/server/src/modules/authorization/dto/index.ts @@ -0,0 +1,2 @@ +export { SignIn } from './sign-in.input' +export { User } from './user.entity' diff --git a/apps/server/src/modules/authorization/dto/sign-in.input.ts b/apps/server/src/modules/authorization/dto/sign-in.input.ts new file mode 100644 index 00000000..9b13b23a --- /dev/null +++ b/apps/server/src/modules/authorization/dto/sign-in.input.ts @@ -0,0 +1,11 @@ +import { StringField } from '@/decorators' +import { InputType } from '@nestjs/graphql' + +@InputType() +export class SignIn { + @StringField({ example: 'user123' }) + username: string + + @StringField({ example: 'password456' }) + password: string +} diff --git a/apps/server/src/modules/authorization/dto/user.entity.ts b/apps/server/src/modules/authorization/dto/user.entity.ts new file mode 100644 index 00000000..3595f1ff --- /dev/null +++ b/apps/server/src/modules/authorization/dto/user.entity.ts @@ -0,0 +1,13 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { ApiProperty } from '@nestjs/swagger' + +@ObjectType() +export class User { + @Field() + @ApiProperty({ description: 'Username (used as userId)' }) + username: string + + @Field() + @ApiProperty({ description: 'User avatar location' }) + avatarUrl: string +} diff --git a/apps/server/src/modules/authorization/index.ts b/apps/server/src/modules/authorization/index.ts new file mode 100644 index 00000000..c4707a8c --- /dev/null +++ b/apps/server/src/modules/authorization/index.ts @@ -0,0 +1 @@ +export { AuthModule } from './auth.module' diff --git a/libs/client-shared/src/config/apollo-client.ts b/libs/client-shared/src/config/apollo-client.ts new file mode 100644 index 00000000..f32fbc4a --- /dev/null +++ b/libs/client-shared/src/config/apollo-client.ts @@ -0,0 +1,12 @@ +import { ApolloClient, InMemoryCache } from '@apollo/client' + +import { EndPoints, serverUrl } from '$/config' + +const graphqlUri = `${serverUrl}/${EndPoints._GRAPHQL}` + +const apolloClient = new ApolloClient({ + cache: new InMemoryCache(), + uri: graphqlUri +}) + +export default apolloClient diff --git a/libs/client-shared/src/config/index.ts b/libs/client-shared/src/config/index.ts index e06bd5a8..005e43c1 100644 --- a/libs/client-shared/src/config/index.ts +++ b/libs/client-shared/src/config/index.ts @@ -1,3 +1,4 @@ +export { default as apolloClient } from './apollo-client' export { default as httpService } from './axios' export { LocalStorage } from './local-storage' export { RoutePaths } from './paths' diff --git a/libs/client-shared/src/index.ts b/libs/client-shared/src/index.ts index f0df7be2..346c5e51 100644 --- a/libs/client-shared/src/index.ts +++ b/libs/client-shared/src/index.ts @@ -1,5 +1,6 @@ export * from './config' export * from './hooks' +export * from './lib/apollo' export * from './lib/components' export * from './lib/helpers' export * from './lib/icons' diff --git a/libs/client-shared/src/lib/apollo/index.ts b/libs/client-shared/src/lib/apollo/index.ts new file mode 100644 index 00000000..d1675506 --- /dev/null +++ b/libs/client-shared/src/lib/apollo/index.ts @@ -0,0 +1 @@ +export { mutate } from './mutate' diff --git a/libs/client-shared/src/lib/apollo/mutate.ts b/libs/client-shared/src/lib/apollo/mutate.ts new file mode 100644 index 00000000..fd302820 --- /dev/null +++ b/libs/client-shared/src/lib/apollo/mutate.ts @@ -0,0 +1,20 @@ +import { apolloClient } from '@/config' +import { AnyObject, ApolloMutation } from '@/types' + +type WithTypeName

= P & { __typename: string } + +// type Operation = Extract + +export const mutate = async ( + mutation: ApolloMutation, + args: Args +): Promise> => { + const response = await apolloClient.mutate({ + mutation: mutation.gql, + variables: { + payload: args + } + }) + + return response.data?.[mutation.method] as WithTypeName +} diff --git a/libs/client-shared/src/types/common/graphql.ts b/libs/client-shared/src/types/common/graphql.ts new file mode 100644 index 00000000..0d1bcf87 --- /dev/null +++ b/libs/client-shared/src/types/common/graphql.ts @@ -0,0 +1,6 @@ +import { DocumentNode } from 'graphql/language' + +export interface ApolloMutation { + gql: DocumentNode + method: string +} diff --git a/libs/client-shared/src/types/index.ts b/libs/client-shared/src/types/index.ts index 6ff1b0c3..14f4328e 100644 --- a/libs/client-shared/src/types/index.ts +++ b/libs/client-shared/src/types/index.ts @@ -1,4 +1,5 @@ export * from './common/common' +export * from './common/graphql' export * from './common/type-guards' export * from './lib/animations' export * from './lib/context' diff --git a/libs/config/src/lib/server.ts b/libs/config/src/lib/server.ts index f66add23..2f9bb32b 100644 --- a/libs/config/src/lib/server.ts +++ b/libs/config/src/lib/server.ts @@ -8,9 +8,12 @@ export const compilerApiUrl = export const serverUrl = process.env['SERVER_URL'] ?? 'http://localhost:6868' export const EndPoints = { - CODE_EXECUTOR_API: 'execute' + CODE_EXECUTOR_API: 'execute', + _GRAPHQL: 'graphql' } as const export const serverAppName = 'CodeGear API' export const serverDocsPrefix = 'docs' + +export const graphqlArg = '_graphql' diff --git a/package.json b/package.json index 2e8b372b..93797677 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@testing-library/react": "14.0.0", "@types/antd": "^1.0.0", "@types/axios": "^0.14.0", + "@types/express": "^4.17.17", "@types/jest": "^29.4.0", "@types/node": "18.14.2", "@types/react": "^18.2.14", @@ -66,6 +67,7 @@ "eslint-plugin-import": "2.27.5", "eslint-plugin-jsx-a11y": "6.7.1", "eslint-plugin-preact": "^0.1.0", + "eslint-plugin-prefer-arrow": "^1.2.3", "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "4.6.0", "hygen": "^6.2.11", @@ -91,11 +93,16 @@ "vitest": "^0.32.0" }, "dependencies": { + "@apollo/client": "^3.7.17", + "@apollo/react-hooks": "^4.0.0", + "@apollo/server": "^4.9.0", "@monaco-editor/react": "^4.5.1", + "@nestjs/apollo": "^12.0.7", "@nestjs/axios": "^3.0.0", "@nestjs/common": "^10.0.2", "@nestjs/config": "^3.0.0", "@nestjs/core": "^10.0.2", + "@nestjs/graphql": "^12.0.8", "@nestjs/platform-express": "^10.0.2", "@nestjs/swagger": "^7.1.1", "@reach/portal": "^0.18.0", @@ -106,6 +113,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "express": "^4.18.2", + "graphql": "^16.7.1", "mobx": "^6.9.0", "mobx-react-lite": "^3.4.3", "moment": "^2.29.4", From 1f9083c58d45ac91e4d9c486abef9d478dedb646 Mon Sep 17 00:00:00 2001 From: Gearonix Date: Fri, 28 Jul 2023 19:25:38 +0300 Subject: [PATCH 04/10] chore: added prisma, manually implemented logic for splitting .prisma files with nx workspaces (it was hard!) --- apps/server/.gitignore | 2 ++ apps/server/prisma/concat-prisma-files.js | 27 ++++++++++++++++++ apps/server/prisma/config/base.prisma | 8 ++++++ apps/server/prisma/models/users.prisma | 6 ++++ apps/server/prisma/schema.prisma | 14 +++++++++ apps/server/project.json | 12 ++++++++ apps/server/src/_schema.gql | 21 -------------- .../src/modules/authorization/auth.module.ts | 4 ++- .../modules/authorization/auth.resolver.ts | 5 +++- .../src/lib/helpers/local-storage.ts | 10 ++++--- libs/config/src/lib/client.ts | 2 ++ libs/nest-services/src/index.ts | 1 + libs/nest-services/src/prisma/index.ts | 2 ++ .../nest-services/src/prisma/prisma.module.ts | 9 ++++++ .../src/prisma/prisma.service.ts | 15 ++++++++++ package.json | 16 +++++++---- tools/dev/db_export.sql | Bin 0 -> 768 bytes tools/dev/{ => img}/architecture_diagram.png | Bin tools/dev/{ => img}/git_flow_trunk_based.png | Bin tools/dev/{ => img}/microfrontend.png | Bin tools/dev/{ => img}/nestjs_folders.png | Bin 21 files changed, 121 insertions(+), 33 deletions(-) create mode 100644 apps/server/.gitignore create mode 100644 apps/server/prisma/concat-prisma-files.js create mode 100644 apps/server/prisma/config/base.prisma create mode 100644 apps/server/prisma/models/users.prisma create mode 100644 apps/server/prisma/schema.prisma delete mode 100644 apps/server/src/_schema.gql create mode 100644 libs/nest-services/src/prisma/index.ts create mode 100644 libs/nest-services/src/prisma/prisma.module.ts create mode 100644 libs/nest-services/src/prisma/prisma.service.ts create mode 100644 tools/dev/db_export.sql rename tools/dev/{ => img}/architecture_diagram.png (100%) rename tools/dev/{ => img}/git_flow_trunk_based.png (100%) rename tools/dev/{ => img}/microfrontend.png (100%) rename tools/dev/{ => img}/nestjs_folders.png (100%) diff --git a/apps/server/.gitignore b/apps/server/.gitignore new file mode 100644 index 00000000..8fe2f669 --- /dev/null +++ b/apps/server/.gitignore @@ -0,0 +1,2 @@ +prisma/migrations +_schema.gql diff --git a/apps/server/prisma/concat-prisma-files.js b/apps/server/prisma/concat-prisma-files.js new file mode 100644 index 00000000..b708a570 --- /dev/null +++ b/apps/server/prisma/concat-prisma-files.js @@ -0,0 +1,27 @@ +const concat = require('concat-files') +const path = require('path') + +const resolvePrisma = (...args) => path.join('prisma', ...args) + +const concatPrismaFiles = ({ config, models, dest }) => { + concat( + [ + resolvePrisma('config', config), + ...models.map((model) => resolvePrisma('models', `${model}.prisma`)) + ], + resolvePrisma(dest), + (error) => { + if (error) { + throw error + } + + console.log('Prisma files merged.') + } + ) +} + +concatPrismaFiles({ + config: 'base.prisma', + models: ['users'], + dest: 'schema.prisma' +}) diff --git a/apps/server/prisma/config/base.prisma b/apps/server/prisma/config/base.prisma new file mode 100644 index 00000000..75b96fd2 --- /dev/null +++ b/apps/server/prisma/config/base.prisma @@ -0,0 +1,8 @@ +generator client { + provider = "prisma-client-js" +} + +datasource database { + provider = "mysql" + url = env("DATABASE_URL") +} diff --git a/apps/server/prisma/models/users.prisma b/apps/server/prisma/models/users.prisma new file mode 100644 index 00000000..83f2a889 --- /dev/null +++ b/apps/server/prisma/models/users.prisma @@ -0,0 +1,6 @@ +// Users model + +model users { + username String @id @database.VarChar(14) + password String @database.VarChar(14) +} diff --git a/apps/server/prisma/schema.prisma b/apps/server/prisma/schema.prisma new file mode 100644 index 00000000..f2ee8761 --- /dev/null +++ b/apps/server/prisma/schema.prisma @@ -0,0 +1,14 @@ +generator client { + provider = "prisma-client-js" +} + +datasource database { + provider = "mysql" + url = env("DATABASE_URL") +} +// Users model + +model users { + username String @id @database.VarChar(14) + password String @database.VarChar(14) +} diff --git a/apps/server/project.json b/apps/server/project.json index 667f446e..8def7b7e 100644 --- a/apps/server/project.json +++ b/apps/server/project.json @@ -61,6 +61,18 @@ "codeCoverage": true } } + }, + "migrate:dev": { + "command": "node prisma/concat-prisma-files.js && dotenv -e ../../.serve.env -- npx prisma migrate dev", + "options": { + "cwd": "apps/server" + } + }, + "migrate:prod": { + "command": "node prisma/concat-prisma-files.js && dotenv -e ../../.build.env -- npx prisma migrate deploy", + "options": { + "cwd": "apps/server" + } } }, "tags": [] diff --git a/apps/server/src/_schema.gql b/apps/server/src/_schema.gql deleted file mode 100644 index 8ad00bd7..00000000 --- a/apps/server/src/_schema.gql +++ /dev/null @@ -1,21 +0,0 @@ -# ------------------------------------------------------ -# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) -# ------------------------------------------------------ - -type User { - username: String! - avatarUrl: String! -} - -type Query { - sayHello: String! -} - -type Mutation { - signIn(_graphql: SignIn!): User! -} - -input SignIn { - username: String! - password: String! -} \ No newline at end of file diff --git a/apps/server/src/modules/authorization/auth.module.ts b/apps/server/src/modules/authorization/auth.module.ts index ce40e82e..62f4ea77 100644 --- a/apps/server/src/modules/authorization/auth.module.ts +++ b/apps/server/src/modules/authorization/auth.module.ts @@ -1,9 +1,11 @@ import { AuthResolver } from '@/modules/authorization/auth.resolver' import { Module } from '@nestjs/common' +import { PrismaModule } from '$/services' + @Module({ controllers: [], providers: [AuthResolver], - imports: [] + imports: [PrismaModule] }) export class AuthModule {} diff --git a/apps/server/src/modules/authorization/auth.resolver.ts b/apps/server/src/modules/authorization/auth.resolver.ts index 884209c0..6c7caf46 100644 --- a/apps/server/src/modules/authorization/auth.resolver.ts +++ b/apps/server/src/modules/authorization/auth.resolver.ts @@ -3,12 +3,15 @@ import { Args, Mutation, Query, Resolver } from '@nestjs/graphql' import { SignIn, User } from './dto' import { graphqlArg } from '$/config' +import { PrismaService } from '$/services' @Resolver(() => User) export class AuthResolver { + constructor(private prisma: PrismaService) {} @Mutation(() => User) async signIn(@Args(graphqlArg) payload: SignIn): Promise { - console.log(payload) + const test = await this.prisma.users.findMany() + console.log(test) return { avatarUrl: 'test', username: 'test2' diff --git a/libs/client-shared/src/lib/helpers/local-storage.ts b/libs/client-shared/src/lib/helpers/local-storage.ts index c1a5583c..0647662c 100644 --- a/libs/client-shared/src/lib/helpers/local-storage.ts +++ b/libs/client-shared/src/lib/helpers/local-storage.ts @@ -1,5 +1,7 @@ -import { LocalStorage, LocalStorageKeys } from '../../config/local-storage' -import { isString } from '../../types' +import { LocalStorage, LocalStorageKeys } from '@/config/local-storage' +import { isString } from '@/types' + +import { appName } from '$/config' export class LocalStorageClient { isDisabled = false @@ -12,7 +14,7 @@ export class LocalStorageClient { return defaultVal } - const value = localStorage.getItem(key) as string + const value = localStorage.getItem(`${appName}_${key}`) as string if (!value) { return defaultVal @@ -29,7 +31,7 @@ export class LocalStorageClient { if (isString(value)) { return localStorage.setItem(key, value) } - localStorage.setItem(key, JSON.stringify(value)) + localStorage.setItem(`${appName}_${key}`, JSON.stringify(value)) } public clear(key?: LocalStorageKeys): void { diff --git a/libs/config/src/lib/client.ts b/libs/config/src/lib/client.ts index e5f69d35..5618bde9 100644 --- a/libs/config/src/lib/client.ts +++ b/libs/config/src/lib/client.ts @@ -1 +1,3 @@ export const clientUrl = process.env.CLIENT_URL ?? 'http://localhost:3000' + +export const appName = 'code_gear' diff --git a/libs/nest-services/src/index.ts b/libs/nest-services/src/index.ts index 77e742aa..153c8d4d 100644 --- a/libs/nest-services/src/index.ts +++ b/libs/nest-services/src/index.ts @@ -1,2 +1,3 @@ export * from './executor-api' +export { PrismaModule, PrismaService } from './prisma' export { ServicesModule } from './services.module' diff --git a/libs/nest-services/src/prisma/index.ts b/libs/nest-services/src/prisma/index.ts new file mode 100644 index 00000000..ace17313 --- /dev/null +++ b/libs/nest-services/src/prisma/index.ts @@ -0,0 +1,2 @@ +export { PrismaModule } from './prisma.module' +export { PrismaService } from './prisma.service' diff --git a/libs/nest-services/src/prisma/prisma.module.ts b/libs/nest-services/src/prisma/prisma.module.ts new file mode 100644 index 00000000..c42a2319 --- /dev/null +++ b/libs/nest-services/src/prisma/prisma.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common' + +import { PrismaService } from './prisma.service' + +@Module({ + providers: [PrismaService], + exports: [PrismaService] +}) +export class PrismaModule {} diff --git a/libs/nest-services/src/prisma/prisma.service.ts b/libs/nest-services/src/prisma/prisma.service.ts new file mode 100644 index 00000000..4a215a9e --- /dev/null +++ b/libs/nest-services/src/prisma/prisma.service.ts @@ -0,0 +1,15 @@ +import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common' +import { PrismaClient } from '@prisma/client' + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit { + async onModuleInit() { + await this.$connect() + } + + async enableShutdownHooks(app: INestApplication) { + this.$on('beforeExit', async () => { + await app.close() + }) + } +} diff --git a/package.json b/package.json index 93797677..3a63653b 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,17 @@ "version": "0.0.0", "license": "MIT", "scripts": { - "serve": " nx run-many --targets=serve --all", - "build": " nx run-many --targets=build --all", - "lint": " nx run-many --targets=lint --all", + "serve": "nx run-many --targets=serve --all", + "build": "nx run-many --targets=build --all", + "lint": "nx run-many --targets=lint --all", "dep-graph": "nx dep-graph", "client:serve": "nx run client:serve", "client:build": "nx run client:build", "client:preview": "nx run client:preview", "server:serve": "nx run server:serve --skip-nx-cache", - "server:build": "nx run server:build" + "server:build": "nx run server:build", + "prisma:migrate:dev": "nx run server:migrate:dev", + "prisma:migrate:prod": "nx run server:prod" }, "private": true, "devDependencies": { @@ -38,7 +40,6 @@ "@testing-library/react": "14.0.0", "@types/antd": "^1.0.0", "@types/axios": "^0.14.0", - "@types/express": "^4.17.17", "@types/jest": "^29.4.0", "@types/node": "18.14.2", "@types/react": "^18.2.14", @@ -56,6 +57,7 @@ "@vitest/ui": "^0.32.0", "babel-plugin-styled-components": "1.10.7", "babel-plugin-transform-hook-names": "^1.0.2", + "concat-files": "^0.1.1", "cypress": "^12.11.0", "eslint": "~8.41.0", "eslint-config-gearonix": "1.0.2", @@ -79,6 +81,7 @@ "nx-cloud": "latest", "preact": "^10.15.1", "prettier": "^3.0.0", + "prisma": "^5.0.0", "ts-jest": "^29.1.0", "ts-morph": "^19.0.0", "ts-node": "10.9.1", @@ -105,6 +108,7 @@ "@nestjs/graphql": "^12.0.8", "@nestjs/platform-express": "^10.0.2", "@nestjs/swagger": "^7.1.1", + "@prisma/client": "^5.0.0", "@reach/portal": "^0.18.0", "@react-spring/web": "^9.7.3", "@use-gesture/react": "^10.2.27", @@ -112,7 +116,6 @@ "axios": "^1.1.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", - "express": "^4.18.2", "graphql": "^16.7.1", "mobx": "^6.9.0", "mobx-react-lite": "^3.4.3", @@ -128,6 +131,7 @@ "react-smooth-scrollbar": "^8.0.6", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", + "schemix": "^1.14.1", "smooth-scrollbar": "^8.8.4", "styled-components": "5.3.6", "tslib": "^2.6.0", diff --git a/tools/dev/db_export.sql b/tools/dev/db_export.sql new file mode 100644 index 0000000000000000000000000000000000000000..a599c3bbfae249cde50da08c7dee24d7bd23c32b GIT binary patch literal 768 zcmbV~TT8=05QWdP;D3m3X%Sknf+B(17%xy-sP(z6P1J%-B)$0W)o(Tp)hJYC+0A5U z=ggcl^YPwLT?cBZsUx>N-6_&gp4wVyqO}sm8fnU(oiCbklWNI}as}>n8}Bm_IeUsf z!5R^jU{@pSYUo`1+T&~aPsnZi7^E`)-_b1piY$*h)m#s-@jxvzgc`u;0!|V4L#&Rv z&K?tG_2ryne7>v5SZzHiZ05W%|Jt!D>+(^or5g7=^_^lp-GkiK;TN1&Viv47W#ozf zjgwQu6WTA}X$mi~Gx~7p?4rJSV|(02x`L_IW4-k0$jarn$b|nI%k;PUBN$xkmNQbo zH)UmDbnsP?LfwEn*mNoQp?ioHra_^1)jy)gXY3{uF@b^2#q8T8{5FZD;=AXnn8aOk HyPb_smA`gl literal 0 HcmV?d00001 diff --git a/tools/dev/architecture_diagram.png b/tools/dev/img/architecture_diagram.png similarity index 100% rename from tools/dev/architecture_diagram.png rename to tools/dev/img/architecture_diagram.png diff --git a/tools/dev/git_flow_trunk_based.png b/tools/dev/img/git_flow_trunk_based.png similarity index 100% rename from tools/dev/git_flow_trunk_based.png rename to tools/dev/img/git_flow_trunk_based.png diff --git a/tools/dev/microfrontend.png b/tools/dev/img/microfrontend.png similarity index 100% rename from tools/dev/microfrontend.png rename to tools/dev/img/microfrontend.png diff --git a/tools/dev/nestjs_folders.png b/tools/dev/img/nestjs_folders.png similarity index 100% rename from tools/dev/nestjs_folders.png rename to tools/dev/img/nestjs_folders.png From 039c46e99203d1820e2053af0ace4318a59e0d1b Mon Sep 17 00:00:00 2001 From: Gearonix Date: Sat, 29 Jul 2023 07:03:04 +0300 Subject: [PATCH 05/10] chore: started to implement basic authorization by using passport js --- .../client/src/widgets/sign-in-modal/types.ts | 2 +- apps/server/.gitignore | 1 + apps/server/prisma/models/users.prisma | 5 +- apps/server/prisma/schema.prisma | 5 +- apps/server/src/decorators/index.ts | 1 + .../src/decorators/string-field.decorator.ts | 2 +- apps/server/src/decorators/user.decorator.ts | 9 +++ apps/server/src/guards/gql-auth.guard.ts | 21 +++++++ .../server/src/guards/gql-local-auth.guard.ts | 12 ++++ apps/server/src/guards/jwt-auth.guard.ts | 3 + apps/server/src/guards/local-auth.guard.ts | 3 + .../src/guards/session-serializer.guard.ts | 13 ++++ .../src/modules/authorization/auth.module.ts | 29 ++++++++- .../modules/authorization/auth.resolver.ts | 24 ++++---- .../src/modules/authorization/auth.service.ts | 59 +++++++++++++++++++ .../modules/authorization/dto/user.entity.ts | 4 +- .../authorization/strategies/jwt.strategy.ts | 22 +++++++ .../strategies/local.strategy.ts | 35 +++++++++++ apps/server/src/modules/users/index.ts | 2 + apps/server/src/modules/users/users.module.ts | 13 ++++ .../server/src/modules/users/users.service.ts | 23 ++++++++ libs/config/src/lib/server.ts | 2 + package.json | 7 ++- 23 files changed, 275 insertions(+), 22 deletions(-) create mode 100644 apps/server/src/decorators/user.decorator.ts create mode 100644 apps/server/src/guards/gql-auth.guard.ts create mode 100644 apps/server/src/guards/gql-local-auth.guard.ts create mode 100644 apps/server/src/guards/jwt-auth.guard.ts create mode 100644 apps/server/src/guards/local-auth.guard.ts create mode 100644 apps/server/src/guards/session-serializer.guard.ts create mode 100644 apps/server/src/modules/authorization/strategies/jwt.strategy.ts create mode 100644 apps/server/src/modules/authorization/strategies/local.strategy.ts create mode 100644 apps/server/src/modules/users/index.ts create mode 100644 apps/server/src/modules/users/users.module.ts create mode 100644 apps/server/src/modules/users/users.service.ts diff --git a/apps/client/src/widgets/sign-in-modal/types.ts b/apps/client/src/widgets/sign-in-modal/types.ts index 9879f2ef..2ab80897 100644 --- a/apps/client/src/widgets/sign-in-modal/types.ts +++ b/apps/client/src/widgets/sign-in-modal/types.ts @@ -5,5 +5,5 @@ export interface SignInForm { export interface SignInResponse { username: string - avatarUrl: string + avatarUrl: string | null } diff --git a/apps/server/.gitignore b/apps/server/.gitignore index 8fe2f669..605b7f14 100644 --- a/apps/server/.gitignore +++ b/apps/server/.gitignore @@ -1,2 +1,3 @@ prisma/migrations +prisma/schema.prisma _schema.gql diff --git a/apps/server/prisma/models/users.prisma b/apps/server/prisma/models/users.prisma index 83f2a889..73fdf110 100644 --- a/apps/server/prisma/models/users.prisma +++ b/apps/server/prisma/models/users.prisma @@ -1,6 +1,7 @@ // Users model model users { - username String @id @database.VarChar(14) - password String @database.VarChar(14) + username String @id @database.VarChar(14) + password String @database.VarChar(14) + avatarUrl String? @database.VarChar(100) } diff --git a/apps/server/prisma/schema.prisma b/apps/server/prisma/schema.prisma index f2ee8761..1dc2f5f7 100644 --- a/apps/server/prisma/schema.prisma +++ b/apps/server/prisma/schema.prisma @@ -9,6 +9,7 @@ datasource database { // Users model model users { - username String @id @database.VarChar(14) - password String @database.VarChar(14) + username String @id @database.VarChar(14) + password String @database.VarChar(14) + avatarUrl String? @database.VarChar(100) } diff --git a/apps/server/src/decorators/index.ts b/apps/server/src/decorators/index.ts index f7dc16d1..7501cc05 100644 --- a/apps/server/src/decorators/index.ts +++ b/apps/server/src/decorators/index.ts @@ -1 +1,2 @@ export { StringField } from './string-field.decorator' +export { WithUser } from './user.decorator' diff --git a/apps/server/src/decorators/string-field.decorator.ts b/apps/server/src/decorators/string-field.decorator.ts index 000cd720..25af68cb 100644 --- a/apps/server/src/decorators/string-field.decorator.ts +++ b/apps/server/src/decorators/string-field.decorator.ts @@ -1,4 +1,4 @@ -import { IsString, MaxLength, MinLength } from 'class-validator'; +import { IsString, MaxLength, MinLength } from 'class-validator' import { applyDecorators } from '@nestjs/common' import { Field } from '@nestjs/graphql' diff --git a/apps/server/src/decorators/user.decorator.ts b/apps/server/src/decorators/user.decorator.ts new file mode 100644 index 00000000..0a4aec2d --- /dev/null +++ b/apps/server/src/decorators/user.decorator.ts @@ -0,0 +1,9 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common' +import { GqlExecutionContext } from '@nestjs/graphql' + +export const WithUser = createParamDecorator( + (data: unknown, context: ExecutionContext) => { + const ctx = GqlExecutionContext.create(context) + return ctx.getContext().req.user + } +) diff --git a/apps/server/src/guards/gql-auth.guard.ts b/apps/server/src/guards/gql-auth.guard.ts new file mode 100644 index 00000000..c5c977fe --- /dev/null +++ b/apps/server/src/guards/gql-auth.guard.ts @@ -0,0 +1,21 @@ +import { ExecutionContext } from '@nestjs/common' +import { GqlExecutionContext } from '@nestjs/graphql' +import { AuthGuard } from '@nestjs/passport' + +import { graphqlArg } from '$/config' + +export class GqlAuthGuard extends AuthGuard('local') { + constructor() { + super() + } + + getRequest(context: ExecutionContext) { + const ctx = GqlExecutionContext.create(context) + const gqlReq = ctx.getContext().req + const user = ctx.getArgs()[graphqlArg] + gqlReq.body.username = user.username + gqlReq.body.password = user.password + + return gqlReq + } +} diff --git a/apps/server/src/guards/gql-local-auth.guard.ts b/apps/server/src/guards/gql-local-auth.guard.ts new file mode 100644 index 00000000..3b8dfe00 --- /dev/null +++ b/apps/server/src/guards/gql-local-auth.guard.ts @@ -0,0 +1,12 @@ +import { ExecutionContext, Injectable } from '@nestjs/common' +import { GqlExecutionContext } from '@nestjs/graphql' +import { AuthGuard } from '@nestjs/passport' + +@Injectable() +export class GqlLocalAuthGuard extends AuthGuard('local') { + async canActivate(context: ExecutionContext): Promise { + const ctxRequest = GqlExecutionContext.create(context).getContext().req + console.log(ctxRequest) + return Boolean(ctxRequest) + } +} diff --git a/apps/server/src/guards/jwt-auth.guard.ts b/apps/server/src/guards/jwt-auth.guard.ts new file mode 100644 index 00000000..b77e73a7 --- /dev/null +++ b/apps/server/src/guards/jwt-auth.guard.ts @@ -0,0 +1,3 @@ +import { AuthGuard } from '@nestjs/passport' + +export class JwtAuthGuard extends AuthGuard('jwt') {} diff --git a/apps/server/src/guards/local-auth.guard.ts b/apps/server/src/guards/local-auth.guard.ts new file mode 100644 index 00000000..8accbc2c --- /dev/null +++ b/apps/server/src/guards/local-auth.guard.ts @@ -0,0 +1,3 @@ +import { AuthGuard } from '@nestjs/passport' + +export class LocalAuthGuard extends AuthGuard('local') {} diff --git a/apps/server/src/guards/session-serializer.guard.ts b/apps/server/src/guards/session-serializer.guard.ts new file mode 100644 index 00000000..f2057171 --- /dev/null +++ b/apps/server/src/guards/session-serializer.guard.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@nestjs/common' +import { PassportSerializer } from '@nestjs/passport' + +@Injectable() +export class SessionSerializer extends PassportSerializer { + serializeUser(user: any, done: CallableFunction): any { + return done(null, user) + } + + deserializeUser(payload: any, done: CallableFunction): any { + return done(null, payload) + } +} diff --git a/apps/server/src/modules/authorization/auth.module.ts b/apps/server/src/modules/authorization/auth.module.ts index 62f4ea77..558423de 100644 --- a/apps/server/src/modules/authorization/auth.module.ts +++ b/apps/server/src/modules/authorization/auth.module.ts @@ -1,11 +1,34 @@ +import { SessionSerializer } from '@/guards/session-serializer.guard' import { AuthResolver } from '@/modules/authorization/auth.resolver' +import { AuthService } from '@/modules/authorization/auth.service' +import { UsersModule } from '@/modules/users' import { Module } from '@nestjs/common' +import { JwtModule } from '@nestjs/jwt' +import { PassportModule } from '@nestjs/passport' -import { PrismaModule } from '$/services' +import { JwtStrategy } from './strategies/jwt.strategy' +import { LocalStrategy } from './strategies/local.strategy' + +import { jwtSecret } from '$/config' @Module({ controllers: [], - providers: [AuthResolver], - imports: [PrismaModule] + providers: [ + AuthResolver, + // JwtStrategy, + LocalStrategy, + AuthService, + SessionSerializer + ], + imports: [ + UsersModule, + JwtModule.register({ + secret: jwtSecret, + signOptions: { + expiresIn: '24h' + } + }) + ], + exports: [LocalStrategy] }) export class AuthModule {} diff --git a/apps/server/src/modules/authorization/auth.resolver.ts b/apps/server/src/modules/authorization/auth.resolver.ts index 6c7caf46..34ff64f1 100644 --- a/apps/server/src/modules/authorization/auth.resolver.ts +++ b/apps/server/src/modules/authorization/auth.resolver.ts @@ -1,21 +1,25 @@ +import { WithUser } from '@/decorators' +import { GqlAuthGuard } from '@/guards/gql-auth.guard' +import { GqlLocalAuthGuard } from '@/guards/gql-local-auth.guard' +import { UseGuards } from '@nestjs/common' import { Args, Mutation, Query, Resolver } from '@nestjs/graphql' import { SignIn, User } from './dto' import { graphqlArg } from '$/config' -import { PrismaService } from '$/services' @Resolver(() => User) export class AuthResolver { - constructor(private prisma: PrismaService) {} - @Mutation(() => User) - async signIn(@Args(graphqlArg) payload: SignIn): Promise { - const test = await this.prisma.users.findMany() - console.log(test) - return { - avatarUrl: 'test', - username: 'test2' - } + constructor() {} + @Query(() => User) + @UseGuards(GqlAuthGuard, GqlLocalAuthGuard) + async signIn( + @Args(graphqlArg) payload: SignIn, + @WithUser() user + ): Promise { + console.log('USER') + console.log(user) + return user } // Graphql requires at least one @Query to diff --git a/apps/server/src/modules/authorization/auth.service.ts b/apps/server/src/modules/authorization/auth.service.ts index e69de29b..d1c80cde 100644 --- a/apps/server/src/modules/authorization/auth.service.ts +++ b/apps/server/src/modules/authorization/auth.service.ts @@ -0,0 +1,59 @@ +import * as bcrypt from 'bcryptjs' + +import { UsersService } from '@/modules/users' +import { HttpException, HttpStatus, Injectable } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' + +import { SignIn } from './dto' + +@Injectable() +export class AuthService { + constructor( + private readonly jwtService: JwtService, + private readonly usersService: UsersService + ) {} + + async validate(payload: SignIn) { + const user = await this.usersService.getUserByUsername(payload.username) + + if (!user) { + return null + } + + // const isPasswordEquals = await bcrypt.compare( + // payload.password, + // user.password + // ) + + const isPasswordEquals = payload.password === user.password + + if (!isPasswordEquals) { + return null + } + + return user + } + + async login(payload: SignIn) { + return this.generateToken(payload.username) + } + + async registerUser(payload: SignIn) { + const hashPassword = await bcrypt.hash(payload.password, 5) + + await this.usersService.createUser({ + ...payload, + password: hashPassword + }) + + return this.generateToken(payload.username) + } + + async generateToken(username: string) { + const accessToken = this.jwtService.sign({ + payload: { username } + }) + + return { accessToken } + } +} diff --git a/apps/server/src/modules/authorization/dto/user.entity.ts b/apps/server/src/modules/authorization/dto/user.entity.ts index 3595f1ff..42cf6947 100644 --- a/apps/server/src/modules/authorization/dto/user.entity.ts +++ b/apps/server/src/modules/authorization/dto/user.entity.ts @@ -7,7 +7,7 @@ export class User { @ApiProperty({ description: 'Username (used as userId)' }) username: string - @Field() + @Field({ nullable: true }) @ApiProperty({ description: 'User avatar location' }) - avatarUrl: string + avatarUrl?: string } diff --git a/apps/server/src/modules/authorization/strategies/jwt.strategy.ts b/apps/server/src/modules/authorization/strategies/jwt.strategy.ts new file mode 100644 index 00000000..3c853d29 --- /dev/null +++ b/apps/server/src/modules/authorization/strategies/jwt.strategy.ts @@ -0,0 +1,22 @@ +import { ExtractJwt, Strategy } from 'passport-jwt' + +import { SignIn } from '@/modules/authorization/dto' +import { UsersService } from '@/modules/users' +import { Injectable } from '@nestjs/common' +import { PassportStrategy } from '@nestjs/passport' + +import { jwtSecret } from '$/config' + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(private readonly usersService: UsersService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: jwtSecret + }) + } + + validate(payload: SignIn) { + return this.usersService.getUserByUsername(payload.username) + } +} diff --git a/apps/server/src/modules/authorization/strategies/local.strategy.ts b/apps/server/src/modules/authorization/strategies/local.strategy.ts new file mode 100644 index 00000000..a0a43c0a --- /dev/null +++ b/apps/server/src/modules/authorization/strategies/local.strategy.ts @@ -0,0 +1,35 @@ +import { Strategy } from 'passport-local' + +import { SignIn } from '@/modules/authorization/dto' +import { + Injectable, + InternalServerErrorException, + UnauthorizedException +} from '@nestjs/common' +import { PassportStrategy } from '@nestjs/passport' + +import { AuthService } from '../auth.service' + +@Injectable() +export class LocalStrategy extends PassportStrategy(Strategy) { + constructor(private readonly authService: AuthService) { + super({ usernameField: 'username', passwordField: 'password' }) + } + + async validate(username: string, password: string) { + try { + const user = await this.authService.validate({ + username, + password + }) + + if (!user) { + throw new UnauthorizedException() + } + + return user + } catch (error) { + throw new InternalServerErrorException(error.message) + } + } +} diff --git a/apps/server/src/modules/users/index.ts b/apps/server/src/modules/users/index.ts new file mode 100644 index 00000000..8a3b8492 --- /dev/null +++ b/apps/server/src/modules/users/index.ts @@ -0,0 +1,2 @@ +export { UsersModule } from './users.module' +export { UsersService } from './users.service' diff --git a/apps/server/src/modules/users/users.module.ts b/apps/server/src/modules/users/users.module.ts new file mode 100644 index 00000000..8c4f57ca --- /dev/null +++ b/apps/server/src/modules/users/users.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common' + +import { UsersService } from './users.service' + +import { PrismaModule } from '$/services' + +@Module({ + controllers: [], + providers: [UsersService], + imports: [PrismaModule], + exports: [UsersService] +}) +export class UsersModule {} diff --git a/apps/server/src/modules/users/users.service.ts b/apps/server/src/modules/users/users.service.ts new file mode 100644 index 00000000..1aa03b46 --- /dev/null +++ b/apps/server/src/modules/users/users.service.ts @@ -0,0 +1,23 @@ +import { SignIn } from '@/modules/authorization/dto' +import { Injectable } from '@nestjs/common' + +import { PrismaService } from '$/services' + +@Injectable() +export class UsersService { + constructor(private prisma: PrismaService) {} + + public getUserByUsername(username: string) { + return this.prisma.users.findUnique({ + where: { + username + } + }) + } + + public createUser(user: SignIn) { + return this.prisma.users.create({ + data: user + }) + } +} diff --git a/libs/config/src/lib/server.ts b/libs/config/src/lib/server.ts index 2f9bb32b..2a07a04a 100644 --- a/libs/config/src/lib/server.ts +++ b/libs/config/src/lib/server.ts @@ -17,3 +17,5 @@ export const serverAppName = 'CodeGear API' export const serverDocsPrefix = 'docs' export const graphqlArg = '_graphql' + +export const jwtSecret = '3e29ac48-2d68-1' diff --git a/package.json b/package.json index 3a63653b..f93c0782 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,11 @@ "@testing-library/react": "14.0.0", "@types/antd": "^1.0.0", "@types/axios": "^0.14.0", + "@types/bcryptjs": "^2.4.2", "@types/jest": "^29.4.0", "@types/node": "18.14.2", + "@types/passport-jwt": "^3.0.9", + "@types/passport-local": "^1.0.35", "@types/react": "^18.2.14", "@types/react-dom": "18.2.6", "@types/react-is": "18.2.1", @@ -106,6 +109,7 @@ "@nestjs/config": "^3.0.0", "@nestjs/core": "^10.0.2", "@nestjs/graphql": "^12.0.8", + "@nestjs/jwt": "^10.1.0", "@nestjs/platform-express": "^10.0.2", "@nestjs/swagger": "^7.1.1", "@prisma/client": "^5.0.0", @@ -114,6 +118,7 @@ "@use-gesture/react": "^10.2.27", "antd": "^5.7.0", "axios": "^1.1.3", + "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "graphql": "^16.7.1", @@ -121,6 +126,7 @@ "mobx-react-lite": "^3.4.3", "moment": "^2.29.4", "normalize.css": "^8.0.1", + "passport-jwt": "^4.0.1", "preact-compat": "^3.19.0", "qs-stringify": "^1.2.1", "react": "18.2.0", @@ -131,7 +137,6 @@ "react-smooth-scrollbar": "^8.0.6", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", - "schemix": "^1.14.1", "smooth-scrollbar": "^8.8.4", "styled-components": "5.3.6", "tslib": "^2.6.0", From 3ad319edcf50a6000b562739260d61b10423b8f3 Mon Sep 17 00:00:00 2001 From: Gearonix Date: Sat, 29 Jul 2023 08:08:05 +0300 Subject: [PATCH 06/10] feat: added ability to register user --- apps/server/prisma/models/users.prisma | 2 +- apps/server/prisma/schema.prisma | 2 +- .../http-exception.filter.ts | 6 ++--- apps/server/src/guards/jwt-auth.guard.ts | 10 ++++++- .../src/modules/authorization/auth.module.ts | 2 +- .../modules/authorization/auth.resolver.ts | 27 ++++++++++--------- .../src/modules/authorization/auth.service.ts | 20 +++++--------- .../src/modules/authorization/dto/index.ts | 1 + .../modules/authorization/dto/token.entity.ts | 9 +++++++ .../authorization/strategies/jwt.strategy.ts | 6 +++-- libs/config/src/lib/server.ts | 2 +- 11 files changed, 49 insertions(+), 38 deletions(-) create mode 100644 apps/server/src/modules/authorization/dto/token.entity.ts diff --git a/apps/server/prisma/models/users.prisma b/apps/server/prisma/models/users.prisma index 73fdf110..ba3ed938 100644 --- a/apps/server/prisma/models/users.prisma +++ b/apps/server/prisma/models/users.prisma @@ -2,6 +2,6 @@ model users { username String @id @database.VarChar(14) - password String @database.VarChar(14) + password String @database.VarChar(150) avatarUrl String? @database.VarChar(100) } diff --git a/apps/server/prisma/schema.prisma b/apps/server/prisma/schema.prisma index 1dc2f5f7..8aecc76f 100644 --- a/apps/server/prisma/schema.prisma +++ b/apps/server/prisma/schema.prisma @@ -10,6 +10,6 @@ datasource database { model users { username String @id @database.VarChar(14) - password String @database.VarChar(14) + password String @database.VarChar(150) avatarUrl String? @database.VarChar(100) } diff --git a/apps/server/src/exception-filters/http-exception.filter.ts b/apps/server/src/exception-filters/http-exception.filter.ts index 88ed585c..12964e63 100644 --- a/apps/server/src/exception-filters/http-exception.filter.ts +++ b/apps/server/src/exception-filters/http-exception.filter.ts @@ -17,11 +17,9 @@ export class HttpExceptionFilter implements ExceptionFilter { const request = ctx.getRequest() const status = exception.getStatus() - if (graphqlArg in response) { - return - } + console.log(response) - response.status(status).json({ + response.status?.(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, diff --git a/apps/server/src/guards/jwt-auth.guard.ts b/apps/server/src/guards/jwt-auth.guard.ts index b77e73a7..b74c9388 100644 --- a/apps/server/src/guards/jwt-auth.guard.ts +++ b/apps/server/src/guards/jwt-auth.guard.ts @@ -1,3 +1,11 @@ +import { ExecutionContext } from '@nestjs/common' +import { GqlExecutionContext } from '@nestjs/graphql' import { AuthGuard } from '@nestjs/passport' -export class JwtAuthGuard extends AuthGuard('jwt') {} +export class JwtAuthGuard extends AuthGuard('jwt') { + getRequest(context: ExecutionContext) { + const ctx = GqlExecutionContext.create(context) + console.log(ctx.getContext().req.headers) + return ctx.getContext().req + } +} diff --git a/apps/server/src/modules/authorization/auth.module.ts b/apps/server/src/modules/authorization/auth.module.ts index 558423de..6def727a 100644 --- a/apps/server/src/modules/authorization/auth.module.ts +++ b/apps/server/src/modules/authorization/auth.module.ts @@ -15,7 +15,7 @@ import { jwtSecret } from '$/config' controllers: [], providers: [ AuthResolver, - // JwtStrategy, + JwtStrategy, LocalStrategy, AuthService, SessionSerializer diff --git a/apps/server/src/modules/authorization/auth.resolver.ts b/apps/server/src/modules/authorization/auth.resolver.ts index 34ff64f1..b73eee9b 100644 --- a/apps/server/src/modules/authorization/auth.resolver.ts +++ b/apps/server/src/modules/authorization/auth.resolver.ts @@ -1,31 +1,32 @@ import { WithUser } from '@/decorators' import { GqlAuthGuard } from '@/guards/gql-auth.guard' import { GqlLocalAuthGuard } from '@/guards/gql-local-auth.guard' +import { JwtAuthGuard } from '@/guards/jwt-auth.guard' import { UseGuards } from '@nestjs/common' import { Args, Mutation, Query, Resolver } from '@nestjs/graphql' -import { SignIn, User } from './dto' +import { AuthService } from './auth.service' +import { AccessToken, SignIn, User } from './dto' import { graphqlArg } from '$/config' @Resolver(() => User) export class AuthResolver { - constructor() {} - @Query(() => User) + constructor(private authService: AuthService) {} + @Query(() => AccessToken) @UseGuards(GqlAuthGuard, GqlLocalAuthGuard) - async signIn( - @Args(graphqlArg) payload: SignIn, - @WithUser() user - ): Promise { - console.log('USER') - console.log(user) - return user + async signIn(@Args(graphqlArg) payload: SignIn, @WithUser() user) { + return this.authService.generateToken(user.username) } + // @Mutation(() => AccessToken) + // Graphql requires at least one @Query to // work so for now this will be here lol - @Query(() => String) - sayHello(): string { - return 'Hello World!' + @Query(() => User) + @UseGuards(JwtAuthGuard) + sayHello(@WithUser() user): string { + console.log('say-hello') + return user } } diff --git a/apps/server/src/modules/authorization/auth.service.ts b/apps/server/src/modules/authorization/auth.service.ts index d1c80cde..51c57672 100644 --- a/apps/server/src/modules/authorization/auth.service.ts +++ b/apps/server/src/modules/authorization/auth.service.ts @@ -17,15 +17,13 @@ export class AuthService { const user = await this.usersService.getUserByUsername(payload.username) if (!user) { - return null + return this.registerUser(payload) } - // const isPasswordEquals = await bcrypt.compare( - // payload.password, - // user.password - // ) - - const isPasswordEquals = payload.password === user.password + const isPasswordEquals = await bcrypt.compare( + payload.password, + user.password + ) if (!isPasswordEquals) { return null @@ -34,19 +32,13 @@ export class AuthService { return user } - async login(payload: SignIn) { - return this.generateToken(payload.username) - } - async registerUser(payload: SignIn) { const hashPassword = await bcrypt.hash(payload.password, 5) - await this.usersService.createUser({ + return this.usersService.createUser({ ...payload, password: hashPassword }) - - return this.generateToken(payload.username) } async generateToken(username: string) { diff --git a/apps/server/src/modules/authorization/dto/index.ts b/apps/server/src/modules/authorization/dto/index.ts index 55707104..dc141e38 100644 --- a/apps/server/src/modules/authorization/dto/index.ts +++ b/apps/server/src/modules/authorization/dto/index.ts @@ -1,2 +1,3 @@ export { SignIn } from './sign-in.input' +export { AccessToken } from './token.entity' export { User } from './user.entity' diff --git a/apps/server/src/modules/authorization/dto/token.entity.ts b/apps/server/src/modules/authorization/dto/token.entity.ts new file mode 100644 index 00000000..d1f9bf82 --- /dev/null +++ b/apps/server/src/modules/authorization/dto/token.entity.ts @@ -0,0 +1,9 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { ApiProperty } from '@nestjs/swagger' + +@ObjectType() +export class AccessToken { + @Field() + @ApiProperty({ description: 'Authorization token (jwt)' }) + accessToken: string +} diff --git a/apps/server/src/modules/authorization/strategies/jwt.strategy.ts b/apps/server/src/modules/authorization/strategies/jwt.strategy.ts index 3c853d29..202c08fc 100644 --- a/apps/server/src/modules/authorization/strategies/jwt.strategy.ts +++ b/apps/server/src/modules/authorization/strategies/jwt.strategy.ts @@ -12,11 +12,13 @@ export class JwtStrategy extends PassportStrategy(Strategy) { constructor(private readonly usersService: UsersService) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, secretOrKey: jwtSecret }) } - validate(payload: SignIn) { - return this.usersService.getUserByUsername(payload.username) + validate(payload: { payload: { username: string } }) { + console.log(payload) + return this.usersService.getUserByUsername(payload.payload.username) } } diff --git a/libs/config/src/lib/server.ts b/libs/config/src/lib/server.ts index 2a07a04a..8d0bcef9 100644 --- a/libs/config/src/lib/server.ts +++ b/libs/config/src/lib/server.ts @@ -18,4 +18,4 @@ export const serverDocsPrefix = 'docs' export const graphqlArg = '_graphql' -export const jwtSecret = '3e29ac48-2d68-1' +export const jwtSecret = process.env['JWT_SECRET'] From 3bf7056a6d05a0e547ee2f3b6b71a85c95a23c5c Mon Sep 17 00:00:00 2001 From: Gearonix Date: Sat, 29 Jul 2023 09:51:33 +0300 Subject: [PATCH 07/10] refactor: refactored authorization on server --- .eslintrc.js | 3 +- apps/server/prisma/schema.prisma | 15 -------- apps/server/src/app.module.ts | 6 ++-- apps/server/src/auth/auth.module.ts | 26 ++++++++++++++ apps/server/src/auth/auth.resolver.ts | 30 ++++++++++++++++ .../authorization => auth}/auth.service.ts | 24 +++++++------ .../{modules/authorization => auth}/index.ts | 0 .../dto => auth/inputs}/sign-in.input.ts | 2 +- apps/server/src/auth/responses/index.ts | 2 ++ .../responses/token.response.ts} | 0 .../responses/user.response.ts} | 2 +- .../strategies/jwt.strategy.ts | 12 +++---- .../strategies/local.strategy.ts | 5 +-- apps/server/src/auth/types.ts | 3 ++ .../src/{ => common}/decorators/index.ts | 0 .../decorators/string-field.decorator.ts | 0 .../{ => common}/decorators/user.decorator.ts | 0 .../http-exception.filter.ts | 2 -- .../{ => common}/exception-filters/index.ts | 0 .../src/{ => common}/guards/gql-auth.guard.ts | 0 .../guards/gql-local-auth.guard.ts | 1 - apps/server/src/common/guards/index.ts | 3 ++ .../src/{ => common}/guards/jwt-auth.guard.ts | 1 - .../src/{ => common}/pipes/validation.pipe.ts | 0 .../code-executor.controller.ts | 0 .../code-executor-api/code-executor.module.ts | 0 .../code-executor-api/index.ts | 0 apps/server/src/core/users/index.ts | 2 ++ .../{modules => core}/users/users.module.ts | 6 ++-- .../users/users.repository.ts} | 4 +-- apps/server/src/guards/local-auth.guard.ts | 3 -- .../src/guards/session-serializer.guard.ts | 13 ------- apps/server/src/main.ts | 4 +-- .../src/modules/authorization/auth.module.ts | 34 ------------------- .../modules/authorization/auth.resolver.ts | 32 ----------------- .../src/modules/authorization/dto/index.ts | 3 -- apps/server/src/modules/users/index.ts | 2 -- package.json | 1 + 38 files changed, 101 insertions(+), 140 deletions(-) delete mode 100644 apps/server/prisma/schema.prisma create mode 100644 apps/server/src/auth/auth.module.ts create mode 100644 apps/server/src/auth/auth.resolver.ts rename apps/server/src/{modules/authorization => auth}/auth.service.ts (51%) rename apps/server/src/{modules/authorization => auth}/index.ts (100%) rename apps/server/src/{modules/authorization/dto => auth/inputs}/sign-in.input.ts (80%) create mode 100644 apps/server/src/auth/responses/index.ts rename apps/server/src/{modules/authorization/dto/token.entity.ts => auth/responses/token.response.ts} (100%) rename apps/server/src/{modules/authorization/dto/user.entity.ts => auth/responses/user.response.ts} (91%) rename apps/server/src/{modules/authorization => auth}/strategies/jwt.strategy.ts (56%) rename apps/server/src/{modules/authorization => auth}/strategies/local.strategy.ts (85%) create mode 100644 apps/server/src/auth/types.ts rename apps/server/src/{ => common}/decorators/index.ts (100%) rename apps/server/src/{ => common}/decorators/string-field.decorator.ts (100%) rename apps/server/src/{ => common}/decorators/user.decorator.ts (100%) rename apps/server/src/{ => common}/exception-filters/http-exception.filter.ts (94%) rename apps/server/src/{ => common}/exception-filters/index.ts (100%) rename apps/server/src/{ => common}/guards/gql-auth.guard.ts (100%) rename apps/server/src/{ => common}/guards/gql-local-auth.guard.ts (93%) create mode 100644 apps/server/src/common/guards/index.ts rename apps/server/src/{ => common}/guards/jwt-auth.guard.ts (87%) rename apps/server/src/{ => common}/pipes/validation.pipe.ts (100%) rename apps/server/src/{modules => core}/code-executor-api/code-executor.controller.ts (100%) rename apps/server/src/{modules => core}/code-executor-api/code-executor.module.ts (100%) rename apps/server/src/{modules => core}/code-executor-api/index.ts (100%) create mode 100644 apps/server/src/core/users/index.ts rename apps/server/src/{modules => core}/users/users.module.ts (60%) rename apps/server/src/{modules/users/users.service.ts => core/users/users.repository.ts} (82%) delete mode 100644 apps/server/src/guards/local-auth.guard.ts delete mode 100644 apps/server/src/guards/session-serializer.guard.ts delete mode 100644 apps/server/src/modules/authorization/auth.module.ts delete mode 100644 apps/server/src/modules/authorization/auth.resolver.ts delete mode 100644 apps/server/src/modules/authorization/dto/index.ts delete mode 100644 apps/server/src/modules/users/index.ts diff --git a/.eslintrc.js b/.eslintrc.js index 9e094e62..9d6c18f7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -36,8 +36,7 @@ module.exports = configure({ "react/no-array-index-key": "warn", "@typescript-eslint/no-explicit-any": "warn", "dot-notation": "off", - "prefer-arrow/prefer-arrow-functions": "error", - "@typescript-eslint/no-unused-vars": "warn" + "prefer-arrow/prefer-arrow-functions": "error" } } }) diff --git a/apps/server/prisma/schema.prisma b/apps/server/prisma/schema.prisma deleted file mode 100644 index 8aecc76f..00000000 --- a/apps/server/prisma/schema.prisma +++ /dev/null @@ -1,15 +0,0 @@ -generator client { - provider = "prisma-client-js" -} - -datasource database { - provider = "mysql" - url = env("DATABASE_URL") -} -// Users model - -model users { - username String @id @database.VarChar(14) - password String @database.VarChar(150) - avatarUrl String? @database.VarChar(100) -} diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index 4bb7b32c..c383033a 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -1,14 +1,12 @@ import { join } from 'path' -import { AuthModule } from '@/modules/authorization' +import { AuthModule } from '@/auth' import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo' import { Module } from '@nestjs/common' import { ConfigModule } from '@nestjs/config' import { GraphQLModule } from '@nestjs/graphql' -import { CodeExecutorModule } from './modules/code-executor-api' - -import { clientUrl } from '$/config' +import { CodeExecutorModule } from './core/code-executor-api' @Module({ imports: [ diff --git a/apps/server/src/auth/auth.module.ts b/apps/server/src/auth/auth.module.ts new file mode 100644 index 00000000..218dc69f --- /dev/null +++ b/apps/server/src/auth/auth.module.ts @@ -0,0 +1,26 @@ +import { UsersModule } from '@/core/users' +import { Module } from '@nestjs/common' +import { JwtModule } from '@nestjs/jwt' + +import { JwtStrategy } from './strategies/jwt.strategy' +import { LocalStrategy } from './strategies/local.strategy' +import { AuthResolver } from './auth.resolver' +import { AuthService } from './auth.service' + +import { jwtSecret } from '$/config' + +@Module({ + controllers: [], + providers: [AuthResolver, JwtStrategy, LocalStrategy, AuthService], + imports: [ + UsersModule, + JwtModule.register({ + secret: jwtSecret, + signOptions: { + expiresIn: '24h' + } + }) + ], + exports: [] +}) +export class AuthModule {} diff --git a/apps/server/src/auth/auth.resolver.ts b/apps/server/src/auth/auth.resolver.ts new file mode 100644 index 00000000..e5603a1d --- /dev/null +++ b/apps/server/src/auth/auth.resolver.ts @@ -0,0 +1,30 @@ +import { WithUser } from '@/common/decorators' +import { GqlAuthGuard, GqlLocalAuthGuard, JwtAuthGuard } from '@/common/guards' +import { UseGuards } from '@nestjs/common' +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql' + +import { SignIn } from './inputs/sign-in.input' +import { AuthService } from './auth.service' +import { AccessToken, UserResponse } from './responses' + +import { graphqlArg } from '$/config' + +@Resolver(() => UserResponse) +export class AuthResolver { + constructor(private authService: AuthService) {} + + @Mutation(() => AccessToken) + @UseGuards(GqlAuthGuard, GqlLocalAuthGuard) + async signIn( + @Args(graphqlArg) payload: SignIn, + @WithUser() user + ): Promise { + return this.authService.generateToken(user.username) + } + + @Query(() => UserResponse) + @UseGuards(JwtAuthGuard) + async getProfile(@WithUser() user): Promise { + return user + } +} diff --git a/apps/server/src/modules/authorization/auth.service.ts b/apps/server/src/auth/auth.service.ts similarity index 51% rename from apps/server/src/modules/authorization/auth.service.ts rename to apps/server/src/auth/auth.service.ts index 51c57672..1cae1b00 100644 --- a/apps/server/src/modules/authorization/auth.service.ts +++ b/apps/server/src/auth/auth.service.ts @@ -1,19 +1,21 @@ import * as bcrypt from 'bcryptjs' -import { UsersService } from '@/modules/users' -import { HttpException, HttpStatus, Injectable } from '@nestjs/common' +import { UsersRepository } from '@/core/users' +import { Injectable } from '@nestjs/common' import { JwtService } from '@nestjs/jwt' -import { SignIn } from './dto' +import { SignIn } from './inputs/sign-in.input' +import { AccessToken, UserResponse } from './responses' +import { JwtTokenPayload } from './types' @Injectable() export class AuthService { constructor( private readonly jwtService: JwtService, - private readonly usersService: UsersService + private readonly usersService: UsersRepository ) {} - async validate(payload: SignIn) { + public async validate(payload: SignIn): Promise { const user = await this.usersService.getUserByUsername(payload.username) if (!user) { @@ -32,19 +34,19 @@ export class AuthService { return user } - async registerUser(payload: SignIn) { - const hashPassword = await bcrypt.hash(payload.password, 5) + public async registerUser(user: SignIn): Promise { + const hashPassword = await bcrypt.hash(user.password, 5) return this.usersService.createUser({ - ...payload, + ...user, password: hashPassword }) } - async generateToken(username: string) { + public async generateToken(username: string): Promise { const accessToken = this.jwtService.sign({ - payload: { username } - }) + username + } satisfies JwtTokenPayload) return { accessToken } } diff --git a/apps/server/src/modules/authorization/index.ts b/apps/server/src/auth/index.ts similarity index 100% rename from apps/server/src/modules/authorization/index.ts rename to apps/server/src/auth/index.ts diff --git a/apps/server/src/modules/authorization/dto/sign-in.input.ts b/apps/server/src/auth/inputs/sign-in.input.ts similarity index 80% rename from apps/server/src/modules/authorization/dto/sign-in.input.ts rename to apps/server/src/auth/inputs/sign-in.input.ts index 9b13b23a..a949b9d3 100644 --- a/apps/server/src/modules/authorization/dto/sign-in.input.ts +++ b/apps/server/src/auth/inputs/sign-in.input.ts @@ -1,4 +1,4 @@ -import { StringField } from '@/decorators' +import { StringField } from '@/common/decorators' import { InputType } from '@nestjs/graphql' @InputType() diff --git a/apps/server/src/auth/responses/index.ts b/apps/server/src/auth/responses/index.ts new file mode 100644 index 00000000..893fd5c8 --- /dev/null +++ b/apps/server/src/auth/responses/index.ts @@ -0,0 +1,2 @@ +export { AccessToken } from './token.response' +export { UserResponse } from './user.response' diff --git a/apps/server/src/modules/authorization/dto/token.entity.ts b/apps/server/src/auth/responses/token.response.ts similarity index 100% rename from apps/server/src/modules/authorization/dto/token.entity.ts rename to apps/server/src/auth/responses/token.response.ts diff --git a/apps/server/src/modules/authorization/dto/user.entity.ts b/apps/server/src/auth/responses/user.response.ts similarity index 91% rename from apps/server/src/modules/authorization/dto/user.entity.ts rename to apps/server/src/auth/responses/user.response.ts index 42cf6947..a8a7fff7 100644 --- a/apps/server/src/modules/authorization/dto/user.entity.ts +++ b/apps/server/src/auth/responses/user.response.ts @@ -2,7 +2,7 @@ import { Field, ObjectType } from '@nestjs/graphql' import { ApiProperty } from '@nestjs/swagger' @ObjectType() -export class User { +export class UserResponse { @Field() @ApiProperty({ description: 'Username (used as userId)' }) username: string diff --git a/apps/server/src/modules/authorization/strategies/jwt.strategy.ts b/apps/server/src/auth/strategies/jwt.strategy.ts similarity index 56% rename from apps/server/src/modules/authorization/strategies/jwt.strategy.ts rename to apps/server/src/auth/strategies/jwt.strategy.ts index 202c08fc..7a0d56ed 100644 --- a/apps/server/src/modules/authorization/strategies/jwt.strategy.ts +++ b/apps/server/src/auth/strategies/jwt.strategy.ts @@ -1,15 +1,16 @@ import { ExtractJwt, Strategy } from 'passport-jwt' -import { SignIn } from '@/modules/authorization/dto' -import { UsersService } from '@/modules/users' +import { UsersRepository } from '@/core/users' import { Injectable } from '@nestjs/common' import { PassportStrategy } from '@nestjs/passport' +import { JwtTokenPayload } from '../types' + import { jwtSecret } from '$/config' @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(private readonly usersService: UsersService) { + constructor(private readonly usersService: UsersRepository) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, @@ -17,8 +18,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { }) } - validate(payload: { payload: { username: string } }) { - console.log(payload) - return this.usersService.getUserByUsername(payload.payload.username) + validate(payload: JwtTokenPayload) { + return this.usersService.getUserByUsername(payload.username) } } diff --git a/apps/server/src/modules/authorization/strategies/local.strategy.ts b/apps/server/src/auth/strategies/local.strategy.ts similarity index 85% rename from apps/server/src/modules/authorization/strategies/local.strategy.ts rename to apps/server/src/auth/strategies/local.strategy.ts index a0a43c0a..818eb795 100644 --- a/apps/server/src/modules/authorization/strategies/local.strategy.ts +++ b/apps/server/src/auth/strategies/local.strategy.ts @@ -1,6 +1,5 @@ import { Strategy } from 'passport-local' -import { SignIn } from '@/modules/authorization/dto' import { Injectable, InternalServerErrorException, @@ -10,13 +9,15 @@ import { PassportStrategy } from '@nestjs/passport' import { AuthService } from '../auth.service' +import { UserResponse } from './../responses' + @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private readonly authService: AuthService) { super({ usernameField: 'username', passwordField: 'password' }) } - async validate(username: string, password: string) { + async validate(username: string, password: string): Promise { try { const user = await this.authService.validate({ username, diff --git a/apps/server/src/auth/types.ts b/apps/server/src/auth/types.ts new file mode 100644 index 00000000..ebb89786 --- /dev/null +++ b/apps/server/src/auth/types.ts @@ -0,0 +1,3 @@ +import { UserResponse } from './responses' + +export type JwtTokenPayload = Pick diff --git a/apps/server/src/decorators/index.ts b/apps/server/src/common/decorators/index.ts similarity index 100% rename from apps/server/src/decorators/index.ts rename to apps/server/src/common/decorators/index.ts diff --git a/apps/server/src/decorators/string-field.decorator.ts b/apps/server/src/common/decorators/string-field.decorator.ts similarity index 100% rename from apps/server/src/decorators/string-field.decorator.ts rename to apps/server/src/common/decorators/string-field.decorator.ts diff --git a/apps/server/src/decorators/user.decorator.ts b/apps/server/src/common/decorators/user.decorator.ts similarity index 100% rename from apps/server/src/decorators/user.decorator.ts rename to apps/server/src/common/decorators/user.decorator.ts diff --git a/apps/server/src/exception-filters/http-exception.filter.ts b/apps/server/src/common/exception-filters/http-exception.filter.ts similarity index 94% rename from apps/server/src/exception-filters/http-exception.filter.ts rename to apps/server/src/common/exception-filters/http-exception.filter.ts index 12964e63..09780d36 100644 --- a/apps/server/src/exception-filters/http-exception.filter.ts +++ b/apps/server/src/common/exception-filters/http-exception.filter.ts @@ -7,8 +7,6 @@ import { HttpException } from '@nestjs/common' -import { graphqlArg } from '$/config' - @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { diff --git a/apps/server/src/exception-filters/index.ts b/apps/server/src/common/exception-filters/index.ts similarity index 100% rename from apps/server/src/exception-filters/index.ts rename to apps/server/src/common/exception-filters/index.ts diff --git a/apps/server/src/guards/gql-auth.guard.ts b/apps/server/src/common/guards/gql-auth.guard.ts similarity index 100% rename from apps/server/src/guards/gql-auth.guard.ts rename to apps/server/src/common/guards/gql-auth.guard.ts diff --git a/apps/server/src/guards/gql-local-auth.guard.ts b/apps/server/src/common/guards/gql-local-auth.guard.ts similarity index 93% rename from apps/server/src/guards/gql-local-auth.guard.ts rename to apps/server/src/common/guards/gql-local-auth.guard.ts index 3b8dfe00..ba8b5f88 100644 --- a/apps/server/src/guards/gql-local-auth.guard.ts +++ b/apps/server/src/common/guards/gql-local-auth.guard.ts @@ -6,7 +6,6 @@ import { AuthGuard } from '@nestjs/passport' export class GqlLocalAuthGuard extends AuthGuard('local') { async canActivate(context: ExecutionContext): Promise { const ctxRequest = GqlExecutionContext.create(context).getContext().req - console.log(ctxRequest) return Boolean(ctxRequest) } } diff --git a/apps/server/src/common/guards/index.ts b/apps/server/src/common/guards/index.ts new file mode 100644 index 00000000..5d3868a2 --- /dev/null +++ b/apps/server/src/common/guards/index.ts @@ -0,0 +1,3 @@ +export { GqlAuthGuard } from './gql-auth.guard' +export { GqlLocalAuthGuard } from './gql-local-auth.guard' +export { JwtAuthGuard } from './jwt-auth.guard' diff --git a/apps/server/src/guards/jwt-auth.guard.ts b/apps/server/src/common/guards/jwt-auth.guard.ts similarity index 87% rename from apps/server/src/guards/jwt-auth.guard.ts rename to apps/server/src/common/guards/jwt-auth.guard.ts index b74c9388..60d40233 100644 --- a/apps/server/src/guards/jwt-auth.guard.ts +++ b/apps/server/src/common/guards/jwt-auth.guard.ts @@ -5,7 +5,6 @@ import { AuthGuard } from '@nestjs/passport' export class JwtAuthGuard extends AuthGuard('jwt') { getRequest(context: ExecutionContext) { const ctx = GqlExecutionContext.create(context) - console.log(ctx.getContext().req.headers) return ctx.getContext().req } } diff --git a/apps/server/src/pipes/validation.pipe.ts b/apps/server/src/common/pipes/validation.pipe.ts similarity index 100% rename from apps/server/src/pipes/validation.pipe.ts rename to apps/server/src/common/pipes/validation.pipe.ts diff --git a/apps/server/src/modules/code-executor-api/code-executor.controller.ts b/apps/server/src/core/code-executor-api/code-executor.controller.ts similarity index 100% rename from apps/server/src/modules/code-executor-api/code-executor.controller.ts rename to apps/server/src/core/code-executor-api/code-executor.controller.ts diff --git a/apps/server/src/modules/code-executor-api/code-executor.module.ts b/apps/server/src/core/code-executor-api/code-executor.module.ts similarity index 100% rename from apps/server/src/modules/code-executor-api/code-executor.module.ts rename to apps/server/src/core/code-executor-api/code-executor.module.ts diff --git a/apps/server/src/modules/code-executor-api/index.ts b/apps/server/src/core/code-executor-api/index.ts similarity index 100% rename from apps/server/src/modules/code-executor-api/index.ts rename to apps/server/src/core/code-executor-api/index.ts diff --git a/apps/server/src/core/users/index.ts b/apps/server/src/core/users/index.ts new file mode 100644 index 00000000..be392568 --- /dev/null +++ b/apps/server/src/core/users/index.ts @@ -0,0 +1,2 @@ +export { UsersModule } from './users.module' +export { UsersRepository } from './users.repository' diff --git a/apps/server/src/modules/users/users.module.ts b/apps/server/src/core/users/users.module.ts similarity index 60% rename from apps/server/src/modules/users/users.module.ts rename to apps/server/src/core/users/users.module.ts index 8c4f57ca..797841a8 100644 --- a/apps/server/src/modules/users/users.module.ts +++ b/apps/server/src/core/users/users.module.ts @@ -1,13 +1,13 @@ import { Module } from '@nestjs/common' -import { UsersService } from './users.service' +import { UsersRepository } from './users.repository' import { PrismaModule } from '$/services' @Module({ controllers: [], - providers: [UsersService], + providers: [UsersRepository], imports: [PrismaModule], - exports: [UsersService] + exports: [UsersRepository] }) export class UsersModule {} diff --git a/apps/server/src/modules/users/users.service.ts b/apps/server/src/core/users/users.repository.ts similarity index 82% rename from apps/server/src/modules/users/users.service.ts rename to apps/server/src/core/users/users.repository.ts index 1aa03b46..35a751fd 100644 --- a/apps/server/src/modules/users/users.service.ts +++ b/apps/server/src/core/users/users.repository.ts @@ -1,10 +1,10 @@ -import { SignIn } from '@/modules/authorization/dto' +import { SignIn } from '@/auth/inputs/sign-in.input' import { Injectable } from '@nestjs/common' import { PrismaService } from '$/services' @Injectable() -export class UsersService { +export class UsersRepository { constructor(private prisma: PrismaService) {} public getUserByUsername(username: string) { diff --git a/apps/server/src/guards/local-auth.guard.ts b/apps/server/src/guards/local-auth.guard.ts deleted file mode 100644 index 8accbc2c..00000000 --- a/apps/server/src/guards/local-auth.guard.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AuthGuard } from '@nestjs/passport' - -export class LocalAuthGuard extends AuthGuard('local') {} diff --git a/apps/server/src/guards/session-serializer.guard.ts b/apps/server/src/guards/session-serializer.guard.ts deleted file mode 100644 index f2057171..00000000 --- a/apps/server/src/guards/session-serializer.guard.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@nestjs/common' -import { PassportSerializer } from '@nestjs/passport' - -@Injectable() -export class SessionSerializer extends PassportSerializer { - serializeUser(user: any, done: CallableFunction): any { - return done(null, user) - } - - deserializeUser(payload: any, done: CallableFunction): any { - return done(null, payload) - } -} diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 4c878cd7..ee75aff2 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -1,7 +1,7 @@ import { corsConfig } from '@/config/cors' import { createSwaggerDocs } from '@/config/swagger' -import { HttpExceptionFilter } from '@/exception-filters' -import { ValidationPipe } from '@/pipes/validation.pipe' +import { HttpExceptionFilter } from '@/common/exception-filters' +import { ValidationPipe } from '@/common/pipes/validation.pipe' import { Logger } from '@nestjs/common' import { NestFactory } from '@nestjs/core' import { SwaggerModule } from '@nestjs/swagger' diff --git a/apps/server/src/modules/authorization/auth.module.ts b/apps/server/src/modules/authorization/auth.module.ts deleted file mode 100644 index 6def727a..00000000 --- a/apps/server/src/modules/authorization/auth.module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { SessionSerializer } from '@/guards/session-serializer.guard' -import { AuthResolver } from '@/modules/authorization/auth.resolver' -import { AuthService } from '@/modules/authorization/auth.service' -import { UsersModule } from '@/modules/users' -import { Module } from '@nestjs/common' -import { JwtModule } from '@nestjs/jwt' -import { PassportModule } from '@nestjs/passport' - -import { JwtStrategy } from './strategies/jwt.strategy' -import { LocalStrategy } from './strategies/local.strategy' - -import { jwtSecret } from '$/config' - -@Module({ - controllers: [], - providers: [ - AuthResolver, - JwtStrategy, - LocalStrategy, - AuthService, - SessionSerializer - ], - imports: [ - UsersModule, - JwtModule.register({ - secret: jwtSecret, - signOptions: { - expiresIn: '24h' - } - }) - ], - exports: [LocalStrategy] -}) -export class AuthModule {} diff --git a/apps/server/src/modules/authorization/auth.resolver.ts b/apps/server/src/modules/authorization/auth.resolver.ts deleted file mode 100644 index b73eee9b..00000000 --- a/apps/server/src/modules/authorization/auth.resolver.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { WithUser } from '@/decorators' -import { GqlAuthGuard } from '@/guards/gql-auth.guard' -import { GqlLocalAuthGuard } from '@/guards/gql-local-auth.guard' -import { JwtAuthGuard } from '@/guards/jwt-auth.guard' -import { UseGuards } from '@nestjs/common' -import { Args, Mutation, Query, Resolver } from '@nestjs/graphql' - -import { AuthService } from './auth.service' -import { AccessToken, SignIn, User } from './dto' - -import { graphqlArg } from '$/config' - -@Resolver(() => User) -export class AuthResolver { - constructor(private authService: AuthService) {} - @Query(() => AccessToken) - @UseGuards(GqlAuthGuard, GqlLocalAuthGuard) - async signIn(@Args(graphqlArg) payload: SignIn, @WithUser() user) { - return this.authService.generateToken(user.username) - } - - // @Mutation(() => AccessToken) - - // Graphql requires at least one @Query to - // work so for now this will be here lol - @Query(() => User) - @UseGuards(JwtAuthGuard) - sayHello(@WithUser() user): string { - console.log('say-hello') - return user - } -} diff --git a/apps/server/src/modules/authorization/dto/index.ts b/apps/server/src/modules/authorization/dto/index.ts deleted file mode 100644 index dc141e38..00000000 --- a/apps/server/src/modules/authorization/dto/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { SignIn } from './sign-in.input' -export { AccessToken } from './token.entity' -export { User } from './user.entity' diff --git a/apps/server/src/modules/users/index.ts b/apps/server/src/modules/users/index.ts deleted file mode 100644 index 8a3b8492..00000000 --- a/apps/server/src/modules/users/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { UsersModule } from './users.module' -export { UsersService } from './users.service' diff --git a/package.json b/package.json index f93c0782..e55a1bf3 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "eslint-plugin-prefer-arrow": "^1.2.3", "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "4.6.0", + "express": "^4.18.2", "hygen": "^6.2.11", "jest": "^29.4.1", "jest-environment-node": "^29.5.0", From 44a80db3d6142f83fd789dd614e46fb851f37ec7 Mon Sep 17 00:00:00 2001 From: Gearonix Date: Sat, 29 Jul 2023 12:44:27 +0300 Subject: [PATCH 08/10] feat: finished authorization on client --- .../app/providers/router/config/router.tsx | 5 ++ .../ui/sign-in-modal-template.styles.ts | 4 +- .../ui/sign-in-modal-template.tsx | 8 +++- apps/client/src/pages/about/ui/about.tsx | 4 +- apps/client/src/pages/edit/ui/EditPage.tsx | 9 ++-- .../src/pages/not-found/ui/not-found.tsx | 4 +- apps/client/src/pages/profile/index.ts | 1 + .../src/pages/profile/ui/profile-page.tsx | 7 +++ .../shared/lib/components/page/auth-guard.tsx | 42 ++++++++++++++++ .../src/shared/lib/components/page/index.ts | 1 + .../src/shared/lib/components/page/page.tsx | 13 +++++ apps/client/src/shared/lib/index.ts | 1 + .../graphql/get-profile.query.ts | 15 ++++++ .../sign-in-modal/graphql/sign-in.mutation.ts | 7 ++- .../client/src/widgets/sign-in-modal/index.ts | 2 +- .../widgets/sign-in-modal/lib/exceptions.ts | 1 + .../sign-in-modal/store/auth.services.ts | 46 +++++++++++++++--- .../widgets/sign-in-modal/store/auth.store.ts | 3 +- .../client/src/widgets/sign-in-modal/types.ts | 16 +++++-- .../sign-in-modal/ui/sign-in-modal.tsx | 38 +++++++++++++-- apps/client/vite.config.ts | 3 +- .../http-exception.filter.ts | 2 - .../src/common/guards/gql-auth.guard.ts | 4 +- libs/client-shared/env.d.ts | 1 + .../client-shared/src/config/apollo-client.ts | 26 +++++++++- libs/client-shared/src/config/index.ts | 2 +- .../client-shared/src/config/local-storage.ts | 3 +- libs/client-shared/src/config/paths.ts | 8 +++- libs/client-shared/src/hooks/index.ts | 2 + .../src/hooks/use-async-effect.ts | 10 ++++ .../src/hooks/use-filtered-effect.ts | 19 ++++++++ .../src/lib/apollo/apollo-middleware.ts | 48 +++++++++++++++++++ libs/client-shared/src/lib/apollo/index.ts | 2 +- libs/client-shared/src/lib/apollo/mutate.ts | 20 -------- .../client-shared/src/lib/components/Page.tsx | 8 ---- .../client-shared/src/lib/components/index.ts | 2 +- .../src/lib/helpers/local-storage.ts | 12 +++-- .../client-shared/src/types/common/graphql.ts | 2 +- libs/client-shared/src/ui/index.ts | 2 +- libs/client-shared/vite.config.ts | 17 ------- .../ui/header-right-section.tsx | 10 ++-- 41 files changed, 332 insertions(+), 98 deletions(-) create mode 100644 apps/client/src/pages/profile/index.ts create mode 100644 apps/client/src/pages/profile/ui/profile-page.tsx create mode 100644 apps/client/src/shared/lib/components/page/auth-guard.tsx create mode 100644 apps/client/src/shared/lib/components/page/index.ts create mode 100644 apps/client/src/shared/lib/components/page/page.tsx create mode 100644 apps/client/src/shared/lib/index.ts create mode 100644 apps/client/src/widgets/sign-in-modal/graphql/get-profile.query.ts create mode 100644 apps/client/src/widgets/sign-in-modal/lib/exceptions.ts create mode 100644 libs/client-shared/env.d.ts create mode 100644 libs/client-shared/src/hooks/use-async-effect.ts create mode 100644 libs/client-shared/src/hooks/use-filtered-effect.ts create mode 100644 libs/client-shared/src/lib/apollo/apollo-middleware.ts delete mode 100644 libs/client-shared/src/lib/apollo/mutate.ts delete mode 100644 libs/client-shared/src/lib/components/Page.tsx diff --git a/apps/client/src/app/providers/router/config/router.tsx b/apps/client/src/app/providers/router/config/router.tsx index 94a35de4..fcd698a8 100644 --- a/apps/client/src/app/providers/router/config/router.tsx +++ b/apps/client/src/app/providers/router/config/router.tsx @@ -4,6 +4,7 @@ import { About } from '@/pages/about' import { EditPage } from '@/pages/edit' import { Main } from '@/pages/main' import { NotFound } from '@/pages/not-found' +import { ProfilePage } from '@/pages/profile' import { RoutePaths } from '$/client-shared' @@ -20,6 +21,10 @@ const router = createBrowserRouter([ path: RoutePaths.ABOUT, element: }, + { + path: `${RoutePaths.PROFILE}/:username`, + element: + }, { path: '*', element: diff --git a/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts b/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts index 857b04d0..1cddc55e 100644 --- a/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts +++ b/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts @@ -1,7 +1,7 @@ import { Form } from 'antd' import styled from 'styled-components' -import { ColorButton } from '$/client-shared' +import { ColoredButton } from '$/client-shared' import { wh } from '$/styles' export const SignInModalStyles = styled(Form)` @@ -9,7 +9,7 @@ export const SignInModalStyles = styled(Form)` margin: 0 auto; ` -export const SubmitButton = styled(ColorButton)` +export const SubmitButton = styled.button` height: 40px; width: 100%; cursor: pointer; diff --git a/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.tsx b/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.tsx index fc122fd4..e2324069 100644 --- a/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.tsx +++ b/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.tsx @@ -9,7 +9,7 @@ import { } from './sign-in-modal-template.styles' import { Logo } from '$/assets' -import { ModalTitle } from '$/client-shared' +import { ColoredButton, ModalTitle } from '$/client-shared' interface SignInModalTemplateProps { onSubmit: (data: T) => void @@ -38,7 +38,11 @@ export const SignInModalTemplate = ({ /> - + Sign In diff --git a/apps/client/src/pages/about/ui/about.tsx b/apps/client/src/pages/about/ui/about.tsx index fea66505..67f2371f 100644 --- a/apps/client/src/pages/about/ui/about.tsx +++ b/apps/client/src/pages/about/ui/about.tsx @@ -1,5 +1,7 @@ +import { Page } from '$/client-shared'; + const About = () => { - return

about page
+ return about page } export default About diff --git a/apps/client/src/pages/edit/ui/EditPage.tsx b/apps/client/src/pages/edit/ui/EditPage.tsx index 7f70b72c..acaa3072 100644 --- a/apps/client/src/pages/edit/ui/EditPage.tsx +++ b/apps/client/src/pages/edit/ui/EditPage.tsx @@ -2,13 +2,16 @@ import { Suspense } from 'react' import { SignInModal } from '@/widgets/sign-in-modal' +import { Page } from '$/client-shared' import { Editor } from '$/editor' const EditPage = () => { return ( - - - + + + + + ) } diff --git a/apps/client/src/pages/not-found/ui/not-found.tsx b/apps/client/src/pages/not-found/ui/not-found.tsx index b604937e..673ef559 100644 --- a/apps/client/src/pages/not-found/ui/not-found.tsx +++ b/apps/client/src/pages/not-found/ui/not-found.tsx @@ -1,5 +1,7 @@ +import { Page } from '$/client-shared'; + const NotFound = () => { - return
not found
+ return not found } export default NotFound diff --git a/apps/client/src/pages/profile/index.ts b/apps/client/src/pages/profile/index.ts new file mode 100644 index 00000000..e37b3ffc --- /dev/null +++ b/apps/client/src/pages/profile/index.ts @@ -0,0 +1 @@ +export { default as ProfilePage } from './ui/profile-page' diff --git a/apps/client/src/pages/profile/ui/profile-page.tsx b/apps/client/src/pages/profile/ui/profile-page.tsx new file mode 100644 index 00000000..9e75dba3 --- /dev/null +++ b/apps/client/src/pages/profile/ui/profile-page.tsx @@ -0,0 +1,7 @@ +import { Page } from '$/client-shared' + +const ProfilePage = () => { + return profile +} + +export default ProfilePage diff --git a/apps/client/src/shared/lib/components/page/auth-guard.tsx b/apps/client/src/shared/lib/components/page/auth-guard.tsx new file mode 100644 index 00000000..c9c3e28b --- /dev/null +++ b/apps/client/src/shared/lib/components/page/auth-guard.tsx @@ -0,0 +1,42 @@ +import { useEffect } from 'react' +import { observer } from 'mobx-react-lite' +import { useLocation, useNavigate } from 'react-router-dom' + +import { useStore } from '@/shared/hooks' + +import { + PrivatePaths, + RoutePaths, + useAsyncEffect, + useBooleanState, + WithChildren +} from '$/client-shared' + +const AuthGuard = observer(({ children }: WithChildren) => { + const { isAuthorized, services } = useStore('auth') + const location = useLocation() + const navigate = useNavigate() + const loader = useBooleanState() + + useAsyncEffect(async () => { + await services.getProfile() + loader.on() + }, []) + + useEffect(() => { + if (!loader.val) { + return + } + + const currentPath = location.pathname.split('/')[1] + const isPrivatePath = PrivatePaths.includes(`/${currentPath}`) + + if (isPrivatePath && !isAuthorized) { + navigate(RoutePaths.EDITOR) + } + }, [location, loader.val]) + + return children +}) + +export default AuthGuard diff --git a/apps/client/src/shared/lib/components/page/index.ts b/apps/client/src/shared/lib/components/page/index.ts new file mode 100644 index 00000000..7e3f7af1 --- /dev/null +++ b/apps/client/src/shared/lib/components/page/index.ts @@ -0,0 +1 @@ +export { default as Page } from './page' diff --git a/apps/client/src/shared/lib/components/page/page.tsx b/apps/client/src/shared/lib/components/page/page.tsx new file mode 100644 index 00000000..13a3f04e --- /dev/null +++ b/apps/client/src/shared/lib/components/page/page.tsx @@ -0,0 +1,13 @@ +import AuthGuard from '@/shared/lib/components/page/auth-guard' + +import { ErrorBoundary, WithChildren } from '$/client-shared' + +const Page = ({ children }: WithChildren) => { + return ( + + {children} + + ) +} + +export default Page diff --git a/apps/client/src/shared/lib/index.ts b/apps/client/src/shared/lib/index.ts new file mode 100644 index 00000000..fe5465db --- /dev/null +++ b/apps/client/src/shared/lib/index.ts @@ -0,0 +1 @@ +export * from './components/page' diff --git a/apps/client/src/widgets/sign-in-modal/graphql/get-profile.query.ts b/apps/client/src/widgets/sign-in-modal/graphql/get-profile.query.ts new file mode 100644 index 00000000..4629211e --- /dev/null +++ b/apps/client/src/widgets/sign-in-modal/graphql/get-profile.query.ts @@ -0,0 +1,15 @@ +import { gql } from '@apollo/react-hooks' + +import { ApolloOperation } from '$/client-shared' + +export const getProfileQuery: ApolloOperation = { + gql: gql` + query { + getProfile { + username + avatarUrl + } + } + `, + method: 'getProfile' +} diff --git a/apps/client/src/widgets/sign-in-modal/graphql/sign-in.mutation.ts b/apps/client/src/widgets/sign-in-modal/graphql/sign-in.mutation.ts index 7b600d9a..7f7f369c 100644 --- a/apps/client/src/widgets/sign-in-modal/graphql/sign-in.mutation.ts +++ b/apps/client/src/widgets/sign-in-modal/graphql/sign-in.mutation.ts @@ -1,13 +1,12 @@ import { gql } from '@apollo/react-hooks' -import { ApolloMutation } from '$/client-shared' +import { ApolloOperation } from '$/client-shared' -export const SignInMutation: ApolloMutation = { +export const SignInMutation: ApolloOperation = { gql: gql` mutation SignIn($payload: SignIn!) { signIn(_graphql: $payload) { - username - avatarUrl + accessToken } } `, diff --git a/apps/client/src/widgets/sign-in-modal/index.ts b/apps/client/src/widgets/sign-in-modal/index.ts index 430ac96a..0e186892 100644 --- a/apps/client/src/widgets/sign-in-modal/index.ts +++ b/apps/client/src/widgets/sign-in-modal/index.ts @@ -1,2 +1,2 @@ export { AuthStore } from './store/auth.store' -export { SignInModal } from './ui/sign-in-modal' +export { default as SignInModal } from './ui/sign-in-modal' diff --git a/apps/client/src/widgets/sign-in-modal/lib/exceptions.ts b/apps/client/src/widgets/sign-in-modal/lib/exceptions.ts new file mode 100644 index 00000000..228f62fe --- /dev/null +++ b/apps/client/src/widgets/sign-in-modal/lib/exceptions.ts @@ -0,0 +1 @@ +export const WrongPassword = 'You entered the wrong password.' diff --git a/apps/client/src/widgets/sign-in-modal/store/auth.services.ts b/apps/client/src/widgets/sign-in-modal/store/auth.services.ts index b5c9422a..23167062 100644 --- a/apps/client/src/widgets/sign-in-modal/store/auth.services.ts +++ b/apps/client/src/widgets/sign-in-modal/store/auth.services.ts @@ -1,17 +1,51 @@ import { makeAutoObservable } from 'mobx' +import { AuthStore } from '@/widgets/sign-in-modal' +import { getProfileQuery } from '@/widgets/sign-in-modal/graphql/get-profile.query' + import { SignInMutation } from '../graphql/sign-in.mutation' -import { SignInForm, SignInResponse } from '../types' +import { AccessToken, SignInForm, SignInResponse, UserEntity } from '../types' -import { mutate } from '$/client-shared' +import { ApolloMiddleware, LocalStorageClient } from '$/client-shared' export class AuthServices { - constructor() { + private state: AuthStore + private readonly storage: LocalStorageClient + private readonly apollo: ApolloMiddleware + + constructor(root) { + this.state = root + this.storage = new LocalStorageClient() + this.apollo = new ApolloMiddleware() makeAutoObservable(this) } - async signIn(form: SignInForm) { - const res = await mutate(SignInMutation, form) - console.log(res) + async signIn(form: SignInForm): Promise { + const [payload] = await this.apollo.operate( + SignInMutation, + form, + 'mutate' + ) + + if (payload) { + this.storage.set('AUTH_TOKEN', payload.accessToken) + + await this.getProfile() + } + + return { + isError: !payload + } + } + + async getProfile() { + const [payload] = await this.apollo.operate( + getProfileQuery + ) + + if (payload) { + this.state.username = payload.username + this.state.isAuthorized = true + } } } diff --git a/apps/client/src/widgets/sign-in-modal/store/auth.store.ts b/apps/client/src/widgets/sign-in-modal/store/auth.store.ts index b2291dc5..023f5b47 100644 --- a/apps/client/src/widgets/sign-in-modal/store/auth.store.ts +++ b/apps/client/src/widgets/sign-in-modal/store/auth.store.ts @@ -9,6 +9,7 @@ export class AuthStore { constructor() { makeAutoObservable(this) - this.services = new AuthServices() + + this.services = new AuthServices(this) } } diff --git a/apps/client/src/widgets/sign-in-modal/types.ts b/apps/client/src/widgets/sign-in-modal/types.ts index 2ab80897..95a7d8dd 100644 --- a/apps/client/src/widgets/sign-in-modal/types.ts +++ b/apps/client/src/widgets/sign-in-modal/types.ts @@ -1,9 +1,19 @@ -export interface SignInForm { +interface WithUsername { username: string +} + +export interface SignInForm extends WithUsername{ password: string } +export interface AccessToken { + accessToken: string +} + +export interface UserEntity extends WithUsername { + avatarUrl: string +} + export interface SignInResponse { - username: string - avatarUrl: string | null + isError: boolean } diff --git a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx index ae586a70..0767444d 100644 --- a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx +++ b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx @@ -1,26 +1,56 @@ +import { useEffect } from 'react' import { observer } from 'mobx-react-lite' +import { useNavigate } from 'react-router-dom' import { SignInModalTemplate } from '@/entities/sign-in-modal-template' import { useStore } from '@/shared/hooks' +import { WrongPassword } from '@/widgets/sign-in-modal/lib/exceptions' import { SignInForm } from '@/widgets/sign-in-modal/types' -import { Modal, VoidFunction } from '$/client-shared' +import { + Modal, + NotificationsProvider, + RoutePaths, + useFilteredEffect, + useNotifications, + VoidFunction +} from '$/client-shared' interface SignInModalProps { isOpen: boolean onClose: VoidFunction } -export const SignInModal = observer(({ isOpen, onClose }: SignInModalProps) => { +const SignInModal = observer(({ isOpen, onClose }: SignInModalProps) => { const auth = useStore('auth') + const notify = useNotifications() + const navigate = useNavigate() - const onSubmit = (data: SignInForm) => { - auth.services.signIn(data) + const onSubmit = async (data: SignInForm) => { + const { isError } = await auth.services.signIn(data) + if (isError) { + return notify.open({ + type: 'error', + message: WrongPassword + }) + } } + useFilteredEffect(() => { + navigate(`${RoutePaths.PROFILE}/${auth.username}`) + }, [auth.username]) + return ( onSubmit={onSubmit} /> ) }) + +export default (props: SignInModalProps) => { + return ( + + + + ) +} diff --git a/apps/client/vite.config.ts b/apps/client/vite.config.ts index 29836da3..356b2fdc 100644 --- a/apps/client/vite.config.ts +++ b/apps/client/vite.config.ts @@ -10,7 +10,8 @@ import preact from '@preact/preset-vite' export default defineConfig({ cacheDir: '../../node_modules/.vite/client', define: { - 'process.env': process.env + 'process.env': process.env, + _isDev_: process.env.NODE_ENV === 'development' }, server: { port: 3000, diff --git a/apps/server/src/common/exception-filters/http-exception.filter.ts b/apps/server/src/common/exception-filters/http-exception.filter.ts index 09780d36..9e5fc6c0 100644 --- a/apps/server/src/common/exception-filters/http-exception.filter.ts +++ b/apps/server/src/common/exception-filters/http-exception.filter.ts @@ -15,8 +15,6 @@ export class HttpExceptionFilter implements ExceptionFilter { const request = ctx.getRequest() const status = exception.getStatus() - console.log(response) - response.status?.(status).json({ statusCode: status, timestamp: new Date().toISOString(), diff --git a/apps/server/src/common/guards/gql-auth.guard.ts b/apps/server/src/common/guards/gql-auth.guard.ts index c5c977fe..eca61b8c 100644 --- a/apps/server/src/common/guards/gql-auth.guard.ts +++ b/apps/server/src/common/guards/gql-auth.guard.ts @@ -12,9 +12,7 @@ export class GqlAuthGuard extends AuthGuard('local') { getRequest(context: ExecutionContext) { const ctx = GqlExecutionContext.create(context) const gqlReq = ctx.getContext().req - const user = ctx.getArgs()[graphqlArg] - gqlReq.body.username = user.username - gqlReq.body.password = user.password + gqlReq.body = ctx.getArgs()[graphqlArg] return gqlReq } diff --git a/libs/client-shared/env.d.ts b/libs/client-shared/env.d.ts new file mode 100644 index 00000000..2aa172f2 --- /dev/null +++ b/libs/client-shared/env.d.ts @@ -0,0 +1 @@ +declare const _isDev_: string diff --git a/libs/client-shared/src/config/apollo-client.ts b/libs/client-shared/src/config/apollo-client.ts index f32fbc4a..b5c5105f 100644 --- a/libs/client-shared/src/config/apollo-client.ts +++ b/libs/client-shared/src/config/apollo-client.ts @@ -1,12 +1,34 @@ -import { ApolloClient, InMemoryCache } from '@apollo/client' +import { LocalStorageClient } from '@/lib/helpers' +import { Nullable } from '@/types' +import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client' +import { setContext } from '@apollo/client/link/context' import { EndPoints, serverUrl } from '$/config' const graphqlUri = `${serverUrl}/${EndPoints._GRAPHQL}` +const storage = new LocalStorageClient() + +const httpLink = createHttpLink({ + uri: graphqlUri +}) + +const authLink = setContext((_, { headers }) => { + const accessToken = storage.get>('AUTH_TOKEN', '') + return { + headers: { + ...headers, + Authorization: `Bearer ${accessToken}`, + // Needed to upload files + 'apollo-require-preflight': true + } + } +}) + const apolloClient = new ApolloClient({ cache: new InMemoryCache(), - uri: graphqlUri + link: authLink.concat(httpLink) }) +// apolloClient. export default apolloClient diff --git a/libs/client-shared/src/config/index.ts b/libs/client-shared/src/config/index.ts index 005e43c1..6fbc2a85 100644 --- a/libs/client-shared/src/config/index.ts +++ b/libs/client-shared/src/config/index.ts @@ -1,4 +1,4 @@ export { default as apolloClient } from './apollo-client' export { default as httpService } from './axios' export { LocalStorage } from './local-storage' -export { RoutePaths } from './paths' +export { PrivatePaths, RoutePaths } from './paths' diff --git a/libs/client-shared/src/config/local-storage.ts b/libs/client-shared/src/config/local-storage.ts index f15d1b20..74bc9cc4 100644 --- a/libs/client-shared/src/config/local-storage.ts +++ b/libs/client-shared/src/config/local-storage.ts @@ -6,7 +6,8 @@ export const LocalStorage = { EDITOR_TAB_SIZE: 'EDITOR_TAB_SIZE', EDITOR_CUSTOM_BACKGROUND: 'EDITOR_CUSTOM_BACKGROUND', EDITOR_CUSTOM_COLOR: 'EDITOR_CUSTOM_COLOR', - EDITOR_HTML_PREVIEW: 'EDITOR_HTML_PREVIEW' + EDITOR_HTML_PREVIEW: 'EDITOR_HTML_PREVIEW', + AUTH_TOKEN: 'AUTH_TOKEN' } as const export type LocalStorageKeys = keyof typeof LocalStorage diff --git a/libs/client-shared/src/config/paths.ts b/libs/client-shared/src/config/paths.ts index 2e7d13bd..80e7ac8b 100644 --- a/libs/client-shared/src/config/paths.ts +++ b/libs/client-shared/src/config/paths.ts @@ -1,11 +1,15 @@ enum AppRoutes { MAIN = 'MAIN', EDITOR = 'EDITOR', - ABOUT = 'ABOUT' + ABOUT = 'ABOUT', + PROFILE = 'PROFILE' } export const RoutePaths: Record = { [AppRoutes.MAIN]: '/', [AppRoutes.EDITOR]: '/edit', - [AppRoutes.ABOUT]: '/about' + [AppRoutes.ABOUT]: '/about', + [AppRoutes.PROFILE]: '/users' } + +export const PrivatePaths = [RoutePaths.PROFILE] diff --git a/libs/client-shared/src/hooks/index.ts b/libs/client-shared/src/hooks/index.ts index 92308635..3766d244 100644 --- a/libs/client-shared/src/hooks/index.ts +++ b/libs/client-shared/src/hooks/index.ts @@ -1,6 +1,8 @@ export { useAltKeyDown } from './use-alt-key-down' +export { useAsyncEffect } from './use-async-effect' export { useBooleanState } from './use-boolean-state' export { useDebounce } from './use-debounce' +export { useFilteredEffect } from './use-filtered-effect' export { useFullScreen } from './use-full-screen' export { useNotifications } from './use-notifications' export { useOverflow } from './use-overflow' diff --git a/libs/client-shared/src/hooks/use-async-effect.ts b/libs/client-shared/src/hooks/use-async-effect.ts new file mode 100644 index 00000000..9078c731 --- /dev/null +++ b/libs/client-shared/src/hooks/use-async-effect.ts @@ -0,0 +1,10 @@ +import { DependencyList, useEffect } from 'react' + +export const useAsyncEffect = ( + cb: () => Promise, + deps: DependencyList +) => { + useEffect(() => { + cb() + }, deps) +} diff --git a/libs/client-shared/src/hooks/use-filtered-effect.ts b/libs/client-shared/src/hooks/use-filtered-effect.ts new file mode 100644 index 00000000..7a69ac1d --- /dev/null +++ b/libs/client-shared/src/hooks/use-filtered-effect.ts @@ -0,0 +1,19 @@ +import React, { useEffect } from 'react' + +interface UseFilteredEffect { + (callback: React.EffectCallback, deps: React.DependencyList): void +} + +export const useFilteredEffect: UseFilteredEffect = ( + callback: () => void, + deps +) => { + useEffect(() => { + for (const dependency of deps) { + if (!dependency) { + return + } + } + return callback() + }, deps) +} diff --git a/libs/client-shared/src/lib/apollo/apollo-middleware.ts b/libs/client-shared/src/lib/apollo/apollo-middleware.ts new file mode 100644 index 00000000..4498f993 --- /dev/null +++ b/libs/client-shared/src/lib/apollo/apollo-middleware.ts @@ -0,0 +1,48 @@ +import { apolloClient } from '@/config' +import { AnyObject, ApolloOperation, Nullable } from '@/types' + +type WithTypeName

= P & { __typename: string } + +interface ToPayload { + payload: A +} + +type OperationType = 'query' | 'mutate' +export class ApolloMiddleware { + public async operate( + mutation: ApolloOperation, + args?: Args, + type: OperationType = 'query' + ): Promise<[Nullable>, Nullable]> { + const options = { + variables: args + ? { + payload: args + } + : undefined + } + let response + + try { + if (type === 'mutate') { + response = await apolloClient.mutate>({ + mutation: mutation.gql, + ...options + }) + } else { + response = await apolloClient.query>({ + query: mutation.gql, + ...options + }) + } + + return [response?.data?.[mutation.method] as WithTypeName, null] + } catch (error: unknown) { + if (_isDev_) { + console.log(error) + } + + return [null, error as string] + } + } +} diff --git a/libs/client-shared/src/lib/apollo/index.ts b/libs/client-shared/src/lib/apollo/index.ts index d1675506..149cc42a 100644 --- a/libs/client-shared/src/lib/apollo/index.ts +++ b/libs/client-shared/src/lib/apollo/index.ts @@ -1 +1 @@ -export { mutate } from './mutate' +export { ApolloMiddleware } from './apollo-middleware' diff --git a/libs/client-shared/src/lib/apollo/mutate.ts b/libs/client-shared/src/lib/apollo/mutate.ts deleted file mode 100644 index fd302820..00000000 --- a/libs/client-shared/src/lib/apollo/mutate.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { apolloClient } from '@/config' -import { AnyObject, ApolloMutation } from '@/types' - -type WithTypeName

= P & { __typename: string } - -// type Operation = Extract - -export const mutate = async ( - mutation: ApolloMutation, - args: Args -): Promise> => { - const response = await apolloClient.mutate({ - mutation: mutation.gql, - variables: { - payload: args - } - }) - - return response.data?.[mutation.method] as WithTypeName -} diff --git a/libs/client-shared/src/lib/components/Page.tsx b/libs/client-shared/src/lib/components/Page.tsx deleted file mode 100644 index 5eaeffd2..00000000 --- a/libs/client-shared/src/lib/components/Page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { ErrorBoundary } from '@/providers' -import { WithChildren } from '@/types' - -const Page = ({ children }: WithChildren) => { - return {children} -} - -export default Page diff --git a/libs/client-shared/src/lib/components/index.ts b/libs/client-shared/src/lib/components/index.ts index b6485fd6..5cf94978 100644 --- a/libs/client-shared/src/lib/components/index.ts +++ b/libs/client-shared/src/lib/components/index.ts @@ -3,4 +3,4 @@ export { useAnimations } from '../../providers/animation-provider' export { Display } from './Display' -export { default as Page } from './Page' +export { default as Page } from '../../../../../apps/client/src/shared/lib/components/page/page' diff --git a/libs/client-shared/src/lib/helpers/local-storage.ts b/libs/client-shared/src/lib/helpers/local-storage.ts index 0647662c..c2cfae48 100644 --- a/libs/client-shared/src/lib/helpers/local-storage.ts +++ b/libs/client-shared/src/lib/helpers/local-storage.ts @@ -14,7 +14,7 @@ export class LocalStorageClient { return defaultVal } - const value = localStorage.getItem(`${appName}_${key}`) as string + const value = localStorage.getItem(this.withPrefix(key)) as string if (!value) { return defaultVal @@ -29,17 +29,21 @@ export class LocalStorageClient { } if (isString(value)) { - return localStorage.setItem(key, value) + return localStorage.setItem(this.withPrefix(key), value) } - localStorage.setItem(`${appName}_${key}`, JSON.stringify(value)) + localStorage.setItem(this.withPrefix(key), JSON.stringify(value)) } public clear(key?: LocalStorageKeys): void { if (key) { - return localStorage.removeItem(key) + return localStorage.removeItem(this.withPrefix(key)) } localStorage.clear() } + + private withPrefix(key: string) { + return `${appName}__${key}` + } } const isJson = (value: string) => { diff --git a/libs/client-shared/src/types/common/graphql.ts b/libs/client-shared/src/types/common/graphql.ts index 0d1bcf87..1275a9ea 100644 --- a/libs/client-shared/src/types/common/graphql.ts +++ b/libs/client-shared/src/types/common/graphql.ts @@ -1,6 +1,6 @@ import { DocumentNode } from 'graphql/language' -export interface ApolloMutation { +export interface ApolloOperation { gql: DocumentNode method: string } diff --git a/libs/client-shared/src/ui/index.ts b/libs/client-shared/src/ui/index.ts index c1c82a8b..3cc30288 100644 --- a/libs/client-shared/src/ui/index.ts +++ b/libs/client-shared/src/ui/index.ts @@ -1,4 +1,4 @@ -export { default as ColorButton } from './color-button/color-button' +export { default as ColoredButton } from './color-button/color-button' export { Modal, ModalSeparator, ModalTitle } from './modal' export { Popover } from './popover' export { Select } from './select/select' diff --git a/libs/client-shared/vite.config.ts b/libs/client-shared/vite.config.ts index fd5b3523..cd6e08aa 100644 --- a/libs/client-shared/vite.config.ts +++ b/libs/client-shared/vite.config.ts @@ -8,7 +8,6 @@ import react from '@vitejs/plugin-react' export default defineConfig({ cacheDir: '../../node_modules/.vite/client-shared', - plugins: [ dts({ entryRoot: 'src', @@ -20,30 +19,14 @@ export default defineConfig({ root: '../../' }) ], - - // Uncomment this if you are using workers. - // worker: { - // plugins: [ - // viteTsConfigPaths({ - // root: '../../', - // }), - // ], - // }, - - // Configuration for building your library. - // See: https://vitejs.dev/guide/build.html#library-mode build: { lib: { - // Could also be a dictionary or array of multiple entry points. entry: 'src/index.ts', name: 'client-shared', fileName: 'index', - // Change this to the formats you want to support. - // Don't forget to update your package.json as well. formats: ['es', 'cjs'] }, rollupOptions: { - // External packages that should not be bundled into your library. external: [ 'react', 'react-dom', diff --git a/libs/editor/src/entities/header-right-section/ui/header-right-section.tsx b/libs/editor/src/entities/header-right-section/ui/header-right-section.tsx index 2a59ff45..e08870dc 100644 --- a/libs/editor/src/entities/header-right-section/ui/header-right-section.tsx +++ b/libs/editor/src/entities/header-right-section/ui/header-right-section.tsx @@ -3,7 +3,7 @@ import { useTheme } from 'styled-components' import { RightSection } from './header-right-section.styles' -import { ColorButton } from '$/client-shared' +import { ColoredButton } from '$/client-shared' interface HeaderRightSectionProps { runCode: () => void @@ -16,19 +16,19 @@ const HeaderRightSection = observer( const theme = useTheme() return ( - Run Code - - + Sign in - + ) } From c2fc73dad8ecbb0f0a20505a0feb2b84f1d1d02d Mon Sep 17 00:00:00 2001 From: Gearonix Date: Tue, 1 Aug 2023 15:26:09 +0300 Subject: [PATCH 09/10] lint: fixed ci problems --- apps/client/src/app/app.tsx | 2 +- apps/client/src/app/entry.tsx | 2 +- .../ui/sign-in-modal-template.styles.ts | 1 - apps/client/src/pages/about/ui/about.tsx | 2 +- apps/client/src/pages/edit/index.ts | 2 +- .../edit/ui/{EditPage.tsx => edit-page.tsx} | 2 +- apps/client/src/pages/main/ui/main.tsx | 4 +- .../src/pages/not-found/ui/not-found.tsx | 2 +- .../src/pages/profile/ui/profile-page.tsx | 2 +- .../client/src/widgets/sign-in-modal/types.ts | 2 +- .../sign-in-modal/ui/sign-in-modal.tsx | 1 - .../src/common/guards/gql-auth.guard.ts | 1 + apps/server/src/main.ts | 6 +-- libs/client-shared/.eslintrc.json | 2 +- .../client-shared/src/lib/components/index.ts | 1 - libs/editor/src/app/editor.tsx | 44 +++++++++---------- .../use-theme-loader/assert-theme-object.ts | 1 + libs/editor/src/shared/exceptions.ts | 8 ++-- .../lib/get-language-from-name.ts | 1 + .../widgets/settings/ui/settings.styles.ts | 3 +- .../src/widgets/settings/ui/settings.tsx | 2 +- .../src/widgets/tabs/hooks/use-confirm.ts | 6 +-- package.json | 1 + 23 files changed, 49 insertions(+), 49 deletions(-) rename apps/client/src/pages/edit/ui/{EditPage.tsx => edit-page.tsx} (88%) diff --git a/apps/client/src/app/app.tsx b/apps/client/src/app/app.tsx index 939a26c5..112df515 100644 --- a/apps/client/src/app/app.tsx +++ b/apps/client/src/app/app.tsx @@ -6,7 +6,7 @@ import { StoreProvider } from './providers/store' import 'normalize.css' -function App() { +const App = () => { return ( diff --git a/apps/client/src/app/entry.tsx b/apps/client/src/app/entry.tsx index 162866a1..41491a02 100644 --- a/apps/client/src/app/entry.tsx +++ b/apps/client/src/app/entry.tsx @@ -4,7 +4,7 @@ import { registerSW } from 'virtual:pwa-register' import App from './app' const updateSW = registerSW({ - onNeedRefresh() { + onNeedRefresh: () => { if (confirm('New content available. Reload?')) { updateSW(true) } diff --git a/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts b/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts index 1cddc55e..81f86ea4 100644 --- a/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts +++ b/apps/client/src/entities/sign-in-modal-template/ui/sign-in-modal-template.styles.ts @@ -1,7 +1,6 @@ import { Form } from 'antd' import styled from 'styled-components' -import { ColoredButton } from '$/client-shared' import { wh } from '$/styles' export const SignInModalStyles = styled(Form)` diff --git a/apps/client/src/pages/about/ui/about.tsx b/apps/client/src/pages/about/ui/about.tsx index 67f2371f..bc8b1d55 100644 --- a/apps/client/src/pages/about/ui/about.tsx +++ b/apps/client/src/pages/about/ui/about.tsx @@ -1,4 +1,4 @@ -import { Page } from '$/client-shared'; +import { Page } from '@/shared/lib' const About = () => { return about page diff --git a/apps/client/src/pages/edit/index.ts b/apps/client/src/pages/edit/index.ts index 1822a0ee..56d7da3c 100644 --- a/apps/client/src/pages/edit/index.ts +++ b/apps/client/src/pages/edit/index.ts @@ -1 +1 @@ -export { default as EditPage } from './ui/EditPage' +export { default as EditPage } from './ui/edit-page' diff --git a/apps/client/src/pages/edit/ui/EditPage.tsx b/apps/client/src/pages/edit/ui/edit-page.tsx similarity index 88% rename from apps/client/src/pages/edit/ui/EditPage.tsx rename to apps/client/src/pages/edit/ui/edit-page.tsx index acaa3072..f357d911 100644 --- a/apps/client/src/pages/edit/ui/EditPage.tsx +++ b/apps/client/src/pages/edit/ui/edit-page.tsx @@ -1,8 +1,8 @@ import { Suspense } from 'react' +import { Page } from '@/shared/lib' import { SignInModal } from '@/widgets/sign-in-modal' -import { Page } from '$/client-shared' import { Editor } from '$/editor' const EditPage = () => { diff --git a/apps/client/src/pages/main/ui/main.tsx b/apps/client/src/pages/main/ui/main.tsx index 7727498f..39db1b40 100644 --- a/apps/client/src/pages/main/ui/main.tsx +++ b/apps/client/src/pages/main/ui/main.tsx @@ -1,6 +1,8 @@ import { Link } from 'react-router-dom' -import { Page, RoutePaths } from '$/client-shared' +import { Page } from '@/shared/lib' + +import { RoutePaths } from '$/client-shared' const Main = () => { return ( diff --git a/apps/client/src/pages/not-found/ui/not-found.tsx b/apps/client/src/pages/not-found/ui/not-found.tsx index 673ef559..a2767a39 100644 --- a/apps/client/src/pages/not-found/ui/not-found.tsx +++ b/apps/client/src/pages/not-found/ui/not-found.tsx @@ -1,4 +1,4 @@ -import { Page } from '$/client-shared'; +import { Page } from '@/shared/lib' const NotFound = () => { return not found diff --git a/apps/client/src/pages/profile/ui/profile-page.tsx b/apps/client/src/pages/profile/ui/profile-page.tsx index 9e75dba3..39e88844 100644 --- a/apps/client/src/pages/profile/ui/profile-page.tsx +++ b/apps/client/src/pages/profile/ui/profile-page.tsx @@ -1,4 +1,4 @@ -import { Page } from '$/client-shared' +import { Page } from '@/shared/lib' const ProfilePage = () => { return profile diff --git a/apps/client/src/widgets/sign-in-modal/types.ts b/apps/client/src/widgets/sign-in-modal/types.ts index 95a7d8dd..12068da7 100644 --- a/apps/client/src/widgets/sign-in-modal/types.ts +++ b/apps/client/src/widgets/sign-in-modal/types.ts @@ -2,7 +2,7 @@ interface WithUsername { username: string } -export interface SignInForm extends WithUsername{ +export interface SignInForm extends WithUsername { password: string } diff --git a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx index 0767444d..7df2aaa1 100644 --- a/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx +++ b/apps/client/src/widgets/sign-in-modal/ui/sign-in-modal.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react' import { observer } from 'mobx-react-lite' import { useNavigate } from 'react-router-dom' diff --git a/apps/server/src/common/guards/gql-auth.guard.ts b/apps/server/src/common/guards/gql-auth.guard.ts index eca61b8c..e4bb4981 100644 --- a/apps/server/src/common/guards/gql-auth.guard.ts +++ b/apps/server/src/common/guards/gql-auth.guard.ts @@ -5,6 +5,7 @@ import { AuthGuard } from '@nestjs/passport' import { graphqlArg } from '$/config' export class GqlAuthGuard extends AuthGuard('local') { + // eslint-disable-next-line @typescript-eslint/no-useless-constructor constructor() { super() } diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index ee75aff2..88985417 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -1,7 +1,7 @@ -import { corsConfig } from '@/config/cors' -import { createSwaggerDocs } from '@/config/swagger' import { HttpExceptionFilter } from '@/common/exception-filters' import { ValidationPipe } from '@/common/pipes/validation.pipe' +import { corsConfig } from '@/config/cors' +import { createSwaggerDocs } from '@/config/swagger' import { Logger } from '@nestjs/common' import { NestFactory } from '@nestjs/core' import { SwaggerModule } from '@nestjs/swagger' @@ -10,7 +10,7 @@ import { AppModule } from './app.module' import { serverDocsPrefix, serverPort, serverPrefix } from '$/config' -async function bootstrap() { +const bootstrap = async () => { const app = await NestFactory.create(AppModule) app.setGlobalPrefix(serverPrefix) diff --git a/libs/client-shared/.eslintrc.json b/libs/client-shared/.eslintrc.json index 7b6798b2..43591fed 100644 --- a/libs/client-shared/.eslintrc.json +++ b/libs/client-shared/.eslintrc.json @@ -1,4 +1,4 @@ { "extends": ["plugin:@nx/react", "../../.eslintrc.js"], - "ignorePatterns": ["!**/*"] + "ignorePatterns": ["!**/*", "env.d.ts"] } diff --git a/libs/client-shared/src/lib/components/index.ts b/libs/client-shared/src/lib/components/index.ts index 5cf94978..35e99e0d 100644 --- a/libs/client-shared/src/lib/components/index.ts +++ b/libs/client-shared/src/lib/components/index.ts @@ -3,4 +3,3 @@ export { useAnimations } from '../../providers/animation-provider' export { Display } from './Display' -export { default as Page } from '../../../../../apps/client/src/shared/lib/components/page/page' diff --git a/libs/editor/src/app/editor.tsx b/libs/editor/src/app/editor.tsx index 34c1c609..b9e10593 100644 --- a/libs/editor/src/app/editor.tsx +++ b/libs/editor/src/app/editor.tsx @@ -12,7 +12,7 @@ import { ModalsContextProvider } from './providers/modals-provider' import { ThemeLoader } from './providers/theme-loader' import { EditorStyles, EditorWrapper } from './styles/editor.styles' -import { NotificationsProvider, Page, useOverflow } from '$/client-shared' +import { NotificationsProvider, useOverflow } from '$/client-shared' interface EditorProps { SignIn: () => ReactElement @@ -22,28 +22,26 @@ export const Editor = ({ SignIn }: EditorProps) => { useOverflow() return ( - - - - - - -

- -