From 14945a2c5d510652648086b70d9103e870af8609 Mon Sep 17 00:00:00 2001
From: pahaz
dYH$}P+lv#mUZ z_*v4=n3b_TS8-qE|Lpw1$4!?^ZhNqcxblaa@f(~f)v2e>5Q`g1`spUmV=S{d?IPKV z>CVH1YLQqzNeY<(^lWnS$!^gkh!cEgps!>vb$Xe`N*e2;uGPsRTZhmW7gm(>sFQ7= z_(ripA@cdmd0%mfCyOBL&AZ?6RBxYYifKMgsn9!T>(JyM21?%=W z!FxpovI4G04IVle4mW>S643!0-q~Cyyynj`@3yJ1PCC!}7Txn_PEil%)#K6%m~I)~ zuplGAS_qw|PJNMWG5%dtCBlJy^IHP&rw$-<$}xru0rG$)f1lflzW1~ed9h&x?s!&J zOKibT!L3bCbvj?TGUd0a)W&}2I8MZ@2-1@crN0rVN%Zo2N3vjY`d!Y7u10=UZaIh< zVwSYxg2%WyFJmeI-=KZJw~~i8Q*DL~X0LRffc`>az2we$)53f(j^TZ@C^V*;i4Fsg z(R9QejyOopBguWHKxI4hrU+h_oqd5}v6gu?B4I#^T}7V=od_fr;+#nJWj}>W^`i X>wN2eldbyS0`Ok?5o?_LkDb>KZQG4kH;^}s8*wzC( 1ockJmKc-=9cF3xnTjWp?yB7;a;KR_XggUt&@ttsk?-DHSSAq zrHO*R{*t4|7SBS@iQj1!55kj1^UR+Uk+)Pv>c->oP$R-_Fa7FsMO6h@U>r~^>*Bdw zH@}q8ueQ_t9G7mz?lP0lH{%PE9#V!!&;a8LN&T9+Gz{&hYSg+5i4Skl*@7Y~bpjnP zn}3y~$c?awno$HJgUzabZB)3mI-yUH8zMqae4vt=rx>Ub61BM@pT@sN6ab7u< MrW WZHKOhvec z&5JB@`u4AP=9vv$Hp6w^9tm*W6_+z@RkP<>BNsd{$^BibuW!`x8mg=`TMkcW`Sh}r zcv3RsP^6*o7iUYu^&@Ew$s&p^sMG$vG%Zws(q?Ki1-MTW2{^T&OCl3JYtZX{>I@@W zh#=x~E?}D0!d$fl>r~nMoancf94j&j`kLs0gr9jA)zV2bDh)#MjC}FI@frBRRMPtP zCy`=li42j=uE$k^J2^jrV4>!{UwaRHvS3*~TbnKiI>H 1fhweZP;24h`yy(Nn?8cMYVLtTwiajU*KC(TI4oULK%zWrTeU z!HnaKhpT1@qqMvwch}BA=fIA|JJp-JsNS^24h(1;EB^DQL-~sxGbhSW>|e&!W-~8c zt{DxZ2opUK_ ydYiZAG)L0Q*{=QLIMkuP)jZ_aC zn8LUg5oev>V!3K_p6lCQZu5DdK_`GxN*DOfj4GxRV})1PLooD+5f2R1CTlk(`S$6j zT#fK94jaGkN OOofd!Xoa%Fd$Hc{1qdI1LyXX0tkhfKE4Z%)aKETB4a@g5v zuHmc6%NDjSB*1_YIG}D2DAl7yB9eybL%=j1=Kwax8h{V~@VQz7Fr4zsekU#uN1LuU zUKVzC_GhaD;-j?^%(u7e-4Ep8g`(oUIY3er3xD`@IV!o{_Muo~si#5I8biQ!54{+6 z6H!gKI|fg%VbXkz{Tgi8I$O^9!EK=m=U||i+S9V{_R W{-#M!evhX zO{r0vR)yg=6gG*Iv9fk?aWN;YYV)YO Jq={@}Uc5~6pCcO7f+IJC`~$RK z3JMKuaDHn5 gT)ZEu%?nulYdqs!Oye|1_ALB=c9a!13- zoZJ`IwL5-fwb2sLpx~*~>B1>jD>iMAg#Wj|2J|-@stOd#24N;Oa-(+#U+UD>&*gLn z`5hQO!A4xD+@Z^XIs!B5S3UcfAS+vrwv_yssIjVz)t^_qHhZzE$K*CAYJBDCi>Fwy z8E02>uIA+89EQI67!E5CXL9)D Fm56iimHc`FjuN9v*Zu>H= N&jz8#1>5eGwxgkwtxW= zJrn8$6=8v0HXlN`YL6_yq+8S9lC#lBB^^zZm#=CmzmCuGDYj8XQ2^ID4sL>Ile2%w z`puMKV=~)@TC aoxDfO>$T1=;h||r@dzE(`-#);m7)P{(^7Vb4vl8 z; PuNd{-Ds6p&gs#)$=9nni|JCo!KxM_Tmqx0SC4 zGv$fXP}-o(2}~)issMZ`#T9g#89H=sVu+`6ZJB6&komwyJopixN3A(9z2Zn@U z{BQtoUwgc{R%tWia^H05m>V>S)n}e@NTB0Kjo`-}`aa+A7Qm3; KBN zTazx;=BCnlltBVQAK>>krhTmfNF&6^gZ~uCQ>J&Uh&D~}M5~Xnb7K1OVYVW0m8ww^ zQ33E#QYHR2 $5mc^wDd(tzW z#!@o_aNBtbDI *pYH-vSw2t|Kkx8LPR*dP| z fmxoUHo!YQMHlg9Ij+w 5&-(e+@%osS3C0%Xjo7RBr$t`&AoWQ}}4nEoka_X)D$b$v6_!TAL`a z!rLV@n&mQYxeDkpFyc~pY`;@CbYwi8ox=1aJ8+N*l<=d2K>o^*MRSC6B}5SJCoOx2 zf~dg;dBvhso{eI3Z_J(%wJ@WBh0&hxk(lhcKab~w7LmdJ4G}g3xE6~QM%nxx5^tM9 zAhEFSyeySG8APDhcwaoIRdn_G`gqA&)c^GNj&&JD=lvIgBr+Ac_D1rS`D}8lX>oiw zZYGhb2YZ K#*sMOu9|0Jm^TF1Z;b4)81YcRjn!ebNlf`au@ z#J5 K*5Fj)XGH^J(HlSDwXEpCbODc*(VFc(Da#%E!}mQ(;26qN(; zx$haOelpj_ds9WojkxQ8j1sn ?Y0 zJd0q#ITilSa57Qzg#U<6UH@85Tfc)-*!A)7cA^34; %ZA82bb*i~Lg(Z>;f1_$K<84+ wY58z-}WHG;GGvWVICQ0ucm?L1U>wWdJ7BRDF zJnhTn=fUYh^LlfBSc?=& fi;47S0>ajVq3jYw&PKmScqO+B^Yk=BA$_{@5Mi0!X7Las(n`u*ESg(8R7nj zN}TBqgfNP>Zr9l^jqLuQeOJmC@n~Gudh-nD_=!|O{^?!+jJkYSrHQ>vQD2VuNH` zkQkN8uqZuXSTIxkk&ZvFOO=j@$4>rBu5??H#*%iO>Gb}hbs0}o4Ka@ bf%jQ%)H=5M#W9D|!qX800o|7zz<(iG3y(S&{6nRyfh(RQG%%VyDa5E|D0e_I z4Ii=jxykwmH0#yd)DE{bFVPxvt ~f;uC5+;84DE8lZqLmC5_2mbbaCcoA+>{L_k-%bGw4injbt>+}o zRvv{olt0h5)>t@(;_eA~UBBwt;D$KH(Z+nk0pv?<3q$ZbD?jDUDW iOHl%DyE@3=GqKuRWT-3NqbE5t(dG zat`9D`34Qg&<17SK|+erSGarMZ~>QE@}OJ4&fXouIg1Ph4g+ug1)tJ!s;ohMk7E?4 z%lWe$3M!$2Z0RWE hh>#zcc|@fICf( zXpyHjN=Q7OT)q@c@myVh^|e5_PHnsx&S7nfZEVnK^{u6pXtG-o*m?FksTCA4 ZW^nxQOu;r=gAkN1$J-+uP{*8j00$p2^ zVdOQr`F0vReum=an|I$=bKJjwInJ&7zWj)>*dy$%*@m1aU;*_(#cWzxT&sVDVGzb# z?yp773+Y?_TTo!|fdY04Wbl(AZpJ4L>GaTbD>zn$DGSf)T)D(#4 *l(I4q5ht&n1n)`ZI`Ko8SCL Z{F13RE(AP@YTukLpsLo3gyLgV2ubLKn@&M}J{ c09y#zLyf9?L#U_3-$!3I<7)Z4YDF-t*h@e+PG)hFcHO=k6*irT{5 ziSRP_AnRrX=;>BpVsc0}Qs1%Va`5u;X8=xA;njU#1dSPZCM2n#p$R3cLLFu~%Je`* z4;VFYXrvroT+l5q9lq5*OQ^MBB>%Q<8-`i`DwuhnhgwSY*G*(gTYej>2nM$6lhhs| zs!KIg#EASf%>PBXkan=oh_%*{JoWwdK1xbOZAoAwIUhs4^rt LF> z4P_(tHkb50ltQjYnw!tlQtwRizE$fReI=YhFYLk*H^K$V9zV%t3|;G28t39f7rJ!2 zDB?Th_$HYEz4He%MOjsNy&w2WTk$Xe-9rb95A<{7UmHc!ly|iyDltTV#q{1v#}Hd; z*DI!L$?6*`Y5>$k;_URFpFFRQ8<;t#XytFG7rnT^eFX eQ~V bbCAH}A^_uJ3<#YHd3tXh@KX8yx;iuq)VgX>;2-xw^ dUC75 zCagC>6cMa#S~hS6p+Q?8 ^^3UC#v&DHtfDviw+pUWYUY*?&EQ}i@_BxHb@{rrhDW#O^n}=4tgcNnC zi}Drr4`yI+=%9nj))1k!gevO;s?F>mWu6pPHJQB|PGKE9ZI^3EJpATjwX|DGQfs3I zQe)f9PXyXO6(vMV#4TCW0a4l+dCL2TsB7{r-O#zuPY=luH!msli`7vfdVnTB2r2Tz zBOM{GivFzCCx^c1G}o05ef}?~18%YQqIvC!utOCZ!dH|fn?pm^){Zlg=wvalgs_f` zEFJ-n<*_sV`N!`<>z`;)Uw+nF+bDnI#GK^Q=2uek-5cMa{Hf3u;m{)E!LI~&I-ebr zs@9N5ZNT0_o37YVrH+jf5+6()NtLSNUNB_Ef_ {M^O->p;(6z!z6x|%fS$ +;rF *0~{`DGo26Ca@CvV~nSu1?aCM z9NRqS5DHfU5K+<2TYo;nV2RvKOR;Qv)y3ig3#wCXskorPc+5Oz7x!^yI19C8G?7Po z>+y3ixpmPZ-ZX{7#RzY5XAmq70jV=P3_3VCZVVPuNo2K<$c_N2i*3G|O{tY^F!{D2 z9t?28( M9_fTc9|*3u^B(2+3UmkAyv*bbY|qm98~7_*@r+V!^yi>d^ifj`ZOUrlVKSBB z><@i#g@(UTWRk!a#kY?Q4`!mVOPu3j=$Bbo!!8oE6^qq`gF(Sr7U9f#mzjPYytkMu z_vs>W;Hw7~`H#JO3omGiXn#C_qMSTKI7W;X12Q20KZ`TqG7*g#_g;P3wx mBRSS)BZCgUU+0#JRLo@01?V*oJi;Ln3KHHGg8Mz`> z${uv4^I07`MS+ucNK#dxSOHAOYl@6UiDnj`UF~qt&01p3?~4yqp&8Yv%DYNd&Gxgt zb7gvh$F)azZpujFR J8aUcqdEsYw{UqwT+r5q}+}<>7PF@TEz817JM<56oU+R zBt=$8=a(J|K4=(rXL`C!8}YJ_Qg;)&^OG%1&X2ceJseuAD$8|y(-=z*bC{t$ORNZK zlSR$hlRM+f9U?N2KrRk$eyK9c-?Yv{hC`K=Zl}?F1l2sOu97AmWQ8!D)5gEmULv*? zIonP*QXBYIApgOaZZv6ity=l&oe|mK-7>xA;n&Rz5eMrLG%gPA!R2LLrze*=T3fVM zS;C&7@)lzvBHG{8W9^w{Sqk!Fq{7INZ$<7d)tuHlKu*fwKXdJOQNp~&^U71z7D}8x z_sG=-?HJmO^tyGQ-VscgYS&s>mNniJa`#JwYO;uj*0H+|qeO%@Sx(WW%()!$(hH1y zPML#bRwWmg6MmyUVte 6@u&?frWdbh9SYqJK&cimKO@w0Gnt86kU@?nJ%P_qVR;buX*ME53qm2L(c9uW1_9M831dALlMl=9F%b zSt-?LOLZ)0{kUgr<{K#5g(H%koSYCBB~v8zE#XRD@>j0*R#%wxTjsqVRxp>|bA3|& zmv)4%I*bq#Rn%@dm6w9i9ae~{O_SaYcT=K<$v0= o*ffie5fVVamv32-1i) z_x#IQV!P@BYyAybBEk7tAJ#BMGLr&L&TQoVJao0$P(ngPLpbaW n56zY?4IlA4F;7u+ zyk_4)5^Iv;-+U}gC9=_)KPTF}8KYI;u%0WMt3H@-skNEtn*Ns1UCm=N|0(2J&u#S^ zHJ3)YK9zXws4FxyI7`?_qip8vWBX%BV~GmY!($K}nieP;NV@!;c |Eu=j2ZliJ6wL~( =@Vg_! xtj qw^kF|H#EtV!u;0yP zwX3Olw5K~A;4b=Az0`k#)aQ@URIvqGu`ouf#d#cqh8iJ{a{;tNRxh99pQ{}KOMkxD zER(+I6pM7PQSyHCvGgAu1jz0@v?y2oEZxV5!DEn|4jQ0W2L%S1t@QqS-&Q0ZT?%Wy z(kX0pVOb=@&xv}PjSTkk1kY+JOWBmBinI)ks|t>+VeHx)vi9Z}D+qhJvVYbS9@)(K zI_Y2pRzHVCjw-&&g=D|`Iz#*IGEc_lI~C(M*qI#5AJjX$Zl3W)h)C~(kU2!#Yw@V` zJT0M?)X3sXQEXRReiwaN$rU&!Dq@KuUTpIT RNN07C?Btd$gS6Fj%>F6-t8We_ zidDmKEIS8P;P99Wr}=2j$>Cz7TvzfyH5~GuJv%uXpHgJDEg`B|A~6>d7jqKXCPAlA zb{cswK5grM@8|0U-Ki!Q>jx5@ |0xpw?a!F(o{w4`V%@^M3Ftg}pL;|L< zg+&*FaVP2$OPl;jaPU$GpO+ih^BVuYK<;Ix>%tr8&*t_(8H|N|Ic3g;iByatn`pZM zze6UsoI7tI@~={(FT|>PS$F&rd}XRSlEsHqMug^wwXo~{RDo|mL~Y+{r1XL(c b|3+?%EXE8$))T%SQ>WGUqw?t>_2wXoFkoWP!Niperm^l6r8i4@3Ng<|0tsn zLQqj|
fq_u{HJeMa@$7vIYTyj+4lB2B$ z7zkC`eLtSo!jr@)=;^ha*uS*DJeH0i;ZX#C`g5sW&>7q=D;;L0Kkl#~Kb1_nUl!Cj z;5MYe%v{!K9bWEM2+fi=3h{!2L>D+Jr43>Yb$2zbrvw&4|I!e2T~Hfa_u-j7+B)H?;hhXt+Mt z%<8Z2) YD=Zd z!$~ih%C$Y7XG|ONYNL4~3CFCS4lg{e{?@}2nvBfl^l1p?m^2|3@s<&uw&d=y67ZVc zoUGW$&5Ku`Sx#ME-}){h_!y`-l7ocUMx3yPF*x_-d*hGCxmkKxFnPbGMLb%tw(!D} zxT8dZQ^AV(=JPsdVEfquLiD=FDhTG}t+3zr%v`C5)N=vvqQv=9pwd9Q=q<7@O*w;B z4KKx|qm$G2M@@?-`J{}C9ac7r90p`U=Ug240VTA#`hIu-PKSj5v%@(g19!%=Xs%Qz zV{HqM3*VDYHP+#F!z@qr^LTuyDp6oF9`okKdxQDG-n^b-;a`TIs;X0M`3`qjH9rvn zRIu_KU1}& Kze7fAiQr|@`m@x85T{&3Hx;v#WqpLaiUKts3_6s`ni zpfb;JDdO)8fSXKAr}YbJ)m4vFZIoIT#24Fa!#=zyZ$LcyKvA3r0?QNpt_APT$U@y| zLHwO&nl-K3%KA|FEYEY-dR1TLcWLF6xBAoPJ`XqVb4$#^Fwb%XOJTx{wBQ5YvVNgU zahzOg?7@F(Ov<=1YIa%~{7_s *`uhQo2<55Y~kPM2<4yfP2voYO~=5zQDYrS zD6IhZw+4NKO8p&YDb?jXsg6LQEWhY5@&Bhe!8knN!a5XtlI>I7>=n$PoT#*5Nb9 z%(!;9ViqoN`N=(?vb?aLL>8H-HJ?KK5kP*ky|bhF^=?^>)W~xS&6JQyt7-Zky5eco z_eSp!=uY`c#UfKy51+o} ;#J zgIL#<%G>h1OY6G5Az5{m+fEX(9J@O^GX5lN_R)Srvq3n{j)6YpQ^*^$(yM2NTdcNP z{W00jH 5jwL2wdRDiFo-KtaNX;`ym?Go-zGtAE3oEJabkCf=-&?Hr zc}b|ov4STJ!bV{?`zuQvOi^vcp|^YQNZ1KaCm`9gq>X98pr{@US0P1(-dM{X_gJNL zp)87qmD$vVlJuGQM6dIU#FO?+@q8-UU(3Err8~!NtF^-&r?|TvUk14Atpu68>3?Gk zvngiNtoNPLzRl#jM5nfxIppyEvh|~7&83fI=eKB^Y3mcbCRe%-BvJV?@W**Vik++{ zN;NwMIdPDr7N1|)n$--Wz?4dOe{{L!&1s5&Z4*uopZ^)sIP9_n%MRjYEY;s)2DK45 zGH+Adergj%C0{~_J>!QJdeOzH;ji@Vr??(S}@N|TDr(@W@ zQ*HaxQu}ZE=R(!Jg!Zg14&o4jfnB-n;jV17d9JQDLRdu1xyx8wWdmWBsM1iv!|k6c zy~vf2-IN*BN|^F6BMfBu*nkZ+243Difv-vHoh2gpnQy~fjr$)%zg}RBK#c?rkaD3* zU-q*3AffQGFXJ3xSjyj`H{eZvEG$(RiQ4L2WVcKWfhQY;GHS1uZ)}BDP8>~} V9f)TuufJ6H{%&Jcqbc!%@A;OLdbJZ-8XAM|^lR)C>Gp|bJG)H3^?*e=I!OK0 zZ?>m2o ouY8OM2e z^6yXm>Lc%8u^ZNTQ6!vA;U3Ba9&h#qTLF3o)#oNoYnhIG;ZH&w9Ut*>)lJ zNv#OL3djZ|wNF!_Q$e?2u}Q`6M{Z)!V}ZYdEAFZNR;-dqH|$?QSd`&_kY6u?f0su# z22;dX6$A{WYz09|acYZehx|4kKq`=XV)6@m#p1C;qjo*mQI2PYo2^>RK_FEPY|u<& zf*^;ZuJrAbS41IYsQ k;AjQLVZyWAv2;Am!d z ^b4CCuE6*aWF)^oS-l=-LhFdUW@)7;Z)d$HoRaFg8Uy*7EAa^2CCFLSOGvA vMQN%FYd5yH`LJn~)!g1*oYn_BeMI zBrfl+QN?b 1OLE9+ygh}~~**Z0BcEZ;x7YAebuhSa9}iaGwt zZSBaogpA@Ze;`2gUIxgCdQ8)R%2bEpeU!$Q-MiR>AoQ>!JN&Tw9+y>0U^`+a!3PH& zgIUqu{~)ZAhyn3~wxS6B-M_b+G*SGC5bYlOHtnu=Um=jnRHre!#=efeiYWd_IWW38 z-HEbqZ~wjm^TMIQoN+_e)PTbtp4=`Du*^WgrWrOM3lH^g*c&kX_l Q_>Mt5f(u}Y!yT6i(~>>D`zI8h23#Cp{DuYyzx2_E_$(c2 zh@`i>_wENRN>_NV*xmR5pyh6t#>|&P(-_q8+mkIa_;DHNfS?KC$-rOl60IlvvmtDw&S;r`FhR%BfmrV^s$I;T|;sQdt5`bt2cJ7Yszd1Je&ahcuRChS$ z$$*hD9$`IQ_w<+a)l-t-Zp &3u>J2aeW-wO8z )?D=+u!ZHc&>g$E~-HL z7Uy&RktSc`n1Vq9R+%W2^}_PwNOzn6p(eB8h060q8FnBDaeh2ga04Z->7VyL(u^Lj zH$O)hAf&-Etb{xC4miyOQ*_98`mO)t%Us6ruUCFxZP<0=e}jkBAv{cChM9@-n4-$; zHcIw7q8wBm%4@8@Z<7|bWeG5VKgtq@HK_B9%(vdARrvq_rhWY{fPZ5FdFtr6%xxJz zD-Ai{c-Rdl0qd=pU=n9Bc;v1BG^P$OKQ+VlQlz@_;Y;Mu2wd#nG#CPDtlQejedoE` zf8bgS=ye=-b7$J*jCyl*sCAg=9F(sU7Kk$gX&0}k(@c2q8|R&m#*YZ^d N>;7SO;g^(`HQ4>YFC%xFl1fV<%}kNzB-!&8SUPYLl5xtva1* zOk*4ytqY6d0s?Lr1w~L66$M;SaRVL*DEkhIvWx7yh@kxazvH`a?t9+*xM&dGH2=)c zz4zR6&i|d|p7Wpo|9R0~bA~v!`e98-eyJ(pl-nYQDdAIw>;9_G8Zv4y|C9Ug))YCm z+bx-BJI}r$dUW~X)&n-E90_#@J3@75Qm(xYutdb6+ia%{JM-#PKg{XQ%vq;+NkB z4YgU2J!bIo8SPGEz8d;y$QvWZI=WA@U0$GfkJ=aCvGhV+$g$W3muy!2 Ht$vcoG($?TXxIme)7-Bv*&hxHY@s9Z@u#Vf)9TAy3NMT zJEt5AZ~U8A=cLkKSB3C@HBSv%hAHg*vC|i8*LnSB^4s>YVSeEevt~Jdp7@79b4bdh z*fDnY{~1yFO|VOF#>|NqO4gkAv>W!xYj#ULcj)(g>i_QGQ<+0UJyws~@n`BzA67BUaA(edABBO)5fJe_$y^CITOeaw&nS&%vM*-+cqaM`KM z` 2MPfTFb%WR4t_tDIfi+l60nU`Y6GVaXVwo? z;4)mmINZDqoQCnS7V?ziXV6oaA6N@`16C}rg813nim*#iy#bydMlhF2iB@z4W?;uY z@z{!ROHhFs*zKhRE4%_b_UnG5uC6Xc*$*E+q|VMx#doqqV23`VNDn(YsG_2RP}kkN zcd5O-UDVmy(n2jQ&D7S`M)#SKe(zo--M@cdyhr|;ni^5(g9i_&s_GuKwzl%MO4QZC z^@3Zto|xz;s;{r7#)bx}ud5^ENBIVRPKduo-Q5;d7fBaW#19$Q;^OG)mCF B7#!^mpHbq9BC*ZtUP(T?O>5M& M57&D7tfpfWZP;#1Hwv zi7}PNF()TmjIH!6)&2Of`V#i&(Ib&fDx)+bq<6?DznAJ)xmv`pQf43}4=DVYD?a=8 zQAu&J);=0A_?w%W$kAavo#Zt^ZB12Z)PTT`b$-_lPda|=r~*WbNxj04H9j>ZS*)X( z85z{n*r-@J_64vd!v26Y6l>D=UVCVVhr6ZhYVS1R#~$T($d}w)w^G27!(v_bJ9Ln? zZP`rc&qa{=+7o% +d6e+SUyGxip4yJ6m|vGJMjGq0aML6Hy1H61 z=FrWQ2znRh1avWpl<<4)*~NN6tdzy#H9akjJlx&1?880chah~jp(jr~B@=z`=X;Qj z9St^lXO16p2W`~4FV)mkle6Q79@j7ystG^zAgn(@fdN7&|8K?5r%>;pt9C4*Lsz zSKa_~{9 (>gs&m^PD1NIKb4eP|2LnVuptQkLaWAr!ledzdD;}7{BU>OxsUB;_9eyk
4qJ_g$YbU%;nZbGlX_~NYHOG5v{2GlEhY&ehis*p &JSa2JxHhw^AgJCSJGJZqe217gK@iYJ6@sV`CzPdujbH~q^ zx8uj>AA3Nl2M}kbWB!P_w{d1XPscIOVP4Ao^>Z@nKwaY)ibdi)I_87SriKGf_yGuY zqHdiM2u^>GxrDi|2{PW3W8mlU%w^0kiXdyegn{pUUzA{(9AC)i=U*@@AKYb```{Kb zA^T5#P`B19A$ytD%+H$7+>a!y2eR>fn@}y=b1N#!g$=T;tyTEoVUL8L4QKr3=4Ps{ zt`fcz*b=MQ$AI@p2dA_BwXCd^ia9TAGI&>5c*{hF;XztkjPG#nhA%!jDTxvj6DXeJ z8Zy$;L>xmpU*WHSj}LJZh<||16#lv!?5{)q8`rOkc!5-ooj_jH0~;CYR5;T=B_BXw zXNUa*cE*bGauG)&0ff&V3>!b{dvf0?uE-BYn!L~PtU`+NRr2;a8L=wpD+| m^Wbf4&?NJ>zDVj^bT_r_9ov0KB|5IDKB4D<--pW%r-ZDObqGS zzW`qj(j>CEYHu`VDTwsA>*(v9m+*=nV-uw#aWo|GQV zuZr^Vy+FPkcXf5OXp7V*a`}jtDl1doKH<}XFALwL0x8NzKKLJDZ$``!d;}O<#EVHJ zmmkFTUUOfEln+;oCDug+Qk9RnjaV<(PvMI|Y?XW+k;}(Ctu@58DahdV`#tsj81kvg zM|>Az)Fguc8h9j<%ZFVXab-#bpBZ8il)hEYuPQ%;V@Bk@aYoN3*jKeP`|gF%zYX%ovl!(Cv~T1?mht>yz|e2!FUSWmCLMFXoIjr+c=DGq z^vn5Mj^Y319(IyZ%sOT*b`l<&iOjHRtnPu4XChZ?Bz}f%u{UR3 V}o^djW1VLcJ?;%0lUu{`oZmjOnc z8*x4bX6)ISnHl09XQsOqCh<4M^3Wqu4rf2;_K2^$%`y8p7vt<95%yvU7pED^<9#vb zg-)5CjyzrBd{a=6ZxX)%48UX{V|mC$d_D97#M3u5HHtU^#5kiJAhaDZP{?Z_lo#WN zXXwf(3;bvg&icrU_)7E*WShlOpsdOKk+ls~8c8J`WuI&Q8oq&N{(hv7ka6Jq{TH*; Or)pQ^!Se8f(Ek8$g - - diff --git a/apps/_demo/schema/Auth.js b/apps/_demo/schema/Auth.js deleted file mode 100644 index ba4499c6..00000000 --- a/apps/_demo/schema/Auth.js +++ /dev/null @@ -1,76 +0,0 @@ -const { getById, getSchemaCtx } = require('@core/keystone/schema') -const { GQLCustomSchema } = require('@core/keystone/schema') - -const { admin } = require('../utils/firebase.back.utils') - -const AuthenticateUserWithFirebaseIdTokenService = new GQLCustomSchema('AuthenticateUserWithFirebaseIdTokenService', { - types: [ - { - access: true, - type: 'input AuthenticateUserWithFirebaseIdTokenInput { firebaseIdToken: String! }', - }, - { - access: true, - type: 'type AuthenticateUserWithFirebaseIdTokenOutput { item: User }', - }, - ], - mutations: [ - { - access: true, - schema: 'authenticateUserWithFirebaseIdToken(data: AuthenticateUserWithFirebaseIdTokenInput!): AuthenticateUserWithFirebaseIdTokenOutput', - resolver: async (parent, args, context, info, extra = {}) => { - await AuthenticateUserWithFirebaseIdTokenService.emit('beforeAuthenticateUserWithFirebaseIdToken', { - parent, args, context, info, extra, - }) - - const { data } = args - const { firebaseIdToken } = data - if (!firebaseIdToken) throw new Error('[error] no firebaseIdToken') - - const { uid, phone_number } = await admin.auth().verifyIdToken(firebaseIdToken) - - const { errors: findErrors, data: findData } = await context.executeGraphQL({ - context: context.createContext({ skipAccessControl: true }), - query: ` - query findUserByImportId($uid: String!) { - objs: allUsers(where: { importId: $uid }) { - id - importId - email - phone - } - } - `, - variables: { uid }, - }) - - if (findErrors || !findData.objs || findData.objs.length !== 1 || !findData.objs[0].id) { - const msg = '[notfound.error] Unable to find user. Try to register' - console.error(msg, findErrors) - throw new Error(msg) - } - - const userData = findData.objs[0] - - if (userData.phone !== phone_number) { - // TODO(pahaz): need to replace obj.phone by Firebase.phone_number - } - - const { keystone } = await getSchemaCtx(AuthenticateUserWithFirebaseIdTokenService) - await context.startAuthedSession({ item: userData, list: keystone.lists['User'] }) - - const result = { - item: await getById('User', userData.id), - } - await AuthenticateUserWithFirebaseIdTokenService.emit('afterAuthenticateUserWithFirebaseIdToken', { - parent, args, context, info, extra, result, - }) - return result - }, - }, - ], -}) - -module.exports = { - AuthenticateUserWithFirebaseIdTokenService, -} diff --git a/apps/_demo/schema/Organization.js b/apps/_demo/schema/Organization.js deleted file mode 100644 index 89a657d8..00000000 --- a/apps/_demo/schema/Organization.js +++ /dev/null @@ -1,71 +0,0 @@ -const { Wysiwyg } = require('@keystonejs/fields-wysiwyg-tinymce') -const { LocalFileAdapter } = require('@keystonejs/file-adapters') -const { Text, File } = require('@keystonejs/fields') -const { Organization: BaseOrganization, OrganizationToUserLink: BaseOrganizationToUserLink, RegisterNewOrganizationService: BaseRegisterNewOrganizationService, InviteNewUserToOrganizationService: BaseInviteNewUserToOrganizationService, AcceptOrRejectOrganizationInviteService } = require('@core/keystone/schemas/Organization') -const conf = require('@core/config') -const faker = require('faker') - -const AVATAR_FILE_ADAPTER = new LocalFileAdapter({ - src: `${conf.MEDIA_ROOT}/orgavatars`, - path: `${conf.MEDIA_URL}/orgavatars`, -}) - -const Organization = BaseOrganization._override({ - fields: { - // settings: { type: MultiCheck, options: ['Feature1', 'Feature2'] }, - avatar: { type: File, adapter: AVATAR_FILE_ADAPTER }, - description: { - factory: () => faker.lorem.paragraph(), - type: Wysiwyg, - }, - }, -}) - -const OrganizationToUserLink = BaseOrganizationToUserLink._override({ - fields: { - phone: { - factory: () => faker.phone.phoneNumberFormat(), - type: Text, - hooks: { - resolveInput: async ({ resolvedData }) => { - return resolvedData['phone'] && resolvedData['phone'].toLowerCase().replace(/\D/g, '') - }, - }, - }, - }, -}) - -const RegisterNewOrganizationService = BaseRegisterNewOrganizationService._override({ - types: [ - { - access: true, - type: 'input RegisterNewOrganizationInput { name: String!, description: String!, avatar: Upload }', - }, - ], -}) - -RegisterNewOrganizationService.on('beforeRegisterNewOrganization', async ({ parent, args, context, info, extra }) => { - extra.extraLinkData = { phone: context.authedItem.phone } -}) - -const InviteNewUserToOrganizationService = BaseInviteNewUserToOrganizationService._override({ - types: [ - { - access: true, - type: 'input InviteNewUserToOrganizationInput { organization: OrganizationWhereUniqueInput!, name: String, email: String!, phone: String }', - }, - ], -}) - -InviteNewUserToOrganizationService.on('afterInviteNewUserToOrganization', ({ parent, args, context, info, extra, result }) => { - // TODO(pahaz): show invite link! and create invite page (for not registered email) - console.log('Fake send security email!', JSON.stringify(result)) -}) - -module.exports = { - Organization, - OrganizationToUserLink, - RegisterNewOrganizationService, - InviteNewUserToOrganizationService, - AcceptOrRejectOrganizationInviteService, -} diff --git a/apps/_demo/schema/Organization.test.js b/apps/_demo/schema/Organization.test.js deleted file mode 100644 index d7510f74..00000000 --- a/apps/_demo/schema/Organization.test.js +++ /dev/null @@ -1,660 +0,0 @@ -/** - * @jest-environment node - */ - -const { createSchemaObject, setFakeClientMode } = require('@core/keystone/test.utils') -const { makeLoggedInClient, makeLoggedInAdminClient, makeClient, createUser, gql } = require('@core/keystone/test.utils') -const conf = require('@core/config') -const faker = require('faker') -if (conf.TESTS_FAKE_CLIENT_MODE) setFakeClientMode(require.resolve('../index')) - -const { Organization, OrganizationToUserLink } = require('../schema/Organization') - -const ALL_ORGANIZATIONS_QUERY = gql` - query { - objs: allOrganizations { - id - name - } - } -` - -const COUNT_OF_ORGANIZATIONS_QUERY = gql` - query { - meta: _allOrganizationsMeta { - count - } - } -` - -const ALL_ORGANIZATION_TO_USER_LINKS_QUERY = gql` - query { - objs: allOrganizationToUserLinks { - id - user { - id - name - } - organization { - id - name - } - } - } -` - -const COUNT_OF_ORGANIZATION_TO_USER_LINKS_QUERY = gql` - query { - meta: _allOrganizationToUserLinksMeta { - count - } - } -` - -const GET_ORGANIZATION_WITH_LINKS_QUERY = gql` - query q($id: ID!) { - obj: Organization (where: {id: $id}) { - id - userLinks { - organization { - id - } - user { - id - } - role - id - } - } - } -` - -const REGISTER_NEW_ORGANIZATION_MUTATION = gql` - mutation reg($data: RegisterNewOrganizationInput!) { - obj: registerNewOrganization(data: $data) { - id - userLinks { - id - organization { - id - name - description - } - user { - id - } - role - } - } - } -` - -const DELETE_ORGANIZATION_TO_USER_LINK_MUTATION = gql` - mutation delObj($id: ID!) { - obj: deleteOrganizationToUserLink(id: $id) { - id - role - } - } -` - -const UPDATE_ORGANIZATION_TO_USER_LINK_MUTATION = gql` - mutation update($id: ID!, $data: OrganizationToUserLinkUpdateInput){ - obj: updateOrganizationToUserLink(id: $id, data: $data) { - id - organization { - id - } - user { - id - } - role - } - } -` - -const GET_ORGANIZATION_TO_USER_LINK_CODE_BY_ID_QUERY = gql` - query getCode($id: ID!){ - obj: OrganizationToUserLink(where: {id: $id}) { - id - code - } - } -` - -test('anonymous: get all Organizations', async () => { - const client = await makeClient() - const { data, errors } = await client.query(ALL_ORGANIZATIONS_QUERY) - expect(errors[0]).toMatchObject({ - 'data': { 'target': 'allOrganizations', 'type': 'query' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['objs'], - }) - expect(data).toEqual({ 'objs': null }) -}) - -test('anonymous: get count of Organizations', async () => { - const client = await makeClient() - const { data, errors } = await client.query(COUNT_OF_ORGANIZATIONS_QUERY) - expect(errors[0]).toMatchObject({ - 'data': { 'target': '_allOrganizationsMeta', 'type': 'query' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['meta', 'count'], - }) - expect(data).toEqual({ meta: { count: null } }) -}) - -test('user: get all Organizations', async () => { - await createSchemaObject(Organization) - const user = await createUser() - const client = await makeLoggedInClient(user) - const { data, errors } = await client.query(ALL_ORGANIZATIONS_QUERY) - expect(errors).toEqual(undefined) - expect(data.objs.length).toBeGreaterThan(0) -}) - -test('user: get count of Organizations', async () => { - await createSchemaObject(Organization) - const user = await createUser() - const client = await makeLoggedInClient(user) - const { data } = await client.query(COUNT_OF_ORGANIZATIONS_QUERY) - expect(data.meta.count).toBeGreaterThan(0) -}) - -test('anonymous: get all OrganizationToUserLinks', async () => { - const client = await makeClient() - const { data, errors } = await client.query(ALL_ORGANIZATION_TO_USER_LINKS_QUERY) - expect(errors[0]).toMatchObject({ - 'data': { 'target': 'allOrganizationToUserLinks', 'type': 'query' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['objs'], - }) - expect(data).toEqual({ 'objs': null }) -}) - -test('anonymous: get count of OrganizationToUserLinks', async () => { - const client = await makeClient() - const { data, errors } = await client.query(COUNT_OF_ORGANIZATION_TO_USER_LINKS_QUERY) - expect(errors[0]).toMatchObject({ - 'data': { 'target': '_allOrganizationToUserLinksMeta', 'type': 'query' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['meta', 'count'], - }) - expect(data).toEqual({ meta: { count: null } }) -}) - -test('user: get all OrganizationToUserLinks', async () => { - await createSchemaObject(OrganizationToUserLink) - const user = await createUser() - const client = await makeLoggedInClient(user) - const { data, errors } = await client.query(ALL_ORGANIZATION_TO_USER_LINKS_QUERY) - expect(errors).toEqual(undefined) - expect(data.objs).toHaveLength(0) -}) - -test('user: get count of OrganizationToUserLinks', async () => { - await createSchemaObject(OrganizationToUserLink) - const user = await createUser() - const client = await makeLoggedInClient(user) - const { data } = await client.query(COUNT_OF_ORGANIZATION_TO_USER_LINKS_QUERY) - expect(data.meta.count).toEqual(0) -}) - -test('user: hide OrganizationToUserLink for everyone who not in userLinks', async () => { - await createSchemaObject(OrganizationToUserLink) - const { id, _raw_query_data } = await createSchemaObject(OrganizationToUserLink) - const organization = _raw_query_data.organization.create - const user = _raw_query_data.user.create - expect(user.email).toMatch(/^.+$/g) - expect(user.password).toMatch(/^.+$/g) - expect(organization.name).toMatch(/^.+$/g) - - // check by member - const client_member = await makeLoggedInClient(user) - const { data: data1, errors: errors1 } = await client_member.query(ALL_ORGANIZATION_TO_USER_LINKS_QUERY) - expect(errors1).toEqual(undefined) - expect(data1.objs).toEqual([expect.objectContaining({ - id, - user: expect.objectContaining({ id: client_member.user.id }), - organization: expect.objectContaining({ name: organization.name }), - })]) - - const { data: data2, errors: errors2 } = await client_member.query(COUNT_OF_ORGANIZATION_TO_USER_LINKS_QUERY) - expect(errors2).toEqual(undefined) - expect(data2.meta.count).toEqual(1) -}) - -test('user: access to change OrganizationToUserLink only for owners', async () => { - const user1 = await createUser() - const user2 = await createUser() - const user3 = await createUser() - const { id: organizationId } = await createSchemaObject(Organization) - const { id: link1Id } = await createSchemaObject(OrganizationToUserLink, { - organization: { connect: { id: organizationId } }, - user: { connect: { id: user1.id } }, - role: 'owner', - }) - const { id: link2Id } = await createSchemaObject(OrganizationToUserLink, { - organization: { connect: { id: organizationId } }, - user: { connect: { id: user2.id } }, - role: 'member', - }) - const { id: link3Id } = await createSchemaObject(OrganizationToUserLink, { - organization: { connect: { id: organizationId } }, - user: { connect: { id: user3.id } }, - role: 'member', - }) - const client_owner = await makeLoggedInClient(user1) - - // check DB state - const { data: data0, errors: errors0 } = await client_owner.query(GET_ORGANIZATION_WITH_LINKS_QUERY, { id: organizationId }) - expect(errors0).toEqual(undefined) - expect(data0.obj).toEqual({ - 'id': organizationId, - 'userLinks': [ - { - 'id': link1Id, - 'organization': { - 'id': organizationId, - }, - 'role': 'owner', - 'user': { - 'id': user1.id, - }, - }, - { - 'id': link2Id, - 'organization': { - 'id': organizationId, - }, - 'role': 'member', - 'user': { - 'id': user2.id, - }, - }, - { - 'id': link3Id, - 'organization': { - 'id': organizationId, - }, - 'role': 'member', - 'user': { - 'id': user3.id, - }, - }, - ], - }) - - // delete user 3 - const { data: data2, errors: errors2 } = await client_owner.mutate(DELETE_ORGANIZATION_TO_USER_LINK_MUTATION, { id: link3Id }) - expect(errors2).toEqual(undefined) - expect(data2.obj).toEqual(expect.objectContaining({ id: link3Id })) - - const client_member = await makeLoggedInClient(user2) - const { data: data3, errors: errors3 } = await client_member.query(GET_ORGANIZATION_WITH_LINKS_QUERY, { id: organizationId }) - expect(errors3).toEqual(undefined) - expect(data3.obj).toEqual({ - 'id': organizationId, - 'userLinks': [ - { - 'id': link1Id, - 'organization': { - 'id': organizationId, - }, - 'role': 'owner', - 'user': { - 'id': user1.id, - }, - }, - { - 'id': link2Id, - 'organization': { - 'id': organizationId, - }, - 'role': 'member', - 'user': { - 'id': user2.id, - }, - }, - ], - }) - - // try to delete user 1 - const { data: data4, errors: errors4 } = await client_member.mutate(DELETE_ORGANIZATION_TO_USER_LINK_MUTATION, { id: link1Id }) - expect(errors4[0]).toMatchObject({ - 'data': { 'target': 'deleteOrganizationToUserLink', 'type': 'mutation' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['obj'], - }) - expect(data4).toEqual({ 'obj': null }) -}) - -test('registerNewOrganization() by user', async () => { - const user = await createUser() - const client = await makeLoggedInClient(user) - const name = faker.company.companyName() - const description = faker.lorem.paragraph() - const { data, errors } = await client.mutate(REGISTER_NEW_ORGANIZATION_MUTATION, { - data: { name, description }, - }) - - // created company - expect(errors).toEqual(undefined) - expect(data.obj.id).toMatch(/^[0-9a-zA-Z-_]+$/) - expect(data.obj.userLinks).toEqual([ - expect.objectContaining({ - 'organization': expect.objectContaining({ name, description }), - 'user': { 'id': user.id }, - 'role': 'owner', - }), - ]) -}) - -test('no access to change another organization', async () => { - const { id: organizationId } = await createSchemaObject(Organization) - const { id: linkId } = await createSchemaObject(OrganizationToUserLink, { - organization: { connect: { id: organizationId } }, - role: 'owner', - }) - - const user = await createUser() - const client = await makeLoggedInClient(user) - const { errors } = await client.mutate(REGISTER_NEW_ORGANIZATION_MUTATION, { - data: { name: faker.company.companyName(), description: faker.lorem.paragraph() }, - }) - expect(errors).toEqual(undefined) - - const { errors: err1 } = await client.mutate(UPDATE_ORGANIZATION_TO_USER_LINK_MUTATION, { - id: linkId, - data: { user: { connect: { id: user.id } } }, - }) - expect(err1[0]).toMatchObject({ - 'data': { 'target': 'updateOrganizationToUserLink', 'type': 'mutation' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['obj'], - }) - const { errors: err2 } = await client.mutate(UPDATE_ORGANIZATION_TO_USER_LINK_MUTATION, { - id: linkId, - data: { role: 'member' }, - }) - expect(err2[0]).toMatchObject({ - 'data': { 'target': 'updateOrganizationToUserLink', 'type': 'mutation' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['obj'], - }) -}) - -const INVITE_NEW_USER_MUTATION = gql` - mutation inviteNewUser($data: InviteNewUserToOrganizationInput!) { - obj: inviteNewUserToOrganization(data: $data) { - id - role - user { - id - } - } - } -` - -async function inviteNewUser (client, organizationId, email) { - const { data, errors } = await client.mutate(INVITE_NEW_USER_MUTATION, { - data: { - organization: { id: organizationId }, - email: email, - name: 'user2', - }, - }) - expect(errors).toEqual(undefined) - expect(data.obj.id).toMatch(/^[0-9a-zA-Z-_]+$/) - expect(data.obj.role).toEqual('member') - return { data } -} - -test('owner: invite new user', async () => { - const user = await createUser() - const user2 = await createUser() - const client = await makeLoggedInClient(user) - const { data, errors } = await client.mutate(REGISTER_NEW_ORGANIZATION_MUTATION, { - data: { name: faker.company.companyName(), description: faker.lorem.paragraph() }, - }) - expect(errors).toEqual(undefined) - - // invite - const { data: d2 } = await inviteNewUser(client, data.obj.id, user2.email) - expect(d2.obj.user.id).toEqual(user2.id) -}) - -test('owner: try to invite already invited user', async () => { - const user = await createUser() - const user2 = await createUser() - const client = await makeLoggedInClient(user) - const { data, errors } = await client.mutate(REGISTER_NEW_ORGANIZATION_MUTATION, { - data: { name: faker.company.companyName(), description: faker.lorem.paragraph() }, - }) - expect(errors).toEqual(undefined) - - // invite - await inviteNewUser(client, data.obj.id, user2.email) - { - const { errors } = await client.mutate(INVITE_NEW_USER_MUTATION, { - data: { - organization: { id: data.obj.id }, - email: user2.email, - name: 'user2', - }, - }) - expect(JSON.stringify(errors)).toContain('[error.already.exists]') - } -}) - -test('owner: try to invite already invited email', async () => { - const user = await createUser() - const client = await makeLoggedInClient(user) - const { data, errors } = await client.mutate(REGISTER_NEW_ORGANIZATION_MUTATION, { - data: { name: faker.company.companyName(), description: faker.lorem.paragraph() }, - }) - expect(errors).toEqual(undefined) - - // invite - await inviteNewUser(client, data.obj.id, 'xm2' + user.email) - { - const { errors } = await client.mutate(INVITE_NEW_USER_MUTATION, { - data: { - organization: { id: data.obj.id }, - email: 'xm2' + user.email, - name: 'user2', - }, - }) - expect(JSON.stringify(errors)).toContain('[error.already.exists]') - } -}) - -test('owner: has access to invite/update/delete OrganizationToUserLinks', async () => { - const user = await createUser() - const user2 = await createUser() - const client = await makeLoggedInClient(user) - const { data, errors } = await client.mutate(REGISTER_NEW_ORGANIZATION_MUTATION, { - data: { name: faker.company.companyName(), description: faker.lorem.paragraph() }, - }) - expect(errors).toEqual(undefined) - - const { data: d2 } = await inviteNewUser(client, data.obj.id, user2.email) - - // update - const { data: d3, errors: err3 } = await client.mutate(UPDATE_ORGANIZATION_TO_USER_LINK_MUTATION, { - id: d2.obj.id, - data: { - role: 'owner', - }, - }) - expect(err3).toEqual(undefined) - expect(d3.obj.role).toEqual('owner') - - // delete - const { data: d4, errors: err4 } = await client.mutate(DELETE_ORGANIZATION_TO_USER_LINK_MUTATION, { - id: d2.obj.id, - }) - expect(err4).toEqual(undefined) - expect(d4.obj.id).toEqual(d2.obj.id) -}) - -test('owner: has no access to update OrganizationToUserLinks organization/user attrs', async () => { - const { id: organizationId } = await createSchemaObject(Organization) - const user = await createUser() - const user2 = await createUser() - const client = await makeLoggedInClient(user) - const { data, errors } = await client.mutate(REGISTER_NEW_ORGANIZATION_MUTATION, { - data: { name: faker.company.companyName(), description: faker.lorem.paragraph() }, - }) - expect(errors).toEqual(undefined) - - const { data: d2 } = await inviteNewUser(client, data.obj.id, user2.email) - - // update organization - const { errors: err3 } = await client.mutate(UPDATE_ORGANIZATION_TO_USER_LINK_MUTATION, { - id: d2.obj.id, - data: { - organization: { connect: { id: organizationId } }, - }, - }) - expect(err3[0]).toMatchObject({ - 'data': { 'target': 'updateOrganizationToUserLink', 'type': 'mutation' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['obj'], - }) - // update user - const { errors: err4 } = await client.mutate(UPDATE_ORGANIZATION_TO_USER_LINK_MUTATION, { - id: d2.obj.id, - data: { - user: { connect: { id: user2.id } }, - }, - }) - expect(err4[0]).toMatchObject({ - 'data': { 'target': 'updateOrganizationToUserLink', 'type': 'mutation' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['obj'], - }) -}) - -const ACCEPT_OR_REJECT_BY_ID_MUTATION = gql` - mutation acceptOrReject($id: ID!, $data: AcceptOrRejectOrganizationInviteInput!){ - obj: acceptOrRejectOrganizationInviteById(id: $id, data: $data) { - id isAccepted isRejected - } - } -` - -const ACCEPT_OR_REJECT_BY_CODE_MUTATION = gql` - mutation acceptOrReject($code: String!, $data: AcceptOrRejectOrganizationInviteInput!){ - obj: acceptOrRejectOrganizationInviteByCode(code: $code, data: $data) { - id isAccepted isRejected - } - } -` - -// TODO(pahaz): check antonymous ACCEPT_OR_REJECT_BY_ID_MUTATION - -test('user: accept/reject OrganizationToUserLinks by ID', async () => { - const user = await createUser() - const user2 = await createUser() - const client = await makeLoggedInClient(user) - const { data, errors } = await client.mutate(REGISTER_NEW_ORGANIZATION_MUTATION, { - data: { name: faker.company.companyName(), description: faker.lorem.paragraph() }, - }) - expect(errors).toEqual(undefined) - - // create - const { data: d2 } = await inviteNewUser(client, data.obj.id, user2.email) - - // accept - const member_client = await makeLoggedInClient(user2) - const { data: d3, errors: err3 } = await member_client.mutate(ACCEPT_OR_REJECT_BY_ID_MUTATION, { - id: d2.obj.id, - data: { - isAccepted: true, - }, - }) - expect(err3).toEqual(undefined) - expect(d3.obj).toEqual({ - id: d2.obj.id, - isAccepted: true, - isRejected: false, - }) - - // reject - const { data: d4, errors: err4 } = await member_client.mutate(ACCEPT_OR_REJECT_BY_ID_MUTATION, { - id: d2.obj.id, - data: { - isRejected: true, - }, - }) - expect(err4).toEqual(undefined) - expect(d4.obj).toEqual({ - id: d2.obj.id, - isAccepted: false, - isRejected: true, - }) -}) - -async function getInviteCode (id) { - const admin = await makeLoggedInAdminClient() - const { data, errors } = await admin.query(GET_ORGANIZATION_TO_USER_LINK_CODE_BY_ID_QUERY, { id }) - expect(errors).toEqual(undefined) - expect(data.obj.id).toEqual(id) - console.log(data) - return data.obj.code -} - -test('user: accept/reject OrganizationToUserLinks by CODE', async () => { - const user = await createUser() - const client = await makeLoggedInClient(user) - const { data, errors } = await client.mutate(REGISTER_NEW_ORGANIZATION_MUTATION, { - data: { name: faker.company.companyName(), description: faker.lorem.paragraph() }, - }) - expect(errors).toEqual(undefined) - - // create - const { data: d2 } = await inviteNewUser(client, data.obj.id, 'x2' + user.email) - expect(d2.obj.user).toBeNull() - - const code = await getInviteCode(d2.obj.id) - - // accept - const user2 = await createUser() - const member_client = await makeLoggedInClient(user2) - const { data: d3, errors: err3 } = await member_client.mutate(ACCEPT_OR_REJECT_BY_CODE_MUTATION, { - code, - data: { - isAccepted: true, - }, - }) - expect(err3).toEqual(undefined) - expect(d3.obj).toEqual({ - id: d2.obj.id, - isAccepted: true, - isRejected: false, - }) - - // second time! - const { errors: err4 } = await member_client.mutate(ACCEPT_OR_REJECT_BY_CODE_MUTATION, { - code, - data: { - isAccepted: true, - }, - }) - expect(err4[0]).toMatchObject({ - 'data': { 'target': 'acceptOrRejectOrganizationInviteByCode', 'type': 'mutation' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['obj'], - }) -}) diff --git a/apps/_demo/schema/Test.js b/apps/_demo/schema/Test.js deleted file mode 100644 index fb0922ea..00000000 --- a/apps/_demo/schema/Test.js +++ /dev/null @@ -1,183 +0,0 @@ -const { historical, versioned, uuided, tracked, softDeleted } = require('@core/keystone/plugins') -const { - Checkbox, - Decimal, - Float, - Integer, - Select, - Slug, - Url, - Uuid, - Text, - Relationship, - DateTimeUtc, - DateTime, - CalendarDay, - File, - Password, - Virtual, -} = require('@keystonejs/fields') -const { Content } = require('@keystonejs/fields-content') -const { GQLListSchema } = require('@core/keystone/schema') -const { Color } = require('@keystonejs/fields-color') -const { LocationGoogle } = require('@keystonejs/fields-location-google') -const { v4: uuid } = require('uuid') -const { Stars, Options, Json, AutoIncrementInteger } = require('@core/keystone/fields') -const { LocalFileAdapter } = require('@keystonejs/file-adapters') -const conf = require('@core/config') - -const adapter = new LocalFileAdapter({ - src: `${conf.MEDIA_ROOT}/test`, - path: `${conf.MEDIA_URL}/test`, -}) - -const Test = new GQLListSchema('Test', { - fields: { - date1: { type: DateTime }, - utc: { type: DateTimeUtc }, - day: { type: CalendarDay }, - file: { type: File, adapter }, - pass: { type: Password }, - vir1: { - type: Virtual, - access: { create: false, update: false, delete: false, read: true }, - resolver: (item) => item.day ? '!' : 'no date!', - }, - - heroColor: { type: Color }, - - isEnabled: { type: Checkbox, isRequired: true, defaultValue: false }, - - body: { - type: Content, - blocks: [ - Content.blocks.blockquote, - Content.blocks.image, - Content.blocks.link, - Content.blocks.orderedList, - Content.blocks.unorderedList, - Content.blocks.heading, - ], - }, - - rating1: { type: Integer, isRequired: true, defaultValue: 3 }, - rating2: { type: Float }, - rating3: { type: Decimal }, - venue: { - type: LocationGoogle, - googleMapsKey: 'no', - }, - - status_renamed: { type: Select, options: 'pending, processed, no', isRequired: true, defaultValue: 'no' }, - uuid: { - type: Uuid, - isRequired: true, - defaultValue: () => uuid(), - }, - url: { - type: Url, - isRequired: true, - defaultValue: 'scheme:[//authority]path[?query][#fragment]', - }, - slug: { - type: Slug, - isRequired: true, - from: 'id', - }, - users: { - type: Relationship, - ref: 'User', - many: true, - }, - self: { - type: Relationship, - ref: 'Test', - many: false, - }, - related: { - type: Relationship, - ref: 'Test', - many: true, - }, - - rating: { type: Stars, starCount: 5 }, - settings: { type: Options, options: ['Feature1', 'Feature2'] }, - meta: { type: Json }, - text: { type: Text }, - item: { - type: Relationship, - ref: 'TestItem', - }, - }, - access: { - read: true, - create: true, - update: true, - delete: true, - auth: true, - }, - plugins: [versioned(), tracked(), historical()], -}) - -const TestItem = new GQLListSchema('TestItem', { - fields: { - test: { - type: Relationship, - ref: 'Test', - isRequired: true, - }, - meta: { - type: Json, - defaultValue: {}, - isRequired: true, - }, - }, - access: { - read: true, - create: true, - update: true, - delete: true, - auth: true, - }, - plugins: [uuided(), versioned(), tracked(), historical()], -}) - -const TestSoftDeletedObj = new GQLListSchema('TestSoftDeletedObj', { - fields: { - meta: { - type: Json, - }, - }, - access: { - read: true, - create: true, - update: true, - delete: true, - auth: true, - }, - plugins: [versioned(), tracked(), softDeleted(), historical()] -}) - -const TestAutoIncrementNumber = new GQLListSchema('TestAutoIncrementNumber', { - fields: { - number: { - type: AutoIncrementInteger, - isRequired: false, - kmigratorOptions: { unique: true, null: false }, - }, - }, - access: { - read: true, - create: true, - update: true, - delete: true, - auth: true, - }, -}) - -module.exports = { - Test, - TestItem, - TestSoftDeletedObj, - TestAutoIncrementNumber, -} diff --git a/apps/_demo/schema/Test.test.js b/apps/_demo/schema/Test.test.js deleted file mode 100644 index 499323cb..00000000 --- a/apps/_demo/schema/Test.test.js +++ /dev/null @@ -1,420 +0,0 @@ -/** - * @jest-environment node - */ - -const conf = require('@core/config') -const { setFakeClientMode } = require('@core/keystone/test.utils') -if (conf.TESTS_FAKE_CLIENT_MODE) setFakeClientMode(require.resolve('../index')) - -const faker = require('faker') -const { isMongo } = require('@core/keystone/test.utils') -const { makeClient, makeLoggedInClient, makeLoggedInAdminClient } = require('@core/keystone/test.utils') -const { genTestGQLUtils } = require('@core/keystone/gen.gql.utils') -const { UUID_RE, DATETIME_RE } = require('@core/keystone/test.utils') - -const TEST_FIELDS = '{ id v text meta createdAt updatedAt createdBy { id } updatedBy { id } }' -const TEST_ITEM_FIELDS = `{ id v meta test ${TEST_FIELDS} }` -const Test = genTestGQLUtils('Test', TEST_FIELDS) -const TestItem = genTestGQLUtils('TestItem', TEST_ITEM_FIELDS) - -const TEST_HISTORY_FIELDS = '{ id v text history_id history_action history_date }' -const TEST_ITEM_HISTORY_FIELDS = '{ id v meta test history_id history_action history_date }' -const TestHistoryRecord = genTestGQLUtils('TestHistoryRecord', TEST_HISTORY_FIELDS) -const TestItemHistoryRecord = genTestGQLUtils('TestItemHistoryRecord', TEST_ITEM_HISTORY_FIELDS) - -const TEST_SOFT_DELETED_FIELDS = '{ id v meta deletedAt }' -const TestSoftDeletedObj = genTestGQLUtils('TestSoftDeletedObj', TEST_SOFT_DELETED_FIELDS) - -const TestAutoIncrementNumber = genTestGQLUtils('TestAutoIncrementNumber', '{ id number }') - -describe('Json field', () => { - async function testJsonValue (value) { - const client = await makeClient() - let obj = await Test.create(client, { meta: value }) - expect(obj.meta).toStrictEqual(value) - } - - test('object as value', async () => { - await testJsonValue({ foo: 'foo', bar: 2, buz: false, no: null, yes: true }) - }) - test('object with array as value', async () => { - if (isMongo()) return console.error('SKIP() Mongo: {} === null!') - - await testJsonValue({ - foo: ['foo', 1, 33.3], - bar: 2, - buz: false, - no: [null, false], - yes: true, - e1: [], - e2: {}, - }) - }) - test('{} as value', async () => { - if (isMongo()) return console.error('SKIP() Mongo: {} === null!') - await testJsonValue({}) - }) - test('null as value', async () => { - await testJsonValue(null) - }) - test('true as value', async () => { - await testJsonValue(true) - }) - test('false as value', async () => { - await testJsonValue(false) - }) - test('"" as value', async () => { - await testJsonValue('') - }) - test('[] as value', async () => { - await testJsonValue([]) - }) - test('0 as value', async () => { - await testJsonValue(0) - }) - test('{"":[{}]} as value', async () => { - await testJsonValue({ '': [{}] }) - await testJsonValue({ '': [{ '': [{}] }] }) - }) - test('number as value', async () => { - await testJsonValue(faker.random.number()) - await testJsonValue(faker.random.float()) - }) - test('string as value', async () => { - await testJsonValue(faker.internet.email()) - await testJsonValue(JSON.stringify(JSON.stringify(faker.internet.email()))) - await testJsonValue('\'') - await testJsonValue('"') - await testJsonValue('--') - await testJsonValue('%') - await testJsonValue('~') - await testJsonValue('~~') - }) - test('array as value', async () => { - await testJsonValue(faker.random.arrayElements()) - }) -}) - -describe('Json field exact match filter', () => { - async function testFilterByValue (value, metaSuffix = '') { - // NOTE: the test may fail by access denied to createdBy { id } field! - const client = await makeLoggedInClient() - const obj = await Test.create(client, { meta: value }) - const objs = await Test.getAll(client, { ['meta' + metaSuffix]: value }) - const objsIds = objs.map(x => x.id) - const objsMetas = [...(new Set(objs.map(x => JSON.stringify(x.meta))))].map(JSON.parse) - expect(objsIds).toContain(obj.id) - expect(objsMetas).toStrictEqual([value]) - } - - test('object as value', async () => { - if (isMongo()) return console.error('SKIP() Mongo: need a custom query parser!') - - await testFilterByValue({ foo: 'foo', bar: 2, buz: false, no: null, yes: true }) - // await testFilterByValue({ foo: 'foo', bar: 2, buz: false, no: null, yes: true }, '_in') // ok - }) - test('object with array as value', async () => { - if (isMongo()) return console.error('SKIP() Mongo: need a custom query parser!') - - await testFilterByValue({ - foo: ['foo', 1, 33.3], - bar: 2, - buz: false, - no: [null, false], - yes: true, - e1: [], - e2: {}, - }) - // await testFilterByValue({ - // foo: ['foo', 1, 33.3], - // bar: 2, - // buz: false, - // no: [null, false], - // yes: true, - // e1: [], - // e2: {}, - // }, - // '_in', - // ) // ok - }) - test('{} as value', async () => { - if (isMongo()) return console.error('SKIP() Mongo: {} === null!') - - await testFilterByValue({}) - // await testFilterByValue({}, '_in') // ok - }) - test('null as value', async () => { - await testFilterByValue(null) - // await testFilterByValue(null, '_in') // err - }) - test('true as value', async () => { - await testFilterByValue(true) - // await testFilterByValue(true, '_in') // err - }) - test('false as value', async () => { - await testFilterByValue(false) - // await testFilterByValue(false, '_in') // ok - }) - test('by ""', async () => { - await testFilterByValue('') - // await testFilterByValue('', '_in') // ok - }) - test('by []', async () => { - await testFilterByValue([]) - // await testFilterByValue([], '_in') // err - }) - test('by 0', async () => { - await testFilterByValue(0) - }) - test('by {"":[{}]}', async () => { - if (isMongo()) return console.error('SKIP() Mongo: {} === null!') - - await testFilterByValue({ '': [{}] }) - await testFilterByValue({ '': [{ '': [{}] }] }) - }) - test('by number', async () => { - await testFilterByValue(faker.random.number()) - await testFilterByValue(faker.random.float()) - }) - test('by string', async () => { - await testFilterByValue(faker.internet.email()) - await testFilterByValue(JSON.stringify(JSON.stringify(faker.internet.email()))) - await testFilterByValue('\'') - await testFilterByValue('"') - await testFilterByValue('--') - await testFilterByValue('%') - await testFilterByValue('~') - await testFilterByValue('~~') - }) - test('by array', async () => { - await testFilterByValue(faker.random.arrayElements()) - }) -}) - -describe('historical()', () => { - test('create/update/delete history', async () => { - const client = await makeClient() - let obj = await Test.create(client, {}) - await Test.update(client, obj.id, { text: 'hello' }) - await Test.update(client, obj.id, { text: 'no' }) - await Test.delete(client, obj.id) - - let histObjs = await TestHistoryRecord.getAll(client, { history_id: obj.id }) - expect(histObjs).toEqual([ - expect.objectContaining({ history_action: 'c', history_id: obj.id, text: null, v: 1 }), - expect.objectContaining({ history_action: 'u', history_id: obj.id, text: 'hello', v: 2 }), - expect.objectContaining({ history_action: 'u', history_id: obj.id, text: 'no', v: 3 }), - expect.objectContaining({ history_action: 'd', history_id: obj.id, text: 'no', v: 3 }), - ]) - }) - - test('delete related object and set FK null without history update', async () => { - if (isMongo()) return console.error('SKIP() Mongo: doesn\'t support UUID fk!') - const client = await makeClient() - let obj = await TestItem.create(client, { test: { create: { text: 'new1' } }, meta: { foo: 1 } }) - await TestItem.update(client, obj.id, { meta: { foo: 2 } }) - await Test.update(client, obj.test.id, { text: 'new2' }) - await Test.delete(client, obj.test.id) - await TestItem.delete(client, obj.id) - - let histObjs = await TestHistoryRecord.getAll(client, { history_id: obj.test.id }) - expect(histObjs).toEqual([ - expect.objectContaining({ history_action: 'c', history_id: obj.test.id, text: 'new1', v: 1 }), - expect.objectContaining({ history_action: 'u', history_id: obj.test.id, text: 'new2', v: 2 }), - expect.objectContaining({ history_action: 'd', history_id: obj.test.id, text: 'new2', v: 2 }), - ]) - - histObjs = await TestItemHistoryRecord.getAll(client, { history_id: obj.id }) - expect(histObjs).toEqual([ - expect.objectContaining({ - history_action: 'c', - history_id: obj.id, - meta: { foo: 1 }, - v: 1, - test: String(obj.test.id), - }), - expect.objectContaining({ - history_action: 'u', - history_id: obj.id, - meta: { foo: 2 }, - v: 2, - test: String(obj.test.id), - }), - expect.objectContaining({ - history_action: 'd', - history_id: obj.id, - meta: { foo: 2 }, - v: 2, - test: null, - }), - ]) - }) -}) - -describe('versioned()', () => { - test('check v field autoincrement', async () => { - const client = await makeClient() - - let obj = await Test.create(client, {}) - expect(obj.v).toBe(1) - - obj = await Test.update(client, obj.id, { text: 'hello' }) - expect(obj.v).toBe(2) - }) - test('try to set v', async () => { - const admin = await makeLoggedInAdminClient() - const { data, errors } = await Test.create(admin, { v: 5 }, { raw: true }) - expect(data).toEqual(undefined) - expect(JSON.stringify(errors)).toMatch(/Field [\\"']+v[\\"']+ is not defined by type [\\"']+TestCreateInput[\\"']+/i) - }) -}) - -describe('uuided()', () => { - test('chek id field is uuid', async () => { - const client = await makeClient() - let obj = await TestItem.create(client, { test: { create: { text: 'autoGen' } } }) - expect(obj.id).toMatch(UUID_RE) - }) -}) - -describe('tracked()', () => { - test('check createAt/updateAt for anonymous', async () => { - const client = await makeClient() - let { id, createdAt, updatedAt } = await Test.create(client, {}) - expect(createdAt).toMatch(DATETIME_RE) - expect(updatedAt).toMatch(DATETIME_RE) - expect(updatedAt).toEqual(createdAt) - let obj = await Test.update(client, id, { text: 'new' }) - expect(obj.createdAt).toEqual(createdAt) - expect(obj.updatedAt).toMatch(DATETIME_RE) - expect(obj.updatedAt).not.toEqual(createdAt) - }) - test('check createBy/updateBy for anonymous', async () => { - const client = await makeClient() - let { id, createdBy, updatedBy } = await Test.create(client, {}) - expect(createdBy).toBe(null) - expect(updatedBy).toBe(null) - let obj = await Test.update(client, id, { text: 'new' }) - expect(obj.createdBy).toBe(null) - expect(obj.updatedBy).toBe(null) - }) - test('check createBy/updateBy for user1/user2', async () => { - const client = await makeLoggedInClient() - const admin = await makeLoggedInAdminClient() - let { id, createdBy, updatedBy } = await Test.create(client, {}) - expect(createdBy).toEqual({ id: client.user.id }) - expect(updatedBy).toEqual({ id: client.user.id }) - let obj = await Test.update(admin, id, { text: 'new' }) - expect(obj.createdBy).toEqual({ id: client.user.id }) - expect(obj.updatedBy).toEqual({ id: admin.user.id }) - }) - test('try to set createBy', async () => { - const client = await makeLoggedInClient() - const admin = await makeLoggedInAdminClient() - const { data, errors } = await Test.create(client, { createdBy: { connect: { id: admin.user.id } } }, { raw: true }) - expect(data).toEqual(undefined) - expect(JSON.stringify(errors)).toMatch(/Field [\\"']+createdBy[\\"']+ is not defined by type [\\"']+TestCreateInput[\\"']+/i) - }) -}) - -describe('softDeleted()', () => { - test('check deletedAt is auto generated and accept any string', async () => { - const client = await makeClient() - let obj = await TestSoftDeletedObj.create(client) - obj = await TestSoftDeletedObj.update(client, obj.id, { deletedAt: 'true' }) - expect(obj.deletedAt).toMatch(DATETIME_RE) - }) - test('check delete after delete', async () => { - const client = await makeClient() - let obj = await TestSoftDeletedObj.create(client) - obj = await TestSoftDeletedObj.update(client, obj.id, { deletedAt: 'true' }) - const { errors } = await TestSoftDeletedObj.update(client, obj.id, { deletedAt: new Date().toISOString() }, { raw: true }) - expect(errors[0]).toMatchObject({ - 'message': 'Already deleted', - 'name': 'GraphQLError', - }) - }) - test('check update after delete', async () => { - const client = await makeClient() - let obj = await TestSoftDeletedObj.create(client) - obj = await TestSoftDeletedObj.update(client, obj.id, { deletedAt: 'true' }) - const { errors } = await TestSoftDeletedObj.update(client, obj.id, { meta: { foo: 1 } }, { raw: true }) - expect(errors[0]).toMatchObject({ - 'message': 'Already deleted', - 'name': 'GraphQLError', - }) - }) - test('check create deleted obj', async () => { - const client = await makeClient() - const { errors } = await TestSoftDeletedObj.create(client, { deletedAt: 'true' }, { raw: true }) - expect(errors[0]).toMatchObject({ - 'message': 'Variable "$data" got invalid value { deletedAt: "true" }; Field "deletedAt" is not defined by type "TestSoftDeletedObjCreateInput".', - 'name': 'GraphQLError', - }) - }) - test('check disallow to hard delete', async () => { - const client = await makeLoggedInAdminClient() - let obj = await TestSoftDeletedObj.create(client) - const { errors } = await TestSoftDeletedObj.delete(client, obj.id, { raw: true }) - expect(errors[0]).toMatchObject({ - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - }) - }) - test('check filter by default deletedAt = null', async () => { - const client = await makeLoggedInAdminClient() - const rand = faker.random.number() - let obj1 = await TestSoftDeletedObj.create(client, { meta: { rand } }) - let obj2 = await TestSoftDeletedObj.create(client, { meta: { rand } }) - - await TestSoftDeletedObj.update(client, obj1.id, { deletedAt: 'true' }) - - const objs = await TestSoftDeletedObj.getAll(client, { meta_in: [{ rand }] }) - expect(objs.map(x => x.id)).toEqual([obj2.id]) - }) - test('check filter by default newId = null', async () => { - const client = await makeLoggedInAdminClient() - const rand = faker.random.number() - let obj1 = await TestSoftDeletedObj.create(client, { meta: { rand } }) - let obj2 = await TestSoftDeletedObj.create(client, { meta: { rand } }) - - await TestSoftDeletedObj.update(client, obj1.id, { newId: obj2.id }) - - let objs = await TestSoftDeletedObj.getAll(client, { meta_in: [{ rand }] }) - expect(objs.map(x => x.id)).toEqual([obj2.id]) - }) -}) - -describe('TestAutoIncrementNumber field', () => { - if (isMongo()) return console.error('SKIP() Mongo: Need to implement AutoIncrementIntegerMongooseFieldAdapter!') - - test('generate incremental number', async () => { - const client = await makeLoggedInClient() - const obj = await TestAutoIncrementNumber.create(client) - expect(typeof obj.number).toBe('number') - const obj2 = await TestAutoIncrementNumber.create(client) - expect(obj2.number > obj.number).toBe(true) - }) - - test('set number by hands', async () => { - const client = await makeLoggedInClient() - const number = faker.random.number(1000, 10000000) - const obj = await TestAutoIncrementNumber.create(client, { number }) - expect(obj.number).toBe(number) - }) - - test('set existing number', async () => { - const client = await makeLoggedInClient() - const obj = await TestAutoIncrementNumber.create(client) - const { data, errors } = await TestAutoIncrementNumber.create(client, { number: obj.number }, { raw: true }) - expect(data).toStrictEqual({ 'obj': null }) - expect(errors[0]).toMatchObject({ - message: 'You attempted to perform an invalid mutation', - name: 'ValidationFailureError', - data: { - messages: ['[unique:alreadyExists:number] Field number should be unique'], - }, - path: ['obj'], - }) - }) -}) diff --git a/apps/_demo/schema/User.js b/apps/_demo/schema/User.js deleted file mode 100644 index ce61f010..00000000 --- a/apps/_demo/schema/User.js +++ /dev/null @@ -1,124 +0,0 @@ -const { Wysiwyg } = require('@keystonejs/fields-wysiwyg-tinymce') -const { LocalFileAdapter } = require('@keystonejs/file-adapters') -const { Text, Checkbox, File } = require('@keystonejs/fields') -const { User: BaseUser, ForgotPasswordAction, ForgotPasswordService, RegisterNewUserService: BaseRegisterNewUserService } = require('@core/keystone/schemas/User') -const conf = require('@core/config') -const access = require('@core/keystone/access') -const faker = require('faker') -const { admin } = require('../utils/firebase.back.utils') - -const { Stars, Json, Options } = require('@core/keystone/fields') - -const AVATAR_FILE_ADAPTER = new LocalFileAdapter({ - src: `${conf.MEDIA_ROOT}/avatars`, - path: `${conf.MEDIA_URL}/avatars`, -}) - -const User = BaseUser._override({ - fields: { - avatar: { type: File, adapter: AVATAR_FILE_ADAPTER }, - rating: { type: Stars, starCount: 5 }, - settings: { type: Options, options: ['Feature1', 'Feature2'] }, - meta: { type: Json }, - aboutMyself: { type: Wysiwyg }, - phone: { - factory: () => faker.phone.phoneNumberFormat(), - type: Text, - access: { - read: true, - create: access.userIsAdmin, - update: access.userIsAdmin, - }, - hooks: { - resolveInput: async ({ resolvedData }) => { - return resolvedData['phone'] && resolvedData['phone'].toLowerCase().replace(/[^+0-9]/g, '') - }, - }, - // TODO(pahaz): think about mongodb! - kmigratorOptions: { unique: true }, // Just for postgres (bug with mongo) - }, - isPhoneVerified: { - type: Checkbox, - defaultValue: false, - access: { - read: true, - create: access.userIsAdmin, - update: access.userIsAdmin, - }, - }, - importId: { - factory: () => faker.random.uuid(), - type: Text, - access: { - read: true, - create: access.userIsAdmin, - update: access.userIsAdmin, - }, - // TODO(pahaz): think about mongodb! - kmigratorOptions: { unique: true }, // Just for postgres (bug with mongo) - }, - }, -}) - -const RegisterNewUserService = BaseRegisterNewUserService._override({ - types: [ - { - access: true, - type: 'input RegisterNewUserInput { name: String!, email: String!, password: String!, firebaseIdToken: String }', - }, - ], -}) - -async function checkUnique (context, model, models, field, value) { - const { errors, data } = await context.executeGraphQL({ - context: context.createContext({ skipAccessControl: true }), - query: ` - query find($where: ${model}WhereInput!) { - objs: all${models}(where: $where) { - id - } - } - `, - variables: { where: { [field]: value } }, - }) - - if (errors) { - const msg = `[error] Unable to check field ${field} uniques` - console.error(msg, errors) - throw new Error(msg) - } - - if (data.objs.length !== 0) { - throw new Error(`[unique:${field}:multipleFound] ${models} with this ${field} is already exists`) - } -} - -RegisterNewUserService.on('beforeRegisterNewUser', async ({ parent, args, context, info, extra }) => { - const idToken = args.data.firebaseIdToken - if (!idToken) return - - delete args.data.firebaseIdToken - const { uid, phone_number } = await admin.auth().verifyIdToken(idToken) - await checkUnique(context, 'User', 'Users', 'phone', phone_number) - await checkUnique(context, 'User', 'Users', 'importId', uid) - extra.extraUserData = { - phone: phone_number, - isPhoneVerified: true, - importId: uid, - } -}) - -ForgotPasswordService.on('afterStartPasswordRecovery', ({ parent, args, context, info, extra, result }) => { - console.log('Fake send security email!', JSON.stringify(result)) -}) - -ForgotPasswordService.on('afterChangePasswordWithToken', ({ parent, args, context, info, extra, result }) => { - console.log('Fake send security email!', JSON.stringify(result)) -}) - -module.exports = { - User, - ForgotPasswordAction, - ForgotPasswordService, - RegisterNewUserService, -} diff --git a/apps/_demo/schema/User.test.js b/apps/_demo/schema/User.test.js deleted file mode 100644 index 5d87ed8f..00000000 --- a/apps/_demo/schema/User.test.js +++ /dev/null @@ -1,521 +0,0 @@ -/** - * @jest-environment node - */ - -const conf = require('@core/config') -const { setFakeClientMode } = require('@core/keystone/test.utils') -if (conf.TESTS_FAKE_CLIENT_MODE) setFakeClientMode(require.resolve('../index')) - -const faker = require('faker') -const { getRandomString, isMongo, DEFAULT_TEST_USER_IDENTITY, DEFAULT_TEST_USER_SECRET, gql } = require('@core/keystone/test.utils') -const { makeClient, makeLoggedInClient, createUser, getSchemaObject, createSchemaObject, makeLoggedInAdminClient } = require('@core/keystone/test.utils') - -const { User, ForgotPasswordAction } = require('../schema/User') - -describe('MODEL', () => { - const GET_USER_BY_ID_QUERY = gql` - query getUserById($id: ID!) { - user: User(where: {id: $id}) { - id - email - name - } - } - ` - - test('user: convert email to lower case', async () => { - const client = await makeLoggedInAdminClient() - const email = 'XXX' + getRandomString() + '@example.com' - const user = await createUser({ email }) - - const { data } = await client.query(GET_USER_BY_ID_QUERY, { id: user.id }) - expect(data.user).toEqual({ email: email.toLowerCase(), id: user.id, name: user.name }) - - const client2 = await makeLoggedInClient({ email: email.toLowerCase(), password: user.password }) - expect(client2.user.id).toEqual(user.id) - - // TODO(pahaz): fix in a future (it's no OK if you can't logged in by upper case email) - const checkAuthByUpperCaseEmail = async () => { - await makeLoggedInClient({ email, password: user.password }) - } - await expect(checkAuthByUpperCaseEmail).rejects.toThrow(/passwordAuth:identity:notFound/) - }) - - test('createSchemaObject()', async () => { - const { id } = await createSchemaObject(User) - expect(id).toMatch(/^[A-Za-z0-9-]+$/g) - }) -}) - -describe('QUERY ALL/COUNT ACCESS', () => { - const ALL_USERS_QUERY = gql` - query { - users: allUsers { - id - email - name - } - } - ` - - const COUNT_OF_USERS_QUERY = gql` - query { - meta: _allUsersMeta { - count - } - } - ` - - test('anonymous: get all users', async () => { - const client = await makeClient() - const { data, errors } = await client.query(ALL_USERS_QUERY) - expect(data).toEqual({ users: null }) - expect(errors[0]).toMatchObject({ - 'data': { 'target': 'allUsers', 'type': 'query' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['users'], - }) - }) - - test('anonymous: get count of users', async () => { - const client = await makeClient() - const { data, errors } = await client.query(COUNT_OF_USERS_QUERY) - expect(data).toEqual({ meta: { count: null } }) - expect(errors[0]).toMatchObject({ - 'data': { 'target': '_allUsersMeta', 'type': 'query' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['meta', 'count'], - }) - }) - - test('user: get all users', async () => { - const user = await createUser() - const client = await makeLoggedInClient(user) - const { data } = await client.query(ALL_USERS_QUERY) - expect(data.users).toEqual( - expect.arrayContaining([ - expect.objectContaining({ id: user.id, name: user.name, email: user.email }), - expect.objectContaining({ email: null }), - ]), - ) - }) - - test('user: get count of users', async () => { - const user = await createUser() - const client = await makeLoggedInClient(user) - const { data } = await client.query(COUNT_OF_USERS_QUERY) - expect(data.meta.count).toBeGreaterThanOrEqual(2) - }) -}) - -describe('SIGNIN', () => { - const SIGNIN_MUTATION = gql` - mutation sigin($identity: String, $secret: String) { - auth: authenticateUserWithPassword(email: $identity, password: $secret) { - user: item { - id - } - } - } - ` - - const GET_MY_USERINFO = gql` - query getUser { - user: authenticatedUser { - id - } - } - ` - - test('anonymous: try to sign in', async () => { - const client = await makeClient() - const { data, errors } = await client.mutate(SIGNIN_MUTATION, { - 'identity': DEFAULT_TEST_USER_IDENTITY, - 'secret': DEFAULT_TEST_USER_SECRET, - }) - expect(errors).toEqual(undefined) - expect(data.auth.user.id).toMatch(/[a-zA-Z0-9-_]+/) - }) - - test('anonymous: get user info', async () => { - const client = await makeClient() - const { data, errors } = await client.query(GET_MY_USERINFO) - expect(errors).toEqual(undefined) - expect(data).toEqual({ 'user': null }) - }) - - test('get user info after sign in', async () => { - const client = await makeLoggedInClient() - const { data, errors } = await client.query(GET_MY_USERINFO) - expect(errors).toEqual(undefined) - expect(data.user).toEqual({ id: client.user.id }) - }) - - test('anonymous: wrong password', async () => { - const client = await makeClient() - const { data, errors } = await client.mutate(SIGNIN_MUTATION, { - 'identity': DEFAULT_TEST_USER_IDENTITY, - 'secret': 'wrong password', - }) - expect(data).toEqual({ 'auth': null }) - expect(JSON.stringify(errors)).toEqual(expect.stringMatching('passwordAuth:secret:mismatch')) - }) - - test('anonymous: wrong email', async () => { - const client = await makeClient() - const { data, errors } = await client.mutate(SIGNIN_MUTATION, { - 'identity': 'some3571592131usermail@example.com', - 'secret': 'wrong password', - }) - expect(data).toEqual({ 'auth': null }) - expect(JSON.stringify(errors)).toEqual(expect.stringMatching('passwordAuth:identity:notFound')) - }) - - test('check auth by empty password', async () => { - const user = await createUser({ password: '' }) - const checkAuthByEmptyPassword = async () => { - await makeLoggedInClient({ email: user.email, password: '' }) - } - await expect(checkAuthByEmptyPassword).rejects.toThrow(/passwordAuth:secret:notSet/) - }) -}) - -describe('REGISTER', () => { - const REGISTER_NEW_USER_MUTATION = gql` - mutation registerNewUser($data: RegisterNewUserInput!) { - user: registerNewUser(data: $data) { - id - } - } - ` - - test('register new user', async () => { - const client = await makeClient() - const name = faker.fake('{{name.suffix}} {{name.firstName}} {{name.lastName}}') - const password = faker.internet.password() - const email = faker.internet.exampleEmail() - const { data, errors } = await client.mutate(REGISTER_NEW_USER_MUTATION, { data: { name, password, email } }) - expect(errors).toEqual(undefined) - expect(data.user.id).toMatch(/^[0-9a-zA-Z-_]+$/) - }) - - test('register user with existed email', async () => { - const user = await createUser() - const client = await makeClient() - const name = faker.fake('{{name.suffix}} {{name.firstName}} {{name.lastName}}') - const password = faker.internet.password() - const email = user.email - const { errors } = await client.mutate(REGISTER_NEW_USER_MUTATION, { data: { name, password, email } }) - expect(JSON.stringify(errors)).toMatch(/register:email:multipleFound/) - }) -}) - -describe('FORGOT_RECOVERY_CHANGE_PASSWORD', () => { - const ALL_FORGOT_PASSWORD_ACTIONS_QUERY = gql` - query { - objs: allForgotPasswordActions { - id - user { - id - } - token - } - } - ` - - const START_PASSWORD_RECOVERY_MUTATION = gql` - mutation startPasswordRecovery($email: String!){ - status: startPasswordRecovery(email: $email) - } - ` - - const CHANGE_PASSWORD_WITH_TOKEN_MUTATION = gql` - mutation changePasswordWithToken($token: String!, $password: String!) { - status: changePasswordWithToken(token: $token, password: $password) - } - ` - - const ALL_TOKENS_FOR_USER_QUERY = gql` - query findTokenForUser($email: String!) { - passwordTokens: allForgotPasswordActions(where: { user: { email: $email}}) { - id - token - user { - id - } - } - } - ` - - test('anonymous: get all ForgotPasswordActions', async () => { - const client = await makeClient() - const { data, errors } = await client.query(ALL_FORGOT_PASSWORD_ACTIONS_QUERY) - expect(errors[0]).toMatchObject({ - 'data': { 'target': 'allForgotPasswordActions', 'type': 'query' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['objs'], - }) - expect(data).toEqual({ objs: null }) - }) - - test('user: get all ForgotPasswordActions', async () => { - const user = await createUser() - const client = await makeLoggedInClient(user) - const { data, errors } = await client.query(ALL_FORGOT_PASSWORD_ACTIONS_QUERY) - expect(errors[0]).toMatchObject({ - 'data': { 'target': 'allForgotPasswordActions', 'type': 'query' }, - 'message': 'You do not have access to this resource', - 'name': 'AccessDeniedError', - 'path': ['objs'], - }) - expect(data).toEqual({ objs: null }) - }) - - test('reset forgotten password', async () => { - const user = await createUser() - const client = await makeClient() - const adm = await makeLoggedInAdminClient() - const res1 = await client.mutate(START_PASSWORD_RECOVERY_MUTATION, { email: user.email }) - expect(res1.errors).toEqual(undefined) - expect(res1.data).toEqual({ status: 'ok' }) - - // get created token - const { data: { passwordTokens } } = await adm.query(ALL_TOKENS_FOR_USER_QUERY, { email: user.email }) - expect(passwordTokens).toHaveLength(1) - const token = passwordTokens[0].token - expect(token).toMatch(/^[a-zA-Z0-9-]{7,40}$/g) - - // change password - const password = `${user.password}:${user.password}:new` - const res2 = await client.mutate(CHANGE_PASSWORD_WITH_TOKEN_MUTATION, { token, password }) - expect(res2.errors).toEqual(undefined) - expect(res2.data).toEqual({ status: 'ok' }) - - // check logging by new password - const newClient = await makeLoggedInClient({ email: user.email, password }) - expect(newClient.user.id).toEqual(user.id) - }) - - test('start recovery for unknown email', async () => { - const client = await makeClient() - const res1 = await client.mutate(START_PASSWORD_RECOVERY_MUTATION, { email: 'random231314@emample.com' }) - expect(JSON.stringify(res1.errors)).toEqual(expect.stringMatching('unknown-user')) - }) - - test('change password to empty', async () => { - const { id } = await createSchemaObject(ForgotPasswordAction) - const obj = await getSchemaObject(ForgotPasswordAction, ['token'], { id }) - const client = await makeClient() - const res1 = await client.mutate(CHANGE_PASSWORD_WITH_TOKEN_MUTATION, { - token: obj.token, password: '', - }) - expect(res1.data).toEqual({ status: 'ok' }) - }) -}) - -describe('MODEL User.settings field', () => { - const USER_FIELDS = '{ id settings { Feature1 Feature2 } }' - const GET_USER_BY_ID_QUERY = gql` - query getUserById($id: ID!) { - user: User(where: {id: $id}) ${USER_FIELDS} - } - ` - - const UPDATE_USER_BY_ID_MUTATION = gql` - mutation updateUserById($id:ID!, $data: UserUpdateInput!) { - user: updateUser(id: $id, data: $data) ${USER_FIELDS} - } - ` - - const ALL_USERS_QUERY = gql` - query getAll($data: UserWhereInput!) { - users: allUsers(where: $data) ${USER_FIELDS} - } - ` - - test('user: set settings', async () => { - const client = await makeLoggedInAdminClient() - const user = await createUser({ settings: { 'Feature1': true } }) - console.log('user.id', user.id) - - const { data, errors } = await client.query(GET_USER_BY_ID_QUERY, { id: user.id }) - expect(errors).toEqual(undefined) - expect(data.user).toEqual({ - id: user.id, - settings: { - 'Feature1': true, - 'Feature2': null, - }, - }) - }) - - test('user: set unknown settings', async () => { - async function run () { - return await createUser({ settings: { 'Feature22': true, 'Feature1': true } }) - } - - await expect(run).rejects.toThrow(/Variable "\$data" got invalid value/) - }) - - test('user: set settings = null', async () => { - const client = await makeLoggedInAdminClient() - const user = await createUser({ settings: null }) - console.log('user.id', user.id) - - const { data, errors } = await client.query(GET_USER_BY_ID_QUERY, { id: user.id }) - expect(errors).toEqual(undefined) - expect(data.user).toEqual({ - id: user.id, - settings: { - 'Feature1': null, - 'Feature2': null, - }, - }) - }) - - test('user: set settings value = null', async () => { - const client = await makeLoggedInAdminClient() - const user = await createUser({ settings: { 'Feature1': null } }) - console.log('user.id', user.id) - - const { data, errors } = await client.query(GET_USER_BY_ID_QUERY, { id: user.id }) - expect(errors).toEqual(undefined) - expect(data.user).toEqual({ - id: user.id, - settings: { - 'Feature1': null, - 'Feature2': null, - }, - }) - }) - - test('user: set settings = undefined', async () => { - const client = await makeLoggedInAdminClient() - const user = await createUser({ settings: undefined }) - console.log('user.id', user.id) - - const { data, errors } = await client.query(GET_USER_BY_ID_QUERY, { id: user.id }) - expect(errors).toEqual(undefined) - expect(data.user).toEqual({ - id: user.id, - settings: { - 'Feature1': null, - 'Feature2': null, - }, - }) - }) - - test('user: set settings = {}', async () => { - const client = await makeLoggedInAdminClient() - const user = await createUser({ settings: {} }) - console.log('user.id', user.id) - - const { data, errors } = await client.query(GET_USER_BY_ID_QUERY, { id: user.id }) - expect(errors).toEqual(undefined) - expect(data.user).toEqual({ - id: user.id, - settings: { - 'Feature1': null, - 'Feature2': null, - }, - }) - }) - - test('user: merge settings', async () => { - const client = await makeLoggedInAdminClient() - const user = await createUser({ settings: { 'Feature1': false } }) - console.log('user.id', user.id) - - const { data, errors } = await client.mutate(UPDATE_USER_BY_ID_MUTATION, { - id: user.id, - data: { settings: { 'Feature2': true } }, - }) - expect(errors).toEqual(undefined) - expect(data.user).toEqual({ - id: user.id, - settings: { - 'Feature1': false, - 'Feature2': true, - }, - }) - }) - - test('user: filter settings by EQ', async () => { - if (isMongo()) return console.error('SKIP() Mongo: need a custom query parser!') - - const client = await makeLoggedInAdminClient() - const user1 = await createUser({ settings: { 'Feature1': true } }) - const user2 = await createUser({ settings: { 'Feature1': true } }) - const user3 = await createUser({ settings: { 'Feature1': false } }) - { - const { data, errors } = await client.query(ALL_USERS_QUERY, { data: { settings: { 'Feature1': true } } }) - expect(errors).toEqual(undefined) - const ids = data.users.map(x => x.id) - expect(ids).toContain(user1.id) - expect(ids).toContain(user2.id) - expect(ids).not.toContain(user3.id) - } - { - const { data, errors } = await client.query(ALL_USERS_QUERY, { data: { settings: { 'Feature1': false } } }) - expect(errors).toEqual(undefined) - const ids = data.users.map(x => x.id) - expect(ids).not.toContain(user1.id) - expect(ids).not.toContain(user2.id) - expect(ids).toContain(user3.id) - } - }) - - test('user: filter settings by NULL', async () => { - const client = await makeLoggedInAdminClient() - const user1 = await createUser({ settings: null }) - const user2 = await createUser({ settings: undefined }) - const user3 = await createUser({ settings: {} }) - const user4 = await createUser({ settings: { 'Feature1': null } }) - const user5 = await createUser({ settings: { 'Feature1': undefined } }) - const user6 = await createUser({ settings: { 'Feature1': false } }) - const user7 = await createUser({ settings: { 'Feature1': true } }) - { - const { data, errors } = await client.query(ALL_USERS_QUERY, { data: { settings: null } }) - expect(errors).toEqual(undefined) - const ids = data.users.map(x => x.id) - expect(ids).toContain(user1.id) - expect(ids).toContain(user2.id) - expect(ids).toContain(user3.id) - expect(ids).toContain(user4.id) - expect(ids).toContain(user5.id) - expect(ids).not.toContain(user6.id) - expect(ids).not.toContain(user7.id) - } - }) - - test('user: filter settings by IN', async () => { - const client = await makeLoggedInAdminClient() - const user1 = await createUser({ settings: null }) - const user2 = await createUser({ settings: undefined }) - const user3 = await createUser({ settings: {} }) - const user4 = await createUser({ settings: { 'Feature1': null } }) - const user5 = await createUser({ settings: { 'Feature1': undefined } }) - const user6 = await createUser({ settings: { 'Feature1': false } }) - const user7 = await createUser({ settings: { 'Feature1': true } }) - const user8 = await createUser({ settings: { 'Feature1': true, 'Feature2': true } }) - const user9 = await createUser({ settings: { 'Feature1': true, 'Feature2': false } }) - { - const { data, errors } = await client.query(ALL_USERS_QUERY, { data: { settings_in: [null, { 'Feature1': true }] } }) - expect(errors).toEqual(undefined) - const ids = data.users.map(x => x.id) - expect(ids).toContain(user1.id) - expect(ids).toContain(user2.id) - expect(ids).toContain(user3.id) - expect(ids).toContain(user4.id) - expect(ids).toContain(user5.id) - expect(ids).not.toContain(user6.id) - expect(ids).toContain(user7.id) - expect(ids).not.toContain(user8.id) - expect(ids).not.toContain(user9.id) - } - }) -}) - diff --git a/apps/_demo/utils/firebase.back.utils.js b/apps/_demo/utils/firebase.back.utils.js deleted file mode 100644 index 276ad272..00000000 --- a/apps/_demo/utils/firebase.back.utils.js +++ /dev/null @@ -1,16 +0,0 @@ -const admin = require('firebase-admin') -const conf = require('@core/config') - -const FIREBASE_ADMIN_CONFIG = conf['FIREBASE_ADMIN_CONFIG'] && JSON.parse(conf['FIREBASE_ADMIN_CONFIG']) -const FIREBASE_CONFIG = conf['FIREBASE_CONFIG'] && JSON.parse(conf['FIREBASE_CONFIG']) - -if (FIREBASE_ADMIN_CONFIG) { - admin.initializeApp({ - credential: admin.credential.cert(FIREBASE_ADMIN_CONFIG), - databaseURL: FIREBASE_CONFIG.databaseURL, - }) -} - -module.exports = { - admin, -} diff --git a/apps/_demo/utils/firebase.front.utils.js b/apps/_demo/utils/firebase.front.utils.js deleted file mode 100644 index adc2c702..00000000 --- a/apps/_demo/utils/firebase.front.utils.js +++ /dev/null @@ -1,25 +0,0 @@ -import * as firebase from 'firebase' -import getConfig from 'next/config' - -const { - publicRuntimeConfig: { firebaseConfig }, -} = getConfig() - -const FIREBASE_CONFIG = firebaseConfig || {} -try { - if (FIREBASE_CONFIG.apiKey) { - firebase.initializeApp(FIREBASE_CONFIG) - // firebase.analytics() - } -} catch (err) { - // ignore app already initialized error on snack - console.error(err) -} - -const isFirebaseConfigValid = !!FIREBASE_CONFIG.apiKey - -export default firebase -export { - isFirebaseConfigValid, - FIREBASE_CONFIG, -} diff --git a/apps/_ex01back/index.js b/apps/_ex01back/index.js deleted file mode 100644 index a8559700..00000000 --- a/apps/_ex01back/index.js +++ /dev/null @@ -1,53 +0,0 @@ -const { Keystone } = require('@keystonejs/keystone') -const { GraphQLApp } = require('@keystonejs/app-graphql') -const { AdminUIApp } = require('@keystonejs/app-admin-ui') -const { PasswordAuthStrategy } = require('@keystonejs/auth-password') -const { createItems } = require('@keystonejs/server-side-graphql-client') - -const conf = require('@core/config') -const { registerSchemas } = require('@core/keystone/schema') -const { prepareDefaultKeystoneConfig } = require('@core/keystone/setup.utils') - -const keystone = new Keystone({ - ...prepareDefaultKeystoneConfig(conf), - onConnect: async () => { - if (conf.NODE_ENV !== 'development') return // Just for dev purposes! - - // This function can be called before tables are created! (we just ignore this) - try { - const users = await keystone.lists.User.adapter.findAll() - if (!users.length) { - const initialData = require('./initialData') - for (let { listKey, items } of initialData) { - await createItems({ - keystone, - listKey, - items, - }) - } - } - } catch (e) { - console.warn('Keystone.onConnect() Error:', e) - } - }, -}) - -registerSchemas(keystone, [ - require('./schema/User'), -]) - -const authStrategy = keystone.createAuthStrategy({ - type: PasswordAuthStrategy, - list: 'User', -}) - -module.exports = { - keystone, - apps: [ - new GraphQLApp(), - new AdminUIApp({ - authStrategy, - enableDefaultRoute: true, - }), - ], -} diff --git a/apps/_ex01back/initialData.js b/apps/_ex01back/initialData.js deleted file mode 100644 index 5b777d87..00000000 --- a/apps/_ex01back/initialData.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = [ - { - listKey: 'User', - items: [ - { - data: { - name: 'Admin', - email: 'admin@example.com', - isEmailVerified: false, - isAdmin: true, - isActive: true, - dob: '1990-01-02', - password: '3a74b3f07978', - }, - }, - { - data: { - name: 'JustUser', - email: 'user@example.com', - isEmailVerified: false, - isAdmin: false, - isActive: true, - dob: '1995-06-09', - password: '1a92b3a07c78', - }, - }, - ], - }, -] diff --git a/apps/_ex01back/migrations/20210203205359-0001_initial.js b/apps/_ex01back/migrations/20210203205359-0001_initial.js deleted file mode 100644 index 4ce7451f..00000000 --- a/apps/_ex01back/migrations/20210203205359-0001_initial.js +++ /dev/null @@ -1,31 +0,0 @@ -// auto generated by kmigrator -// KMIGRATOR:0001_initial:IyBHZW5lcmF0ZWQgYnkgRGphbmdvIDMuMS4yIG9uIDIwMjEtMDItMDMgMTc6NTQKCmltcG9ydCBkamFuZ28uY29udHJpYi5wb3N0Z3Jlcy5maWVsZHMuanNvbmIKZnJvbSBkamFuZ28uZGIgaW1wb3J0IG1pZ3JhdGlvbnMsIG1vZGVscwppbXBvcnQgZGphbmdvLmRiLm1vZGVscy5kZWxldGlvbgoKCmNsYXNzIE1pZ3JhdGlvbihtaWdyYXRpb25zLk1pZ3JhdGlvbik6CgogICAgaW5pdGlhbCA9IFRydWUKCiAgICBkZXBlbmRlbmNpZXMgPSBbCiAgICBdCgogICAgb3BlcmF0aW9ucyA9IFsKICAgICAgICBtaWdyYXRpb25zLkNyZWF0ZU1vZGVsKAogICAgICAgICAgICBuYW1lPSd1c2VyJywKICAgICAgICAgICAgZmllbGRzPVsKICAgICAgICAgICAgICAgICgnaWQnLCBtb2RlbHMuQXV0b0ZpZWxkKHByaW1hcnlfa2V5PVRydWUsIHNlcmlhbGl6ZT1GYWxzZSkpLAogICAgICAgICAgICAgICAgKCdlbWFpbCcsIG1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlLCB1bmlxdWU9VHJ1ZSkpLAogICAgICAgICAgICAgICAgKCdpc0VtYWlsVmVyaWZpZWQnLCBtb2RlbHMuQm9vbGVhbkZpZWxkKCkpLAogICAgICAgICAgICAgICAgKCdpc0FkbWluJywgbW9kZWxzLkJvb2xlYW5GaWVsZCgpKSwKICAgICAgICAgICAgICAgICgnbmFtZScsIG1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ2lzQWN0aXZlJywgbW9kZWxzLkJvb2xlYW5GaWVsZCgpKSwKICAgICAgICAgICAgICAgICgncGFzc3dvcmQnLCBtb2RlbHMuQ2hhckZpZWxkKGJsYW5rPVRydWUsIG1heF9sZW5ndGg9NjAsIG51bGw9VHJ1ZSkpLAogICAgICAgICAgICAgICAgKCdhdmF0YXInLCBkamFuZ28uY29udHJpYi5wb3N0Z3Jlcy5maWVsZHMuanNvbmIuSlNPTkZpZWxkKGJsYW5rPVRydWUsIG51bGw9VHJ1ZSkpLAogICAgICAgICAgICAgICAgKCdkb2InLCBtb2RlbHMuRGF0ZUZpZWxkKGJsYW5rPVRydWUsIG51bGw9VHJ1ZSkpLAogICAgICAgICAgICAgICAgKCdhYm91dE15c2VsZicsIG1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ3VwZGF0ZWRBdF91dGMnLCBtb2RlbHMuRGF0ZVRpbWVGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgndXBkYXRlZEF0X29mZnNldCcsIG1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ2NyZWF0ZWRBdF91dGMnLCBtb2RlbHMuRGF0ZVRpbWVGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgnY3JlYXRlZEF0X29mZnNldCcsIG1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ2NyZWF0ZWRCeScsIG1vZGVscy5Gb3JlaWduS2V5KGJsYW5rPVRydWUsIGRiX2NvbHVtbj0nY3JlYXRlZEJ5JywgbnVsbD1UcnVlLCBvbl9kZWxldGU9ZGphbmdvLmRiLm1vZGVscy5kZWxldGlvbi5ET19OT1RISU5HLCByZWxhdGVkX25hbWU9JysnLCB0bz0nX2RqYW5nb19zY2hlbWEudXNlcicpKSwKICAgICAgICAgICAgICAgICgndXBkYXRlZEJ5JywgbW9kZWxzLkZvcmVpZ25LZXkoYmxhbms9VHJ1ZSwgZGJfY29sdW1uPSd1cGRhdGVkQnknLCBudWxsPVRydWUsIG9uX2RlbGV0ZT1kamFuZ28uZGIubW9kZWxzLmRlbGV0aW9uLkRPX05PVEhJTkcsIHJlbGF0ZWRfbmFtZT0nKycsIHRvPSdfZGphbmdvX3NjaGVtYS51c2VyJykpLAogICAgICAgICAgICBdLAogICAgICAgICAgICBvcHRpb25zPXsKICAgICAgICAgICAgICAgICdkYl90YWJsZSc6ICdVc2VyJywKICAgICAgICAgICAgfSwKICAgICAgICApLAogICAgXQo= - -exports.up = async (knex) => { - await knex.raw(` - BEGIN; --- --- Create model user --- -CREATE TABLE "User" ("id" serial NOT NULL PRIMARY KEY, "email" text NULL UNIQUE, "isEmailVerified" boolean NOT NULL, "isAdmin" boolean NOT NULL, "name" text NULL, "isActive" boolean NOT NULL, "password" varchar(60) NULL, "avatar" jsonb NULL, "dob" date NULL, "aboutMyself" text NULL, "updatedAt_utc" timestamp with time zone NULL, "updatedAt_offset" text NULL, "createdAt_utc" timestamp with time zone NULL, "createdAt_offset" text NULL, "createdBy" integer NULL, "updatedBy" integer NULL); -ALTER TABLE "User" ADD CONSTRAINT "User_createdBy_7f87595a_fk_User_id" FOREIGN KEY ("createdBy") REFERENCES "User" ("id") DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE "User" ADD CONSTRAINT "User_updatedBy_998aac8b_fk_User_id" FOREIGN KEY ("updatedBy") REFERENCES "User" ("id") DEFERRABLE INITIALLY DEFERRED; -CREATE INDEX "User_email_667201b5_like" ON "User" ("email" text_pattern_ops); -CREATE INDEX "User_createdBy_7f87595a" ON "User" ("createdBy"); -CREATE INDEX "User_updatedBy_998aac8b" ON "User" ("updatedBy"); -COMMIT; - - `) -} - -exports.down = async (knex) => { - await knex.raw(` - BEGIN; --- --- Create model user --- -DROP TABLE "User" CASCADE; -COMMIT; - - `) -} diff --git a/apps/_ex01back/package.json b/apps/_ex01back/package.json deleted file mode 100644 index 08d4ed11..00000000 --- a/apps/_ex01back/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@app/ex01back", - "version": "2.0.0", - "private": true, - "scripts": { - "test": "jest --testTimeout=10000", - "dev": "nodemon --exec keystone dev", - "debug": "node inspect node_modules/.bin/keystone dev", - "build": "keystone build", - "start": "keystone start", - "makemigrations": "./../../bin/kmigrator.py makemigrations", - "migrate": "./../../bin/kmigrator.py migrate" - }, - "dependencies": { - "@keystonejs/adapter-knex": "^12.0.2", - "@keystonejs/adapter-mongoose": "^10.0.1", - "@keystonejs/adapter-prisma": "^1.1.2", - "@keystonejs/app-admin-ui": "^7.3.9", - "@keystonejs/app-graphql": "^6.1.0", - "@keystonejs/app-next": "^5.2.1", - "@keystonejs/app-static": "^5.1.2", - "@keystonejs/auth-password": "^5.1.17", - "@keystonejs/fields-wysiwyg-tinymce": "^5.3.13", - "@keystonejs/file-adapters": "^7.0.3", - "@keystonejs/keystone": "^17.1.1", - "@keystonejs/server-side-graphql-client": "^1.1.2", - "@keystonejs/utils": "^6.0.1", - "@keystonejs/fields-color": "^1.0.7", - "@keystonejs/fields-content": "^9.0.2", - "@keystonejs/fields-location-google": "^3.0.2", - "@keystonejs/list-plugins": "^7.1.4", - "cross-env": "^7.0.0", - "nodemon": "^2.0.7" - }, - "repository": "https://github.com/pahaz/hackathon-boilerplate-starter-kit/tree/master/apps/_ex01back" -} diff --git a/apps/_ex01back/schema/User.js b/apps/_ex01back/schema/User.js deleted file mode 100644 index d2ee68ce..00000000 --- a/apps/_ex01back/schema/User.js +++ /dev/null @@ -1,12 +0,0 @@ -const { Wysiwyg } = require('@keystonejs/fields-wysiwyg-tinymce') -const { User: BaseUser } = require('@core/keystone/schemas/User') - -const User = BaseUser._override({ - fields: { - aboutMyself: { type: Wysiwyg }, - }, -}) - -module.exports = { - User, -} diff --git a/apps/_ex01front/package.json b/apps/_ex01front/package.json deleted file mode 100644 index a917f3f8..00000000 --- a/apps/_ex01front/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@app/ex01front", - "version": "1.0.0", - "scripts": { - "dev": "next", - "build": "next build", - "start": "next start", - "test": "next build" - }, - "dependencies": { - "next": "^9.5.2" - }, - "repository": "https://github.com/pahaz/hackathon-boilerplate-starter-kit/tree/master/apps/_ex01front" -} diff --git a/apps/_ex01front/pages/index.jsx b/apps/_ex01front/pages/index.jsx deleted file mode 100644 index 551f7d32..00000000 --- a/apps/_ex01front/pages/index.jsx +++ /dev/null @@ -1,5 +0,0 @@ -function HomePage () { - return Welcome to Next.js!-} - -export default HomePage diff --git a/apps/_ex01mobile/App.js b/apps/_ex01mobile/App.js deleted file mode 100644 index 88e86db5..00000000 --- a/apps/_ex01mobile/App.js +++ /dev/null @@ -1,18 +0,0 @@ -import 'react-native-gesture-handler' -import { StatusBar } from 'expo-status-bar' -import React from 'react' -import { IconRegistry } from '@ui-kitten/components' -import { EvaIconsPack } from '@ui-kitten/eva-icons' - -import { AppNavigator } from './AppNavigator' -import { ThemeState } from './context/theme' - -export default function App () { - return (<> -- - -- - >) -} diff --git a/apps/_ex01mobile/AppNavigator.js b/apps/_ex01mobile/AppNavigator.js deleted file mode 100644 index 9ac4347a..00000000 --- a/apps/_ex01mobile/AppNavigator.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react' -import { NavigationContainer } from '@react-navigation/native' -import { createStackNavigator } from '@react-navigation/stack' - -import { IndexScreen } from './screens/index.screen' -import { DetailsScreen } from './screens/details.screen' - -const { Navigator, Screen } = createStackNavigator() - -const HomeNavigator = () => ( - - -) - -export const AppNavigator = () => ( -- - - -) diff --git a/apps/_ex01mobile/app.json b/apps/_ex01mobile/app.json deleted file mode 100644 index a296d3a5..00000000 --- a/apps/_ex01mobile/app.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "expo": { - "name": "_mobile01", - "slug": "_mobile01", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icon.png", - "splash": { - "image": "./assets/splash.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "updates": { - "fallbackToCacheTimeout": 0 - }, - "assetBundlePatterns": [ - "**/*" - ], - "ios": { - "supportsTablet": true - }, - "web": { - "favicon": "./assets/favicon.png" - } - } -} diff --git a/apps/_ex01mobile/assets/favicon.png b/apps/_ex01mobile/assets/favicon.png deleted file mode 100644 index e75f697b1801871ad8cd9309b05e8ffe8c6b6d01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1466 zcmV;r1x5OaP)- F>1w{Y zBeHf{*q3<2*AtQf4s&-m0MsH$EBv51Nj=s=Appw|nd1Yi(-DKZBN$9bAlWN83A_)0 z$4U=S!XyBuAm(`t#aW=l*tHPgHRE~MrmzGWN*Eidc=$BV2uYe|Rpi@t-me&ht6I?| ze$M(9=%DxSVTwNL7B *O`z`fRE$T)18O{B^J5OHo #W %kD-}gAcJO3n1x6Q{X*TFh-d!yx?Z$G16f%*K?exQ+p ztyb%4*R_Y=)qQBLG-9hc_A|ub$th|8Sk1bi@fFe$DwUpU57nc*-z8<&dM#e3a2hB! z16wLhz7o)!MC8}$7Jv9c-X$w^Xr (M9+`Py)~O3rGmgbvjOzXjGl>h9lp*QEn %coj{`wU^_3U|=B`xxU;X3K1L?JT?0?+@K!|MWVr zmC=;rjX@CoW3kMZA^8ZAy52^R{+-YG!J5q^YP&$t9F`&J8*KzV4t3ZZZJ>~XP7}Bs z<}$a~2r_E?4rlN=(}RBkF~6rBo}Sz7#r{X49&!gODP+TcB*@uq57EII-_>qWEt44B z`5o+tysMLY*Dq^n@4_vzKRu3We5|DI+i%NV=Z|)QAl{d i_@%07*qoM6N<$f(5Fv<^TWy diff --git a/apps/_ex01mobile/assets/icon.png b/apps/_ex01mobile/assets/icon.png deleted file mode 100644 index 6eaf3029532605a3eacae6f90b4770cccc8a169f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 642 zcmeAS@N?(olHy`uVBq!ia0vp^2SAtuNHCOdH@?llz~t%a;uunK>+N;j)sF*2S|5J? zUw=@jaj{-+=PGRxE _I n2=r*~8kqq T11AV78TEA|IG>q4VXDC4pe3gSy%oMyTxE z8783cW?*=6q{R} 5>MRxj 8JNK^cZQ#j6f2lrl5|K0 zDtoyTtoK0w`y pMhL$=`G5>dIW+jZbduGn8F4Dgi z%B8;J$+ 8{X`M>WO_NRHaf9=v=TfBvLUaX2{{9n-rWmDEUGE7^#_^0)O+MN4wA8*ewtGp(6 r?D#GJhTYYX(<*N_R7Re bP0l+XkK_K*dU diff --git a/apps/_ex01mobile/assets/splash.png b/apps/_ex01mobile/assets/splash.png deleted file mode 100644 index cc94f379de325e1292ad7843f958a74fb7547d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9306 zcmdsdi9b}||GzAiy^s=>QiO&QQ}#94#=gXmx2+8BAp728DYA -9R1^UT!vHaEv<4kjihZhbu+b0#KM z_}|US!r%lJ35GBptoJohnoLYJ$(*}RYz+F4pZRSqrm9|%c?Q8}YGirqwvUmRm{@mr zHy0Pz!NEaTSeRlTN7g@6e{*9Cru`j-PpY1|jHX zw6ljY$|oW2+b7eRPlDriYNQwnR?gGKCYP5kBp<@LSxwKLCtl4D@>PcHdkJ%qr#_^4 zX+@B;=l1<@gW#KyCAekJlaq5;YxXoc){mxz^-Dyw)&sKS`nF6b;DK~&vsMy`u9ZB- zb#LjtYP4-PP%<{w)};!`WR0#=A-sNMl1&oaUZMaex5dmLFRPbkKp}gZ{9J(l^$J+) zZ?b03VBB3CWb~DQ&F+2BpSeCJSXq=E8lpXz>*4n#^6sMh1Sy1s&TNtEev9 `5bRRiw@kq_!Wv zPm58%x!36%F-L-DGLua
2bTo2%QnYEj#ZRA1j#*Fq7CUtB!I#f* zpJ(rq0Nlc T~BJPc10s9+v^%c$e?<-;4Mz zTT+L1D!qPFj4L0$OQsl`v~o-|+eTXqYz+QqqE1pJQ~`}Qx%Qt|tKW`?uN3*+6w4)> z^4jWDN9J7I+)Lcx>OFcN;l=O8B5UN`_}>FR-CzfrROhD+&rm~Ua!Bpu3@4_`CV#L; zj<80>=Gah#mN5}(7b_9tQri!1suC)D%$0~4)s?9F_*k1-w^uetgnvs$7~MfnS*^J# zSkdeSss1E|xMNFM=vR#4f5;L_@};B5BQX8HMlFeQz`NtVDH^{U?h|P}a Ogixys`@t%fEH~ zv)WI|=)OEueYMwE{5t)-nb&JoGigmML#Y2-jY_CK5+8! pz zwA0~c(z2U_@So9)C)F!(-HCc&haUgwDT(_%&s%faMG|-Jr5x0$YA9>devqYPW;?D> zb~)uuD2LPIdZNRJxI6)_J=VBrC%;b~LgMB0UBe3EbM2ObYs$?l3M>4-j^qivKfU{w zhrN1ZL2THXw=vLV8u<~TOZF85yYOM`_<;4$`S0uQ$@|xLt+n^df;F_wg7_xvqfZ@3 z@G5BY4Cpo!#O85@XPY6-&AG(=k^Ns$t-fTg^kG4379Pdq*eKWhe&Xx?U9RRso@Fjt ziX?|qvyvXdK1U1zmqpd<_DVd)D}FzM3`s98d{ Q>Y~`@ z=dg_9=lq`%5t~hIi=)@Y7I% nCpH>R}QWP;7T^fHgdkY@k&TTE;Nfk26TXEu1C z=~M#oc{C#HEP4P{UXBS1J1t-*F2xqu !sp6HyVNFe7ba J@{}( z*b^lT5b!_R*Arcw3l|fK?K8!YuV Gj>Ixx8xAz_ z_)-U<7?#_x((>5F6actUoo{>>J?Dw#J5>w0l8-;QnZ8k1DEjD+f7pjUdTMSYc2TJw zBRULYd0h{a0&>UXuQ~%()G|aItYnNm-2p;R4NDXUhUfuU$6Hr(jF%m%w2qSk!r~so zE0u`Zmie{&)Tf3N59tC(N V&;;1Fmuc0~VD=Oww_LBFVrajCs?WxtF*vfrR| zajD)69q2raD~Toz0uJpqzC<$0)u%CK4Q-8%^h2j(@t(CxRn?W12_ETKViMXw75_lz zz_@|EVY~&xf80LsErF+?91mf$F?7|`@`GwD3946IAOD;36P~I%R*q o2go3N%UU{chM;20YL?c zbGK5g0EdbEkouV7_Szqd(otpFfbU7H4ug%Kco15H)BfpV#ofeDD C8Z^#7T-&_c5p}MYR)Ild0Svd zO5ck<>)juQO}J^6Ac}*PWC+b %XuykfX0U` zN5?2g6Fd0R9FDl}RZ;|mDIH>{A>1*I(USLfYOsXabD&}geWg%@7G6x>+9ZI$c+2j& z%RXATU^K6tyP3?QAm{)e8|8Oj)k0fRb)CA#m#l7F1CGN(hXLw#J3eO2ckR!O&mb_M zQ2^X^PajfWy5gGrnzzQ%h^EWU3eUzMx3FH8qUZMTSzS8<1pta7=nvxKKWv;ad||=_ z_9z1nXd8-eZ5JDwBX_lL(HeZ8K#GOHO;2~})C*&srhU*pMT3^hh~qRIPdKU`>sV9k z%?qsdO{~I@G-r%?bn`Q2R7j#QNPh!`o?7~$efd5jeRYVHZAPtU%3gL0wdb{s1NWZaea^vS<(Pu%Ot(mDC^gyX9h2Q0u5YV2WH zMaMi5X#9oOtvi!^>>%9n`&W8+Qt$*m^PU3MyWj&0aQcb-va+hY%#HHNpgWHPuUUM6 z9p*qu-E}Ix5FzgW3pnYm!{IANnZ+SLU!0d;R^*%&usYbZa4}Ok>{-PmaI*mQ9s*40 z0gK(ybz65uG2#P5 l0iZg+f%x$E@6BF#%!@FOv(1kzZ_RdrHAE za;QUu%}BBFa%W~B5@Yd>9DY~W@=x!hPFXG`o9w{D4-1rlmG8fXFY45w+zhz%C7R}i znOO%h{1WVwmuw6&yiWMxo*`b%YwFh$*#9ZRJ&AhENglv)!1xw>;$_iNV|2BMQ6}=o zV)ryG6gkCH%*XjI%QPQZ=0EmjC!-zQ!~Za$iu0_qxUU-EZtB0Zx$q{k@xBZ*Zx=x^ zz_5U*1epIgWfPbr@5R~j+1xxc-y(RbDS81+l}mXrmu@$Kzwa!Gshwp4{0z_oc0O>n z?ZT=#btqp9?2!aCD}*`W*j{E~enb5oonv5+1Ykb?*Y_J(5r5ukMvN^{K0{oM7pOqt z{6!@tMK|y)JC>KU?Y^N@v?$O5SjK(REfaFPEFQqt@C35LE-RW-kGwdKb$Uwd2@9~y z%Fv?mcs?UFZfomHI HjEi*uPJ%Z`~vV!~c|n3nYJ0-FDrBDV~76=~KR%@gN}uAJrq{IqdbM>(HW zOWl>5F!K{@YIapm+G8jRuBT~cp2(iMg9yL!cZZoR0K*x&ijws6#v! X53$__JbjN!q@wM zg<06FFv47q3-hl};v`ZEt@|G#gTT~}p%#xsz6aThw^rU}0+!qpx=w8-#JqbXUoWV7 zYMTSjc?}p EHb6ijAEw^FaB!%56PsGd*E4Zi)IARRK(+p6G?qF!TzmvT^^3pN`s$R` ncbT z2QCmrH_mCDht+J)8_wKU;{qxSaAmJF+=_{3@0_>b8P}xQ_vsdYeEk@rb7JKNj}!t5 zw8m#pYEh!W$F_rppAQbf*45WMgI;&~TWHcY0#xOMdX55~na~dni$Z qtFQ^F@amJdLX{c_+rBDvM^Ena_Md1XR&!(C3iBQ z??z#39AMK~AoVpupoqTWL_j=5-Ugd&G156{cROFHhQ7cq7xWwl`Uw2|FAxvyJ6}2M z9aA}9@a-z>jD@=dHL8XQ4yQR`73fT=?o^$G6q>1?$_(Fq3W@$P6#@GHfTJt!8WdK* zo^nyjNca(S@)hwqRgkkENvV>mEx5paR0H=Ey6zqp0@rC4o4$m; z&oqdf#h3y{hP1~JKh=ofK*r2dtc!cHF;5@`Big`efC5d!JL)F?OImjtY2v^k_%NKq zFtScXKKc?h*~tuYCzhbz=3cPuL%!`pYSHe-L8Hn5!%tuVq&inU(##E>xh*`j1f^bj z7wgUP&*KAg%1|tH%U$?%c_TydFswmSSC{66HH6(+fa V~;k(m# z>3>SX?tTV1LjRXeJ`K}E &ZQ%RoG<== z9qyBGpncfag*vp#yf7Sirxq{$m1+%rL+1MR9|kJ)aCDV-(tsN$c5FF;FP#UTeTMA3 zLdh;H^kTm#2kKYV4QT%JdF_>!Tv6&tjhnDICnNZHQ2#$CE`F4oq39WH+{HjyZ(exl zD8PRk-N)BK3UZZ#uXywBkgW(kncv3mT+Yt8JFSu(-}u~&i7k;Ls0bT~7ybGgR6w+X zbzIs*OVji*u2qtuYA>HN3s@2mFWZu$drflFWH9^=$r?Cm_?4tgpQW5+(;k=z9ffm> z07c40%iB(AzdurtMg>G$Sg%y!i+7eG|A?c6)8)fJA{7!*$uTHqPhE6m1?bd!oJKbU zWgIkV5CJ7nr%YkYntRT)aWu)ijovSwR>UOt7|H>LEgK71wWdbIQI9&Qj!Uf%3xr^` zo3Mcpxudh}r*lD%5CC=PIR959n198VJ5F4*R1fFHeZ&xbetLuLnytCH3>!160)}(y z8&p8-#@KxFuuVB&bzjv+gIwoOKc1s!sxIRsT$Z>!gJ^~HY-w{*(bcD}dLB2=vjSEa zvHC2#@OoK!{f1w8URlDF*+it-sc`EWZ$SeQ>a|*4?Od*=CIv>Ceu1>`+P+u#X~yb5 z45#3wSV%!v_)k=iBTIcy0+&8>kS7Hy2Rje{mRw3}+kN&%y!G|RY*u%)6)c`$pAyfk z-yt4h&{`}&ff~ThSO2vqM=qIe2k8b e>l7&_9eU zIA)%T@UvDryb;>+@Js;%AP=_jOZTdMcbp1k`Sqgu#d9w#7W_+Q+~1c4GC|Q_NskCw zPDolTG+5Nbm-qBpgkn5!hWF0Zj;mk~8_=S-o-s0GAGns{m-4=(a!iZ1Koz%q?pX2p z+2HGN#TIWkx-TcOkpgvrx0-}QMJVAREn<{bLZ->a`!`W&H&{>oX$GIF#Uj7WF+1)3 z>4E|oAeL$Ui`6 Hp z?I*W+=wHouiS+o)Q2cX2ANG~o@kop)YFyy*&C1Tly@4 2`fF=WnGAMHB)sX~F}AC!b%#>B1ied@E=)=cU0 ze(;#}*g!EVN4)YqsWQ1}_4bOvlt#|GOB$v) 19Xzw0Gy-(Gl>B@?0eGKp)eK6ChvAC)j#{ z=KYVL!4lL`OZ%JC-!IQRqAzlAl;svMirn;ktMzONN(LL*FzRlr?w@jxmB<2s$DO}~ z-SH?TgN1xzL`}JPWa}-XTPa56iHFO)b(ns*(boJ=U{aN3(p2ny `L0jFWaRJAk2N(R zb z{R`(8c(O)V92h;Pz1N`p`q$azYT5Rg$Y;%TKQx9j9s0#veRo{T>cPUs!WHruJd_!V zNnmi5pH_(zHVO2?Rm-;lwg|wNn%Q_{MT!&8LwKjQZs+e<2k!f(LN_sAfTDB0)#0nN zU+!dMJR=UMf|r48-rGaNC(~{P!VLss6Vj08W7nU#C88JM_uk4mCZ$;%m~2UfzKhtk zd-jO|w1|T`I&TCrmzFPG8vWnZru<>d*7%P0*Ef6CXICdo!r(zj4?{b%Lm)1zbu2g4 z$ 6r z6T-H4Z0ZgBaqY?X2?+YU%K4-JV{hL>hj%Q#N??A0q)WSKU42R5$@*hEj}NPoYnb2e zy|xiBs@niWi(12Sc9;lRt!E*PQC$(+Ir4u-Rn9+s0X=U|TBy3TILO<0&`9+}!;RLo zS4D_D_KAif57H0%gR$Vk6Az}I6i@{bz`V(CXp+t1jP>Te=VISfC&n-rFrfLo&$J)E z &!K?)U<-k({Qwtt@WNr-`0QM)syv3pWxSWG0ddcUXnhWHi3C4~|hU;sMQ zpLyzaTpA`UX?VXmj4yH`D@93RECV2Tpg({`Ne)B&r@#Do5TSGcWUs1U+K--Db}&T0 z1B*6_WahryS6hJ)w4EZvUD$k>-HDpHi@|~1FQI{^#7}m|F9GxCqVyPUE>-crA|Uo6 z^k_b6Ma#4*X95C)%85kwFY%r~`Vs=a@;@Gc){ rV}x!KBPDba5Yu^acK;-9_+u9U#W}J8ngSMaIX_jv^h*9mMHa{^I<6a?`h1jx zSae@#0@+aLmaDpY=%GV#Rfeai9%!(RO%FX$wnIe{DpRosYu7a36|8`7IC=ymc_UaE zvO+Pc;0=KI8Q4|8%)06G;MtYEJ!P@e!%+8X1_9v%!|}AAm@ij=M38&lbM`-*kNue; zywzZgF>UsmKP*=rG=hzKs__jZb&s_fqc=`WQw5cP&5HI8PwFpPSP>!+UOGum^+fL< zYNiS*13?M2I?SREu8HB!Z$EglQ7&=-45)v#VsiZHj#G=DZ`jcsh^MHhGo!CRaM>*7 zTK|xLJ(~{G;lY_?3f}3AA1Wurw;D_=kg^-otpxA}j3vm0cuo}BvKrCe8)E#dzkR?c z2MVpY)PVtwpC%4>yvaM;_}u)241mXC(; `?al|HNGlnVyfUkEl1zaWeyw*_Z_bS0VNP@{Kj)K5f86~?n%)RS5@7}A&uVZ7?b zOC? bE`@Ht2>Co|wyPjS@isi*a{l-Sm}6ZY_;g-wQA_MCf~8ZFL6eh9RH61fT|26Ayr& z5;uIqEL>vk)0dJ}9T(yX^Cv#h*LN`=VCzk8T>B>pMc*)mmDVC)m9kUBwJYDhK^;77 zh@L$07(@k5ZFsh+S;C>!$Bby=xln$qx+!cBje^^#8S%8Mp41CY{d(=@&TD}QgBm2_ zeADK6z$G+vEb}StDn?oWndpBd|19J-h17w&7*@LdThiUjfgvYZ2t?BndFpWqtXr&) zLxlfG&R!vfQ)mriGA8rle$Do`H+0UX6kVmMPk`BFpm9NLddRX}s|gOdZZXLclGnP- zq41;POUkO}aE!BON6DnY;@tMClan;`dd?_T5u`TE= ~)u$br(v*NPG{4ba5YB)Z>s@Wf*A;tjMEjxTXwXz8bQ zOn+dNJ{vR t7B17N&SNz2$MMif~O)+T@!;#D*4=AOjopC^s6lYJnz+HFT *a0ge8puGSXMT*(#NX>L@pkL)SZQoR2WGKGoZ&Bo{#d69Q2t8l@k+Si1Lt%FIckOG zy1*Vqh~lr;jcC1m=gX^W4RLCxN2I)9r7Ha!+wTAQRRGFPxXA&87rZY|Sj9EmLeY3x zlv>Lekh+y)wOdBRC}e>`-G9q>wN;#}^tpeqhIb~TJK%dUSJ_QQeL1tBSy3;GKTTcX zBM3V<(CU&GzejP1L>sFitzqSO%crT0o1-8-26*7hNS-KTCw{1NOQi?4wmG}=k}xRH z57~?q=aPRAs6*k`Jo@qP0kT!gp>#9qP^l-sjRTmgEguNytnc^5{a!8gHpzO2cvTF9 z!0OHFw0gfkdm?rDZZrOW_e3g>Q=!lh!QClZ&=dJuMCy-)N#2<9VNz9nZY!jY=c$g0 zklJ6&O2AJZ>^6VZzmCQMj(huo;AHZ?Y4bj0zkojwj%p$-S1KK>Zx2r<9Bj`QSw)iD j5B=Xfx59{uZI9)r1XoR$V)vVWG3wtk)~V9^@A3ZwM7s($ diff --git a/apps/_ex01mobile/babel.config.js b/apps/_ex01mobile/babel.config.js deleted file mode 100644 index d265563a..00000000 --- a/apps/_ex01mobile/babel.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = function (api) { - api.cache(true) - return { - presets: ['babel-preset-expo'], - } -} diff --git a/apps/_ex01mobile/context/theme.js b/apps/_ex01mobile/context/theme.js deleted file mode 100644 index 812af5ad..00000000 --- a/apps/_ex01mobile/context/theme.js +++ /dev/null @@ -1,24 +0,0 @@ -import React, { createContext, useState } from 'react' -import { ApplicationProvider } from '@ui-kitten/components' -import * as eva from '@eva-design/eva' - -export const ThemeContext = createContext({ - theme: 'light', - toggleTheme: () => {}, -}) - -export const ThemeState = ({ children }) => { - const [theme, setTheme] = useState('light') - const toggleTheme = () => { - const nextTheme = theme === 'light' ? 'dark' : 'light' - setTheme(nextTheme) - } - - return - -} diff --git a/apps/_ex01mobile/metro.config.js b/apps/_ex01mobile/metro.config.js deleted file mode 100644 index 77a75227..00000000 --- a/apps/_ex01mobile/metro.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const { createMetroConfiguration } = require('expo-yarn-workspaces') - -module.exports = createMetroConfiguration(__dirname) diff --git a/apps/_ex01mobile/package.json b/apps/_ex01mobile/package.json deleted file mode 100644 index f14000dd..00000000 --- a/apps/_ex01mobile/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@app/ex01mobile", - "version": "1.0.0", - "main": "__generated__/AppEntry.js", - "scripts": { - "dev": "expo start", - "start": "expo start", - "android": "expo start --android", - "ios": "expo start --ios", - "web": "expo start --web", - "eject": "expo eject", - "postinstall": "expo-yarn-workspaces postinstall" - }, - "dependencies": { - "@eva-design/eva": "^2.0.0", - "@expo/vector-icons": "^10.2.0", - "@react-native-community/hooks": "^2.6.0", - "@react-native-community/masked-view": "^0.1.10", - "@react-navigation/native": "^5.7.3", - "@react-navigation/stack": "^5.9.0", - "@ui-kitten/components": "^5.0.0", - "@ui-kitten/eva-icons": "^5.0.0", - "expo": "^38.0.8", - "expo-font": "^8.2.2", - "expo-status-bar": "^1.0.2", - "react": "^16.13.1", - "react-dom": "^16.13.1", - "react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz", - "react-native-gesture-handler": "^1.7.0", - "react-native-reanimated": "^1.13.0", - "react-native-safe-area-context": "^3.1.7", - "react-native-screens": "^2.10.1", - "react-native-svg": "^12.1.0", - "react-native-web": "^0.13.9" - }, - "devDependencies": { - "@babel/core": "^7.10.3", - "babel-preset-expo": "~8.1.0", - "expo-yarn-workspaces": "^1.2.1" - }, - "repository": "https://github.com/pahaz/hackathon-boilerplate-starter-kit/tree/master/apps/_ex01mobile" -} diff --git a/apps/_ex01mobile/screens/details.screen.js b/apps/_ex01mobile/screens/details.screen.js deleted file mode 100644 index 7c8885fe..00000000 --- a/apps/_ex01mobile/screens/details.screen.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import { SafeAreaView, StyleSheet } from 'react-native' -import { Divider, Icon, Layout, Text, TopNavigation, TopNavigationAction } from '@ui-kitten/components' - -const BackIcon = (props) => ( -- {children} - --) - -export const DetailsScreen = (props) => { - const { navigation } = props - - const navigateBack = () => { - navigation.goBack() - } - - const BackAction = () => ( - - ) - - return ( - - - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - alignItems: 'center', - justifyContent: 'center', - }, -}) diff --git a/apps/_ex01mobile/screens/index.screen.js b/apps/_ex01mobile/screens/index.screen.js deleted file mode 100644 index 864f9aec..00000000 --- a/apps/_ex01mobile/screens/index.screen.js +++ /dev/null @@ -1,44 +0,0 @@ -import React, { useContext } from 'react' -import { SafeAreaView, StyleSheet } from 'react-native' -import { Button, Divider, Icon, Layout, TopNavigation } from '@ui-kitten/components' -import { Entypo } from '@expo/vector-icons' - -import { ThemeContext } from '../context/theme' - -export const IndexScreen = ({ navigation }) => { - const themeContext = useContext(ThemeContext) - - const navigateDetails = () => { - navigation.navigate('Details') - } - - return ( -- - - -DETAILS -- - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - alignItems: 'center', - justifyContent: 'center', - }, - icon: { - width: 32, - height: 32, - }, -}) diff --git a/apps/_ex02back/index.js b/apps/_ex02back/index.js deleted file mode 100644 index bcc7dfee..00000000 --- a/apps/_ex02back/index.js +++ /dev/null @@ -1,61 +0,0 @@ -const { Keystone } = require('@keystonejs/keystone') -const { PasswordAuthStrategy } = require('@keystonejs/auth-password') -const { GraphQLApp } = require('@keystonejs/app-graphql') -const { AdminUIApp } = require('@keystonejs/app-admin-ui') -const { StaticApp } = require('@keystonejs/app-static') -const { createItems } = require('@keystonejs/server-side-graphql-client') - -const conf = require('@core/config') -const access = require('@core/keystone/access') -const { registerSchemas } = require('@core/keystone/schema') -const { prepareDefaultKeystoneConfig } = require('@core/keystone/setup.utils') - -const keystone = new Keystone({ - ...prepareDefaultKeystoneConfig(conf), - onConnect: async () => { - // Initialise some data - if (conf.NODE_ENV !== 'development') return // Just for dev env purposes! - // This function can be called before tables are created! (we just ignore this) - try { - const users = await keystone.lists.User.adapter.findAll() - if (!users.length) { - const initialData = require('./initialData') - for (let { listKey, items } of initialData) { - console.log(`🗿 createItems(${listKey}) -> ${items.length}`) - await createItems({ - keystone, - listKey, - items, - }) - } - } - } catch (e) { - console.warn('onConnectError:', e) - } - }, -}) - -registerSchemas(keystone, [ - require('./schema/User'), - require('./schema/Organization'), -]) - -const authStrategy = keystone.createAuthStrategy({ - type: PasswordAuthStrategy, - list: 'User', -}) - -module.exports = { - keystone, - apps: [ - new GraphQLApp({ - apollo: { debug: conf.NODE_ENV === 'development' || conf.NODE_ENV === 'test' }, - }), - new StaticApp({ path: conf.MEDIA_URL, src: conf.MEDIA_ROOT }), - new AdminUIApp({ - adminPath: '/admin', - isAccessAllowed: access.userIsAdmin, - authStrategy, - }), - ], -} diff --git a/apps/_ex02back/initialData.js b/apps/_ex02back/initialData.js deleted file mode 100644 index 5b777d87..00000000 --- a/apps/_ex02back/initialData.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = [ - { - listKey: 'User', - items: [ - { - data: { - name: 'Admin', - email: 'admin@example.com', - isEmailVerified: false, - isAdmin: true, - isActive: true, - dob: '1990-01-02', - password: '3a74b3f07978', - }, - }, - { - data: { - name: 'JustUser', - email: 'user@example.com', - isEmailVerified: false, - isAdmin: false, - isActive: true, - dob: '1995-06-09', - password: '1a92b3a07c78', - }, - }, - ], - }, -] diff --git a/apps/_ex02back/migrations/20210203214827-0001_initial.js b/apps/_ex02back/migrations/20210203214827-0001_initial.js deleted file mode 100644 index 9d3708db..00000000 --- a/apps/_ex02back/migrations/20210203214827-0001_initial.js +++ /dev/null @@ -1,73 +0,0 @@ -// auto generated by kmigrator -// KMIGRATOR:0001_initial:IyBHZW5lcmF0ZWQgYnkgRGphbmdvIDMuMS4yIG9uIDIwMjEtMDItMDMgMTg6NDgKCmltcG9ydCBkamFuZ28uY29udHJpYi5wb3N0Z3Jlcy5maWVsZHMuanNvbmIKZnJvbSBkamFuZ28uZGIgaW1wb3J0IG1pZ3JhdGlvbnMsIG1vZGVscwppbXBvcnQgZGphbmdvLmRiLm1vZGVscy5kZWxldGlvbgoKCmNsYXNzIE1pZ3JhdGlvbihtaWdyYXRpb25zLk1pZ3JhdGlvbik6CgogICAgaW5pdGlhbCA9IFRydWUKCiAgICBkZXBlbmRlbmNpZXMgPSBbCiAgICBdCgogICAgb3BlcmF0aW9ucyA9IFsKICAgICAgICBtaWdyYXRpb25zLkNyZWF0ZU1vZGVsKAogICAgICAgICAgICBuYW1lPSdvcmdhbml6YXRpb24nLAogICAgICAgICAgICBmaWVsZHM9WwogICAgICAgICAgICAgICAgKCdpZCcsIG1vZGVscy5BdXRvRmllbGQocHJpbWFyeV9rZXk9VHJ1ZSwgc2VyaWFsaXplPUZhbHNlKSksCiAgICAgICAgICAgICAgICAoJ25hbWUnLCBtb2RlbHMuVGV4dEZpZWxkKCkpLAogICAgICAgICAgICAgICAgKCd1cGRhdGVkQXRfdXRjJywgbW9kZWxzLkRhdGVUaW1lRmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ3VwZGF0ZWRBdF9vZmZzZXQnLCBtb2RlbHMuVGV4dEZpZWxkKGJsYW5rPVRydWUsIG51bGw9VHJ1ZSkpLAogICAgICAgICAgICAgICAgKCdjcmVhdGVkQXRfdXRjJywgbW9kZWxzLkRhdGVUaW1lRmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ2NyZWF0ZWRBdF9vZmZzZXQnLCBtb2RlbHMuVGV4dEZpZWxkKGJsYW5rPVRydWUsIG51bGw9VHJ1ZSkpLAogICAgICAgICAgICBdLAogICAgICAgICAgICBvcHRpb25zPXsKICAgICAgICAgICAgICAgICdkYl90YWJsZSc6ICdPcmdhbml6YXRpb24nLAogICAgICAgICAgICB9LAogICAgICAgICksCiAgICAgICAgbWlncmF0aW9ucy5DcmVhdGVNb2RlbCgKICAgICAgICAgICAgbmFtZT0ndXNlcicsCiAgICAgICAgICAgIGZpZWxkcz1bCiAgICAgICAgICAgICAgICAoJ2lkJywgbW9kZWxzLkF1dG9GaWVsZChwcmltYXJ5X2tleT1UcnVlLCBzZXJpYWxpemU9RmFsc2UpKSwKICAgICAgICAgICAgICAgICgnZW1haWwnLCBtb2RlbHMuVGV4dEZpZWxkKGJsYW5rPVRydWUsIG51bGw9VHJ1ZSwgdW5pcXVlPVRydWUpKSwKICAgICAgICAgICAgICAgICgnaXNFbWFpbFZlcmlmaWVkJywgbW9kZWxzLkJvb2xlYW5GaWVsZCgpKSwKICAgICAgICAgICAgICAgICgnaXNBZG1pbicsIG1vZGVscy5Cb29sZWFuRmllbGQoKSksCiAgICAgICAgICAgICAgICAoJ25hbWUnLCBtb2RlbHMuVGV4dEZpZWxkKGJsYW5rPVRydWUsIG51bGw9VHJ1ZSkpLAogICAgICAgICAgICAgICAgKCdpc0FjdGl2ZScsIG1vZGVscy5Cb29sZWFuRmllbGQoKSksCiAgICAgICAgICAgICAgICAoJ3Bhc3N3b3JkJywgbW9kZWxzLkNoYXJGaWVsZChibGFuaz1UcnVlLCBtYXhfbGVuZ3RoPTYwLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgnYXZhdGFyJywgZGphbmdvLmNvbnRyaWIucG9zdGdyZXMuZmllbGRzLmpzb25iLkpTT05GaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgnZG9iJywgbW9kZWxzLkRhdGVGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgnbWV0YScsIGRqYW5nby5jb250cmliLnBvc3RncmVzLmZpZWxkcy5qc29uYi5KU09ORmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ2Fib3V0TXlzZWxmJywgbW9kZWxzLlRleHRGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgndXBkYXRlZEF0X3V0YycsIG1vZGVscy5EYXRlVGltZUZpZWxkKGJsYW5rPVRydWUsIG51bGw9VHJ1ZSkpLAogICAgICAgICAgICAgICAgKCd1cGRhdGVkQXRfb2Zmc2V0JywgbW9kZWxzLlRleHRGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgnY3JlYXRlZEF0X3V0YycsIG1vZGVscy5EYXRlVGltZUZpZWxkKGJsYW5rPVRydWUsIG51bGw9VHJ1ZSkpLAogICAgICAgICAgICAgICAgKCdjcmVhdGVkQXRfb2Zmc2V0JywgbW9kZWxzLlRleHRGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgnY3JlYXRlZEJ5JywgbW9kZWxzLkZvcmVpZ25LZXkoYmxhbms9VHJ1ZSwgZGJfY29sdW1uPSdjcmVhdGVkQnknLCBudWxsPVRydWUsIG9uX2RlbGV0ZT1kamFuZ28uZGIubW9kZWxzLmRlbGV0aW9uLkRPX05PVEhJTkcsIHJlbGF0ZWRfbmFtZT0nKycsIHRvPSdfZGphbmdvX3NjaGVtYS51c2VyJykpLAogICAgICAgICAgICAgICAgKCd1cGRhdGVkQnknLCBtb2RlbHMuRm9yZWlnbktleShibGFuaz1UcnVlLCBkYl9jb2x1bW49J3VwZGF0ZWRCeScsIG51bGw9VHJ1ZSwgb25fZGVsZXRlPWRqYW5nby5kYi5tb2RlbHMuZGVsZXRpb24uRE9fTk9USElORywgcmVsYXRlZF9uYW1lPScrJywgdG89J19kamFuZ29fc2NoZW1hLnVzZXInKSksCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgIG9wdGlvbnM9ewogICAgICAgICAgICAgICAgJ2RiX3RhYmxlJzogJ1VzZXInLAogICAgICAgICAgICB9LAogICAgICAgICksCiAgICAgICAgbWlncmF0aW9ucy5DcmVhdGVNb2RlbCgKICAgICAgICAgICAgbmFtZT0nb3JnYW5pemF0aW9udG91c2VybGluaycsCiAgICAgICAgICAgIGZpZWxkcz1bCiAgICAgICAgICAgICAgICAoJ2lkJywgbW9kZWxzLkF1dG9GaWVsZChwcmltYXJ5X2tleT1UcnVlLCBzZXJpYWxpemU9RmFsc2UpKSwKICAgICAgICAgICAgICAgICgnY29kZScsIG1vZGVscy5VVUlERmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlLCB1bmlxdWU9VHJ1ZSkpLAogICAgICAgICAgICAgICAgKCdlbWFpbCcsIG1vZGVscy5UZXh0RmllbGQoKSksCiAgICAgICAgICAgICAgICAoJ25hbWUnLCBtb2RlbHMuVGV4dEZpZWxkKGJsYW5rPVRydWUsIG51bGw9VHJ1ZSkpLAogICAgICAgICAgICAgICAgKCdyb2xlJywgbW9kZWxzLkNoYXJGaWVsZChjaG9pY2VzPVsoJ293bmVyJywgJ293bmVyJyksICgnbWVtYmVyJywgJ21lbWJlcicpXSwgbWF4X2xlbmd0aD01MCkpLAogICAgICAgICAgICAgICAgKCdpc0FjY2VwdGVkJywgbW9kZWxzLkJvb2xlYW5GaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgnaXNSZWplY3RlZCcsIG1vZGVscy5Cb29sZWFuRmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ3VwZGF0ZWRBdF91dGMnLCBtb2RlbHMuRGF0ZVRpbWVGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgndXBkYXRlZEF0X29mZnNldCcsIG1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ2NyZWF0ZWRBdF91dGMnLCBtb2RlbHMuRGF0ZVRpbWVGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgnY3JlYXRlZEF0X29mZnNldCcsIG1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ2NyZWF0ZWRCeScsIG1vZGVscy5Gb3JlaWduS2V5KGJsYW5rPVRydWUsIGRiX2NvbHVtbj0nY3JlYXRlZEJ5JywgbnVsbD1UcnVlLCBvbl9kZWxldGU9ZGphbmdvLmRiLm1vZGVscy5kZWxldGlvbi5ET19OT1RISU5HLCByZWxhdGVkX25hbWU9JysnLCB0bz0nX2RqYW5nb19zY2hlbWEudXNlcicpKSwKICAgICAgICAgICAgICAgICgnb3JnYW5pemF0aW9uJywgbW9kZWxzLkZvcmVpZ25LZXkoZGJfY29sdW1uPSdvcmdhbml6YXRpb24nLCBvbl9kZWxldGU9ZGphbmdvLmRiLm1vZGVscy5kZWxldGlvbi5ET19OT1RISU5HLCByZWxhdGVkX25hbWU9JysnLCB0bz0nX2RqYW5nb19zY2hlbWEub3JnYW5pemF0aW9uJykpLAogICAgICAgICAgICAgICAgKCd1cGRhdGVkQnknLCBtb2RlbHMuRm9yZWlnbktleShibGFuaz1UcnVlLCBkYl9jb2x1bW49J3VwZGF0ZWRCeScsIG51bGw9VHJ1ZSwgb25fZGVsZXRlPWRqYW5nby5kYi5tb2RlbHMuZGVsZXRpb24uRE9fTk9USElORywgcmVsYXRlZF9uYW1lPScrJywgdG89J19kamFuZ29fc2NoZW1hLnVzZXInKSksCiAgICAgICAgICAgICAgICAoJ3VzZXInLCBtb2RlbHMuRm9yZWlnbktleShibGFuaz1UcnVlLCBkYl9jb2x1bW49J3VzZXInLCBudWxsPVRydWUsIG9uX2RlbGV0ZT1kamFuZ28uZGIubW9kZWxzLmRlbGV0aW9uLkRPX05PVEhJTkcsIHJlbGF0ZWRfbmFtZT0nKycsIHRvPSdfZGphbmdvX3NjaGVtYS51c2VyJykpLAogICAgICAgICAgICBdLAogICAgICAgICAgICBvcHRpb25zPXsKICAgICAgICAgICAgICAgICdkYl90YWJsZSc6ICdPcmdhbml6YXRpb25Ub1VzZXJMaW5rJywKICAgICAgICAgICAgfSwKICAgICAgICApLAogICAgICAgIG1pZ3JhdGlvbnMuQWRkRmllbGQoCiAgICAgICAgICAgIG1vZGVsX25hbWU9J29yZ2FuaXphdGlvbicsCiAgICAgICAgICAgIG5hbWU9J2NyZWF0ZWRCeScsCiAgICAgICAgICAgIGZpZWxkPW1vZGVscy5Gb3JlaWduS2V5KGJsYW5rPVRydWUsIGRiX2NvbHVtbj0nY3JlYXRlZEJ5JywgbnVsbD1UcnVlLCBvbl9kZWxldGU9ZGphbmdvLmRiLm1vZGVscy5kZWxldGlvbi5ET19OT1RISU5HLCByZWxhdGVkX25hbWU9JysnLCB0bz0nX2RqYW5nb19zY2hlbWEudXNlcicpLAogICAgICAgICksCiAgICAgICAgbWlncmF0aW9ucy5BZGRGaWVsZCgKICAgICAgICAgICAgbW9kZWxfbmFtZT0nb3JnYW5pemF0aW9uJywKICAgICAgICAgICAgbmFtZT0ndXBkYXRlZEJ5JywKICAgICAgICAgICAgZmllbGQ9bW9kZWxzLkZvcmVpZ25LZXkoYmxhbms9VHJ1ZSwgZGJfY29sdW1uPSd1cGRhdGVkQnknLCBudWxsPVRydWUsIG9uX2RlbGV0ZT1kamFuZ28uZGIubW9kZWxzLmRlbGV0aW9uLkRPX05PVEhJTkcsIHJlbGF0ZWRfbmFtZT0nKycsIHRvPSdfZGphbmdvX3NjaGVtYS51c2VyJyksCiAgICAgICAgKSwKICAgIF0K - -exports.up = async (knex) => { - await knex.raw(` - BEGIN; --- --- Create model organization --- -CREATE TABLE "Organization" ("id" serial NOT NULL PRIMARY KEY, "name" text NOT NULL, "updatedAt_utc" timestamp with time zone NULL, "updatedAt_offset" text NULL, "createdAt_utc" timestamp with time zone NULL, "createdAt_offset" text NULL); --- --- Create model user --- -CREATE TABLE "User" ("id" serial NOT NULL PRIMARY KEY, "email" text NULL UNIQUE, "isEmailVerified" boolean NOT NULL, "isAdmin" boolean NOT NULL, "name" text NULL, "isActive" boolean NOT NULL, "password" varchar(60) NULL, "avatar" jsonb NULL, "dob" date NULL, "meta" jsonb NULL, "aboutMyself" text NULL, "updatedAt_utc" timestamp with time zone NULL, "updatedAt_offset" text NULL, "createdAt_utc" timestamp with time zone NULL, "createdAt_offset" text NULL, "createdBy" integer NULL, "updatedBy" integer NULL); --- --- Create model organizationtouserlink --- -CREATE TABLE "OrganizationToUserLink" ("id" serial NOT NULL PRIMARY KEY, "code" uuid NULL UNIQUE, "email" text NOT NULL, "name" text NULL, "role" varchar(50) NOT NULL, "isAccepted" boolean NULL, "isRejected" boolean NULL, "updatedAt_utc" timestamp with time zone NULL, "updatedAt_offset" text NULL, "createdAt_utc" timestamp with time zone NULL, "createdAt_offset" text NULL, "createdBy" integer NULL, "organization" integer NOT NULL, "updatedBy" integer NULL, "user" integer NULL); --- --- Add field createdBy to organization --- -ALTER TABLE "Organization" ADD COLUMN "createdBy" integer NULL CONSTRAINT "Organization_createdBy_1dd7d630_fk_User_id" REFERENCES "User"("id") DEFERRABLE INITIALLY DEFERRED; SET CONSTRAINTS "Organization_createdBy_1dd7d630_fk_User_id" IMMEDIATE; --- --- Add field updatedBy to organization --- -ALTER TABLE "Organization" ADD COLUMN "updatedBy" integer NULL CONSTRAINT "Organization_updatedBy_e4a88050_fk_User_id" REFERENCES "User"("id") DEFERRABLE INITIALLY DEFERRED; SET CONSTRAINTS "Organization_updatedBy_e4a88050_fk_User_id" IMMEDIATE; -ALTER TABLE "User" ADD CONSTRAINT "User_createdBy_7f87595a_fk_User_id" FOREIGN KEY ("createdBy") REFERENCES "User" ("id") DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE "User" ADD CONSTRAINT "User_updatedBy_998aac8b_fk_User_id" FOREIGN KEY ("updatedBy") REFERENCES "User" ("id") DEFERRABLE INITIALLY DEFERRED; -CREATE INDEX "User_email_667201b5_like" ON "User" ("email" text_pattern_ops); -CREATE INDEX "User_createdBy_7f87595a" ON "User" ("createdBy"); -CREATE INDEX "User_updatedBy_998aac8b" ON "User" ("updatedBy"); -ALTER TABLE "OrganizationToUserLink" ADD CONSTRAINT "OrganizationToUserLink_createdBy_2c72c4aa_fk_User_id" FOREIGN KEY ("createdBy") REFERENCES "User" ("id") DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE "OrganizationToUserLink" ADD CONSTRAINT "OrganizationToUserLink_organization_047b72d2_fk_Organization_id" FOREIGN KEY ("organization") REFERENCES "Organization" ("id") DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE "OrganizationToUserLink" ADD CONSTRAINT "OrganizationToUserLink_updatedBy_758766b7_fk_User_id" FOREIGN KEY ("updatedBy") REFERENCES "User" ("id") DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE "OrganizationToUserLink" ADD CONSTRAINT "OrganizationToUserLink_user_d86c251a_fk_User_id" FOREIGN KEY ("user") REFERENCES "User" ("id") DEFERRABLE INITIALLY DEFERRED; -CREATE INDEX "OrganizationToUserLink_createdBy_2c72c4aa" ON "OrganizationToUserLink" ("createdBy"); -CREATE INDEX "OrganizationToUserLink_organization_047b72d2" ON "OrganizationToUserLink" ("organization"); -CREATE INDEX "OrganizationToUserLink_updatedBy_758766b7" ON "OrganizationToUserLink" ("updatedBy"); -CREATE INDEX "OrganizationToUserLink_user_d86c251a" ON "OrganizationToUserLink" ("user"); -CREATE INDEX "Organization_createdBy_1dd7d630" ON "Organization" ("createdBy"); -CREATE INDEX "Organization_updatedBy_e4a88050" ON "Organization" ("updatedBy"); -COMMIT; - - `) -} - -exports.down = async (knex) => { - await knex.raw(` - BEGIN; --- --- Add field updatedBy to organization --- -ALTER TABLE "Organization" DROP COLUMN "updatedBy" CASCADE; --- --- Add field createdBy to organization --- -ALTER TABLE "Organization" DROP COLUMN "createdBy" CASCADE; --- --- Create model organizationtouserlink --- -DROP TABLE "OrganizationToUserLink" CASCADE; --- --- Create model user --- -DROP TABLE "User" CASCADE; --- --- Create model organization --- -DROP TABLE "Organization" CASCADE; -COMMIT; - - `) -} diff --git a/apps/_ex02back/migrations/20210204185931-0002_auto_20210204_1559.js b/apps/_ex02back/migrations/20210204185931-0002_auto_20210204_1559.js deleted file mode 100644 index 4acbfd61..00000000 --- a/apps/_ex02back/migrations/20210204185931-0002_auto_20210204_1559.js +++ /dev/null @@ -1,57 +0,0 @@ -// auto generated by kmigrator -// KMIGRATOR:0002_auto_20210204_1559:IyBHZW5lcmF0ZWQgYnkgRGphbmdvIDMuMS4yIG9uIDIwMjEtMDItMDQgMTU6NTkKCmltcG9ydCBkamFuZ28uY29udHJpYi5wb3N0Z3Jlcy5maWVsZHMuanNvbmIKZnJvbSBkamFuZ28uZGIgaW1wb3J0IG1pZ3JhdGlvbnMsIG1vZGVscwppbXBvcnQgZGphbmdvLmRiLm1vZGVscy5kZWxldGlvbgoKCmNsYXNzIE1pZ3JhdGlvbihtaWdyYXRpb25zLk1pZ3JhdGlvbik6CgogICAgZGVwZW5kZW5jaWVzID0gWwogICAgICAgICgnX2RqYW5nb19zY2hlbWEnLCAnMDAwMV9pbml0aWFsJyksCiAgICBdCgogICAgb3BlcmF0aW9ucyA9IFsKICAgICAgICBtaWdyYXRpb25zLkFkZEZpZWxkKAogICAgICAgICAgICBtb2RlbF9uYW1lPSdvcmdhbml6YXRpb24nLAogICAgICAgICAgICBuYW1lPSdhdmF0YXInLAogICAgICAgICAgICBmaWVsZD1kamFuZ28uY29udHJpYi5wb3N0Z3Jlcy5maWVsZHMuanNvbmIuSlNPTkZpZWxkKGJsYW5rPVRydWUsIG51bGw9VHJ1ZSksCiAgICAgICAgKSwKICAgICAgICBtaWdyYXRpb25zLkFkZEZpZWxkKAogICAgICAgICAgICBtb2RlbF9uYW1lPSdvcmdhbml6YXRpb24nLAogICAgICAgICAgICBuYW1lPSdkZXNjcmlwdGlvbicsCiAgICAgICAgICAgIGZpZWxkPW1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSwKICAgICAgICApLAogICAgICAgIG1pZ3JhdGlvbnMuQWRkRmllbGQoCiAgICAgICAgICAgIG1vZGVsX25hbWU9J29yZ2FuaXphdGlvbnRvdXNlcmxpbmsnLAogICAgICAgICAgICBuYW1lPSdwaG9uZScsCiAgICAgICAgICAgIGZpZWxkPW1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSwKICAgICAgICApLAogICAgICAgIG1pZ3JhdGlvbnMuQ3JlYXRlTW9kZWwoCiAgICAgICAgICAgIG5hbWU9J2ZvcmdvdHBhc3N3b3JkYWN0aW9uJywKICAgICAgICAgICAgZmllbGRzPVsKICAgICAgICAgICAgICAgICgnaWQnLCBtb2RlbHMuQXV0b0ZpZWxkKHByaW1hcnlfa2V5PVRydWUsIHNlcmlhbGl6ZT1GYWxzZSkpLAogICAgICAgICAgICAgICAgKCd0b2tlbicsIG1vZGVscy5UZXh0RmllbGQodW5pcXVlPVRydWUpKSwKICAgICAgICAgICAgICAgICgncmVxdWVzdGVkQXRfdXRjJywgbW9kZWxzLkRhdGVUaW1lRmllbGQoKSksCiAgICAgICAgICAgICAgICAoJ3JlcXVlc3RlZEF0X29mZnNldCcsIG1vZGVscy5UZXh0RmllbGQoKSksCiAgICAgICAgICAgICAgICAoJ2V4cGlyZXNBdF91dGMnLCBtb2RlbHMuRGF0ZVRpbWVGaWVsZCgpKSwKICAgICAgICAgICAgICAgICgnZXhwaXJlc0F0X29mZnNldCcsIG1vZGVscy5UZXh0RmllbGQoKSksCiAgICAgICAgICAgICAgICAoJ3VzZWRBdF91dGMnLCBtb2RlbHMuRGF0ZVRpbWVGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgndXNlZEF0X29mZnNldCcsIG1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ3VwZGF0ZWRBdF91dGMnLCBtb2RlbHMuRGF0ZVRpbWVGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgndXBkYXRlZEF0X29mZnNldCcsIG1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ2NyZWF0ZWRBdF91dGMnLCBtb2RlbHMuRGF0ZVRpbWVGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpKSwKICAgICAgICAgICAgICAgICgnY3JlYXRlZEF0X29mZnNldCcsIG1vZGVscy5UZXh0RmllbGQoYmxhbms9VHJ1ZSwgbnVsbD1UcnVlKSksCiAgICAgICAgICAgICAgICAoJ2NyZWF0ZWRCeScsIG1vZGVscy5Gb3JlaWduS2V5KGJsYW5rPVRydWUsIGRiX2NvbHVtbj0nY3JlYXRlZEJ5JywgbnVsbD1UcnVlLCBvbl9kZWxldGU9ZGphbmdvLmRiLm1vZGVscy5kZWxldGlvbi5ET19OT1RISU5HLCByZWxhdGVkX25hbWU9JysnLCB0bz0nX2RqYW5nb19zY2hlbWEudXNlcicpKSwKICAgICAgICAgICAgICAgICgndXBkYXRlZEJ5JywgbW9kZWxzLkZvcmVpZ25LZXkoYmxhbms9VHJ1ZSwgZGJfY29sdW1uPSd1cGRhdGVkQnknLCBudWxsPVRydWUsIG9uX2RlbGV0ZT1kamFuZ28uZGIubW9kZWxzLmRlbGV0aW9uLkRPX05PVEhJTkcsIHJlbGF0ZWRfbmFtZT0nKycsIHRvPSdfZGphbmdvX3NjaGVtYS51c2VyJykpLAogICAgICAgICAgICAgICAgKCd1c2VyJywgbW9kZWxzLkZvcmVpZ25LZXkoYmxhbms9VHJ1ZSwgZGJfY29sdW1uPSd1c2VyJywgbnVsbD1UcnVlLCBvbl9kZWxldGU9ZGphbmdvLmRiLm1vZGVscy5kZWxldGlvbi5ET19OT1RISU5HLCByZWxhdGVkX25hbWU9JysnLCB0bz0nX2RqYW5nb19zY2hlbWEudXNlcicpKSwKICAgICAgICAgICAgXSwKICAgICAgICAgICAgb3B0aW9ucz17CiAgICAgICAgICAgICAgICAnZGJfdGFibGUnOiAnRm9yZ290UGFzc3dvcmRBY3Rpb24nLAogICAgICAgICAgICB9LAogICAgICAgICksCiAgICBdCg== - -exports.up = async (knex) => { - await knex.raw(` - BEGIN; --- --- Add field avatar to organization --- -ALTER TABLE "Organization" ADD COLUMN "avatar" jsonb NULL; --- --- Add field description to organization --- -ALTER TABLE "Organization" ADD COLUMN "description" text NULL; --- --- Add field phone to organizationtouserlink --- -ALTER TABLE "OrganizationToUserLink" ADD COLUMN "phone" text NULL; --- --- Create model forgotpasswordaction --- -CREATE TABLE "ForgotPasswordAction" ("id" serial NOT NULL PRIMARY KEY, "token" text NOT NULL UNIQUE, "requestedAt_utc" timestamp with time zone NOT NULL, "requestedAt_offset" text NOT NULL, "expiresAt_utc" timestamp with time zone NOT NULL, "expiresAt_offset" text NOT NULL, "usedAt_utc" timestamp with time zone NULL, "usedAt_offset" text NULL, "updatedAt_utc" timestamp with time zone NULL, "updatedAt_offset" text NULL, "createdAt_utc" timestamp with time zone NULL, "createdAt_offset" text NULL, "createdBy" integer NULL, "updatedBy" integer NULL, "user" integer NULL); -ALTER TABLE "ForgotPasswordAction" ADD CONSTRAINT "ForgotPasswordAction_createdBy_c0278297_fk_User_id" FOREIGN KEY ("createdBy") REFERENCES "User" ("id") DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE "ForgotPasswordAction" ADD CONSTRAINT "ForgotPasswordAction_updatedBy_60e6cb54_fk_User_id" FOREIGN KEY ("updatedBy") REFERENCES "User" ("id") DEFERRABLE INITIALLY DEFERRED; -ALTER TABLE "ForgotPasswordAction" ADD CONSTRAINT "ForgotPasswordAction_user_3c52ec86_fk_User_id" FOREIGN KEY ("user") REFERENCES "User" ("id") DEFERRABLE INITIALLY DEFERRED; -CREATE INDEX "ForgotPasswordAction_token_9a0cc9b4_like" ON "ForgotPasswordAction" ("token" text_pattern_ops); -CREATE INDEX "ForgotPasswordAction_createdBy_c0278297" ON "ForgotPasswordAction" ("createdBy"); -CREATE INDEX "ForgotPasswordAction_updatedBy_60e6cb54" ON "ForgotPasswordAction" ("updatedBy"); -CREATE INDEX "ForgotPasswordAction_user_3c52ec86" ON "ForgotPasswordAction" ("user"); -COMMIT; - - `) -} - -exports.down = async (knex) => { - await knex.raw(` - BEGIN; --- --- Create model forgotpasswordaction --- -DROP TABLE "ForgotPasswordAction" CASCADE; --- --- Add field phone to organizationtouserlink --- -ALTER TABLE "OrganizationToUserLink" DROP COLUMN "phone" CASCADE; --- --- Add field description to organization --- -ALTER TABLE "Organization" DROP COLUMN "description" CASCADE; --- --- Add field avatar to organization --- -ALTER TABLE "Organization" DROP COLUMN "avatar" CASCADE; -COMMIT; - - `) -} diff --git a/apps/_ex02back/multi-app-support.js b/apps/_ex02back/multi-app-support.js deleted file mode 100644 index 0ec250a9..00000000 --- a/apps/_ex02back/multi-app-support.js +++ /dev/null @@ -1,18 +0,0 @@ -const { prepareKeystoneExpressApp } = require('@core/keystone/test.utils') - -const URL_PREFIX = '/' -const NAME = 'BACK02KEYSTONE' - -async function prepareBackServer (server) {} - -async function prepareBackApp () { - const { app } = await prepareKeystoneExpressApp(require.resolve('./index')) - return app -} - -module.exports = { - NAME, - URL_PREFIX, - prepareBackApp, - prepareBackServer, -} diff --git a/apps/_ex02back/package.json b/apps/_ex02back/package.json deleted file mode 100644 index 2796a999..00000000 --- a/apps/_ex02back/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@app/ex02back", - "version": "2.0.0", - "private": true, - "scripts": { - "test": "jest --testTimeout=10000", - "dev": "nodemon --exec keystone dev", - "debug": "node inspect node_modules/.bin/keystone dev", - "build": "keystone build", - "start": "keystone start", - "makemigrations": "./../../bin/kmigrator.py makemigrations", - "migrate": "./../../bin/kmigrator.py migrate" - }, - "dependencies": { - "@keystonejs/adapter-knex": "^12.0.2", - "@keystonejs/adapter-mongoose": "^10.0.1", - "@keystonejs/adapter-prisma": "^1.1.2", - "@keystonejs/app-admin-ui": "^7.3.9", - "@keystonejs/app-graphql": "^6.1.0", - "@keystonejs/app-next": "^5.2.1", - "@keystonejs/app-static": "^5.1.2", - "@keystonejs/auth-password": "^5.1.17", - "@keystonejs/fields-wysiwyg-tinymce": "^5.3.13", - "@keystonejs/file-adapters": "^7.0.3", - "@keystonejs/keystone": "^17.1.1", - "@keystonejs/server-side-graphql-client": "^1.1.2", - "@keystonejs/utils": "^6.0.1", - "@keystonejs/fields-color": "^1.0.7", - "@keystonejs/fields-content": "^9.0.2", - "@keystonejs/fields-location-google": "^3.0.2", - "@keystonejs/list-plugins": "^7.1.4", - "date-fns": "^2.15.0", - "firebase-admin": "^9.1.1", - "jest": "^26.6.3", - "nodemon": "^2.0.7", - "validator": "^13.1.1" - }, - "repository": "https://github.com/pahaz/hackathon-boilerplate-starter-kit/tree/master/apps/_ex02back" -} diff --git a/apps/_ex02back/schema/Organization.js b/apps/_ex02back/schema/Organization.js deleted file mode 100644 index b2575f5a..00000000 --- a/apps/_ex02back/schema/Organization.js +++ /dev/null @@ -1,68 +0,0 @@ -const { Wysiwyg } = require('@keystonejs/fields-wysiwyg-tinymce') -const { LocalFileAdapter } = require('@keystonejs/file-adapters') -const { File, Text } = require('@keystonejs/fields') - -const conf = require('@core/config') -const { ...OrganizationSchemas } = require('@core/keystone/schemas/Organization') -const { Organization: BaseOrganization } = require('@core/keystone/schemas/Organization') -const { OrganizationToUserLink: BaseOrganizationToUserLink } = require('@core/keystone/schemas/Organization') -const { RegisterNewOrganizationService: BaseRegisterNewOrganizationService } = require('@core/keystone/schemas/Organization') -const { InviteNewUserToOrganizationService: BaseInviteNewUserToOrganizationService } = require('@core/keystone/schemas/Organization') - -const AVATAR_FILE_ADAPTER = new LocalFileAdapter({ - src: `${conf.MEDIA_ROOT}/orgavatars`, - path: `${conf.MEDIA_URL}/orgavatars`, -}) - -const Organization = BaseOrganization._override({ - fields: { - avatar: { type: File, adapter: AVATAR_FILE_ADAPTER }, - description: { - type: Wysiwyg, - }, - }, -}) - -const OrganizationToUserLink = BaseOrganizationToUserLink._override({ - fields: { - phone: { - type: Text, - hooks: { - resolveInput: async ({ resolvedData }) => { - return resolvedData['phone'] && resolvedData['phone'].toLowerCase().replace(/\D/g, '') - }, - }, - }, - }, -}) - -const RegisterNewOrganizationService = BaseRegisterNewOrganizationService._override({ - types: [ - { - access: true, - type: 'input RegisterNewOrganizationInput { name: String!, description: String!, avatar: Upload }', - }, - ], -}) - -const InviteNewUserToOrganizationService = BaseInviteNewUserToOrganizationService._override({ - types: [ - { - access: true, - type: 'input InviteNewUserToOrganizationInput { organization: OrganizationWhereUniqueInput!, name: String, email: String!, phone: String }', - }, - ], -}) - -InviteNewUserToOrganizationService.on('afterInviteNewUserToOrganization', ({ parent, args, context, info, extra, result }) => { - // NOTE: send invite link by email! - console.log('Fake send security email!', JSON.stringify(result)) -}) - -module.exports = { - ...OrganizationSchemas, - Organization, - OrganizationToUserLink, - RegisterNewOrganizationService, - InviteNewUserToOrganizationService, -} diff --git a/apps/_ex02back/schema/User.js b/apps/_ex02back/schema/User.js deleted file mode 100644 index 50ae06dd..00000000 --- a/apps/_ex02back/schema/User.js +++ /dev/null @@ -1,26 +0,0 @@ -const { Wysiwyg } = require('@keystonejs/fields-wysiwyg-tinymce') -const { LocalFileAdapter } = require('@keystonejs/file-adapters') -const { File } = require('@keystonejs/fields') - -const conf = require('@core/config') -const { ...UserSchemas } = require('@core/keystone/schemas/User') -const { User: BaseUser } = require('@core/keystone/schemas/User') -const { Json } = require('@core/keystone/fields') - -const AVATAR_FILE_ADAPTER = new LocalFileAdapter({ - src: `${conf.MEDIA_ROOT}/avatars`, - path: `${conf.MEDIA_URL}/avatars`, -}) - -const User = BaseUser._override({ - fields: { - avatar: { type: File, adapter: AVATAR_FILE_ADAPTER }, - meta: { type: Json }, - aboutMyself: { type: Wysiwyg }, - }, -}) - -module.exports = { - ...UserSchemas, - User, -} diff --git a/apps/_ex02front/babel.config.js b/apps/_ex02front/babel.config.js deleted file mode 100644 index f1dc37a3..00000000 --- a/apps/_ex02front/babel.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = function (api) { - api.cache(true) - return { - 'presets': ['next/babel'], - } -} diff --git a/apps/_ex02front/containers/AuthRequired.js b/apps/_ex02front/containers/AuthRequired.js deleted file mode 100644 index 0cafdf72..00000000 --- a/apps/_ex02front/containers/AuthRequired.js +++ /dev/null @@ -1,54 +0,0 @@ -/** @jsx jsx */ -import { css, jsx } from '@emotion/core' -import { Spin, Typography } from 'antd' -import { LoadingOutlined } from '@ant-design/icons' -import { useEffect } from 'react' -import Router, { useRouter } from 'next/router' -import qs from 'qs' -import { useAuth } from '@core/next/auth' -import { useIntl } from '@core/next/intl' - -import { isFunction } from '../utils/ecmascript.utils' - -function RedirectToLogin () { - const { asPath } = useRouter() - - const intl = useIntl() - const RedirectingMsg = intl.formatMessage({ id: 'Redirecting' }) - - useEffect(() => { - const clearHandle = setTimeout(() => { - Router.push('/auth/signin?' + qs.stringify({ next: asPath })) - }, 200) - return () => { - clearTimeout(clearHandle) - } - }) - return- - - - -- - - {RedirectingMsg} -} - -export function AuthRequired ({ children }) { - const auth = useAuth() - const { isAuthenticated, isLoading } = auth - - const intl = useIntl() - const SignInRequiredMsg = intl.formatMessage({ id: 'SignInRequired' }) - - if (isLoading) { - const antIcon =- return - } - - if (!isAuthenticated) { - return <> - {SignInRequiredMsg} -- > - } - - if (isFunction(children)) { - return children(auth) - } - - return children -} diff --git a/apps/_ex02front/containers/BaseLayout/BaseLayout.jsx b/apps/_ex02front/containers/BaseLayout/BaseLayout.jsx deleted file mode 100644 index 9daa6913..00000000 --- a/apps/_ex02front/containers/BaseLayout/BaseLayout.jsx +++ /dev/null @@ -1,287 +0,0 @@ -/** @jsx jsx */ -import { css, jsx } from '@emotion/core' -import { createContext, useContext, useState } from 'react' -import { ConfigProvider, Drawer, Layout, Menu, PageHeader as AntPageHeader } from 'antd' -import { DashboardOutlined } from '@ant-design/icons' -import Router from 'next/router' -import enUS from 'antd/lib/locale/en_US' -import ruRU from 'antd/lib/locale/ru_RU' - -import './antd-custom.less' -import TopMenuItems from './components/TopMenuItems' -import { useIntl } from '@core/next/intl' -import { useAntdMediaQuery } from '../../utils/mediaQuery.utils' - -const LayoutContext = createContext({}) -const useLayoutContext = () => useContext(LayoutContext) -const { Header, Sider, Content } = Layout -const { SubMenu } = Menu - -const ANT_DEFAULT_LOCALE = enUS -const ANT_LOCALES = { - ru: ruRU, - en: enUS, -} - -const DEFAULT_MENU = [ - { - path: '/', - icon: , - locale: 'menu.Home', - }, - { - path: '/auth/signin', - locale: 'menu.Auth', - hideInMenu: true, - children: [ - { - path: '/auth/signin', - locale: 'menu.SignIn', - }, - { - path: '/auth/register', - locale: 'menu.SignUp', - }, - { - path: '/auth/forgot', - locale: 'menu.ResetPassword', - }, - { - path: '/auth/change-password', - locale: 'menu.ChangePassword', - }, - ], - }, -] - -const layoutCss = css` - height: 100%; - display: flex; - align-items: stretch; -` - -const subLayoutCss = css` - width: 100%; - display: flex; - align-items: stretch; -` - -const sideMenuSiderCss = css` - z-index: 10; - box-shadow: 2px 0 6px rgba(0,21,41,.35); - min-height: 100%; -` - -const sideMenuLogoCss = css` - height: 64px; - margin: 0 24px; - cursor: pointer; - - transition: all 0.2s; - filter: brightness(10); - - .ant-layout-sider-collapsed & { - height: 48px; - margin: 8px 16px; - } -` - -const topMenuCss = css` - z-index: 9; - background: #fff; - padding: 0; - box-shadow: 2px 0 6px rgba(0,21,41,.35); - min-width: 100%; - clear: both; -` - -const topMenuLogoCss = css` - float: left; - height: 64px; - margin: 0 24px; - cursor: pointer; - - @media (max-width: 768px) { - margin: 0 12px; - border-radius: 0; - } - @media (max-width: 480px) { - margin: 0 12px; - border-radius: 0; - } -` - -const pageWrapperCss = css` - margin: 0; - padding: 0; -` - -const pageHeaderCss = css` - margin: 0 24px 24px; - padding: 24px; - background: #fff; - - @media (max-width: 768px) { - margin: 0 0 12px; - } - @media (max-width: 480px) { - margin: 0 0 12px; - } -` - -const pageContentCss = css` - margin: 24px; - padding: 24px; - background: #fff; - border-radius: 2px; - - @media (max-width: 768px) { - margin: 12px 0; - border-radius: 0; - } - @media (max-width: 480px) { - margin: 12px 0; - border-radius: 0; - } -` - -function renderMenuData (menuData, menuItemRender, localeRender, onClickMenuItem) { - return menuData.map((item) => { - if (item.hideInMenu) return null - const text = item.locale ? localeRender(item.locale) : item.name - return ( - (item.children && !item.hideChildrenInMenu) ? - onClickMenuItem(item)}> - {renderMenuData(item.children, menuItemRender, localeRender, onClickMenuItem)} - - : -onClickMenuItem(item)}> - {menuItemRender(item, text)} - - ) - }) -} - -function SideMenu ({ logoLocation, onLogoClick, menuData, menuItemRender, localeRender, onClickMenuItem, sideMenuWidth, isMobile, isSideMenuCollapsed, toggleSideMenuCollapsed }) { - const logo = - const menu = - return isMobile ? ( -- - ) : ( -- {logo}{menu} - -- {(logoLocation === 'sideMenu') ? logo : null}{menu} - - ) -} - -function BaseLayout ({ children, logoLocation = 'sideMenu', className, style, ...props }) { - // .layout { .top-menu .side-menu .page-wrapper { .page-header .page-content } } - // try to be compatible with https://github.com/ant-design/ant-design-pro-layout/blob/master/README.md#api - const intl = useIntl() - const colSize = useAntdMediaQuery() - const isMobile = (colSize === 'xs') && !props.disableMobile - console.log(isMobile, colSize) - - const localeRender = (locale) => intl.formatMessage({ id: locale }) - const menuDataRender = props.menuDataRender || (() => DEFAULT_MENU) - const menuItemRender = props.menuItemRender || ((props, item) => item) - const onLogoClick = props.onLogoClick || (() => Router.push('/')) - const onClickMenuItem = props.onClickMenuItem || ((item) => (item.children) ? null : Router.push(item.path)) - const menuData = menuDataRender() - const isMenuEmpty = !menuData || menuData.length === 0 - - const sideMenuWidth = 200 - - const [isSideMenuCollapsed, setIsSideMenuCollapsed] = useState((isMobile) ? false : true) - - function toggleSideMenuCollapsed () { - setIsSideMenuCollapsed(!isSideMenuCollapsed) - } - - // FIXES - if (isMenuEmpty) { - logoLocation = 'topMenu' - className = className + ' hided-side-menu' - } - - // TODO(pahaz): should we move locale logic from here to other place? (Like AntLocale ?) - return (- ) -} - -function PageWrapper ({ children, className, style }) { - return- -- -- - -- {logoLocation === 'topMenu' ? : null} - - {children} -- - {children} - -} - -function PageHeader ({ children, className, style, title, subTitle }) { - return- {children} - -} - -function PageContent ({ children, className, style }) { - return- {children} --} - -export default BaseLayout -export { - useLayoutContext, - PageWrapper, - PageHeader, - PageContent, -} diff --git a/apps/_ex02front/containers/BaseLayout/TopMenuOnlyLayout.jsx b/apps/_ex02front/containers/BaseLayout/TopMenuOnlyLayout.jsx deleted file mode 100644 index 82691717..00000000 --- a/apps/_ex02front/containers/BaseLayout/TopMenuOnlyLayout.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' - -import BaseLayout, { PageContent, PageWrapper } from './BaseLayout' - -function TopMenuOnlyLayout ({ children, ...props }) { - return- -} - -export default TopMenuOnlyLayout diff --git a/apps/_ex02front/containers/BaseLayout/antd-custom.less b/apps/_ex02front/containers/BaseLayout/antd-custom.less deleted file mode 100644 index fc51b3de..00000000 --- a/apps/_ex02front/containers/BaseLayout/antd-custom.less +++ /dev/null @@ -1,11 +0,0 @@ -@import '~antd/es/style/themes/default'; -//@import '~antd/es/style/themes/dark'; -//@import '~antd/es/style/themes/compact'; - -// Ant customization -// https://ant.design/docs/react/customize-theme -@primary-color: #C02428; - -// Global ANTD ALL -@import '~antd/lib/style/core/index'; -@import '~antd/lib/style/components'; diff --git a/apps/_ex02front/containers/BaseLayout/components/TopMenuItems.jsx b/apps/_ex02front/containers/BaseLayout/components/TopMenuItems.jsx deleted file mode 100644 index 52fca189..00000000 --- a/apps/_ex02front/containers/BaseLayout/components/TopMenuItems.jsx +++ /dev/null @@ -1,143 +0,0 @@ -import { Avatar, Dropdown, Menu, Spin, Tag } from 'antd' -import { LogoutOutlined, MenuUnfoldOutlined } from '@ant-design/icons' -import Router from 'next/router' -import { useAuth } from '@core/next/auth' -import { useIntl } from '@core/next/intl' -import { useOrganization } from '@core/next/organization' -import styled from '@emotion/styled' - -const TopMenuLeftWrapper = styled.div` - float: left; - height: 100%; - overflow: hidden; -` - -const TopMenuRightWrapper = styled.div` - float: right; - height: 100%; - margin-left: auto; - overflow: hidden; -` - -const TopMenuItem = styled.div` - display: inline-block; - height: 100%; - padding: 0 24px; - cursor: pointer; - transition: all 0.3s; - > i { - vertical-align: middle; - } - &:hover { - background: rgba(0, 0, 0, 0.025); - } - .avatar { - margin-right: 8px; - } - - @media (max-width: 768px) { - padding: 0 12px; - } - @media (max-width: 480px) { - .name { - display: none; - } - .avatar { - margin-right: 0; - } - .tag { - display: none; - } - .ellipsable180 { - max-width: 180px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } -` - -function goToSignin () { - Router.push('/auth/signin') -} - -function goToOrganization () { - Router.push('/organizations') -} - -function TopMenuItems ({ isMobile, isSideMenuCollapsed, toggleSideMenuCollapsed }) { - const auth = useAuth() - const org = useOrganization() - const withDropdownMenu = true - const avatarUrl = (auth.user && auth.user.avatar && auth.user.avatar.publicUrl) ? auth.user.avatar.publicUrl : 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png' - - const intl = useIntl() - const SignOutMsg = intl.formatMessage({ id: 'SignOut' }) - const SignInMsg = intl.formatMessage({ id: 'SignIn' }) - const AvatarMsg = intl.formatMessage({ id: 'Avatar' }) - const OwnerMsg = intl.formatMessage({ id: 'Owner' }) - const GuestUsernameMsg = intl.formatMessage({ id: 'baselayout.menuheader.GuestUsername' }) - - if (org && org.isLoading || auth.isLoading) { - return ( -- -{children} --- ) - } - - const menu = ( - - ) - - const avatar = ( -- - - ) - - const sigin = ( -- {auth.user ? auth.user.name : GuestUsernameMsg} - - {SignInMsg} - - ) - - const signedInItems = withDropdownMenu ? ({avatar} ) : (avatar) - const signedOutItems = (sigin) - - const organizationName = (org && org.organization) ?- : null - - const menuCollapser = isMobile ?- -- {org.organization.name}{' '} - {(org.link && org.link.role === 'owner') ? --{OwnerMsg} - : - null} -- : null - - return (<> - {menuCollapser} - {organizationName} -- - {auth.isAuthenticated ? signedInItems : signedOutItems} - - >) -} - -export default TopMenuItems diff --git a/apps/_ex02front/containers/BaseLayout/index.js b/apps/_ex02front/containers/BaseLayout/index.js deleted file mode 100644 index 4d9a9710..00000000 --- a/apps/_ex02front/containers/BaseLayout/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import BaseLayout, { PageContent, PageHeader, PageWrapper, useLayoutContext } from './BaseLayout' -import TopMenuOnlyLayout from './TopMenuOnlyLayout' - -export default BaseLayout -export { - TopMenuOnlyLayout, - useLayoutContext, - PageWrapper, PageHeader, PageContent, -} diff --git a/apps/_ex02front/containers/Chat.jsx b/apps/_ex02front/containers/Chat.jsx deleted file mode 100644 index 0aaa42e9..00000000 --- a/apps/_ex02front/containers/Chat.jsx +++ /dev/null @@ -1,240 +0,0 @@ -/** @jsx jsx */ -import { css, Global, jsx } from '@emotion/core' -import { useEffect, useRef, useState } from 'react' -import { Input } from 'antd' -import { SendOutlined } from '@ant-design/icons' -import QueueAnim from 'rc-queue-anim' -import { useImmer } from 'use-immer' -import socketIOClient from 'socket.io-client' - -const CHAT_SERVER_URL = 'http://localhost:3000/' -const CHAT_DEBUG = false - -function MessageList ({ messages, user }) { - const $messagesContainer = useRef(null) - const $endOfMessages = useRef(null) - - useEffect(() => { - // we need to wait before an animation - setTimeout(() => { - if ($endOfMessages.current) $endOfMessages.current.scrollIntoView({ behavior: 'smooth' }) - }, 320) - }, [messages]) - - function handleEndAnimation () { - $messagesContainer.current.scrollTop = 99999 - } - - const items = messages.map((msg) => { - return ( -- - ) - }) - - return ( -- {msg.message} ---- {items} - --- ) -} - -function MessageInput ({ onSendMessage }) { - const [message, setMessage] = useState('') - - function handleEnter (e) { - e.preventDefault() - if (!message) return - setMessage('') - onSendMessage(message) - } - - return ( --- ) -} - -function Chat () { - const [messages, setMessages] = useImmer([]) - const [user, setUser] = useState('unknown') - const [socket, setSocket] = useState(null) - - useEffect(() => { - if (CHAT_DEBUG) console.log('chat', 'init') - const socket = socketIOClient(CHAT_SERVER_URL) - socket.on('connect', () => { - if (CHAT_DEBUG) console.log('chat', socket.id, 'connected') - setUser(socket.id) - setSocket(socket) - }) - socket.on('chat message', (msg) => { - if (CHAT_DEBUG) console.log('chat', socket.id, 'receive', msg) - setMessages(messages => {messages.push(msg)}) - }) - return () => { - if (CHAT_DEBUG) console.log('chat', socket.id, 'destroyed') - socket.removeAllListeners() - socket.close() - setSocket(null) - } - }, []) - - function handleSendMessage (message) { - const msg = { user, message, id: `${Date.now()}-${Math.random()}` } - socket && socket.emit('chat message', msg) - if (CHAT_DEBUG) console.log('chat', (socket) ? socket.id : null, 'send', msg) - } - - return (<> -- {setMessage(e.target.value)}} value={message} - placeholder="Type something ..."/> - ----- - - >) -} - -export default Chat \ No newline at end of file diff --git a/apps/_ex02front/containers/FormBlocks.js b/apps/_ex02front/containers/FormBlocks.js deleted file mode 100644 index 18f2aa73..00000000 --- a/apps/_ex02front/containers/FormBlocks.js +++ /dev/null @@ -1,51 +0,0 @@ -import { useApolloClient } from '@core/next/apollo' -import { useEffect, useMemo, useState } from 'react' -import { Select } from 'antd' - -function SearchInput ({ search, ...props }) { - const client = useApolloClient() - const [selected, setSelected] = useState('') - const [data, setData] = useState([]) - const [value, setValue] = useState('') - const options = useMemo( - () => data.map(d => {d.text} ), - [data, value], - ) - - async function handleSearch (value) { - const data = await search(client, (selected) ? selected + ' ' + value : value) - if (data.length) setData(data) - setValue(value) - } - - function handleSelect (value, options) { - setSelected(options.children) - } - - function handleClear () { - setSelected('') - } - - useEffect(() => { - handleSearch('') - }, []) - - return -} - -export { - SearchInput, -} diff --git a/apps/_ex02front/containers/FormList.js b/apps/_ex02front/containers/FormList.js deleted file mode 100644 index bf637c35..00000000 --- a/apps/_ex02front/containers/FormList.js +++ /dev/null @@ -1,330 +0,0 @@ -import { Button, Dropdown, Form, Input, List, Menu, Modal, Popconfirm, Skeleton, Typography } from 'antd' -import { DownOutlined, PlusOutlined } from '@ant-design/icons' -import styled from '@emotion/styled' -import React, { useState } from 'react' -import { useIntl } from '@core/next/intl' -import { useMutation } from '@core/next/apollo' - -import { runMutation } from '../utils/mutations.utils' - -const identity = (x) => !!x -const NON_FIELD_ERROR_NAME = '_NON_FIELD_ERROR_' - -class ValidationError extends Error { - constructor (message, field = NON_FIELD_ERROR_NAME) { - super(message) - this.name = 'ValidationError' - this.field = field - } -} - -const SListItemForm = styled(Form)` - width: 100%; - display: flex; - flex-flow: row wrap; - justify-content: center; - align-items: stretch; - align-content: stretch; -` - -const SListItem = styled(List.Item)` - padding: 0 !important; -` - -const SListItemMeta = styled(List.Item.Meta)` - flex: 1 0 55%; - margin: 16px 24px; -` - -const SListItemExtra = styled.div` - flex: none; - margin: 16px 24px; - - & ul:first-child { - margin-top: 0; - } -` - -const SSkeleton = styled(Skeleton)` - margin: 16px 24px; -` - -const SListActionsUl = styled.ul` - margin: 10px 10px 0; - text-align: center; - - > li { - margin: 0; - } -` - -function FormList ({ dataSource, renderItem, ...extra }) { - if (!renderItem) throw new Error('renderItem prop is required') - - return- - function renderItemWrapper (item) { - const itemData = renderItem(item) - const itemMeta = { key: item.id, ...(itemData.itemMeta || {}) } - const formMeta = { layout: 'inline', ...(itemData.formMeta || {}) } - const mainBlockMeta = { key: `m${item.id}`, ...(itemData.mainBlockMeta || {}) } - const extraBlockMeta = { key: `e${item.id}`, ...(itemData.extraBlockMeta || {}) } - return
- - } -} - -function CreateFormListItemButton ({ label, ...extra }) { - return -} - -function ExtraDropdownActionsMenu ({ actions }) { - const actionsLine = actions.filter(identity) - const [popConfirmProps, setPopConfirmProps] = useState({ visible: false, title: null, icon: null }) - - function handleAction ({ key }) { - const action = actionsLine[key] - if (action.confirm) { - setPopConfirmProps({ - visible: true, - onConfirm: action.action, - onCancel: () => setPopConfirmProps({ visible: false, title: null, icon: null }), - ...action.confirm, - }) - } else { - setPopConfirmProps({ visible: false, title: null, icon: null }) - action.action() - } - } - - return- -- -- - {(itemData.actions && Array.isArray(itemData.actions)) ? - itemData.actions - .map((actionsLine, i) => { - if (!actionsLine) return null - if (!Array.isArray(actionsLine)) throw new Error('renderItem() => itemData.actions should be array of arrays') - const cleanedActionsLine = actionsLine.filter(identity) - const length = cleanedActionsLine.length - if (length === 0) return null - return -- {cleanedActionsLine - .map((action, j) => { - if (!action) return null - return - }) - .filter(identity) - : itemData.actions} -- {action} - {j !== length - 1 && } - - }) - } -- -} - -function ExpandableDescription ({ children }) { - const intl = useIntl() - const ReadMoreMsg = intl.formatMessage({ id: 'ReadMore' }) - return- {actionsLine.map((action, i) => -{action.label} )} - }> - ...- - {children} - -} - -function useCreateAndEditModalForm () { - const [visible, setIsModalVisible] = useState(false) - const [editableItem, setModalObject] = useState(null) - - function openCreateModal () { - setIsModalVisible(true) - setModalObject(null) - } - - function openEditModal (item) { - setIsModalVisible(true) - setModalObject(item) - } - - function cancelModal () { - setIsModalVisible(false) - setModalObject(null) - } - - return { visible, editableItem, openCreateModal, openEditModal, cancelModal } -} - -function CreateModalFormWithButton ({ CreateButtonLabelMsg, OnCreatedMsg, ...props }) { // eslint-disable-line no-unused-vars - // TODO(pahaz): use it somewhere as example! (and remove eslint-disable-line) - const { visible, openCreateModal, cancelModal } = useCreateAndEditModalForm() - - const intl = useIntl() - const CreateMsg = intl.formatMessage({ id: 'Create' }) - const CreatedMsg = intl.formatMessage({ id: 'Created' }) - const SaveMsg = intl.formatMessage({ id: 'Save' }) - - return <> -- - > -} - -function CRUDListBlock () { // eslint-disable-line no-unused-vars - // TODO(pahaz): inside organizations logic here (and remove eslint-disable-line) -} - -function BaseModalForm ({ action, mutation, mutationExtraVariables, mutationExtraData, mutationOptions, formValuesToMutationDataPreprocessor, formValuesToMutationDataPreprocessorContext, formInitialValues, children, onMutationCompleted, onFormValuesChange, modalExtraFooter = [], visible, cancelModal, ModalTitleMsg, ModalCancelButtonLabelMsg, ModalSaveButtonLabelMsg, ErrorToFormFieldMsgMapping, OnErrorMsg, OnCompletedMsg }) { - // TODO(pahaz): refactor all (mutation, mutationExtraVariables, mutationOptions, mutationExtraData) and remove it - if (mutationOptions) throw new Error('mutationOptions is not supported!') - if (!children) throw new Error('need to define Form.Item inside ModalForm') - if (!mutation && !action) throw new Error('need to pass mutation or action prop') - if (action && mutation) throw new Error('impossible to pass mutation and action prop') - if (action && mutationExtraVariables) throw new Error('impossible to pass action and mutationExtraVariables prop') - if (action && mutationExtraData) throw new Error('impossible to pass action and mutationExtraData prop') - if (typeof visible === 'undefined') throw new Error('need to pass visible prop') - if (typeof cancelModal === 'undefined') throw new Error('need to pass cancelModal prop') - if (!mutationExtraData) mutationExtraData = {} - if (!mutationExtraVariables) mutationExtraVariables = {} - if (typeof mutationExtraData !== 'object') throw new Error('wrong mutationExtraData prop') - if (typeof mutationExtraVariables !== 'object') throw new Error('wrong mutationExtraVariables prop') - - const [form] = Form.useForm() - const [isLoading, setIsLoading] = useState(false) - let create = null - if (!action) { - [create] = useMutation(mutation) // eslint-disable-line react-hooks/rules-of-hooks - } - - const intl = useIntl() - const CancelMsg = intl.formatMessage({ id: 'Cancel' }) - const SaveMsg = intl.formatMessage({ id: 'Save' }) - const ClientSideErrorMsg = intl.formatMessage({ id: 'ClientSideError' }) - - function handleFormSubmit (values) { - if (values.hasOwnProperty(NON_FIELD_ERROR_NAME)) delete values[NON_FIELD_ERROR_NAME] - let data - try { - data = (formValuesToMutationDataPreprocessor) ? formValuesToMutationDataPreprocessor(values, formValuesToMutationDataPreprocessorContext) : values - } catch (err) { - if (err instanceof ValidationError) { - let errors = [] - if (ErrorToFormFieldMsgMapping) { - const errorString = `${err}` - Object.keys(ErrorToFormFieldMsgMapping).forEach((msg) => { - if (errorString.includes(msg)) { - errors.push(ErrorToFormFieldMsgMapping[msg]) - } - }) - } - if (errors.length === 0) { - errors = [{ name: err.field || NON_FIELD_ERROR_NAME, errors: [String(err.message)] }] - } - form.setFields(errors) - return - } else { - form.setFields([{ name: NON_FIELD_ERROR_NAME, errors: [ClientSideErrorMsg] }]) - throw err // unknown error, rethrow it (**) - } - } - form.setFields([{ name: NON_FIELD_ERROR_NAME, errors: [] }]) - setIsLoading(true) - - const actionOrMutationProps = (!action) ? - { mutation: create, variables: { data: { ...data, ...mutationExtraData }, ...mutationExtraVariables } } : - { action: () => action({ ...data }) } - - return runMutation({ - ...actionOrMutationProps, - onCompleted: () => { - if (onMutationCompleted) onMutationCompleted() - form.resetFields() - }, - onFinally: () => { - setIsLoading(false) - cancelModal() - }, - intl, - form, - ErrorToFormFieldMsgMapping, - OnErrorMsg, - OnCompletedMsg, - }) - } - - function handleSave () { - form.submit() - } - - function handleChangeForm (changedValues, allValues) { - if (onFormValuesChange) onFormValuesChange(changedValues, allValues) - } - - return ( {ModalCancelButtonLabelMsg || CancelMsg}, - , - ]} - > - ) -} - -export { - ValidationError, - useCreateAndEditModalForm, - BaseModalForm, - CreateFormListItemButton, - ExtraDropdownActionsMenu, - ExpandableDescription, -} - -export default FormList diff --git a/apps/_ex02front/containers/FormTable.js b/apps/_ex02front/containers/FormTable.js deleted file mode 100644 index e271ad40..00000000 --- a/apps/_ex02front/containers/FormTable.js +++ /dev/null @@ -1,203 +0,0 @@ -import React, { useContext, useEffect, useMemo, useRef, useState } from 'react' -import { Form, Input, Table } from 'antd' - -const DEFAULT_ROW_CONTEXT = { editing: false } -const RowFormContext = React.createContext() -const RowContext = React.createContext() -const TableContext = React.createContext() - -function DefaultCellWrapper ({ ...props }) { - return- {children} - - -} - -function DefaultCellInner ({ children, record, rowIndex, column }) { - const inputRef = useRef() - const form = useContext(RowFormContext) - // const { editable, setRowContext } = useContext(RowContext) - const [editing, setEditing] = useState(false) - - useEffect(() => { - if (column.editable && editing && inputRef.current) { - inputRef.current.focus() - } - }, [column, editing, inputRef]) - - function toggleEdit () { - setEditing(!editing) - form.setFieldsValue({ - // GET - [column.dataIndex]: record[column.dataIndex], - }) - } - - async function handleEdited () { - try { - const values = await form.validateFields() - if (column.onEdited) { - column.onEdited(record, values) - } else { - console.warn('no column.onEdited(record, newValues) callback!', record, values) - } - toggleEdit() - } catch (errInfo) { - console.log('handleEdited() failed:', errInfo) - } - } - - if (!column.editable) return children - if (editing) { - return - - - } else { - return- {children} -- } -} - -function EditableCell ({ - record, rowIndex, column, // onCellExtraProps - children, - ...props -}) { - const { CellWrapper, CellInner } = useContext(TableContext) - - // console.log('EditableCell', rowIndex, record, column, children, props) - - let Wrapper = CellWrapper || DefaultCellWrapper - let Inner = CellInner || DefaultCellInner - - if (!record || !column) { - return{children} // placeholder time! - } - - return- -} - -function DefaultRowWrapper ({ ...props }) { - return- -} - -function DefaultRowInner ({ children, record, rowIndex }) { - const form = useContext(RowFormContext) - return -} - -function EditableRow ({ - record, rowIndex, // onRowExtraProps - children, - ...props -}) { - // Base logic: provide `rowForm` and `rowContext` - const { RowWrapper, RowInner, rowContextInitialState } = useContext(TableContext) - const [form] = Form.useForm() - const [rowContext, setRowContext] = useState(rowContextInitialState || DEFAULT_ROW_CONTEXT) - const context = useMemo(() => ({ - ...rowContextInitialState || DEFAULT_ROW_CONTEXT, - ...rowContext, - setRowContext, - }), [rowContext]) - - // console.log('EditableRow', rowIndex, record, children, props) - - let Wrapper = RowWrapper || DefaultRowWrapper - let Inner = RowInner || DefaultRowInner - - if (!record) { - return {children} // placeholder time! - } - - return ( -- - ) -} - -function EditableBody (props) { - // console.log('EditableBody', props) - return -} - -const TABLE_COMPONENTS = { - body: { - wrapper: EditableBody, - row: EditableRow, - cell: EditableCell, - }, -} - -function _addExtraCellProps (columns) { - // add: record, rowIndex, column - return columns - .map(column => { - return { - ...column, - // Set props on per cell - onCell: (record, rowIndex) => { - const baseOnCell = (column.onCell) ? column.onCell(record, rowIndex) : {} - return { - ...baseOnCell, - record, - column, - rowIndex, - } - }, - } - }) -} - -function _addExtraRowProps (columns, onRow = null) { - // add: record, rowIndex - return (record, rowIndex) => { - const baseOnRow = (onRow) ? onRow(record, rowIndex) : {} - return { - ...baseOnRow, - record, - rowIndex, - } - } -} - -function FormTable ({ columns, dataSource, pagination, onChangeFilterPaginationSort, rowContextInitialState, tableContextInitialState, RowInner, CellInner }) { - // Each row has RowContext and RowFormContext! - if (!columns) throw new Error('columns prop is required') - - const fixedColumns = useMemo(() => _addExtraCellProps(columns), [columns]) - const fixedOnRow = useMemo(() => _addExtraRowProps(columns), [columns]) - - // TODO(pahaz): add CellWrapper, RowWrapper if you know any use case?! - return- -- -- - -} - -FormTable.TableContext = TableContext -FormTable.RowFormContext = RowFormContext -FormTable.RowContext = RowContext -export default FormTable diff --git a/apps/_ex02front/containers/FormTableBlocks.js b/apps/_ex02front/containers/FormTableBlocks.js deleted file mode 100644 index 7e859b7e..00000000 --- a/apps/_ex02front/containers/FormTableBlocks.js +++ /dev/null @@ -1,313 +0,0 @@ -import React, { useContext, useEffect } from 'react' -import { Button, Form, Input, Space } from 'antd' -import { useIntl } from '@core/next/intl' -import ExcelExporterButton from './FormTableExcelImport' -import { CreateFormListItemButton, ExtraDropdownActionsMenu } from './FormList' -import FormTable from './FormTable' -import { useAuth } from '@core/next/auth' -import { useImmerReducer } from 'use-immer' -import { DeleteOutlined, QuestionCircleOutlined, SaveOutlined } from '@ant-design/icons' - -const _USE_TABLE_INITIAL_STATE = { - actions: {}, // { Create: ({values, item, form, ...}) => { ... } - // forms: {}, - data: [], - loading: false, - pagination: { - total: undefined, - current: 1, - pageSize: 20, - }, - filters: {}, - sorter: {}, -} - -function createNewGQLItem () { - return { - id: Math.random(), - isUnsavedNew: true, - } -} - -function _tableStateReducer (draft, action) { - switch (action.type) { - case 'reset': { - return _USE_TABLE_INITIAL_STATE - } - case 'set': { - const { key, value } = action - draft[key] = value - return undefined - } - case 'merge': { - const { key, value } = action - draft[key] = { ...draft[key], ...value } - return undefined - } - case 'remove': { - const { where } = action - const keys = Object.keys(where) - const indexes = [] - draft.data.forEach((x, index) => { - if (keys.every((k) => x[k] === where[k])) { - indexes.push(index) - } - }) - indexes.reverse() - for (let index of indexes) { - draft.data.splice(index, 1) - } - return undefined - } - case 'query': { - const { pagination, filters, sorter } = action - if (pagination) draft['pagination'] = { ...draft['pagination'], ...pagination } - if (filters) draft['filters'] = { ...draft['filters'], ...filters } - if (sorter) draft['sorter'] = { ...draft['sorter'], ...sorter } - return undefined - } - } -} - -function useTable () { - const [state, dispatch] = useImmerReducer(_tableStateReducer, _USE_TABLE_INITIAL_STATE) - - return { - state, - setData: (value) => dispatch({ type: 'set', key: 'data', value }), - updateFilterPaginationSort: (pagination, filters, sorter) => dispatch({ - type: 'query', - pagination, - filters, - sorter, - }), - updateActions: (value) => dispatch({ type: 'merge', key: 'actions', value }), - action: (name, args) => state.actions[name](args), - reset: () => dispatch({ type: 'reset' }), - remove: (where) => dispatch({ type: 'remove', where }), - } -} - -function _useTableRowForm () { - const form = useContext(FormTable.RowFormContext) - const { table } = useContext(FormTable.TableContext) - const { editing, loading, hidden, setRowContext } = useContext(FormTable.RowContext) - - function setEditing (value) { - return setRowContext(x => ({ ...x, editing: value })) - } - - function setLoading (value) { - return setRowContext(x => ({ ...x, loading: value })) - } - - function setHidden (value) { - return setRowContext(x => ({ ...x, hidden: value })) - } - - function action (name, args) { - table.action(name, args) - } - - function remove (where) { - table.remove(where) - } - - return { - action, remove, - form, editing, loading, hidden, - setEditing, - setLoading, - setHidden, - } -} - -function RenderActionsColumn (text, item, index) { - const { user } = useAuth() - - const intl = useIntl() - const AreYouSureMsg = intl.formatMessage({ id: 'AreYouSure' }) - const DeleteMsg = intl.formatMessage({ id: 'Delete' }) - const EditMsg = intl.formatMessage({ id: 'Edit' }) - - const { isUnsavedNew } = item - const { action, remove, form, setEditing, setLoading, editing, loading } = _useTableRowForm() - - function validateFields () { - setLoading(true) - return form.validateFields() - .then((values) => action('CreateOrUpdate', { values, item, form })) - .then(() => (isUnsavedNew) ? remove({ id: item.id }) : null) - .then(() => (isUnsavedNew) ? null : setEditing(false)) - .finally(() => setLoading(false)) - } - - function deleteRow () { - setLoading(false) - setEditing(false) - remove({ id: item.id }) - } - - return-
- {(isUnsavedNew || editing) ? - - : null} - {(isUnsavedNew) ? - - : - -} - -function toGQLSortBy (sorter) { - if (sorter) { - let { field, order } = sorter - if (field && order) { - order = order.toLowerCase() - if (order === 'asc' || order === 'ascend') order = 'ASC' - else order = 'DESC' - return `${field}_${order}` - } - } - return undefined -} - -function toGQLWhere (filters) { - const where = {} - Object.keys(filters).forEach((key) => { - const v = filters[key] - if (v && v.length === 1) { - Object.assign(where, JSON.parse(v[0])) - } else if (v && v.length >= 1) { - if (where.OR) { - // TODO(pahaz): it looks a problem in case of multiple OR - throw new Error('Multiple OR query! Does not support!') - } - Object.assign(where, { OR: v.map(JSON.parse) }) - } - }) - return where -} - -function TableCellInner ({ children, record, rowIndex, column }) { - const { editable, dataIndex, rules, normalize, editableInput } = column - const { form, editing } = _useTableRowForm() - - const intl = useIntl() - const FieldIsRequiredMsg = intl.formatMessage({ id: 'FieldIsRequired' }) - - useEffect(() => { - form.setFieldsValue({ - // TODO(pahaz): think about normalize! - [dataIndex]: (normalize) ? normalize(record[dataIndex]) : record[dataIndex], - }) - }, []) - - if (!editable || !editing) return children - const input = (editableInput) ? editableInput() : - - return, - }, - label: DeleteMsg, - action: () => action('Delete', { values: { id: item.id }, item, form }), - }, - { - label: EditMsg, - action: () => { - setEditing(true) - }, - }, - ]}/> - } - - {input} - -} - -function NewOrExportTableBlock ({ columns, table }) { - // createNewGQLItem, renderItem - const data = table.state.data - const setData = table.setData - - const intl = useIntl() - const CreateMsg = intl.formatMessage({ id: 'Create' }) - - function handleSetExportData (data) { - setData(data.map(x => { - return { ...createNewGQLItem(), ...x } - })) - } - - function handleAdd () { - setData([...data, createNewGQLItem()]) - } - - // function handleSaveAll () { - // console.log('TODO ... as child extra actions') - // } - - // console.log('RERDER! NewOrExportTableBlock', columns) - - return <> -x.importFromFile))} setExportedData={handleSetExportData}/> - - {(data.length) ? - - {/**/} - - : null} - > -} - -function ViewOrEditTableBlock ({ columns, table }) { - const data = table.state.data - const pagination = table.state.pagination - const onChangeFilterPaginationSort = table.updateFilterPaginationSort - returnx.create))} - CellInner={TableCellInner} - rowContextInitialState={{ editing: true, loading: false }} - tableContextInitialState={{ table }} - /> - -} - -export { - useTable, - RenderActionsColumn, - toGQLSortBy, - toGQLWhere, - NewOrExportTableBlock, - ViewOrEditTableBlock, -} diff --git a/apps/_ex02front/containers/FormTableExcelImport.js b/apps/_ex02front/containers/FormTableExcelImport.js deleted file mode 100644 index f39f3148..00000000 --- a/apps/_ex02front/containers/FormTableExcelImport.js +++ /dev/null @@ -1,257 +0,0 @@ -import React, { useEffect, useState } from 'react' -import XLSX from 'xlsx' -import { Button, Col, Form, Input, Progress, Row, Select, Table, Tooltip, Typography, Upload } from 'antd' -import { InboxOutlined } from '@ant-design/icons' -import { defaultValidator, fromExData, reValidateExData, toExData } from '../utils/excel.utils' -import { useIntl } from '@core/next/intl' -import { useImmer } from 'use-immer' -import styled from '@emotion/styled' -import { CreateFormListItemButton } from './FormList' - -const SHEET_JS_ACCEPT_FILES = [ - 'xlsx', 'xlsb', 'xlsm', 'xls', 'xml', 'csv', 'txt', - 'ods', 'fods', 'uos', 'sylk', 'dif', 'dbf', 'prn', - 'qpw', '123', 'wb*', 'wq*', 'html', 'htm', -].map(function (x) { return '.' + x }).join(',') - -const ErrorText = styled.div` - word-break: break-word; - background: red; -` - -const OkText = styled.div` - word-break: break-word; - background: green; -` - -const WarnText = styled.div` - word-break: break-word; - background: yellow; -` - -function MappingForm ({ columns, cols, onChangeMapping, onFinish }) { - const [form] = Form.useForm() - const [values, setValues] = useState({}) - - const intl = useIntl() - const ColumnMsg = intl.formatMessage({ id: 'containers.FormTableExcelImport.Column' }) - const SelectColumnMsg = intl.formatMessage({ id: 'containers.FormTableExcelImport.SelectColumn' }) - const NextStepButtonLabelMsg = intl.formatMessage({ id: 'containers.FormTableExcelImport.NextStepButtonLabel' }) - - function handleChanges (changedValues, allValues) { - const titleToIndex = Object.fromEntries(cols.map((col) => [col.title, col.dataIndex])) - const remapped = Object.fromEntries( - Object.entries(allValues) - .filter(([k, v]) => v) - .map(([k, v]) => [titleToIndex[v], k]), - ) - setValues(allValues) - onChangeMapping(remapped) - } - - {/* disable chrome autofill hack 2 */} - useEffect(() => { - document.querySelectorAll('.ant-select-selector input').forEach((e) => { - e.setAttribute('autocomplete', `stopDamnAutocomplete${Math.random()}`) - }) - }) - - const selectColumnComponent = ( - - ) - - return - - - {columns.map((column, index) => { - return- {selectColumnComponent} - - })} -- - - -} - -function ExcelExporterButton ({ columns, setExportedData }) { - if (!columns) throw new Error('no columns prop') - - const [step, setStep] = useState(-1) - const [tableState, setTableState] = useImmer({ data: [], cols: [], mapping: {} }) - const validators = Object.fromEntries(columns.map(column => [column.dataIndex, column.importValidator || defaultValidator])) - - const intl = useIntl() - const ImportFromFileButtonLabelMsg = intl.formatMessage({ id: 'containers.FormTableExcelImport.ImportFromFileButtonLabel' }) - const ClickOrDragImportFileTextMsg = intl.formatMessage({ id: 'containers.FormTableExcelImport.ClickOrDragImportFileText' }) - const ClickOrDragImportFileHintMsg = intl.formatMessage({ id: 'containers.FormTableExcelImport.ClickOrDragImportFileHint' }) - const Step1TextMsg = intl.formatMessage({ id: 'containers.FormTableExcelImport.Step1Text' }) - const Step2TextMsg = intl.formatMessage({ id: 'containers.FormTableExcelImport.Step2Text' }) - const Step3TextMsg = intl.formatMessage({ id: 'containers.FormTableExcelImport.Step3Text' }) - const StepHelpText = { - 1: Step1TextMsg, - 2: Step2TextMsg, - 3: Step3TextMsg, - } - - function handleFile (file/*:File*/) { - /* Boilerplate to set up FileReader */ - const reader = new FileReader() - const rABS = !!reader.readAsBinaryString - reader.onload = (e) => { - /* Parse data */ - const bstr = e.target.result - const wb = XLSX.read(bstr, { type: rABS ? 'binary' : 'array' }) - /* Get first worksheet */ - const wsname = wb.SheetNames[0] - const ws = wb.Sheets[wsname] - /* Convert array of arrays */ - const cols = makeAntdCols(ws['!ref'], { render: renderCell }) - const data = makeAntdData(ws) - setStep(2) - setTableState((draft) => { - draft.cols = cols - draft.data = data - }) - } - if (rABS) { - reader.readAsBinaryString(file) - } else { - reader.readAsArrayBuffer(file) - } - } - - function handleChangeMapping (mapping) { - setTableState(draft => { - console.log('reValidateExData', tableState.mapping, mapping, validators) - reValidateExData(draft.data, draft.mapping, mapping, validators) - draft.mapping = mapping - }) - } - - function handleFinish () { - setStep(step + 1) - setExportedData(fromExData(tableState.data, tableState.mapping)) - } - - return <> -setStep(1)} label={ImportFromFileButtonLabelMsg} - style={{ marginBottom: '16px', width: '100%', display: step === -1 ? 'block' : 'none' }}/> - {(step > 0) ? -