From 6d7eee569047939307f513607fb81004739c046e Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 13 Apr 2025 10:15:58 +0200 Subject: [PATCH 001/135] updated db schema --- docs/db/catbytes_database_schema.pdf | Bin 412177 -> 466498 bytes docs/db/schema.dbml | 7 ++++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/db/catbytes_database_schema.pdf b/docs/db/catbytes_database_schema.pdf index dd9aa84d6da01e705c1856c8a9a7d202a79f3de2..3031382e2cf1aeb90f6dddf9409b0fb7094bc6f9 100644 GIT binary patch literal 466498 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πOv7A}s3i=$Xl^7}|(bS#XHh0#$lAcw-}SQs78H0~3J zk2n;yE7iulIO6gD0#}U|tKjP_%IFXds^0Sg{>p;Th8i7hv2ws(C>tllTF|B}#ID8o zXbQ1}amp29Wx}zN(r&6M6etUq>V6Y*HlZQBEWA?bHd=*ZQWl=!Yp1M?3T1K-o0gmi zUXwQ`x*s`BRb?VE5$2V8J49b10I+BxSoM)ty3M6BX%bC!%?xWova&#ITCySxN)A~K z8%`9v8cAnbN?kY-d*}5!M2bS!7L(8lCE4geOg6h1Jb<=sLejB7Y*x}C>O&sss4YOu zg>a^!g-V7M3DP9wB-avEr!h5>YS-~~Bf1xnTgcgQr4>r3rH~S?=l*CW8MT@Zp!b`M zj7$)lmW)^xlEZ-}CnHEo$Vtde8dR=DhMJn;vtg1~e#!riEz+GoYC%}O$D+(vUq zMmUm^iOk+S*{s80X30k@lxoBM|4cG!vt7bEi`FKHO-n|y6@9)HQv17*k(Y(iQR&w1 zrDKZMSch{E~ zh1!J0WXA#4m9j5#iv=5vL09ytlX}{$=mL?K`53)ujC!K2N>Bs0s9L@L2maR7y&XDr zqsDki;%?#n8{%1)oJ9l{kyylGA0BsO{T@sl3Q{I*p%wica&zCfCbE4R%vQo3Ft?TR zR-YJ=N!en9ZPfZb`1t^7r0a;jt(jBrd^>I7%TV|-6uu0FFN3228Lf(NO@a#vTvqJO zB75lc=ZUj3bYv)e86b|rmtjr54Bgg1>^saLnrzAHWt5}<6wg*-J!!l{!@5NtB|^0! zId|LZDT+$&n^F|TA7CVkt}roJl~~%7Owp6Gc~+fm@N_Pw_lhW@MTq>I=%7VhpW$#@ zxY&v?R*0p71^ERu{0?RZ4$AR_(KzGNV6t;Q___zrznfA z$xGH$i#8gakuch3MV@M?V`BzdY;^(=Bgb^T7zunZ&(4u7rR;V)vAjA~n4zPRSX#VV zUGGUjxVLWm)Hmzmuq#_|6FZ2-l-)Wj8EcK6#L>i$x5aMsl(>&G@5EHHlBHu6;1U!h zo3=?{BfJ|%Th))>o3{0%iKDrJ7jd)br0{b8#hyc>h!{u2NFpRR=9(t=lg=PhjXrC& zu*fgHljoc(GRZb&I!=-KKhTWt|e_|KF_Mpuqm93&(r)oBg ze8E9sMk%&*Gj=g6=Bvegbv$%tVMcM&)ndNtx3(zetHpd(ck8%Xy z5p5z5GnX3l`(50`dVY@C%rD-Ct44BYi;W}JOj{XU$tqxMR)QkbWgbDrvspP$b^RWU z3HxrN+3gGFVFg7XipjOIjF-I_{K&0hF3wJrG3K@-R z_hpTlKnFJQtWcB<_bbjJ9@JUu&3RfDjLk|s_;ciNp2^qkO^>M9{ubYZ#(-E1LypwS zFQTgYM5();>pyb{3BQnaCnO8TW+fzap?QQPO4-xSpy~q zm!PNQUUF_7L}ItOsoMeAz9fqgrb+X?K?)<2*#Wfu#1D#Zyp~3mt!G#Gk!Ia&0w&^} znJQJQNmox~<=x3)Y)s?YCIGvA@hQw{--%5CEZD9S4YJo@Az`cSdQul+%H&J!3ll(L z0w_!XUc1XY57(^BhI9FSq=*VxM1@paQLz=BprMEgS*)a3%Swu7OS~4kjef7!W#6n= zWLa4h+E^2SEmC#}{J+-e$%?KllY^U*1bbf%7g5a8!91I`1lwz5CC$VPWd%>9bua)( za4cCqEW45qrg(7VdZqsjXs03|-+kBMN&6S|lA|y;FW z9I@LU;20}GCzvYq`n3*{@}L3aZmT!wHJW(NHm9;eZWZ8?RELvXr$G=Dlq!k%fa5o( zZ5wYiB|u{aGk8scjd9G3i&Y}W2 zZZ{|0EU!5yG=-y|#1*pJ>^Gs!MoZ?}(t)^{Arr8WZ{{n43Kl^HhbCfO17uik3| zWY=O6&yi%A|70XiKPQ&SBqOn|KJhwVykhXflXG0 zbkW?cJ84-kHZN%rMJtC}4ckt*(^MB*GVb+g2cqdRi5gDTyDJoBqZeD+T%*V0_4Otn z6UL?`pI$Rp3%BuZ76d^;1Joc0lx$HHbEg%G7~2unb;Vo`DN)pQCnXEU<|U=Bp{er< zT?RLmo2Is6*XBUI6^gJ)I1mx@)}3rD7@L=D2D$RAbZ^o9>l(OzQrG_=i@g!6`z)_) z^*Ljf@nUr^zZgR^y-@LbxmDO)rce02^(Ge+#-=3~=0hvWJBZ6-RUTgw`C#ONLkEP? zkfa=BkIgj94HcfH~g_VR{1Nms1p%;qd0=U_hCn>p){U`P?&vK_}Iw6>dZm|n0 zi+0x5t}7a@CkMB2?P?pX?WSdqx(S;$Z?%gN*0sULkMn(IFdfu(ERS-XtHm&qfdy6y zLw2#I+b~d@<~t)@VaOi!pxO;~O>6ag3A>itzN0xWBM5%EtSd`Cthon6k=!9K(;=^%)*Vi3UAM#TG-uG<96P+Vkxm5r9UYJ~n>l$?djmt}+-T&HDHZ zT<2L~$VM0&ehNdj&`!@sa`l5nu?>muvV|eL0Y7>0u&%@?Lu4UL8G9!TdaYPw$rq1e zX*SltuDd5UTTNK9IlHHCq-1M>+nkau+uUWOO>e}G*SyJm*O;8Bq$~OhCN0{$qJi_8 zxYbC({Z4DXl_4n>2V}}sNWn~W3n|!;ZVM@RCna7WAWChx!<{V!_u1TFt@_=p!`f+g!6*0Z&yK#H@$r8n;sCU?U zy2XZcYG7HRXhTUGidJ(%(kjKhbz8UCZ%Mq6PNy~KLPJdc8qI!gmHA$Wh%OD;r#qHdRu+VY zdVX~N>u$!eg-WFNHP1FO^Vi8iOjT}O%LLYzTZvPgH_H$|GBMleBym~}(S#d-Xxexy z)$0Tw@M7)?rPyd!db>pt&exl0ED)QNXov}%M>O7cH(f7!R2%aUOuxf+`_|lvgDCMc345%YC%ePLrz7NF)^?N#Wc}PwOD&V;h0Q% zRYv;ev~?NXfq|7CEBM;oajaOX3>c2YjT5FL8YyvhMd_((4q-+zW{gEIoT4+UiD102 z*4&yaX*(tcwy~{gsyplkZ4V}B&{Y`lCA=|#j$8F+w_or1&V)K70|kr}POOFbzA)eW zd2O*VQQ^eeB-;1rQ#i3o*C?D=b>bG2MW;}H#Hg5#@&n0Uwo+t9(r%3emRzxD!(CWy z5wqLeKzY^xwrSd!Ip}vAje4(B@3(5~R>7+2ZiD(`uqJ3z zw>zcSB$I){tT@<~nIoVFpS(W_gSp?BfDJB}GoP_op|vw${Xh{mHL zP0>`-Fp-GQ^V-`i)A3gUV`Cf2w9(jYHE80gpVL;8%~;uG04L-|z1buBCMFu!I|js| z&g5InS&KPqF=s92ti_y_37T6jS#^7n z(wr5CEPQrQ3p88E^RYmGA5mySy;5^TOMG3{Jmr{Fe6?UqS$ti~%r!@R?a1x{=2SzY zX41y(>4kWfZEFem$R2^r1&5OE3PspxztkME!F;stWMjeDyksLA``KjkcL*|XlU05Z zH++j^o2=l=t#zj>8_1?5ET+LkHqD`|SXU)0b43T@-Jr!f9A8h@CA!fkURJNw#Lc+Z z9Zt(yiDmTq3Td$q>i$em{VtKon0hKC!Jf*_~{^t8`0x7rIdE5xWsX;_ z|8kkrv;XvO1wo{Dw{g6F>*8Y2;bL15Y}dPTyn5sA&mV9;^>Gj!y5yc*y}Gnm<=z%e z)oGLW(>E4>zCFwDN8G(nxL3P<>(1RH{C59A(D^wCj(&G@@y3$7v#e>q(BoE)WP9nK z{u?fK2Z0ojJi70GaIDGYalX$F?|#FRkZ+mqE{;;?T|p#u<4TV5tpwljs~Y@Ks+St2 z#nQ#n-%8xKzTp#LFZ?v*Y5H4^%G=5hmv@v8m7kIPt#1@W+oEdpZuDOCMf9~`H2Iy` zIm4awcVB7WYx}sL{_cNUTB|l!+g;mNJ6Jmc0kmo_)?TiiuHCNvTKii~p5}ZH%pBNx zV9$ZtfrAH*9GE}w)xq#!<>12ycO2Y(@bQED4nBAA?7?piE*<_~|8w5wUz$}4+U5H) z-&gsb;Cq_yTYOXh;8L(z6WoH&X6f6~B@35wFMKo?pFz3e@p;6;rQ8FbCGhzW_Te=2Y(_)26PU=Jx@QdX4ML2A5D1YuRf1tE-OJyXUkAv zeM)CbZ z!7=F<&Ao$qqf|b;cV_2oxqNiz;oeMH8uEyMw>u6G-2ao3=4fRX$2)iA(%t^FbSb`A zkXv^o86|QzIqGk@Hx~%amAB3<2hTO@jUFD5%{{w!RSq4L(eQMmb>>W~ar)|+mtH!v z@Y-uKl=;pSJtwRzcmyUZndRLxRmny2DeVd?l1JES?~ojK%>xXME9N_wgQX`4!@O#aBHjw@~6Nbx`QgE!LI6AQXz;3?b z_dI#Y=fq{{5C{G%?!@s?p{ADmDd`e92D9ZT6s$`@_mW$OZ!6BiXZoUlhf{^DTt@CF zM9cqKBoiUYQb^%wKCe-!Xgm@Z7$gJA=8o<*2&6 zH<(pAcy7nwSZh0fWC&4hKXmlaLm@rIZOvmE~S|6BHG@>kC5=T-^TO{(blJ zh3dhdr6qpi`-;0YJZgXb?bIKK-n=}#H@sMs+Gz^TU%J~Ok*5Bk7?D}|*QWY{rjlOp zFg4j5Xm1x9oYB^hTI|_7dqf&UT0)wlx4TjaJ@`LdM&~&(JLEW!tlxTr!wngJ20aeD ze&sOy|Mh^3PrGU|%ntuA9+sO=?h`ulg^N01eXqm%_UiH@!SY;n|NhGC^3Gk4KI+Pu z#QMy^aQh=WDD9rTE$9g1QldHv={S0#^r1`)hW}E&uWH*Op>F;ko8A94r`O!C)Nxno z$g*35JHG9SN+sC($lUUyzB@Ff zJ7`zfvwQb}A(CB(*wNifXE-n)h9A0YzvDFL``%TqKN1o6mV5To|M?%di-*J0rI&t= zzWD3^{U3R@eB>}s4j*)n-Ld3zalfQNK7crAgz`g`{lTLQO%GLON~OJ(<(=C~%Vpu= z!o;@|4;)6Y{m}M9%s^p)BO0Oj!ZefrLL0YVyY|d;2S0rN>(5KuJ~flcA=9ahdiP0 zgl?1#K{lh-zWdxOrxrf?-jnWAFLig7MCRQ03Cs*9wO{`HFNe6i^&^K*X0}$of9&YZQ(W`E<%yHe z)sBcr=l=iYzuCL;`pHv!=RZ63>6hi}Ki~Q7x4->*`)4=EFe^Sm4|-f$@$q2!;m4}W z(ag@BTSxqA$g5rr1vjwa?ONrET$MhIX66oF1JGLz*r%L+=PR9AI*BrxW6!*Q@WpqZ zm72N#?x^{{9NyH_j-xWjT-8CU96YRSC#se!%b}7M$wWW^_|90;p!G^Z_8&g`(o4@i zHyA|1;Q5cvN~3i8Jx1St$BoqP$u6J&Bf*ow&f{BmJw8`ip7nG>3IObcA$PQs373FF z?@Wj`&$Z4yx&Qbxr=>>6`93^&&VlB7*TXX2ROilJia7H>C*#n$*~LDg2r0=B?@xpG z9YO8*&K=m92u=tpaa(*aa|6!$+KX&q*zv|rB#walmAiL3cp}c zM}udM?|t&Arw>OvcOKXZgE}y~{CH*gp&950VWRmQEC96a9S+@&W*qUjVS;+J_O_I$ z_A(C6c)msccSLM+|E;CBWgzyw;jyg;F8%e4RPo4G8I2>+r?~%S_&@(U!yoQKXzbG; zU-Cn>yQh>fc;}ffzyA755sJ7gJ%AeRmLBkMWqIZy2JY=!rA|~t#|~)(l|M$6(2{#1 z{$KtxDN*wOv8{)sWfH|p5PUMB7KfgZ7y!PZEZeM>zcJ|L|>@2LJ1S z;qGOR-+lKl|7~|yh7#9Dx!4okp|p=8!y)tRs4Q=PXnAhi!Gp7N%e!|y_E-XgvCK1+ zbr5zOD^Wx;bX8`(+#t0XafjRg>fh4=c>lJtmr?d?sY$E9Dit{%zQXaR%N&IFCGgEJ z_doy6{L!1wa{X$h$+7uCgX1UeS3WJ-elIk&~x$ZTeT1UIwNgdYM6f z5&R{1{P;r;?Krw8ifWI|F3&~2_2C6jp%)ZAUt%CSs?-2q_R^OiaqqsceD~Tv{R^4T zy(V~B3c&mS!<9m008~kxaO7MQ^d3k*H=>r@Obia7_g@qGGpLuur zbAIxFRBk`@%(;`TlP?}U@;*9;Qs5CN%{6ldI(SNoUfsR>k%u9M05Z_o98ZzqcTj$Y zPZ0}MViBpJ*V-*J7(}k^&wlj8Q&$?&)*e46FO^R4i~q0hKBOFV$1Co)fP3FRb9a8b za@jSV`x*74vj|c2=e@Afap=*_AU}@x3fL!lf$E14@&Pn=Q5K4 z?XQ0mj(I#DR=6I#bJUM!o>pIVsXapqPf#8{W$scIofytmf^8+x;^d5vW0%k6F^7zABD?vFbZP_wc zU7iteDvCMkkpV=<5QY(#uRE3wlzdC_d+Z6n|J8pRHbN=JXXz*Yw^UV%voi`Q!&5@P zm8YJpM!}=oA70)T_#$WtcD8oz3*+kQRL&2v7>s<4c0c{(mf45eUPwN4WQm+FA3OW_ zp4Se(`uWL6Uyj?Lf90#^rH_3WiZHlWlD$7S%W-DQvCpJ2ERD_N_r4`*^6N6xV@YPy zsD%Dmd|ioGJPP8WR4fict&mckuU^eO83dIYU;X8dvu_50pgBHn!OpQoe{V%17akoS zs~1qA>+iYGfO3=|kBFi>Eiz(9e40s{pG3Jeq&C@@f9 zpuj+ZfdT^s1_}%m7$`7MV4%RjI$=O99-239BD?(esen7>x*TVo3)-6IZ)yj*tllZN z!~`Z!!!LsKd|!>O2A^uGE8&jd1GTK24k{-X%xa;!WVE^p%!H+W!2LYv(GFOA&wVYhzkt)XndSA*id=kE_ zUXW{raYb{k#i^YotkMb|#Ts@r3WHGZyvHJ(L9`^T&ebVReMPB7DJ6be&!_eHiyrs8 zccLeQU*&hHt2A^;VV>5U-_>-k6zAR3ND3#AZUpxdPn-JIVZ{_rCDF_)!LN=olpded z6zBAKN^^f(TjyluaPWNP#oz#x(orfAN=bbMw)+?A@l@kzCvgm4c4<8AjDA%V(u*US z5xPl!wI;kQ_hOpKI}d4x^znO29YP_guj1@0t*uguP)qy`1pxo!v=ZOdoPQYRPdeUQ z>FD~ROGCP`H+xRNr(`7b*>`*OOLks42UrEmW({b+(K z50exBn%>!mcMYjPZt{Qs-@zxeLBml^^sUyWDo6D~`eLj_xa)eeKPpFZE$FyY?)6^H z_l4l`I6p^?aa*kPYvx+Ox`QE>DTC0d!?JZXdpY=Bt>L%h+#OFz{Bm!TlYaug)Hc2% zoO6XMc&hf`nsC?jm9~Olh_94(+P{T=EB6!bEp$BO;#%VO6z)6OYb|H8T;2;+=zhdm zTwfjGbA$;C$&izrN)xH9fBo&zFB>l;C%`MUJJK^eD&wW_ELf{}C+7 zDSgkAYfd`lh&p~o&kiZ!uyyadT{lh!*W>Ed@ZMV#`iw5{N_ z@(rV?hIg`9X*cosvm~$>u4E! z)C^08--f>BXnJ|36Fe3_OXTLtv@3q1^;?d-vsh)Tl%>)L?u_ssr_}SXM3TngR&&_l za6a=Cy^j77k1s*RafsR9f|B+(a`)*4YkW1=Ju#M*$NDEu7V`Q263E&do+u4Q`qsO6 zx5^zo#--GpO0Fg2T&;H0QBqn_pzRs8WBg8h-f}` zq~!C`TQt8P&MTz91xJu1;_`4-syGqXD7qOO)R9;6-sMW}=thrM>`_?X zuFgI+%y*ZsOL7$L$GR+TzK<&}IU&EaC%EI| z@SPLUc7?w^_@%7%r^R`1>sP^LedoRMAb2mlLhrm1ysfp7T&3vzGf!~%b!~U1azyju zNzfkR=3sDpi{7t18SIySt{A?o&tKK!VLhJJqsz(75DpL9b8GOXzI7$K6u7(h%G-Uf z_wxk*TwuF9*W;FqborL9gygCFrC4wh`Kx;Uq8{53PH^Bbh|hW#=U;r^KH{$AE_^pA z!7-$+9Q}v4F32|?fd%WDew%wsYq!VsE+?mJkA?0P#yv3eyQW;OIGTIg_?{BKJ#l()n`YBSn4Qte$le*d-~F{RiclW`$~Q@It!Mo zKlJzV>@&!{PY1VWj|Jx{mpvCdN51b0J-dI+xw>Vy;AZv>IacPNp2z&?^$67dtmbx8 z>+q?TGH$WFxstp8LfjH-J&#)KzE@w+ns2O}%1bFT!B4|0sqLukR=WD;KIulBaKfu} zYo~uFIqGV!rDqT41M$2d9-)_Y5V#$WEs0;(^!i@rctuCmx*nerSy;J0tM_6~JGzoS zrSH$o-V2mUO1Cxjvx(y&xpx06Un$*@Fz+1W%*_SkUkq+v`|tOhzmMOZ`POT@_UWB{ z%nJ1zzc+Kxm1#!D9XEURIXimhF_iO3oL+16aryP2uUAf!^RZrU)uWEjqk0Ao2fAz~ zaOwO}%QMQ!-?mQ~J`<+w%i>VU(vaVE0hbQe#kLeKrV`o-4IG8J* z59?C`0vKr*_3n#$?CJ4yJs#KNX+08Ui8omrV7$*Q^Zr4->yBsi`mCAm8S`#BSEr$O zy~p*krc&QwJtie$m&VJ*`t*=e#vODKt>`;X5g+Tlx*pv~c?UUhyZ%Fc} zC;6V^d*(q(r|&vF&3oFH-0>B?enp?2%{lI_oR!j5ZU#S$^Bd+Tce&G&r*K@>REd=B z{@ps0;m@{8;kxRIl>QUlk$0lI`u-lc_YBjQM_4Sb`AKOcqK8J+l4%VemtW9yzs8T` z6O_VIne-7kRf1WjM_XClH5Y8-_>insX6@#VU}vz4^`v`PgZ60fn5?a4mGM)onBO1N zf&;<9;E=>C2#y4-q79y9$=`Fq^T7+jJmsnf4Pdu|cF+mBK`-bBgW$#BrQqe@mEcvD zxE~Kr1Sf-2!RcTjI1{`coDJRx-elqXx!~>Ko#5Tzz2N=eeDFbVA^0%(DEK(|B=|J= zEciV5BKR`+iu!*OES6MHq!^I8Pw*|NjisbKR??SdOI!4;^P)J1l4^3}Un`!!4KC{Q zOL|;dG418d(<_-fQU|s644-@#TnoMrZUonZn|kk7@I!DrSPt$4_kz2@kHJrR_vhf( z;FsXH;P=3V2$FpM7W^^HC4rmLTxnaWT6(DTa7pzklBTr1w4=1Mq%+Y{P}*JEQ`%c9 z5dq?{(&I#dc(U|VX(G7x`rZpv-DQ! zTHE_4(v8y1(yh|%lFqJ5%cVP|yQO<^J|&$cmVPSzOsuY7 zOTU%=TKc_Y;xT?BKIg}&6Tb0}@KyZ&gVbfc+Jh-XDGbAM7=<$-u?@m4;nuJk&V}1J zJ`_G2J`!#ZcZ55`UE%I$udyj>W6Up$&@TqWLxIe6g2f~Blq44SOaCjs<8a@*~ z8~!C!ExzDCd_GkBQA~BA>QCZd);T;Ds@-4TR=rHv)azE*4m)92!g=X$0(ylng)fJ% zgsBZ-h6)TjA~S zhj2N(6W$H)g+GQrg+GVCgujNrg?|ly5B~`NCL>_+{qy1gQKnQ5%Vi>e&Je3|w!Edh zwOlRF5eKwdeu&tWkCe9)4|HdFS9y1NPkC?o(eh*E$IDNYpDaIB-dEmVu9Xjz4-zl* z>GI+7k@8XEhCW;VOZmC-^W_)H^W|gZdbv??9D1TV~sQhvH zlk%tK&&r>dzbJoM{;K?S`J3`$`P=fv@}=@p`EvP6`D*!E`MdJ>S z%FE?D<-6s3L_6xHJsz_Xiu~^dNg`0dOUg}dNO({+86DQYSDq{V00*Y zIyxL3iH=6kM9)TliJps|k6wu82~t&$8c{Q9MeV2)b)z0pDhAPu(M!?G(JRrb(QDCh z;?SIoPDQ7qh3HK5dUQ5=BYHD>D>@gw9lfIwci)fBM;}BNq7S2wqK~6bqEDmGqR)xL z`(^YMv3S3U7Nc*Yiv*TjiY^mWQi4o=M}W!e(T(V4bSt_Y{SYk^vE^=bFZwb1Df&73 zCHgh`E&6Npd-TqS=gx(v&d=Vvd97YQQTON5TW|fiw0!5{t>q7J zF5S8tUF1+XeQ}ZODwo{pG=4%cyZ}`$(bLa4lH^Xb+&AmCwcka8(9My-(Oi?TDp0C z@#dv#7c1u$FW$SmRJpDvpX8)JpQ@a5)xNH$=$usVI)}#P9M0mcl0#(!3ZUDcShG**Jk5{KcB2za4+B0Q*=QZ=&m%- zhpvI{xd!^s*WjM7!H2E}_w+RL;qtYcS7z?XvHD@M{MCEOU$Yy_b03e&HTP5E z`qRYqFQeZ$q}P2)-V*Wu;N^;L+*O#s>ZdF(mp>x(yT zENa18gZ8Y47k$Q~GVo?DT;(bffHQ9`-oCvE!Qc4y(qj1bz3`oT;V0K1MJ+-2-nH`i ztG8x8xOU~nV)?@2y;)zH@_SdWl~3_~@6I&`=EPu|YLeN^_gb~kWT)ys%w&gFw69_V*pzXxCjQZr6MMz9Y7Fec*q)4%)6e=G3mA z^7j|~`HVX^+%VK`c(@G@x8dP7Jluwd+wgE39&W?KZFsnzF|->VZo|WEoc8cM{Dz0$ z^zfS=z8?eFRLG@odiYHbzv|u}hu`+_{czE4 z`}(&%{I-YR_VC*te%r%ud-!b+zwP0-J^YS`+wpKa9&X3O?f828L8IOAa62Au$HVP- zxE&9-j%w-qkHyU>26)Ra~Cn=?$V{LNLx$Su3Wu) zb=%#m$O!KG&fMi|KgQSF?ogJS{((=**9(LpQXHVB1|GVRVcm40Kr|+((@2;os zuBY#=r|+)sS6xrvT~FU#Pv2cn-(64NT~FWL7@nu|uBY>!hu`z?dmetz!|!?ct`Y0) zo`>J_@Lgka-^1^D_&pE5=i&D}{GNy3^YHr~e&56Id-#1{ufET>@AK{ZeEUA%zR$Ps z^X>b52R@&HuLm-OkZXJ3@i_4H82EY&JiLL2H}LQV9^Syi8+dpF4{zY%`Ds%7gs;a5 z5C4RRf5O8*;o+a~@K1R7Cp`QU9{vdr|AdEs!oxq|;h*sEPk8tzJswYb_EZj%(mv_opY-rgdibY2{8JwODPR9n9{wp0|CEP+ z%ELe9;h*yGPkH#KJp5B0{wZJoQy%^)5C4>hf7-+Mow@BhbNjT1f7-)8?cuvlUT>fF z@K1aAr#<}B9{y<$|FnmH+QUEX;h*;K7d-q0U;hPP{{;_!!NXth@E3gj7d-q04}ZbK zU-0l3Jp2U@f5F3F@bDKre9zR{XFU8f9{w2*|BQ!!#=}43;h*vF&v^J}Jp3~r{uvM7 zG0}S4Gtu@L58pA_I=eP%|LS<=*m2C9`wo7`%Y_}s%(?I2cf4HKam<|i4t~cmbACJc z9WNJl>aPBsx`W@TyZW=$r-Se1!;Y5^J8oQTEV%y3S@FYJ@xxim$64{iS@FYJ%g0&q z!&&jeS!1Ag z@Lm7pw}*FX8~;k*9XSaAK5v#-DFpZxaqcm0#!zW%O%^4r(n^-q5L`n&$g zZ(o1cKl$zJ@A_wB!SzqhzW%O%^4r(n^-q5L`p1K?>z~~B@Lm7pw}*FX8~ z;k*9XSaAK5vxo2cC%-*>*FX8~;k*9HZ;yZ1Kl$zP@A@adJ^o$)!1Ag_;>xY zvEceAXOC~!ANlR^?fN6XeSKYjY}QYl+&R(@{QvEJOLtRA*WS5FvSlx08-t-~;MiOZ z*w*D7$!ch@d=WP7HneRF4dx=-@&#ncEy-=5o9@h$Nmi0UCYfZAACXzs$|P%LWs*V0 zndEQe*_R_7U*M(RZ@rW619VQ+uG&?5*REZcQ?9Dbb5G2=RzqPt+oZ7G79LGQ4x#ZX z2ZeR+U|vY^<BJZwe2gX%$Al#h zg=LPxqcFvrMCWxlzXM^1@k04E@i@OWtdETp%Z+5f9a9*S-~%wl zQ?bJF0tcdhRc-7PYj5q1R~r#LueSNk-w?@ZnXTlr(%ZoGm`*Z1F#_v%sniwz+J^aBNoC>JBZfN>-$B=bX~Mdm9#p{zUF2@Ux~$xUb#*d;qnpf1cWC9C13ETavTmnUa0%TgSFzwYQm-dO zi9MKFCZR7bOo#*~3(caX5;E_GtBqFX-GI5DgUkIYT<#*^au)$tx(HwAcfQW=e4XF< zI=}OEe&_4_&e!>!uk$-!=Xbu&?|hx#`8vPzb$;jT{La_;ov-sdU*~te&hLDk-}ySf z^L2jb>-^5w`JJ!xJ74E_zRvG_=?V)IvJM27%L|vw3zx?cxIB)*<#D7iA?rYJc^rew z<2YO%$Kdj~0+&??F3TG(%NZ`q87|8iuF{qDAHYga)_V#Qvfcw%^OtoVz?!eD?*P{P zWqk**=I>3kHwTsW=4iv}o=&9Nn`4TGF!$8=Da@lU^?M4-0Fu^OVOnQ}X`Kb8lc`SV z=W3~3-4DhOtWans!4ljUiw3q(g@`xCMFT9vef^a3^;62%PbptNrF{LA^7T{7*H0;5 zKc#&Al=Agc%GXaRUq7XM{gm?cQ_9y*CSO09{K;(lhf=LNvR!@9#5iwqDQCn} zzKEAx;gLLnz&VyTgy+iMPK|kH59LW7<(a&~v-l`y#-qF$PjY9k@WeT1{>z9Iq(QMng*`-uJX#R)NT+8K%xxJiDevzEjsoe3|oO zLd1Os;?0-2;)3Yu5BRIfJ!d=2PC zdkYo@Q(ZdJXk3?!suK$GKH6K>Y1(Ov{rbGk7H@$}?3krJQvzAoN85sI?1F7Ujxh)W zbAlJaInK-Kj~cFdSN(hjzx%Hzuql^>-L@>QVCTH16;&Wr5=6 zpxv!_UAO@zrGD({bvDG04aRb+kyF4Lshp}4$UXo$;j&>@Z8z`F7R;!@r`a{nn4Tz> zxOP2XB2XOXYnw7LJB-UXjVCdk#IcZd}llMLL@@^ZO*!A1Mp6V3#P1ECI-}9DHg8L9SCG7om zu@|}sw+`PUq^aT*Q3<<`DOH~L0&gLV_657xi%q#Tkg|?F*Q5Bq2^tNA?!gws2vSwC zqnJXg_`iw0#U*0{z6x;a2q(UXW?d-`9mB-}FgB5288|iU^Bw`$^#3;YazDiX`|t(4 z2b>RMM|U_?5FakvL5yYKQ<=7zJ2ylpNc&gv-bPL?_;pdv8r*f{zQg>#MOYOwaKU#= zCWKo7mdjZ0;jSWui?mb<%B{itl^ACmxGrQ%95;|s;5U#F)o>j++gx8#mO3Q%BbTZQ z?Ds5j7kl7|AIiIeH^CL8ISz9QbVx3nOmzuR5oyDr!qqDBc8{S%WpAaYa!~q_rsjy= z>|=lVK0?Eq64sv*cpn7^RE8>if!-ZVy>qCXtsF>#dn}nMYrND@f$>SR7vb9g&OW#! zsk4duo`OU!%bj8I<1JQC9;o%%^0qd}?B~fmAq%8_( z2k8df6tNGV>YLiaDz_#V97~H6eJh}i{)C*0;O#rOft-T&Sx0F|OLh@Ynn5*FhD$Aa z6LNhB&gYt`M7m#=CMbK*P8N_S=~xA4DoUUtJ&>O7YSQL{0^WAq$ndcaHoxT zz@+vQgiuc=(x*N{>Z6sMr{In#7SsSF(pTj!H_2)1TbN@Nq zpcJ3O`D>*)Dyvp)e2&V|bYEWLqiO$&434J$5~&{hjqt;PIk4bvG zjoxVoy$JQ^()L^J$Dk8g82u+O(fFbFw^t|V@-U+Vw?*I|yv?7&`&WNz2Ejw8yl_(r zuFSznF3JhVyJ$AVr_3U_lR@s3@IxF#B%oSmAjY&YD|N44tOVyWS7Y**H?Ckx+uYbAHJw7Ff0CsQsq`ZW~du(h4y zZk!S?zOOe*JF}IIstby)JGy`*$o#!~Jr90LLlpL~h8+pmJ?372Jh{jri+j7f<I)l`PHy;===z#7OZLRF8o zg{G43R(C0V@EmLoxlnUp%^59Vv*1*zzE<1bZB%Pux#~A6-O?ScYDY&NhnJ(~_@j zmP-WY>$FK2_!+cEVu7IHR4xtGFYeCp=evvTuPL+$Dh)N`Wa8KY#78HJ^%4~Yt?Mw` ztj z+Wmz(zktf{%n;RZsFIVPsN^OSL>){mRUcQjsXue6n@7cdLJOuVK%x$}S+e1oK;_np zl?D~44xJ$7T6huWBo*RBhmVw>NsP4y5CL052oHyGd`wOoyVPaFCro$YY z<@XzK@O$<<1~3k%Ar-6mMdNyw;jX8KLHG7;?KH@2HGX2o&xMIV4&N#jyN39~f3eacBRcKO9!izFq-1vhJ)fRD}_0LZo zUBn^6EDqHn)!_$+qB?N|su#zbu7Bzv3=WNSI&nUnv4SHQ$R+$lPSOP7@N>buaF6U; z9)2$VNX2(M+*9rachJ2G1xUN!xp&+tch~*k{^?TeC!T@Gz{!Er1Ma}!z}10~ftQ2U zV07@rVDI3m!EXjH4Bi}^8GJohy7E8%3)169HeiWSGNMfLFEW^!$1{aT4SB=Y{6WUB z<%nqxLQL2~#u4H5Ps;PmC*^72h?&Uq>SyF>LY`gDBj+a^Yk3`(CixG`lHd!*g}aAk zNwCV(a8J7ZuFLXFp&u4`Hr(GoB})oFB+ut6&z=MFTt6UB63z`gWI~qYIgV!%PXW)N zAI~5Ug;{?{fJK%vn?K5w9-iKkC2L%Ffg{%6G!ep{Jpiw<0NH8>2w)(L7509^QqruL=50Li{}iUCwPeUE#?x1n?WZ1l#+u`f ziP${;A5tHQXoa}g3g1C+$-FFQ=U~jb9Sym| z^6BK9wGwm}e0~n+=;-{k{m#PaY5P5&R9km8tbUwUE8*nYj=gG~3wIHqNEtMLHr411 zx__zy{7GOGJ2>(K{s4y~$X;lo(r}?x>~Yp^5BG3fH!h9$#;#oHxN!2M(a}+J;ZI{=oYhB# z+l{vr(nGPnESDR!yHR0Hf(Anz)PIu_}q_N3gD|J%Q!uHx3IG~QO{qn9_W*MGjO?ne6n<9F}( z!RexS551pee`BP#6No4c9Et^qhO#1|RN=U=<-9?Yh!fzw-%tzUiRAR))vLbO3%&zO z=OllsfNW1RYU!MYtaQtdtlQsvHuL-4M{m5bH*>E5veo-OEf8SS{xD1f?)fV;;{8H1 zlKbfqyz#pQliF6mm-7om+%F>TiPyS~T1Wii#b~T{vhU0pVUeWEro~|!etOXA>Fx!i zFzgMcdj@(gkrrjaswfZ`w+{3yk3Rf-F&^zPX|{7ae*Ve3Z$56?q)9j`(1*)HzQrXX z*KR!+aX(I&TGEjpsCJGMXN&Ds2(uq|&YT_ZJcsMX+PgbyXH?Hqr01;Tr%#<45IM48 z_RB2gerWl?h-K}727V;gQ5tFSdyD5KO&^Z^{k+#s%43ecaO(U2_dox8LAph}4qg4d znZ(r0Y9FTF+@-l(6j6ORrb>TCxrgg)hR072c8-tZCb-8o}_DrBOKe*^%Dsr%#`!ONzq3 zp&z^($-c|^oA+kyM{l0vct`0uZpY(}L3&DPtWDPuA(a!iqcw}I&I7`CavmyqlEgsG z9|F^I{jdKxwYoYrT`W2}jyo?Y=HBw-eE#wBJzgvOh=4u{MgHBm)ac}Rd*3%5(OOKa zMhGmTSMgb?Nc;PuQQM>H5P}77_sW(0(A{M-NuBsaaazby&L?QVj<-+qjkoX)k+!eL z-Y(x`E@=VyI_u^Y?M;*+!_XTJ9{=H zz+fzIj^P-@(ai-~5Yxe&Mn@`&-32=M5%E8MYAjsnZlh_F{BCw#SbpD21Nu<$apBqU zMo7XIzYY2R&2NWuBe$OY zMsfJv7{TlE$mJ{NZlteYd;F1lkWq=UvynNdzUnV^% zyu)kFOfJ8C@#bbQ&>$!`Gf4dke%fT}?>=xxV|9^qsN0tY@)Gky)NAzow{gcf({rNM zX;6m`>2SUNG;s2QMSWoQTq7SD$HPz*7le|eVMyw=J`A-XSL(X3X6PMyB=qb1s|3DE z;Hw0_O5m#mzDnS$1inh(s|3DE;Hw0_O5m#mzDnS$1inh(s|3DE;Hw0_O5m#mzDnS$ z1pYx1;J;~b-uT-GHVE+Zz}VxzdLSGosfQWoY0Nk7!GhX?H9&ti;lGnC@cVn1-?VKcQ)jopgbr?4tpAYGk4M0u(2V3g^9c- zo?x(ZpJDi6XpG)$wu!^`qZgZeEB|n|B99mkUUFmV@?Uh|kG@2XY)p!MZ2AkvAH~@5 zP_&cMz<{p7>5NFz)G+g!WJv%6a_D5g9QYcw>Ce*mao7UmF01M5^TrB%D;&0BpH;J? z(Z~ez&IXOFUy$@8V^AI(7Vs^2l3H@4+=9Afcg22hjE3;!uUBFmU9JO=zj$TsYsN)q zi~ceOpK$p!(2jsQyAmhH0;8G&sf;%iYxc7&*KL?G3t>MMEs;2f3P*Hy3^70W)NV6I z@!S>(!JIB)VFdRV(g@;6@!L?gb4E_`<-Ek~uDqXdd9$h^ND}pzWJtarRkHtVu<=Prh3Syy)*Zm{}jLU4P0jFC3PPF(-ePF zdKwNn#*e-a66097VcnO!sol0@*DT+P(}sQ*kNp4-$y`iU%BZ_w<$_RWK>BCu84pnD zqy932T@A5#u#bVuVMPB7b}iTwNYvy?#!=@Zx3MYotkMG#?WAZv@!8Z@9y^=kAFu+= zU@jI2mrG0e^C-&UGWLr>Z5e%5XylAp#!mEskyX7%8pbvv!)VpN81tfSHrHolh&e<| zM8;&|&|-YX744A}5-RD~ALQH?N*h&uqkN=iIm@{3HK!bSm^X%mZgd$CFy$ANncbbH zJefnGt*I#Q9g%4C7mVy8xaYc9%$b735IYuZ_G4f7l4W%%SlGJl}HtMClu0$QWwNQAwhs#wBX&9Jc`(Wbxd_^O{@^y&XaS)2x$Doe4k1jQ$FT{$kJR zj;sS;2Ann=vIWDu*oy_COYqHOR@sSXiDs90h9M&ZG9p>A%k2<5U9l&uIh%)^408!V z$OLcwhg3`S$oMRBWX^^nI0MS{lMeJJa7B-p$7QDUl%=~LsTwOWHqMG2*GvU3{2cCo z59porPKe_?Zz9z-7VFXs!FmiXoDCV2)hg6zZe%| z)C&Wqu-2Fq;#>E32j?pzzPyD;>gQ;#Mk%CE8{!fS#g>@X8|LDi zwoFH7ztUzg!n1BV%m;GdT;R6d)>0ePw^84qBmDyBcAyQRMah?}Bkx2O6tbuq8{dRQ87=`qAIP^Du?;t<%5eGc7 zu}qY=u%ESsc)=5l-J&ZIX;Bp3Efl5ymL4!D562J{!ehE^54dbw^bT^D^jwDJuwSV1 z_XTStV@RDNHAAAwO(@J~P&bSxUu(I;d>w(0nM;QB5n~ATzBCSTuEw#xW?5xCWF117 zj79-P=b&~O7)9dEFZnn|RsJH6ZJ~v(;+Z$ja!To&&f?;_`L-#Hoxh=-J!Od!RXk{g z``~H?7t;-~EJq=Sr>vudp>-9O^56C&A%1@w&p1$fe-&SP9jW6l^9x^;LVY1E`BV;p z&S7NS3;B^jtDZmS6H1@M2=_`{XWY;J4WF#(9QkSp_s)OSFB=5E=FMYH+*;Pl8TtF?IC(H-+>{^^YdbIFrrC}0ZwOBs&&^=o`YBiDut(p~e%5tl z%`Qt11LXJmU<}2YL=cKq}6&m6pRWLo^fzX&G;%11B!DjS1(uabIIO7wefZYhw<={H{cLj6&qRQ4omV za^Yt_a)D@IFZ|8uw64A0Ctvs>-hzkI9>XIt!H>!#zko-94seMel(NO!LB5XVNMsP` zJG(Lid~_g;a;D;Z?@gfO4qqx_aOdyoCle#8d3{4Mtk zW6L|w{^?`Wqe*>1uG8AOSK#tmD%@b^OD&#fzi@AI9qm8|&tfE;Vko>izoGF3qn~}N z{H9!~@A$IZU$d5q4#{f%_neE&$K;zq=Io+#GaM^#`P+fu3ty8QD_;o%>_3`=VBY;w zF1clW&;99K=$&JKj$>zzzUl89v1e2n(KOaA!mIQ6=AiaYai}hw3ZlL)s5P23GA zxN#M$4hg<6UZVW3 zjiSk4+AIMo`xiX8wis9@uU)V&X7bvC*&a@9^7l_Tz)tKhM9LF>i1o;agltSjKgmgi$qq!IhzOLGB)|DEt+dj#B75;{z^z{muAj$ORb` z_NVbjGnXK5xMqjhX~xYi^MuJ?G=yXJn7!snT#ihB!#ri4Hv3J}JY$|UzcJ6@4F7ra z0?zTfIE6fD@;5wVUdH+UtL8QHx;bRtFmIZ-%wcoH9OZk9Qf8WQJkvKbX4V|Tf#vVa z+vXkfd-JY&&m1@NCeNuDYto!Dr_F+SpD*HgVE$mvnLnEI=0o$5`4eF<)`GccE}4(b zW%G&o)co0eW`O!R9jzNmR{q!wO)Mxy2cvv$3Ov>_)st?Ps zOv|!t%dsL>)QVYcR=X9qI;>8-yQ~vdx7B0yS|_bO>y&lc>bK5VXRU8=kJq=>dFz68 z(Q>T;YtXu6UAC@RSFLN-b!*7FVciV0%(`U_2Yzy`kuY?e-x{?NLAaIVG&287Tb|`x z8M@4dLvfvrb=$gQeQ(`mm{(|nlz+>$#;v?HVNF_7*0fcy?prg~to6Y9!J4yvwC1gc z)+6gDYr$HymaNCtvh~DzihIkRSxK2wdS$(~iq@*NX02N#Ys1>KwybUIjrGi8jeGAR zxT!5>x7qD>-0r|FcyYT6H@9`$J-7|;q}^wqvQOLn_8I%E{f&Lj{??K}4O_Fem) zJ#Odi3479>vZw8Weczt3XYB{}5B8k>qdjjwv>(|&*$eify<|VOm+dF^Q~PK8nf=^e zv0vCP?N|0|yJ)Z4YxcTbvN!Bad&}Oo-`H>Mvb|$h?5e$M|6|gDD`|AlKI@g@*&X9A%x#`?; zhMf^-)JZr=C*`Ca&+(m%lXb?Nobw$nD7)i)@7#6nIpa>=nQ$hZDQDU#IQN|yXV!V( z{NT(vKRWY#-{eoug0tu>Igg!X=ZW*w`Pq5qJa<-bYvN1XMETk&I;+l_vyKbZHgKid zmb2}=!S!lo+_F@0s?M(Si&JyzPQ%%Aes%Vpcg}m~gY%p7yYtbRe>^v5O)jwgrG#3! zP2{KAVWO{AU#r$i5~D_v5^5wVp~jICFoKldDX!J3l~_^SXr9f9MT_jj)XrQ?jhA&y zwk*PRthM~kB&^3cYj{Pc*bsGeU7RW8U2Mdrl}Jg6Obd|`JMn3xE3VPazVTAi8&i{Z zF}0Hxot7L+>~!335$_0@M04&FBb#_}!INqT9yZ!KWRwjc-|Jls3E(U+S0LRA6VOOIzkrfI^MKs2rp{%J(tLg($0C_oSbV zE(qnCIE#W?XQyMaC6bPM%g<#_CB=GJDmf8dlEe*moF%HD2362wsh~ZnpvPK*JuSgw zDZw5)k;im-dSs8@@yB8Q<9lH!rj`+7YHqLNNsF(J{gD6XkpF$lAAKez{J>898LCEA z`9sqiDOW3-^?06m5wv2{8_Q2~P^?&Dx8ot3xe|Z170S zmY6|oFIHf}zFgWac24isx4{5ov`wp-M7EH~L7fkGwn@JQcpO5GKiny8N_IWlAv21M z8DUz^1nk{M?iXQPiWyA9khjuvo*~Kxy~xrQd=436jQmjC-7P}lcUISn){lGE{GRo6 z8>+}0SdX^tg{^92aeH&8XfGA_V#^MWf6jZ@Fk?E}Aeo zF=^G8L(FAsZ;9GGOnbfNOZbPozqc@1%SKzD^wCvr{u=emgai zfjB2-FH`f>=(DFrpJA9(!^NVWr$(P)6H~**A|LK4SD1|w!c3+#P*2VKd1~IzQ)ADb zn)&n8*t4g`o;@}8?1{1GgqKvhV#o}z(oHJeq|!|)UF{gWq;!g2QjAV|Nj9K{^lUf{ zE*oJ?cquV9?WNdo8(=ov23O0SQu-;iG?-HQYKhPbED@%Zz8HZ-dZjN$AOS0VF#-u# z>5CD_1T1cf{KN<(V5KicAOS0V9T#wEyW(s4#TX>;RPJI760nv(t@OnxB*K-x7>9%z z1*Pw4`Nc>i!nOQ5TzFzE65&c;j70+0@{6%Zz*>G!>5JuGgsc2LrSB_UU+MZH137S9-qE%V~Y*lF=t@%`&<#&J*rMqx^*&*rU&P6}ISAppbo;|IvmgB$dp(m}CXhKz5kM3+&_8O&V9Tt!)>+Pj?YiRMn zlC;@EJDK83jODy!bhlJT7vW6r)wtEClG4snNofag#b97Em1!^48<=J^O6%<~YREPX zwmR8pqWJ3_Wam-+oou~O0j8TuWa5FLVIKUtdEgA${A0z!9dkXQuo`uNJU@iZL9QCO zTs3gHO5k#pq;R>v6i~Vnz+AO(xoY8RdMzRhb|}8)BV7sbxN6~Y)xzbfEli7@`a(hM z*uxdoPKsUbL`scvrq#Y|TJ6iG13To>1d`Lz1d`LjFI-W@R3ROCxK*uHB2|7r=Jy`G z9WrUn;_)OA`Z;Y^zUq~)a7N*b>bKg@#!YmRKBM}bQT@(nzmie?&ZvH8RKGK--x<~K zjOuqLpr?ACQ9T#S+6gbK^t0N&vPxen5%Ef2EN}x>`cj#ImA)971g!MM;3QzBFNP@r zD}6DD3Bw&qUkqXb)^e$#OHT||B3$zo!)mT8_NZ&nx}B($A|L^GZLja?C6Jyvk7w zaVEUHmN&2T^GZLj^_W-sYQff13$~tqoO$|j=BWi+Pe0N;wP5S11zS%o*m`Qg)>8|% zo?5W=w6S<XtE&sHZe_G40otjrr{VS;c6;%EOm489yUr_lMRQ?5(e?jG6Q27^>zI37qPdibs zp!B7aO~Bp@w=Z8iM_=cKzRnAM>CAyA^nINd`U#=$>%7pH&K!6`-`9Deuk%7*`%z!# zhrZ4aeVrfrIzRMve(1}%n4FgO376#qm*oSONr7!Ih zu+o?I2}AcvU)m>Nr7!Ihu+o?I30Ub%`vk1?rF{Zc`qDlDD}8C7fR(0aGrIC0ftYkqx36ce=Qp_K@tAWcR zgPS`^LwkB6M^@wU;A1qAI3_H4C@gaf9)&61Bs#Cd`5g#5j2FtUiO2c1VSQ|*SZ*W( zPb%=FPcZ9bW;;QsV+xb4yD&`gRIG5kz=7yrRU13S+FN_$)kXx*t8ISsH$-w;W-Ixu z^fqukrjtxhjKK0!`N#Z>z@FcPb&;0g`TX(;T{&&Sx>nhQb;&D$qq8G*lsNi;osL=7 zEcLU}vA=t^rK}v#H5+_JDK0A=`#W{*R3lhHHKMwSFPygw^eJDjs9fNi15eC0m(3WC z%?exHp`}&HiWF{oax4tw8R(*=AzuGrke9mH*FzBpnC_%1#llB369CyT(8@mHO(r;o z+e1n|)=@p&3FRmJbO+^yg=W$ot+GiLajVKQO<32{gDN z4y}B1K*wfF*6p+kE}h**uF_tDcr^ST{k-%i3S+rC_=G}0$(aO9VF!ytC zxnG6LT?Ab2BH&6F;p_a)*ZG~V^E+SXcfQW=e4XF!uk$-!=Xbu& z?|hx#`8vPzb$;jT{La_;ov-sdU*~te&hLDk-}ySf^L2jb>-^5w`JFFaVPQhnf#7m^ z;c|K5@;CyQ$1%7(jua+j9SAOuV{myKh0Eg@Tpm~8vI@avdBbHn!(}#Q)Xv%qvR)d~GvEtRYL!T6DYy+KJFEWwSjXz-~hM7%LB8ek#r z>!*~jpHjYlO8NRJ@l&_yszJ5yi`YGk>r;Nx{e@LV6c;Y zF{V-8h!O=jlz(}nO>!@9#5iwqDQCn}zKEAx;gLLnz&VyTgy+iMPK|kH59LW7<(a&~ zv-l`y#-qF$PjY9k z@WeT1{>z9Iq(QMng*`-uJX#R)NT+8K%xxJi zDevzEjsoe3|oOLd1Os;?>zT*lOHqf2V%ah})Vob_~bu zs~YYYUG^FEM+`bU!t}7aHdJmiX7uttX^Cq$E`*@CF&Kh6jLRXY)A-in z^<%&CGsQcDebHpT4fStAX4vg4{%^2%dQko6;8z_nzvVsEf`jwd(3zG&@vxcku=(-% zkXiCi8!ta<&it>LdOvD9{ja6}`wX@R<@x(k|5MD<&tWh1rm=;6)1NEnUz5t0l;W?H z_{+=JD$P+naIvfY9m-jQ5&M9{#r7;b3m9luuA^>nP0@ z&HPXV%NKrZWCqT|zU0<(%1Nj$gGZ31c_ zeFbq1Jfk=xQsP?MM3~5X71Ugu6d_r)XuDhoBtycankb>nJIMPeW;johv0xs>n??E} z^4ms^o5*t(?izAz)#@VRNfTQ2L6oWGkw#GJ8cuMy;By1-`*4FYQ;m?`xQs<5T15(% zb&V*J1WBi)%*27qT2n(f)i}X#AY;<*I!k&DF`_}F=>d-chr`Xj;IOu(ft}$0K^oHj zpj<&O(Zafoay-QU`#8}PqzLd^RDyH?Mk`P1DRxnZ#JTjyE=~(2_z{gKIUG&r;#^P- zY2QF5R8|+7P3K^?5JqQWT%1D+bS=v}`H;5S_ci zs6O%k{`G%-@#+K_oHjb>eu&_N6#^(s;QgyV0SKMr;uEbVPFPu-4~}rrT#V;A5!}%r z_rGygfuG}2{H9FxACaK2al^$OAt)vO-GBJ-KF@1tqJm%eL>c)d55WN8896Hp89DJ% zWgXk!O2r-WRaf7{lW8~MlIPByz(~96kL3b?l7W`FcCuhUTiI^7qmOFUwZ&4y-Po?I z*GhGK)UB1=)zap6C7w*V*v;2ah{MRVdz=z4zOOe*JF}IIstby)d(`AT!XDPJ?SKr> zUw=HgI7C>Bd%L^k(hfF8x(OJZch^fB$nMAD?mR5ByQ6uOr8GRdj`;1y`(dPNVJyAh zExF(bDU!kxtlO9ByT!FqtytMC#qZ#c`iZ-DHt_E*iRDmqDj}&>H`ca_HNy5%ck%Xt zO@K#0CMkf=KuUlN18D)W40r;JG2jbOWFRZR8Ur~2))^=YP-0+JfDH!L81TV7I9L}T z$v{Z}W;7#=`e};ECM1R#%?hJFGny4feP%Q(jQY%IRv7h}w=8&rkW?BZE~?sQRjlpR zYN!RSf36@BJU-u>DzaPMrS!pbNE^M7Hgd&{7O)|2s#IUAZSOX!wK&64=?`~Hl}o9_ zVzE+};KFvflyaec33ow4CMK%y+~>oYWX>H<<$O1phA)}Oa?=~R4l5gNG+ zy&9cu6wBLd`O0RwL}0#7J8>C5K1w_ggnZIaz2fc+eBs_i=9w2);c(N42`2feyB@p7llc7{TaD4I*QA(AeRLLYlKq5B=PZlJYdXAE3 z#()EUBtuG-O1ywn(ZrXk_h+ZjG~LmlK|*>@AjSk_y12L6paw~ZBo3-%RKbCsf%~EZ zE&gaP5$YoXD#J5FR1u-N$b6zMnoJONFtt>DT-l~(4+RnVf1QETPQ*F|} z01sX)&mIg*=Ektg@<>=V4erBfK{t0858E-ZL-8P>;GEHYf{55JtbkZ0_p|gwI zA22YZ`7Knd=w(Sa&~5Y7AW7;+z?A#ky)&Jjnk@MEs|5bwJUN+36ehh?ZlW-i$$6=K eVk&X>OHjBq)*HoIgS7*Uc^N-`<;wKK!v6;dRap%H literal 412177 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 diff --git a/docs/db/schema.dbml b/docs/db/schema.dbml index f1e2561..db05fff 100644 --- a/docs/db/schema.dbml +++ b/docs/db/schema.dbml @@ -19,6 +19,7 @@ Table users { id integer [primary key] name varchar [not null] email email [unique, not null] + contact varchar about text img_link varchar discord_nickname varchar [unique, not null] @@ -44,6 +45,8 @@ Table mentors { about text status enum created_at timestamp + last_modified_by integer [ref: > users.id] + last_modified_at timestamp } // tags are created dynamically from related table Table projects { @@ -145,4 +148,6 @@ Ref: users.id <> users_learnings.user_id Ref: study_buddy.id <> users_learnings.learning_entity_id // links relationships: other resources to be added -Ref: links.entity_id > projects.id \ No newline at end of file +Ref: links.entity_id > projects.id + +Ref: "study_buddy"."id" < "study_buddy"."created_by" \ No newline at end of file From 858ae9bad83fc01cc64b54e9efd085f88a8c5a97 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 13 Apr 2025 10:55:05 +0200 Subject: [PATCH 002/135] updated swagger --- docs/catbytes_api_swagger.json | 294 ++++++++++++++++++--------------- docs/catbytes_api_swagger.yaml | 226 +++++++++++++------------ 2 files changed, 279 insertions(+), 241 deletions(-) diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index 980b4fc..80a4bc2 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -27,14 +27,14 @@ "name": "users", "description": "User related operations" }, - { - "name": "projects", - "description": "Projects and collaborations" - }, { "name": "mentors", "description": "Mentor related operations" }, + { + "name": "projects", + "description": "Projects and collaborations" + }, { "name": "discord integration", "description": "Discord related operations" @@ -1558,41 +1558,28 @@ } } }, - "/mentor": { + "/mentors": { "post": { "tags": [ "mentors" ], - "summary": "Create mentor - in request state", - "description": "Creates new mentor entity and puts in 'in-review' state", + "summary": "Create a new mentor application", + "description": "Creates a new mentor entity in 'pending' status. Available to all logged-in users. If user already has a mentor profile, creation is not allowed.\n", "operationId": "createMentor", "requestBody": { - "description": "Data user inserted on registration", + "required": true, + "description": "Data provided by the user during application", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "Jane Doe" - }, - "experience": { - "type": "string", - "example": "I'm working with 3D for 3 years already" - }, - "contact_info": { - "type": "string", - "example": "janedoe in discord" - } - } + "$ref": "#/components/schemas/MentorCreateRequest" } } } }, "responses": { - "200": { - "description": "Successful operation", + "201": { + "description": "Mentor application created", "content": { "application/json": { "schema": { @@ -1606,6 +1593,12 @@ } } } + }, + "400": { + "description": "User already has a mentor profile" + }, + "403": { + "description": "User is not authenticated" } } }, @@ -1613,74 +1606,57 @@ "tags": [ "mentors" ], - "summary": "Get mentors - filtered", - "description": "This endpoint is used for displaying both active and in-review mentors", + "summary": "Get mentor list", + "description": "Returns a list of mentor applications. When filtering by 'pending' or 'rejected', or not filtering at all, user must have elevated (admin) role. Returns user_id and some aggregated data from user profile\n", "operationId": "getMentors", "parameters": [ { - "name": "type", + "name": "status", "in": "query", - "description": "The type of mentor entities to fetch. **active** OR **inreview**.", - "required": true, + "description": "Status of mentors to retrieve (e.g. active, pending, rejected)", + "required": false, "schema": { - "type": "string" + "type": "string", + "enum": [ + "active", + "pending", + "rejected", + "inactive" + ] } } ], "responses": { "200": { - "description": "successful operation", + "description": "List of mentors", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "applications": { - "type": "array", - "items": { - "type": "object" - }, - "example": [ - { - "mentor_id": 1, - "name": "Jane Doe", - "description": "I'm working with 3D for 3 years already" - }, - { - "mentor_id": 2, - "name": "Mary Roe", - "description": "I love frontend" - } - ] - } - } + "$ref": "#/components/schemas/MentorListResponse" } } } }, - "400": { - "description": "Invalid query parameter" - }, "403": { - "description": "User is not allowed to view mentors list (might happen for GET mentors under review)" + "description": "User is not allowed to view this list" } } } }, - "/mentor/{mentor_id}": { + "/mentors/{mentor_id}": { "get": { "tags": [ "mentors" ], - "summary": "Get mentor by id", - "description": "", + "summary": "Get mentor by ID", + "description": "Returns full mentor data if user is logged in. Pending/rejected mentors require admin role.\n", "operationId": "getMentorById", "parameters": [ { "name": "mentor_id", "in": "path", - "description": "The id of mentor that needs to be fetched. Use 42 for testing. ", "required": true, + "description": "ID of the mentor to retrieve", "schema": { "type": "integer" } @@ -1688,49 +1664,17 @@ ], "responses": { "200": { - "description": "Successful operation", + "description": "Mentor details", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 42 - }, - "name": { - "type": "string", - "example": "Jane Doe" - }, - "contact": { - "type": "string", - "example": "janedoe in discord" - }, - "experience": { - "type": "string", - "example": "I have an extended IT experience" - }, - "about": { - "type": "string", - "example": "I can help with ..." - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "nodejs", - "fullstack" - ] - } - } + "$ref": "#/components/schemas/MentorDetails" } } } }, - "400": { - "description": "Invalid mentor id supplied" + "403": { + "description": "Access denied" }, "404": { "description": "Mentor not found" @@ -1741,14 +1685,13 @@ "tags": [ "mentors" ], - "summary": "Update mentor", - "description": "This can only be done by the logged in user with mentor role. Any changes that are performed in mentor's card come here.", + "summary": "Update mentor profile", + "description": "Only the user who owns this mentor card can update it. Only selected fields can be changed.\n", "operationId": "updateMentor", "parameters": [ { "name": "mentor_id", "in": "path", - "description": "id of mentor that need to be updated", "required": true, "schema": { "type": "integer" @@ -1756,52 +1699,38 @@ } ], "requestBody": { - "description": "Update an existing mentor info", + "required": true, "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "experience": { - "type": "string", - "example": "I have a very extended IT experience" - }, - "about": { - "type": "string", - "example": "I can pass my experience to you" - } - } + "$ref": "#/components/schemas/MentorUpdateRequest" } } } }, "responses": { "200": { - "description": "Successful operation" - }, - "400": { - "description": "Invalid mentor id supplied" + "description": "Mentor profile updated" }, "403": { - "description": "You don't have the rights to edit this mentor" + "description": "Not authorized" }, "404": { "description": "Mentor not found" } } }, - "post": { + "patch": { "tags": [ "mentors" ], "summary": "Update mentor status", - "description": "This can only be done by the logged in user with moderator role. This endpoint is used to change state (for now) of mentor entity", - "operationId": "changeMentor", + "description": "Only users with admin or mentor role can change mentor status.\n", + "operationId": "patchMentorStatus", "parameters": [ { "name": "mentor_id", "in": "path", - "description": "id of mentor that need to be changed", "required": true, "schema": { "type": "integer" @@ -1809,7 +1738,7 @@ } ], "requestBody": { - "description": "Update an existing mentor entity status (enum(\"active\", \\\"inactive\", \\\"blocked\"))", + "required": true, "content": { "application/json": { "schema": { @@ -1817,7 +1746,11 @@ "properties": { "status": { "type": "string", - "example": "active" + "enum": [ + "active", + "rejected", + "inactive" + ] } } } @@ -1826,13 +1759,10 @@ }, "responses": { "200": { - "description": "Successful operation" - }, - "400": { - "description": "Invalid mentor id or status supplied" + "description": "Status updated" }, "403": { - "description": "You don't have the rights to edit this mentor" + "description": "Not authorized" }, "404": { "description": "Mentor not found" @@ -1843,14 +1773,13 @@ "tags": [ "mentors" ], - "summary": "Delete mentor entity", - "description": "This can only be done by the logged in user with moderator or mentor role.", + "summary": "Delete mentor profile", + "description": "Only the owner can delete their mentor card. Deletes the entity and removes mentor role from user.\n", "operationId": "deleteMentor", "parameters": [ { "name": "mentor_id", "in": "path", - "description": "The id of mentor that needs to be deleted", "required": true, "schema": { "type": "integer" @@ -1859,13 +1788,10 @@ ], "responses": { "200": { - "description": "Successful operation" - }, - "400": { - "description": "Invalid mentor_id supplied" + "description": "Mentor deleted" }, "403": { - "description": "You don't have the rights to edit this mentor" + "description": "Not authorized" }, "404": { "description": "Mentor not found" @@ -1969,5 +1895,101 @@ } } } + }, + "components": { + "schemas": { + "MentorCreateRequest": { + "type": "object", + "properties": { + "contact": { + "type": "string" + }, + "about": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "MentorUpdateRequest": { + "type": "object", + "properties": { + "contact": { + "type": "string" + }, + "about": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "MentorListResponse": { + "type": "object", + "properties": { + "mentors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MentorSummary" + } + } + } + }, + "MentorSummary": { + "type": "object", + "properties": { + "mentor_id": { + "type": "integer" + }, + "user_id": { + "type": "integer" + }, + "about": { + "type": "string" + }, + "status": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "img_link": { + "type": "string" + } + } + }, + "MentorDetails": { + "allOf": [ + { + "$ref": "#/components/schemas/MentorSummary" + }, + { + "type": "object", + "properties": { + "contact": { + "type": "string" + }, + "discord_nickname": { + "type": "string" + } + } + } + ] + } + } } } \ No newline at end of file diff --git a/docs/catbytes_api_swagger.yaml b/docs/catbytes_api_swagger.yaml index e185701..eef88e3 100644 --- a/docs/catbytes_api_swagger.yaml +++ b/docs/catbytes_api_swagger.yaml @@ -25,10 +25,10 @@ tags: description: Available roles - name: users description: User related operations - - name: projects - description: Projects and collaborations - name: mentors description: Mentor related operations + - name: projects + description: Projects and collaborations - name: discord integration description: Discord related operations paths: @@ -1037,32 +1037,26 @@ paths: description: You're not allowed to edit this resource '404': description: Application not found. - /mentor: + + /mentors: post: tags: - mentors - summary: Create mentor - in request state - description: Creates new mentor entity and puts in 'in-review' state + summary: Create a new mentor application + description: > + Creates a new mentor entity in 'pending' status. Available to all logged-in users. + If user already has a mentor profile, creation is not allowed. operationId: createMentor requestBody: - description: Data user inserted on registration + required: true + description: Data provided by the user during application content: application/json: schema: - type: object - properties: - name: - type: string - example: Jane Doe - experience: - type: string - example: I'm working with 3D for 3 years already - contact_info: - type: string - example: janedoe in discord + $ref: '#/components/schemas/MentorCreateRequest' responses: - '200': - description: Successful operation + '201': + description: Mentor application created content: application/json: schema: @@ -1071,140 +1065,102 @@ paths: id: type: integer example: 42 + '400': + description: User already has a mentor profile + '403': + description: User is not authenticated get: tags: - mentors - summary: Get mentors - filtered - description: This endpoint is used for displaying both active and in-review mentors + summary: Get mentor list + description: > + Returns a list of mentor applications. When filtering by 'pending' or 'rejected', or not filtering at all, + user must have elevated (admin) role. Returns user_id and some aggregated data from user profile operationId: getMentors parameters: - - name: type + - name: status in: query - description: The type of mentor entities to fetch. **active** OR **inreview**. - required: true + description: Status of mentors to retrieve (e.g. active, pending, rejected) + required: false schema: type: string + enum: [active, pending, rejected, inactive] responses: '200': - description: successful operation + description: List of mentors content: application/json: schema: - type: object - properties: - applications: - type: array - items: - type: object - example: - - mentor_id: 1 - name: Jane Doe - description: I'm working with 3D for 3 years already - - mentor_id: 2 - name: Mary Roe - description: I love frontend - '400': - description: Invalid query parameter + $ref: '#/components/schemas/MentorListResponse' '403': - description: User is not allowed to view mentors list (might happen for GET mentors under review) - /mentor/{mentor_id}: + description: User is not allowed to view this list + /mentors/{mentor_id}: get: tags: - mentors - summary: Get mentor by id - description: '' + summary: Get mentor by ID + description: > + Returns full mentor data if user is logged in. Pending/rejected mentors require admin role. operationId: getMentorById parameters: - name: mentor_id in: path - description: 'The id of mentor that needs to be fetched. Use 42 for testing. ' required: true + description: ID of the mentor to retrieve schema: type: integer responses: '200': - description: Successful operation + description: Mentor details content: application/json: schema: - type: object - properties: - id: - type: integer - example: 42 - name: - type: string - example: Jane Doe - contact: - type: string - example: janedoe in discord - experience: - type: string - example: I have an extended IT experience - about: - type: string - example: I can help with ... - tags: - type: array - items: - type: string - example: - - nodejs - - fullstack - '400': - description: Invalid mentor id supplied + $ref: '#/components/schemas/MentorDetails' + '403': + description: Access denied '404': description: Mentor not found put: tags: - mentors - summary: Update mentor - description: This can only be done by the logged in user with mentor role. Any changes that are performed in mentor's card come here. + summary: Update mentor profile + description: > + Only the user who owns this mentor card can update it. Only selected fields can be changed. operationId: updateMentor parameters: - name: mentor_id in: path - description: id of mentor that need to be updated required: true schema: type: integer requestBody: - description: Update an existing mentor info + required: true content: application/json: schema: - type: object - properties: - experience: - type: string - example: I have a very extended IT experience - about: - type: string - example: I can pass my experience to you + $ref: '#/components/schemas/MentorUpdateRequest' responses: '200': - description: Successful operation - '400': - description: Invalid mentor id supplied + description: Mentor profile updated '403': - description: You don't have the rights to edit this mentor + description: Not authorized '404': description: Mentor not found - post: + patch: tags: - mentors summary: Update mentor status - description: This can only be done by the logged in user with moderator role. This endpoint is used to change state (for now) of mentor entity - operationId: changeMentor + description: > + Only users with admin or mentor role can change mentor status. + operationId: patchMentorStatus parameters: - name: mentor_id in: path - description: id of mentor that need to be changed required: true schema: type: integer requestBody: - description: Update an existing mentor entity status (enum("active", \"inactive", \"blocked")) + required: true content: application/json: schema: @@ -1212,38 +1168,35 @@ paths: properties: status: type: string - example: active + enum: [active, rejected, inactive] responses: '200': - description: Successful operation - '400': - description: Invalid mentor id or status supplied + description: Status updated '403': - description: You don't have the rights to edit this mentor + description: Not authorized '404': description: Mentor not found delete: tags: - mentors - summary: Delete mentor entity - description: This can only be done by the logged in user with moderator or mentor role. + summary: Delete mentor profile + description: > + Only the owner can delete their mentor card. Deletes the entity and removes mentor role from user. operationId: deleteMentor parameters: - name: mentor_id in: path - description: The id of mentor that needs to be deleted required: true schema: type: integer responses: '200': - description: Successful operation - '400': - description: Invalid mentor_id supplied + description: Mentor deleted '403': - description: You don't have the rights to edit this mentor + description: Not authorized '404': description: Mentor not found + /generate-invite: post: tags: @@ -1306,3 +1259,66 @@ paths: type: string description: Error message. example: Failed to generate invite + +components: + schemas: + MentorCreateRequest: + type: object + properties: + contact: + type: string + about: + type: string + tags: + type: array + items: + type: string + MentorUpdateRequest: + type: object + properties: + contact: + type: string + about: + type: string + tags: + type: array + items: + type: string + + MentorListResponse: + type: object + properties: + mentors: + type: array + items: + $ref: '#/components/schemas/MentorSummary' + + MentorSummary: + type: object + properties: + mentor_id: + type: integer + user_id: + type: integer + about: + type: string + status: + type: string + tags: + type: array + items: + type: string + name: # these are aggregated from users table + type: string + img_link: # these are aggregated from users table + type: string + + MentorDetails: + allOf: + - $ref: '#/components/schemas/MentorSummary' + - type: object + properties: + contact: + type: string + discord_nickname: # this is aggregated from user + type: string \ No newline at end of file From 683e237ae118157a954a0123df435c6e034f1723 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Tue, 15 Apr 2025 09:09:20 +0200 Subject: [PATCH 003/135] updated deployment documentation, added docs on knex migrations --- docs/how-tos/aws_ec2_deployment.md | 44 +++++++++++++++++++++-------- docs/how-tos/knex_migrations.md | 45 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 docs/how-tos/knex_migrations.md diff --git a/docs/how-tos/aws_ec2_deployment.md b/docs/how-tos/aws_ec2_deployment.md index ab3b17a..35e9d15 100644 --- a/docs/how-tos/aws_ec2_deployment.md +++ b/docs/how-tos/aws_ec2_deployment.md @@ -1,9 +1,11 @@ # AWS EC2 Deployment -This app is deployed on the AWS EC2 Linux VM. -To manage the VM you should sign in to AWS Console either as a root user or as an IAM user with proper access policies. +## General info + +This app is deployed on the AWS EC2 Linux VM.
+We're using Docker to run the server. -PM2 is used on the VM in order to run the server. +To manage the VM you should sign in to AWS Console either as a root user or as an IAM user with proper access policies. If you want to connect to the VM from your machine, please use the key provided to you. @@ -19,15 +21,35 @@ If you have the .pem permission error, run the following command: chmod 400 /path/to/your-key.pem ``` +## Deploying the changes + +We're assuming the changes are already tested locally, reviewed in a PR and are merged into `develop` or `main` branch. + +We're using Docker with `docker-compose` to deploy the app. +
⚠️ **NB!** +We have two `docker-compose` files, one per environment. Make sure you use the correct one. In order to deploy updated app, follow the steps below: -
    -
  • Commit & push your updated code
  • -
  • SSH into AWS EC2
  • -
  • PM2 stop the server
  • -
  • Git pull code changes
  • -
  • Run server `node server.js` to test the changes are applied
  • -
  • PM2 start the server
  • -
+* SSH into AWS EC2 +* `cd` into webplatform-backend +* `git pull` the changes (verify you're at the relevant branch first) +* run `npm install` if there are new dependencies +* apply database migrations if needed (see `knex_migrations.md`) +* run this command to stop and remove running instance of the app in docker: +```bash +docker-compose -f docker-compose.{env}.yml down +``` +* run server with `node server.js` to verify the changes are applied and everything works as expected +* if everything is ok, stop the server and deploy it using `docker-compose`: +```bash +docker-compose -f docker-compose.{env}.yml up -d --force-recreate +``` +* Verify the server is up and running before leaving :) Dev API address: https://devapi.catbytes.io/ +PROD API: https://prodapi.catbytes.io/ + +## Basic Docker commands +* run `docker ps -a` to see all running Docker containers +* `docker logs {container_id}` to see running container's logs +* you might need to re-build using `docker-compose -f docker-compose.{env}.yml build --no-cache` before running `docker-compose up` in case changes were not picked up diff --git a/docs/how-tos/knex_migrations.md b/docs/how-tos/knex_migrations.md new file mode 100644 index 0000000..d225ec2 --- /dev/null +++ b/docs/how-tos/knex_migrations.md @@ -0,0 +1,45 @@ +# Database migrations with Knex + +Migrations documentation: https://knexjs.org/guide/migrations.html + +We're using `knex migrate` for database migrations - no manual changes should be performed anymore. + +The steps would always be as following: +1. Developing the feature locally, **using local postgresql instance**, all database changes should be stored as migrations in `knex/migrations` folder and tested on a local instance. See **Creating and applying migrations** section below for detais. +2. After the changes are reviewed and approved, the PR is megred into `develop` branch, the migrations should be applied to DEV RDS together with deploying the changes to dev EC2 (see `aws_ec2_deployment.md`) +3. After the changes are merged into `main`, the migrations should be applied to the PROD database together with deployment to PROD EC2. + +## Creating and applying migrations + +### Creating migrations + +Migrations are stored as `*.js` files in `knex/migrations` folder inside the project. Each migration file contains `exports.up` and `exports.down` functions: the first is used to apply migrations, the second is for rollbacks. + +When you're working on a feature and some database changes need to be performed, for example: +- table schema update +- adding a enum or a custom constraint etc +- making data changes (see `assign_members.js`) + +you should create a new migration.
+Run this command and give your migration a well-explanatory name: +```bash +knex migrate:make {migration_name} +``` +This will generate a new file and you will need to populate it with your changes AND with a rollback query to undo these changes if needed. See existing migrations files to get an idea of how this should work, knex itself also has a good documentation, the link is in the beginning of this document. + +**NB~** The agreement is to use `async function` in migrations for consistency, so even though `migrate:make` generates sync methods, please update them. + +It is also a good practice to separate migrations by tables or logic rather by "PR" or "task". Try to think how would this be rolled back. + +### Applying migrations + +After all required migrations are created, you can apply them using: +``` +knex migrate:latest +``` + +This will generate an output requesting you to acknowledge the environment you're applying the changes to. Please be careful and make sure you're running the migrations on the database you actually need. + +The default configuration to load is DEVELOPMENT. To run against local setup, your `ENVIRONMENT` env var should be set to `local`. + +When running migrations on a prod server, you need to explicitly set the `NODE_ENV=production` in command line before applying migrations. \ No newline at end of file From a55b3340e1058ff99fe8215edda4bbd59bde9207 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Tue, 15 Apr 2025 09:09:34 +0200 Subject: [PATCH 004/135] improved environment logging --- db.js | 2 +- knexfile.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db.js b/db.js index 0aba1cd..ea52a3a 100644 --- a/db.js +++ b/db.js @@ -9,7 +9,7 @@ let knexInstance = null; async function getDbSettings() { const isLocal = process.env.ENVIRONMENT === "local"; - console.log("isLocal: ", isLocal); + console.log("Environment: ", isLocal ? "local" : process.env.NODE_ENV ?? "development"); if (isLocal) { databaseUsername = process.env.DB_USER; databaseHost = process.env.DB_HOST || "localhost"; diff --git a/knexfile.js b/knexfile.js index 4984e07..0051019 100644 --- a/knexfile.js +++ b/knexfile.js @@ -32,7 +32,7 @@ module.exports = { console.log(`\n⚠️ !!!! IMPORTANT !!!! ⚠️ Before proceeding with migrations, verify your changes in migration files and apply them on the local database first. - Check if the output above says "isLocal: true". + Check if the output above says "Environment: local". If you are sure, you can proceed with migrations on remote database. Please acknowledge this message by typing "ack" and pressing Enter. `); From cb675a16508d70df69873cd5b87cd3529d0886bf Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sat, 19 Apr 2025 10:44:45 +0200 Subject: [PATCH 005/135] migrated to pino logger --- aws/ssm-helper.js | 11 +- discordBot.js | 5 +- logger.js | 26 ++++ middleware/authentication.js | 7 +- middleware/authorization.js | 7 +- package-lock.json | 223 ++++++++++++++++++++++++++++++-- package.json | 6 +- routes/applications.js | 7 +- routes/discord.js | 3 +- routes/roles.js | 3 +- routes/users.js | 17 +-- server.js | 3 +- services/discord_bot_service.js | 3 +- services/mailer_service.js | 6 +- utils.js | 9 +- 15 files changed, 291 insertions(+), 45 deletions(-) create mode 100644 logger.js diff --git a/aws/ssm-helper.js b/aws/ssm-helper.js index d54f63b..ac1105b 100644 --- a/aws/ssm-helper.js +++ b/aws/ssm-helper.js @@ -1,4 +1,5 @@ const { SSMClient, GetParametersCommand } = require('@aws-sdk/client-ssm'); +const logger = require('../logger')(__filename); async function loadSecrets(region, names, withDecryption = false, parseJson = false) { const ssmClient = new SSMClient({ @@ -6,7 +7,7 @@ async function loadSecrets(region, names, withDecryption = false, parseJson = fa }); try { - console.log('Fetching secrets from region:', region); + logger.debug(`Fetching secrets from region: ${region}`); const command = new GetParametersCommand({ Names: names, WithDecryption: withDecryption, @@ -20,11 +21,11 @@ async function loadSecrets(region, names, withDecryption = false, parseJson = fa return acc; }, {}); - console.log('Fetched secrets:', Object.keys(secrets)); + logger.debug(Object.keys(secrets), 'Fetched secrets'); return secrets; - } catch (error) { - console.error('Error fetching parameters:', error); - throw error; + } catch (err) { + logger.error(err, 'Error fetching parameters'); + throw err; } } diff --git a/discordBot.js b/discordBot.js index 0f47777..6019b65 100644 --- a/discordBot.js +++ b/discordBot.js @@ -1,6 +1,7 @@ const { Client, GatewayIntentBits } = require('discord.js'); const { loadSecrets } = require("./aws/ssm-helper"); const config = require('config'); +const logger = require('./logger')(__filename); const discordBot = { discordClient: null, @@ -36,8 +37,8 @@ async function initDiscordBot() { discordBot.discordClient = discordClient; discordBot.guildId = serverId; discordBot.channelId = channelId; - } catch (error) { - console.error("Error initializing Discord bot:", error); + } catch (err) { + logger.error(err, 'Error initializing Discord bot'); } }); } diff --git a/logger.js b/logger.js new file mode 100644 index 0000000..4813d3c --- /dev/null +++ b/logger.js @@ -0,0 +1,26 @@ +const path = require('path'); +const pino = require('pino'); + +require("dotenv").config(); + +const baseLogger = pino({ + level: process.env.DEFAULT_LOG_LEVEL || process.env.NODE_ENV !== 'production' + ? 'debug' + : 'info', + transport: process.env.NODE_ENV !== 'production' + ? + { + level: 'debug', + target: 'pino-pretty', // pretty output for local/dev + options: { + colorize: true, + ignore: 'pid,hostname,module', + messageFormat: '[{module}] {msg}' + } + } + : undefined // raw json for prod/Loki +}); + +module.exports = function getLogger(callerFilename) { + return baseLogger.child({ module: path.basename(callerFilename) }); +}; \ No newline at end of file diff --git a/middleware/authentication.js b/middleware/authentication.js index f44040e..3812371 100644 --- a/middleware/authentication.js +++ b/middleware/authentication.js @@ -1,18 +1,19 @@ const userService = require('../services/user_service'); const { respondWithError } = require('../routes/helpers'); +const logger = require('../logger')(__filename); function authenticate() { return async (req, res, next) => { try { const userFirebaseId = req.cookies?.userUID; if (!userFirebaseId) { - console.warn(`No user UID found in request cookies at ${req.path}`); + logger.warn(`No user UID found in request cookies at ${req.path}`); return next(); } const user = await userService.getUserByFirebaseId(userFirebaseId); if (!user) { - console.warn(`Found user UID in request cookies, but no corresponding user found in database`); + logger.warn('Found user UID in request cookies, but no corresponding user found in database'); return respondWithError(res, 401, "User with provided UID not found"); } @@ -21,7 +22,7 @@ function authenticate() { next(); } catch (err) { - console.error('Unexpected error while autenthicating request:', err); + logger.error(err, 'Unexpected error while autenthicating request'); return respondWithError(res); } }; diff --git a/middleware/authorization.js b/middleware/authorization.js index ee0a413..1e2023f 100644 --- a/middleware/authorization.js +++ b/middleware/authorization.js @@ -1,6 +1,7 @@ const utils = require('../utils'); const repo = require('../repositories/authorization_repository'); const { respondWithError } = require('../routes/helpers'); +const logger = require('../logger')(__filename); function verifyRole(roleName) { return async (req, res, next) => { @@ -17,7 +18,7 @@ function verifyRole(roleName) { } next(); } catch (err) { - console.error('Error verifying role:', err); + logger.error(err, 'Error verifying role'); return respondWithError(res); } }; @@ -30,7 +31,7 @@ const OWNED_ENTITIES = { function verifyOwnership(entityTable) { return async (req, res, next) => { - console.log("Middleware invoked for:", req.url); + logger.debug(`Middleware invoked for: ${req.url}`); try { const resourceId = req.params.id; const userId = req.userId; @@ -56,7 +57,7 @@ function verifyOwnership(entityTable) { next(); } catch (err) { - console.error('Error verifying ownership:', err); + logger.error(err, 'Error verifying ownership'); return respondWithError(res); } }; diff --git a/package-lock.json b/package-lock.json index 4b1cfea..5719f9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,10 @@ "nodemailer": "^6.10.0", "nodemailer-express-handlebars": "^7.0.0", "nodemon": "^3.1.9", - "pg": "^8.13.1" + "pg": "^8.13.1", + "pino": "^9.6.0", + "pino-http": "^10.4.0", + "pino-pretty": "^13.0.0" }, "devDependencies": { "@stylistic/eslint-plugin-js": "^2.12.1", @@ -2549,6 +2552,15 @@ "license": "MIT", "optional": true }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2911,6 +2923,15 @@ "node": ">= 8" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3072,7 +3093,6 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "license": "MIT", - "optional": true, "dependencies": { "once": "^1.4.0" } @@ -3413,6 +3433,12 @@ "node": ">=18.0.0" } }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3435,6 +3461,21 @@ "license": "MIT", "peer": true }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, "node_modules/fast-xml-parser": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", @@ -3706,7 +3747,6 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "license": "ISC", - "optional": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -3972,6 +4012,12 @@ "node": ">= 0.4" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, "node_modules/html-entities": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", @@ -4301,6 +4347,15 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -4990,6 +5045,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -5007,7 +5071,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", - "optional": true, "dependencies": { "wrappy": "1" } @@ -5251,6 +5314,79 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pino": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", + "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-http": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-10.4.0.tgz", + "integrity": "sha512-vjQsKBE+VN1LVchjbfLE7B6nBeGASZNRNKsR68VS0DolTm5R3zo+47JX1wjm0O96dcbvA7vnqt8YqOWlG5nN0w==", + "license": "MIT", + "dependencies": { + "get-caller-file": "^2.0.5", + "pino": "^9.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.0.0.tgz", + "integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -5321,6 +5457,22 @@ "fsevents": "2.3.3" } }, + "node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/proto3-json-serializer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", @@ -5378,6 +5530,16 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5404,6 +5566,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -5455,6 +5623,15 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -5548,12 +5725,27 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -5706,6 +5898,15 @@ "node": ">=10" } }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5818,9 +6019,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -5945,6 +6144,15 @@ "license": "MIT", "optional": true }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/tildify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", @@ -6232,8 +6440,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/ws": { "version": "8.18.0", diff --git a/package.json b/package.json index 49257c2..8a8dc6a 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,13 @@ "nodemailer": "^6.10.0", "nodemailer-express-handlebars": "^7.0.0", "nodemon": "^3.1.9", - "pg": "^8.13.1" + "pg": "^8.13.1", + "pino": "^9.6.0", + "pino-http": "^10.4.0", + "pino-pretty": "^13.0.0" }, "devDependencies": { "@stylistic/eslint-plugin-js": "^2.12.1", "prisma": "^5.21.1" } } - diff --git a/routes/applications.js b/routes/applications.js index 547954e..b0395f3 100644 --- a/routes/applications.js +++ b/routes/applications.js @@ -7,6 +7,7 @@ const { checkConstraintViolationOrRespondWith500 } = require("./helpers"); const { sendEmailOnApplicationStatusChange } = require("../services/mailer_service"); +const logger = require('../logger')(__filename); const router = express.Router(); router.use(express.json()); @@ -16,7 +17,7 @@ router.get("/applications", verifyRole(ROLE_NAMES.mentor), async (req, res) => { const result = await applService.getAllApplications(); res.json({ applications: result }); } catch (err) { - console.error(err); + logger.error(err); respondWithError(res); } }); @@ -34,7 +35,7 @@ router.post("/applications", async (req, res) => { res.status(201).json(result); } catch (err) { - console.error(err); + logger.error(err); checkConstraintViolationOrRespondWith500(err, res, 'applications'); } }); @@ -82,7 +83,7 @@ router.put("/applications/:id", verifyRole(ROLE_NAMES.mentor), async (req, res) await sendEmailOnApplicationStatusChange(application.email, application.name, status); res.status(200).json(application); } catch (err) { - console.error(err); + logger.error(err); respondWithError(res); } } diff --git a/routes/discord.js b/routes/discord.js index 173a588..6a9f0ea 100644 --- a/routes/discord.js +++ b/routes/discord.js @@ -3,6 +3,7 @@ const discordService = require('../services/discord_bot_service'); const { respondWithError } = require("./helpers"); const { verifyRole } = require("../middleware/authorization"); const { ROLE_NAMES } = require("../utils"); +const logger = require('../logger')(__filename); const router = express.Router(); router.use(express.json()); @@ -12,7 +13,7 @@ router.post('/generate-invite', verifyRole(ROLE_NAMES.member), async (req, res) const invite = await discordService.generateInviteLink(req.userId); res.json({ invite_link: invite }); } catch (error) { - console.error('Error generating invite:', error); + logger.error('Error generating invite:', error); const status = error.status || 500; respondWithError(res, status, error.message); } diff --git a/routes/roles.js b/routes/roles.js index dba3bd0..999fd0f 100644 --- a/routes/roles.js +++ b/routes/roles.js @@ -1,6 +1,7 @@ const express = require("express"); const { respondWithError } = require("./helpers"); const rolesService = require("../services/roles_service"); +const logger = require('../logger')(__filename); const router = express.Router(); router.use(express.json()); @@ -10,7 +11,7 @@ router.get("/roles", async (req, res) => { const result = await rolesService.getAllRoles(); res.json({roles: result}); } catch (err) { - console.error(err); + logger.error(err); respondWithError(res); } }); diff --git a/routes/users.js b/routes/users.js index 5a7e771..3d389c6 100644 --- a/routes/users.js +++ b/routes/users.js @@ -8,6 +8,7 @@ const { ROLE_NAMES } = require("../utils"); const {verifyOwnership, verifyRole, OWNED_ENTITIES} = require("../middleware/authorization"); const { isValidIntegerId, respondWithError, isUniqueConstraintViolation, isNotNullConstraintViolation, parseColumnNameFromConstraint } = require("./helpers"); +const logger = require('../logger')(__filename); router.use(express.json()); @@ -47,8 +48,8 @@ router.post("/users/login", async (req, res) => { res.cookie('userUID', firebaseId, { httpOnly: true, secure: true, sameSite: 'none' }); res.status(200).json({ user: user }); } catch (error) { - console.error(error); - return respondWithError(res, 401, "Unauthorized"); + logger.error(error); + return respondWithError(res); } }); @@ -58,7 +59,7 @@ router.get("/users", verifyRole(ROLE_NAMES.member), async (req, res) => { const users = await userService.getAllUsers(); res.json({ users }); } catch (err) { - console.error(err); + logger.error(err); respondWithError(res); } }); @@ -66,14 +67,14 @@ router.get("/users", verifyRole(ROLE_NAMES.member), async (req, res) => { // Create a new user router.post("/users", async (req, res) => { const { name, email, about, languages } = req.body; - console.log(req.body); // Log the entire request body + // console.log(req.body); // Log the entire request body try { // todo: firebase will only know user's email, we will need to get user's application by email // and populate user entity with that data here const user = await userService.createNewMemberUser(name, email, about, languages); res.status(201).json({ id: user.id }); } catch (err) { - console.error(err); + logger.error(err); const violatedValue = parseColumnNameFromConstraint(err.constraint, 'users'); if (isUniqueConstraintViolation(err.code)) { return respondWithError( @@ -102,7 +103,7 @@ router.get("/users/:id", verifyRole(ROLE_NAMES.member), async (req, res) => { } res.json(userInfo); } catch (err) { - console.error(err); + logger.error(err); respondWithError(res); } }); @@ -121,7 +122,7 @@ router.put("/users/:id", verifyOwnership(OWNED_ENTITIES.USER), async (req, res) } res.status(200).json(updatedUser); } catch (err) { - console.error(err); + logger.error(err); respondWithError(res); } }); @@ -139,7 +140,7 @@ router.delete("/users/:id", verifyOwnership(OWNED_ENTITIES.USER), async (req, re } res.status(200).json({ user_id: id }); } catch (err) { - console.error(err); + logger.error(err); respondWithError(res); } }); diff --git a/server.js b/server.js index c402f93..c125c7c 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,7 @@ const { initDiscordBot } = require('./discordBot.js'); const utils = require('./utils'); const admin = require("firebase-admin"); const { authenticate } = require("./middleware/authentication"); +const logger = require('./logger')(__filename); const app = express(); @@ -46,6 +47,6 @@ app.use(authenticate()); app.use(routes); app.listen(8080,'0.0.0.0', () => { - console.log(`Server is running`); + logger.info('Server is running'); }); })(); diff --git a/services/discord_bot_service.js b/services/discord_bot_service.js index 5743f4d..94a92ff 100644 --- a/services/discord_bot_service.js +++ b/services/discord_bot_service.js @@ -1,6 +1,7 @@ const { discordBot } = require('../discordBot.js'); const userService = require('../services/user_service'); const repo = require('../repositories/discord_bot_repository'); +const logger = require('../logger')(__filename); async function generateInviteLink(userId) { try { @@ -15,7 +16,7 @@ async function generateInviteLink(userId) { return invite.url; } catch (error) { - console.error('Failed to generate invite:', error.message); + logger.error({ error }, 'Failed to generate discord invite'); throw error; } } diff --git a/services/mailer_service.js b/services/mailer_service.js index 2608a3d..25cd951 100644 --- a/services/mailer_service.js +++ b/services/mailer_service.js @@ -4,6 +4,7 @@ const config = require('config'); const { loadSecrets } = require("../aws/ssm-helper"); const { APPL_STATUSES } = require("../utils"); const discordService = require("../services/discord_bot_service"); +const logger = require('../logger')(__filename); require("dotenv").config(); @@ -103,7 +104,7 @@ async function sendEmailOnApplicationStatusChange(email, name, status) { await sendApplicationRejectedEmail(email, name); } else { - console.log(`Not sending aplication status change email, because status ${status} + logger.warn(`Not sending aplication status change email, because status ${status} is not in email-sending allow-list.`); } } @@ -111,9 +112,8 @@ async function sendEmailOnApplicationStatusChange(email, name, status) { // todo: add correct error processing: if "domain does not accept mail" - log it, // put to some deadletter for future manual verification // if some other error - we should retry sending email - add queue - console.error(`Error sending email to ${email} on application status change:`, err.message); + logger.error(`Error sending email to ${email} on application status change: ${err.message}`); } - } module.exports = { initMailer, sendEmailOnApplicationStatusChange }; \ No newline at end of file diff --git a/utils.js b/utils.js index d5a5709..d8ec7e2 100644 --- a/utils.js +++ b/utils.js @@ -1,6 +1,7 @@ const repo = require('./repositories/roles_repository'); const { loadSecrets } = require("./aws/ssm-helper"); const config = require('config'); +const logger = require('./logger')(__filename); let rolesCache = null; @@ -23,7 +24,7 @@ async function getFirebaseSdkServiceAccount() { } } catch (error) { - console.error("Error getting service account:", error); + logger.error({ error }, "Error getting service account"); throw new Error("Failed to retrieve service account"); } return serviceAccount; @@ -36,14 +37,14 @@ async function loadRolesIntoMemory() { rolesCache = roles.reduce((acc, role) => { acc[role.role_name] = role.id; if (!isRoleExists(role.role_name)) { - console.warn(`Role ${role.role_name} is in database, but is not in the ROLE_NAMES enum.`); + logger.warn(`Role ${role.role_name} is in database, but is not in the ROLE_NAMES enum.`); } return acc; }, {}); - console.log('Roles loaded into memory:', rolesCache); + logger.info({ rolesCache }, 'Roles loaded into memory'); } } catch (error) { - console.error("Error loading roles:", error); + logger.error({ error }, "Error loading roles"); throw new Error("Failed to initialize roles"); } } From 8a208a744e24589c808ff050a7f66cfbcc19a4e5 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sat, 19 Apr 2025 10:47:48 +0200 Subject: [PATCH 006/135] unified logging format --- routes/discord.js | 8 ++++---- services/discord_bot_service.js | 6 +++--- utils.js | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/routes/discord.js b/routes/discord.js index 6a9f0ea..82d5970 100644 --- a/routes/discord.js +++ b/routes/discord.js @@ -12,10 +12,10 @@ router.post('/generate-invite', verifyRole(ROLE_NAMES.member), async (req, res) try { const invite = await discordService.generateInviteLink(req.userId); res.json({ invite_link: invite }); - } catch (error) { - logger.error('Error generating invite:', error); - const status = error.status || 500; - respondWithError(res, status, error.message); + } catch (err) { + logger.error(err, 'Error generating invite'); + const status = err.status || 500; + respondWithError(res, status, err.message); } }); diff --git a/services/discord_bot_service.js b/services/discord_bot_service.js index 94a92ff..b766e35 100644 --- a/services/discord_bot_service.js +++ b/services/discord_bot_service.js @@ -15,9 +15,9 @@ async function generateInviteLink(userId) { } return invite.url; } - catch (error) { - logger.error({ error }, 'Failed to generate discord invite'); - throw error; + catch (err) { + logger.error(err, 'Failed to generate discord invite'); + throw err; } } diff --git a/utils.js b/utils.js index d8ec7e2..4d11c9f 100644 --- a/utils.js +++ b/utils.js @@ -23,8 +23,8 @@ async function getFirebaseSdkServiceAccount() { serviceAccount = JSON.parse(jsonFile); } } - catch (error) { - logger.error({ error }, "Error getting service account"); + catch (err) { + logger.error(err, "Error getting service account"); throw new Error("Failed to retrieve service account"); } return serviceAccount; @@ -41,10 +41,10 @@ async function loadRolesIntoMemory() { } return acc; }, {}); - logger.info({ rolesCache }, 'Roles loaded into memory'); + logger.info(rolesCache, 'Roles loaded into memory'); } - } catch (error) { - logger.error({ error }, "Error loading roles"); + } catch (err) { + logger.error(err, "Error loading roles"); throw new Error("Failed to initialize roles"); } } From 3dcc5855f9b9f115d132545ac5080848c1e533d7 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sat, 19 Apr 2025 11:01:01 +0200 Subject: [PATCH 007/135] added pino-http to log http requests if needed --- docker-compose.prod.yml | 1 + logger.js | 7 +++++-- server.js | 9 ++++++++- utils.js | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 7814b72..019482b 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -4,6 +4,7 @@ services: build: . environment: NODE_ENV: production + LOGGING_HTTP_REQUESTS: true command: npx nodemon --exec npm start ports: - '3000:3000' diff --git a/logger.js b/logger.js index 4813d3c..fcb80a8 100644 --- a/logger.js +++ b/logger.js @@ -21,6 +21,9 @@ const baseLogger = pino({ : undefined // raw json for prod/Loki }); -module.exports = function getLogger(callerFilename) { +function getLogger(callerFilename) { return baseLogger.child({ module: path.basename(callerFilename) }); -}; \ No newline at end of file +} + +module.exports = getLogger; +module.exports.baseLogger = baseLogger; \ No newline at end of file diff --git a/server.js b/server.js index c125c7c..7f6601c 100644 --- a/server.js +++ b/server.js @@ -9,11 +9,16 @@ const { initDiscordBot } = require('./discordBot.js'); const utils = require('./utils'); const admin = require("firebase-admin"); const { authenticate } = require("./middleware/authentication"); -const logger = require('./logger')(__filename); +const pinoHttp = require('pino-http'); +const { baseLogger } = require('./logger'); const app = express(); // Middleware +if (process.env.LOGGING_HTTP_REQUESTS === 'true') { + app.use(pinoHttp({ logger: baseLogger })); +} + app.use(express.json()); app.use(cookieParser()); app.use(cors({ @@ -32,6 +37,8 @@ app.options('*', cors()); app.use(authenticate()); (async () => { + const logger = require('./logger')(__filename); + await initDb(); await initMailer(); await initDiscordBot(); diff --git a/utils.js b/utils.js index 4d11c9f..704a0d9 100644 --- a/utils.js +++ b/utils.js @@ -41,7 +41,7 @@ async function loadRolesIntoMemory() { } return acc; }, {}); - logger.info(rolesCache, 'Roles loaded into memory'); + logger.debug(rolesCache, 'Roles loaded into memory'); } } catch (err) { logger.error(err, "Error loading roles"); From 8bde7262412d17bd12d58b9e78bbc5d2f5f7c2bf Mon Sep 17 00:00:00 2001 From: vm2052 Date: Mon, 21 Apr 2025 21:12:55 +0300 Subject: [PATCH 008/135] integrating discord oauth --- oauth.js | 32 ++++++++ package-lock.json | 187 +++++++++++++++++++++++++++++++++++++++------- package.json | 2 +- routes/discord.js | 54 +++++++++++++ server.js | 2 + 5 files changed, 248 insertions(+), 29 deletions(-) create mode 100644 oauth.js diff --git a/oauth.js b/oauth.js new file mode 100644 index 0000000..e2ba798 --- /dev/null +++ b/oauth.js @@ -0,0 +1,32 @@ +const { loadSecrets } = require("./aws/ssm-helper"); +const config = require('config'); + +const discordAuth = { + discordClientId: null, + discordClientSecret: null, + discordRedirectUrl: null, +}; + +async function initOAuth() { + let discordClientId, discordClientSecret, discordRedirectUrl = null; + + if (process.env.ENVIRONMENT === "local") { + discordClientId = process.env.DISCORD_CLIENT_ID; + discordClientSecret = process.env.DISCORD_CLIENT_SECRET; + discordRedirectUrl = process.env.DISCORD_REDIRECT_URI; + } else { + const params = await loadSecrets(config.aws.param_store_region, ['/catbytes_webplatform/oauth_creds'], true, true); + console.log(params); + discordClientId = params['oauth_creds']['discord']['clientID']; + discordClientSecret = params['oauth_creds']['discord']['clientSecret']; + discordRedirectUrl = params['oauth_creds']['discord']['redirectURIs'][0]; + } + + discordAuth.discordClientId = discordClientId; + discordAuth.discordClientSecret = discordClientSecret; + discordAuth.discordRedirectUrl = discordRedirectUrl; + console.log(discordAuth); +} + +module.exports = { discordAuth, initOAuth }; + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4b1cfea..8fed2b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@aws-sdk/client-ssm": "^3.699.0", "@prisma/client": "^5.21.1", + "axios": "^1.8.4", "config": "^3.3.12", "cookie-parser": "^1.4.7", "cors": "^2.8.5", @@ -2546,8 +2547,33 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "license": "MIT", - "optional": true + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/balanced-match": { "version": "1.0.2", @@ -2682,6 +2708,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2791,7 +2830,6 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", - "optional": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2950,7 +2988,6 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", - "optional": true, "engines": { "node": ">=0.4.0" } @@ -3018,6 +3055,20 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexify": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", @@ -3078,13 +3129,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -3098,6 +3146,33 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3592,6 +3667,26 @@ "license": "ISC", "peer": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -3712,16 +3807,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3739,6 +3839,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/getopts": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", @@ -3862,12 +3975,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3936,10 +4049,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -3948,11 +4061,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -4708,6 +4824,15 @@ "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", "license": "MIT" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -5372,6 +5497,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/package.json b/package.json index 49257c2..e692c69 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@aws-sdk/client-ssm": "^3.699.0", "@prisma/client": "^5.21.1", + "axios": "^1.8.4", "config": "^3.3.12", "cookie-parser": "^1.4.7", "cors": "^2.8.5", @@ -31,4 +32,3 @@ "prisma": "^5.21.1" } } - diff --git a/routes/discord.js b/routes/discord.js index 173a588..8944a09 100644 --- a/routes/discord.js +++ b/routes/discord.js @@ -1,9 +1,14 @@ const express = require('express'); const discordService = require('../services/discord_bot_service'); +const userService = require("../services/user_service"); const { respondWithError } = require("./helpers"); const { verifyRole } = require("../middleware/authorization"); const { ROLE_NAMES } = require("../utils"); +const {discordAuth} = require ("../oauth.js"); +const axios = require('axios'); +const querystring = require('querystring'); + const router = express.Router(); router.use(express.json()); @@ -18,4 +23,53 @@ router.post('/generate-invite', verifyRole(ROLE_NAMES.member), async (req, res) } }); +router.get('/auth/discord', (req, res) => { + res.redirect(`https://discord.com/api/oauth2/authorize?client_id=${discordAuth.discordClientId} + &redirect_uri=${encodeURIComponent(discordAuth.discordRedirectUrl)}&response_type=code&scope=identify%20email`); +}); + +router.get('/auth/discord/callback', async (req, res) => { + const { code } = req.query; + + if (!code) return respondWithError(res, 400, 'no code provided'); + + try { + + // exchange code for access token + const tokenResponse = await axios.post('https://discord.com/api/oauth2/token', + querystring.stringify({ + client_id: discordAuth.discordClientId, + client_secret: discordAuth.discordClientSecret, + grant_type: 'authorization_code', + code: code, + redirect_uri: discordAuth.discordRedirectUrl, + scope: 'identify email' + }), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + + // get user data from discord + const userResponse = await axios.get('https://discord.com/api/users/@me', { + headers: { + Authorization: `Bearer ${ tokenResponse.data.access_token}` + } + }); + + const discordUser = userResponse.data; + + console.log(discordUser); + const user = await userService.getUserByEmail(discordUser.email); + + console.log(user); + res.cookie('userUID', user.firebaseId, { httpOnly: true, secure: true, sameSite: 'none' }); + res.status(200).json({ user: user }); + + } catch (error) { + console.error('Error:', error); + res.status(500).send('Authentication failed'); + } +}); + module.exports = router; \ No newline at end of file diff --git a/server.js b/server.js index c402f93..3da71d2 100644 --- a/server.js +++ b/server.js @@ -6,6 +6,7 @@ const cors = require("cors"); const { initDb } = require('./db'); const { initMailer } = require('./services/mailer_service'); const { initDiscordBot } = require('./discordBot.js'); +const { initOAuth } = require('./oauth.js'); const utils = require('./utils'); const admin = require("firebase-admin"); const { authenticate } = require("./middleware/authentication"); @@ -34,6 +35,7 @@ app.use(authenticate()); await initDb(); await initMailer(); await initDiscordBot(); + await initOAuth(); await utils.loadRolesIntoMemory(); const firebaseServiceAccount = await utils.getFirebaseSdkServiceAccount(); admin.initializeApp({ From 48cbf180677cc021513473bc2d81c7d8840f7f5f Mon Sep 17 00:00:00 2001 From: vm2052 Date: Mon, 21 Apr 2025 21:44:06 +0300 Subject: [PATCH 009/135] updated error res --- oauth.js | 4 +--- routes/discord.js | 7 ++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/oauth.js b/oauth.js index e2ba798..ab78eb7 100644 --- a/oauth.js +++ b/oauth.js @@ -16,16 +16,14 @@ async function initOAuth() { discordRedirectUrl = process.env.DISCORD_REDIRECT_URI; } else { const params = await loadSecrets(config.aws.param_store_region, ['/catbytes_webplatform/oauth_creds'], true, true); - console.log(params); discordClientId = params['oauth_creds']['discord']['clientID']; discordClientSecret = params['oauth_creds']['discord']['clientSecret']; - discordRedirectUrl = params['oauth_creds']['discord']['redirectURIs'][0]; + discordRedirectUrl = params['oauth_creds']['discord']['redirectURIs'][1]; } discordAuth.discordClientId = discordClientId; discordAuth.discordClientSecret = discordClientSecret; discordAuth.discordRedirectUrl = discordRedirectUrl; - console.log(discordAuth); } module.exports = { discordAuth, initOAuth }; diff --git a/routes/discord.js b/routes/discord.js index 8944a09..cfab0af 100644 --- a/routes/discord.js +++ b/routes/discord.js @@ -59,16 +59,17 @@ router.get('/auth/discord/callback', async (req, res) => { const discordUser = userResponse.data; - console.log(discordUser); const user = await userService.getUserByEmail(discordUser.email); + if (!user) { + return respondWithError(res, 404, 'User not found'); + } - console.log(user); res.cookie('userUID', user.firebaseId, { httpOnly: true, secure: true, sameSite: 'none' }); res.status(200).json({ user: user }); } catch (error) { console.error('Error:', error); - res.status(500).send('Authentication failed'); + respondWithError(res, 500, 'Authentication failed'); } }); From 4ff194b83261af85fdd7022d30764d1b2ae46982 Mon Sep 17 00:00:00 2001 From: vm2052 Date: Mon, 21 Apr 2025 22:33:12 +0300 Subject: [PATCH 010/135] updated swagger docs --- docs/catbytes_api_swagger.json | 151 +++++++++++++++++++++++++++++++++ docs/catbytes_api_swagger.yaml | 104 +++++++++++++++++++++++ 2 files changed, 255 insertions(+) diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index 80a4bc2..fc64d1f 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -1894,6 +1894,157 @@ } } } + }, + "/auth/discord": { + "get": { + "tags": [ + "discord integration" + ], + "summary": "Initiate Discord OAuth2 flow", + "description": "Redirects to Discord's OAuth2 authorization page to start the authentication process.", + "responses": { + "302": { + "description": "Redirect to Discord's authorization page", + "headers": { + "Location": { + "schema": { + "type": "string", + "example": "https://discord.com/api/oauth2/authorize?client_id=12345&redirect_uri=https%3A%2F%2Fdevapi.catbytes.io%2Fauth%2Fdiscord%2Fcallback&response_type=code&scope=identify%20email" + } + } + } + } + } + } + }, + "/auth/discord/callback": { + "get": { + "tags": [ + "discord integration" + ], + "summary": "Handle Discord OAuth2 callback", + "description": "Handles the callback from Discord after user authorization, exchanges code for tokens, and authenticates the user.", + "parameters": [ + { + "name": "code", + "in": "query", + "description": "Authorization code returned from Discord", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful login", + "headers": { + "Set-Cookie": { + "description": "Secure cookie containing 'userUID'", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 42 + }, + "name": { + "type": "string", + "example": "Jane" + }, + "email": { + "type": "string", + "example": "jane@example.com" + }, + "about": { + "type": "string", + "example": "Hi I am Jane" + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2024-12-15T17:27:56.146Z" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "English", + "Spanish" + ] + } + } + } + } + } + } + } + }, + "400": { + "description": "No authorization code provided", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error message.", + "example": "No code provided" + } + } + } + } + } + }, + "404": { + "description": "User with this email not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error message.", + "example": "User not found" + } + } + } + } + } + }, + "500": { + "description": "Authentication failed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error message.", + "example": "Authentication failed" + } + } + } + } + } + } + } + } } }, "components": { diff --git a/docs/catbytes_api_swagger.yaml b/docs/catbytes_api_swagger.yaml index eef88e3..1441488 100644 --- a/docs/catbytes_api_swagger.yaml +++ b/docs/catbytes_api_swagger.yaml @@ -1259,6 +1259,110 @@ paths: type: string description: Error message. example: Failed to generate invite + /auth/discord: + get: + tags: + - discord integration + summary: Initiate Discord OAuth2 flow + description: >- + Redirects to Discord's OAuth2 authorization page to start the + authentication process. + responses: + '302': + description: Redirect to Discord's authorization page + headers: + Location: + schema: + type: string + example: >- + https://discord.com/api/oauth2/authorize?client_id=12345&redirect_uri=https%3A%2F%2Fdevapi.catbytes.io%2Fauth%2Fdiscord%2Fcallback&response_type=code&scope=identify%20email + /auth/discord/callback: + get: + tags: + - discord integration + summary: Handle Discord OAuth2 callback + description: >- + Handles the callback from Discord after user authorization, exchanges + code for tokens, and authenticates the user. + parameters: + - name: code + in: query + description: Authorization code returned from Discord + required: true + schema: + type: string + responses: + '200': + description: Successful login + headers: + Set-Cookie: + description: Secure cookie containing 'userUID' + schema: + type: string + content: + application/json: + schema: + type: object + properties: + user: + type: object + properties: + id: + type: integer + example: 42 + name: + type: string + example: Jane + email: + type: string + example: jane@example.com + about: + type: string + example: Hi I am Jane + created_at: + type: string + format: date-time + example: '2024-12-15T17:27:56.146Z' + languages: + type: array + items: + type: string + example: + - English + - Spanish + '400': + description: No authorization code provided + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error message. + example: No code provided + '404': + description: User with this email not found + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error message. + example: User not found + '500': + description: Authentication failed + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error message. + example: Authentication failed components: schemas: From c220a3e2ad5cab86929d620809621a67fb3863b2 Mon Sep 17 00:00:00 2001 From: vm2052 Date: Tue, 29 Apr 2025 21:12:00 +0300 Subject: [PATCH 011/135] fixed as per comments --- config/development.json | 3 +- config/production.json | 3 +- docs/catbytes_api_swagger.json | 151 ++++---------------------------- docs/catbytes_api_swagger.yaml | 152 +++++++-------------------------- oauth.js | 2 +- routes/discord.js | 50 +---------- routes/users.js | 46 ++++------ services/auth_service.js | 84 ++++++++++++++++++ 8 files changed, 153 insertions(+), 338 deletions(-) create mode 100644 services/auth_service.js diff --git a/config/development.json b/config/development.json index d4a4b35..80354df 100644 --- a/config/development.json +++ b/config/development.json @@ -8,5 +8,6 @@ "cors": { "allowedOrigins": ["http://localhost:5173", "https://dev.catbytes.io"] }, - "platform_url": "https://dev.catbytes.io/" + "platform_url": "https://dev.catbytes.io/", + "discord_redirect_url":"https://dev.catbytes.io/auth/discord/callback" } \ No newline at end of file diff --git a/config/production.json b/config/production.json index 9a5264b..9a55eeb 100644 --- a/config/production.json +++ b/config/production.json @@ -8,5 +8,6 @@ "cors": { "allowedOrigins": ["https://catbytes.io"] }, - "platform_url": "https://catbytes.io/" + "platform_url": "https://catbytes.io/", + "discord_redirect_url":"https://catbytes.io/auth/discord/callback" } \ No newline at end of file diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index fc64d1f..d64faaa 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -424,14 +424,22 @@ "operationId": "loginUser", "parameters": [ { - "name": "token", + "name": "firebase_token", "in": "header", - "description": "Firebase token", - "required": true, + "description": "Firebase ID token used for Firebase authentication", "schema": { - "type": "string", - "example": "FIREBASE_TOKEN" - } + "type": "string" + }, + "required": false + }, + { + "name": "discord_code", + "in": "header", + "description": "Authorization code returned by Discord OAuth2 flow", + "schema": { + "type": "string" + }, + "required": false } ], "responses": { @@ -1900,7 +1908,7 @@ "tags": [ "discord integration" ], - "summary": "Initiate Discord OAuth2 flow", + "summary": "✅ Initiate Discord OAuth2 flow", "description": "Redirects to Discord's OAuth2 authorization page to start the authentication process.", "responses": { "302": { @@ -1916,135 +1924,6 @@ } } } - }, - "/auth/discord/callback": { - "get": { - "tags": [ - "discord integration" - ], - "summary": "Handle Discord OAuth2 callback", - "description": "Handles the callback from Discord after user authorization, exchanges code for tokens, and authenticates the user.", - "parameters": [ - { - "name": "code", - "in": "query", - "description": "Authorization code returned from Discord", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Successful login", - "headers": { - "Set-Cookie": { - "description": "Secure cookie containing 'userUID'", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "user": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 42 - }, - "name": { - "type": "string", - "example": "Jane" - }, - "email": { - "type": "string", - "example": "jane@example.com" - }, - "about": { - "type": "string", - "example": "Hi I am Jane" - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2024-12-15T17:27:56.146Z" - }, - "languages": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "English", - "Spanish" - ] - } - } - } - } - } - } - } - }, - "400": { - "description": "No authorization code provided", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "description": "Error message.", - "example": "No code provided" - } - } - } - } - } - }, - "404": { - "description": "User with this email not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "description": "Error message.", - "example": "User not found" - } - } - } - } - } - }, - "500": { - "description": "Authentication failed", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "description": "Error message.", - "example": "Authentication failed" - } - } - } - } - } - } - } - } } }, "components": { diff --git a/docs/catbytes_api_swagger.yaml b/docs/catbytes_api_swagger.yaml index 1441488..2b5ee41 100644 --- a/docs/catbytes_api_swagger.yaml +++ b/docs/catbytes_api_swagger.yaml @@ -284,13 +284,18 @@ paths: description: Authenticates a user based on a provided Firebase token sent in the header. On successful authentication, sets a secure 'userUID' cookie. operationId: loginUser parameters: - - name: token + - name: firebase_token in: header - description: Firebase token - required: true + description: Firebase ID token used for Firebase authentication + schema: + type: string + required: false + - name: discord_code + in: header + description: Authorization code returned by Discord OAuth2 flow schema: type: string - example: FIREBASE_TOKEN + required: false responses: '200': description: Successful login @@ -1037,15 +1042,13 @@ paths: description: You're not allowed to edit this resource '404': description: Application not found. - /mentors: post: tags: - mentors summary: Create a new mentor application - description: > - Creates a new mentor entity in 'pending' status. Available to all logged-in users. - If user already has a mentor profile, creation is not allowed. + description: | + Creates a new mentor entity in 'pending' status. Available to all logged-in users. If user already has a mentor profile, creation is not allowed. operationId: createMentor requestBody: required: true @@ -1073,9 +1076,8 @@ paths: tags: - mentors summary: Get mentor list - description: > - Returns a list of mentor applications. When filtering by 'pending' or 'rejected', or not filtering at all, - user must have elevated (admin) role. Returns user_id and some aggregated data from user profile + description: | + Returns a list of mentor applications. When filtering by 'pending' or 'rejected', or not filtering at all, user must have elevated (admin) role. Returns user_id and some aggregated data from user profile operationId: getMentors parameters: - name: status @@ -1084,7 +1086,11 @@ paths: required: false schema: type: string - enum: [active, pending, rejected, inactive] + enum: + - active + - pending + - rejected + - inactive responses: '200': description: List of mentors @@ -1099,7 +1105,7 @@ paths: tags: - mentors summary: Get mentor by ID - description: > + description: | Returns full mentor data if user is logged in. Pending/rejected mentors require admin role. operationId: getMentorById parameters: @@ -1124,7 +1130,7 @@ paths: tags: - mentors summary: Update mentor profile - description: > + description: | Only the user who owns this mentor card can update it. Only selected fields can be changed. operationId: updateMentor parameters: @@ -1150,7 +1156,7 @@ paths: tags: - mentors summary: Update mentor status - description: > + description: | Only users with admin or mentor role can change mentor status. operationId: patchMentorStatus parameters: @@ -1168,7 +1174,10 @@ paths: properties: status: type: string - enum: [active, rejected, inactive] + enum: + - active + - rejected + - inactive responses: '200': description: Status updated @@ -1180,7 +1189,7 @@ paths: tags: - mentors summary: Delete mentor profile - description: > + description: | Only the owner can delete their mentor card. Deletes the entity and removes mentor role from user. operationId: deleteMentor parameters: @@ -1196,7 +1205,6 @@ paths: description: Not authorized '404': description: Mentor not found - /generate-invite: post: tags: @@ -1263,10 +1271,8 @@ paths: get: tags: - discord integration - summary: Initiate Discord OAuth2 flow - description: >- - Redirects to Discord's OAuth2 authorization page to start the - authentication process. + summary: ✅ Initiate Discord OAuth2 flow + description: Redirects to Discord's OAuth2 authorization page to start the authentication process. responses: '302': description: Redirect to Discord's authorization page @@ -1274,96 +1280,7 @@ paths: Location: schema: type: string - example: >- - https://discord.com/api/oauth2/authorize?client_id=12345&redirect_uri=https%3A%2F%2Fdevapi.catbytes.io%2Fauth%2Fdiscord%2Fcallback&response_type=code&scope=identify%20email - /auth/discord/callback: - get: - tags: - - discord integration - summary: Handle Discord OAuth2 callback - description: >- - Handles the callback from Discord after user authorization, exchanges - code for tokens, and authenticates the user. - parameters: - - name: code - in: query - description: Authorization code returned from Discord - required: true - schema: - type: string - responses: - '200': - description: Successful login - headers: - Set-Cookie: - description: Secure cookie containing 'userUID' - schema: - type: string - content: - application/json: - schema: - type: object - properties: - user: - type: object - properties: - id: - type: integer - example: 42 - name: - type: string - example: Jane - email: - type: string - example: jane@example.com - about: - type: string - example: Hi I am Jane - created_at: - type: string - format: date-time - example: '2024-12-15T17:27:56.146Z' - languages: - type: array - items: - type: string - example: - - English - - Spanish - '400': - description: No authorization code provided - content: - application/json: - schema: - type: object - properties: - error: - type: string - description: Error message. - example: No code provided - '404': - description: User with this email not found - content: - application/json: - schema: - type: object - properties: - error: - type: string - description: Error message. - example: User not found - '500': - description: Authentication failed - content: - application/json: - schema: - type: object - properties: - error: - type: string - description: Error message. - example: Authentication failed - + example: https://discord.com/api/oauth2/authorize?client_id=12345&redirect_uri=https%3A%2F%2Fdevapi.catbytes.io%2Fauth%2Fdiscord%2Fcallback&response_type=code&scope=identify%20email components: schemas: MentorCreateRequest: @@ -1388,7 +1305,6 @@ components: type: array items: type: string - MentorListResponse: type: object properties: @@ -1396,7 +1312,6 @@ components: type: array items: $ref: '#/components/schemas/MentorSummary' - MentorSummary: type: object properties: @@ -1412,11 +1327,10 @@ components: type: array items: type: string - name: # these are aggregated from users table + name: type: string - img_link: # these are aggregated from users table + img_link: type: string - MentorDetails: allOf: - $ref: '#/components/schemas/MentorSummary' @@ -1424,5 +1338,5 @@ components: properties: contact: type: string - discord_nickname: # this is aggregated from user - type: string \ No newline at end of file + discord_nickname: + type: string diff --git a/oauth.js b/oauth.js index ab78eb7..d6636e2 100644 --- a/oauth.js +++ b/oauth.js @@ -18,7 +18,7 @@ async function initOAuth() { const params = await loadSecrets(config.aws.param_store_region, ['/catbytes_webplatform/oauth_creds'], true, true); discordClientId = params['oauth_creds']['discord']['clientID']; discordClientSecret = params['oauth_creds']['discord']['clientSecret']; - discordRedirectUrl = params['oauth_creds']['discord']['redirectURIs'][1]; + discordRedirectUrl = config.discord_redirect_url; } discordAuth.discordClientId = discordClientId; diff --git a/routes/discord.js b/routes/discord.js index cfab0af..0a1f6eb 100644 --- a/routes/discord.js +++ b/routes/discord.js @@ -1,13 +1,10 @@ const express = require('express'); const discordService = require('../services/discord_bot_service'); -const userService = require("../services/user_service"); const { respondWithError } = require("./helpers"); const { verifyRole } = require("../middleware/authorization"); const { ROLE_NAMES } = require("../utils"); -const {discordAuth} = require ("../oauth.js"); -const axios = require('axios'); -const querystring = require('querystring'); +const {discordAuth} = require ("../oauth.js"); const router = express.Router(); router.use(express.json()); @@ -28,49 +25,4 @@ router.get('/auth/discord', (req, res) => { &redirect_uri=${encodeURIComponent(discordAuth.discordRedirectUrl)}&response_type=code&scope=identify%20email`); }); -router.get('/auth/discord/callback', async (req, res) => { - const { code } = req.query; - - if (!code) return respondWithError(res, 400, 'no code provided'); - - try { - - // exchange code for access token - const tokenResponse = await axios.post('https://discord.com/api/oauth2/token', - querystring.stringify({ - client_id: discordAuth.discordClientId, - client_secret: discordAuth.discordClientSecret, - grant_type: 'authorization_code', - code: code, - redirect_uri: discordAuth.discordRedirectUrl, - scope: 'identify email' - }), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }); - - // get user data from discord - const userResponse = await axios.get('https://discord.com/api/users/@me', { - headers: { - Authorization: `Bearer ${ tokenResponse.data.access_token}` - } - }); - - const discordUser = userResponse.data; - - const user = await userService.getUserByEmail(discordUser.email); - if (!user) { - return respondWithError(res, 404, 'User not found'); - } - - res.cookie('userUID', user.firebaseId, { httpOnly: true, secure: true, sameSite: 'none' }); - res.status(200).json({ user: user }); - - } catch (error) { - console.error('Error:', error); - respondWithError(res, 500, 'Authentication failed'); - } -}); - module.exports = router; \ No newline at end of file diff --git a/routes/users.js b/routes/users.js index 5a7e771..741afec 100644 --- a/routes/users.js +++ b/routes/users.js @@ -2,8 +2,7 @@ const express = require("express"); const router = express.Router(); const userService = require("../services/user_service"); -const applService = require("../services/applications_service"); -const admin = require("firebase-admin"); +const authService = require("../services/auth_service"); const { ROLE_NAMES } = require("../utils"); const {verifyOwnership, verifyRole, OWNED_ENTITIES} = require("../middleware/authorization"); const { isValidIntegerId, respondWithError, isUniqueConstraintViolation, @@ -13,42 +12,27 @@ router.use(express.json()); // POST /users/login router.post("/users/login", async (req, res) => { - const token = req.headers['token']; - if (!token) { - return respondWithError(res, 401, "No token provided"); + const firebaseToken = req.headers['firebase_token'] || null; + const discordCode = req.headers['discord_code'] || null; + if (!firebaseToken && !discordCode) { + return respondWithError(res, 401, 'No token provided or invalid token'); } - try { - const decodedToken = await admin.auth().verifyIdToken(token); - const email = decodedToken.email; - const firebaseId = decodedToken.uid; - - if (!decodedToken.email_verified) { - return respondWithError(res, 403, "Email not verified"); - } - const application = await applService.getApplicationByEmail(email); - if (!application || !application.status === 'approved') { - return respondWithError(res, 403, "Application is not approved or does not exist"); - } + try { + let result; - let user = await userService.getUserByEmail(email); - if (!user) { - user = await userService.createNewMemberUser( - application.name, - email, - application.about, - application.languages, - application.discord_nickname - ); + if (firebaseToken) { + result = await authService.handleFirebaseAuth(firebaseToken); + } else if (discordCode) { + result = await authService.handleDiscordAuth(discordCode); } - await userService.updateUserById(user.id, {firebase_id: firebaseId}); + res.cookie('userUID', result.user.firebaseId, { httpOnly: true, secure: true, sameSite: 'none' }); + res.status(200).json({ user: result.user }); - res.cookie('userUID', firebaseId, { httpOnly: true, secure: true, sameSite: 'none' }); - res.status(200).json({ user: user }); } catch (error) { - console.error(error); - return respondWithError(res, 401, "Unauthorized"); + console.error("Authentication Error:", error.message); + return respondWithError(res, error.status || 500, error.message); } }); diff --git a/services/auth_service.js b/services/auth_service.js new file mode 100644 index 0000000..72dfbfc --- /dev/null +++ b/services/auth_service.js @@ -0,0 +1,84 @@ +const admin = require('firebase-admin'); +const axios = require('axios'); +const querystring = require('querystring'); +const userService = require('../services/user_service'); +const applService = require("../services/applications_service"); + +const {discordAuth} = require ("../oauth.js"); + +async function handleFirebaseAuth(firebaseToken) { + try { + const decodedToken = await admin.auth().verifyIdToken(firebaseToken); + const email = decodedToken.email; + const firebaseId = decodedToken.uid; + + if (!decodedToken.email_verified) { + throw { status: 403, message: 'Email not verified' }; + } + + const application = await applService.getApplicationByEmail(email); + if (!application || application.status !== 'approved') { + throw { status: 403, message: 'Application is not approved or does not exist' }; + } + + let user = await userService.getUserByEmail(email); + if (!user) { + user = await userService.createNewMemberUser( + application.name, + email, + application.about, + application.languages, + application.discord_nickname + ); + } + + await userService.updateUserById(user.id, { firebase_id: firebaseId }); + + return { user }; + } catch (error) { + console.error("Firebase Token Verification Failed:", error.message); + throw { status: 401, message: 'Unauthorized' }; + } +} + +; + +async function handleDiscordAuth(code){ + try { + // exchange code for access token + const tokenResponse = await axios.post('https://discord.com/api/oauth2/token', + querystring.stringify({ + client_id: discordAuth.discordClientId, + client_secret: discordAuth.discordClientSecret, + grant_type: 'authorization_code', + code: code, + redirect_uri: discordAuth.discordRedirectUrl, + scope: 'identify email' + }), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + // get user data from discord + const userResponse = await axios.get('https://discord.com/api/users/@me', { + headers: { + Authorization: `Bearer ${tokenResponse.data.access_token}` + } + }); + + const discordUser = userResponse.data; + const user = await userService.getUserByEmail(discordUser.email); + + if (!user) { + throw { status: 404, message: 'User not found' }; + } + + return { user }; + } catch (error) { + console.error("Discord Authentication Failed:", error.message); + throw { status: 500, message: 'Authentication failed' }; + } +} + +; +module.exports = { handleFirebaseAuth, handleDiscordAuth}; \ No newline at end of file From db324e06016f6cdedf0567cadca5297936ab44e1 Mon Sep 17 00:00:00 2001 From: vm2052 Date: Thu, 1 May 2025 11:56:41 +0300 Subject: [PATCH 012/135] fixed as per comments --- config/development.json | 3 +-- config/production.json | 3 +-- oauth.js | 2 +- routes/users.js | 19 ++++++++++--------- services/auth_service.js | 12 ++++-------- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/config/development.json b/config/development.json index 80354df..d4a4b35 100644 --- a/config/development.json +++ b/config/development.json @@ -8,6 +8,5 @@ "cors": { "allowedOrigins": ["http://localhost:5173", "https://dev.catbytes.io"] }, - "platform_url": "https://dev.catbytes.io/", - "discord_redirect_url":"https://dev.catbytes.io/auth/discord/callback" + "platform_url": "https://dev.catbytes.io/" } \ No newline at end of file diff --git a/config/production.json b/config/production.json index 9a55eeb..9a5264b 100644 --- a/config/production.json +++ b/config/production.json @@ -8,6 +8,5 @@ "cors": { "allowedOrigins": ["https://catbytes.io"] }, - "platform_url": "https://catbytes.io/", - "discord_redirect_url":"https://catbytes.io/auth/discord/callback" + "platform_url": "https://catbytes.io/" } \ No newline at end of file diff --git a/oauth.js b/oauth.js index d6636e2..0e02f7f 100644 --- a/oauth.js +++ b/oauth.js @@ -18,7 +18,7 @@ async function initOAuth() { const params = await loadSecrets(config.aws.param_store_region, ['/catbytes_webplatform/oauth_creds'], true, true); discordClientId = params['oauth_creds']['discord']['clientID']; discordClientSecret = params['oauth_creds']['discord']['clientSecret']; - discordRedirectUrl = config.discord_redirect_url; + discordRedirectUrl = `${config.platform_url}auth/discord/callback`; } discordAuth.discordClientId = discordClientId; diff --git a/routes/users.js b/routes/users.js index 741afec..6426580 100644 --- a/routes/users.js +++ b/routes/users.js @@ -14,25 +14,26 @@ router.use(express.json()); router.post("/users/login", async (req, res) => { const firebaseToken = req.headers['firebase_token'] || null; const discordCode = req.headers['discord_code'] || null; - if (!firebaseToken && !discordCode) { - return respondWithError(res, 401, 'No token provided or invalid token'); - } try { - let result; + let user; if (firebaseToken) { - result = await authService.handleFirebaseAuth(firebaseToken); + user = await authService.handleFirebaseAuth(firebaseToken); } else if (discordCode) { - result = await authService.handleDiscordAuth(discordCode); + user = await authService.handleDiscordAuth(discordCode); } + else return respondWithError(res, 401, 'No token provided or invalid token'); - res.cookie('userUID', result.user.firebaseId, { httpOnly: true, secure: true, sameSite: 'none' }); - res.status(200).json({ user: result.user }); + res.cookie('userUID', user.firebaseId, { httpOnly: true, secure: true, sameSite: 'none' }); + res.status(200).json({ user: user }); } catch (error) { console.error("Authentication Error:", error.message); - return respondWithError(res, error.status || 500, error.message); + if (error.status) { + return respondWithError(res, error.status, error.message); + } + return respondWithError(res, 500, "Authentication failed"); } }); diff --git a/services/auth_service.js b/services/auth_service.js index 72dfbfc..fdc7fb9 100644 --- a/services/auth_service.js +++ b/services/auth_service.js @@ -34,14 +34,12 @@ async function handleFirebaseAuth(firebaseToken) { await userService.updateUserById(user.id, { firebase_id: firebaseId }); - return { user }; + return user; } catch (error) { console.error("Firebase Token Verification Failed:", error.message); throw { status: 401, message: 'Unauthorized' }; } -} - -; +}; async function handleDiscordAuth(code){ try { @@ -76,9 +74,7 @@ async function handleDiscordAuth(code){ return { user }; } catch (error) { console.error("Discord Authentication Failed:", error.message); - throw { status: 500, message: 'Authentication failed' }; + throw { status: 401, message: 'Unauthorized' }; } -} - -; +}; module.exports = { handleFirebaseAuth, handleDiscordAuth}; \ No newline at end of file From 24ba55ffaa153ddf797f9bbd39e2a0044aad2568 Mon Sep 17 00:00:00 2001 From: vm2052 Date: Thu, 1 May 2025 12:01:03 +0300 Subject: [PATCH 013/135] fixed eslint --- services/auth_service.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/auth_service.js b/services/auth_service.js index fdc7fb9..8df8700 100644 --- a/services/auth_service.js +++ b/services/auth_service.js @@ -39,7 +39,9 @@ async function handleFirebaseAuth(firebaseToken) { console.error("Firebase Token Verification Failed:", error.message); throw { status: 401, message: 'Unauthorized' }; } -}; +} + +; async function handleDiscordAuth(code){ try { @@ -76,5 +78,7 @@ async function handleDiscordAuth(code){ console.error("Discord Authentication Failed:", error.message); throw { status: 401, message: 'Unauthorized' }; } -}; +} + +; module.exports = { handleFirebaseAuth, handleDiscordAuth}; \ No newline at end of file From 52c771e9e85c711f04888e269ce62ebd901deda8 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Tue, 29 Apr 2025 21:24:52 +0200 Subject: [PATCH 014/135] wip: setting up grafana integration --- .gitignore | 1 + Dockerfile | 2 +- config.alloy | 36 ++++++++++++++++++++++++ db.js | 2 +- docker-compose.dev.yml | 34 ++++++++++++++++++++-- docs/how-tos/setup_local_postgresql.md | 2 +- logger.js | 27 +++++++++++++----- loki-config.yml | 39 ++++++++++++++++++++++++++ server.js | 3 +- services/mailer_service.js | 2 +- 10 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 config.alloy create mode 100644 loki-config.yml diff --git a/.gitignore b/.gitignore index 957d190..2a2f0d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules .env +.env.local .DS_Store /.vs .vscode/ diff --git a/Dockerfile b/Dockerfile index cc54451..515c8e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,5 +6,5 @@ COPY package.json package-lock.json ./ RUN npm install COPY . . -EXPOSE 3000 +EXPOSE 8080 CMD ["node", "server.js"] \ No newline at end of file diff --git a/config.alloy b/config.alloy new file mode 100644 index 0000000..60650da --- /dev/null +++ b/config.alloy @@ -0,0 +1,36 @@ +// Discover Docker containers and extract metadata. +discovery.docker "linux" { + host = "unix:///var/run/docker.sock" +} + +// Define a relabeling rule to create a service name from the container name. +discovery.relabel "logs_integrations_docker" { + targets = discovery.docker.linux.targets + + rule { + source_labels = ["__meta_docker_container_name"] + regex = "/(.*)" + target_label = "service_name" + } + + } + +// Configure a loki.source.docker component to collect logs from Docker containers. +loki.source.docker "default" { + host = "unix:///var/run/docker.sock" + targets = discovery.relabel.logs_integrations_docker.output + labels = {"platform" = "docker"} + forward_to = [loki.write.cloud.receiver] +} + +// Send logs to Grafana Cloud Loki. +loki.write "cloud" { + endpoint { + url = sys.env("GRAFANA_LOKI_URL") + + basic_auth { + username = sys.env("GRAFANA_LOKI_USERNAME") + password = sys.env("GRAFANA_CLOUD_API_KEY") + } + } +} \ No newline at end of file diff --git a/db.js b/db.js index ea52a3a..5e25f56 100644 --- a/db.js +++ b/db.js @@ -2,7 +2,7 @@ const knex = require('knex'); const { loadSecrets } = require("./aws/ssm-helper"); const config = require('config'); -require("dotenv").config(); +require('dotenv').config({ path: '.env.local' }); let databaseUsername, databaseHost, databaseName, databasePassword, databasePort; let knexInstance = null; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index ff2b8cf..436716e 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,10 +1,40 @@ version: '2' services: app: + container_name: catbytes_webplatform build: . environment: NODE_ENV: development + ports: + - '8080:8080' command: npx nodemon --exec npm start + volumes: + - ~/.aws:/root/.aws:ro + + loki: + image: grafana/loki:latest + container_name: loki + ports: + - "3100:3100" + volumes: + - ./loki-config.yml:/etc/loki/config.yml + command: -config.file=/etc/loki/config.yml + + alloy: + image: grafana/alloy:latest + container_name: alloy ports: - - '3000:3000' - network_mode: "host" \ No newline at end of file + - '12345:12345' + volumes: + - './config.alloy:/etc/alloy/config.alloy' + - /var/run/docker.sock:/var/run/docker.sock + - /var/lib/docker/containers:/var/lib/docker/containers:ro + environment: + GRAFANA_LOKI_URL: https://logs-prod-035.grafana.net/loki/api/v1/push + GRAFANA_LOKI_USERNAME: 1191028 + GRAFANA_CLOUD_API_KEY: ${GRAFANA_CLOUD_API_KEY} + command: + - run + - --server.http.listen-addr=0.0.0.0:12345 + - --storage.path=/var/lib/alloy/data + - /etc/alloy/config.alloy diff --git a/docs/how-tos/setup_local_postgresql.md b/docs/how-tos/setup_local_postgresql.md index bdab9e2..10fd7b1 100644 --- a/docs/how-tos/setup_local_postgresql.md +++ b/docs/how-tos/setup_local_postgresql.md @@ -52,7 +52,7 @@ Step 2. Create a database config (e.x. `db.js`) ```javascript const { Pool } = require("pg"); -require("dotenv").config(); +require('dotenv').config({ path: '.env.local' }); const pool = new Pool({ user: process.env.DB_USER, diff --git a/logger.js b/logger.js index fcb80a8..c35500e 100644 --- a/logger.js +++ b/logger.js @@ -1,17 +1,21 @@ const path = require('path'); const pino = require('pino'); -require("dotenv").config(); +require('dotenv').config({ path: '.env.local' }); + +// If LOGGER_ENV is set to 'local', use pretty print for local development +// Otherwise, use JSON and formatiing for deployed service - e.g. Grafana Loki +const isGrafanaLogging = process.env.LOGGER_ENV !== "local"; const baseLogger = pino({ - level: process.env.DEFAULT_LOG_LEVEL || process.env.NODE_ENV !== 'production' - ? 'debug' - : 'info', - transport: process.env.NODE_ENV !== 'production' + level: isGrafanaLogging + ? 'info' + : 'debug', + transport: !isGrafanaLogging ? { level: 'debug', - target: 'pino-pretty', // pretty output for local/dev + target: 'pino-pretty', // pretty output for local options: { colorize: true, ignore: 'pid,hostname,module', @@ -22,7 +26,16 @@ const baseLogger = pino({ }); function getLogger(callerFilename) { - return baseLogger.child({ module: path.basename(callerFilename) }); + const childOptions = { + module: path.basename(callerFilename) + }; + + // add environment information if logging to Grafana + if (isGrafanaLogging) { + childOptions.env = process.env.NODE_ENV; + } + + return baseLogger.child(childOptions); } module.exports = getLogger; diff --git a/loki-config.yml b/loki-config.yml new file mode 100644 index 0000000..0efbec8 --- /dev/null +++ b/loki-config.yml @@ -0,0 +1,39 @@ +# This is a complete configuration to deploy Loki backed by the filesystem. +# The index will be shipped to the storage via tsdb-shipper. + +auth_enabled: false + +limits_config: + allow_structured_metadata: true + volume_enabled: true + +server: + http_listen_port: 3100 + +common: + ring: + instance_addr: 0.0.0.0 + kvstore: + store: inmemory + replication_factor: 1 + path_prefix: /tmp/loki + +schema_config: + configs: + - from: 2020-05-15 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +storage_config: + tsdb_shipper: + active_index_directory: /tmp/loki/index + cache_location: /tmp/loki/index_cache + filesystem: + directory: /tmp/loki/chunks + +pattern_ingester: + enabled: true \ No newline at end of file diff --git a/server.js b/server.js index 7f6601c..89c6deb 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,3 @@ -require("dotenv").config(); const config = require('config'); const express = require("express"); const cookieParser = require('cookie-parser'); @@ -12,6 +11,8 @@ const { authenticate } = require("./middleware/authentication"); const pinoHttp = require('pino-http'); const { baseLogger } = require('./logger'); +require('dotenv').config({ path: '.env.local' }); + const app = express(); // Middleware diff --git a/services/mailer_service.js b/services/mailer_service.js index 25cd951..75d1317 100644 --- a/services/mailer_service.js +++ b/services/mailer_service.js @@ -6,7 +6,7 @@ const { APPL_STATUSES } = require("../utils"); const discordService = require("../services/discord_bot_service"); const logger = require('../logger')(__filename); -require("dotenv").config(); +require('dotenv').config({ path: '.env.local' }); const mailerConfig = config.mailer; const webplatformUrl = config.platform_url; From 15c2aeb31f6cfc8766a2d119c2f4786fad1aa2ec Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Thu, 1 May 2025 16:54:35 +0200 Subject: [PATCH 015/135] added base docker-compose file, updated docker-compose files for dev and prod --- docker-compose.base.yml | 40 ++++++++++++++++++++++++++++++++++++++++ docker-compose.dev.yml | 36 ++++-------------------------------- docker-compose.prod.yml | 12 +++++++----- 3 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 docker-compose.base.yml diff --git a/docker-compose.base.yml b/docker-compose.base.yml new file mode 100644 index 0000000..b5b76b9 --- /dev/null +++ b/docker-compose.base.yml @@ -0,0 +1,40 @@ +version: '2' +services: + app: + container_name: catbytes_webplatform + build: . + environment: + NODE_ENV: ${NODE_ENV} + ports: + - '8080:8080' + command: npx nodemon --exec npm start + volumes: + - ~/.aws:/root/.aws:ro + + loki: + image: grafana/loki:latest + container_name: loki + ports: + - "3100:3100" + volumes: + - ./loki-config.yml:/etc/loki/config.yml + command: -config.file=/etc/loki/config.yml + + alloy: + image: grafana/alloy:latest + container_name: alloy + ports: + - '12345:12345' + volumes: + - './config.alloy:/etc/alloy/config.alloy' + - /var/run/docker.sock:/var/run/docker.sock + - /var/lib/docker/containers:/var/lib/docker/containers:ro + environment: + GRAFANA_LOKI_URL: ${GRAFANA_LOKI_URL} + GRAFANA_LOKI_USERNAME: ${GRAFANA_LOKI_USERNAME} + GRAFANA_CLOUD_API_KEY: ${GRAFANA_CLOUD_API_KEY} + command: + - run + - --server.http.listen-addr=0.0.0.0:12345 + - --storage.path=/var/lib/alloy/data + - /etc/alloy/config.alloy diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 436716e..ae7c1cd 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,40 +1,12 @@ version: '2' + services: app: - container_name: catbytes_webplatform - build: . + command: npx nodemon --exec npm start environment: NODE_ENV: development - ports: - - '8080:8080' - command: npx nodemon --exec npm start - volumes: - - ~/.aws:/root/.aws:ro - - loki: - image: grafana/loki:latest - container_name: loki - ports: - - "3100:3100" - volumes: - - ./loki-config.yml:/etc/loki/config.yml - command: -config.file=/etc/loki/config.yml - + alloy: - image: grafana/alloy:latest - container_name: alloy - ports: - - '12345:12345' - volumes: - - './config.alloy:/etc/alloy/config.alloy' - - /var/run/docker.sock:/var/run/docker.sock - - /var/lib/docker/containers:/var/lib/docker/containers:ro environment: GRAFANA_LOKI_URL: https://logs-prod-035.grafana.net/loki/api/v1/push - GRAFANA_LOKI_USERNAME: 1191028 - GRAFANA_CLOUD_API_KEY: ${GRAFANA_CLOUD_API_KEY} - command: - - run - - --server.http.listen-addr=0.0.0.0:12345 - - --storage.path=/var/lib/alloy/data - - /etc/alloy/config.alloy + GRAFANA_LOKI_USERNAME: 1191028 \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 019482b..8c797bb 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,11 +1,13 @@ version: '2' + services: app: - build: . + command: npx nodemon --exec npm start environment: NODE_ENV: production LOGGING_HTTP_REQUESTS: true - command: npx nodemon --exec npm start - ports: - - '3000:3000' - network_mode: "host" \ No newline at end of file + + alloy: + environment: + GRAFANA_LOKI_URL: https://logs-prod-035.grafana.net/loki/api/v1/push + GRAFANA_LOKI_USERNAME: 1191028 \ No newline at end of file From b744fdc5384b37ff6f010f7b4731ce2ecfcb904f Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Thu, 1 May 2025 18:38:12 +0200 Subject: [PATCH 016/135] removed loki --- docker-compose.base.yml | 9 --------- loki-config.yml | 39 --------------------------------------- 2 files changed, 48 deletions(-) delete mode 100644 loki-config.yml diff --git a/docker-compose.base.yml b/docker-compose.base.yml index b5b76b9..17d9c91 100644 --- a/docker-compose.base.yml +++ b/docker-compose.base.yml @@ -11,15 +11,6 @@ services: volumes: - ~/.aws:/root/.aws:ro - loki: - image: grafana/loki:latest - container_name: loki - ports: - - "3100:3100" - volumes: - - ./loki-config.yml:/etc/loki/config.yml - command: -config.file=/etc/loki/config.yml - alloy: image: grafana/alloy:latest container_name: alloy diff --git a/loki-config.yml b/loki-config.yml deleted file mode 100644 index 0efbec8..0000000 --- a/loki-config.yml +++ /dev/null @@ -1,39 +0,0 @@ -# This is a complete configuration to deploy Loki backed by the filesystem. -# The index will be shipped to the storage via tsdb-shipper. - -auth_enabled: false - -limits_config: - allow_structured_metadata: true - volume_enabled: true - -server: - http_listen_port: 3100 - -common: - ring: - instance_addr: 0.0.0.0 - kvstore: - store: inmemory - replication_factor: 1 - path_prefix: /tmp/loki - -schema_config: - configs: - - from: 2020-05-15 - store: tsdb - object_store: filesystem - schema: v13 - index: - prefix: index_ - period: 24h - -storage_config: - tsdb_shipper: - active_index_directory: /tmp/loki/index - cache_location: /tmp/loki/index_cache - filesystem: - directory: /tmp/loki/chunks - -pattern_ingester: - enabled: true \ No newline at end of file From be362ff90cf398e8dcc7c57707ed4da65c9b3d13 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Thu, 1 May 2025 18:41:39 +0200 Subject: [PATCH 017/135] updated docker-compose files --- docker-compose.dev.yml | 6 ++++++ docker-compose.prod.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index ae7c1cd..00ee585 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -2,11 +2,17 @@ version: '2' services: app: + extends: + file: docker-compose.base.yml + service: app command: npx nodemon --exec npm start environment: NODE_ENV: development alloy: + extends: + file: docker-compose.base.yml + service: alloy environment: GRAFANA_LOKI_URL: https://logs-prod-035.grafana.net/loki/api/v1/push GRAFANA_LOKI_USERNAME: 1191028 \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 8c797bb..15abc40 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -2,12 +2,18 @@ version: '2' services: app: + extends: + file: docker-compose.base.yml + service: app command: npx nodemon --exec npm start environment: NODE_ENV: production LOGGING_HTTP_REQUESTS: true alloy: + extends: + file: docker-compose.base.yml + service: alloy environment: GRAFANA_LOKI_URL: https://logs-prod-035.grafana.net/loki/api/v1/push GRAFANA_LOKI_USERNAME: 1191028 \ No newline at end of file From c409f3962e240b879a12a0638fdad16cf8e25dfb Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Thu, 8 May 2025 17:31:07 +0200 Subject: [PATCH 018/135] change log level formatting --- logger.js | 7 ++++++- package-lock.json | 18 +++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/logger.js b/logger.js index c35500e..6b3100b 100644 --- a/logger.js +++ b/logger.js @@ -22,7 +22,12 @@ const baseLogger = pino({ messageFormat: '[{module}] {msg}' } } - : undefined // raw json for prod/Loki + : undefined, // raw json for prod/Loki + formatters: { + level: (label) => { + return { level: label.toLowerCase() }; + }, + } }); function getLogger(callerFilename) { diff --git a/package-lock.json b/package-lock.json index ec864b7..13f8a56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2552,6 +2552,15 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/axios": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", @@ -2578,15 +2587,6 @@ "node": ">= 6" } }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", From 03620f67cf82673c7e211045f57e3b5b456e23de Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Thu, 8 May 2025 18:42:28 +0200 Subject: [PATCH 019/135] added logs info to readme --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 514d8fe..db213c9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,17 @@ Located in the `/docs` directory. `*.json` file is used for rendering, `*.yaml` Also published on GitHub pages: https://catbytes-community.github.io/webplatform-backend/ +### Monitoring +Service logs are sent to Grafana. Main dashboard: https://catbytes.grafana.net/goto/S3QCkuxHR?orgId=1 +Here are also some predefined queries to search in logs: + +| Environment | Url | +| -------- | ------- | +| DEV | https://catbytes.grafana.net/goto/dkl-MubNg?orgId=1 | +| PROD | https://catbytes.grafana.net/goto/cvZfGXbNR?orgId=1 | + +Insert value you're looking for in the 'Line contains' box. + ### Documentation for developers Located in the `/docs/how-tos` directory. All important information on local run, deployments, etc is stored there. From 5f182d645823e5fa1166bb51ecc53be60fe78e84 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Wed, 25 Jun 2025 11:18:03 +0200 Subject: [PATCH 020/135] fixed custom headers naming and logging for users/login --- routes/users.js | 8 ++++---- services/auth_service.js | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/routes/users.js b/routes/users.js index 8fad711..efa315c 100644 --- a/routes/users.js +++ b/routes/users.js @@ -7,14 +7,15 @@ const { ROLE_NAMES } = require("../utils"); const {verifyOwnership, verifyRole, OWNED_ENTITIES} = require("../middleware/authorization"); const { isValidIntegerId, respondWithError, isUniqueConstraintViolation, isNotNullConstraintViolation, parseColumnNameFromConstraint } = require("./helpers"); -const logger = require('../logger')(__filename); + + const logger = require('../logger')(__filename); router.use(express.json()); // POST /users/login router.post("/users/login", async (req, res) => { - const firebaseToken = req.headers['firebase_token'] || null; - const discordCode = req.headers['discord_code'] || null; + const firebaseToken = req.get('X-Firebase-Token') || null; + const discordCode = req.get('X-Discord-Code') || null; try { let user; @@ -30,7 +31,6 @@ router.post("/users/login", async (req, res) => { res.status(200).json({ user: user }); } catch (error) { - logger.error("Authentication Error:", error.message); if (error.status) { return respondWithError(res, error.status, error.message); } diff --git a/services/auth_service.js b/services/auth_service.js index 8df8700..fa925c1 100644 --- a/services/auth_service.js +++ b/services/auth_service.js @@ -3,9 +3,10 @@ const axios = require('axios'); const querystring = require('querystring'); const userService = require('../services/user_service'); const applService = require("../services/applications_service"); - const {discordAuth} = require ("../oauth.js"); +const logger = require('../logger')(__filename); + async function handleFirebaseAuth(firebaseToken) { try { const decodedToken = await admin.auth().verifyIdToken(firebaseToken); @@ -36,7 +37,7 @@ async function handleFirebaseAuth(firebaseToken) { return user; } catch (error) { - console.error("Firebase Token Verification Failed:", error.message); + logger.error({ error: error.message }, "Firebase Token Verification Failed"); throw { status: 401, message: 'Unauthorized' }; } } @@ -75,7 +76,7 @@ async function handleDiscordAuth(code){ return { user }; } catch (error) { - console.error("Discord Authentication Failed:", error.message); + logger.error({ error: error.message }, "Discord Authentication Failed", ); throw { status: 401, message: 'Unauthorized' }; } } From aa4f7d8864ba36892972967862615acc13a703e0 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Wed, 25 Jun 2025 11:22:51 +0200 Subject: [PATCH 021/135] updated Swagger --- docs/catbytes_api_swagger.json | 6 +++--- docs/catbytes_api_swagger.yaml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index d64faaa..1f11067 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -424,7 +424,7 @@ "operationId": "loginUser", "parameters": [ { - "name": "firebase_token", + "name": "X-Firebase-Token", "in": "header", "description": "Firebase ID token used for Firebase authentication", "schema": { @@ -433,7 +433,7 @@ "required": false }, { - "name": "discord_code", + "name": "X-Discord-Token", "in": "header", "description": "Authorization code returned by Discord OAuth2 flow", "schema": { @@ -1812,7 +1812,7 @@ "tags": [ "discord integration" ], - "summary": "Generate a Discord invite link", + "summary": "✅ Generate a Discord invite link", "description": "Generates a unique invite link for a user to join the Discord server. Validates if the user already exists on the server and checks if 7 days have passed since the last invite. Requires authentication via a `userUID` cookie.", "responses": { "200": { diff --git a/docs/catbytes_api_swagger.yaml b/docs/catbytes_api_swagger.yaml index 2b5ee41..0b86ac9 100644 --- a/docs/catbytes_api_swagger.yaml +++ b/docs/catbytes_api_swagger.yaml @@ -284,13 +284,13 @@ paths: description: Authenticates a user based on a provided Firebase token sent in the header. On successful authentication, sets a secure 'userUID' cookie. operationId: loginUser parameters: - - name: firebase_token + - name: X-Firebase-Token in: header description: Firebase ID token used for Firebase authentication schema: type: string required: false - - name: discord_code + - name: X-Discord-Code in: header description: Authorization code returned by Discord OAuth2 flow schema: @@ -1209,7 +1209,7 @@ paths: post: tags: - discord integration - summary: Generate a Discord invite link + summary: ✅ Generate a Discord invite link description: Generates a unique invite link for a user to join the Discord server. Validates if the user already exists on the server and checks if 7 days have passed since the last invite. Requires authentication via a `userUID` cookie. responses: '200': From 542411f2a0677204c77f22937ec250c3c22e35fe Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Wed, 25 Jun 2025 11:24:16 +0200 Subject: [PATCH 022/135] fixed linter errors --- routes/users.js | 2 +- services/auth_service.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/routes/users.js b/routes/users.js index efa315c..7dc6cb0 100644 --- a/routes/users.js +++ b/routes/users.js @@ -8,7 +8,7 @@ const {verifyOwnership, verifyRole, OWNED_ENTITIES} = require("../middleware/aut const { isValidIntegerId, respondWithError, isUniqueConstraintViolation, isNotNullConstraintViolation, parseColumnNameFromConstraint } = require("./helpers"); - const logger = require('../logger')(__filename); +const logger = require('../logger')(__filename); router.use(express.json()); diff --git a/services/auth_service.js b/services/auth_service.js index fa925c1..567a0ab 100644 --- a/services/auth_service.js +++ b/services/auth_service.js @@ -3,6 +3,7 @@ const axios = require('axios'); const querystring = require('querystring'); const userService = require('../services/user_service'); const applService = require("../services/applications_service"); + const {discordAuth} = require ("../oauth.js"); const logger = require('../logger')(__filename); From f2f7fbdd3ba4a367fa0c811cbae68d6c94b277c9 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Wed, 25 Jun 2025 11:46:01 +0200 Subject: [PATCH 023/135] updated vulnerable dependencies & installed jest --- package-lock.json | 8508 +++++++++++++++++++++++++++++++-------------- package.json | 4 +- 2 files changed, 5884 insertions(+), 2628 deletions(-) diff --git a/package-lock.json b/package-lock.json index 13f8a56..5dddc17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,23 @@ }, "devDependencies": { "@stylistic/eslint-plugin-js": "^2.12.1", - "prisma": "^5.21.1" + "jest": "^29.7.0", + "prisma": "^5.21.1", + "supertest": "^7.1.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@aws-crypto/sha256-browser": { @@ -704,3754 +720,6301 @@ } } }, - "node_modules/@discordjs/builders": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.1.tgz", - "integrity": "sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==", - "license": "Apache-2.0", + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", "dependencies": { - "@discordjs/formatters": "^0.6.0", - "@discordjs/util": "^1.1.1", - "@sapphire/shapeshift": "^4.0.0", - "discord-api-types": "^0.37.119", - "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.4", - "tslib": "^2.6.3" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "node": ">=6.9.0" } }, - "node_modules/@discordjs/collection": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", - "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", - "license": "Apache-2.0", + "node_modules/@babel/compat-data": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.11.0" + "node": ">=6.9.0" } }, - "node_modules/@discordjs/formatters": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.0.tgz", - "integrity": "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==", - "license": "Apache-2.0", + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "dev": true, + "license": "MIT", "dependencies": { - "discord-api-types": "^0.37.114" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": ">=16.11.0" + "node": ">=6.9.0" }, "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@discordjs/rest": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.3.tgz", - "integrity": "sha512-+SO4RKvWsM+y8uFHgYQrcTl/3+cY02uQOH7/7bKbVZsTfrfpoE62o5p+mmV+s7FVhTX82/kQUGGbu4YlV60RtA==", - "license": "Apache-2.0", + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@discordjs/collection": "^2.1.1", - "@discordjs/util": "^1.1.1", - "@sapphire/async-queue": "^1.5.3", - "@sapphire/snowflake": "^3.5.3", - "@vladfrangu/async_event_emitter": "^2.4.6", - "discord-api-types": "^0.37.119", - "magic-bytes.js": "^1.10.0", - "tslib": "^2.6.3", - "undici": "6.21.1" + "ms": "^2.1.3" }, "engines": { - "node": ">=18" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", - "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@discordjs/util": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", - "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@discordjs/ws": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.1.tgz", - "integrity": "sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ==", - "license": "Apache-2.0", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@discordjs/collection": "^2.1.0", - "@discordjs/rest": "^2.4.3", - "@discordjs/util": "^1.1.0", - "@sapphire/async-queue": "^1.5.2", - "@types/ws": "^8.5.10", - "@vladfrangu/async_event_emitter": "^2.2.4", - "discord-api-types": "^0.37.119", - "tslib": "^2.6.2", - "ws": "^8.17.0" + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "node": ">=6.9.0" } }, - "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", - "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@babel/core": "^7.0.0" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", "dependencies": { - "@eslint/object-schema": "^2.1.5", - "debug": "^4.3.1", - "minimatch": "^3.1.2" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ms": "^2.1.3" + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=6.0" + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/config-array/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@eslint/core": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", - "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.15" + "@babel/helper-plugin-utils": "^7.12.13" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ms": "^2.1.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=6.0" + "node": ">=6.9.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", - "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", "dependencies": { - "levn": "^0.4.1" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@fastify/busboy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.0.tgz", - "integrity": "sha512-yHmUtGwEbW6HsKpPqT140/L6GpHtquHogRLgtanJFep3UAfDkE0fQfC49U+F9irCAoJVlv3M7VSp4rrtO4LnfA==", - "license": "MIT" + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@firebase/app-check-interop-types": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", - "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", - "license": "Apache-2.0" + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@firebase/app-types": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", - "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", - "license": "Apache-2.0" + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", - "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", - "license": "Apache-2.0" + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@firebase/component": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.11.tgz", - "integrity": "sha512-eQbeCgPukLgsKD0Kw5wQgsMDX5LeoI1MIrziNDjmc6XDq5ZQnuUymANQgAb2wp1tSF9zDSXyxJmIUXaKgN58Ug==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", "dependencies": { - "@firebase/util": "1.10.2", - "tslib": "^2.1.0" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@firebase/database": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.10.tgz", - "integrity": "sha512-sWp2g92u7xT4BojGbTXZ80iaSIaL6GAL0pwvM0CO/hb0nHSnABAqsH7AhnWGsGvXuEvbPr7blZylPaR9J+GSuQ==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", "dependencies": { - "@firebase/app-check-interop-types": "0.3.3", - "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.6.11", - "@firebase/logger": "0.4.4", - "@firebase/util": "1.10.2", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@firebase/database-compat": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.1.tgz", - "integrity": "sha512-IsFivOjdE1GrjTeKoBU/ZMenESKDXidFDzZzHBPQ/4P20ptGdrl3oLlWrV/QJqJ9lND4IidE3z4Xr5JyfUW1vg==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@firebase/component": "0.6.11", - "@firebase/database": "1.0.10", - "@firebase/database-types": "1.0.7", - "@firebase/logger": "0.4.4", - "@firebase/util": "1.10.2", - "tslib": "^2.1.0" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@firebase/database-types": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.7.tgz", - "integrity": "sha512-I7zcLfJXrM0WM+ksFmFdAMdlq/DFmpeMNa+/GNsLyFo5u/lX5zzkPzGe3srVWqaBQBY5KprylDGxOsP6ETfL0A==", - "license": "Apache-2.0", + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", "dependencies": { - "@firebase/app-types": "0.9.3", - "@firebase/util": "1.10.2" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@firebase/logger": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", - "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", - "license": "Apache-2.0", + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" } }, - "node_modules/@firebase/util": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.2.tgz", - "integrity": "sha512-qnSHIoE9FK+HYnNhTI8q14evyqbc/vHRivfB4TgCIUOl4tosmKSQlp7ltymOlMP4xVIJTg5wrkfcZ60X4nUf7Q==", - "license": "Apache-2.0", + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.1.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@google-cloud/firestore": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.0.tgz", - "integrity": "sha512-88uZ+jLsp1aVMj7gh3EKYH1aulTAMFAp8sH/v5a9w8q8iqSG27RiWLoxSAFr/XocZ9hGiWH1kEnBw+zl3xAgNA==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/api": "^1.3.0", - "fast-deep-equal": "^3.1.1", - "functional-red-black-tree": "^1.0.1", - "google-gax": "^4.3.3", - "protobufjs": "^7.2.6" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@google-cloud/paginator": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", - "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@discordjs/builders": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.2.tgz", + "integrity": "sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A==", "license": "Apache-2.0", - "optional": true, "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" + "@discordjs/formatters": "^0.6.1", + "@discordjs/util": "^1.1.1", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.38.1", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@google-cloud/projectify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", - "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", "license": "Apache-2.0", - "optional": true, "engines": { - "node": ">=14.0.0" + "node": ">=16.11.0" } }, - "node_modules/@google-cloud/promisify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", - "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "node_modules/@discordjs/formatters": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz", + "integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==", "license": "Apache-2.0", - "optional": true, + "dependencies": { + "discord-api-types": "^0.38.1" + }, "engines": { - "node": ">=14" + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@google-cloud/storage": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.14.0.tgz", - "integrity": "sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==", + "node_modules/@discordjs/rest": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.5.1.tgz", + "integrity": "sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==", "license": "Apache-2.0", - "optional": true, "dependencies": { - "@google-cloud/paginator": "^5.0.0", - "@google-cloud/projectify": "^4.0.0", - "@google-cloud/promisify": "^4.0.0", - "abort-controller": "^3.0.0", - "async-retry": "^1.3.3", - "duplexify": "^4.1.3", - "fast-xml-parser": "^4.4.1", - "gaxios": "^6.0.2", - "google-auth-library": "^9.6.3", - "html-entities": "^2.5.2", - "mime": "^3.0.0", - "p-limit": "^3.0.1", - "retry-request": "^7.0.0", - "teeny-request": "^9.0.0", - "uuid": "^8.0.0" + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.1.1", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.38.1", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" }, "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage/node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "license": "MIT", - "optional": true, - "bin": { - "mime": "cli.js" + "node": ">=18" }, - "engines": { - "node": ">=10.0.0" + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@google-cloud/storage/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@grpc/grpc-js": { - "version": "1.12.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.4.tgz", - "integrity": "sha512-NBhrxEWnFh0FxeA0d//YP95lRFsSx2TNLEUQg4/W+5f/BMxcCjgOOIT24iD+ZB/tZw057j44DaIxja7w4XMrhg==", + "node_modules/@discordjs/util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", + "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@grpc/proto-loader": "^0.7.13", - "@js-sdsl/ordered-map": "^4.4.2" - }, "engines": { - "node": ">=12.10.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", - "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "node_modules/@discordjs/ws": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", "license": "Apache-2.0", - "optional": true, "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.38.1", + "tslib": "^2.6.2", + "ws": "^8.17.0" }, "engines": { - "node": ">=6" + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", "license": "Apache-2.0", - "peer": true, "engines": { - "node": ">=18.18.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "peer": true, "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=18.18.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "peer": true, "engines": { - "node": ">=18.18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "peer": true, "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "license": "Apache-2.0", "peer": true, - "engines": { - "node": ">=18.18" + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", + "node_modules/@eslint/config-array/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=12" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "node_modules/@eslint/config-array/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "peer": true + }, + "node_modules/@eslint/core": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", + "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "ms": "^2.1.3" }, "engines": { - "node": ">=12" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "peer": true + }, + "node_modules/@eslint/js": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "dev": true, + "license": "MIT", + "peer": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "levn": "^0.4.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } + "node_modules/@fastify/busboy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.0.tgz", + "integrity": "sha512-yHmUtGwEbW6HsKpPqT140/L6GpHtquHogRLgtanJFep3UAfDkE0fQfC49U+F9irCAoJVlv3M7VSp4rrtO4LnfA==", + "license": "MIT" }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/component": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.11.tgz", + "integrity": "sha512-eQbeCgPukLgsKD0Kw5wQgsMDX5LeoI1MIrziNDjmc6XDq5ZQnuUymANQgAb2wp1tSF9zDSXyxJmIUXaKgN58Ug==", "license": "Apache-2.0", - "optional": true, + "dependencies": { + "@firebase/util": "1.10.2", + "tslib": "^2.1.0" + }, "engines": { - "node": ">=8.0.0" + "node": ">=18.0.0" } }, - "node_modules/@prisma/client": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.21.1.tgz", - "integrity": "sha512-3n+GgbAZYjaS/k0M03yQsQfR1APbr411r74foknnsGpmhNKBG49VuUkxIU6jORgvJPChoD4WC4PqoHImN1FP0w==", - "hasInstallScript": true, + "node_modules/@firebase/database": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.10.tgz", + "integrity": "sha512-sWp2g92u7xT4BojGbTXZ80iaSIaL6GAL0pwvM0CO/hb0nHSnABAqsH7AhnWGsGvXuEvbPr7blZylPaR9J+GSuQ==", "license": "Apache-2.0", - "engines": { - "node": ">=16.13" - }, - "peerDependencies": { - "prisma": "*" + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.11", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.2", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/debug": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.21.1.tgz", - "integrity": "sha512-uY8SAhcnORhvgtOrNdvWS98Aq/nkQ9QDUxrWAgW8XrCZaI3j2X7zb7Xe6GQSh6xSesKffFbFlkw0c2luHQviZA==", - "devOptional": true, - "license": "Apache-2.0" + "node_modules/@firebase/database-compat": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.1.tgz", + "integrity": "sha512-IsFivOjdE1GrjTeKoBU/ZMenESKDXidFDzZzHBPQ/4P20ptGdrl3oLlWrV/QJqJ9lND4IidE3z4Xr5JyfUW1vg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.11", + "@firebase/database": "1.0.10", + "@firebase/database-types": "1.0.7", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.2", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@prisma/engines": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.21.1.tgz", - "integrity": "sha512-hGVTldUkIkTwoV8//hmnAAiAchi4oMEKD3aW5H2RrnI50tTdwza7VQbTTAyN3OIHWlK5DVg6xV7X8N/9dtOydA==", - "devOptional": true, - "hasInstallScript": true, + "node_modules/@firebase/database-types": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.7.tgz", + "integrity": "sha512-I7zcLfJXrM0WM+ksFmFdAMdlq/DFmpeMNa+/GNsLyFo5u/lX5zzkPzGe3srVWqaBQBY5KprylDGxOsP6ETfL0A==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.21.1", - "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", - "@prisma/fetch-engine": "5.21.1", - "@prisma/get-platform": "5.21.1" + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.10.2" } }, - "node_modules/@prisma/engines-version": { - "version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36.tgz", - "integrity": "sha512-qvnEflL0//lh44S/T9NcvTMxfyowNeUxTunPcDfKPjyJNrCNf2F1zQLcUv5UHAruECpX+zz21CzsC7V2xAeM7Q==", - "devOptional": true, - "license": "Apache-2.0" + "node_modules/@firebase/logger": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", + "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@prisma/fetch-engine": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.21.1.tgz", - "integrity": "sha512-70S31vgpCGcp9J+mh/wHtLCkVezLUqe/fGWk3J3JWZIN7prdYSlr1C0niaWUyNK2VflLXYi8kMjAmSxUVq6WGQ==", - "devOptional": true, + "node_modules/@firebase/util": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.2.tgz", + "integrity": "sha512-qnSHIoE9FK+HYnNhTI8q14evyqbc/vHRivfB4TgCIUOl4tosmKSQlp7ltymOlMP4xVIJTg5wrkfcZ60X4nUf7Q==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.21.1", - "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", - "@prisma/get-platform": "5.21.1" + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@prisma/get-platform": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.21.1.tgz", - "integrity": "sha512-sRxjL3Igst3ct+e8ya/x//cDXmpLbZQ5vfps2N4tWl4VGKQAmym77C/IG/psSMsQKszc8uFC/q1dgmKFLUgXZQ==", - "devOptional": true, + "node_modules/@google-cloud/firestore": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.0.tgz", + "integrity": "sha512-88uZ+jLsp1aVMj7gh3EKYH1aulTAMFAp8sH/v5a9w8q8iqSG27RiWLoxSAFr/XocZ9hGiWH1kEnBw+zl3xAgNA==", "license": "Apache-2.0", + "optional": true, "dependencies": { - "@prisma/debug": "5.21.1" + "@opentelemetry/api": "^1.3.0", + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.3.3", + "protobufjs": "^7.2.6" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.14.0.tgz", + "integrity": "sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^4.4.1", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.4.tgz", + "integrity": "sha512-NBhrxEWnFh0FxeA0d//YP95lRFsSx2TNLEUQg4/W+5f/BMxcCjgOOIT24iD+ZB/tZw057j44DaIxja7w4XMrhg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@prisma/client": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.21.1.tgz", + "integrity": "sha512-3n+GgbAZYjaS/k0M03yQsQfR1APbr411r74foknnsGpmhNKBG49VuUkxIU6jORgvJPChoD4WC4PqoHImN1FP0w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.21.1.tgz", + "integrity": "sha512-uY8SAhcnORhvgtOrNdvWS98Aq/nkQ9QDUxrWAgW8XrCZaI3j2X7zb7Xe6GQSh6xSesKffFbFlkw0c2luHQviZA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.21.1.tgz", + "integrity": "sha512-hGVTldUkIkTwoV8//hmnAAiAchi4oMEKD3aW5H2RrnI50tTdwza7VQbTTAyN3OIHWlK5DVg6xV7X8N/9dtOydA==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.21.1", + "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", + "@prisma/fetch-engine": "5.21.1", + "@prisma/get-platform": "5.21.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36.tgz", + "integrity": "sha512-qvnEflL0//lh44S/T9NcvTMxfyowNeUxTunPcDfKPjyJNrCNf2F1zQLcUv5UHAruECpX+zz21CzsC7V2xAeM7Q==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.21.1.tgz", + "integrity": "sha512-70S31vgpCGcp9J+mh/wHtLCkVezLUqe/fGWk3J3JWZIN7prdYSlr1C0niaWUyNK2VflLXYi8kMjAmSxUVq6WGQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.21.1", + "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", + "@prisma/get-platform": "5.21.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.21.1.tgz", + "integrity": "sha512-sRxjL3Igst3ct+e8ya/x//cDXmpLbZQ5vfps2N4tWl4VGKQAmym77C/IG/psSMsQKszc8uFC/q1dgmKFLUgXZQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.21.1" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause", "optional": true }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause", + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.8.tgz", + "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.12.tgz", + "integrity": "sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.4.tgz", + "integrity": "sha512-iFh2Ymn2sCziBRLPuOOxRPkuCx/2gBdXtBGuCUFLUe6bWYjKnhHyIPqGeNkLZ5Aco/5GjebRTBFiWID3sDbrKw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^3.0.10", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-stream": "^3.3.1", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.7.tgz", + "integrity": "sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.11", + "@smithy/property-provider": "^3.1.10", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.1.tgz", + "integrity": "sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.7", + "@smithy/querystring-builder": "^3.0.10", + "@smithy/types": "^3.7.1", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-node": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.10.tgz", + "integrity": "sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.10.tgz", + "integrity": "sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.12.tgz", + "integrity": "sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.4.tgz", + "integrity": "sha512-TybiW2LA3kYVd3e+lWhINVu1o26KJbBwOpADnf0L4x/35vLVica77XVR5hvV9+kWeTGeSJ3IHTcYxbRxlbwhsg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.4", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.11", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-middleware": "^3.0.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "3.0.28", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.28.tgz", + "integrity": "sha512-vK2eDfvIXG1U64FEUhYxoZ1JSj4XFbYWkK36iz02i3pFwWiDz1Q7jKhGTBCwx/7KqJNk4VS7d7cDLXFOvP7M+g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.11", + "@smithy/protocol-http": "^4.1.7", + "@smithy/service-error-classification": "^3.0.10", + "@smithy/smithy-client": "^3.4.5", + "@smithy/types": "^3.7.1", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.10.tgz", + "integrity": "sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.10.tgz", + "integrity": "sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.11.tgz", + "integrity": "sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.10", + "@smithy/shared-ini-file-loader": "^3.1.11", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.1.tgz", + "integrity": "sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^3.1.8", + "@smithy/protocol-http": "^4.1.7", + "@smithy/querystring-builder": "^3.0.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.10.tgz", + "integrity": "sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", + "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.10.tgz", + "integrity": "sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.10.tgz", + "integrity": "sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.10.tgz", + "integrity": "sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.11.tgz", + "integrity": "sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.3.tgz", + "integrity": "sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.5.tgz", + "integrity": "sha512-k0sybYT9zlP79sIKd1XGm4TmK0AS1nA2bzDHXx7m0nGi3RQ8dxxQUs4CPkSmQTKAo+KF9aINU3KzpGIpV7UoMw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.4", + "@smithy/middleware-endpoint": "^3.2.4", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "@smithy/util-stream": "^3.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", + "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.10.tgz", + "integrity": "sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^3.0.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "3.0.28", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.28.tgz", + "integrity": "sha512-6bzwAbZpHRFVJsOztmov5PGDmJYsbNSoIEfHSJJyFLzfBGCCChiO3od9k7E/TLgrCsIifdAbB9nqbVbyE7wRUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.10", + "@smithy/smithy-client": "^3.4.5", + "@smithy/types": "^3.7.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "3.0.28", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.28.tgz", + "integrity": "sha512-78ENJDorV1CjOQselGmm3+z7Yqjj5HWCbjzh0Ixuq736dh1oEnD9sAttSBNSLlpZsX8VQnmERqA2fEFlmqWn8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^3.0.12", + "@smithy/credential-provider-imds": "^3.2.7", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/property-provider": "^3.1.10", + "@smithy/smithy-client": "^3.4.5", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.6.tgz", + "integrity": "sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.10.tgz", + "integrity": "sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.10.tgz", + "integrity": "sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^3.0.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.1.tgz", + "integrity": "sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/types": "^3.7.1", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.9.tgz", + "integrity": "sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^3.1.8", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.12.1.tgz", + "integrity": "sha512-5ybogtEgWIGCR6dMnaabztbWyVdAPDsf/5XOk6jBonWug875Q9/a6gm9QxnU3rhdyDEnckWKX7dduwYJMOWrVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT", "optional": true }, - "node_modules/@protobufjs/codegen": { + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause", - "optional": true + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause", + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT", "optional": true }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", "optional": true, "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause", - "optional": true + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause", - "optional": true + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause", - "optional": true + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause", + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT", "optional": true }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause", - "optional": true + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" }, - "node_modules/@sapphire/async-queue": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", - "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "dependencies": { + "@types/node": "*" } }, - "node_modules/@sapphire/shapeshift": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", - "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=v16" + "@types/yargs-parser": "*" } }, - "node_modules/@sapphire/snowflake": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", - "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", + "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", "license": "MIT", "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" } }, - "node_modules/@smithy/abort-controller": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.8.tgz", - "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", - "license": "Apache-2.0", + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "event-target-shim": "^5.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=6.5" } }, - "node_modules/@smithy/config-resolver": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.12.tgz", - "integrity": "sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==", - "license": "Apache-2.0", + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/types": "^3.7.1", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.10", - "tslib": "^2.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.6" } }, - "node_modules/@smithy/core": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.4.tgz", - "integrity": "sha512-iFh2Ymn2sCziBRLPuOOxRPkuCx/2gBdXtBGuCUFLUe6bWYjKnhHyIPqGeNkLZ5Aco/5GjebRTBFiWID3sDbrKw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-serde": "^3.0.10", - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-stream": "^3.3.1", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=16.0.0" + "node": ">=0.4.0" } }, - "node_modules/@smithy/credential-provider-imds": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.7.tgz", - "integrity": "sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/property-provider": "^3.1.10", - "@smithy/types": "^3.7.1", - "@smithy/url-parser": "^3.0.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.1.tgz", - "integrity": "sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^4.1.7", - "@smithy/querystring-builder": "^3.0.10", - "@smithy/types": "^3.7.1", - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" } }, - "node_modules/@smithy/hash-node": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.10.tgz", - "integrity": "sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==", - "license": "Apache-2.0", + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@smithy/types": "^3.7.1", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=16.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@smithy/invalid-dependency": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.10.tgz", - "integrity": "sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==", - "license": "Apache-2.0", + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/@smithy/middleware-content-length": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.12.tgz", - "integrity": "sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==", - "license": "Apache-2.0", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@smithy/middleware-endpoint": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.4.tgz", - "integrity": "sha512-TybiW2LA3kYVd3e+lWhINVu1o26KJbBwOpADnf0L4x/35vLVica77XVR5hvV9+kWeTGeSJ3IHTcYxbRxlbwhsg==", - "license": "Apache-2.0", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", "dependencies": { - "@smithy/core": "^2.5.4", - "@smithy/middleware-serde": "^3.0.10", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/shared-ini-file-loader": "^3.1.11", - "@smithy/types": "^3.7.1", - "@smithy/url-parser": "^3.0.10", - "@smithy/util-middleware": "^3.0.10", - "tslib": "^2.6.2" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=16.0.0" + "node": ">= 8" } }, - "node_modules/@smithy/middleware-retry": { - "version": "3.0.28", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.28.tgz", - "integrity": "sha512-vK2eDfvIXG1U64FEUhYxoZ1JSj4XFbYWkK36iz02i3pFwWiDz1Q7jKhGTBCwx/7KqJNk4VS7d7cDLXFOvP7M+g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/protocol-http": "^4.1.7", - "@smithy/service-error-classification": "^3.0.10", - "@smithy/smithy-client": "^3.4.5", - "@smithy/types": "^3.7.1", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-retry": "^3.0.10", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "optional": true, "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/@smithy/middleware-serde": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.10.tgz", - "integrity": "sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==", - "license": "Apache-2.0", + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "optional": true, "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=8.0.0" } }, - "node_modules/@smithy/middleware-stack": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.10.tgz", - "integrity": "sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==", - "license": "Apache-2.0", + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, - "node_modules/@smithy/node-config-provider": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.11.tgz", - "integrity": "sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==", - "license": "Apache-2.0", + "node_modules/axios/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", "dependencies": { - "@smithy/property-provider": "^3.1.10", - "@smithy/shared-ini-file-loader": "^3.1.11", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" }, "engines": { - "node": ">=16.0.0" + "node": ">= 6" } }, - "node_modules/@smithy/node-http-handler": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.1.tgz", - "integrity": "sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==", - "license": "Apache-2.0", + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/abort-controller": "^3.1.8", - "@smithy/protocol-http": "^4.1.7", - "@smithy/querystring-builder": "^3.0.10", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" }, "engines": { - "node": ">=16.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" } }, - "node_modules/@smithy/property-provider": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.10.tgz", - "integrity": "sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==", - "license": "Apache-2.0", + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", - "license": "Apache-2.0", + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/@smithy/querystring-builder": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.10.tgz", - "integrity": "sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==", - "license": "Apache-2.0", + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^3.7.1", - "@smithy/util-uri-escape": "^3.0.0", - "tslib": "^2.6.2" + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" }, "engines": { - "node": ">=16.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@smithy/querystring-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.10.tgz", - "integrity": "sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==", - "license": "Apache-2.0", + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, - "engines": { - "node": ">=16.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@smithy/service-error-classification": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.10.tgz", - "integrity": "sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==", - "license": "Apache-2.0", + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^3.7.1" + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { - "node": ">=16.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" } }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.11.tgz", - "integrity": "sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/signature-v4": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.3.tgz", - "integrity": "sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==", - "license": "Apache-2.0", + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/@smithy/smithy-client": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.5.tgz", - "integrity": "sha512-k0sybYT9zlP79sIKd1XGm4TmK0AS1nA2bzDHXx7m0nGi3RQ8dxxQUs4CPkSmQTKAo+KF9aINU3KzpGIpV7UoMw==", - "license": "Apache-2.0", + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { - "@smithy/core": "^2.5.4", - "@smithy/middleware-endpoint": "^3.2.4", - "@smithy/middleware-stack": "^3.0.10", - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "@smithy/util-stream": "^3.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "license": "Apache-2.0", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "fill-range": "^7.1.1" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.10.tgz", - "integrity": "sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^3.0.10", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "node": ">=8" } }, - "node_modules/@smithy/util-base64": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", - "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", - "license": "Apache-2.0", + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">=16.0.0" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/@smithy/util-body-length-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", - "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "node-int64": "^0.4.0" } }, - "node_modules/@smithy/util-body-length-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", - "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">= 0.8" } }, - "node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/util-config-provider": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", - "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", - "license": "Apache-2.0", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.4" } }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.28", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.28.tgz", - "integrity": "sha512-6bzwAbZpHRFVJsOztmov5PGDmJYsbNSoIEfHSJJyFLzfBGCCChiO3od9k7E/TLgrCsIifdAbB9nqbVbyE7wRUw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^3.1.10", - "@smithy/smithy-client": "^3.4.5", - "@smithy/types": "^3.7.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 10.0.0" + "node": ">=6" } }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.28", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.28.tgz", - "integrity": "sha512-78ENJDorV1CjOQselGmm3+z7Yqjj5HWCbjzh0Ixuq736dh1oEnD9sAttSBNSLlpZsX8VQnmERqA2fEFlmqWn8w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^3.0.12", - "@smithy/credential-provider-imds": "^3.2.7", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/property-provider": "^3.1.10", - "@smithy/smithy-client": "^3.4.5", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 10.0.0" + "node": ">=6" } }, - "node_modules/@smithy/util-endpoints": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.6.tgz", - "integrity": "sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==", - "license": "Apache-2.0", + "node_modules/caniuse-lite": { + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@smithy/util-hex-encoding": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", - "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=10" } }, - "node_modules/@smithy/util-middleware": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.10.tgz", - "integrity": "sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==", - "license": "Apache-2.0", + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/@smithy/util-retry": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.10.tgz", - "integrity": "sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==", - "license": "Apache-2.0", + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { - "@smithy/service-error-classification": "^3.0.10", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">= 6" } }, - "node_modules/@smithy/util-stream": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.1.tgz", - "integrity": "sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^4.1.1", - "@smithy/node-http-handler": "^3.3.1", - "@smithy/types": "^3.7.1", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", - "license": "Apache-2.0", + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "devOptional": true, + "license": "ISC", "dependencies": { - "tslib": "^2.6.2" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=12" } }, - "node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.0.0" + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, - "node_modules/@smithy/util-waiter": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.9.tgz", - "integrity": "sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==", - "license": "Apache-2.0", + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { - "@smithy/abort-controller": "^3.1.8", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "color-name": "~1.1.4" }, "engines": { - "node": ">=16.0.0" + "node": ">=7.0.0" } }, - "node_modules/@stylistic/eslint-plugin-js": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.12.1.tgz", - "integrity": "sha512-5ybogtEgWIGCR6dMnaabztbWyVdAPDsf/5XOk6jBonWug875Q9/a6gm9QxnU3rhdyDEnckWKX7dduwYJMOWrVA==", - "dev": true, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=8.40.0" + "node": ">= 0.8" } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "license": "MIT", - "optional": true, "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/caseless": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", - "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", - "license": "MIT", - "optional": true + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "node_modules/config": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/config/-/config-3.3.12.tgz", + "integrity": "sha512-Vmx389R/QVM3foxqBzXO8t2tUikYZP64Q6vQxGrsMpREeJc/aWRnPRERXWsYzOHAumx/AOoILWe6nU3ZJL+6Sw==", "license": "MIT", "dependencies": { - "@types/node": "*" + "json5": "^2.2.3" + }, + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", - "peer": true + "engines": { + "node": ">= 0.6" + } }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", - "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "license": "MIT", "dependencies": { - "@types/node": "*" + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "license": "MIT", - "optional": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@types/qs": { - "version": "6.9.17", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", - "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, "license": "MIT" }, - "node_modules/@types/request": { - "version": "2.48.12", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", - "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "license": "MIT", - "optional": true, "dependencies": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" } }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, "license": "MIT", "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "license": "MIT", - "optional": true - }, - "node_modules/@types/uuid": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "license": "MIT" + "engines": { + "node": "*" + } }, - "node_modules/@types/ws": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", - "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "@types/node": "*" + "ms": "2.0.0" } }, - "node_modules/@vladfrangu/async_event_emitter": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", - "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, "engines": { - "node": ">=6.5" + "node": ">=0.10.0" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, "engines": { "node": ">=0.4.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": ">= 0.8" } }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", "engines": { - "node": ">= 14" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=8" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/discord-api-types": { + "version": "0.38.13", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.13.tgz", + "integrity": "sha512-FELWJRgLVQuR7Az8RhdEZE0k6QNjSW9PCUcU1iyP2Gke8HrJmnMceSS9pD93UM64s3tvZzJPajpPLjWZJylf4g==", "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, + "node_modules/discord.js": { + "version": "14.20.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.20.0.tgz", + "integrity": "sha512-5fRTptK2vpuz+bTuAEUQLSo/3AgCSLHl6Mm9+/ofb+8cbbnjWllhtaqRBq7XcpzlBnfNEugKv8HvCwcOtIHpCg==", + "license": "Apache-2.0", "dependencies": { - "color-convert": "^2.0.1" + "@discordjs/builders": "^1.11.2", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.6.1", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.1", + "@discordjs/ws": "^1.2.3", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "^0.38.1", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">= 8" + "node": ">= 0.4" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0", - "peer": true + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" } }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.173", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.173.tgz", + "integrity": "sha512-2bFhXP2zqSfQHugjqJIDFVwa+qIxyNApenmXTp9EjaKtdPrES5Qcn9/aSFy/NaP2E+fWG/zxKu/LBvY36p5VNQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "retry": "0.13.1" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">= 0.8" } }, - "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "once": "^1.4.0" } }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" + "is-arrayish": "^0.2.1" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bignumber.js": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { - "node": "*" + "node": ">= 0.4" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.4" } }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">= 0.4" } }, - "node_modules/bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/eslint": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.17.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "license": "MIT", - "peer": true, + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/eslint/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=10" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/eslint/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": ">= 8.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "engines": { + "node": ">=4" } }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, "dependencies": { - "is-glob": "^4.0.1" + "estraverse": "^5.1.0" }, "engines": { - "node": ">= 6" + "node": ">=0.10" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "optional": true, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=12" + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.6" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "license": "MIT" + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "license": "MIT", + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, "engines": { - "node": ">=14" + "node": ">= 0.8.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/config": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/config/-/config-3.3.12.tgz", - "integrity": "sha512-Vmx389R/QVM3foxqBzXO8t2tUikYZP64Q6vQxGrsMpREeJc/aWRnPRERXWsYzOHAumx/AOoILWe6nU3ZJL+6Sw==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, "license": "MIT", "dependencies": { - "json5": "^2.2.3" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 10.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", + "node_modules/express-handlebars": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-8.0.1.tgz", + "integrity": "sha512-mdas0PTbgQnwSyAjcYM7OMaftM8nJ3Kqz6yAyK4iCFvMOGGvh6pv42IHwcE5PBpS6ffYeZRSsgAdYUMG4CSjhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "glob": "^11.0.0", + "graceful-fs": "^4.2.11", + "handlebars": "^4.7.8" + }, "engines": { - "node": ">= 0.6" + "node": ">=20" } }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/farmhash-modern": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", + "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18.0.0" } }, - "node_modules/cookie-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, "license": "MIT", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } + "peer": true }, - "node_modules/cookie-parser/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=6" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], "license": "MIT", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "strnum": "^1.0.5" }, - "engines": { - "node": ">= 0.10" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "websocket-driver": ">=0.5.1" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "license": "MIT", - "engines": { - "node": "*" + "node": ">=0.8.0" } }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "ms": "2.0.0" + "bser": "2.1.1" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", - "peer": true - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", + "peer": true, "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=16.0.0" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, "engines": { - "node": ">=0.4.0" + "node": ">=8" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, "engines": { "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/discord-api-types": { - "version": "0.37.119", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.119.tgz", - "integrity": "sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg==", - "license": "MIT" - }, - "node_modules/discord.js": { - "version": "14.18.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.18.0.tgz", - "integrity": "sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw==", + "node_modules/firebase-admin": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.0.1.tgz", + "integrity": "sha512-sKQ/Yw8o/WdC9qTKvuLMBjTbdcBISIXW4+M9PXk0bNjxEbZf1Er7EVq47eRb5+bnKof10xlns6zAIbj4tmSexg==", "license": "Apache-2.0", "dependencies": { - "@discordjs/builders": "^1.10.1", - "@discordjs/collection": "1.5.3", - "@discordjs/formatters": "^0.6.0", - "@discordjs/rest": "^2.4.3", - "@discordjs/util": "^1.1.1", - "@discordjs/ws": "^1.2.1", - "@sapphire/snowflake": "3.5.3", - "discord-api-types": "^0.37.119", - "fast-deep-equal": "3.1.3", - "lodash.snakecase": "4.1.1", - "tslib": "^2.6.3", - "undici": "6.21.1" + "@fastify/busboy": "^3.0.0", + "@firebase/database-compat": "^2.0.0", + "@firebase/database-types": "^1.0.6", + "@types/node": "^22.8.7", + "farmhash-modern": "^1.1.0", + "google-auth-library": "^9.14.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.1.0", + "node-forge": "^1.3.1", + "uuid": "^11.0.2" }, "engines": { "node": ">=18" }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "optionalDependencies": { + "@google-cloud/firestore": "^7.10.0", + "@google-cloud/storage": "^7.14.0" } }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node_modules/firebase-admin/node_modules/uuid": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/duplexify": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", - "license": "MIT", - "optional": true, - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" + "node": ">=16" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC", + "peer": true }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "license": "MIT", + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, "engines": { - "node": ">= 0.4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", "license": "MIT", + "optional": true, "dependencies": { - "es-errors": "^1.3.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.12" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" }, "engines": { - "node": ">= 0.4" + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, "license": "MIT", - "peer": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" - }, + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", - "dev": true, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "license": "MIT", - "peer": true, + "optional": true + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "node": ">=14" } }, - "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=14" } }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6.9.0" } }, - "node_modules/eslint/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", - "peer": true, "dependencies": { - "ms": "^2.1.3" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "license": "MIT", - "peer": true + "engines": { + "node": ">=8.0.0" + } }, - "node_modules/esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getopts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", + "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "license": "ISC", "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "20 || >=22" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "peer": true, "dependencies": { - "estraverse": "^5.1.0" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=0.10" + "node": ">=10.13.0" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" + "balanced-match": "^1.0.0" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=4.0" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", + "node_modules/google-auth-library": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", + "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=14" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", + "node_modules/google-gax": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "license": "Apache-2.0", "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, "engines": { - "node": ">=6" + "node": ">=14" } }, - "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, "engines": { - "node": ">= 0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/express-handlebars": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-8.0.1.tgz", - "integrity": "sha512-mdas0PTbgQnwSyAjcYM7OMaftM8nJ3Kqz6yAyK4iCFvMOGGvh6pv42IHwcE5PBpS6ffYeZRSsgAdYUMG4CSjhQ==", - "license": "BSD-3-Clause", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", "dependencies": { - "glob": "^11.0.0", - "graceful-fs": "^4.2.11", - "handlebars": "^4.7.8" + "gaxios": "^6.0.0", + "jws": "^4.0.0" }, "engines": { - "node": ">=20" + "node": ">=14.0.0" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/farmhash-modern": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", - "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, "engines": { - "node": ">=18.0.0" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/fast-copy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", - "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", - "peer": true - }, - "node_modules/fast-redact": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", - "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, - "node_modules/fast-xml-parser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "license": "MIT", "dependencies": { - "strnum": "^1.0.5" + "es-define-property": "^1.0.0" }, - "bin": { - "fxparser": "src/cli/cli.js" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", - "peer": true, "dependencies": { - "flat-cache": "^4.0.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", "statuses": "2.0.1", - "unpipe": "~1.0.0" + "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.8" } }, - "node_modules/find-up": { + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "license": "MIT" + }, + "node_modules/http-proxy-agent": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "license": "MIT", - "peer": true, + "optional": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=10" + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 6.0.0" } }, - "node_modules/firebase-admin": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.0.1.tgz", - "integrity": "sha512-sKQ/Yw8o/WdC9qTKvuLMBjTbdcBISIXW4+M9PXk0bNjxEbZf1Er7EVq47eRb5+bnKof10xlns6zAIbj4tmSexg==", - "license": "Apache-2.0", + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "optional": true, "dependencies": { - "@fastify/busboy": "^3.0.0", - "@firebase/database-compat": "^2.0.0", - "@firebase/database-types": "^1.0.6", - "@types/node": "^22.8.7", - "farmhash-modern": "^1.1.0", - "google-auth-library": "^9.14.2", - "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^3.1.0", - "node-forge": "^1.3.1", - "uuid": "^11.0.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=18" + "node": ">=6.0" }, - "optionalDependencies": { - "@google-cloud/firestore": "^7.10.0", - "@google-cloud/storage": "^7.14.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/firebase-admin/node_modules/uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } + "optional": true }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", - "peer": true, "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=16" + "node": ">= 14" } }, - "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=4.0" + "node": ">=6.0" }, "peerDependenciesMeta": { - "debug": { + "supports-color": { "optional": true } } }, - "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10.17.0" } }, - "node_modules/form-data": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", - "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", - "optional": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12", - "safe-buffer": "^5.2.1" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">= 0.12" + "node": ">=0.10.0" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">= 0.6" + "node": ">= 4" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", + "node": ">=6" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", - "optional": true - }, - "node_modules/gaxios": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", - "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" - }, + "peer": true, "engines": { - "node": ">=14" + "node": ">=4" } }, - "node_modules/gcp-metadata": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", - "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", - "license": "Apache-2.0", + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", "dependencies": { - "gaxios": "^6.0.0", - "json-bigint": "^1.0.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=14" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=0.8.19" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">= 0.10" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, "engines": { - "node": ">= 0.4" + "node": ">= 0.10" } }, - "node_modules/getopts": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", - "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, - "node_modules/glob": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", - "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", - "license": "ISC", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "binary-extensions": "^2.0.0" }, "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "peer": true, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, - "engines": { - "node": ">=10.13.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/google-auth-library": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", - "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", - "license": "Apache-2.0", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", - "jws": "^4.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=14" + "node": ">=0.10.0" } }, - "node_modules/google-gax": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", - "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@grpc/grpc-js": "^1.10.9", - "@grpc/proto-loader": "^0.7.13", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "google-auth-library": "^9.3.0", - "node-fetch": "^2.7.0", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^2.0.2", - "protobufjs": "^7.3.2", - "retry-request": "^7.0.0", - "uuid": "^9.0.1" - }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=0.12.0" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, - "node_modules/gtoken": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", - "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", - "license": "MIT", - "dependencies": { - "gaxios": "^6.0.0", - "jws": "^4.0.0" - }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=14.0.0" + "node": ">=8" } }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "license": "MIT", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "node": ">=10" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "MIT", - "peer": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "es-define-property": "^1.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=10" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">= 0.4" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "has-symbols": "^1.0.3" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", + "node_modules/jackspeak": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.3.tgz", + "integrity": "sha512-oSwM7q8PTHQWuZAlp995iPpPJ4Vkl7qT0ZRD+9duL9j2oBy6KcTfyxc8mEuHJYC+z/kbps80aJLkaNzTOrf/kw==", + "license": "BlueOak-1.0.0", "dependencies": { - "function-bind": "^1.1.2" + "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">= 0.4" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "license": "MIT" - }, - "node_modules/html-entities": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", - "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT", - "optional": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">= 0.8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "license": "MIT" - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "debug": "4" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">= 6.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "ms": "^2.1.3" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=6.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { - "supports-color": { + "@types/node": { + "optional": true + }, + "ts-node": { "optional": true } } }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "detect-newline": "^3.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">= 4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "license": "ISC" + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=0.8.19" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">= 0.10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=0.12.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/jackspeak": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.3.tgz", - "integrity": "sha512-oSwM7q8PTHQWuZAlp995iPpPJ4Vkl7qT0ZRD+9duL9j2oBy6KcTfyxc8mEuHJYC+z/kbps80aJLkaNzTOrf/kw==", - "license": "BlueOak-1.0.0", + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": "20 || >=22" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/jose": { @@ -4472,6 +7035,13 @@ "node": ">=10" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -4486,6 +7056,19 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -4503,6 +7086,13 @@ "license": "MIT", "peer": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4652,6 +7242,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/knex": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz", @@ -4732,6 +7332,16 @@ "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==", "license": "MIT" }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4752,6 +7362,13 @@ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4874,11 +7491,37 @@ } }, "node_modules/magic-bytes.js": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", - "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", + "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", "license": "MIT" }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4906,6 +7549,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -4915,6 +7565,20 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -4948,6 +7612,16 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4989,8 +7663,7 @@ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", @@ -5036,6 +7709,20 @@ "node": ">= 6.13.0" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, "node_modules/nodemailer": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", @@ -5139,6 +7826,19 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5200,6 +7900,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5252,6 +7968,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -5272,6 +7998,25 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -5287,11 +8032,20 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5333,9 +8087,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/pg": { @@ -5427,6 +8181,13 @@ "split2": "^4.1.0" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -5512,6 +8273,85 @@ "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", "license": "MIT" }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -5562,6 +8402,34 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prisma": { "version": "5.21.1", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.21.1.tgz", @@ -5598,6 +8466,20 @@ ], "license": "MIT" }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/proto3-json-serializer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", @@ -5682,6 +8564,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -5727,6 +8626,13 @@ "node": ">= 0.8" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5779,8 +8685,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.10.0" } @@ -5802,6 +8708,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -5811,6 +8730,16 @@ "node": ">=8" } }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -6029,6 +8958,23 @@ "node": ">=10" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/sonic-boom": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", @@ -6047,6 +8993,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -6056,6 +9013,36 @@ "node": ">= 10.x" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -6092,6 +9079,20 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6146,6 +9147,26 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6171,13 +9192,102 @@ "license": "MIT", "optional": true }, + "node_modules/superagent": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.1.tgz", + "integrity": "sha512-O+PCv11lgTNJUzy49teNAWLjBZfc+A1enOwTpLlH6/rsvKcTwcdTT8m9azGkVqM7HBl5jpyZ7KTPhHweokBcdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/supertest": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.1.tgz", + "integrity": "sha512-aI59HBTlG9e2wTjxGJV+DygfNLgnWbGdZxiA/sgrnNNikIW8lbDvCtF6RnhZoJ82nU7qv7ZLjrvWqCEm52fAmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^10.2.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -6275,6 +9385,43 @@ "license": "MIT", "optional": true }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/thread-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", @@ -6293,6 +9440,13 @@ "node": ">=8" } }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6355,6 +9509,29 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -6388,9 +9565,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "6.21.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", - "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "license": "MIT", "engines": { "node": ">=18.17" @@ -6411,6 +9588,37 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6451,6 +9659,21 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -6460,6 +9683,16 @@ "node": ">= 0.8" } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -6535,8 +9768,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6573,10 +9806,31 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -6607,8 +9861,8 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "devOptional": true, "license": "ISC", - "optional": true, "engines": { "node": ">=10" } @@ -6623,8 +9877,8 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -6642,8 +9896,8 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "devOptional": true, "license": "ISC", - "optional": true, "engines": { "node": ">=12" } diff --git a/package.json b/package.json index c89b010..1e93c64 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,8 @@ }, "devDependencies": { "@stylistic/eslint-plugin-js": "^2.12.1", - "prisma": "^5.21.1" + "jest": "^29.7.0", + "prisma": "^5.21.1", + "supertest": "^7.1.1" } } From 486149705aea212dc0779d3b35787fed3838c34d Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Wed, 25 Jun 2025 15:46:46 +0200 Subject: [PATCH 024/135] whitelist custom headers for cors --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 3abc44d..cdcd14a 100644 --- a/server.js +++ b/server.js @@ -33,7 +33,7 @@ app.use(cors({ }, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization', 'token'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-Firebase-Token', 'X-Discord-Code'], })); app.options('*', cors()); app.use(authenticate()); From 2cba25a811075a8c21d30c017ad71ecc6bcf9e5d Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Wed, 25 Jun 2025 12:28:19 +0200 Subject: [PATCH 025/135] tests for users/login + some setups --- app.js | 35 + appForTests.js | 6 + config/test.json | 2 + coverage/clover.xml | 429 +++++++++++ coverage/coverage-final.json | 17 + coverage/lcov-report/base.css | 224 ++++++ coverage/lcov-report/block-navigation.js | 87 +++ coverage/lcov-report/favicon.png | Bin 0 -> 445 bytes coverage/lcov-report/index.html | 191 +++++ coverage/lcov-report/prettify.css | 1 + coverage/lcov-report/prettify.js | 2 + coverage/lcov-report/sort-arrow-sprite.png | Bin 0 -> 138 bytes coverage/lcov-report/sorter.js | 196 +++++ .../webplatform-backend/app.js.html | 187 +++++ .../webplatform-backend/aws/index.html | 116 +++ .../aws/ssm-helper.js.html | 205 +++++ .../webplatform-backend/db.js.html | 268 +++++++ .../webplatform-backend/index.html | 161 ++++ .../middleware/authentication.js.html | 175 +++++ .../middleware/authorization.js.html | 283 +++++++ .../webplatform-backend/middleware/index.html | 131 ++++ .../webplatform-backend/oauth.js.html | 172 +++++ .../applications_repository.js.html | 169 +++++ .../authorization_repository.js.html | 121 +++ .../repositories/index.html | 161 ++++ .../repositories/roles_repository.js.html | 121 +++ .../repositories/user_repository.js.html | 268 +++++++ .../webplatform-backend/routes/index.html | 116 +++ .../webplatform-backend/routes/users.js.html | 484 ++++++++++++ .../services/applications_service.js.html | 151 ++++ .../services/auth_service.js.html | 340 +++++++++ .../webplatform-backend/services/index.html | 161 ++++ .../services/roles_service.js.html | 133 ++++ .../services/user_service.js.html | 208 +++++ .../webplatform-backend/utils.js.html | 292 +++++++ coverage/lcov.info | 711 ++++++++++++++++++ jest.config.js | 6 + logger.js | 11 +- middleware/authentication.js | 1 + package.json | 4 +- server.js | 35 +- tests/routes/users.test.js | 80 ++ 42 files changed, 6423 insertions(+), 38 deletions(-) create mode 100644 app.js create mode 100644 appForTests.js create mode 100644 config/test.json create mode 100644 coverage/clover.xml create mode 100644 coverage/coverage-final.json create mode 100644 coverage/lcov-report/base.css create mode 100644 coverage/lcov-report/block-navigation.js create mode 100644 coverage/lcov-report/favicon.png create mode 100644 coverage/lcov-report/index.html create mode 100644 coverage/lcov-report/prettify.css create mode 100644 coverage/lcov-report/prettify.js create mode 100644 coverage/lcov-report/sort-arrow-sprite.png create mode 100644 coverage/lcov-report/sorter.js create mode 100644 coverage/lcov-report/webplatform-backend/app.js.html create mode 100644 coverage/lcov-report/webplatform-backend/aws/index.html create mode 100644 coverage/lcov-report/webplatform-backend/aws/ssm-helper.js.html create mode 100644 coverage/lcov-report/webplatform-backend/db.js.html create mode 100644 coverage/lcov-report/webplatform-backend/index.html create mode 100644 coverage/lcov-report/webplatform-backend/middleware/authentication.js.html create mode 100644 coverage/lcov-report/webplatform-backend/middleware/authorization.js.html create mode 100644 coverage/lcov-report/webplatform-backend/middleware/index.html create mode 100644 coverage/lcov-report/webplatform-backend/oauth.js.html create mode 100644 coverage/lcov-report/webplatform-backend/repositories/applications_repository.js.html create mode 100644 coverage/lcov-report/webplatform-backend/repositories/authorization_repository.js.html create mode 100644 coverage/lcov-report/webplatform-backend/repositories/index.html create mode 100644 coverage/lcov-report/webplatform-backend/repositories/roles_repository.js.html create mode 100644 coverage/lcov-report/webplatform-backend/repositories/user_repository.js.html create mode 100644 coverage/lcov-report/webplatform-backend/routes/index.html create mode 100644 coverage/lcov-report/webplatform-backend/routes/users.js.html create mode 100644 coverage/lcov-report/webplatform-backend/services/applications_service.js.html create mode 100644 coverage/lcov-report/webplatform-backend/services/auth_service.js.html create mode 100644 coverage/lcov-report/webplatform-backend/services/index.html create mode 100644 coverage/lcov-report/webplatform-backend/services/roles_service.js.html create mode 100644 coverage/lcov-report/webplatform-backend/services/user_service.js.html create mode 100644 coverage/lcov-report/webplatform-backend/utils.js.html create mode 100644 coverage/lcov.info create mode 100644 jest.config.js create mode 100644 tests/routes/users.test.js diff --git a/app.js b/app.js new file mode 100644 index 0000000..eebc94d --- /dev/null +++ b/app.js @@ -0,0 +1,35 @@ +const config = require('config'); +const express = require("express"); +const cookieParser = require('cookie-parser'); +const cors = require("cors"); +const { authenticate } = require("./middleware/authentication"); +const pinoHttp = require('pino-http'); +const { baseLogger } = require('./logger'); + +require('dotenv').config({ path: '.env.local' }); + +const app = express(); + +// Middleware +if (process.env.LOGGING_HTTP_REQUESTS === 'true') { + app.use(pinoHttp({ logger: baseLogger })); +} + +app.use(express.json()); +app.use(cookieParser()); +app.use(cors({ + origin: (origin, callback) => { + if (!origin || config.cors.allowedOrigins.includes(origin)) { + callback(null, origin); + } else { + callback(new Error('Not allowed by CORS')); + } + }, + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'token'], +})); +app.options('*', cors()); +app.use(authenticate()); + +module.exports = app; \ No newline at end of file diff --git a/appForTests.js b/appForTests.js new file mode 100644 index 0000000..93aaa94 --- /dev/null +++ b/appForTests.js @@ -0,0 +1,6 @@ +const app = require('./app'); +const routes = require('./routes/routes'); + +app.use(routes); + +module.exports = app; \ No newline at end of file diff --git a/config/test.json b/config/test.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/config/test.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/coverage/clover.xml b/coverage/clover.xml new file mode 100644 index 0000000..2de783e --- /dev/null +++ b/coverage/clover.xml @@ -0,0 +1,429 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json new file mode 100644 index 0000000..d5fb565 --- /dev/null +++ b/coverage/coverage-final.json @@ -0,0 +1,17 @@ +{"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/app.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/app.js","statementMap":{"0":{"start":{"line":1,"column":15},"end":{"line":1,"column":32}},"1":{"start":{"line":2,"column":16},"end":{"line":2,"column":34}},"2":{"start":{"line":3,"column":21},"end":{"line":3,"column":45}},"3":{"start":{"line":4,"column":13},"end":{"line":4,"column":28}},"4":{"start":{"line":5,"column":25},"end":{"line":5,"column":63}},"5":{"start":{"line":6,"column":17},"end":{"line":6,"column":37}},"6":{"start":{"line":7,"column":23},"end":{"line":7,"column":42}},"7":{"start":{"line":9,"column":0},"end":{"line":9,"column":49}},"8":{"start":{"line":11,"column":12},"end":{"line":11,"column":21}},"9":{"start":{"line":14,"column":0},"end":{"line":16,"column":1}},"10":{"start":{"line":15,"column":2},"end":{"line":15,"column":44}},"11":{"start":{"line":18,"column":0},"end":{"line":18,"column":24}},"12":{"start":{"line":19,"column":0},"end":{"line":19,"column":24}},"13":{"start":{"line":20,"column":0},"end":{"line":31,"column":4}},"14":{"start":{"line":22,"column":4},"end":{"line":26,"column":5}},"15":{"start":{"line":23,"column":6},"end":{"line":23,"column":29}},"16":{"start":{"line":25,"column":6},"end":{"line":25,"column":49}},"17":{"start":{"line":32,"column":0},"end":{"line":32,"column":25}},"18":{"start":{"line":33,"column":0},"end":{"line":33,"column":24}},"19":{"start":{"line":35,"column":0},"end":{"line":35,"column":21}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":21,"column":10},"end":{"line":21,"column":11}},"loc":{"start":{"line":21,"column":32},"end":{"line":27,"column":3}},"line":21}},"branchMap":{"0":{"loc":{"start":{"line":14,"column":0},"end":{"line":16,"column":1}},"type":"if","locations":[{"start":{"line":14,"column":0},"end":{"line":16,"column":1}},{"start":{},"end":{}}],"line":14},"1":{"loc":{"start":{"line":22,"column":4},"end":{"line":26,"column":5}},"type":"if","locations":[{"start":{"line":22,"column":4},"end":{"line":26,"column":5}},{"start":{"line":24,"column":11},"end":{"line":26,"column":5}}],"line":22},"2":{"loc":{"start":{"line":22,"column":8},"end":{"line":22,"column":62}},"type":"binary-expr","locations":[{"start":{"line":22,"column":8},"end":{"line":22,"column":15}},{"start":{"line":22,"column":19},"end":{"line":22,"column":62}}],"line":22}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":0,"11":1,"12":1,"13":1,"14":1,"15":1,"16":0,"17":1,"18":1,"19":1},"f":{"0":1},"b":{"0":[0,1],"1":[1,0],"2":[1,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"ff5d057cc22887d21ae671290ced3c3a58bc4e60"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/db.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/db.js","statementMap":{"0":{"start":{"line":1,"column":13},"end":{"line":1,"column":28}},"1":{"start":{"line":2,"column":24},"end":{"line":2,"column":51}},"2":{"start":{"line":3,"column":15},"end":{"line":3,"column":32}},"3":{"start":{"line":5,"column":0},"end":{"line":5,"column":49}},"4":{"start":{"line":8,"column":19},"end":{"line":8,"column":23}},"5":{"start":{"line":11,"column":18},"end":{"line":11,"column":53}},"6":{"start":{"line":12,"column":2},"end":{"line":12,"column":90}},"7":{"start":{"line":13,"column":2},"end":{"line":33,"column":3}},"8":{"start":{"line":14,"column":4},"end":{"line":14,"column":43}},"9":{"start":{"line":15,"column":4},"end":{"line":15,"column":54}},"10":{"start":{"line":16,"column":4},"end":{"line":16,"column":39}},"11":{"start":{"line":17,"column":4},"end":{"line":17,"column":43}},"12":{"start":{"line":18,"column":4},"end":{"line":18,"column":47}},"13":{"start":{"line":22,"column":22},"end":{"line":22,"column":32}},"14":{"start":{"line":23,"column":24},"end":{"line":26,"column":11}},"15":{"start":{"line":28,"column":4},"end":{"line":28,"column":50}},"16":{"start":{"line":29,"column":4},"end":{"line":29,"column":41}},"17":{"start":{"line":30,"column":4},"end":{"line":30,"column":37}},"18":{"start":{"line":31,"column":4},"end":{"line":31,"column":50}},"19":{"start":{"line":32,"column":4},"end":{"line":32,"column":42}},"20":{"start":{"line":35,"column":2},"end":{"line":42,"column":4}},"21":{"start":{"line":46,"column":19},"end":{"line":46,"column":40}},"22":{"start":{"line":48,"column":2},"end":{"line":59,"column":5}},"23":{"start":{"line":62,"column":0},"end":{"line":62,"column":72}},"24":{"start":{"line":62,"column":57},"end":{"line":62,"column":69}}},"fnMap":{"0":{"name":"getDbSettings","decl":{"start":{"line":10,"column":15},"end":{"line":10,"column":28}},"loc":{"start":{"line":10,"column":31},"end":{"line":43,"column":1}},"line":10},"1":{"name":"initDb","decl":{"start":{"line":45,"column":15},"end":{"line":45,"column":21}},"loc":{"start":{"line":45,"column":24},"end":{"line":60,"column":1}},"line":45},"2":{"name":"(anonymous_2)","decl":{"start":{"line":62,"column":51},"end":{"line":62,"column":52}},"loc":{"start":{"line":62,"column":57},"end":{"line":62,"column":69}},"line":62}},"branchMap":{"0":{"loc":{"start":{"line":12,"column":31},"end":{"line":12,"column":88}},"type":"cond-expr","locations":[{"start":{"line":12,"column":41},"end":{"line":12,"column":48}},{"start":{"line":12,"column":51},"end":{"line":12,"column":88}}],"line":12},"1":{"loc":{"start":{"line":12,"column":51},"end":{"line":12,"column":88}},"type":"binary-expr","locations":[{"start":{"line":12,"column":51},"end":{"line":12,"column":71}},{"start":{"line":12,"column":75},"end":{"line":12,"column":88}}],"line":12},"2":{"loc":{"start":{"line":13,"column":2},"end":{"line":33,"column":3}},"type":"if","locations":[{"start":{"line":13,"column":2},"end":{"line":33,"column":3}},{"start":{"line":20,"column":7},"end":{"line":33,"column":3}}],"line":13},"3":{"loc":{"start":{"line":15,"column":19},"end":{"line":15,"column":53}},"type":"binary-expr","locations":[{"start":{"line":15,"column":19},"end":{"line":15,"column":38}},{"start":{"line":15,"column":42},"end":{"line":15,"column":53}}],"line":15},"4":{"loc":{"start":{"line":18,"column":19},"end":{"line":18,"column":46}},"type":"binary-expr","locations":[{"start":{"line":18,"column":19},"end":{"line":18,"column":38}},{"start":{"line":18,"column":42},"end":{"line":18,"column":46}}],"line":18},"5":{"loc":{"start":{"line":41,"column":9},"end":{"line":41,"column":56}},"type":"cond-expr","locations":[{"start":{"line":41,"column":19},"end":{"line":41,"column":24}},{"start":{"line":41,"column":27},"end":{"line":41,"column":56}}],"line":41}},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":2,"24":1},"f":{"0":0,"1":0,"2":1},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"c55475ba25f6567300c9d59a11d7ab9eb667d91b"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/oauth.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/oauth.js","statementMap":{"0":{"start":{"line":1,"column":24},"end":{"line":1,"column":51}},"1":{"start":{"line":2,"column":15},"end":{"line":2,"column":32}},"2":{"start":{"line":4,"column":20},"end":{"line":8,"column":1}},"3":{"start":{"line":11,"column":65},"end":{"line":11,"column":69}},"4":{"start":{"line":13,"column":2},"end":{"line":22,"column":3}},"5":{"start":{"line":14,"column":4},"end":{"line":14,"column":52}},"6":{"start":{"line":15,"column":4},"end":{"line":15,"column":60}},"7":{"start":{"line":16,"column":4},"end":{"line":16,"column":58}},"8":{"start":{"line":18,"column":19},"end":{"line":18,"column":118}},"9":{"start":{"line":19,"column":4},"end":{"line":19,"column":67}},"10":{"start":{"line":20,"column":4},"end":{"line":20,"column":75}},"11":{"start":{"line":21,"column":4},"end":{"line":21,"column":71}},"12":{"start":{"line":24,"column":2},"end":{"line":24,"column":48}},"13":{"start":{"line":25,"column":2},"end":{"line":25,"column":56}},"14":{"start":{"line":26,"column":2},"end":{"line":26,"column":54}},"15":{"start":{"line":29,"column":0},"end":{"line":29,"column":44}}},"fnMap":{"0":{"name":"initOAuth","decl":{"start":{"line":10,"column":15},"end":{"line":10,"column":24}},"loc":{"start":{"line":10,"column":27},"end":{"line":27,"column":1}},"line":10}},"branchMap":{"0":{"loc":{"start":{"line":13,"column":2},"end":{"line":22,"column":3}},"type":"if","locations":[{"start":{"line":13,"column":2},"end":{"line":22,"column":3}},{"start":{"line":17,"column":9},"end":{"line":22,"column":3}}],"line":13}},"s":{"0":1,"1":1,"2":1,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":1},"f":{"0":0},"b":{"0":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"d2714503c006b7000f45bb5cbe5f143c122f8750"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/utils.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/utils.js","statementMap":{"0":{"start":{"line":1,"column":13},"end":{"line":1,"column":55}},"1":{"start":{"line":2,"column":24},"end":{"line":2,"column":51}},"2":{"start":{"line":3,"column":15},"end":{"line":3,"column":32}},"3":{"start":{"line":4,"column":15},"end":{"line":4,"column":46}},"4":{"start":{"line":6,"column":17},"end":{"line":6,"column":21}},"5":{"start":{"line":8,"column":19},"end":{"line":11,"column":1}},"6":{"start":{"line":14,"column":23},"end":{"line":14,"column":27}},"7":{"start":{"line":15,"column":2},"end":{"line":29,"column":3}},"8":{"start":{"line":16,"column":4},"end":{"line":24,"column":5}},"9":{"start":{"line":17,"column":6},"end":{"line":17,"column":59}},"10":{"start":{"line":20,"column":24},"end":{"line":20,"column":34}},"11":{"start":{"line":21,"column":26},"end":{"line":21,"column":127}},"12":{"start":{"line":22,"column":23},"end":{"line":22,"column":58}},"13":{"start":{"line":23,"column":6},"end":{"line":23,"column":44}},"14":{"start":{"line":27,"column":4},"end":{"line":27,"column":55}},"15":{"start":{"line":28,"column":4},"end":{"line":28,"column":58}},"16":{"start":{"line":30,"column":2},"end":{"line":30,"column":24}},"17":{"start":{"line":34,"column":2},"end":{"line":49,"column":3}},"18":{"start":{"line":35,"column":4},"end":{"line":45,"column":5}},"19":{"start":{"line":36,"column":20},"end":{"line":36,"column":44}},"20":{"start":{"line":37,"column":6},"end":{"line":43,"column":13}},"21":{"start":{"line":38,"column":8},"end":{"line":38,"column":38}},"22":{"start":{"line":39,"column":8},"end":{"line":41,"column":9}},"23":{"start":{"line":40,"column":10},"end":{"line":40,"column":99}},"24":{"start":{"line":42,"column":8},"end":{"line":42,"column":19}},"25":{"start":{"line":44,"column":6},"end":{"line":44,"column":59}},"26":{"start":{"line":47,"column":4},"end":{"line":47,"column":45}},"27":{"start":{"line":48,"column":4},"end":{"line":48,"column":50}},"28":{"start":{"line":53,"column":2},"end":{"line":55,"column":3}},"29":{"start":{"line":54,"column":4},"end":{"line":54,"column":44}},"30":{"start":{"line":56,"column":2},"end":{"line":56,"column":30}},"31":{"start":{"line":60,"column":2},"end":{"line":60,"column":50}},"32":{"start":{"line":63,"column":22},"end":{"line":67,"column":1}},"33":{"start":{"line":69,"column":0},"end":{"line":69,"column":121}}},"fnMap":{"0":{"name":"getFirebaseSdkServiceAccount","decl":{"start":{"line":13,"column":15},"end":{"line":13,"column":43}},"loc":{"start":{"line":13,"column":46},"end":{"line":31,"column":1}},"line":13},"1":{"name":"loadRolesIntoMemory","decl":{"start":{"line":33,"column":15},"end":{"line":33,"column":34}},"loc":{"start":{"line":33,"column":37},"end":{"line":50,"column":1}},"line":33},"2":{"name":"(anonymous_2)","decl":{"start":{"line":37,"column":32},"end":{"line":37,"column":33}},"loc":{"start":{"line":37,"column":47},"end":{"line":43,"column":7}},"line":37},"3":{"name":"getRole","decl":{"start":{"line":52,"column":9},"end":{"line":52,"column":16}},"loc":{"start":{"line":52,"column":27},"end":{"line":57,"column":1}},"line":52},"4":{"name":"isRoleExists","decl":{"start":{"line":59,"column":9},"end":{"line":59,"column":21}},"loc":{"start":{"line":59,"column":28},"end":{"line":61,"column":1}},"line":59}},"branchMap":{"0":{"loc":{"start":{"line":16,"column":4},"end":{"line":24,"column":5}},"type":"if","locations":[{"start":{"line":16,"column":4},"end":{"line":24,"column":5}},{"start":{"line":19,"column":9},"end":{"line":24,"column":5}}],"line":16},"1":{"loc":{"start":{"line":35,"column":4},"end":{"line":45,"column":5}},"type":"if","locations":[{"start":{"line":35,"column":4},"end":{"line":45,"column":5}},{"start":{},"end":{}}],"line":35},"2":{"loc":{"start":{"line":39,"column":8},"end":{"line":41,"column":9}},"type":"if","locations":[{"start":{"line":39,"column":8},"end":{"line":41,"column":9}},{"start":{},"end":{}}],"line":39},"3":{"loc":{"start":{"line":53,"column":2},"end":{"line":55,"column":3}},"type":"if","locations":[{"start":{"line":53,"column":2},"end":{"line":55,"column":3}},{"start":{},"end":{}}],"line":53}},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":2,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":2,"33":2},"f":{"0":0,"1":0,"2":0,"3":0,"4":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"221d1712c37cbb0dce496b7df09415f1118448b6"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/aws/ssm-helper.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/aws/ssm-helper.js","statementMap":{"0":{"start":{"line":1,"column":44},"end":{"line":1,"column":74}},"1":{"start":{"line":2,"column":15},"end":{"line":2,"column":47}},"2":{"start":{"line":5,"column":20},"end":{"line":7,"column":4}},"3":{"start":{"line":9,"column":2},"end":{"line":29,"column":3}},"4":{"start":{"line":10,"column":4},"end":{"line":10,"column":60}},"5":{"start":{"line":11,"column":20},"end":{"line":14,"column":6}},"6":{"start":{"line":16,"column":19},"end":{"line":16,"column":48}},"7":{"start":{"line":18,"column":20},"end":{"line":22,"column":10}},"8":{"start":{"line":19,"column":18},"end":{"line":19,"column":39}},"9":{"start":{"line":20,"column":6},"end":{"line":20,"column":59}},"10":{"start":{"line":21,"column":6},"end":{"line":21,"column":17}},"11":{"start":{"line":24,"column":4},"end":{"line":24,"column":58}},"12":{"start":{"line":25,"column":4},"end":{"line":25,"column":19}},"13":{"start":{"line":27,"column":4},"end":{"line":27,"column":51}},"14":{"start":{"line":28,"column":4},"end":{"line":28,"column":14}},"15":{"start":{"line":33,"column":2},"end":{"line":37,"column":3}},"16":{"start":{"line":34,"column":4},"end":{"line":34,"column":29}},"17":{"start":{"line":36,"column":4},"end":{"line":36,"column":17}},"18":{"start":{"line":40,"column":0},"end":{"line":40,"column":33}}},"fnMap":{"0":{"name":"loadSecrets","decl":{"start":{"line":4,"column":15},"end":{"line":4,"column":26}},"loc":{"start":{"line":4,"column":85},"end":{"line":30,"column":1}},"line":4},"1":{"name":"(anonymous_1)","decl":{"start":{"line":18,"column":45},"end":{"line":18,"column":46}},"loc":{"start":{"line":18,"column":71},"end":{"line":22,"column":5}},"line":18},"2":{"name":"parseJsonValue","decl":{"start":{"line":32,"column":9},"end":{"line":32,"column":23}},"loc":{"start":{"line":32,"column":31},"end":{"line":38,"column":1}},"line":32}},"branchMap":{"0":{"loc":{"start":{"line":4,"column":42},"end":{"line":4,"column":64}},"type":"default-arg","locations":[{"start":{"line":4,"column":59},"end":{"line":4,"column":64}}],"line":4},"1":{"loc":{"start":{"line":4,"column":66},"end":{"line":4,"column":83}},"type":"default-arg","locations":[{"start":{"line":4,"column":78},"end":{"line":4,"column":83}}],"line":4},"2":{"loc":{"start":{"line":20,"column":17},"end":{"line":20,"column":58}},"type":"cond-expr","locations":[{"start":{"line":20,"column":29},"end":{"line":20,"column":50}},{"start":{"line":20,"column":53},"end":{"line":20,"column":58}}],"line":20}},"s":{"0":2,"1":2,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":2},"f":{"0":0,"1":0,"2":0},"b":{"0":[0],"1":[0],"2":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"dd5a2beff850a1c2b15b27a4464938c24bb46ffe"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/middleware/authentication.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/middleware/authentication.js","statementMap":{"0":{"start":{"line":1,"column":20},"end":{"line":1,"column":55}},"1":{"start":{"line":2,"column":29},"end":{"line":2,"column":57}},"2":{"start":{"line":3,"column":15},"end":{"line":3,"column":47}},"3":{"start":{"line":6,"column":2},"end":{"line":28,"column":4}},"4":{"start":{"line":7,"column":4},"end":{"line":27,"column":5}},"5":{"start":{"line":8,"column":29},"end":{"line":8,"column":49}},"6":{"start":{"line":9,"column":6},"end":{"line":12,"column":7}},"7":{"start":{"line":10,"column":8},"end":{"line":10,"column":75}},"8":{"start":{"line":11,"column":8},"end":{"line":11,"column":22}},"9":{"start":{"line":14,"column":19},"end":{"line":14,"column":72}},"10":{"start":{"line":15,"column":6},"end":{"line":18,"column":7}},"11":{"start":{"line":16,"column":8},"end":{"line":16,"column":102}},"12":{"start":{"line":17,"column":8},"end":{"line":17,"column":78}},"13":{"start":{"line":20,"column":6},"end":{"line":20,"column":27}},"14":{"start":{"line":21,"column":6},"end":{"line":21,"column":33}},"15":{"start":{"line":23,"column":6},"end":{"line":23,"column":13}},"16":{"start":{"line":25,"column":6},"end":{"line":25,"column":73}},"17":{"start":{"line":26,"column":6},"end":{"line":26,"column":35}},"18":{"start":{"line":31,"column":0},"end":{"line":31,"column":34}}},"fnMap":{"0":{"name":"authenticate","decl":{"start":{"line":5,"column":9},"end":{"line":5,"column":21}},"loc":{"start":{"line":5,"column":24},"end":{"line":29,"column":1}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":6,"column":9},"end":{"line":6,"column":10}},"loc":{"start":{"line":6,"column":35},"end":{"line":28,"column":3}},"line":6}},"branchMap":{"0":{"loc":{"start":{"line":9,"column":6},"end":{"line":12,"column":7}},"type":"if","locations":[{"start":{"line":9,"column":6},"end":{"line":12,"column":7}},{"start":{},"end":{}}],"line":9},"1":{"loc":{"start":{"line":15,"column":6},"end":{"line":18,"column":7}},"type":"if","locations":[{"start":{"line":15,"column":6},"end":{"line":18,"column":7}},{"start":{},"end":{}}],"line":15}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":1,"17":1,"18":1},"f":{"0":1,"1":1},"b":{"0":[1,0],"1":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"a58617a5d6872fe3545e0b9e4dad0eb27bed516a"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/middleware/authorization.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/middleware/authorization.js","statementMap":{"0":{"start":{"line":1,"column":14},"end":{"line":1,"column":33}},"1":{"start":{"line":2,"column":13},"end":{"line":2,"column":64}},"2":{"start":{"line":3,"column":29},"end":{"line":3,"column":57}},"3":{"start":{"line":4,"column":15},"end":{"line":4,"column":47}},"4":{"start":{"line":7,"column":2},"end":{"line":24,"column":4}},"5":{"start":{"line":8,"column":4},"end":{"line":23,"column":5}},"6":{"start":{"line":9,"column":21},"end":{"line":9,"column":31}},"7":{"start":{"line":10,"column":6},"end":{"line":12,"column":7}},"8":{"start":{"line":11,"column":8},"end":{"line":11,"column":68}},"9":{"start":{"line":14,"column":21},"end":{"line":14,"column":44}},"10":{"start":{"line":15,"column":23},"end":{"line":15,"column":60}},"11":{"start":{"line":16,"column":6},"end":{"line":18,"column":7}},"12":{"start":{"line":17,"column":8},"end":{"line":17,"column":88}},"13":{"start":{"line":19,"column":6},"end":{"line":19,"column":13}},"14":{"start":{"line":21,"column":6},"end":{"line":21,"column":48}},"15":{"start":{"line":22,"column":6},"end":{"line":22,"column":35}},"16":{"start":{"line":27,"column":23},"end":{"line":30,"column":1}},"17":{"start":{"line":33,"column":2},"end":{"line":63,"column":4}},"18":{"start":{"line":34,"column":4},"end":{"line":34,"column":55}},"19":{"start":{"line":35,"column":4},"end":{"line":62,"column":5}},"20":{"start":{"line":36,"column":25},"end":{"line":36,"column":38}},"21":{"start":{"line":37,"column":21},"end":{"line":37,"column":31}},"22":{"start":{"line":38,"column":6},"end":{"line":40,"column":7}},"23":{"start":{"line":39,"column":8},"end":{"line":39,"column":68}},"24":{"start":{"line":42,"column":6},"end":{"line":44,"column":7}},"25":{"start":{"line":43,"column":8},"end":{"line":43,"column":108}},"26":{"start":{"line":47,"column":6},"end":{"line":52,"column":7}},"27":{"start":{"line":48,"column":8},"end":{"line":48,"column":63}},"28":{"start":{"line":50,"column":23},"end":{"line":50,"column":82}},"29":{"start":{"line":51,"column":8},"end":{"line":51,"column":54}},"30":{"start":{"line":54,"column":6},"end":{"line":56,"column":7}},"31":{"start":{"line":55,"column":8},"end":{"line":55,"column":86}},"32":{"start":{"line":58,"column":6},"end":{"line":58,"column":13}},"33":{"start":{"line":60,"column":6},"end":{"line":60,"column":53}},"34":{"start":{"line":61,"column":6},"end":{"line":61,"column":35}},"35":{"start":{"line":67,"column":0},"end":{"line":67,"column":65}}},"fnMap":{"0":{"name":"verifyRole","decl":{"start":{"line":6,"column":9},"end":{"line":6,"column":19}},"loc":{"start":{"line":6,"column":30},"end":{"line":25,"column":1}},"line":6},"1":{"name":"(anonymous_1)","decl":{"start":{"line":7,"column":9},"end":{"line":7,"column":10}},"loc":{"start":{"line":7,"column":35},"end":{"line":24,"column":3}},"line":7},"2":{"name":"verifyOwnership","decl":{"start":{"line":32,"column":9},"end":{"line":32,"column":24}},"loc":{"start":{"line":32,"column":38},"end":{"line":64,"column":1}},"line":32},"3":{"name":"(anonymous_3)","decl":{"start":{"line":33,"column":9},"end":{"line":33,"column":10}},"loc":{"start":{"line":33,"column":35},"end":{"line":63,"column":3}},"line":33}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":6},"end":{"line":12,"column":7}},"type":"if","locations":[{"start":{"line":10,"column":6},"end":{"line":12,"column":7}},{"start":{},"end":{}}],"line":10},"1":{"loc":{"start":{"line":16,"column":6},"end":{"line":18,"column":7}},"type":"if","locations":[{"start":{"line":16,"column":6},"end":{"line":18,"column":7}},{"start":{},"end":{}}],"line":16},"2":{"loc":{"start":{"line":38,"column":6},"end":{"line":40,"column":7}},"type":"if","locations":[{"start":{"line":38,"column":6},"end":{"line":40,"column":7}},{"start":{},"end":{}}],"line":38},"3":{"loc":{"start":{"line":42,"column":6},"end":{"line":44,"column":7}},"type":"if","locations":[{"start":{"line":42,"column":6},"end":{"line":44,"column":7}},{"start":{},"end":{}}],"line":42},"4":{"loc":{"start":{"line":42,"column":10},"end":{"line":42,"column":32}},"type":"binary-expr","locations":[{"start":{"line":42,"column":10},"end":{"line":42,"column":21}},{"start":{"line":42,"column":25},"end":{"line":42,"column":32}}],"line":42},"5":{"loc":{"start":{"line":47,"column":6},"end":{"line":52,"column":7}},"type":"if","locations":[{"start":{"line":47,"column":6},"end":{"line":52,"column":7}},{"start":{"line":49,"column":13},"end":{"line":52,"column":7}}],"line":47},"6":{"loc":{"start":{"line":51,"column":19},"end":{"line":51,"column":53}},"type":"binary-expr","locations":[{"start":{"line":51,"column":19},"end":{"line":51,"column":30}},{"start":{"line":51,"column":34},"end":{"line":51,"column":53}}],"line":51},"7":{"loc":{"start":{"line":54,"column":6},"end":{"line":56,"column":7}},"type":"if","locations":[{"start":{"line":54,"column":6},"end":{"line":56,"column":7}},{"start":{},"end":{}}],"line":54}},"s":{"0":1,"1":1,"2":1,"3":1,"4":2,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":1,"17":2,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":1},"f":{"0":2,"1":0,"2":2,"3":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"c2e76812cb4e313c0dd5250f6ce6362258b5877d"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/applications_repository.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/applications_repository.js","statementMap":{"0":{"start":{"line":1,"column":20},"end":{"line":1,"column":36}},"1":{"start":{"line":3,"column":13},"end":{"line":3,"column":22}},"2":{"start":{"line":6,"column":2},"end":{"line":6,"column":48}},"3":{"start":{"line":10,"column":2},"end":{"line":12,"column":20}},"4":{"start":{"line":16,"column":24},"end":{"line":19,"column":19}},"5":{"start":{"line":20,"column":2},"end":{"line":20,"column":21}},"6":{"start":{"line":24,"column":2},"end":{"line":26,"column":13}},"7":{"start":{"line":29,"column":0},"end":{"line":29,"column":109}}},"fnMap":{"0":{"name":"getAllApplications","decl":{"start":{"line":5,"column":15},"end":{"line":5,"column":33}},"loc":{"start":{"line":5,"column":36},"end":{"line":7,"column":1}},"line":5},"1":{"name":"createNewApplication","decl":{"start":{"line":9,"column":15},"end":{"line":9,"column":35}},"loc":{"start":{"line":9,"column":84},"end":{"line":13,"column":1}},"line":9},"2":{"name":"updateApplicationById","decl":{"start":{"line":15,"column":15},"end":{"line":15,"column":36}},"loc":{"start":{"line":15,"column":82},"end":{"line":21,"column":1}},"line":15},"3":{"name":"getApplicationByFields","decl":{"start":{"line":23,"column":15},"end":{"line":23,"column":37}},"loc":{"start":{"line":23,"column":46},"end":{"line":27,"column":1}},"line":23}},"branchMap":{},"s":{"0":1,"1":1,"2":0,"3":0,"4":0,"5":0,"6":0,"7":1},"f":{"0":0,"1":0,"2":0,"3":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"e2f5459c12f1e6248f847d4cf039322c3d7aedad"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/authorization_repository.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/authorization_repository.js","statementMap":{"0":{"start":{"line":1,"column":20},"end":{"line":1,"column":36}},"1":{"start":{"line":4,"column":15},"end":{"line":4,"column":24}},"2":{"start":{"line":5,"column":2},"end":{"line":5,"column":99}},"3":{"start":{"line":9,"column":15},"end":{"line":9,"column":24}},"4":{"start":{"line":10,"column":2},"end":{"line":10,"column":100}},"5":{"start":{"line":13,"column":0},"end":{"line":13,"column":49}}},"fnMap":{"0":{"name":"verifyRole","decl":{"start":{"line":3,"column":15},"end":{"line":3,"column":25}},"loc":{"start":{"line":3,"column":42},"end":{"line":6,"column":1}},"line":3},"1":{"name":"verifyOwnership","decl":{"start":{"line":8,"column":15},"end":{"line":8,"column":30}},"loc":{"start":{"line":8,"column":64},"end":{"line":11,"column":1}},"line":8}},"branchMap":{},"s":{"0":1,"1":0,"2":0,"3":0,"4":0,"5":1},"f":{"0":0,"1":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"b2303f11089ff50517ce5c5d6d75731c1c7305c4"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/roles_repository.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/roles_repository.js","statementMap":{"0":{"start":{"line":1,"column":20},"end":{"line":1,"column":36}},"1":{"start":{"line":4,"column":15},"end":{"line":4,"column":24}},"2":{"start":{"line":5,"column":2},"end":{"line":5,"column":41}},"3":{"start":{"line":9,"column":15},"end":{"line":9,"column":24}},"4":{"start":{"line":10,"column":2},"end":{"line":10,"column":72}},"5":{"start":{"line":13,"column":0},"end":{"line":13,"column":51}}},"fnMap":{"0":{"name":"getAllRoles","decl":{"start":{"line":3,"column":15},"end":{"line":3,"column":26}},"loc":{"start":{"line":3,"column":29},"end":{"line":6,"column":1}},"line":3},"1":{"name":"assignRoleToUser","decl":{"start":{"line":8,"column":15},"end":{"line":8,"column":31}},"loc":{"start":{"line":8,"column":48},"end":{"line":11,"column":1}},"line":8}},"branchMap":{},"s":{"0":2,"1":0,"2":0,"3":0,"4":0,"5":2},"f":{"0":0,"1":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"44b0fa85d8948cc68ca25e6099db30c902e41e50"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/user_repository.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/user_repository.js","statementMap":{"0":{"start":{"line":1,"column":20},"end":{"line":1,"column":36}},"1":{"start":{"line":4,"column":15},"end":{"line":4,"column":24}},"2":{"start":{"line":5,"column":2},"end":{"line":5,"column":63}},"3":{"start":{"line":9,"column":15},"end":{"line":9,"column":24}},"4":{"start":{"line":10,"column":17},"end":{"line":12,"column":19}},"5":{"start":{"line":13,"column":2},"end":{"line":13,"column":29}},"6":{"start":{"line":14,"column":2},"end":{"line":14,"column":14}},"7":{"start":{"line":18,"column":15},"end":{"line":18,"column":24}},"8":{"start":{"line":19,"column":15},"end":{"line":19,"column":58}},"9":{"start":{"line":20,"column":2},"end":{"line":22,"column":3}},"10":{"start":{"line":21,"column":4},"end":{"line":21,"column":31}},"11":{"start":{"line":23,"column":2},"end":{"line":23,"column":14}},"12":{"start":{"line":27,"column":15},"end":{"line":27,"column":24}},"13":{"start":{"line":28,"column":15},"end":{"line":28,"column":56}},"14":{"start":{"line":29,"column":2},"end":{"line":31,"column":3}},"15":{"start":{"line":30,"column":4},"end":{"line":30,"column":31}},"16":{"start":{"line":32,"column":2},"end":{"line":32,"column":14}},"17":{"start":{"line":36,"column":15},"end":{"line":36,"column":24}},"18":{"start":{"line":37,"column":2},"end":{"line":40,"column":43}},"19":{"start":{"line":44,"column":15},"end":{"line":44,"column":24}},"20":{"start":{"line":45,"column":17},"end":{"line":48,"column":19}},"21":{"start":{"line":49,"column":2},"end":{"line":51,"column":3}},"22":{"start":{"line":50,"column":4},"end":{"line":50,"column":31}},"23":{"start":{"line":52,"column":2},"end":{"line":52,"column":14}},"24":{"start":{"line":56,"column":15},"end":{"line":56,"column":24}},"25":{"start":{"line":57,"column":2},"end":{"line":57,"column":51}},"26":{"start":{"line":60,"column":0},"end":{"line":61,"column":36}}},"fnMap":{"0":{"name":"getAllUsers","decl":{"start":{"line":3,"column":15},"end":{"line":3,"column":26}},"loc":{"start":{"line":3,"column":29},"end":{"line":6,"column":1}},"line":3},"1":{"name":"createNewUser","decl":{"start":{"line":8,"column":15},"end":{"line":8,"column":28}},"loc":{"start":{"line":8,"column":37},"end":{"line":15,"column":1}},"line":8},"2":{"name":"getUserInfoById","decl":{"start":{"line":17,"column":15},"end":{"line":17,"column":30}},"loc":{"start":{"line":17,"column":35},"end":{"line":24,"column":1}},"line":17},"3":{"name":"getUserByFields","decl":{"start":{"line":26,"column":15},"end":{"line":26,"column":30}},"loc":{"start":{"line":26,"column":39},"end":{"line":33,"column":1}},"line":26},"4":{"name":"getUserRolesById","decl":{"start":{"line":35,"column":15},"end":{"line":35,"column":31}},"loc":{"start":{"line":35,"column":36},"end":{"line":41,"column":1}},"line":35},"5":{"name":"updateUserById","decl":{"start":{"line":43,"column":15},"end":{"line":43,"column":29}},"loc":{"start":{"line":43,"column":43},"end":{"line":53,"column":1}},"line":43},"6":{"name":"deleteUserById","decl":{"start":{"line":55,"column":15},"end":{"line":55,"column":29}},"loc":{"start":{"line":55,"column":34},"end":{"line":58,"column":1}},"line":55}},"branchMap":{"0":{"loc":{"start":{"line":20,"column":2},"end":{"line":22,"column":3}},"type":"if","locations":[{"start":{"line":20,"column":2},"end":{"line":22,"column":3}},{"start":{},"end":{}}],"line":20},"1":{"loc":{"start":{"line":29,"column":2},"end":{"line":31,"column":3}},"type":"if","locations":[{"start":{"line":29,"column":2},"end":{"line":31,"column":3}},{"start":{},"end":{}}],"line":29},"2":{"loc":{"start":{"line":49,"column":2},"end":{"line":51,"column":3}},"type":"if","locations":[{"start":{"line":49,"column":2},"end":{"line":51,"column":3}},{"start":{},"end":{}}],"line":49}},"s":{"0":2,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":2},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"f01ffb29044108361e68cf3b54cf71786f38931a"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/routes/users.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/routes/users.js","statementMap":{"0":{"start":{"line":1,"column":16},"end":{"line":1,"column":34}},"1":{"start":{"line":3,"column":15},"end":{"line":3,"column":31}},"2":{"start":{"line":4,"column":20},"end":{"line":4,"column":55}},"3":{"start":{"line":5,"column":20},"end":{"line":5,"column":55}},"4":{"start":{"line":6,"column":23},"end":{"line":6,"column":42}},"5":{"start":{"line":7,"column":54},"end":{"line":7,"column":92}},"6":{"start":{"line":9,"column":66},"end":{"line":9,"column":86}},"7":{"start":{"line":11,"column":15},"end":{"line":11,"column":47}},"8":{"start":{"line":13,"column":0},"end":{"line":13,"column":27}},"9":{"start":{"line":16,"column":0},"end":{"line":40,"column":3}},"10":{"start":{"line":17,"column":2},"end":{"line":17,"column":40}},"11":{"start":{"line":18,"column":24},"end":{"line":18,"column":59}},"12":{"start":{"line":19,"column":22},"end":{"line":19,"column":55}},"13":{"start":{"line":21,"column":2},"end":{"line":39,"column":3}},"14":{"start":{"line":24,"column":4},"end":{"line":29,"column":81}},"15":{"start":{"line":25,"column":6},"end":{"line":25,"column":65}},"16":{"start":{"line":26,"column":11},"end":{"line":29,"column":81}},"17":{"start":{"line":27,"column":6},"end":{"line":27,"column":62}},"18":{"start":{"line":29,"column":9},"end":{"line":29,"column":81}},"19":{"start":{"line":31,"column":4},"end":{"line":31,"column":95}},"20":{"start":{"line":32,"column":4},"end":{"line":32,"column":41}},"21":{"start":{"line":35,"column":4},"end":{"line":37,"column":5}},"22":{"start":{"line":36,"column":6},"end":{"line":36,"column":64}},"23":{"start":{"line":38,"column":4},"end":{"line":38,"column":63}},"24":{"start":{"line":43,"column":0},"end":{"line":51,"column":3}},"25":{"start":{"line":44,"column":2},"end":{"line":50,"column":3}},"26":{"start":{"line":45,"column":18},"end":{"line":45,"column":49}},"27":{"start":{"line":46,"column":4},"end":{"line":46,"column":24}},"28":{"start":{"line":48,"column":4},"end":{"line":48,"column":22}},"29":{"start":{"line":49,"column":4},"end":{"line":49,"column":26}},"30":{"start":{"line":54,"column":0},"end":{"line":77,"column":3}},"31":{"start":{"line":55,"column":44},"end":{"line":55,"column":52}},"32":{"start":{"line":57,"column":2},"end":{"line":76,"column":3}},"33":{"start":{"line":60,"column":17},"end":{"line":60,"column":85}},"34":{"start":{"line":61,"column":4},"end":{"line":61,"column":42}},"35":{"start":{"line":63,"column":4},"end":{"line":63,"column":22}},"36":{"start":{"line":64,"column":26},"end":{"line":64,"column":80}},"37":{"start":{"line":65,"column":4},"end":{"line":74,"column":5}},"38":{"start":{"line":66,"column":6},"end":{"line":70,"column":8}},"39":{"start":{"line":71,"column":11},"end":{"line":74,"column":5}},"40":{"start":{"line":72,"column":6},"end":{"line":73,"column":56}},"41":{"start":{"line":75,"column":4},"end":{"line":75,"column":26}},"42":{"start":{"line":80,"column":0},"end":{"line":95,"column":3}},"43":{"start":{"line":81,"column":17},"end":{"line":81,"column":27}},"44":{"start":{"line":82,"column":2},"end":{"line":84,"column":3}},"45":{"start":{"line":83,"column":4},"end":{"line":83,"column":66}},"46":{"start":{"line":85,"column":2},"end":{"line":94,"column":3}},"47":{"start":{"line":86,"column":21},"end":{"line":86,"column":54}},"48":{"start":{"line":87,"column":4},"end":{"line":89,"column":5}},"49":{"start":{"line":88,"column":6},"end":{"line":88,"column":58}},"50":{"start":{"line":90,"column":4},"end":{"line":90,"column":23}},"51":{"start":{"line":92,"column":4},"end":{"line":92,"column":22}},"52":{"start":{"line":93,"column":4},"end":{"line":93,"column":26}},"53":{"start":{"line":98,"column":0},"end":{"line":114,"column":3}},"54":{"start":{"line":99,"column":17},"end":{"line":99,"column":27}},"55":{"start":{"line":100,"column":37},"end":{"line":100,"column":45}},"56":{"start":{"line":101,"column":2},"end":{"line":103,"column":3}},"57":{"start":{"line":102,"column":4},"end":{"line":102,"column":66}},"58":{"start":{"line":104,"column":2},"end":{"line":113,"column":3}},"59":{"start":{"line":105,"column":24},"end":{"line":105,"column":112}},"60":{"start":{"line":106,"column":4},"end":{"line":108,"column":5}},"61":{"start":{"line":107,"column":6},"end":{"line":107,"column":58}},"62":{"start":{"line":109,"column":4},"end":{"line":109,"column":38}},"63":{"start":{"line":111,"column":4},"end":{"line":111,"column":22}},"64":{"start":{"line":112,"column":4},"end":{"line":112,"column":26}},"65":{"start":{"line":117,"column":0},"end":{"line":132,"column":3}},"66":{"start":{"line":118,"column":17},"end":{"line":118,"column":27}},"67":{"start":{"line":119,"column":2},"end":{"line":121,"column":3}},"68":{"start":{"line":120,"column":4},"end":{"line":120,"column":66}},"69":{"start":{"line":122,"column":2},"end":{"line":131,"column":3}},"70":{"start":{"line":123,"column":19},"end":{"line":123,"column":55}},"71":{"start":{"line":124,"column":4},"end":{"line":126,"column":5}},"72":{"start":{"line":125,"column":6},"end":{"line":125,"column":59}},"73":{"start":{"line":127,"column":4},"end":{"line":127,"column":42}},"74":{"start":{"line":129,"column":4},"end":{"line":129,"column":22}},"75":{"start":{"line":130,"column":4},"end":{"line":130,"column":26}},"76":{"start":{"line":134,"column":0},"end":{"line":134,"column":24}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":16,"column":28},"end":{"line":16,"column":29}},"loc":{"start":{"line":16,"column":48},"end":{"line":40,"column":1}},"line":16},"1":{"name":"(anonymous_1)","decl":{"start":{"line":43,"column":52},"end":{"line":43,"column":53}},"loc":{"start":{"line":43,"column":72},"end":{"line":51,"column":1}},"line":43},"2":{"name":"(anonymous_2)","decl":{"start":{"line":54,"column":22},"end":{"line":54,"column":23}},"loc":{"start":{"line":54,"column":42},"end":{"line":77,"column":1}},"line":54},"3":{"name":"(anonymous_3)","decl":{"start":{"line":80,"column":56},"end":{"line":80,"column":57}},"loc":{"start":{"line":80,"column":76},"end":{"line":95,"column":1}},"line":80},"4":{"name":"(anonymous_4)","decl":{"start":{"line":98,"column":63},"end":{"line":98,"column":64}},"loc":{"start":{"line":98,"column":83},"end":{"line":114,"column":1}},"line":98},"5":{"name":"(anonymous_5)","decl":{"start":{"line":117,"column":66},"end":{"line":117,"column":67}},"loc":{"start":{"line":117,"column":86},"end":{"line":132,"column":1}},"line":117}},"branchMap":{"0":{"loc":{"start":{"line":18,"column":24},"end":{"line":18,"column":59}},"type":"binary-expr","locations":[{"start":{"line":18,"column":24},"end":{"line":18,"column":51}},{"start":{"line":18,"column":55},"end":{"line":18,"column":59}}],"line":18},"1":{"loc":{"start":{"line":19,"column":22},"end":{"line":19,"column":55}},"type":"binary-expr","locations":[{"start":{"line":19,"column":22},"end":{"line":19,"column":47}},{"start":{"line":19,"column":51},"end":{"line":19,"column":55}}],"line":19},"2":{"loc":{"start":{"line":24,"column":4},"end":{"line":29,"column":81}},"type":"if","locations":[{"start":{"line":24,"column":4},"end":{"line":29,"column":81}},{"start":{"line":26,"column":11},"end":{"line":29,"column":81}}],"line":24},"3":{"loc":{"start":{"line":26,"column":11},"end":{"line":29,"column":81}},"type":"if","locations":[{"start":{"line":26,"column":11},"end":{"line":29,"column":81}},{"start":{"line":29,"column":9},"end":{"line":29,"column":81}}],"line":26},"4":{"loc":{"start":{"line":35,"column":4},"end":{"line":37,"column":5}},"type":"if","locations":[{"start":{"line":35,"column":4},"end":{"line":37,"column":5}},{"start":{},"end":{}}],"line":35},"5":{"loc":{"start":{"line":65,"column":4},"end":{"line":74,"column":5}},"type":"if","locations":[{"start":{"line":65,"column":4},"end":{"line":74,"column":5}},{"start":{"line":71,"column":11},"end":{"line":74,"column":5}}],"line":65},"6":{"loc":{"start":{"line":71,"column":11},"end":{"line":74,"column":5}},"type":"if","locations":[{"start":{"line":71,"column":11},"end":{"line":74,"column":5}},{"start":{},"end":{}}],"line":71},"7":{"loc":{"start":{"line":82,"column":2},"end":{"line":84,"column":3}},"type":"if","locations":[{"start":{"line":82,"column":2},"end":{"line":84,"column":3}},{"start":{},"end":{}}],"line":82},"8":{"loc":{"start":{"line":87,"column":4},"end":{"line":89,"column":5}},"type":"if","locations":[{"start":{"line":87,"column":4},"end":{"line":89,"column":5}},{"start":{},"end":{}}],"line":87},"9":{"loc":{"start":{"line":101,"column":2},"end":{"line":103,"column":3}},"type":"if","locations":[{"start":{"line":101,"column":2},"end":{"line":103,"column":3}},{"start":{},"end":{}}],"line":101},"10":{"loc":{"start":{"line":106,"column":4},"end":{"line":108,"column":5}},"type":"if","locations":[{"start":{"line":106,"column":4},"end":{"line":108,"column":5}},{"start":{},"end":{}}],"line":106},"11":{"loc":{"start":{"line":119,"column":2},"end":{"line":121,"column":3}},"type":"if","locations":[{"start":{"line":119,"column":2},"end":{"line":121,"column":3}},{"start":{},"end":{}}],"line":119},"12":{"loc":{"start":{"line":124,"column":4},"end":{"line":126,"column":5}},"type":"if","locations":[{"start":{"line":124,"column":4},"end":{"line":126,"column":5}},{"start":{},"end":{}}],"line":124}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":1,"25":0,"26":0,"27":0,"28":0,"29":0,"30":1,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":1,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":1,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":1,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":1},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"d575c6d75ca35ae6d0aeb047a5c799340f2c0fd9"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/applications_service.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/applications_service.js","statementMap":{"0":{"start":{"line":1,"column":13},"end":{"line":1,"column":63}},"1":{"start":{"line":4,"column":2},"end":{"line":4,"column":41}},"2":{"start":{"line":8,"column":2},"end":{"line":8,"column":89}},"3":{"start":{"line":12,"column":2},"end":{"line":12,"column":87}},"4":{"start":{"line":16,"column":2},"end":{"line":16,"column":59}},"5":{"start":{"line":20,"column":2},"end":{"line":20,"column":53}},"6":{"start":{"line":23,"column":0},"end":{"line":23,"column":130}}},"fnMap":{"0":{"name":"getAllApplications","decl":{"start":{"line":3,"column":15},"end":{"line":3,"column":33}},"loc":{"start":{"line":3,"column":36},"end":{"line":5,"column":1}},"line":3},"1":{"name":"createNewApplication","decl":{"start":{"line":7,"column":15},"end":{"line":7,"column":35}},"loc":{"start":{"line":7,"column":84},"end":{"line":9,"column":1}},"line":7},"2":{"name":"updateApplicationStatus","decl":{"start":{"line":11,"column":15},"end":{"line":11,"column":38}},"loc":{"start":{"line":11,"column":84},"end":{"line":13,"column":1}},"line":11},"3":{"name":"getApplicationByEmail","decl":{"start":{"line":15,"column":15},"end":{"line":15,"column":36}},"loc":{"start":{"line":15,"column":44},"end":{"line":17,"column":1}},"line":15},"4":{"name":"getApplicationById","decl":{"start":{"line":19,"column":15},"end":{"line":19,"column":33}},"loc":{"start":{"line":19,"column":38},"end":{"line":21,"column":1}},"line":19}},"branchMap":{},"s":{"0":1,"1":0,"2":0,"3":0,"4":0,"5":0,"6":1},"f":{"0":0,"1":0,"2":0,"3":0,"4":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"d60ce397ae75dfec103613819ef03713325e0118"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/auth_service.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/auth_service.js","statementMap":{"0":{"start":{"line":1,"column":14},"end":{"line":1,"column":39}},"1":{"start":{"line":2,"column":14},"end":{"line":2,"column":30}},"2":{"start":{"line":3,"column":20},"end":{"line":3,"column":42}},"3":{"start":{"line":4,"column":20},"end":{"line":4,"column":55}},"4":{"start":{"line":5,"column":20},"end":{"line":5,"column":63}},"5":{"start":{"line":7,"column":22},"end":{"line":7,"column":45}},"6":{"start":{"line":9,"column":15},"end":{"line":9,"column":47}},"7":{"start":{"line":12,"column":2},"end":{"line":43,"column":3}},"8":{"start":{"line":13,"column":25},"end":{"line":13,"column":72}},"9":{"start":{"line":14,"column":18},"end":{"line":14,"column":36}},"10":{"start":{"line":15,"column":23},"end":{"line":15,"column":39}},"11":{"start":{"line":17,"column":4},"end":{"line":19,"column":5}},"12":{"start":{"line":18,"column":6},"end":{"line":18,"column":59}},"13":{"start":{"line":21,"column":24},"end":{"line":21,"column":70}},"14":{"start":{"line":22,"column":4},"end":{"line":24,"column":5}},"15":{"start":{"line":23,"column":6},"end":{"line":23,"column":86}},"16":{"start":{"line":26,"column":15},"end":{"line":26,"column":54}},"17":{"start":{"line":27,"column":4},"end":{"line":35,"column":5}},"18":{"start":{"line":28,"column":6},"end":{"line":34,"column":8}},"19":{"start":{"line":37,"column":4},"end":{"line":37,"column":75}},"20":{"start":{"line":39,"column":4},"end":{"line":39,"column":16}},"21":{"start":{"line":41,"column":4},"end":{"line":41,"column":81}},"22":{"start":{"line":42,"column":4},"end":{"line":42,"column":51}},"23":{"start":{"line":49,"column":2},"end":{"line":82,"column":3}},"24":{"start":{"line":51,"column":26},"end":{"line":63,"column":8}},"25":{"start":{"line":65,"column":25},"end":{"line":69,"column":6}},"26":{"start":{"line":71,"column":24},"end":{"line":71,"column":41}},"27":{"start":{"line":72,"column":17},"end":{"line":72,"column":68}},"28":{"start":{"line":74,"column":4},"end":{"line":76,"column":5}},"29":{"start":{"line":75,"column":6},"end":{"line":75,"column":55}},"30":{"start":{"line":78,"column":4},"end":{"line":78,"column":20}},"31":{"start":{"line":80,"column":4},"end":{"line":80,"column":78}},"32":{"start":{"line":81,"column":4},"end":{"line":81,"column":51}},"33":{"start":{"line":86,"column":0},"end":{"line":86,"column":58}}},"fnMap":{"0":{"name":"handleFirebaseAuth","decl":{"start":{"line":11,"column":15},"end":{"line":11,"column":33}},"loc":{"start":{"line":11,"column":49},"end":{"line":44,"column":1}},"line":11},"1":{"name":"handleDiscordAuth","decl":{"start":{"line":48,"column":15},"end":{"line":48,"column":32}},"loc":{"start":{"line":48,"column":38},"end":{"line":83,"column":1}},"line":48}},"branchMap":{"0":{"loc":{"start":{"line":17,"column":4},"end":{"line":19,"column":5}},"type":"if","locations":[{"start":{"line":17,"column":4},"end":{"line":19,"column":5}},{"start":{},"end":{}}],"line":17},"1":{"loc":{"start":{"line":22,"column":4},"end":{"line":24,"column":5}},"type":"if","locations":[{"start":{"line":22,"column":4},"end":{"line":24,"column":5}},{"start":{},"end":{}}],"line":22},"2":{"loc":{"start":{"line":22,"column":8},"end":{"line":22,"column":57}},"type":"binary-expr","locations":[{"start":{"line":22,"column":8},"end":{"line":22,"column":20}},{"start":{"line":22,"column":24},"end":{"line":22,"column":57}}],"line":22},"3":{"loc":{"start":{"line":27,"column":4},"end":{"line":35,"column":5}},"type":"if","locations":[{"start":{"line":27,"column":4},"end":{"line":35,"column":5}},{"start":{},"end":{}}],"line":27},"4":{"loc":{"start":{"line":74,"column":4},"end":{"line":76,"column":5}},"type":"if","locations":[{"start":{"line":74,"column":4},"end":{"line":76,"column":5}},{"start":{},"end":{}}],"line":74}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":1},"f":{"0":0,"1":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"c9f8ebea05957cd72bdcb4fe9cac1e4952e35c25"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/roles_service.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/roles_service.js","statementMap":{"0":{"start":{"line":1,"column":13},"end":{"line":1,"column":56}},"1":{"start":{"line":2,"column":14},"end":{"line":2,"column":33}},"2":{"start":{"line":5,"column":2},"end":{"line":10,"column":3}},"3":{"start":{"line":6,"column":19},"end":{"line":6,"column":42}},"4":{"start":{"line":7,"column":4},"end":{"line":7,"column":48}},"5":{"start":{"line":9,"column":4},"end":{"line":9,"column":80}},"6":{"start":{"line":14,"column":2},"end":{"line":14,"column":34}},"7":{"start":{"line":17,"column":0},"end":{"line":17,"column":51}}},"fnMap":{"0":{"name":"assignRoleToUser","decl":{"start":{"line":4,"column":15},"end":{"line":4,"column":31}},"loc":{"start":{"line":4,"column":50},"end":{"line":11,"column":1}},"line":4},"1":{"name":"getAllRoles","decl":{"start":{"line":13,"column":15},"end":{"line":13,"column":26}},"loc":{"start":{"line":13,"column":29},"end":{"line":15,"column":1}},"line":13}},"branchMap":{},"s":{"0":2,"1":2,"2":0,"3":0,"4":0,"5":0,"6":0,"7":2},"f":{"0":0,"1":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"045b74db2a85cdc9d05532e7171e47199ba12e54"} +,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/user_service.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/user_service.js","statementMap":{"0":{"start":{"line":1,"column":13},"end":{"line":1,"column":55}},"1":{"start":{"line":2,"column":21},"end":{"line":2,"column":57}},"2":{"start":{"line":6,"column":2},"end":{"line":6,"column":34}},"3":{"start":{"line":10,"column":13},"end":{"line":11,"column":62}},"4":{"start":{"line":12,"column":2},"end":{"line":12,"column":57}},"5":{"start":{"line":13,"column":2},"end":{"line":13,"column":14}},"6":{"start":{"line":17,"column":19},"end":{"line":17,"column":49}},"7":{"start":{"line":18,"column":2},"end":{"line":21,"column":3}},"8":{"start":{"line":19,"column":18},"end":{"line":19,"column":49}},"9":{"start":{"line":20,"column":4},"end":{"line":20,"column":47}},"10":{"start":{"line":22,"column":2},"end":{"line":22,"column":18}},"11":{"start":{"line":26,"column":2},"end":{"line":26,"column":48}},"12":{"start":{"line":30,"column":2},"end":{"line":30,"column":39}},"13":{"start":{"line":34,"column":2},"end":{"line":34,"column":63}},"14":{"start":{"line":38,"column":2},"end":{"line":38,"column":52}},"15":{"start":{"line":41,"column":0},"end":{"line":42,"column":40}}},"fnMap":{"0":{"name":"getAllUsers","decl":{"start":{"line":4,"column":15},"end":{"line":4,"column":26}},"loc":{"start":{"line":4,"column":29},"end":{"line":7,"column":1}},"line":4},"1":{"name":"createNewMemberUser","decl":{"start":{"line":9,"column":15},"end":{"line":9,"column":34}},"loc":{"start":{"line":9,"column":83},"end":{"line":14,"column":1}},"line":9},"2":{"name":"getUserById","decl":{"start":{"line":16,"column":15},"end":{"line":16,"column":26}},"loc":{"start":{"line":16,"column":31},"end":{"line":23,"column":1}},"line":16},"3":{"name":"updateUserById","decl":{"start":{"line":25,"column":15},"end":{"line":25,"column":29}},"loc":{"start":{"line":25,"column":43},"end":{"line":27,"column":1}},"line":25},"4":{"name":"deleteUserById","decl":{"start":{"line":29,"column":15},"end":{"line":29,"column":29}},"loc":{"start":{"line":29,"column":34},"end":{"line":31,"column":1}},"line":29},"5":{"name":"getUserByFirebaseId","decl":{"start":{"line":33,"column":15},"end":{"line":33,"column":34}},"loc":{"start":{"line":33,"column":47},"end":{"line":35,"column":1}},"line":33},"6":{"name":"getUserByEmail","decl":{"start":{"line":37,"column":15},"end":{"line":37,"column":29}},"loc":{"start":{"line":37,"column":37},"end":{"line":39,"column":1}},"line":37}},"branchMap":{"0":{"loc":{"start":{"line":18,"column":2},"end":{"line":21,"column":3}},"type":"if","locations":[{"start":{"line":18,"column":2},"end":{"line":21,"column":3}},{"start":{},"end":{}}],"line":18},"1":{"loc":{"start":{"line":20,"column":21},"end":{"line":20,"column":46}},"type":"cond-expr","locations":[{"start":{"line":20,"column":36},"end":{"line":20,"column":41}},{"start":{"line":20,"column":44},"end":{"line":20,"column":46}}],"line":20}},"s":{"0":2,"1":2,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":2},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0},"b":{"0":[0,0],"1":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"01f8e055ec5113921e4a49bd541a95106cdada12"} +} diff --git a/coverage/lcov-report/base.css b/coverage/lcov-report/base.css new file mode 100644 index 0000000..f418035 --- /dev/null +++ b/coverage/lcov-report/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/coverage/lcov-report/block-navigation.js b/coverage/lcov-report/block-navigation.js new file mode 100644 index 0000000..cc12130 --- /dev/null +++ b/coverage/lcov-report/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selecter that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/coverage/lcov-report/favicon.png b/coverage/lcov-report/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1525b811a167671e9de1fa78aab9f5c0b61cef7 GIT binary patch literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 27.93% + Statements + 100/358 +
+ + +
+ 4.08% + Branches + 4/98 +
+ + +
+ 10.71% + Functions + 6/56 +
+ + +
+ 27.73% + Lines + 99/357 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
webplatform-backend +
+
38.94%37/9510.71%3/2820%2/1038.29%36/94
webplatform-backend/aws +
+
15.78%3/190%0/40%0/315.78%3/19
webplatform-backend/middleware +
+
34.54%19/555%1/2066.66%4/634.54%19/55
webplatform-backend/repositories +
+
19.14%9/470%0/60%0/1519.14%9/47
webplatform-backend/routes +
+
20.77%16/770%0/260%0/620.77%16/77
webplatform-backend/services +
+
24.61%16/650%0/140%0/1624.61%16/65
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/prettify.css b/coverage/lcov-report/prettify.css new file mode 100644 index 0000000..b317a7c --- /dev/null +++ b/coverage/lcov-report/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/lcov-report/prettify.js b/coverage/lcov-report/prettify.js new file mode 100644 index 0000000..b322523 --- /dev/null +++ b/coverage/lcov-report/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/lcov-report/sort-arrow-sprite.png b/coverage/lcov-report/sort-arrow-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed68316eb3f65dec9063332d2f69bf3093bbfab GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc literal 0 HcmV?d00001 diff --git a/coverage/lcov-report/sorter.js b/coverage/lcov-report/sorter.js new file mode 100644 index 0000000..2bb296a --- /dev/null +++ b/coverage/lcov-report/sorter.js @@ -0,0 +1,196 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + if ( + row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()) + ) { + row.style.display = ''; + } else { + row.style.display = 'none'; + } + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/coverage/lcov-report/webplatform-backend/app.js.html b/coverage/lcov-report/webplatform-backend/app.js.html new file mode 100644 index 0000000..75dbfb1 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/app.js.html @@ -0,0 +1,187 @@ + + + + + + Code coverage report for webplatform-backend/app.js + + + + + + + + + +
+
+

All files / webplatform-backend app.js

+
+ +
+ 90% + Statements + 18/20 +
+ + +
+ 50% + Branches + 3/6 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 90% + Lines + 18/20 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +351x +1x +1x +1x +1x +1x +1x +  +1x +  +1x +  +  +1x +  +  +  +1x +1x +1x +  +1x +1x +  +  +  +  +  +  +  +  +1x +1x +  +1x
const config = require('config');
+const express = require("express");
+const cookieParser = require('cookie-parser');
+const cors = require("cors");
+const { authenticate } = require("./middleware/authentication");
+const pinoHttp = require('pino-http');
+const { baseLogger } = require('./logger');
+ 
+require('dotenv').config({ path: '.env.local' });
+ 
+const app = express();
+ 
+// Middleware
+Iif (process.env.LOGGING_HTTP_REQUESTS === 'true') {
+  app.use(pinoHttp({ logger: baseLogger }));
+}
+ 
+app.use(express.json());
+app.use(cookieParser());
+app.use(cors({
+  origin: (origin, callback) => {
+    if (!origin || config.cors.allowedOrigins.includes(origin)) {
+      callback(null, origin);
+    } else E{
+      callback(new Error('Not allowed by CORS'));
+    }
+  },
+  credentials: true,
+  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
+  allowedHeaders: ['Content-Type', 'Authorization', 'token'], 
+}));
+app.options('*', cors()); 
+app.use(authenticate());
+ 
+module.exports = app;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/aws/index.html b/coverage/lcov-report/webplatform-backend/aws/index.html new file mode 100644 index 0000000..d6bd954 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/aws/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for webplatform-backend/aws + + + + + + + + + +
+
+

All files webplatform-backend/aws

+
+ +
+ 15.78% + Statements + 3/19 +
+ + +
+ 0% + Branches + 0/4 +
+ + +
+ 0% + Functions + 0/3 +
+ + +
+ 15.78% + Lines + 3/19 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
ssm-helper.js +
+
15.78%3/190%0/40%0/315.78%3/19
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/aws/ssm-helper.js.html b/coverage/lcov-report/webplatform-backend/aws/ssm-helper.js.html new file mode 100644 index 0000000..dce333a --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/aws/ssm-helper.js.html @@ -0,0 +1,205 @@ + + + + + + Code coverage report for webplatform-backend/aws/ssm-helper.js + + + + + + + + + +
+
+

All files / webplatform-backend/aws ssm-helper.js

+
+ +
+ 15.78% + Statements + 3/19 +
+ + +
+ 0% + Branches + 0/4 +
+ + +
+ 0% + Functions + 0/3 +
+ + +
+ 15.78% + Lines + 3/19 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +412x +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x + 
const { SSMClient, GetParametersCommand } = require('@aws-sdk/client-ssm');
+const logger = require('../logger')(__filename);
+ 
+async function loadSecrets(region, names, withDecryption = false, parseJson = false) {
+  const ssmClient = new SSMClient({
+    region: region,
+  });      
+ 
+  try {
+    logger.debug(`Fetching secrets from region: ${region}`);
+    const command = new GetParametersCommand({
+      Names: names,
+      WithDecryption: withDecryption,
+    });
+ 
+    const result = await ssmClient.send(command);
+ 
+    const secrets = result.Parameters.reduce((acc, { Name, Value }) => {
+      const key = Name.split('/').pop();
+      acc[key] = parseJson ? parseJsonValue(Value) : Value;
+      return acc;
+    }, {});
+ 
+    logger.debug(Object.keys(secrets), 'Fetched secrets');
+    return secrets;
+  } catch (err) {
+    logger.error(err, 'Error fetching parameters');
+    throw err;
+  }
+}
+ 
+function parseJsonValue(value) {
+  try {
+    return JSON.parse(value);
+  } catch {
+    return value;
+  }
+}
+ 
+module.exports = { loadSecrets }; 
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/db.js.html b/coverage/lcov-report/webplatform-backend/db.js.html new file mode 100644 index 0000000..d559f9a --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/db.js.html @@ -0,0 +1,268 @@ + + + + + + Code coverage report for webplatform-backend/db.js + + + + + + + + + +
+
+

All files / webplatform-backend db.js

+
+ +
+ 28% + Statements + 7/25 +
+ + +
+ 0% + Branches + 0/12 +
+ + +
+ 33.33% + Functions + 1/3 +
+ + +
+ 25% + Lines + 6/24 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +622x +2x +2x +  +2x +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x
const knex = require('knex');
+const { loadSecrets } = require("./aws/ssm-helper");
+const config = require('config');
+ 
+require('dotenv').config({ path: '.env.local' });
+ 
+let databaseUsername, databaseHost, databaseName, databasePassword, databasePort;
+let knexInstance = null;
+ 
+async function getDbSettings() {
+  const isLocal = process.env.ENVIRONMENT === "local";
+  console.log("Environment: ", isLocal ? "local" : process.env.NODE_ENV ?? "development");
+  if (isLocal) {
+    databaseUsername = process.env.DB_USER;
+    databaseHost = process.env.DB_HOST || "localhost";
+    databaseName = process.env.DB_NAME;
+    databasePassword = process.env.DB_PASS;
+    databasePort = process.env.DB_PORT || 5432;
+  }
+  else {
+    // For remote RSD access, load secrets from AWS SSM
+    const awsConfig = config.aws;
+    const credentials = await loadSecrets(
+      awsConfig.param_store_region, 
+      ['/catbytes_webplatform/db_username', '/catbytes_webplatform/db_password'], 
+      true);
+ 
+    databaseUsername = credentials['db_username'];
+    databaseHost = awsConfig.db_endpoint;
+    databaseName = awsConfig.db_name;
+    databasePassword = credentials['db_password'];
+    databasePort = awsConfig.databasePort;
+  }
+ 
+  return {
+    databaseUsername,
+    databaseHost,
+    databaseName,
+    databasePassword,
+    databasePort,
+    ssl: isLocal ? false : { rejectUnauthorized: false }
+  };
+}
+ 
+async function initDb() {
+  const settings = await getDbSettings();
+  
+  knexInstance = knex({
+    client: 'pg',
+    connection: {
+      host: settings.databaseHost,
+      user: settings.databaseUsername,
+      password: settings.databasePassword,
+      database: settings.databaseName,
+      port: settings.databasePort,
+      connectionTimeoutMillis: 5000,
+      ssl: settings.ssl
+    },   
+  });
+}
+ 
+module.exports = { getDbSettings, initDb, getKnex: () => knexInstance };
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/index.html b/coverage/lcov-report/webplatform-backend/index.html new file mode 100644 index 0000000..8ebf0f5 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for webplatform-backend + + + + + + + + + +
+
+

All files webplatform-backend

+
+ +
+ 38.94% + Statements + 37/95 +
+ + +
+ 10.71% + Branches + 3/28 +
+ + +
+ 20% + Functions + 2/10 +
+ + +
+ 38.29% + Lines + 36/94 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
app.js +
+
90%18/2050%3/6100%1/190%18/20
db.js +
+
28%7/250%0/1233.33%1/325%6/24
oauth.js +
+
25%4/160%0/20%0/125%4/16
utils.js +
+
23.52%8/340%0/80%0/523.52%8/34
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/middleware/authentication.js.html b/coverage/lcov-report/webplatform-backend/middleware/authentication.js.html new file mode 100644 index 0000000..f3dcae9 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/middleware/authentication.js.html @@ -0,0 +1,175 @@ + + + + + + Code coverage report for webplatform-backend/middleware/authentication.js + + + + + + + + + +
+
+

All files / webplatform-backend/middleware authentication.js

+
+ +
+ 57.89% + Statements + 11/19 +
+ + +
+ 25% + Branches + 1/4 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 57.89% + Lines + 11/19 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +311x +1x +1x +  +  +1x +1x +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +1x +  +  +  +  +1x
const userService = require('../services/user_service');
+const { respondWithError } = require('../routes/helpers');
+const logger = require('../logger')(__filename);
+ 
+function authenticate() {
+  return async (req, res, next) => {
+    try {
+      const userFirebaseId = req.cookies?.userUID;
+      Eif (!userFirebaseId) {
+        logger.warn(`No user UID found in request cookies at ${req.path}`);
+        return next();
+      }
+ 
+      const user = await userService.getUserByFirebaseId(userFirebaseId);
+      if (!user) {
+        logger.warn('Found user UID in request cookies, but no corresponding user found in database');
+        return respondWithError(res, 401, "User with provided UID not found");
+      }
+ 
+      req.userId = user.id;
+      req.userEmail = user.email;
+ 
+      next();
+    } catch (err) {
+      logger.error(err, 'Unexpected error while autenthicating request');
+      return respondWithError(res);
+    }
+  };
+}
+ 
+module.exports = { authenticate };
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/middleware/authorization.js.html b/coverage/lcov-report/webplatform-backend/middleware/authorization.js.html new file mode 100644 index 0000000..5e2c654 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/middleware/authorization.js.html @@ -0,0 +1,283 @@ + + + + + + Code coverage report for webplatform-backend/middleware/authorization.js + + + + + + + + + +
+
+

All files / webplatform-backend/middleware authorization.js

+
+ +
+ 22.22% + Statements + 8/36 +
+ + +
+ 0% + Branches + 0/16 +
+ + +
+ 50% + Functions + 2/4 +
+ + +
+ 22.22% + Lines + 8/36 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +671x +1x +1x +1x +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x
const utils = require('../utils');
+const repo = require('../repositories/authorization_repository');
+const { respondWithError } = require('../routes/helpers');
+const logger = require('../logger')(__filename);
+ 
+function verifyRole(roleName) {
+  return async (req, res, next) => {
+    try {
+      const userId = req.userId;
+      if (!userId) {
+        return respondWithError(res, 401, "User not authenticated");
+      }
+ 
+      const roleId = utils.getRole(roleName);
+      const userRole = await repo.verifyRole(userId, roleId);
+      if (userRole.length === 0){
+        return respondWithError(res, 403, "You're not allowed to access this resource");
+      }
+      next();
+    } catch (err) {
+      logger.error(err, 'Error verifying role');
+      return respondWithError(res);
+    }
+  };
+}
+ 
+const OWNED_ENTITIES = {
+  USER: 'users',
+  MENTOR: 'mentors'
+};
+ 
+function verifyOwnership(entityTable) {
+  return async (req, res, next) => {
+    logger.debug(`Middleware invoked for: ${req.url}`);
+    try {
+      const resourceId = req.params.id;
+      const userId = req.userId;
+      if (!userId) {
+        return respondWithError(res, 401, "User not authenticated");
+      }
+    
+      if (!resourceId || !userId) {
+        return respondWithError(res, 400, `Invalid request: missing ${entityTable} ID or user information`);
+      }
+            
+      let resource;
+      if (entityTable === OWNED_ENTITIES.USER) {
+        resource = userId.toString() === resourceId.toString();
+      } else {
+        const result = await repo.verifyOwnership(entityTable, resourceId, userId);
+        resource = result.rows && result.length !== 0;
+      }
+    
+      if (!resource) {
+        return respondWithError(res, 403, "You're not allowed to edit this resource");
+      }
+    
+      next();
+    } catch (err) {
+      logger.error(err, 'Error verifying ownership');
+      return respondWithError(res);
+    }
+  };
+}
+ 
+ 
+module.exports = { verifyRole, verifyOwnership, OWNED_ENTITIES };
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/middleware/index.html b/coverage/lcov-report/webplatform-backend/middleware/index.html new file mode 100644 index 0000000..e039ef4 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/middleware/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for webplatform-backend/middleware + + + + + + + + + +
+
+

All files webplatform-backend/middleware

+
+ +
+ 34.54% + Statements + 19/55 +
+ + +
+ 5% + Branches + 1/20 +
+ + +
+ 66.66% + Functions + 4/6 +
+ + +
+ 34.54% + Lines + 19/55 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
authentication.js +
+
57.89%11/1925%1/4100%2/257.89%11/19
authorization.js +
+
22.22%8/360%0/1650%2/422.22%8/36
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/oauth.js.html b/coverage/lcov-report/webplatform-backend/oauth.js.html new file mode 100644 index 0000000..58d9958 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/oauth.js.html @@ -0,0 +1,172 @@ + + + + + + Code coverage report for webplatform-backend/oauth.js + + + + + + + + + +
+
+

All files / webplatform-backend oauth.js

+
+ +
+ 25% + Statements + 4/16 +
+ + +
+ 0% + Branches + 0/2 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 25% + Lines + 4/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +301x +1x +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x + 
const { loadSecrets } = require("./aws/ssm-helper");
+const config = require('config');
+ 
+const discordAuth = {
+  discordClientId: null,
+  discordClientSecret: null,
+  discordRedirectUrl: null,
+};
+ 
+async function initOAuth() {
+  let discordClientId, discordClientSecret, discordRedirectUrl = null;
+ 
+  if (process.env.ENVIRONMENT === "local") {
+    discordClientId = process.env.DISCORD_CLIENT_ID;
+    discordClientSecret = process.env.DISCORD_CLIENT_SECRET;
+    discordRedirectUrl = process.env.DISCORD_REDIRECT_URI;
+  } else {
+    const params = await loadSecrets(config.aws.param_store_region, ['/catbytes_webplatform/oauth_creds'], true, true);
+    discordClientId = params['oauth_creds']['discord']['clientID'];
+    discordClientSecret = params['oauth_creds']['discord']['clientSecret'];
+    discordRedirectUrl = `${config.platform_url}auth/discord/callback`;
+  }
+ 
+  discordAuth.discordClientId = discordClientId;
+  discordAuth.discordClientSecret = discordClientSecret;
+  discordAuth.discordRedirectUrl = discordRedirectUrl;
+}
+ 
+module.exports = { discordAuth, initOAuth };
+  
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/repositories/applications_repository.js.html b/coverage/lcov-report/webplatform-backend/repositories/applications_repository.js.html new file mode 100644 index 0000000..33c36a5 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/repositories/applications_repository.js.html @@ -0,0 +1,169 @@ + + + + + + Code coverage report for webplatform-backend/repositories/applications_repository.js + + + + + + + + + +
+
+

All files / webplatform-backend/repositories applications_repository.js

+
+ +
+ 37.5% + Statements + 3/8 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/4 +
+ + +
+ 37.5% + Lines + 3/8 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +291x +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x
const { getKnex } = require("../db");
+ 
+const knex = getKnex();
+ 
+async function getAllApplications() {
+  return await knex("applications").select("*"); 
+}
+ 
+async function createNewApplication(name, about, email, videoLink, discordNickname) {
+  return await knex('applications')
+    .insert({ name: name, about: about, email: email, video_link: videoLink, discord_nickname: discordNickname })
+    .returning('*');
+}
+ 
+async function updateApplicationById(id, status, comment, modifiedBy, modifiedAt) {
+  const [application] = await knex('applications')
+    .where({ id })
+    .update({ status: status, comment: comment, modified_by: modifiedBy, modified_at: modifiedAt })
+    .returning('*');
+  return application;
+}
+ 
+async function getApplicationByFields(fields) {
+  return await knex('applications')
+    .where(fields)
+    .first();
+}
+ 
+module.exports = { getAllApplications, createNewApplication, updateApplicationById, getApplicationByFields };
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/repositories/authorization_repository.js.html b/coverage/lcov-report/webplatform-backend/repositories/authorization_repository.js.html new file mode 100644 index 0000000..e705998 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/repositories/authorization_repository.js.html @@ -0,0 +1,121 @@ + + + + + + Code coverage report for webplatform-backend/repositories/authorization_repository.js + + + + + + + + + +
+
+

All files / webplatform-backend/repositories authorization_repository.js

+
+ +
+ 33.33% + Statements + 2/6 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/2 +
+ + +
+ 33.33% + Lines + 2/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +131x +  +  +  +  +  +  +  +  +  +  +  +1x
const { getKnex } = require("../db");
+ 
+async function verifyRole(userId, roleId) {
+  const knex = getKnex();
+  return await knex("user_roles").where("user_id", userId).andWhere("role_id", roleId).select("*");
+}
+ 
+async function verifyOwnership(entityTable, resourceId, userId) {
+  const knex = getKnex();
+  return await knex(entityTable).where("id", resourceId).andWhere("created_by", userId).select("*");
+}
+ 
+module.exports = { verifyRole, verifyOwnership };
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/repositories/index.html b/coverage/lcov-report/webplatform-backend/repositories/index.html new file mode 100644 index 0000000..3843c97 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/repositories/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for webplatform-backend/repositories + + + + + + + + + +
+
+

All files webplatform-backend/repositories

+
+ +
+ 19.14% + Statements + 9/47 +
+ + +
+ 0% + Branches + 0/6 +
+ + +
+ 0% + Functions + 0/15 +
+ + +
+ 19.14% + Lines + 9/47 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
applications_repository.js +
+
37.5%3/8100%0/00%0/437.5%3/8
authorization_repository.js +
+
33.33%2/6100%0/00%0/233.33%2/6
roles_repository.js +
+
33.33%2/6100%0/00%0/233.33%2/6
user_repository.js +
+
7.4%2/270%0/60%0/77.4%2/27
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/repositories/roles_repository.js.html b/coverage/lcov-report/webplatform-backend/repositories/roles_repository.js.html new file mode 100644 index 0000000..922baca --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/repositories/roles_repository.js.html @@ -0,0 +1,121 @@ + + + + + + Code coverage report for webplatform-backend/repositories/roles_repository.js + + + + + + + + + +
+
+

All files / webplatform-backend/repositories roles_repository.js

+
+ +
+ 33.33% + Statements + 2/6 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/2 +
+ + +
+ 33.33% + Lines + 2/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +132x +  +  +  +  +  +  +  +  +  +  +  +2x
const { getKnex } = require("../db");
+ 
+async function getAllRoles() {
+  const knex = getKnex();
+  return await knex('roles').select('*');
+}
+ 
+async function assignRoleToUser(userId, roleId) {
+  const knex = getKnex();
+  await knex("user_roles").insert({ role_id: roleId, user_id: userId });
+}
+ 
+module.exports = { getAllRoles, assignRoleToUser };
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/repositories/user_repository.js.html b/coverage/lcov-report/webplatform-backend/repositories/user_repository.js.html new file mode 100644 index 0000000..4079b2d --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/repositories/user_repository.js.html @@ -0,0 +1,268 @@ + + + + + + Code coverage report for webplatform-backend/repositories/user_repository.js + + + + + + + + + +
+
+

All files / webplatform-backend/repositories user_repository.js

+
+ +
+ 7.4% + Statements + 2/27 +
+ + +
+ 0% + Branches + 0/6 +
+ + +
+ 0% + Functions + 0/7 +
+ + +
+ 7.4% + Lines + 2/27 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +622x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  + 
const { getKnex } = require("../db");
+ 
+async function getAllUsers() {
+  const knex = getKnex();
+  return await knex("users").select("id", "name", "languages");
+}
+ 
+async function createNewUser(values) {
+  const knex = getKnex();
+  const [user] = await knex("users")
+    .insert(values)
+    .returning("*");
+  delete user["firebase_id"];
+  return user;
+}
+ 
+async function getUserInfoById(id) {
+  const knex = getKnex();
+  const user = await knex("users").where("id", id).first();
+  if (user !== undefined){
+    delete user["firebase_id"];
+  }
+  return user;
+}
+ 
+async function getUserByFields(fields) {
+  const knex = getKnex();
+  const user = await knex("users").where(fields).first();
+  if (user !== undefined){
+    delete user["firebase_id"];
+  }
+  return user;
+}
+ 
+async function getUserRolesById(id) {
+  const knex = getKnex();
+  return await knex("roles")
+    .join("user_roles", "roles.id", "user_roles.role_id")
+    .where("user_roles.user_id", id)
+    .select("roles.role_name", "roles.id");
+}
+ 
+async function updateUserById(id, updates) {
+  const knex = getKnex();
+  const [user] = await knex("users")
+    .where("id", id)
+    .update(updates)
+    .returning("*");
+  if (user !== undefined){
+    delete user["firebase_id"];
+  }
+  return user;
+}
+ 
+async function deleteUserById(id) {
+  const knex = getKnex();
+  return await knex("users").where("id", id).del();
+}
+ 
+module.exports = { getAllUsers, createNewUser, getUserInfoById, getUserRolesById, updateUserById, 
+  deleteUserById, getUserByFields };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/routes/index.html b/coverage/lcov-report/webplatform-backend/routes/index.html new file mode 100644 index 0000000..abb0997 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/routes/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for webplatform-backend/routes + + + + + + + + + +
+
+

All files webplatform-backend/routes

+
+ +
+ 20.77% + Statements + 16/77 +
+ + +
+ 0% + Branches + 0/26 +
+ + +
+ 0% + Functions + 0/6 +
+ + +
+ 20.77% + Lines + 16/77 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
users.js +
+
20.77%16/770%0/260%0/620.77%16/77
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/routes/users.js.html b/coverage/lcov-report/webplatform-backend/routes/users.js.html new file mode 100644 index 0000000..231bd4e --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/routes/users.js.html @@ -0,0 +1,484 @@ + + + + + + Code coverage report for webplatform-backend/routes/users.js + + + + + + + + + +
+
+

All files / webplatform-backend/routes users.js

+
+ +
+ 20.77% + Statements + 16/77 +
+ + +
+ 0% + Branches + 0/26 +
+ + +
+ 0% + Functions + 0/6 +
+ + +
+ 20.77% + Lines + 16/77 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +1341x +  +1x +1x +1x +1x +1x +  +1x +  +1x +  +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x
const express = require("express");
+ 
+const router = express.Router();
+const userService = require("../services/user_service");
+const authService = require("../services/auth_service");
+const { ROLE_NAMES } = require("../utils");
+const {verifyOwnership, verifyRole, OWNED_ENTITIES} = require("../middleware/authorization");
+const { isValidIntegerId, respondWithError, isUniqueConstraintViolation, 
+  isNotNullConstraintViolation, parseColumnNameFromConstraint } = require("./helpers");
+ 
+const logger = require('../logger')(__filename);
+ 
+router.use(express.json());
+ 
+// POST /users/login
+router.post("/users/login", async (req, res) => {
+  console.log('Route handler reached!');
+  const firebaseToken = req.get('X-Firebase-Token') || null;
+  const discordCode = req.get('X-Discord-Code') || null;
+ 
+  try {
+    let user;
+ 
+    if (firebaseToken) {
+      user = await authService.handleFirebaseAuth(firebaseToken);
+    } else if (discordCode) {
+      user = await authService.handleDiscordAuth(discordCode);
+    }
+    else return respondWithError(res, 401, 'No token provided or invalid token');
+ 
+    res.cookie('userUID', user.firebaseId, { httpOnly: true, secure: true, sameSite: 'none' });
+    res.status(200).json({ user: user });
+ 
+  } catch (error) {
+    if (error.status) {
+      return respondWithError(res, error.status, error.message);
+    }
+    return respondWithError(res, 500, "Authentication failed");
+  }
+});
+ 
+// Get all users
+router.get("/users", verifyRole(ROLE_NAMES.member), async (req, res) => {
+  try {
+    const users = await userService.getAllUsers();
+    res.json({ users });
+  } catch (err) {
+    logger.error(err);
+    respondWithError(res);
+  }
+});
+ 
+// Create a new user
+router.post("/users", async (req, res) => {
+  const { name, email, about, languages } = req.body;
+  // console.log(req.body); // Log the entire request body
+  try {
+    // todo: firebase will only know user's email, we will need to get user's application by email
+    // and populate user entity with that data here 
+    const user = await userService.createNewMemberUser(name, email, about, languages);       
+    res.status(201).json({ id: user.id });
+  } catch (err) {
+    logger.error(err);
+    const violatedValue = parseColumnNameFromConstraint(err.constraint, 'users');
+    if (isUniqueConstraintViolation(err.code)) {
+      return respondWithError(
+        res,
+        409,
+        `User with this ${violatedValue} is already registered`
+      );
+    } else if (isNotNullConstraintViolation(err.code)) {
+      return respondWithError(
+        res, 400, `Field ${violatedValue} is required`);
+    }
+    respondWithError(res);
+  }
+});
+ 
+// Get user by ID
+router.get("/users/:id", verifyRole(ROLE_NAMES.member), async (req, res) => {
+  const { id } = req.params;
+  if (!isValidIntegerId(id)) {
+    return respondWithError(res, 400, "Invalid user id supplied");
+  }
+  try {
+    const userInfo = await userService.getUserById(id);
+    if (!userInfo) {
+      return respondWithError(res, 404, "User not found");
+    }
+    res.json(userInfo);
+  } catch (err) {
+    logger.error(err);
+    respondWithError(res);
+  }
+});
+ 
+// Update user by ID
+router.put("/users/:id", verifyOwnership(OWNED_ENTITIES.USER), async (req, res) => {
+  const { id } = req.params;
+  const { name, about, languages } = req.body;
+  if (!isValidIntegerId(id)) {
+    return respondWithError(res, 400, "Invalid user id supplied");
+  }
+  try {
+    const updatedUser = await userService.updateUserById(id, { name: name, about: about, languages: languages });
+    if (!updatedUser) {
+      return respondWithError(res, 404, "User not found");
+    }
+    res.status(200).json(updatedUser);
+  } catch (err) {
+    logger.error(err);
+    respondWithError(res);
+  }
+});
+ 
+// Delete user by ID
+router.delete("/users/:id", verifyOwnership(OWNED_ENTITIES.USER), async (req, res) => {
+  const { id } = req.params;
+  if (!isValidIntegerId(id)) {
+    return respondWithError(res, 400, "Invalid user id supplied");
+  }
+  try {
+    const result = await userService.deleteUserById(id);
+    if (result === 0) {
+      return respondWithError(res, 404, "User not found.");
+    }
+    res.status(200).json({ user_id: id });
+  } catch (err) {
+    logger.error(err);
+    respondWithError(res);
+  }
+});
+ 
+module.exports = router;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/services/applications_service.js.html b/coverage/lcov-report/webplatform-backend/services/applications_service.js.html new file mode 100644 index 0000000..391a6a0 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/services/applications_service.js.html @@ -0,0 +1,151 @@ + + + + + + Code coverage report for webplatform-backend/services/applications_service.js + + + + + + + + + +
+
+

All files / webplatform-backend/services applications_service.js

+
+ +
+ 28.57% + Statements + 2/7 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/5 +
+ + +
+ 28.57% + Lines + 2/7 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +231x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x
const repo = require('../repositories/applications_repository');
+ 
+async function getAllApplications() {
+  return await repo.getAllApplications();
+}
+ 
+async function createNewApplication(name, about, email, videoLink, discordNickname) {
+  return await repo.createNewApplication(name, about, email, videoLink, discordNickname);
+}
+ 
+async function updateApplicationStatus(id, status, comment, modifiedBy, modifiedAt) {
+  return await repo.updateApplicationById(id, status, comment, modifiedBy, modifiedAt);
+}
+ 
+async function getApplicationByEmail(email) {
+  return await repo.getApplicationByFields({email: email});
+}
+ 
+async function getApplicationById(id) {
+  return await repo.getApplicationByFields({id: id});
+}
+ 
+module.exports = { getAllApplications, createNewApplication, updateApplicationStatus, getApplicationByEmail, getApplicationById };
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/services/auth_service.js.html b/coverage/lcov-report/webplatform-backend/services/auth_service.js.html new file mode 100644 index 0000000..a6c4a6b --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/services/auth_service.js.html @@ -0,0 +1,340 @@ + + + + + + Code coverage report for webplatform-backend/services/auth_service.js + + + + + + + + + +
+
+

All files / webplatform-backend/services auth_service.js

+
+ +
+ 23.52% + Statements + 8/34 +
+ + +
+ 0% + Branches + 0/10 +
+ + +
+ 0% + Functions + 0/2 +
+ + +
+ 23.52% + Lines + 8/34 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +861x +1x +1x +1x +1x +  +1x +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x
const admin = require('firebase-admin');
+const axios = require('axios');
+const querystring = require('querystring');
+const userService = require('../services/user_service');
+const applService = require("../services/applications_service");
+ 
+const {discordAuth} = require ("../oauth.js"); 
+ 
+const logger = require('../logger')(__filename);
+ 
+async function handleFirebaseAuth(firebaseToken) {
+  try {
+    const decodedToken = await admin.auth().verifyIdToken(firebaseToken);
+    const email = decodedToken.email;
+    const firebaseId = decodedToken.uid;
+ 
+    if (!decodedToken.email_verified) {
+      throw { status: 403, message: 'Email not verified' };
+    }
+ 
+    const application = await applService.getApplicationByEmail(email);
+    if (!application || application.status !== 'approved') {
+      throw { status: 403, message: 'Application is not approved or does not exist' };
+    }
+ 
+    let user = await userService.getUserByEmail(email);
+    if (!user) {
+      user = await userService.createNewMemberUser(
+        application.name,
+        email,
+        application.about,
+        application.languages,
+        application.discord_nickname
+      );
+    }
+ 
+    await userService.updateUserById(user.id, { firebase_id: firebaseId });
+ 
+    return user;
+  } catch (error) {
+    logger.error({ error: error.message }, "Firebase Token Verification Failed");
+    throw { status: 401, message: 'Unauthorized' };
+  }
+}
+ 
+;
+ 
+async function handleDiscordAuth(code){
+  try {
+    // exchange code for access token
+    const tokenResponse = await axios.post('https://discord.com/api/oauth2/token',
+      querystring.stringify({
+        client_id: discordAuth.discordClientId,
+        client_secret: discordAuth.discordClientSecret,
+        grant_type: 'authorization_code',
+        code: code,
+        redirect_uri: discordAuth.discordRedirectUrl,
+        scope: 'identify email'
+      }), {
+        headers: {
+          'Content-Type': 'application/x-www-form-urlencoded'
+        }
+      });
+    // get user data from discord
+    const userResponse = await axios.get('https://discord.com/api/users/@me', {
+      headers: {
+        Authorization: `Bearer ${tokenResponse.data.access_token}`
+      }
+    });
+ 
+    const discordUser = userResponse.data;
+    const user = await userService.getUserByEmail(discordUser.email);
+ 
+    if (!user) {
+      throw { status: 404, message: 'User not found' };
+    }
+ 
+    return { user };
+  } catch (error) {
+    logger.error({ error: error.message }, "Discord Authentication Failed", );
+    throw { status: 401, message: 'Unauthorized' };
+  }
+}
+ 
+;
+module.exports = { handleFirebaseAuth, handleDiscordAuth};
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/services/index.html b/coverage/lcov-report/webplatform-backend/services/index.html new file mode 100644 index 0000000..9ac9796 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/services/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for webplatform-backend/services + + + + + + + + + +
+
+

All files webplatform-backend/services

+
+ +
+ 24.61% + Statements + 16/65 +
+ + +
+ 0% + Branches + 0/14 +
+ + +
+ 0% + Functions + 0/16 +
+ + +
+ 24.61% + Lines + 16/65 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
applications_service.js +
+
28.57%2/7100%0/00%0/528.57%2/7
auth_service.js +
+
23.52%8/340%0/100%0/223.52%8/34
roles_service.js +
+
37.5%3/8100%0/00%0/237.5%3/8
user_service.js +
+
18.75%3/160%0/40%0/718.75%3/16
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/services/roles_service.js.html b/coverage/lcov-report/webplatform-backend/services/roles_service.js.html new file mode 100644 index 0000000..8987aee --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/services/roles_service.js.html @@ -0,0 +1,133 @@ + + + + + + Code coverage report for webplatform-backend/services/roles_service.js + + + + + + + + + +
+
+

All files / webplatform-backend/services roles_service.js

+
+ +
+ 37.5% + Statements + 3/8 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/2 +
+ + +
+ 37.5% + Lines + 3/8 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +172x +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x
const repo = require('../repositories/roles_repository');
+const utils = require('../utils');
+ 
+async function assignRoleToUser(userId, roleName) {
+  try {
+    const roleId = utils.getRole(roleName);
+    await repo.assignRoleToUser(userId, roleId);
+  } catch (err) {
+    throw new Error(`Failed to assign role to user ${userId}:  ${err.message}`);
+  }
+}
+ 
+async function getAllRoles() {
+  return await repo.getAllRoles();
+}
+ 
+module.exports = { getAllRoles, assignRoleToUser };
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/services/user_service.js.html b/coverage/lcov-report/webplatform-backend/services/user_service.js.html new file mode 100644 index 0000000..13e4142 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/services/user_service.js.html @@ -0,0 +1,208 @@ + + + + + + Code coverage report for webplatform-backend/services/user_service.js + + + + + + + + + +
+
+

All files / webplatform-backend/services user_service.js

+
+ +
+ 18.75% + Statements + 3/16 +
+ + +
+ 0% + Branches + 0/4 +
+ + +
+ 0% + Functions + 0/7 +
+ + +
+ 18.75% + Lines + 3/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +422x +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x + 
const repo = require('../repositories/user_repository');
+const rolesService = require('../services/roles_service');
+ 
+async function getAllUsers() {
+ 
+  return await repo.getAllUsers();
+}
+ 
+async function createNewMemberUser(name, email, about, languages, discordNickname) {
+  var user = await repo.createNewUser({ name: name, email: email, about: about, 
+    languages: languages, discord_nickname: discordNickname });
+  await rolesService.assignRoleToUser(user.id, 'member');
+  return user;
+}
+ 
+async function getUserById(id) {
+  const userInfo = await repo.getUserInfoById(id);
+  if (userInfo) {
+    const roles = await repo.getUserRolesById(id);
+    userInfo.roles = roles.length ? roles : [];    
+  }
+  return userInfo;
+}
+ 
+async function updateUserById(id, updates) {
+  return await repo.updateUserById(id, updates);   
+}
+ 
+async function deleteUserById(id) {
+  return await repo.deleteUserById(id);
+}  
+ 
+async function getUserByFirebaseId(firebaseId) {
+  return await repo.getUserByFields({firebase_id: firebaseId});
+}
+ 
+async function getUserByEmail(email) {
+  return await repo.getUserByFields({email: email});
+}
+ 
+module.exports = { getAllUsers, createNewMemberUser, getUserById, updateUserById, deleteUserById, 
+  getUserByEmail, getUserByFirebaseId };
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/utils.js.html b/coverage/lcov-report/webplatform-backend/utils.js.html new file mode 100644 index 0000000..7343700 --- /dev/null +++ b/coverage/lcov-report/webplatform-backend/utils.js.html @@ -0,0 +1,292 @@ + + + + + + Code coverage report for webplatform-backend/utils.js + + + + + + + + + +
+
+

All files / webplatform-backend utils.js

+
+ +
+ 23.52% + Statements + 8/34 +
+ + +
+ 0% + Branches + 0/8 +
+ + +
+ 0% + Functions + 0/5 +
+ + +
+ 23.52% + Lines + 8/34 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +702x +2x +2x +2x +  +2x +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +2x + 
const repo = require('./repositories/roles_repository');
+const { loadSecrets } = require("./aws/ssm-helper");
+const config = require('config');
+const logger = require('./logger')(__filename);
+ 
+let rolesCache = null;
+ 
+const ROLE_NAMES = {
+  mentor: 'mentor',
+  member: 'member'
+};
+ 
+async function getFirebaseSdkServiceAccount() {
+  let serviceAccount = null;
+  try {
+    if (process.env.ENVIRONMENT === "local") {
+      serviceAccount = require("./serviceAccountKey.json");
+    }
+    else {
+      const awsConfig = config.aws;
+      const credentials = await loadSecrets(awsConfig.param_store_region, ['/catbytes_webplatform/fb_serviceAccountKey'], true);
+      const jsonFile = credentials['fb_serviceAccountKey'];
+      serviceAccount = JSON.parse(jsonFile);
+    }
+  }
+  catch (err) {
+    logger.error(err, "Error getting service account");
+    throw new Error("Failed to retrieve service account");
+  }
+  return serviceAccount;
+}
+ 
+async function loadRolesIntoMemory() {
+  try {
+    if (!rolesCache) {
+      const roles = await repo.getAllRoles();
+      rolesCache = roles.reduce((acc, role) => {
+        acc[role.role_name] = role.id;
+        if (!isRoleExists(role.role_name)) {
+          logger.warn(`Role ${role.role_name} is in database, but is not in the ROLE_NAMES enum.`);
+        }
+        return acc;
+      }, {});
+      logger.debug(rolesCache, 'Roles loaded into memory');
+    }
+  } catch (err) {
+    logger.error(err, "Error loading roles");
+    throw new Error("Failed to initialize roles");
+  }
+}
+ 
+function getRole(roleName) {
+  if (!rolesCache) {
+    throw new Error("Roles are not loaded");
+  }
+  return rolesCache[roleName];
+}
+ 
+function isRoleExists(role) {
+  return Object.values(ROLE_NAMES).includes(role);
+}
+ 
+const APPL_STATUSES = {
+  approved: "approved",
+  rejected: "rejected",
+  pending: "pending",
+};
+ 
+module.exports = { APPL_STATUSES, ROLE_NAMES, isRoleExists, loadRolesIntoMemory, getRole, getFirebaseSdkServiceAccount };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/lcov.info b/coverage/lcov.info new file mode 100644 index 0000000..94834dc --- /dev/null +++ b/coverage/lcov.info @@ -0,0 +1,711 @@ +TN: +SF:app.js +FN:21,(anonymous_0) +FNF:1 +FNH:1 +FNDA:1,(anonymous_0) +DA:1,1 +DA:2,1 +DA:3,1 +DA:4,1 +DA:5,1 +DA:6,1 +DA:7,1 +DA:9,1 +DA:11,1 +DA:14,1 +DA:15,0 +DA:18,1 +DA:19,1 +DA:20,1 +DA:22,1 +DA:23,1 +DA:25,0 +DA:32,1 +DA:33,1 +DA:35,1 +LF:20 +LH:18 +BRDA:14,0,0,0 +BRDA:14,0,1,1 +BRDA:22,1,0,1 +BRDA:22,1,1,0 +BRDA:22,2,0,1 +BRDA:22,2,1,0 +BRF:6 +BRH:3 +end_of_record +TN: +SF:db.js +FN:10,getDbSettings +FN:45,initDb +FN:62,(anonymous_2) +FNF:3 +FNH:1 +FNDA:0,getDbSettings +FNDA:0,initDb +FNDA:1,(anonymous_2) +DA:1,2 +DA:2,2 +DA:3,2 +DA:5,2 +DA:8,2 +DA:11,0 +DA:12,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:22,0 +DA:23,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:35,0 +DA:46,0 +DA:48,0 +DA:62,2 +LF:24 +LH:6 +BRDA:12,0,0,0 +BRDA:12,0,1,0 +BRDA:12,1,0,0 +BRDA:12,1,1,0 +BRDA:13,2,0,0 +BRDA:13,2,1,0 +BRDA:15,3,0,0 +BRDA:15,3,1,0 +BRDA:18,4,0,0 +BRDA:18,4,1,0 +BRDA:41,5,0,0 +BRDA:41,5,1,0 +BRF:12 +BRH:0 +end_of_record +TN: +SF:oauth.js +FN:10,initOAuth +FNF:1 +FNH:0 +FNDA:0,initOAuth +DA:1,1 +DA:2,1 +DA:4,1 +DA:11,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:29,1 +LF:16 +LH:4 +BRDA:13,0,0,0 +BRDA:13,0,1,0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:utils.js +FN:13,getFirebaseSdkServiceAccount +FN:33,loadRolesIntoMemory +FN:37,(anonymous_2) +FN:52,getRole +FN:59,isRoleExists +FNF:5 +FNH:0 +FNDA:0,getFirebaseSdkServiceAccount +FNDA:0,loadRolesIntoMemory +FNDA:0,(anonymous_2) +FNDA:0,getRole +FNDA:0,isRoleExists +DA:1,2 +DA:2,2 +DA:3,2 +DA:4,2 +DA:6,2 +DA:8,2 +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:27,0 +DA:28,0 +DA:30,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:44,0 +DA:47,0 +DA:48,0 +DA:53,0 +DA:54,0 +DA:56,0 +DA:60,0 +DA:63,2 +DA:69,2 +LF:34 +LH:8 +BRDA:16,0,0,0 +BRDA:16,0,1,0 +BRDA:35,1,0,0 +BRDA:35,1,1,0 +BRDA:39,2,0,0 +BRDA:39,2,1,0 +BRDA:53,3,0,0 +BRDA:53,3,1,0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:aws/ssm-helper.js +FN:4,loadSecrets +FN:18,(anonymous_1) +FN:32,parseJsonValue +FNF:3 +FNH:0 +FNDA:0,loadSecrets +FNDA:0,(anonymous_1) +FNDA:0,parseJsonValue +DA:1,2 +DA:2,2 +DA:5,0 +DA:9,0 +DA:10,0 +DA:11,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:24,0 +DA:25,0 +DA:27,0 +DA:28,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:40,2 +LF:19 +LH:3 +BRDA:4,0,0,0 +BRDA:4,1,0,0 +BRDA:20,2,0,0 +BRDA:20,2,1,0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:middleware/authentication.js +FN:5,authenticate +FN:6,(anonymous_1) +FNF:2 +FNH:2 +FNDA:1,authenticate +FNDA:1,(anonymous_1) +DA:1,1 +DA:2,1 +DA:3,1 +DA:6,1 +DA:7,1 +DA:8,1 +DA:9,1 +DA:10,1 +DA:11,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:20,0 +DA:21,0 +DA:23,0 +DA:25,1 +DA:26,1 +DA:31,1 +LF:19 +LH:11 +BRDA:9,0,0,1 +BRDA:9,0,1,0 +BRDA:15,1,0,0 +BRDA:15,1,1,0 +BRF:4 +BRH:1 +end_of_record +TN: +SF:middleware/authorization.js +FN:6,verifyRole +FN:7,(anonymous_1) +FN:32,verifyOwnership +FN:33,(anonymous_3) +FNF:4 +FNH:2 +FNDA:2,verifyRole +FNDA:0,(anonymous_1) +FNDA:2,verifyOwnership +FNDA:0,(anonymous_3) +DA:1,1 +DA:2,1 +DA:3,1 +DA:4,1 +DA:7,2 +DA:8,0 +DA:9,0 +DA:10,0 +DA:11,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:21,0 +DA:22,0 +DA:27,1 +DA:33,2 +DA:34,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:42,0 +DA:43,0 +DA:47,0 +DA:48,0 +DA:50,0 +DA:51,0 +DA:54,0 +DA:55,0 +DA:58,0 +DA:60,0 +DA:61,0 +DA:67,1 +LF:36 +LH:8 +BRDA:10,0,0,0 +BRDA:10,0,1,0 +BRDA:16,1,0,0 +BRDA:16,1,1,0 +BRDA:38,2,0,0 +BRDA:38,2,1,0 +BRDA:42,3,0,0 +BRDA:42,3,1,0 +BRDA:42,4,0,0 +BRDA:42,4,1,0 +BRDA:47,5,0,0 +BRDA:47,5,1,0 +BRDA:51,6,0,0 +BRDA:51,6,1,0 +BRDA:54,7,0,0 +BRDA:54,7,1,0 +BRF:16 +BRH:0 +end_of_record +TN: +SF:repositories/applications_repository.js +FN:5,getAllApplications +FN:9,createNewApplication +FN:15,updateApplicationById +FN:23,getApplicationByFields +FNF:4 +FNH:0 +FNDA:0,getAllApplications +FNDA:0,createNewApplication +FNDA:0,updateApplicationById +FNDA:0,getApplicationByFields +DA:1,1 +DA:3,1 +DA:6,0 +DA:10,0 +DA:16,0 +DA:20,0 +DA:24,0 +DA:29,1 +LF:8 +LH:3 +BRF:0 +BRH:0 +end_of_record +TN: +SF:repositories/authorization_repository.js +FN:3,verifyRole +FN:8,verifyOwnership +FNF:2 +FNH:0 +FNDA:0,verifyRole +FNDA:0,verifyOwnership +DA:1,1 +DA:4,0 +DA:5,0 +DA:9,0 +DA:10,0 +DA:13,1 +LF:6 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:repositories/roles_repository.js +FN:3,getAllRoles +FN:8,assignRoleToUser +FNF:2 +FNH:0 +FNDA:0,getAllRoles +FNDA:0,assignRoleToUser +DA:1,2 +DA:4,0 +DA:5,0 +DA:9,0 +DA:10,0 +DA:13,2 +LF:6 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:repositories/user_repository.js +FN:3,getAllUsers +FN:8,createNewUser +FN:17,getUserInfoById +FN:26,getUserByFields +FN:35,getUserRolesById +FN:43,updateUserById +FN:55,deleteUserById +FNF:7 +FNH:0 +FNDA:0,getAllUsers +FNDA:0,createNewUser +FNDA:0,getUserInfoById +FNDA:0,getUserByFields +FNDA:0,getUserRolesById +FNDA:0,updateUserById +FNDA:0,deleteUserById +DA:1,2 +DA:4,0 +DA:5,0 +DA:9,0 +DA:10,0 +DA:13,0 +DA:14,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:23,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:36,0 +DA:37,0 +DA:44,0 +DA:45,0 +DA:49,0 +DA:50,0 +DA:52,0 +DA:56,0 +DA:57,0 +DA:60,2 +LF:27 +LH:2 +BRDA:20,0,0,0 +BRDA:20,0,1,0 +BRDA:29,1,0,0 +BRDA:29,1,1,0 +BRDA:49,2,0,0 +BRDA:49,2,1,0 +BRF:6 +BRH:0 +end_of_record +TN: +SF:routes/users.js +FN:16,(anonymous_0) +FN:43,(anonymous_1) +FN:54,(anonymous_2) +FN:80,(anonymous_3) +FN:98,(anonymous_4) +FN:117,(anonymous_5) +FNF:6 +FNH:0 +FNDA:0,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +FNDA:0,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) +DA:1,1 +DA:3,1 +DA:4,1 +DA:5,1 +DA:6,1 +DA:7,1 +DA:9,1 +DA:11,1 +DA:13,1 +DA:16,1 +DA:17,0 +DA:18,0 +DA:19,0 +DA:21,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:31,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:38,0 +DA:43,1 +DA:44,0 +DA:45,0 +DA:46,0 +DA:48,0 +DA:49,0 +DA:54,1 +DA:55,0 +DA:57,0 +DA:60,0 +DA:61,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:71,0 +DA:72,0 +DA:75,0 +DA:80,1 +DA:81,0 +DA:82,0 +DA:83,0 +DA:85,0 +DA:86,0 +DA:87,0 +DA:88,0 +DA:90,0 +DA:92,0 +DA:93,0 +DA:98,1 +DA:99,0 +DA:100,0 +DA:101,0 +DA:102,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:109,0 +DA:111,0 +DA:112,0 +DA:117,1 +DA:118,0 +DA:119,0 +DA:120,0 +DA:122,0 +DA:123,0 +DA:124,0 +DA:125,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:134,1 +LF:77 +LH:16 +BRDA:18,0,0,0 +BRDA:18,0,1,0 +BRDA:19,1,0,0 +BRDA:19,1,1,0 +BRDA:24,2,0,0 +BRDA:24,2,1,0 +BRDA:26,3,0,0 +BRDA:26,3,1,0 +BRDA:35,4,0,0 +BRDA:35,4,1,0 +BRDA:65,5,0,0 +BRDA:65,5,1,0 +BRDA:71,6,0,0 +BRDA:71,6,1,0 +BRDA:82,7,0,0 +BRDA:82,7,1,0 +BRDA:87,8,0,0 +BRDA:87,8,1,0 +BRDA:101,9,0,0 +BRDA:101,9,1,0 +BRDA:106,10,0,0 +BRDA:106,10,1,0 +BRDA:119,11,0,0 +BRDA:119,11,1,0 +BRDA:124,12,0,0 +BRDA:124,12,1,0 +BRF:26 +BRH:0 +end_of_record +TN: +SF:services/applications_service.js +FN:3,getAllApplications +FN:7,createNewApplication +FN:11,updateApplicationStatus +FN:15,getApplicationByEmail +FN:19,getApplicationById +FNF:5 +FNH:0 +FNDA:0,getAllApplications +FNDA:0,createNewApplication +FNDA:0,updateApplicationStatus +FNDA:0,getApplicationByEmail +FNDA:0,getApplicationById +DA:1,1 +DA:4,0 +DA:8,0 +DA:12,0 +DA:16,0 +DA:20,0 +DA:23,1 +LF:7 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:services/auth_service.js +FN:11,handleFirebaseAuth +FN:48,handleDiscordAuth +FNF:2 +FNH:0 +FNDA:0,handleFirebaseAuth +FNDA:0,handleDiscordAuth +DA:1,1 +DA:2,1 +DA:3,1 +DA:4,1 +DA:5,1 +DA:7,1 +DA:9,1 +DA:12,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:17,0 +DA:18,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:37,0 +DA:39,0 +DA:41,0 +DA:42,0 +DA:49,0 +DA:51,0 +DA:65,0 +DA:71,0 +DA:72,0 +DA:74,0 +DA:75,0 +DA:78,0 +DA:80,0 +DA:81,0 +DA:86,1 +LF:34 +LH:8 +BRDA:17,0,0,0 +BRDA:17,0,1,0 +BRDA:22,1,0,0 +BRDA:22,1,1,0 +BRDA:22,2,0,0 +BRDA:22,2,1,0 +BRDA:27,3,0,0 +BRDA:27,3,1,0 +BRDA:74,4,0,0 +BRDA:74,4,1,0 +BRF:10 +BRH:0 +end_of_record +TN: +SF:services/roles_service.js +FN:4,assignRoleToUser +FN:13,getAllRoles +FNF:2 +FNH:0 +FNDA:0,assignRoleToUser +FNDA:0,getAllRoles +DA:1,2 +DA:2,2 +DA:5,0 +DA:6,0 +DA:7,0 +DA:9,0 +DA:14,0 +DA:17,2 +LF:8 +LH:3 +BRF:0 +BRH:0 +end_of_record +TN: +SF:services/user_service.js +FN:4,getAllUsers +FN:9,createNewMemberUser +FN:16,getUserById +FN:25,updateUserById +FN:29,deleteUserById +FN:33,getUserByFirebaseId +FN:37,getUserByEmail +FNF:7 +FNH:0 +FNDA:0,getAllUsers +FNDA:0,createNewMemberUser +FNDA:0,getUserById +FNDA:0,updateUserById +FNDA:0,deleteUserById +FNDA:0,getUserByFirebaseId +FNDA:0,getUserByEmail +DA:1,2 +DA:2,2 +DA:6,0 +DA:10,0 +DA:12,0 +DA:13,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:22,0 +DA:26,0 +DA:30,0 +DA:34,0 +DA:38,0 +DA:41,2 +LF:16 +LH:3 +BRDA:18,0,0,0 +BRDA:18,0,1,0 +BRDA:20,1,0,0 +BRDA:20,1,1,0 +BRF:4 +BRH:0 +end_of_record diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..223c98e --- /dev/null +++ b/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + testEnvironment: 'node', + testMatch: ['**/tests/**/*.test.js'], // or '**/*.test.js' for more general + coverageDirectory: 'coverage', + verbose: true, + }; \ No newline at end of file diff --git a/logger.js b/logger.js index 6b3100b..b3ddacc 100644 --- a/logger.js +++ b/logger.js @@ -7,10 +7,15 @@ require('dotenv').config({ path: '.env.local' }); // Otherwise, use JSON and formatiing for deployed service - e.g. Grafana Loki const isGrafanaLogging = process.env.LOGGER_ENV !== "local"; +// When running tests, NODE_ENV is automatically set to 'test' by Jest. +const isTestEnvironment = process.env.NODE_ENV === 'test'; + +const defaultLoggingLevel = isGrafanaLogging ? 'info' : 'debug'; + const baseLogger = pino({ - level: isGrafanaLogging - ? 'info' - : 'debug', + level: isTestEnvironment + ? 'silent' // no logging in tests + : defaultLoggingLevel, transport: !isGrafanaLogging ? { diff --git a/middleware/authentication.js b/middleware/authentication.js index 3812371..752cc10 100644 --- a/middleware/authentication.js +++ b/middleware/authentication.js @@ -5,6 +5,7 @@ const logger = require('../logger')(__filename); function authenticate() { return async (req, res, next) => { try { + logger.warn(`Authenticating request: ${req.path}`); const userFirebaseId = req.cookies?.userUID; if (!userFirebaseId) { logger.warn(`No user UID found in request cookies at ${req.path}`); diff --git a/package.json b/package.json index 1e93c64..db6f51c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "This is CatBytes Web Platform backend repo.", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jest --silent=false", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" }, "keywords": [], "author": "", diff --git a/server.js b/server.js index 3abc44d..0581fe9 100644 --- a/server.js +++ b/server.js @@ -1,42 +1,10 @@ -const config = require('config'); -const express = require("express"); -const cookieParser = require('cookie-parser'); -const cors = require("cors"); const { initDb } = require('./db'); const { initMailer } = require('./services/mailer_service'); const { initDiscordBot } = require('./discordBot.js'); const { initOAuth } = require('./oauth.js'); const utils = require('./utils'); const admin = require("firebase-admin"); -const { authenticate } = require("./middleware/authentication"); -const pinoHttp = require('pino-http'); -const { baseLogger } = require('./logger'); - -require('dotenv').config({ path: '.env.local' }); - -const app = express(); - -// Middleware -if (process.env.LOGGING_HTTP_REQUESTS === 'true') { - app.use(pinoHttp({ logger: baseLogger })); -} - -app.use(express.json()); -app.use(cookieParser()); -app.use(cors({ - origin: (origin, callback) => { - if (!origin || config.cors.allowedOrigins.includes(origin)) { - callback(null, origin); - } else { - callback(new Error('Not allowed by CORS')); - } - }, - credentials: true, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization', 'token'], -})); -app.options('*', cors()); -app.use(authenticate()); +const app = require('./app'); (async () => { const logger = require('./logger')(__filename); @@ -53,7 +21,6 @@ app.use(authenticate()); // Routes const routes = require("./routes/routes"); - app.use(routes); app.listen(8080,'0.0.0.0', () => { diff --git a/tests/routes/users.test.js b/tests/routes/users.test.js new file mode 100644 index 0000000..9ce6e99 --- /dev/null +++ b/tests/routes/users.test.js @@ -0,0 +1,80 @@ +const request = require('supertest'); +const authService = require('../../services/auth_service'); + +jest.mock('../../services/auth_service'); + +const app = require('../../appForTests'); +const mockedUser = { firebaseId: '12345', email: 'test@example.com' }; + +function checkSuccessResponse(res, user) { + expect(res.statusCode).toBe(200); + expect(res.body.user).toEqual(user); + expect(res.headers['set-cookie']).toBeDefined(); +}; + +describe('POST /users/login', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Firebase token auth success', async () => { + authService.handleFirebaseAuth.mockResolvedValue(mockedUser); + const res = await request(app) + .post('/users/login') + .set('X-Firebase-Token', 'valid-firebase-token'); + + expect(authService.handleFirebaseAuth).toHaveBeenCalledWith('valid-firebase-token'); + checkSuccessResponse(res, mockedUser); + }); + + it('Discord code auth success', async () => { + authService.handleDiscordAuth.mockResolvedValue(mockedUser); + const res = await request(app) + .post('/users/login') + .set('X-Discord-Code', 'valid-discord-code'); + + expect(authService.handleDiscordAuth).toHaveBeenCalledWith('valid-discord-code'); + checkSuccessResponse(res, mockedUser); + }); + + it('No auth token provided', async () => { + const res = await request(app) + .post('/users/login'); + + expect(res.statusCode).toBe(401); + expect(res.body.error).toBe('No token provided or invalid token'); + expect(authService.handleFirebaseAuth).not.toHaveBeenCalled(); + expect(authService.handleDiscordAuth).not.toHaveBeenCalled(); + }); + + it('Unsupported token provided', async () => { + const res = await request(app) + .post('/users/login') + .set('X-Random-Token', 'invalid-token'); + + expect(res.statusCode).toBe(401); + expect(res.body.error).toBe('No token provided or invalid token'); + expect(authService.handleFirebaseAuth).not.toHaveBeenCalled(); + expect(authService.handleDiscordAuth).not.toHaveBeenCalled(); + }); + + it('Firebase auth failure', async () => { + authService.handleFirebaseAuth.mockRejectedValue({ status: 401, message: 'Invalid Firebase token' }); + const res = await request(app) + .post('/users/login') + .set('X-Firebase-Token', 'invalid-firebase-token'); + + expect(res.statusCode).toBe(401); + expect(res.body.error).toBe('Invalid Firebase token'); + }); + + it('Discord auth failure', async () => { + authService.handleDiscordAuth.mockRejectedValue({ status: 401, message: 'Invalid Discord code' }); + const res = await request(app) + .post('/users/login') + .set('X-Discord-Code', 'invalid-discord-code'); + + expect(res.statusCode).toBe(401); + expect(res.body.error).toBe('Invalid Discord code'); + }); +}); \ No newline at end of file From 31e59fbb70795e895c2cbf6c2b7568cafd827ed1 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Wed, 25 Jun 2025 16:26:09 +0200 Subject: [PATCH 026/135] added tests coverage results to gitignore --- .gitignore | 1 + coverage/clover.xml | 429 ----------- coverage/coverage-final.json | 17 - coverage/lcov-report/base.css | 224 ------ coverage/lcov-report/block-navigation.js | 87 --- coverage/lcov-report/favicon.png | Bin 445 -> 0 bytes coverage/lcov-report/index.html | 191 ----- coverage/lcov-report/prettify.css | 1 - coverage/lcov-report/prettify.js | 2 - coverage/lcov-report/sort-arrow-sprite.png | Bin 138 -> 0 bytes coverage/lcov-report/sorter.js | 196 ----- .../webplatform-backend/app.js.html | 187 ----- .../webplatform-backend/aws/index.html | 116 --- .../aws/ssm-helper.js.html | 205 ----- .../webplatform-backend/db.js.html | 268 ------- .../webplatform-backend/index.html | 161 ---- .../middleware/authentication.js.html | 175 ----- .../middleware/authorization.js.html | 283 ------- .../webplatform-backend/middleware/index.html | 131 ---- .../webplatform-backend/oauth.js.html | 172 ----- .../applications_repository.js.html | 169 ----- .../authorization_repository.js.html | 121 --- .../repositories/index.html | 161 ---- .../repositories/roles_repository.js.html | 121 --- .../repositories/user_repository.js.html | 268 ------- .../webplatform-backend/routes/index.html | 116 --- .../webplatform-backend/routes/users.js.html | 484 ------------ .../services/applications_service.js.html | 151 ---- .../services/auth_service.js.html | 340 --------- .../webplatform-backend/services/index.html | 161 ---- .../services/roles_service.js.html | 133 ---- .../services/user_service.js.html | 208 ----- .../webplatform-backend/utils.js.html | 292 ------- coverage/lcov.info | 711 ------------------ 34 files changed, 1 insertion(+), 6281 deletions(-) delete mode 100644 coverage/clover.xml delete mode 100644 coverage/coverage-final.json delete mode 100644 coverage/lcov-report/base.css delete mode 100644 coverage/lcov-report/block-navigation.js delete mode 100644 coverage/lcov-report/favicon.png delete mode 100644 coverage/lcov-report/index.html delete mode 100644 coverage/lcov-report/prettify.css delete mode 100644 coverage/lcov-report/prettify.js delete mode 100644 coverage/lcov-report/sort-arrow-sprite.png delete mode 100644 coverage/lcov-report/sorter.js delete mode 100644 coverage/lcov-report/webplatform-backend/app.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/aws/index.html delete mode 100644 coverage/lcov-report/webplatform-backend/aws/ssm-helper.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/db.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/index.html delete mode 100644 coverage/lcov-report/webplatform-backend/middleware/authentication.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/middleware/authorization.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/middleware/index.html delete mode 100644 coverage/lcov-report/webplatform-backend/oauth.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/repositories/applications_repository.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/repositories/authorization_repository.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/repositories/index.html delete mode 100644 coverage/lcov-report/webplatform-backend/repositories/roles_repository.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/repositories/user_repository.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/routes/index.html delete mode 100644 coverage/lcov-report/webplatform-backend/routes/users.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/services/applications_service.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/services/auth_service.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/services/index.html delete mode 100644 coverage/lcov-report/webplatform-backend/services/roles_service.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/services/user_service.js.html delete mode 100644 coverage/lcov-report/webplatform-backend/utils.js.html delete mode 100644 coverage/lcov.info diff --git a/.gitignore b/.gitignore index 2a2f0d9..93d5f92 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules /.vs .vscode/ /serviceAccountKey.json +coverage/ \ No newline at end of file diff --git a/coverage/clover.xml b/coverage/clover.xml deleted file mode 100644 index 2de783e..0000000 --- a/coverage/clover.xml +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json deleted file mode 100644 index d5fb565..0000000 --- a/coverage/coverage-final.json +++ /dev/null @@ -1,17 +0,0 @@ -{"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/app.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/app.js","statementMap":{"0":{"start":{"line":1,"column":15},"end":{"line":1,"column":32}},"1":{"start":{"line":2,"column":16},"end":{"line":2,"column":34}},"2":{"start":{"line":3,"column":21},"end":{"line":3,"column":45}},"3":{"start":{"line":4,"column":13},"end":{"line":4,"column":28}},"4":{"start":{"line":5,"column":25},"end":{"line":5,"column":63}},"5":{"start":{"line":6,"column":17},"end":{"line":6,"column":37}},"6":{"start":{"line":7,"column":23},"end":{"line":7,"column":42}},"7":{"start":{"line":9,"column":0},"end":{"line":9,"column":49}},"8":{"start":{"line":11,"column":12},"end":{"line":11,"column":21}},"9":{"start":{"line":14,"column":0},"end":{"line":16,"column":1}},"10":{"start":{"line":15,"column":2},"end":{"line":15,"column":44}},"11":{"start":{"line":18,"column":0},"end":{"line":18,"column":24}},"12":{"start":{"line":19,"column":0},"end":{"line":19,"column":24}},"13":{"start":{"line":20,"column":0},"end":{"line":31,"column":4}},"14":{"start":{"line":22,"column":4},"end":{"line":26,"column":5}},"15":{"start":{"line":23,"column":6},"end":{"line":23,"column":29}},"16":{"start":{"line":25,"column":6},"end":{"line":25,"column":49}},"17":{"start":{"line":32,"column":0},"end":{"line":32,"column":25}},"18":{"start":{"line":33,"column":0},"end":{"line":33,"column":24}},"19":{"start":{"line":35,"column":0},"end":{"line":35,"column":21}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":21,"column":10},"end":{"line":21,"column":11}},"loc":{"start":{"line":21,"column":32},"end":{"line":27,"column":3}},"line":21}},"branchMap":{"0":{"loc":{"start":{"line":14,"column":0},"end":{"line":16,"column":1}},"type":"if","locations":[{"start":{"line":14,"column":0},"end":{"line":16,"column":1}},{"start":{},"end":{}}],"line":14},"1":{"loc":{"start":{"line":22,"column":4},"end":{"line":26,"column":5}},"type":"if","locations":[{"start":{"line":22,"column":4},"end":{"line":26,"column":5}},{"start":{"line":24,"column":11},"end":{"line":26,"column":5}}],"line":22},"2":{"loc":{"start":{"line":22,"column":8},"end":{"line":22,"column":62}},"type":"binary-expr","locations":[{"start":{"line":22,"column":8},"end":{"line":22,"column":15}},{"start":{"line":22,"column":19},"end":{"line":22,"column":62}}],"line":22}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":0,"11":1,"12":1,"13":1,"14":1,"15":1,"16":0,"17":1,"18":1,"19":1},"f":{"0":1},"b":{"0":[0,1],"1":[1,0],"2":[1,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"ff5d057cc22887d21ae671290ced3c3a58bc4e60"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/db.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/db.js","statementMap":{"0":{"start":{"line":1,"column":13},"end":{"line":1,"column":28}},"1":{"start":{"line":2,"column":24},"end":{"line":2,"column":51}},"2":{"start":{"line":3,"column":15},"end":{"line":3,"column":32}},"3":{"start":{"line":5,"column":0},"end":{"line":5,"column":49}},"4":{"start":{"line":8,"column":19},"end":{"line":8,"column":23}},"5":{"start":{"line":11,"column":18},"end":{"line":11,"column":53}},"6":{"start":{"line":12,"column":2},"end":{"line":12,"column":90}},"7":{"start":{"line":13,"column":2},"end":{"line":33,"column":3}},"8":{"start":{"line":14,"column":4},"end":{"line":14,"column":43}},"9":{"start":{"line":15,"column":4},"end":{"line":15,"column":54}},"10":{"start":{"line":16,"column":4},"end":{"line":16,"column":39}},"11":{"start":{"line":17,"column":4},"end":{"line":17,"column":43}},"12":{"start":{"line":18,"column":4},"end":{"line":18,"column":47}},"13":{"start":{"line":22,"column":22},"end":{"line":22,"column":32}},"14":{"start":{"line":23,"column":24},"end":{"line":26,"column":11}},"15":{"start":{"line":28,"column":4},"end":{"line":28,"column":50}},"16":{"start":{"line":29,"column":4},"end":{"line":29,"column":41}},"17":{"start":{"line":30,"column":4},"end":{"line":30,"column":37}},"18":{"start":{"line":31,"column":4},"end":{"line":31,"column":50}},"19":{"start":{"line":32,"column":4},"end":{"line":32,"column":42}},"20":{"start":{"line":35,"column":2},"end":{"line":42,"column":4}},"21":{"start":{"line":46,"column":19},"end":{"line":46,"column":40}},"22":{"start":{"line":48,"column":2},"end":{"line":59,"column":5}},"23":{"start":{"line":62,"column":0},"end":{"line":62,"column":72}},"24":{"start":{"line":62,"column":57},"end":{"line":62,"column":69}}},"fnMap":{"0":{"name":"getDbSettings","decl":{"start":{"line":10,"column":15},"end":{"line":10,"column":28}},"loc":{"start":{"line":10,"column":31},"end":{"line":43,"column":1}},"line":10},"1":{"name":"initDb","decl":{"start":{"line":45,"column":15},"end":{"line":45,"column":21}},"loc":{"start":{"line":45,"column":24},"end":{"line":60,"column":1}},"line":45},"2":{"name":"(anonymous_2)","decl":{"start":{"line":62,"column":51},"end":{"line":62,"column":52}},"loc":{"start":{"line":62,"column":57},"end":{"line":62,"column":69}},"line":62}},"branchMap":{"0":{"loc":{"start":{"line":12,"column":31},"end":{"line":12,"column":88}},"type":"cond-expr","locations":[{"start":{"line":12,"column":41},"end":{"line":12,"column":48}},{"start":{"line":12,"column":51},"end":{"line":12,"column":88}}],"line":12},"1":{"loc":{"start":{"line":12,"column":51},"end":{"line":12,"column":88}},"type":"binary-expr","locations":[{"start":{"line":12,"column":51},"end":{"line":12,"column":71}},{"start":{"line":12,"column":75},"end":{"line":12,"column":88}}],"line":12},"2":{"loc":{"start":{"line":13,"column":2},"end":{"line":33,"column":3}},"type":"if","locations":[{"start":{"line":13,"column":2},"end":{"line":33,"column":3}},{"start":{"line":20,"column":7},"end":{"line":33,"column":3}}],"line":13},"3":{"loc":{"start":{"line":15,"column":19},"end":{"line":15,"column":53}},"type":"binary-expr","locations":[{"start":{"line":15,"column":19},"end":{"line":15,"column":38}},{"start":{"line":15,"column":42},"end":{"line":15,"column":53}}],"line":15},"4":{"loc":{"start":{"line":18,"column":19},"end":{"line":18,"column":46}},"type":"binary-expr","locations":[{"start":{"line":18,"column":19},"end":{"line":18,"column":38}},{"start":{"line":18,"column":42},"end":{"line":18,"column":46}}],"line":18},"5":{"loc":{"start":{"line":41,"column":9},"end":{"line":41,"column":56}},"type":"cond-expr","locations":[{"start":{"line":41,"column":19},"end":{"line":41,"column":24}},{"start":{"line":41,"column":27},"end":{"line":41,"column":56}}],"line":41}},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":2,"24":1},"f":{"0":0,"1":0,"2":1},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"c55475ba25f6567300c9d59a11d7ab9eb667d91b"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/oauth.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/oauth.js","statementMap":{"0":{"start":{"line":1,"column":24},"end":{"line":1,"column":51}},"1":{"start":{"line":2,"column":15},"end":{"line":2,"column":32}},"2":{"start":{"line":4,"column":20},"end":{"line":8,"column":1}},"3":{"start":{"line":11,"column":65},"end":{"line":11,"column":69}},"4":{"start":{"line":13,"column":2},"end":{"line":22,"column":3}},"5":{"start":{"line":14,"column":4},"end":{"line":14,"column":52}},"6":{"start":{"line":15,"column":4},"end":{"line":15,"column":60}},"7":{"start":{"line":16,"column":4},"end":{"line":16,"column":58}},"8":{"start":{"line":18,"column":19},"end":{"line":18,"column":118}},"9":{"start":{"line":19,"column":4},"end":{"line":19,"column":67}},"10":{"start":{"line":20,"column":4},"end":{"line":20,"column":75}},"11":{"start":{"line":21,"column":4},"end":{"line":21,"column":71}},"12":{"start":{"line":24,"column":2},"end":{"line":24,"column":48}},"13":{"start":{"line":25,"column":2},"end":{"line":25,"column":56}},"14":{"start":{"line":26,"column":2},"end":{"line":26,"column":54}},"15":{"start":{"line":29,"column":0},"end":{"line":29,"column":44}}},"fnMap":{"0":{"name":"initOAuth","decl":{"start":{"line":10,"column":15},"end":{"line":10,"column":24}},"loc":{"start":{"line":10,"column":27},"end":{"line":27,"column":1}},"line":10}},"branchMap":{"0":{"loc":{"start":{"line":13,"column":2},"end":{"line":22,"column":3}},"type":"if","locations":[{"start":{"line":13,"column":2},"end":{"line":22,"column":3}},{"start":{"line":17,"column":9},"end":{"line":22,"column":3}}],"line":13}},"s":{"0":1,"1":1,"2":1,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":1},"f":{"0":0},"b":{"0":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"d2714503c006b7000f45bb5cbe5f143c122f8750"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/utils.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/utils.js","statementMap":{"0":{"start":{"line":1,"column":13},"end":{"line":1,"column":55}},"1":{"start":{"line":2,"column":24},"end":{"line":2,"column":51}},"2":{"start":{"line":3,"column":15},"end":{"line":3,"column":32}},"3":{"start":{"line":4,"column":15},"end":{"line":4,"column":46}},"4":{"start":{"line":6,"column":17},"end":{"line":6,"column":21}},"5":{"start":{"line":8,"column":19},"end":{"line":11,"column":1}},"6":{"start":{"line":14,"column":23},"end":{"line":14,"column":27}},"7":{"start":{"line":15,"column":2},"end":{"line":29,"column":3}},"8":{"start":{"line":16,"column":4},"end":{"line":24,"column":5}},"9":{"start":{"line":17,"column":6},"end":{"line":17,"column":59}},"10":{"start":{"line":20,"column":24},"end":{"line":20,"column":34}},"11":{"start":{"line":21,"column":26},"end":{"line":21,"column":127}},"12":{"start":{"line":22,"column":23},"end":{"line":22,"column":58}},"13":{"start":{"line":23,"column":6},"end":{"line":23,"column":44}},"14":{"start":{"line":27,"column":4},"end":{"line":27,"column":55}},"15":{"start":{"line":28,"column":4},"end":{"line":28,"column":58}},"16":{"start":{"line":30,"column":2},"end":{"line":30,"column":24}},"17":{"start":{"line":34,"column":2},"end":{"line":49,"column":3}},"18":{"start":{"line":35,"column":4},"end":{"line":45,"column":5}},"19":{"start":{"line":36,"column":20},"end":{"line":36,"column":44}},"20":{"start":{"line":37,"column":6},"end":{"line":43,"column":13}},"21":{"start":{"line":38,"column":8},"end":{"line":38,"column":38}},"22":{"start":{"line":39,"column":8},"end":{"line":41,"column":9}},"23":{"start":{"line":40,"column":10},"end":{"line":40,"column":99}},"24":{"start":{"line":42,"column":8},"end":{"line":42,"column":19}},"25":{"start":{"line":44,"column":6},"end":{"line":44,"column":59}},"26":{"start":{"line":47,"column":4},"end":{"line":47,"column":45}},"27":{"start":{"line":48,"column":4},"end":{"line":48,"column":50}},"28":{"start":{"line":53,"column":2},"end":{"line":55,"column":3}},"29":{"start":{"line":54,"column":4},"end":{"line":54,"column":44}},"30":{"start":{"line":56,"column":2},"end":{"line":56,"column":30}},"31":{"start":{"line":60,"column":2},"end":{"line":60,"column":50}},"32":{"start":{"line":63,"column":22},"end":{"line":67,"column":1}},"33":{"start":{"line":69,"column":0},"end":{"line":69,"column":121}}},"fnMap":{"0":{"name":"getFirebaseSdkServiceAccount","decl":{"start":{"line":13,"column":15},"end":{"line":13,"column":43}},"loc":{"start":{"line":13,"column":46},"end":{"line":31,"column":1}},"line":13},"1":{"name":"loadRolesIntoMemory","decl":{"start":{"line":33,"column":15},"end":{"line":33,"column":34}},"loc":{"start":{"line":33,"column":37},"end":{"line":50,"column":1}},"line":33},"2":{"name":"(anonymous_2)","decl":{"start":{"line":37,"column":32},"end":{"line":37,"column":33}},"loc":{"start":{"line":37,"column":47},"end":{"line":43,"column":7}},"line":37},"3":{"name":"getRole","decl":{"start":{"line":52,"column":9},"end":{"line":52,"column":16}},"loc":{"start":{"line":52,"column":27},"end":{"line":57,"column":1}},"line":52},"4":{"name":"isRoleExists","decl":{"start":{"line":59,"column":9},"end":{"line":59,"column":21}},"loc":{"start":{"line":59,"column":28},"end":{"line":61,"column":1}},"line":59}},"branchMap":{"0":{"loc":{"start":{"line":16,"column":4},"end":{"line":24,"column":5}},"type":"if","locations":[{"start":{"line":16,"column":4},"end":{"line":24,"column":5}},{"start":{"line":19,"column":9},"end":{"line":24,"column":5}}],"line":16},"1":{"loc":{"start":{"line":35,"column":4},"end":{"line":45,"column":5}},"type":"if","locations":[{"start":{"line":35,"column":4},"end":{"line":45,"column":5}},{"start":{},"end":{}}],"line":35},"2":{"loc":{"start":{"line":39,"column":8},"end":{"line":41,"column":9}},"type":"if","locations":[{"start":{"line":39,"column":8},"end":{"line":41,"column":9}},{"start":{},"end":{}}],"line":39},"3":{"loc":{"start":{"line":53,"column":2},"end":{"line":55,"column":3}},"type":"if","locations":[{"start":{"line":53,"column":2},"end":{"line":55,"column":3}},{"start":{},"end":{}}],"line":53}},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":2,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":2,"33":2},"f":{"0":0,"1":0,"2":0,"3":0,"4":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"221d1712c37cbb0dce496b7df09415f1118448b6"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/aws/ssm-helper.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/aws/ssm-helper.js","statementMap":{"0":{"start":{"line":1,"column":44},"end":{"line":1,"column":74}},"1":{"start":{"line":2,"column":15},"end":{"line":2,"column":47}},"2":{"start":{"line":5,"column":20},"end":{"line":7,"column":4}},"3":{"start":{"line":9,"column":2},"end":{"line":29,"column":3}},"4":{"start":{"line":10,"column":4},"end":{"line":10,"column":60}},"5":{"start":{"line":11,"column":20},"end":{"line":14,"column":6}},"6":{"start":{"line":16,"column":19},"end":{"line":16,"column":48}},"7":{"start":{"line":18,"column":20},"end":{"line":22,"column":10}},"8":{"start":{"line":19,"column":18},"end":{"line":19,"column":39}},"9":{"start":{"line":20,"column":6},"end":{"line":20,"column":59}},"10":{"start":{"line":21,"column":6},"end":{"line":21,"column":17}},"11":{"start":{"line":24,"column":4},"end":{"line":24,"column":58}},"12":{"start":{"line":25,"column":4},"end":{"line":25,"column":19}},"13":{"start":{"line":27,"column":4},"end":{"line":27,"column":51}},"14":{"start":{"line":28,"column":4},"end":{"line":28,"column":14}},"15":{"start":{"line":33,"column":2},"end":{"line":37,"column":3}},"16":{"start":{"line":34,"column":4},"end":{"line":34,"column":29}},"17":{"start":{"line":36,"column":4},"end":{"line":36,"column":17}},"18":{"start":{"line":40,"column":0},"end":{"line":40,"column":33}}},"fnMap":{"0":{"name":"loadSecrets","decl":{"start":{"line":4,"column":15},"end":{"line":4,"column":26}},"loc":{"start":{"line":4,"column":85},"end":{"line":30,"column":1}},"line":4},"1":{"name":"(anonymous_1)","decl":{"start":{"line":18,"column":45},"end":{"line":18,"column":46}},"loc":{"start":{"line":18,"column":71},"end":{"line":22,"column":5}},"line":18},"2":{"name":"parseJsonValue","decl":{"start":{"line":32,"column":9},"end":{"line":32,"column":23}},"loc":{"start":{"line":32,"column":31},"end":{"line":38,"column":1}},"line":32}},"branchMap":{"0":{"loc":{"start":{"line":4,"column":42},"end":{"line":4,"column":64}},"type":"default-arg","locations":[{"start":{"line":4,"column":59},"end":{"line":4,"column":64}}],"line":4},"1":{"loc":{"start":{"line":4,"column":66},"end":{"line":4,"column":83}},"type":"default-arg","locations":[{"start":{"line":4,"column":78},"end":{"line":4,"column":83}}],"line":4},"2":{"loc":{"start":{"line":20,"column":17},"end":{"line":20,"column":58}},"type":"cond-expr","locations":[{"start":{"line":20,"column":29},"end":{"line":20,"column":50}},{"start":{"line":20,"column":53},"end":{"line":20,"column":58}}],"line":20}},"s":{"0":2,"1":2,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":2},"f":{"0":0,"1":0,"2":0},"b":{"0":[0],"1":[0],"2":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"dd5a2beff850a1c2b15b27a4464938c24bb46ffe"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/middleware/authentication.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/middleware/authentication.js","statementMap":{"0":{"start":{"line":1,"column":20},"end":{"line":1,"column":55}},"1":{"start":{"line":2,"column":29},"end":{"line":2,"column":57}},"2":{"start":{"line":3,"column":15},"end":{"line":3,"column":47}},"3":{"start":{"line":6,"column":2},"end":{"line":28,"column":4}},"4":{"start":{"line":7,"column":4},"end":{"line":27,"column":5}},"5":{"start":{"line":8,"column":29},"end":{"line":8,"column":49}},"6":{"start":{"line":9,"column":6},"end":{"line":12,"column":7}},"7":{"start":{"line":10,"column":8},"end":{"line":10,"column":75}},"8":{"start":{"line":11,"column":8},"end":{"line":11,"column":22}},"9":{"start":{"line":14,"column":19},"end":{"line":14,"column":72}},"10":{"start":{"line":15,"column":6},"end":{"line":18,"column":7}},"11":{"start":{"line":16,"column":8},"end":{"line":16,"column":102}},"12":{"start":{"line":17,"column":8},"end":{"line":17,"column":78}},"13":{"start":{"line":20,"column":6},"end":{"line":20,"column":27}},"14":{"start":{"line":21,"column":6},"end":{"line":21,"column":33}},"15":{"start":{"line":23,"column":6},"end":{"line":23,"column":13}},"16":{"start":{"line":25,"column":6},"end":{"line":25,"column":73}},"17":{"start":{"line":26,"column":6},"end":{"line":26,"column":35}},"18":{"start":{"line":31,"column":0},"end":{"line":31,"column":34}}},"fnMap":{"0":{"name":"authenticate","decl":{"start":{"line":5,"column":9},"end":{"line":5,"column":21}},"loc":{"start":{"line":5,"column":24},"end":{"line":29,"column":1}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":6,"column":9},"end":{"line":6,"column":10}},"loc":{"start":{"line":6,"column":35},"end":{"line":28,"column":3}},"line":6}},"branchMap":{"0":{"loc":{"start":{"line":9,"column":6},"end":{"line":12,"column":7}},"type":"if","locations":[{"start":{"line":9,"column":6},"end":{"line":12,"column":7}},{"start":{},"end":{}}],"line":9},"1":{"loc":{"start":{"line":15,"column":6},"end":{"line":18,"column":7}},"type":"if","locations":[{"start":{"line":15,"column":6},"end":{"line":18,"column":7}},{"start":{},"end":{}}],"line":15}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":1,"17":1,"18":1},"f":{"0":1,"1":1},"b":{"0":[1,0],"1":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"a58617a5d6872fe3545e0b9e4dad0eb27bed516a"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/middleware/authorization.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/middleware/authorization.js","statementMap":{"0":{"start":{"line":1,"column":14},"end":{"line":1,"column":33}},"1":{"start":{"line":2,"column":13},"end":{"line":2,"column":64}},"2":{"start":{"line":3,"column":29},"end":{"line":3,"column":57}},"3":{"start":{"line":4,"column":15},"end":{"line":4,"column":47}},"4":{"start":{"line":7,"column":2},"end":{"line":24,"column":4}},"5":{"start":{"line":8,"column":4},"end":{"line":23,"column":5}},"6":{"start":{"line":9,"column":21},"end":{"line":9,"column":31}},"7":{"start":{"line":10,"column":6},"end":{"line":12,"column":7}},"8":{"start":{"line":11,"column":8},"end":{"line":11,"column":68}},"9":{"start":{"line":14,"column":21},"end":{"line":14,"column":44}},"10":{"start":{"line":15,"column":23},"end":{"line":15,"column":60}},"11":{"start":{"line":16,"column":6},"end":{"line":18,"column":7}},"12":{"start":{"line":17,"column":8},"end":{"line":17,"column":88}},"13":{"start":{"line":19,"column":6},"end":{"line":19,"column":13}},"14":{"start":{"line":21,"column":6},"end":{"line":21,"column":48}},"15":{"start":{"line":22,"column":6},"end":{"line":22,"column":35}},"16":{"start":{"line":27,"column":23},"end":{"line":30,"column":1}},"17":{"start":{"line":33,"column":2},"end":{"line":63,"column":4}},"18":{"start":{"line":34,"column":4},"end":{"line":34,"column":55}},"19":{"start":{"line":35,"column":4},"end":{"line":62,"column":5}},"20":{"start":{"line":36,"column":25},"end":{"line":36,"column":38}},"21":{"start":{"line":37,"column":21},"end":{"line":37,"column":31}},"22":{"start":{"line":38,"column":6},"end":{"line":40,"column":7}},"23":{"start":{"line":39,"column":8},"end":{"line":39,"column":68}},"24":{"start":{"line":42,"column":6},"end":{"line":44,"column":7}},"25":{"start":{"line":43,"column":8},"end":{"line":43,"column":108}},"26":{"start":{"line":47,"column":6},"end":{"line":52,"column":7}},"27":{"start":{"line":48,"column":8},"end":{"line":48,"column":63}},"28":{"start":{"line":50,"column":23},"end":{"line":50,"column":82}},"29":{"start":{"line":51,"column":8},"end":{"line":51,"column":54}},"30":{"start":{"line":54,"column":6},"end":{"line":56,"column":7}},"31":{"start":{"line":55,"column":8},"end":{"line":55,"column":86}},"32":{"start":{"line":58,"column":6},"end":{"line":58,"column":13}},"33":{"start":{"line":60,"column":6},"end":{"line":60,"column":53}},"34":{"start":{"line":61,"column":6},"end":{"line":61,"column":35}},"35":{"start":{"line":67,"column":0},"end":{"line":67,"column":65}}},"fnMap":{"0":{"name":"verifyRole","decl":{"start":{"line":6,"column":9},"end":{"line":6,"column":19}},"loc":{"start":{"line":6,"column":30},"end":{"line":25,"column":1}},"line":6},"1":{"name":"(anonymous_1)","decl":{"start":{"line":7,"column":9},"end":{"line":7,"column":10}},"loc":{"start":{"line":7,"column":35},"end":{"line":24,"column":3}},"line":7},"2":{"name":"verifyOwnership","decl":{"start":{"line":32,"column":9},"end":{"line":32,"column":24}},"loc":{"start":{"line":32,"column":38},"end":{"line":64,"column":1}},"line":32},"3":{"name":"(anonymous_3)","decl":{"start":{"line":33,"column":9},"end":{"line":33,"column":10}},"loc":{"start":{"line":33,"column":35},"end":{"line":63,"column":3}},"line":33}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":6},"end":{"line":12,"column":7}},"type":"if","locations":[{"start":{"line":10,"column":6},"end":{"line":12,"column":7}},{"start":{},"end":{}}],"line":10},"1":{"loc":{"start":{"line":16,"column":6},"end":{"line":18,"column":7}},"type":"if","locations":[{"start":{"line":16,"column":6},"end":{"line":18,"column":7}},{"start":{},"end":{}}],"line":16},"2":{"loc":{"start":{"line":38,"column":6},"end":{"line":40,"column":7}},"type":"if","locations":[{"start":{"line":38,"column":6},"end":{"line":40,"column":7}},{"start":{},"end":{}}],"line":38},"3":{"loc":{"start":{"line":42,"column":6},"end":{"line":44,"column":7}},"type":"if","locations":[{"start":{"line":42,"column":6},"end":{"line":44,"column":7}},{"start":{},"end":{}}],"line":42},"4":{"loc":{"start":{"line":42,"column":10},"end":{"line":42,"column":32}},"type":"binary-expr","locations":[{"start":{"line":42,"column":10},"end":{"line":42,"column":21}},{"start":{"line":42,"column":25},"end":{"line":42,"column":32}}],"line":42},"5":{"loc":{"start":{"line":47,"column":6},"end":{"line":52,"column":7}},"type":"if","locations":[{"start":{"line":47,"column":6},"end":{"line":52,"column":7}},{"start":{"line":49,"column":13},"end":{"line":52,"column":7}}],"line":47},"6":{"loc":{"start":{"line":51,"column":19},"end":{"line":51,"column":53}},"type":"binary-expr","locations":[{"start":{"line":51,"column":19},"end":{"line":51,"column":30}},{"start":{"line":51,"column":34},"end":{"line":51,"column":53}}],"line":51},"7":{"loc":{"start":{"line":54,"column":6},"end":{"line":56,"column":7}},"type":"if","locations":[{"start":{"line":54,"column":6},"end":{"line":56,"column":7}},{"start":{},"end":{}}],"line":54}},"s":{"0":1,"1":1,"2":1,"3":1,"4":2,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":1,"17":2,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":1},"f":{"0":2,"1":0,"2":2,"3":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"c2e76812cb4e313c0dd5250f6ce6362258b5877d"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/applications_repository.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/applications_repository.js","statementMap":{"0":{"start":{"line":1,"column":20},"end":{"line":1,"column":36}},"1":{"start":{"line":3,"column":13},"end":{"line":3,"column":22}},"2":{"start":{"line":6,"column":2},"end":{"line":6,"column":48}},"3":{"start":{"line":10,"column":2},"end":{"line":12,"column":20}},"4":{"start":{"line":16,"column":24},"end":{"line":19,"column":19}},"5":{"start":{"line":20,"column":2},"end":{"line":20,"column":21}},"6":{"start":{"line":24,"column":2},"end":{"line":26,"column":13}},"7":{"start":{"line":29,"column":0},"end":{"line":29,"column":109}}},"fnMap":{"0":{"name":"getAllApplications","decl":{"start":{"line":5,"column":15},"end":{"line":5,"column":33}},"loc":{"start":{"line":5,"column":36},"end":{"line":7,"column":1}},"line":5},"1":{"name":"createNewApplication","decl":{"start":{"line":9,"column":15},"end":{"line":9,"column":35}},"loc":{"start":{"line":9,"column":84},"end":{"line":13,"column":1}},"line":9},"2":{"name":"updateApplicationById","decl":{"start":{"line":15,"column":15},"end":{"line":15,"column":36}},"loc":{"start":{"line":15,"column":82},"end":{"line":21,"column":1}},"line":15},"3":{"name":"getApplicationByFields","decl":{"start":{"line":23,"column":15},"end":{"line":23,"column":37}},"loc":{"start":{"line":23,"column":46},"end":{"line":27,"column":1}},"line":23}},"branchMap":{},"s":{"0":1,"1":1,"2":0,"3":0,"4":0,"5":0,"6":0,"7":1},"f":{"0":0,"1":0,"2":0,"3":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"e2f5459c12f1e6248f847d4cf039322c3d7aedad"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/authorization_repository.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/authorization_repository.js","statementMap":{"0":{"start":{"line":1,"column":20},"end":{"line":1,"column":36}},"1":{"start":{"line":4,"column":15},"end":{"line":4,"column":24}},"2":{"start":{"line":5,"column":2},"end":{"line":5,"column":99}},"3":{"start":{"line":9,"column":15},"end":{"line":9,"column":24}},"4":{"start":{"line":10,"column":2},"end":{"line":10,"column":100}},"5":{"start":{"line":13,"column":0},"end":{"line":13,"column":49}}},"fnMap":{"0":{"name":"verifyRole","decl":{"start":{"line":3,"column":15},"end":{"line":3,"column":25}},"loc":{"start":{"line":3,"column":42},"end":{"line":6,"column":1}},"line":3},"1":{"name":"verifyOwnership","decl":{"start":{"line":8,"column":15},"end":{"line":8,"column":30}},"loc":{"start":{"line":8,"column":64},"end":{"line":11,"column":1}},"line":8}},"branchMap":{},"s":{"0":1,"1":0,"2":0,"3":0,"4":0,"5":1},"f":{"0":0,"1":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"b2303f11089ff50517ce5c5d6d75731c1c7305c4"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/roles_repository.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/roles_repository.js","statementMap":{"0":{"start":{"line":1,"column":20},"end":{"line":1,"column":36}},"1":{"start":{"line":4,"column":15},"end":{"line":4,"column":24}},"2":{"start":{"line":5,"column":2},"end":{"line":5,"column":41}},"3":{"start":{"line":9,"column":15},"end":{"line":9,"column":24}},"4":{"start":{"line":10,"column":2},"end":{"line":10,"column":72}},"5":{"start":{"line":13,"column":0},"end":{"line":13,"column":51}}},"fnMap":{"0":{"name":"getAllRoles","decl":{"start":{"line":3,"column":15},"end":{"line":3,"column":26}},"loc":{"start":{"line":3,"column":29},"end":{"line":6,"column":1}},"line":3},"1":{"name":"assignRoleToUser","decl":{"start":{"line":8,"column":15},"end":{"line":8,"column":31}},"loc":{"start":{"line":8,"column":48},"end":{"line":11,"column":1}},"line":8}},"branchMap":{},"s":{"0":2,"1":0,"2":0,"3":0,"4":0,"5":2},"f":{"0":0,"1":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"44b0fa85d8948cc68ca25e6099db30c902e41e50"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/user_repository.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/repositories/user_repository.js","statementMap":{"0":{"start":{"line":1,"column":20},"end":{"line":1,"column":36}},"1":{"start":{"line":4,"column":15},"end":{"line":4,"column":24}},"2":{"start":{"line":5,"column":2},"end":{"line":5,"column":63}},"3":{"start":{"line":9,"column":15},"end":{"line":9,"column":24}},"4":{"start":{"line":10,"column":17},"end":{"line":12,"column":19}},"5":{"start":{"line":13,"column":2},"end":{"line":13,"column":29}},"6":{"start":{"line":14,"column":2},"end":{"line":14,"column":14}},"7":{"start":{"line":18,"column":15},"end":{"line":18,"column":24}},"8":{"start":{"line":19,"column":15},"end":{"line":19,"column":58}},"9":{"start":{"line":20,"column":2},"end":{"line":22,"column":3}},"10":{"start":{"line":21,"column":4},"end":{"line":21,"column":31}},"11":{"start":{"line":23,"column":2},"end":{"line":23,"column":14}},"12":{"start":{"line":27,"column":15},"end":{"line":27,"column":24}},"13":{"start":{"line":28,"column":15},"end":{"line":28,"column":56}},"14":{"start":{"line":29,"column":2},"end":{"line":31,"column":3}},"15":{"start":{"line":30,"column":4},"end":{"line":30,"column":31}},"16":{"start":{"line":32,"column":2},"end":{"line":32,"column":14}},"17":{"start":{"line":36,"column":15},"end":{"line":36,"column":24}},"18":{"start":{"line":37,"column":2},"end":{"line":40,"column":43}},"19":{"start":{"line":44,"column":15},"end":{"line":44,"column":24}},"20":{"start":{"line":45,"column":17},"end":{"line":48,"column":19}},"21":{"start":{"line":49,"column":2},"end":{"line":51,"column":3}},"22":{"start":{"line":50,"column":4},"end":{"line":50,"column":31}},"23":{"start":{"line":52,"column":2},"end":{"line":52,"column":14}},"24":{"start":{"line":56,"column":15},"end":{"line":56,"column":24}},"25":{"start":{"line":57,"column":2},"end":{"line":57,"column":51}},"26":{"start":{"line":60,"column":0},"end":{"line":61,"column":36}}},"fnMap":{"0":{"name":"getAllUsers","decl":{"start":{"line":3,"column":15},"end":{"line":3,"column":26}},"loc":{"start":{"line":3,"column":29},"end":{"line":6,"column":1}},"line":3},"1":{"name":"createNewUser","decl":{"start":{"line":8,"column":15},"end":{"line":8,"column":28}},"loc":{"start":{"line":8,"column":37},"end":{"line":15,"column":1}},"line":8},"2":{"name":"getUserInfoById","decl":{"start":{"line":17,"column":15},"end":{"line":17,"column":30}},"loc":{"start":{"line":17,"column":35},"end":{"line":24,"column":1}},"line":17},"3":{"name":"getUserByFields","decl":{"start":{"line":26,"column":15},"end":{"line":26,"column":30}},"loc":{"start":{"line":26,"column":39},"end":{"line":33,"column":1}},"line":26},"4":{"name":"getUserRolesById","decl":{"start":{"line":35,"column":15},"end":{"line":35,"column":31}},"loc":{"start":{"line":35,"column":36},"end":{"line":41,"column":1}},"line":35},"5":{"name":"updateUserById","decl":{"start":{"line":43,"column":15},"end":{"line":43,"column":29}},"loc":{"start":{"line":43,"column":43},"end":{"line":53,"column":1}},"line":43},"6":{"name":"deleteUserById","decl":{"start":{"line":55,"column":15},"end":{"line":55,"column":29}},"loc":{"start":{"line":55,"column":34},"end":{"line":58,"column":1}},"line":55}},"branchMap":{"0":{"loc":{"start":{"line":20,"column":2},"end":{"line":22,"column":3}},"type":"if","locations":[{"start":{"line":20,"column":2},"end":{"line":22,"column":3}},{"start":{},"end":{}}],"line":20},"1":{"loc":{"start":{"line":29,"column":2},"end":{"line":31,"column":3}},"type":"if","locations":[{"start":{"line":29,"column":2},"end":{"line":31,"column":3}},{"start":{},"end":{}}],"line":29},"2":{"loc":{"start":{"line":49,"column":2},"end":{"line":51,"column":3}},"type":"if","locations":[{"start":{"line":49,"column":2},"end":{"line":51,"column":3}},{"start":{},"end":{}}],"line":49}},"s":{"0":2,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":2},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"f01ffb29044108361e68cf3b54cf71786f38931a"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/routes/users.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/routes/users.js","statementMap":{"0":{"start":{"line":1,"column":16},"end":{"line":1,"column":34}},"1":{"start":{"line":3,"column":15},"end":{"line":3,"column":31}},"2":{"start":{"line":4,"column":20},"end":{"line":4,"column":55}},"3":{"start":{"line":5,"column":20},"end":{"line":5,"column":55}},"4":{"start":{"line":6,"column":23},"end":{"line":6,"column":42}},"5":{"start":{"line":7,"column":54},"end":{"line":7,"column":92}},"6":{"start":{"line":9,"column":66},"end":{"line":9,"column":86}},"7":{"start":{"line":11,"column":15},"end":{"line":11,"column":47}},"8":{"start":{"line":13,"column":0},"end":{"line":13,"column":27}},"9":{"start":{"line":16,"column":0},"end":{"line":40,"column":3}},"10":{"start":{"line":17,"column":2},"end":{"line":17,"column":40}},"11":{"start":{"line":18,"column":24},"end":{"line":18,"column":59}},"12":{"start":{"line":19,"column":22},"end":{"line":19,"column":55}},"13":{"start":{"line":21,"column":2},"end":{"line":39,"column":3}},"14":{"start":{"line":24,"column":4},"end":{"line":29,"column":81}},"15":{"start":{"line":25,"column":6},"end":{"line":25,"column":65}},"16":{"start":{"line":26,"column":11},"end":{"line":29,"column":81}},"17":{"start":{"line":27,"column":6},"end":{"line":27,"column":62}},"18":{"start":{"line":29,"column":9},"end":{"line":29,"column":81}},"19":{"start":{"line":31,"column":4},"end":{"line":31,"column":95}},"20":{"start":{"line":32,"column":4},"end":{"line":32,"column":41}},"21":{"start":{"line":35,"column":4},"end":{"line":37,"column":5}},"22":{"start":{"line":36,"column":6},"end":{"line":36,"column":64}},"23":{"start":{"line":38,"column":4},"end":{"line":38,"column":63}},"24":{"start":{"line":43,"column":0},"end":{"line":51,"column":3}},"25":{"start":{"line":44,"column":2},"end":{"line":50,"column":3}},"26":{"start":{"line":45,"column":18},"end":{"line":45,"column":49}},"27":{"start":{"line":46,"column":4},"end":{"line":46,"column":24}},"28":{"start":{"line":48,"column":4},"end":{"line":48,"column":22}},"29":{"start":{"line":49,"column":4},"end":{"line":49,"column":26}},"30":{"start":{"line":54,"column":0},"end":{"line":77,"column":3}},"31":{"start":{"line":55,"column":44},"end":{"line":55,"column":52}},"32":{"start":{"line":57,"column":2},"end":{"line":76,"column":3}},"33":{"start":{"line":60,"column":17},"end":{"line":60,"column":85}},"34":{"start":{"line":61,"column":4},"end":{"line":61,"column":42}},"35":{"start":{"line":63,"column":4},"end":{"line":63,"column":22}},"36":{"start":{"line":64,"column":26},"end":{"line":64,"column":80}},"37":{"start":{"line":65,"column":4},"end":{"line":74,"column":5}},"38":{"start":{"line":66,"column":6},"end":{"line":70,"column":8}},"39":{"start":{"line":71,"column":11},"end":{"line":74,"column":5}},"40":{"start":{"line":72,"column":6},"end":{"line":73,"column":56}},"41":{"start":{"line":75,"column":4},"end":{"line":75,"column":26}},"42":{"start":{"line":80,"column":0},"end":{"line":95,"column":3}},"43":{"start":{"line":81,"column":17},"end":{"line":81,"column":27}},"44":{"start":{"line":82,"column":2},"end":{"line":84,"column":3}},"45":{"start":{"line":83,"column":4},"end":{"line":83,"column":66}},"46":{"start":{"line":85,"column":2},"end":{"line":94,"column":3}},"47":{"start":{"line":86,"column":21},"end":{"line":86,"column":54}},"48":{"start":{"line":87,"column":4},"end":{"line":89,"column":5}},"49":{"start":{"line":88,"column":6},"end":{"line":88,"column":58}},"50":{"start":{"line":90,"column":4},"end":{"line":90,"column":23}},"51":{"start":{"line":92,"column":4},"end":{"line":92,"column":22}},"52":{"start":{"line":93,"column":4},"end":{"line":93,"column":26}},"53":{"start":{"line":98,"column":0},"end":{"line":114,"column":3}},"54":{"start":{"line":99,"column":17},"end":{"line":99,"column":27}},"55":{"start":{"line":100,"column":37},"end":{"line":100,"column":45}},"56":{"start":{"line":101,"column":2},"end":{"line":103,"column":3}},"57":{"start":{"line":102,"column":4},"end":{"line":102,"column":66}},"58":{"start":{"line":104,"column":2},"end":{"line":113,"column":3}},"59":{"start":{"line":105,"column":24},"end":{"line":105,"column":112}},"60":{"start":{"line":106,"column":4},"end":{"line":108,"column":5}},"61":{"start":{"line":107,"column":6},"end":{"line":107,"column":58}},"62":{"start":{"line":109,"column":4},"end":{"line":109,"column":38}},"63":{"start":{"line":111,"column":4},"end":{"line":111,"column":22}},"64":{"start":{"line":112,"column":4},"end":{"line":112,"column":26}},"65":{"start":{"line":117,"column":0},"end":{"line":132,"column":3}},"66":{"start":{"line":118,"column":17},"end":{"line":118,"column":27}},"67":{"start":{"line":119,"column":2},"end":{"line":121,"column":3}},"68":{"start":{"line":120,"column":4},"end":{"line":120,"column":66}},"69":{"start":{"line":122,"column":2},"end":{"line":131,"column":3}},"70":{"start":{"line":123,"column":19},"end":{"line":123,"column":55}},"71":{"start":{"line":124,"column":4},"end":{"line":126,"column":5}},"72":{"start":{"line":125,"column":6},"end":{"line":125,"column":59}},"73":{"start":{"line":127,"column":4},"end":{"line":127,"column":42}},"74":{"start":{"line":129,"column":4},"end":{"line":129,"column":22}},"75":{"start":{"line":130,"column":4},"end":{"line":130,"column":26}},"76":{"start":{"line":134,"column":0},"end":{"line":134,"column":24}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":16,"column":28},"end":{"line":16,"column":29}},"loc":{"start":{"line":16,"column":48},"end":{"line":40,"column":1}},"line":16},"1":{"name":"(anonymous_1)","decl":{"start":{"line":43,"column":52},"end":{"line":43,"column":53}},"loc":{"start":{"line":43,"column":72},"end":{"line":51,"column":1}},"line":43},"2":{"name":"(anonymous_2)","decl":{"start":{"line":54,"column":22},"end":{"line":54,"column":23}},"loc":{"start":{"line":54,"column":42},"end":{"line":77,"column":1}},"line":54},"3":{"name":"(anonymous_3)","decl":{"start":{"line":80,"column":56},"end":{"line":80,"column":57}},"loc":{"start":{"line":80,"column":76},"end":{"line":95,"column":1}},"line":80},"4":{"name":"(anonymous_4)","decl":{"start":{"line":98,"column":63},"end":{"line":98,"column":64}},"loc":{"start":{"line":98,"column":83},"end":{"line":114,"column":1}},"line":98},"5":{"name":"(anonymous_5)","decl":{"start":{"line":117,"column":66},"end":{"line":117,"column":67}},"loc":{"start":{"line":117,"column":86},"end":{"line":132,"column":1}},"line":117}},"branchMap":{"0":{"loc":{"start":{"line":18,"column":24},"end":{"line":18,"column":59}},"type":"binary-expr","locations":[{"start":{"line":18,"column":24},"end":{"line":18,"column":51}},{"start":{"line":18,"column":55},"end":{"line":18,"column":59}}],"line":18},"1":{"loc":{"start":{"line":19,"column":22},"end":{"line":19,"column":55}},"type":"binary-expr","locations":[{"start":{"line":19,"column":22},"end":{"line":19,"column":47}},{"start":{"line":19,"column":51},"end":{"line":19,"column":55}}],"line":19},"2":{"loc":{"start":{"line":24,"column":4},"end":{"line":29,"column":81}},"type":"if","locations":[{"start":{"line":24,"column":4},"end":{"line":29,"column":81}},{"start":{"line":26,"column":11},"end":{"line":29,"column":81}}],"line":24},"3":{"loc":{"start":{"line":26,"column":11},"end":{"line":29,"column":81}},"type":"if","locations":[{"start":{"line":26,"column":11},"end":{"line":29,"column":81}},{"start":{"line":29,"column":9},"end":{"line":29,"column":81}}],"line":26},"4":{"loc":{"start":{"line":35,"column":4},"end":{"line":37,"column":5}},"type":"if","locations":[{"start":{"line":35,"column":4},"end":{"line":37,"column":5}},{"start":{},"end":{}}],"line":35},"5":{"loc":{"start":{"line":65,"column":4},"end":{"line":74,"column":5}},"type":"if","locations":[{"start":{"line":65,"column":4},"end":{"line":74,"column":5}},{"start":{"line":71,"column":11},"end":{"line":74,"column":5}}],"line":65},"6":{"loc":{"start":{"line":71,"column":11},"end":{"line":74,"column":5}},"type":"if","locations":[{"start":{"line":71,"column":11},"end":{"line":74,"column":5}},{"start":{},"end":{}}],"line":71},"7":{"loc":{"start":{"line":82,"column":2},"end":{"line":84,"column":3}},"type":"if","locations":[{"start":{"line":82,"column":2},"end":{"line":84,"column":3}},{"start":{},"end":{}}],"line":82},"8":{"loc":{"start":{"line":87,"column":4},"end":{"line":89,"column":5}},"type":"if","locations":[{"start":{"line":87,"column":4},"end":{"line":89,"column":5}},{"start":{},"end":{}}],"line":87},"9":{"loc":{"start":{"line":101,"column":2},"end":{"line":103,"column":3}},"type":"if","locations":[{"start":{"line":101,"column":2},"end":{"line":103,"column":3}},{"start":{},"end":{}}],"line":101},"10":{"loc":{"start":{"line":106,"column":4},"end":{"line":108,"column":5}},"type":"if","locations":[{"start":{"line":106,"column":4},"end":{"line":108,"column":5}},{"start":{},"end":{}}],"line":106},"11":{"loc":{"start":{"line":119,"column":2},"end":{"line":121,"column":3}},"type":"if","locations":[{"start":{"line":119,"column":2},"end":{"line":121,"column":3}},{"start":{},"end":{}}],"line":119},"12":{"loc":{"start":{"line":124,"column":4},"end":{"line":126,"column":5}},"type":"if","locations":[{"start":{"line":124,"column":4},"end":{"line":126,"column":5}},{"start":{},"end":{}}],"line":124}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":1,"25":0,"26":0,"27":0,"28":0,"29":0,"30":1,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":1,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":1,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":1,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":1},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"d575c6d75ca35ae6d0aeb047a5c799340f2c0fd9"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/applications_service.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/applications_service.js","statementMap":{"0":{"start":{"line":1,"column":13},"end":{"line":1,"column":63}},"1":{"start":{"line":4,"column":2},"end":{"line":4,"column":41}},"2":{"start":{"line":8,"column":2},"end":{"line":8,"column":89}},"3":{"start":{"line":12,"column":2},"end":{"line":12,"column":87}},"4":{"start":{"line":16,"column":2},"end":{"line":16,"column":59}},"5":{"start":{"line":20,"column":2},"end":{"line":20,"column":53}},"6":{"start":{"line":23,"column":0},"end":{"line":23,"column":130}}},"fnMap":{"0":{"name":"getAllApplications","decl":{"start":{"line":3,"column":15},"end":{"line":3,"column":33}},"loc":{"start":{"line":3,"column":36},"end":{"line":5,"column":1}},"line":3},"1":{"name":"createNewApplication","decl":{"start":{"line":7,"column":15},"end":{"line":7,"column":35}},"loc":{"start":{"line":7,"column":84},"end":{"line":9,"column":1}},"line":7},"2":{"name":"updateApplicationStatus","decl":{"start":{"line":11,"column":15},"end":{"line":11,"column":38}},"loc":{"start":{"line":11,"column":84},"end":{"line":13,"column":1}},"line":11},"3":{"name":"getApplicationByEmail","decl":{"start":{"line":15,"column":15},"end":{"line":15,"column":36}},"loc":{"start":{"line":15,"column":44},"end":{"line":17,"column":1}},"line":15},"4":{"name":"getApplicationById","decl":{"start":{"line":19,"column":15},"end":{"line":19,"column":33}},"loc":{"start":{"line":19,"column":38},"end":{"line":21,"column":1}},"line":19}},"branchMap":{},"s":{"0":1,"1":0,"2":0,"3":0,"4":0,"5":0,"6":1},"f":{"0":0,"1":0,"2":0,"3":0,"4":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"d60ce397ae75dfec103613819ef03713325e0118"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/auth_service.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/auth_service.js","statementMap":{"0":{"start":{"line":1,"column":14},"end":{"line":1,"column":39}},"1":{"start":{"line":2,"column":14},"end":{"line":2,"column":30}},"2":{"start":{"line":3,"column":20},"end":{"line":3,"column":42}},"3":{"start":{"line":4,"column":20},"end":{"line":4,"column":55}},"4":{"start":{"line":5,"column":20},"end":{"line":5,"column":63}},"5":{"start":{"line":7,"column":22},"end":{"line":7,"column":45}},"6":{"start":{"line":9,"column":15},"end":{"line":9,"column":47}},"7":{"start":{"line":12,"column":2},"end":{"line":43,"column":3}},"8":{"start":{"line":13,"column":25},"end":{"line":13,"column":72}},"9":{"start":{"line":14,"column":18},"end":{"line":14,"column":36}},"10":{"start":{"line":15,"column":23},"end":{"line":15,"column":39}},"11":{"start":{"line":17,"column":4},"end":{"line":19,"column":5}},"12":{"start":{"line":18,"column":6},"end":{"line":18,"column":59}},"13":{"start":{"line":21,"column":24},"end":{"line":21,"column":70}},"14":{"start":{"line":22,"column":4},"end":{"line":24,"column":5}},"15":{"start":{"line":23,"column":6},"end":{"line":23,"column":86}},"16":{"start":{"line":26,"column":15},"end":{"line":26,"column":54}},"17":{"start":{"line":27,"column":4},"end":{"line":35,"column":5}},"18":{"start":{"line":28,"column":6},"end":{"line":34,"column":8}},"19":{"start":{"line":37,"column":4},"end":{"line":37,"column":75}},"20":{"start":{"line":39,"column":4},"end":{"line":39,"column":16}},"21":{"start":{"line":41,"column":4},"end":{"line":41,"column":81}},"22":{"start":{"line":42,"column":4},"end":{"line":42,"column":51}},"23":{"start":{"line":49,"column":2},"end":{"line":82,"column":3}},"24":{"start":{"line":51,"column":26},"end":{"line":63,"column":8}},"25":{"start":{"line":65,"column":25},"end":{"line":69,"column":6}},"26":{"start":{"line":71,"column":24},"end":{"line":71,"column":41}},"27":{"start":{"line":72,"column":17},"end":{"line":72,"column":68}},"28":{"start":{"line":74,"column":4},"end":{"line":76,"column":5}},"29":{"start":{"line":75,"column":6},"end":{"line":75,"column":55}},"30":{"start":{"line":78,"column":4},"end":{"line":78,"column":20}},"31":{"start":{"line":80,"column":4},"end":{"line":80,"column":78}},"32":{"start":{"line":81,"column":4},"end":{"line":81,"column":51}},"33":{"start":{"line":86,"column":0},"end":{"line":86,"column":58}}},"fnMap":{"0":{"name":"handleFirebaseAuth","decl":{"start":{"line":11,"column":15},"end":{"line":11,"column":33}},"loc":{"start":{"line":11,"column":49},"end":{"line":44,"column":1}},"line":11},"1":{"name":"handleDiscordAuth","decl":{"start":{"line":48,"column":15},"end":{"line":48,"column":32}},"loc":{"start":{"line":48,"column":38},"end":{"line":83,"column":1}},"line":48}},"branchMap":{"0":{"loc":{"start":{"line":17,"column":4},"end":{"line":19,"column":5}},"type":"if","locations":[{"start":{"line":17,"column":4},"end":{"line":19,"column":5}},{"start":{},"end":{}}],"line":17},"1":{"loc":{"start":{"line":22,"column":4},"end":{"line":24,"column":5}},"type":"if","locations":[{"start":{"line":22,"column":4},"end":{"line":24,"column":5}},{"start":{},"end":{}}],"line":22},"2":{"loc":{"start":{"line":22,"column":8},"end":{"line":22,"column":57}},"type":"binary-expr","locations":[{"start":{"line":22,"column":8},"end":{"line":22,"column":20}},{"start":{"line":22,"column":24},"end":{"line":22,"column":57}}],"line":22},"3":{"loc":{"start":{"line":27,"column":4},"end":{"line":35,"column":5}},"type":"if","locations":[{"start":{"line":27,"column":4},"end":{"line":35,"column":5}},{"start":{},"end":{}}],"line":27},"4":{"loc":{"start":{"line":74,"column":4},"end":{"line":76,"column":5}},"type":"if","locations":[{"start":{"line":74,"column":4},"end":{"line":76,"column":5}},{"start":{},"end":{}}],"line":74}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":1},"f":{"0":0,"1":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"c9f8ebea05957cd72bdcb4fe9cac1e4952e35c25"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/roles_service.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/roles_service.js","statementMap":{"0":{"start":{"line":1,"column":13},"end":{"line":1,"column":56}},"1":{"start":{"line":2,"column":14},"end":{"line":2,"column":33}},"2":{"start":{"line":5,"column":2},"end":{"line":10,"column":3}},"3":{"start":{"line":6,"column":19},"end":{"line":6,"column":42}},"4":{"start":{"line":7,"column":4},"end":{"line":7,"column":48}},"5":{"start":{"line":9,"column":4},"end":{"line":9,"column":80}},"6":{"start":{"line":14,"column":2},"end":{"line":14,"column":34}},"7":{"start":{"line":17,"column":0},"end":{"line":17,"column":51}}},"fnMap":{"0":{"name":"assignRoleToUser","decl":{"start":{"line":4,"column":15},"end":{"line":4,"column":31}},"loc":{"start":{"line":4,"column":50},"end":{"line":11,"column":1}},"line":4},"1":{"name":"getAllRoles","decl":{"start":{"line":13,"column":15},"end":{"line":13,"column":26}},"loc":{"start":{"line":13,"column":29},"end":{"line":15,"column":1}},"line":13}},"branchMap":{},"s":{"0":2,"1":2,"2":0,"3":0,"4":0,"5":0,"6":0,"7":2},"f":{"0":0,"1":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"045b74db2a85cdc9d05532e7171e47199ba12e54"} -,"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/user_service.js": {"path":"/Users/aliona/Desktop/workspace/Fun/CatBytes/webplatform-backend/services/user_service.js","statementMap":{"0":{"start":{"line":1,"column":13},"end":{"line":1,"column":55}},"1":{"start":{"line":2,"column":21},"end":{"line":2,"column":57}},"2":{"start":{"line":6,"column":2},"end":{"line":6,"column":34}},"3":{"start":{"line":10,"column":13},"end":{"line":11,"column":62}},"4":{"start":{"line":12,"column":2},"end":{"line":12,"column":57}},"5":{"start":{"line":13,"column":2},"end":{"line":13,"column":14}},"6":{"start":{"line":17,"column":19},"end":{"line":17,"column":49}},"7":{"start":{"line":18,"column":2},"end":{"line":21,"column":3}},"8":{"start":{"line":19,"column":18},"end":{"line":19,"column":49}},"9":{"start":{"line":20,"column":4},"end":{"line":20,"column":47}},"10":{"start":{"line":22,"column":2},"end":{"line":22,"column":18}},"11":{"start":{"line":26,"column":2},"end":{"line":26,"column":48}},"12":{"start":{"line":30,"column":2},"end":{"line":30,"column":39}},"13":{"start":{"line":34,"column":2},"end":{"line":34,"column":63}},"14":{"start":{"line":38,"column":2},"end":{"line":38,"column":52}},"15":{"start":{"line":41,"column":0},"end":{"line":42,"column":40}}},"fnMap":{"0":{"name":"getAllUsers","decl":{"start":{"line":4,"column":15},"end":{"line":4,"column":26}},"loc":{"start":{"line":4,"column":29},"end":{"line":7,"column":1}},"line":4},"1":{"name":"createNewMemberUser","decl":{"start":{"line":9,"column":15},"end":{"line":9,"column":34}},"loc":{"start":{"line":9,"column":83},"end":{"line":14,"column":1}},"line":9},"2":{"name":"getUserById","decl":{"start":{"line":16,"column":15},"end":{"line":16,"column":26}},"loc":{"start":{"line":16,"column":31},"end":{"line":23,"column":1}},"line":16},"3":{"name":"updateUserById","decl":{"start":{"line":25,"column":15},"end":{"line":25,"column":29}},"loc":{"start":{"line":25,"column":43},"end":{"line":27,"column":1}},"line":25},"4":{"name":"deleteUserById","decl":{"start":{"line":29,"column":15},"end":{"line":29,"column":29}},"loc":{"start":{"line":29,"column":34},"end":{"line":31,"column":1}},"line":29},"5":{"name":"getUserByFirebaseId","decl":{"start":{"line":33,"column":15},"end":{"line":33,"column":34}},"loc":{"start":{"line":33,"column":47},"end":{"line":35,"column":1}},"line":33},"6":{"name":"getUserByEmail","decl":{"start":{"line":37,"column":15},"end":{"line":37,"column":29}},"loc":{"start":{"line":37,"column":37},"end":{"line":39,"column":1}},"line":37}},"branchMap":{"0":{"loc":{"start":{"line":18,"column":2},"end":{"line":21,"column":3}},"type":"if","locations":[{"start":{"line":18,"column":2},"end":{"line":21,"column":3}},{"start":{},"end":{}}],"line":18},"1":{"loc":{"start":{"line":20,"column":21},"end":{"line":20,"column":46}},"type":"cond-expr","locations":[{"start":{"line":20,"column":36},"end":{"line":20,"column":41}},{"start":{"line":20,"column":44},"end":{"line":20,"column":46}}],"line":20}},"s":{"0":2,"1":2,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":2},"f":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0},"b":{"0":[0,0],"1":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"01f8e055ec5113921e4a49bd541a95106cdada12"} -} diff --git a/coverage/lcov-report/base.css b/coverage/lcov-report/base.css deleted file mode 100644 index f418035..0000000 --- a/coverage/lcov-report/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/coverage/lcov-report/block-navigation.js b/coverage/lcov-report/block-navigation.js deleted file mode 100644 index cc12130..0000000 --- a/coverage/lcov-report/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selecter that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/coverage/lcov-report/favicon.png b/coverage/lcov-report/favicon.png deleted file mode 100644 index c1525b811a167671e9de1fa78aab9f5c0b61cef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
- 27.93% - Statements - 100/358 -
- - -
- 4.08% - Branches - 4/98 -
- - -
- 10.71% - Functions - 6/56 -
- - -
- 27.73% - Lines - 99/357 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
webplatform-backend -
-
38.94%37/9510.71%3/2820%2/1038.29%36/94
webplatform-backend/aws -
-
15.78%3/190%0/40%0/315.78%3/19
webplatform-backend/middleware -
-
34.54%19/555%1/2066.66%4/634.54%19/55
webplatform-backend/repositories -
-
19.14%9/470%0/60%0/1519.14%9/47
webplatform-backend/routes -
-
20.77%16/770%0/260%0/620.77%16/77
webplatform-backend/services -
-
24.61%16/650%0/140%0/1624.61%16/65
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/prettify.css b/coverage/lcov-report/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/coverage/lcov-report/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/lcov-report/prettify.js b/coverage/lcov-report/prettify.js deleted file mode 100644 index b322523..0000000 --- a/coverage/lcov-report/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/lcov-report/sort-arrow-sprite.png b/coverage/lcov-report/sort-arrow-sprite.png deleted file mode 100644 index 6ed68316eb3f65dec9063332d2f69bf3093bbfab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc diff --git a/coverage/lcov-report/sorter.js b/coverage/lcov-report/sorter.js deleted file mode 100644 index 2bb296a..0000000 --- a/coverage/lcov-report/sorter.js +++ /dev/null @@ -1,196 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - if ( - row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()) - ) { - row.style.display = ''; - } else { - row.style.display = 'none'; - } - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); diff --git a/coverage/lcov-report/webplatform-backend/app.js.html b/coverage/lcov-report/webplatform-backend/app.js.html deleted file mode 100644 index 75dbfb1..0000000 --- a/coverage/lcov-report/webplatform-backend/app.js.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/app.js - - - - - - - - - -
-
-

All files / webplatform-backend app.js

-
- -
- 90% - Statements - 18/20 -
- - -
- 50% - Branches - 3/6 -
- - -
- 100% - Functions - 1/1 -
- - -
- 90% - Lines - 18/20 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -351x -1x -1x -1x -1x -1x -1x -  -1x -  -1x -  -  -1x -  -  -  -1x -1x -1x -  -1x -1x -  -  -  -  -  -  -  -  -1x -1x -  -1x
const config = require('config');
-const express = require("express");
-const cookieParser = require('cookie-parser');
-const cors = require("cors");
-const { authenticate } = require("./middleware/authentication");
-const pinoHttp = require('pino-http');
-const { baseLogger } = require('./logger');
- 
-require('dotenv').config({ path: '.env.local' });
- 
-const app = express();
- 
-// Middleware
-Iif (process.env.LOGGING_HTTP_REQUESTS === 'true') {
-  app.use(pinoHttp({ logger: baseLogger }));
-}
- 
-app.use(express.json());
-app.use(cookieParser());
-app.use(cors({
-  origin: (origin, callback) => {
-    if (!origin || config.cors.allowedOrigins.includes(origin)) {
-      callback(null, origin);
-    } else E{
-      callback(new Error('Not allowed by CORS'));
-    }
-  },
-  credentials: true,
-  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
-  allowedHeaders: ['Content-Type', 'Authorization', 'token'], 
-}));
-app.options('*', cors()); 
-app.use(authenticate());
- 
-module.exports = app;
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/aws/index.html b/coverage/lcov-report/webplatform-backend/aws/index.html deleted file mode 100644 index d6bd954..0000000 --- a/coverage/lcov-report/webplatform-backend/aws/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/aws - - - - - - - - - -
-
-

All files webplatform-backend/aws

-
- -
- 15.78% - Statements - 3/19 -
- - -
- 0% - Branches - 0/4 -
- - -
- 0% - Functions - 0/3 -
- - -
- 15.78% - Lines - 3/19 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
ssm-helper.js -
-
15.78%3/190%0/40%0/315.78%3/19
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/aws/ssm-helper.js.html b/coverage/lcov-report/webplatform-backend/aws/ssm-helper.js.html deleted file mode 100644 index dce333a..0000000 --- a/coverage/lcov-report/webplatform-backend/aws/ssm-helper.js.html +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/aws/ssm-helper.js - - - - - - - - - -
-
-

All files / webplatform-backend/aws ssm-helper.js

-
- -
- 15.78% - Statements - 3/19 -
- - -
- 0% - Branches - 0/4 -
- - -
- 0% - Functions - 0/3 -
- - -
- 15.78% - Lines - 3/19 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -412x -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x - 
const { SSMClient, GetParametersCommand } = require('@aws-sdk/client-ssm');
-const logger = require('../logger')(__filename);
- 
-async function loadSecrets(region, names, withDecryption = false, parseJson = false) {
-  const ssmClient = new SSMClient({
-    region: region,
-  });      
- 
-  try {
-    logger.debug(`Fetching secrets from region: ${region}`);
-    const command = new GetParametersCommand({
-      Names: names,
-      WithDecryption: withDecryption,
-    });
- 
-    const result = await ssmClient.send(command);
- 
-    const secrets = result.Parameters.reduce((acc, { Name, Value }) => {
-      const key = Name.split('/').pop();
-      acc[key] = parseJson ? parseJsonValue(Value) : Value;
-      return acc;
-    }, {});
- 
-    logger.debug(Object.keys(secrets), 'Fetched secrets');
-    return secrets;
-  } catch (err) {
-    logger.error(err, 'Error fetching parameters');
-    throw err;
-  }
-}
- 
-function parseJsonValue(value) {
-  try {
-    return JSON.parse(value);
-  } catch {
-    return value;
-  }
-}
- 
-module.exports = { loadSecrets }; 
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/db.js.html b/coverage/lcov-report/webplatform-backend/db.js.html deleted file mode 100644 index d559f9a..0000000 --- a/coverage/lcov-report/webplatform-backend/db.js.html +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/db.js - - - - - - - - - -
-
-

All files / webplatform-backend db.js

-
- -
- 28% - Statements - 7/25 -
- - -
- 0% - Branches - 0/12 -
- - -
- 33.33% - Functions - 1/3 -
- - -
- 25% - Lines - 6/24 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -622x -2x -2x -  -2x -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x
const knex = require('knex');
-const { loadSecrets } = require("./aws/ssm-helper");
-const config = require('config');
- 
-require('dotenv').config({ path: '.env.local' });
- 
-let databaseUsername, databaseHost, databaseName, databasePassword, databasePort;
-let knexInstance = null;
- 
-async function getDbSettings() {
-  const isLocal = process.env.ENVIRONMENT === "local";
-  console.log("Environment: ", isLocal ? "local" : process.env.NODE_ENV ?? "development");
-  if (isLocal) {
-    databaseUsername = process.env.DB_USER;
-    databaseHost = process.env.DB_HOST || "localhost";
-    databaseName = process.env.DB_NAME;
-    databasePassword = process.env.DB_PASS;
-    databasePort = process.env.DB_PORT || 5432;
-  }
-  else {
-    // For remote RSD access, load secrets from AWS SSM
-    const awsConfig = config.aws;
-    const credentials = await loadSecrets(
-      awsConfig.param_store_region, 
-      ['/catbytes_webplatform/db_username', '/catbytes_webplatform/db_password'], 
-      true);
- 
-    databaseUsername = credentials['db_username'];
-    databaseHost = awsConfig.db_endpoint;
-    databaseName = awsConfig.db_name;
-    databasePassword = credentials['db_password'];
-    databasePort = awsConfig.databasePort;
-  }
- 
-  return {
-    databaseUsername,
-    databaseHost,
-    databaseName,
-    databasePassword,
-    databasePort,
-    ssl: isLocal ? false : { rejectUnauthorized: false }
-  };
-}
- 
-async function initDb() {
-  const settings = await getDbSettings();
-  
-  knexInstance = knex({
-    client: 'pg',
-    connection: {
-      host: settings.databaseHost,
-      user: settings.databaseUsername,
-      password: settings.databasePassword,
-      database: settings.databaseName,
-      port: settings.databasePort,
-      connectionTimeoutMillis: 5000,
-      ssl: settings.ssl
-    },   
-  });
-}
- 
-module.exports = { getDbSettings, initDb, getKnex: () => knexInstance };
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/index.html b/coverage/lcov-report/webplatform-backend/index.html deleted file mode 100644 index 8ebf0f5..0000000 --- a/coverage/lcov-report/webplatform-backend/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for webplatform-backend - - - - - - - - - -
-
-

All files webplatform-backend

-
- -
- 38.94% - Statements - 37/95 -
- - -
- 10.71% - Branches - 3/28 -
- - -
- 20% - Functions - 2/10 -
- - -
- 38.29% - Lines - 36/94 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
app.js -
-
90%18/2050%3/6100%1/190%18/20
db.js -
-
28%7/250%0/1233.33%1/325%6/24
oauth.js -
-
25%4/160%0/20%0/125%4/16
utils.js -
-
23.52%8/340%0/80%0/523.52%8/34
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/middleware/authentication.js.html b/coverage/lcov-report/webplatform-backend/middleware/authentication.js.html deleted file mode 100644 index f3dcae9..0000000 --- a/coverage/lcov-report/webplatform-backend/middleware/authentication.js.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/middleware/authentication.js - - - - - - - - - -
-
-

All files / webplatform-backend/middleware authentication.js

-
- -
- 57.89% - Statements - 11/19 -
- - -
- 25% - Branches - 1/4 -
- - -
- 100% - Functions - 2/2 -
- - -
- 57.89% - Lines - 11/19 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -311x -1x -1x -  -  -1x -1x -1x -1x -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -1x -  -  -  -  -1x
const userService = require('../services/user_service');
-const { respondWithError } = require('../routes/helpers');
-const logger = require('../logger')(__filename);
- 
-function authenticate() {
-  return async (req, res, next) => {
-    try {
-      const userFirebaseId = req.cookies?.userUID;
-      Eif (!userFirebaseId) {
-        logger.warn(`No user UID found in request cookies at ${req.path}`);
-        return next();
-      }
- 
-      const user = await userService.getUserByFirebaseId(userFirebaseId);
-      if (!user) {
-        logger.warn('Found user UID in request cookies, but no corresponding user found in database');
-        return respondWithError(res, 401, "User with provided UID not found");
-      }
- 
-      req.userId = user.id;
-      req.userEmail = user.email;
- 
-      next();
-    } catch (err) {
-      logger.error(err, 'Unexpected error while autenthicating request');
-      return respondWithError(res);
-    }
-  };
-}
- 
-module.exports = { authenticate };
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/middleware/authorization.js.html b/coverage/lcov-report/webplatform-backend/middleware/authorization.js.html deleted file mode 100644 index 5e2c654..0000000 --- a/coverage/lcov-report/webplatform-backend/middleware/authorization.js.html +++ /dev/null @@ -1,283 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/middleware/authorization.js - - - - - - - - - -
-
-

All files / webplatform-backend/middleware authorization.js

-
- -
- 22.22% - Statements - 8/36 -
- - -
- 0% - Branches - 0/16 -
- - -
- 50% - Functions - 2/4 -
- - -
- 22.22% - Lines - 8/36 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -671x -1x -1x -1x -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x
const utils = require('../utils');
-const repo = require('../repositories/authorization_repository');
-const { respondWithError } = require('../routes/helpers');
-const logger = require('../logger')(__filename);
- 
-function verifyRole(roleName) {
-  return async (req, res, next) => {
-    try {
-      const userId = req.userId;
-      if (!userId) {
-        return respondWithError(res, 401, "User not authenticated");
-      }
- 
-      const roleId = utils.getRole(roleName);
-      const userRole = await repo.verifyRole(userId, roleId);
-      if (userRole.length === 0){
-        return respondWithError(res, 403, "You're not allowed to access this resource");
-      }
-      next();
-    } catch (err) {
-      logger.error(err, 'Error verifying role');
-      return respondWithError(res);
-    }
-  };
-}
- 
-const OWNED_ENTITIES = {
-  USER: 'users',
-  MENTOR: 'mentors'
-};
- 
-function verifyOwnership(entityTable) {
-  return async (req, res, next) => {
-    logger.debug(`Middleware invoked for: ${req.url}`);
-    try {
-      const resourceId = req.params.id;
-      const userId = req.userId;
-      if (!userId) {
-        return respondWithError(res, 401, "User not authenticated");
-      }
-    
-      if (!resourceId || !userId) {
-        return respondWithError(res, 400, `Invalid request: missing ${entityTable} ID or user information`);
-      }
-            
-      let resource;
-      if (entityTable === OWNED_ENTITIES.USER) {
-        resource = userId.toString() === resourceId.toString();
-      } else {
-        const result = await repo.verifyOwnership(entityTable, resourceId, userId);
-        resource = result.rows && result.length !== 0;
-      }
-    
-      if (!resource) {
-        return respondWithError(res, 403, "You're not allowed to edit this resource");
-      }
-    
-      next();
-    } catch (err) {
-      logger.error(err, 'Error verifying ownership');
-      return respondWithError(res);
-    }
-  };
-}
- 
- 
-module.exports = { verifyRole, verifyOwnership, OWNED_ENTITIES };
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/middleware/index.html b/coverage/lcov-report/webplatform-backend/middleware/index.html deleted file mode 100644 index e039ef4..0000000 --- a/coverage/lcov-report/webplatform-backend/middleware/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/middleware - - - - - - - - - -
-
-

All files webplatform-backend/middleware

-
- -
- 34.54% - Statements - 19/55 -
- - -
- 5% - Branches - 1/20 -
- - -
- 66.66% - Functions - 4/6 -
- - -
- 34.54% - Lines - 19/55 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
authentication.js -
-
57.89%11/1925%1/4100%2/257.89%11/19
authorization.js -
-
22.22%8/360%0/1650%2/422.22%8/36
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/oauth.js.html b/coverage/lcov-report/webplatform-backend/oauth.js.html deleted file mode 100644 index 58d9958..0000000 --- a/coverage/lcov-report/webplatform-backend/oauth.js.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/oauth.js - - - - - - - - - -
-
-

All files / webplatform-backend oauth.js

-
- -
- 25% - Statements - 4/16 -
- - -
- 0% - Branches - 0/2 -
- - -
- 0% - Functions - 0/1 -
- - -
- 25% - Lines - 4/16 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -301x -1x -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x - 
const { loadSecrets } = require("./aws/ssm-helper");
-const config = require('config');
- 
-const discordAuth = {
-  discordClientId: null,
-  discordClientSecret: null,
-  discordRedirectUrl: null,
-};
- 
-async function initOAuth() {
-  let discordClientId, discordClientSecret, discordRedirectUrl = null;
- 
-  if (process.env.ENVIRONMENT === "local") {
-    discordClientId = process.env.DISCORD_CLIENT_ID;
-    discordClientSecret = process.env.DISCORD_CLIENT_SECRET;
-    discordRedirectUrl = process.env.DISCORD_REDIRECT_URI;
-  } else {
-    const params = await loadSecrets(config.aws.param_store_region, ['/catbytes_webplatform/oauth_creds'], true, true);
-    discordClientId = params['oauth_creds']['discord']['clientID'];
-    discordClientSecret = params['oauth_creds']['discord']['clientSecret'];
-    discordRedirectUrl = `${config.platform_url}auth/discord/callback`;
-  }
- 
-  discordAuth.discordClientId = discordClientId;
-  discordAuth.discordClientSecret = discordClientSecret;
-  discordAuth.discordRedirectUrl = discordRedirectUrl;
-}
- 
-module.exports = { discordAuth, initOAuth };
-  
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/repositories/applications_repository.js.html b/coverage/lcov-report/webplatform-backend/repositories/applications_repository.js.html deleted file mode 100644 index 33c36a5..0000000 --- a/coverage/lcov-report/webplatform-backend/repositories/applications_repository.js.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/repositories/applications_repository.js - - - - - - - - - -
-
-

All files / webplatform-backend/repositories applications_repository.js

-
- -
- 37.5% - Statements - 3/8 -
- - -
- 100% - Branches - 0/0 -
- - -
- 0% - Functions - 0/4 -
- - -
- 37.5% - Lines - 3/8 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -291x -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x
const { getKnex } = require("../db");
- 
-const knex = getKnex();
- 
-async function getAllApplications() {
-  return await knex("applications").select("*"); 
-}
- 
-async function createNewApplication(name, about, email, videoLink, discordNickname) {
-  return await knex('applications')
-    .insert({ name: name, about: about, email: email, video_link: videoLink, discord_nickname: discordNickname })
-    .returning('*');
-}
- 
-async function updateApplicationById(id, status, comment, modifiedBy, modifiedAt) {
-  const [application] = await knex('applications')
-    .where({ id })
-    .update({ status: status, comment: comment, modified_by: modifiedBy, modified_at: modifiedAt })
-    .returning('*');
-  return application;
-}
- 
-async function getApplicationByFields(fields) {
-  return await knex('applications')
-    .where(fields)
-    .first();
-}
- 
-module.exports = { getAllApplications, createNewApplication, updateApplicationById, getApplicationByFields };
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/repositories/authorization_repository.js.html b/coverage/lcov-report/webplatform-backend/repositories/authorization_repository.js.html deleted file mode 100644 index e705998..0000000 --- a/coverage/lcov-report/webplatform-backend/repositories/authorization_repository.js.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/repositories/authorization_repository.js - - - - - - - - - -
-
-

All files / webplatform-backend/repositories authorization_repository.js

-
- -
- 33.33% - Statements - 2/6 -
- - -
- 100% - Branches - 0/0 -
- - -
- 0% - Functions - 0/2 -
- - -
- 33.33% - Lines - 2/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -131x -  -  -  -  -  -  -  -  -  -  -  -1x
const { getKnex } = require("../db");
- 
-async function verifyRole(userId, roleId) {
-  const knex = getKnex();
-  return await knex("user_roles").where("user_id", userId).andWhere("role_id", roleId).select("*");
-}
- 
-async function verifyOwnership(entityTable, resourceId, userId) {
-  const knex = getKnex();
-  return await knex(entityTable).where("id", resourceId).andWhere("created_by", userId).select("*");
-}
- 
-module.exports = { verifyRole, verifyOwnership };
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/repositories/index.html b/coverage/lcov-report/webplatform-backend/repositories/index.html deleted file mode 100644 index 3843c97..0000000 --- a/coverage/lcov-report/webplatform-backend/repositories/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/repositories - - - - - - - - - -
-
-

All files webplatform-backend/repositories

-
- -
- 19.14% - Statements - 9/47 -
- - -
- 0% - Branches - 0/6 -
- - -
- 0% - Functions - 0/15 -
- - -
- 19.14% - Lines - 9/47 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
applications_repository.js -
-
37.5%3/8100%0/00%0/437.5%3/8
authorization_repository.js -
-
33.33%2/6100%0/00%0/233.33%2/6
roles_repository.js -
-
33.33%2/6100%0/00%0/233.33%2/6
user_repository.js -
-
7.4%2/270%0/60%0/77.4%2/27
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/repositories/roles_repository.js.html b/coverage/lcov-report/webplatform-backend/repositories/roles_repository.js.html deleted file mode 100644 index 922baca..0000000 --- a/coverage/lcov-report/webplatform-backend/repositories/roles_repository.js.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/repositories/roles_repository.js - - - - - - - - - -
-
-

All files / webplatform-backend/repositories roles_repository.js

-
- -
- 33.33% - Statements - 2/6 -
- - -
- 100% - Branches - 0/0 -
- - -
- 0% - Functions - 0/2 -
- - -
- 33.33% - Lines - 2/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -132x -  -  -  -  -  -  -  -  -  -  -  -2x
const { getKnex } = require("../db");
- 
-async function getAllRoles() {
-  const knex = getKnex();
-  return await knex('roles').select('*');
-}
- 
-async function assignRoleToUser(userId, roleId) {
-  const knex = getKnex();
-  await knex("user_roles").insert({ role_id: roleId, user_id: userId });
-}
- 
-module.exports = { getAllRoles, assignRoleToUser };
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/repositories/user_repository.js.html b/coverage/lcov-report/webplatform-backend/repositories/user_repository.js.html deleted file mode 100644 index 4079b2d..0000000 --- a/coverage/lcov-report/webplatform-backend/repositories/user_repository.js.html +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/repositories/user_repository.js - - - - - - - - - -
-
-

All files / webplatform-backend/repositories user_repository.js

-
- -
- 7.4% - Statements - 2/27 -
- - -
- 0% - Branches - 0/6 -
- - -
- 0% - Functions - 0/7 -
- - -
- 7.4% - Lines - 2/27 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -622x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  - 
const { getKnex } = require("../db");
- 
-async function getAllUsers() {
-  const knex = getKnex();
-  return await knex("users").select("id", "name", "languages");
-}
- 
-async function createNewUser(values) {
-  const knex = getKnex();
-  const [user] = await knex("users")
-    .insert(values)
-    .returning("*");
-  delete user["firebase_id"];
-  return user;
-}
- 
-async function getUserInfoById(id) {
-  const knex = getKnex();
-  const user = await knex("users").where("id", id).first();
-  if (user !== undefined){
-    delete user["firebase_id"];
-  }
-  return user;
-}
- 
-async function getUserByFields(fields) {
-  const knex = getKnex();
-  const user = await knex("users").where(fields).first();
-  if (user !== undefined){
-    delete user["firebase_id"];
-  }
-  return user;
-}
- 
-async function getUserRolesById(id) {
-  const knex = getKnex();
-  return await knex("roles")
-    .join("user_roles", "roles.id", "user_roles.role_id")
-    .where("user_roles.user_id", id)
-    .select("roles.role_name", "roles.id");
-}
- 
-async function updateUserById(id, updates) {
-  const knex = getKnex();
-  const [user] = await knex("users")
-    .where("id", id)
-    .update(updates)
-    .returning("*");
-  if (user !== undefined){
-    delete user["firebase_id"];
-  }
-  return user;
-}
- 
-async function deleteUserById(id) {
-  const knex = getKnex();
-  return await knex("users").where("id", id).del();
-}
- 
-module.exports = { getAllUsers, createNewUser, getUserInfoById, getUserRolesById, updateUserById, 
-  deleteUserById, getUserByFields };
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/routes/index.html b/coverage/lcov-report/webplatform-backend/routes/index.html deleted file mode 100644 index abb0997..0000000 --- a/coverage/lcov-report/webplatform-backend/routes/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/routes - - - - - - - - - -
-
-

All files webplatform-backend/routes

-
- -
- 20.77% - Statements - 16/77 -
- - -
- 0% - Branches - 0/26 -
- - -
- 0% - Functions - 0/6 -
- - -
- 20.77% - Lines - 16/77 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
users.js -
-
20.77%16/770%0/260%0/620.77%16/77
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/routes/users.js.html b/coverage/lcov-report/webplatform-backend/routes/users.js.html deleted file mode 100644 index 231bd4e..0000000 --- a/coverage/lcov-report/webplatform-backend/routes/users.js.html +++ /dev/null @@ -1,484 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/routes/users.js - - - - - - - - - -
-
-

All files / webplatform-backend/routes users.js

-
- -
- 20.77% - Statements - 16/77 -
- - -
- 0% - Branches - 0/26 -
- - -
- 0% - Functions - 0/6 -
- - -
- 20.77% - Lines - 16/77 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -1341x -  -1x -1x -1x -1x -1x -  -1x -  -1x -  -1x -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x
const express = require("express");
- 
-const router = express.Router();
-const userService = require("../services/user_service");
-const authService = require("../services/auth_service");
-const { ROLE_NAMES } = require("../utils");
-const {verifyOwnership, verifyRole, OWNED_ENTITIES} = require("../middleware/authorization");
-const { isValidIntegerId, respondWithError, isUniqueConstraintViolation, 
-  isNotNullConstraintViolation, parseColumnNameFromConstraint } = require("./helpers");
- 
-const logger = require('../logger')(__filename);
- 
-router.use(express.json());
- 
-// POST /users/login
-router.post("/users/login", async (req, res) => {
-  console.log('Route handler reached!');
-  const firebaseToken = req.get('X-Firebase-Token') || null;
-  const discordCode = req.get('X-Discord-Code') || null;
- 
-  try {
-    let user;
- 
-    if (firebaseToken) {
-      user = await authService.handleFirebaseAuth(firebaseToken);
-    } else if (discordCode) {
-      user = await authService.handleDiscordAuth(discordCode);
-    }
-    else return respondWithError(res, 401, 'No token provided or invalid token');
- 
-    res.cookie('userUID', user.firebaseId, { httpOnly: true, secure: true, sameSite: 'none' });
-    res.status(200).json({ user: user });
- 
-  } catch (error) {
-    if (error.status) {
-      return respondWithError(res, error.status, error.message);
-    }
-    return respondWithError(res, 500, "Authentication failed");
-  }
-});
- 
-// Get all users
-router.get("/users", verifyRole(ROLE_NAMES.member), async (req, res) => {
-  try {
-    const users = await userService.getAllUsers();
-    res.json({ users });
-  } catch (err) {
-    logger.error(err);
-    respondWithError(res);
-  }
-});
- 
-// Create a new user
-router.post("/users", async (req, res) => {
-  const { name, email, about, languages } = req.body;
-  // console.log(req.body); // Log the entire request body
-  try {
-    // todo: firebase will only know user's email, we will need to get user's application by email
-    // and populate user entity with that data here 
-    const user = await userService.createNewMemberUser(name, email, about, languages);       
-    res.status(201).json({ id: user.id });
-  } catch (err) {
-    logger.error(err);
-    const violatedValue = parseColumnNameFromConstraint(err.constraint, 'users');
-    if (isUniqueConstraintViolation(err.code)) {
-      return respondWithError(
-        res,
-        409,
-        `User with this ${violatedValue} is already registered`
-      );
-    } else if (isNotNullConstraintViolation(err.code)) {
-      return respondWithError(
-        res, 400, `Field ${violatedValue} is required`);
-    }
-    respondWithError(res);
-  }
-});
- 
-// Get user by ID
-router.get("/users/:id", verifyRole(ROLE_NAMES.member), async (req, res) => {
-  const { id } = req.params;
-  if (!isValidIntegerId(id)) {
-    return respondWithError(res, 400, "Invalid user id supplied");
-  }
-  try {
-    const userInfo = await userService.getUserById(id);
-    if (!userInfo) {
-      return respondWithError(res, 404, "User not found");
-    }
-    res.json(userInfo);
-  } catch (err) {
-    logger.error(err);
-    respondWithError(res);
-  }
-});
- 
-// Update user by ID
-router.put("/users/:id", verifyOwnership(OWNED_ENTITIES.USER), async (req, res) => {
-  const { id } = req.params;
-  const { name, about, languages } = req.body;
-  if (!isValidIntegerId(id)) {
-    return respondWithError(res, 400, "Invalid user id supplied");
-  }
-  try {
-    const updatedUser = await userService.updateUserById(id, { name: name, about: about, languages: languages });
-    if (!updatedUser) {
-      return respondWithError(res, 404, "User not found");
-    }
-    res.status(200).json(updatedUser);
-  } catch (err) {
-    logger.error(err);
-    respondWithError(res);
-  }
-});
- 
-// Delete user by ID
-router.delete("/users/:id", verifyOwnership(OWNED_ENTITIES.USER), async (req, res) => {
-  const { id } = req.params;
-  if (!isValidIntegerId(id)) {
-    return respondWithError(res, 400, "Invalid user id supplied");
-  }
-  try {
-    const result = await userService.deleteUserById(id);
-    if (result === 0) {
-      return respondWithError(res, 404, "User not found.");
-    }
-    res.status(200).json({ user_id: id });
-  } catch (err) {
-    logger.error(err);
-    respondWithError(res);
-  }
-});
- 
-module.exports = router;
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/services/applications_service.js.html b/coverage/lcov-report/webplatform-backend/services/applications_service.js.html deleted file mode 100644 index 391a6a0..0000000 --- a/coverage/lcov-report/webplatform-backend/services/applications_service.js.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/services/applications_service.js - - - - - - - - - -
-
-

All files / webplatform-backend/services applications_service.js

-
- -
- 28.57% - Statements - 2/7 -
- - -
- 100% - Branches - 0/0 -
- - -
- 0% - Functions - 0/5 -
- - -
- 28.57% - Lines - 2/7 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -231x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x
const repo = require('../repositories/applications_repository');
- 
-async function getAllApplications() {
-  return await repo.getAllApplications();
-}
- 
-async function createNewApplication(name, about, email, videoLink, discordNickname) {
-  return await repo.createNewApplication(name, about, email, videoLink, discordNickname);
-}
- 
-async function updateApplicationStatus(id, status, comment, modifiedBy, modifiedAt) {
-  return await repo.updateApplicationById(id, status, comment, modifiedBy, modifiedAt);
-}
- 
-async function getApplicationByEmail(email) {
-  return await repo.getApplicationByFields({email: email});
-}
- 
-async function getApplicationById(id) {
-  return await repo.getApplicationByFields({id: id});
-}
- 
-module.exports = { getAllApplications, createNewApplication, updateApplicationStatus, getApplicationByEmail, getApplicationById };
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/services/auth_service.js.html b/coverage/lcov-report/webplatform-backend/services/auth_service.js.html deleted file mode 100644 index a6c4a6b..0000000 --- a/coverage/lcov-report/webplatform-backend/services/auth_service.js.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/services/auth_service.js - - - - - - - - - -
-
-

All files / webplatform-backend/services auth_service.js

-
- -
- 23.52% - Statements - 8/34 -
- - -
- 0% - Branches - 0/10 -
- - -
- 0% - Functions - 0/2 -
- - -
- 23.52% - Lines - 8/34 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -861x -1x -1x -1x -1x -  -1x -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x
const admin = require('firebase-admin');
-const axios = require('axios');
-const querystring = require('querystring');
-const userService = require('../services/user_service');
-const applService = require("../services/applications_service");
- 
-const {discordAuth} = require ("../oauth.js"); 
- 
-const logger = require('../logger')(__filename);
- 
-async function handleFirebaseAuth(firebaseToken) {
-  try {
-    const decodedToken = await admin.auth().verifyIdToken(firebaseToken);
-    const email = decodedToken.email;
-    const firebaseId = decodedToken.uid;
- 
-    if (!decodedToken.email_verified) {
-      throw { status: 403, message: 'Email not verified' };
-    }
- 
-    const application = await applService.getApplicationByEmail(email);
-    if (!application || application.status !== 'approved') {
-      throw { status: 403, message: 'Application is not approved or does not exist' };
-    }
- 
-    let user = await userService.getUserByEmail(email);
-    if (!user) {
-      user = await userService.createNewMemberUser(
-        application.name,
-        email,
-        application.about,
-        application.languages,
-        application.discord_nickname
-      );
-    }
- 
-    await userService.updateUserById(user.id, { firebase_id: firebaseId });
- 
-    return user;
-  } catch (error) {
-    logger.error({ error: error.message }, "Firebase Token Verification Failed");
-    throw { status: 401, message: 'Unauthorized' };
-  }
-}
- 
-;
- 
-async function handleDiscordAuth(code){
-  try {
-    // exchange code for access token
-    const tokenResponse = await axios.post('https://discord.com/api/oauth2/token',
-      querystring.stringify({
-        client_id: discordAuth.discordClientId,
-        client_secret: discordAuth.discordClientSecret,
-        grant_type: 'authorization_code',
-        code: code,
-        redirect_uri: discordAuth.discordRedirectUrl,
-        scope: 'identify email'
-      }), {
-        headers: {
-          'Content-Type': 'application/x-www-form-urlencoded'
-        }
-      });
-    // get user data from discord
-    const userResponse = await axios.get('https://discord.com/api/users/@me', {
-      headers: {
-        Authorization: `Bearer ${tokenResponse.data.access_token}`
-      }
-    });
- 
-    const discordUser = userResponse.data;
-    const user = await userService.getUserByEmail(discordUser.email);
- 
-    if (!user) {
-      throw { status: 404, message: 'User not found' };
-    }
- 
-    return { user };
-  } catch (error) {
-    logger.error({ error: error.message }, "Discord Authentication Failed", );
-    throw { status: 401, message: 'Unauthorized' };
-  }
-}
- 
-;
-module.exports = { handleFirebaseAuth, handleDiscordAuth};
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/services/index.html b/coverage/lcov-report/webplatform-backend/services/index.html deleted file mode 100644 index 9ac9796..0000000 --- a/coverage/lcov-report/webplatform-backend/services/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/services - - - - - - - - - -
-
-

All files webplatform-backend/services

-
- -
- 24.61% - Statements - 16/65 -
- - -
- 0% - Branches - 0/14 -
- - -
- 0% - Functions - 0/16 -
- - -
- 24.61% - Lines - 16/65 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
applications_service.js -
-
28.57%2/7100%0/00%0/528.57%2/7
auth_service.js -
-
23.52%8/340%0/100%0/223.52%8/34
roles_service.js -
-
37.5%3/8100%0/00%0/237.5%3/8
user_service.js -
-
18.75%3/160%0/40%0/718.75%3/16
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/services/roles_service.js.html b/coverage/lcov-report/webplatform-backend/services/roles_service.js.html deleted file mode 100644 index 8987aee..0000000 --- a/coverage/lcov-report/webplatform-backend/services/roles_service.js.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/services/roles_service.js - - - - - - - - - -
-
-

All files / webplatform-backend/services roles_service.js

-
- -
- 37.5% - Statements - 3/8 -
- - -
- 100% - Branches - 0/0 -
- - -
- 0% - Functions - 0/2 -
- - -
- 37.5% - Lines - 3/8 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -172x -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x
const repo = require('../repositories/roles_repository');
-const utils = require('../utils');
- 
-async function assignRoleToUser(userId, roleName) {
-  try {
-    const roleId = utils.getRole(roleName);
-    await repo.assignRoleToUser(userId, roleId);
-  } catch (err) {
-    throw new Error(`Failed to assign role to user ${userId}:  ${err.message}`);
-  }
-}
- 
-async function getAllRoles() {
-  return await repo.getAllRoles();
-}
- 
-module.exports = { getAllRoles, assignRoleToUser };
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/services/user_service.js.html b/coverage/lcov-report/webplatform-backend/services/user_service.js.html deleted file mode 100644 index 13e4142..0000000 --- a/coverage/lcov-report/webplatform-backend/services/user_service.js.html +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/services/user_service.js - - - - - - - - - -
-
-

All files / webplatform-backend/services user_service.js

-
- -
- 18.75% - Statements - 3/16 -
- - -
- 0% - Branches - 0/4 -
- - -
- 0% - Functions - 0/7 -
- - -
- 18.75% - Lines - 3/16 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -422x -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x - 
const repo = require('../repositories/user_repository');
-const rolesService = require('../services/roles_service');
- 
-async function getAllUsers() {
- 
-  return await repo.getAllUsers();
-}
- 
-async function createNewMemberUser(name, email, about, languages, discordNickname) {
-  var user = await repo.createNewUser({ name: name, email: email, about: about, 
-    languages: languages, discord_nickname: discordNickname });
-  await rolesService.assignRoleToUser(user.id, 'member');
-  return user;
-}
- 
-async function getUserById(id) {
-  const userInfo = await repo.getUserInfoById(id);
-  if (userInfo) {
-    const roles = await repo.getUserRolesById(id);
-    userInfo.roles = roles.length ? roles : [];    
-  }
-  return userInfo;
-}
- 
-async function updateUserById(id, updates) {
-  return await repo.updateUserById(id, updates);   
-}
- 
-async function deleteUserById(id) {
-  return await repo.deleteUserById(id);
-}  
- 
-async function getUserByFirebaseId(firebaseId) {
-  return await repo.getUserByFields({firebase_id: firebaseId});
-}
- 
-async function getUserByEmail(email) {
-  return await repo.getUserByFields({email: email});
-}
- 
-module.exports = { getAllUsers, createNewMemberUser, getUserById, updateUserById, deleteUserById, 
-  getUserByEmail, getUserByFirebaseId };
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov-report/webplatform-backend/utils.js.html b/coverage/lcov-report/webplatform-backend/utils.js.html deleted file mode 100644 index 7343700..0000000 --- a/coverage/lcov-report/webplatform-backend/utils.js.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - Code coverage report for webplatform-backend/utils.js - - - - - - - - - -
-
-

All files / webplatform-backend utils.js

-
- -
- 23.52% - Statements - 8/34 -
- - -
- 0% - Branches - 0/8 -
- - -
- 0% - Functions - 0/5 -
- - -
- 23.52% - Lines - 8/34 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -702x -2x -2x -2x -  -2x -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -2x - 
const repo = require('./repositories/roles_repository');
-const { loadSecrets } = require("./aws/ssm-helper");
-const config = require('config');
-const logger = require('./logger')(__filename);
- 
-let rolesCache = null;
- 
-const ROLE_NAMES = {
-  mentor: 'mentor',
-  member: 'member'
-};
- 
-async function getFirebaseSdkServiceAccount() {
-  let serviceAccount = null;
-  try {
-    if (process.env.ENVIRONMENT === "local") {
-      serviceAccount = require("./serviceAccountKey.json");
-    }
-    else {
-      const awsConfig = config.aws;
-      const credentials = await loadSecrets(awsConfig.param_store_region, ['/catbytes_webplatform/fb_serviceAccountKey'], true);
-      const jsonFile = credentials['fb_serviceAccountKey'];
-      serviceAccount = JSON.parse(jsonFile);
-    }
-  }
-  catch (err) {
-    logger.error(err, "Error getting service account");
-    throw new Error("Failed to retrieve service account");
-  }
-  return serviceAccount;
-}
- 
-async function loadRolesIntoMemory() {
-  try {
-    if (!rolesCache) {
-      const roles = await repo.getAllRoles();
-      rolesCache = roles.reduce((acc, role) => {
-        acc[role.role_name] = role.id;
-        if (!isRoleExists(role.role_name)) {
-          logger.warn(`Role ${role.role_name} is in database, but is not in the ROLE_NAMES enum.`);
-        }
-        return acc;
-      }, {});
-      logger.debug(rolesCache, 'Roles loaded into memory');
-    }
-  } catch (err) {
-    logger.error(err, "Error loading roles");
-    throw new Error("Failed to initialize roles");
-  }
-}
- 
-function getRole(roleName) {
-  if (!rolesCache) {
-    throw new Error("Roles are not loaded");
-  }
-  return rolesCache[roleName];
-}
- 
-function isRoleExists(role) {
-  return Object.values(ROLE_NAMES).includes(role);
-}
- 
-const APPL_STATUSES = {
-  approved: "approved",
-  rejected: "rejected",
-  pending: "pending",
-};
- 
-module.exports = { APPL_STATUSES, ROLE_NAMES, isRoleExists, loadRolesIntoMemory, getRole, getFirebaseSdkServiceAccount };
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/lcov.info b/coverage/lcov.info deleted file mode 100644 index 94834dc..0000000 --- a/coverage/lcov.info +++ /dev/null @@ -1,711 +0,0 @@ -TN: -SF:app.js -FN:21,(anonymous_0) -FNF:1 -FNH:1 -FNDA:1,(anonymous_0) -DA:1,1 -DA:2,1 -DA:3,1 -DA:4,1 -DA:5,1 -DA:6,1 -DA:7,1 -DA:9,1 -DA:11,1 -DA:14,1 -DA:15,0 -DA:18,1 -DA:19,1 -DA:20,1 -DA:22,1 -DA:23,1 -DA:25,0 -DA:32,1 -DA:33,1 -DA:35,1 -LF:20 -LH:18 -BRDA:14,0,0,0 -BRDA:14,0,1,1 -BRDA:22,1,0,1 -BRDA:22,1,1,0 -BRDA:22,2,0,1 -BRDA:22,2,1,0 -BRF:6 -BRH:3 -end_of_record -TN: -SF:db.js -FN:10,getDbSettings -FN:45,initDb -FN:62,(anonymous_2) -FNF:3 -FNH:1 -FNDA:0,getDbSettings -FNDA:0,initDb -FNDA:1,(anonymous_2) -DA:1,2 -DA:2,2 -DA:3,2 -DA:5,2 -DA:8,2 -DA:11,0 -DA:12,0 -DA:13,0 -DA:14,0 -DA:15,0 -DA:16,0 -DA:17,0 -DA:18,0 -DA:22,0 -DA:23,0 -DA:28,0 -DA:29,0 -DA:30,0 -DA:31,0 -DA:32,0 -DA:35,0 -DA:46,0 -DA:48,0 -DA:62,2 -LF:24 -LH:6 -BRDA:12,0,0,0 -BRDA:12,0,1,0 -BRDA:12,1,0,0 -BRDA:12,1,1,0 -BRDA:13,2,0,0 -BRDA:13,2,1,0 -BRDA:15,3,0,0 -BRDA:15,3,1,0 -BRDA:18,4,0,0 -BRDA:18,4,1,0 -BRDA:41,5,0,0 -BRDA:41,5,1,0 -BRF:12 -BRH:0 -end_of_record -TN: -SF:oauth.js -FN:10,initOAuth -FNF:1 -FNH:0 -FNDA:0,initOAuth -DA:1,1 -DA:2,1 -DA:4,1 -DA:11,0 -DA:13,0 -DA:14,0 -DA:15,0 -DA:16,0 -DA:18,0 -DA:19,0 -DA:20,0 -DA:21,0 -DA:24,0 -DA:25,0 -DA:26,0 -DA:29,1 -LF:16 -LH:4 -BRDA:13,0,0,0 -BRDA:13,0,1,0 -BRF:2 -BRH:0 -end_of_record -TN: -SF:utils.js -FN:13,getFirebaseSdkServiceAccount -FN:33,loadRolesIntoMemory -FN:37,(anonymous_2) -FN:52,getRole -FN:59,isRoleExists -FNF:5 -FNH:0 -FNDA:0,getFirebaseSdkServiceAccount -FNDA:0,loadRolesIntoMemory -FNDA:0,(anonymous_2) -FNDA:0,getRole -FNDA:0,isRoleExists -DA:1,2 -DA:2,2 -DA:3,2 -DA:4,2 -DA:6,2 -DA:8,2 -DA:14,0 -DA:15,0 -DA:16,0 -DA:17,0 -DA:20,0 -DA:21,0 -DA:22,0 -DA:23,0 -DA:27,0 -DA:28,0 -DA:30,0 -DA:34,0 -DA:35,0 -DA:36,0 -DA:37,0 -DA:38,0 -DA:39,0 -DA:40,0 -DA:42,0 -DA:44,0 -DA:47,0 -DA:48,0 -DA:53,0 -DA:54,0 -DA:56,0 -DA:60,0 -DA:63,2 -DA:69,2 -LF:34 -LH:8 -BRDA:16,0,0,0 -BRDA:16,0,1,0 -BRDA:35,1,0,0 -BRDA:35,1,1,0 -BRDA:39,2,0,0 -BRDA:39,2,1,0 -BRDA:53,3,0,0 -BRDA:53,3,1,0 -BRF:8 -BRH:0 -end_of_record -TN: -SF:aws/ssm-helper.js -FN:4,loadSecrets -FN:18,(anonymous_1) -FN:32,parseJsonValue -FNF:3 -FNH:0 -FNDA:0,loadSecrets -FNDA:0,(anonymous_1) -FNDA:0,parseJsonValue -DA:1,2 -DA:2,2 -DA:5,0 -DA:9,0 -DA:10,0 -DA:11,0 -DA:16,0 -DA:18,0 -DA:19,0 -DA:20,0 -DA:21,0 -DA:24,0 -DA:25,0 -DA:27,0 -DA:28,0 -DA:33,0 -DA:34,0 -DA:36,0 -DA:40,2 -LF:19 -LH:3 -BRDA:4,0,0,0 -BRDA:4,1,0,0 -BRDA:20,2,0,0 -BRDA:20,2,1,0 -BRF:4 -BRH:0 -end_of_record -TN: -SF:middleware/authentication.js -FN:5,authenticate -FN:6,(anonymous_1) -FNF:2 -FNH:2 -FNDA:1,authenticate -FNDA:1,(anonymous_1) -DA:1,1 -DA:2,1 -DA:3,1 -DA:6,1 -DA:7,1 -DA:8,1 -DA:9,1 -DA:10,1 -DA:11,0 -DA:14,0 -DA:15,0 -DA:16,0 -DA:17,0 -DA:20,0 -DA:21,0 -DA:23,0 -DA:25,1 -DA:26,1 -DA:31,1 -LF:19 -LH:11 -BRDA:9,0,0,1 -BRDA:9,0,1,0 -BRDA:15,1,0,0 -BRDA:15,1,1,0 -BRF:4 -BRH:1 -end_of_record -TN: -SF:middleware/authorization.js -FN:6,verifyRole -FN:7,(anonymous_1) -FN:32,verifyOwnership -FN:33,(anonymous_3) -FNF:4 -FNH:2 -FNDA:2,verifyRole -FNDA:0,(anonymous_1) -FNDA:2,verifyOwnership -FNDA:0,(anonymous_3) -DA:1,1 -DA:2,1 -DA:3,1 -DA:4,1 -DA:7,2 -DA:8,0 -DA:9,0 -DA:10,0 -DA:11,0 -DA:14,0 -DA:15,0 -DA:16,0 -DA:17,0 -DA:19,0 -DA:21,0 -DA:22,0 -DA:27,1 -DA:33,2 -DA:34,0 -DA:35,0 -DA:36,0 -DA:37,0 -DA:38,0 -DA:39,0 -DA:42,0 -DA:43,0 -DA:47,0 -DA:48,0 -DA:50,0 -DA:51,0 -DA:54,0 -DA:55,0 -DA:58,0 -DA:60,0 -DA:61,0 -DA:67,1 -LF:36 -LH:8 -BRDA:10,0,0,0 -BRDA:10,0,1,0 -BRDA:16,1,0,0 -BRDA:16,1,1,0 -BRDA:38,2,0,0 -BRDA:38,2,1,0 -BRDA:42,3,0,0 -BRDA:42,3,1,0 -BRDA:42,4,0,0 -BRDA:42,4,1,0 -BRDA:47,5,0,0 -BRDA:47,5,1,0 -BRDA:51,6,0,0 -BRDA:51,6,1,0 -BRDA:54,7,0,0 -BRDA:54,7,1,0 -BRF:16 -BRH:0 -end_of_record -TN: -SF:repositories/applications_repository.js -FN:5,getAllApplications -FN:9,createNewApplication -FN:15,updateApplicationById -FN:23,getApplicationByFields -FNF:4 -FNH:0 -FNDA:0,getAllApplications -FNDA:0,createNewApplication -FNDA:0,updateApplicationById -FNDA:0,getApplicationByFields -DA:1,1 -DA:3,1 -DA:6,0 -DA:10,0 -DA:16,0 -DA:20,0 -DA:24,0 -DA:29,1 -LF:8 -LH:3 -BRF:0 -BRH:0 -end_of_record -TN: -SF:repositories/authorization_repository.js -FN:3,verifyRole -FN:8,verifyOwnership -FNF:2 -FNH:0 -FNDA:0,verifyRole -FNDA:0,verifyOwnership -DA:1,1 -DA:4,0 -DA:5,0 -DA:9,0 -DA:10,0 -DA:13,1 -LF:6 -LH:2 -BRF:0 -BRH:0 -end_of_record -TN: -SF:repositories/roles_repository.js -FN:3,getAllRoles -FN:8,assignRoleToUser -FNF:2 -FNH:0 -FNDA:0,getAllRoles -FNDA:0,assignRoleToUser -DA:1,2 -DA:4,0 -DA:5,0 -DA:9,0 -DA:10,0 -DA:13,2 -LF:6 -LH:2 -BRF:0 -BRH:0 -end_of_record -TN: -SF:repositories/user_repository.js -FN:3,getAllUsers -FN:8,createNewUser -FN:17,getUserInfoById -FN:26,getUserByFields -FN:35,getUserRolesById -FN:43,updateUserById -FN:55,deleteUserById -FNF:7 -FNH:0 -FNDA:0,getAllUsers -FNDA:0,createNewUser -FNDA:0,getUserInfoById -FNDA:0,getUserByFields -FNDA:0,getUserRolesById -FNDA:0,updateUserById -FNDA:0,deleteUserById -DA:1,2 -DA:4,0 -DA:5,0 -DA:9,0 -DA:10,0 -DA:13,0 -DA:14,0 -DA:18,0 -DA:19,0 -DA:20,0 -DA:21,0 -DA:23,0 -DA:27,0 -DA:28,0 -DA:29,0 -DA:30,0 -DA:32,0 -DA:36,0 -DA:37,0 -DA:44,0 -DA:45,0 -DA:49,0 -DA:50,0 -DA:52,0 -DA:56,0 -DA:57,0 -DA:60,2 -LF:27 -LH:2 -BRDA:20,0,0,0 -BRDA:20,0,1,0 -BRDA:29,1,0,0 -BRDA:29,1,1,0 -BRDA:49,2,0,0 -BRDA:49,2,1,0 -BRF:6 -BRH:0 -end_of_record -TN: -SF:routes/users.js -FN:16,(anonymous_0) -FN:43,(anonymous_1) -FN:54,(anonymous_2) -FN:80,(anonymous_3) -FN:98,(anonymous_4) -FN:117,(anonymous_5) -FNF:6 -FNH:0 -FNDA:0,(anonymous_0) -FNDA:0,(anonymous_1) -FNDA:0,(anonymous_2) -FNDA:0,(anonymous_3) -FNDA:0,(anonymous_4) -FNDA:0,(anonymous_5) -DA:1,1 -DA:3,1 -DA:4,1 -DA:5,1 -DA:6,1 -DA:7,1 -DA:9,1 -DA:11,1 -DA:13,1 -DA:16,1 -DA:17,0 -DA:18,0 -DA:19,0 -DA:21,0 -DA:24,0 -DA:25,0 -DA:26,0 -DA:27,0 -DA:29,0 -DA:31,0 -DA:32,0 -DA:35,0 -DA:36,0 -DA:38,0 -DA:43,1 -DA:44,0 -DA:45,0 -DA:46,0 -DA:48,0 -DA:49,0 -DA:54,1 -DA:55,0 -DA:57,0 -DA:60,0 -DA:61,0 -DA:63,0 -DA:64,0 -DA:65,0 -DA:66,0 -DA:71,0 -DA:72,0 -DA:75,0 -DA:80,1 -DA:81,0 -DA:82,0 -DA:83,0 -DA:85,0 -DA:86,0 -DA:87,0 -DA:88,0 -DA:90,0 -DA:92,0 -DA:93,0 -DA:98,1 -DA:99,0 -DA:100,0 -DA:101,0 -DA:102,0 -DA:104,0 -DA:105,0 -DA:106,0 -DA:107,0 -DA:109,0 -DA:111,0 -DA:112,0 -DA:117,1 -DA:118,0 -DA:119,0 -DA:120,0 -DA:122,0 -DA:123,0 -DA:124,0 -DA:125,0 -DA:127,0 -DA:129,0 -DA:130,0 -DA:134,1 -LF:77 -LH:16 -BRDA:18,0,0,0 -BRDA:18,0,1,0 -BRDA:19,1,0,0 -BRDA:19,1,1,0 -BRDA:24,2,0,0 -BRDA:24,2,1,0 -BRDA:26,3,0,0 -BRDA:26,3,1,0 -BRDA:35,4,0,0 -BRDA:35,4,1,0 -BRDA:65,5,0,0 -BRDA:65,5,1,0 -BRDA:71,6,0,0 -BRDA:71,6,1,0 -BRDA:82,7,0,0 -BRDA:82,7,1,0 -BRDA:87,8,0,0 -BRDA:87,8,1,0 -BRDA:101,9,0,0 -BRDA:101,9,1,0 -BRDA:106,10,0,0 -BRDA:106,10,1,0 -BRDA:119,11,0,0 -BRDA:119,11,1,0 -BRDA:124,12,0,0 -BRDA:124,12,1,0 -BRF:26 -BRH:0 -end_of_record -TN: -SF:services/applications_service.js -FN:3,getAllApplications -FN:7,createNewApplication -FN:11,updateApplicationStatus -FN:15,getApplicationByEmail -FN:19,getApplicationById -FNF:5 -FNH:0 -FNDA:0,getAllApplications -FNDA:0,createNewApplication -FNDA:0,updateApplicationStatus -FNDA:0,getApplicationByEmail -FNDA:0,getApplicationById -DA:1,1 -DA:4,0 -DA:8,0 -DA:12,0 -DA:16,0 -DA:20,0 -DA:23,1 -LF:7 -LH:2 -BRF:0 -BRH:0 -end_of_record -TN: -SF:services/auth_service.js -FN:11,handleFirebaseAuth -FN:48,handleDiscordAuth -FNF:2 -FNH:0 -FNDA:0,handleFirebaseAuth -FNDA:0,handleDiscordAuth -DA:1,1 -DA:2,1 -DA:3,1 -DA:4,1 -DA:5,1 -DA:7,1 -DA:9,1 -DA:12,0 -DA:13,0 -DA:14,0 -DA:15,0 -DA:17,0 -DA:18,0 -DA:21,0 -DA:22,0 -DA:23,0 -DA:26,0 -DA:27,0 -DA:28,0 -DA:37,0 -DA:39,0 -DA:41,0 -DA:42,0 -DA:49,0 -DA:51,0 -DA:65,0 -DA:71,0 -DA:72,0 -DA:74,0 -DA:75,0 -DA:78,0 -DA:80,0 -DA:81,0 -DA:86,1 -LF:34 -LH:8 -BRDA:17,0,0,0 -BRDA:17,0,1,0 -BRDA:22,1,0,0 -BRDA:22,1,1,0 -BRDA:22,2,0,0 -BRDA:22,2,1,0 -BRDA:27,3,0,0 -BRDA:27,3,1,0 -BRDA:74,4,0,0 -BRDA:74,4,1,0 -BRF:10 -BRH:0 -end_of_record -TN: -SF:services/roles_service.js -FN:4,assignRoleToUser -FN:13,getAllRoles -FNF:2 -FNH:0 -FNDA:0,assignRoleToUser -FNDA:0,getAllRoles -DA:1,2 -DA:2,2 -DA:5,0 -DA:6,0 -DA:7,0 -DA:9,0 -DA:14,0 -DA:17,2 -LF:8 -LH:3 -BRF:0 -BRH:0 -end_of_record -TN: -SF:services/user_service.js -FN:4,getAllUsers -FN:9,createNewMemberUser -FN:16,getUserById -FN:25,updateUserById -FN:29,deleteUserById -FN:33,getUserByFirebaseId -FN:37,getUserByEmail -FNF:7 -FNH:0 -FNDA:0,getAllUsers -FNDA:0,createNewMemberUser -FNDA:0,getUserById -FNDA:0,updateUserById -FNDA:0,deleteUserById -FNDA:0,getUserByFirebaseId -FNDA:0,getUserByEmail -DA:1,2 -DA:2,2 -DA:6,0 -DA:10,0 -DA:12,0 -DA:13,0 -DA:17,0 -DA:18,0 -DA:19,0 -DA:20,0 -DA:22,0 -DA:26,0 -DA:30,0 -DA:34,0 -DA:38,0 -DA:41,2 -LF:16 -LH:3 -BRDA:18,0,0,0 -BRDA:18,0,1,0 -BRDA:20,1,0,0 -BRDA:20,1,1,0 -BRF:4 -BRH:0 -end_of_record From e67b5e569093c3cc7791e790d476455de638f1b6 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Wed, 25 Jun 2025 16:28:29 +0200 Subject: [PATCH 027/135] small cleanup --- appForTests.js | 2 ++ jest.config.js | 2 +- middleware/authentication.js | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/appForTests.js b/appForTests.js index 93aaa94..6fc2bfb 100644 --- a/appForTests.js +++ b/appForTests.js @@ -1,6 +1,8 @@ const app = require('./app'); const routes = require('./routes/routes'); +// Setting the app up for tests - so it has all the routes +// (we don't need to wait for the database connection in unit-tests) app.use(routes); module.exports = app; \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 223c98e..2b0e473 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,6 @@ module.exports = { testEnvironment: 'node', - testMatch: ['**/tests/**/*.test.js'], // or '**/*.test.js' for more general + testMatch: ['**/tests/**/*.test.js'], coverageDirectory: 'coverage', verbose: true, }; \ No newline at end of file diff --git a/middleware/authentication.js b/middleware/authentication.js index 752cc10..3812371 100644 --- a/middleware/authentication.js +++ b/middleware/authentication.js @@ -5,7 +5,6 @@ const logger = require('../logger')(__filename); function authenticate() { return async (req, res, next) => { try { - logger.warn(`Authenticating request: ${req.path}`); const userFirebaseId = req.cookies?.userUID; if (!userFirebaseId) { logger.warn(`No user UID found in request cookies at ${req.path}`); From 3fe49119078f9092a43cd7450813f2be3e3f098b Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sat, 28 Jun 2025 14:47:52 +0200 Subject: [PATCH 028/135] fixed undefined in cookies --- repositories/user_repository.js | 6 +++--- routes/users.js | 10 +++++----- services/auth_service.js | 8 +++++--- services/user_service.js | 7 ++++++- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/repositories/user_repository.js b/repositories/user_repository.js index 806e630..1bdc5cb 100644 --- a/repositories/user_repository.js +++ b/repositories/user_repository.js @@ -14,10 +14,10 @@ async function createNewUser(values) { return user; } -async function getUserInfoById(id) { +async function getUserInfoById(id, safeOutput = true) { const knex = getKnex(); const user = await knex("users").where("id", id).first(); - if (user !== undefined){ + if (user !== undefined && safeOutput){ delete user["firebase_id"]; } return user; @@ -26,7 +26,7 @@ async function getUserInfoById(id) { async function getUserByFields(fields) { const knex = getKnex(); const user = await knex("users").where(fields).first(); - if (user !== undefined){ + if (user !== undefined) { delete user["firebase_id"]; } return user; diff --git a/routes/users.js b/routes/users.js index 7dc6cb0..ee777f7 100644 --- a/routes/users.js +++ b/routes/users.js @@ -18,17 +18,17 @@ router.post("/users/login", async (req, res) => { const discordCode = req.get('X-Discord-Code') || null; try { - let user; + let authResult; if (firebaseToken) { - user = await authService.handleFirebaseAuth(firebaseToken); + authResult = await authService.handleFirebaseAuth(firebaseToken); } else if (discordCode) { - user = await authService.handleDiscordAuth(discordCode); + authResult = await authService.handleDiscordAuth(discordCode); } else return respondWithError(res, 401, 'No token provided or invalid token'); - res.cookie('userUID', user.firebaseId, { httpOnly: true, secure: true, sameSite: 'none' }); - res.status(200).json({ user: user }); + res.cookie('userUID', authResult.firebaseId, { httpOnly: true, secure: true, sameSite: 'none' }); + res.status(200).json({ user: authResult.user }); } catch (error) { if (error.status) { diff --git a/services/auth_service.js b/services/auth_service.js index 567a0ab..7cdcc67 100644 --- a/services/auth_service.js +++ b/services/auth_service.js @@ -36,7 +36,7 @@ async function handleFirebaseAuth(firebaseToken) { await userService.updateUserById(user.id, { firebase_id: firebaseId }); - return user; + return { user, firebaseId }; } catch (error) { logger.error({ error: error.message }, "Firebase Token Verification Failed"); throw { status: 401, message: 'Unauthorized' }; @@ -74,13 +74,15 @@ async function handleDiscordAuth(code){ if (!user) { throw { status: 404, message: 'User not found' }; } + // temporary solution to get backend working for ongoing frontend development, + // will be fixed soon by aliona + const firebaseId = await userService.getUserFirebaseId(user.id); - return { user }; + return { user, firebaseId }; } catch (error) { logger.error({ error: error.message }, "Discord Authentication Failed", ); throw { status: 401, message: 'Unauthorized' }; } } -; module.exports = { handleFirebaseAuth, handleDiscordAuth}; \ No newline at end of file diff --git a/services/user_service.js b/services/user_service.js index d379040..a8c869d 100644 --- a/services/user_service.js +++ b/services/user_service.js @@ -38,5 +38,10 @@ async function getUserByEmail(email) { return await repo.getUserByFields({email: email}); } +async function getUserFirebaseId(id) { + const user = await repo.getUserInfoById(id, false); + return user?.firebase_id; +} + module.exports = { getAllUsers, createNewMemberUser, getUserById, updateUserById, deleteUserById, - getUserByEmail, getUserByFirebaseId }; \ No newline at end of file + getUserByEmail, getUserByFirebaseId, getUserFirebaseId }; \ No newline at end of file From fd778e29f9ac93a4b78f82c4ebbfba93eccf4c5e Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sat, 28 Jun 2025 14:31:27 +0200 Subject: [PATCH 029/135] users routes tests --- routes/users.js | 2 +- tests/routes/users.test.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/routes/users.js b/routes/users.js index 7dc6cb0..aaf6a36 100644 --- a/routes/users.js +++ b/routes/users.js @@ -16,7 +16,7 @@ router.use(express.json()); router.post("/users/login", async (req, res) => { const firebaseToken = req.get('X-Firebase-Token') || null; const discordCode = req.get('X-Discord-Code') || null; - + try { let user; diff --git a/tests/routes/users.test.js b/tests/routes/users.test.js index 9ce6e99..ebaf09b 100644 --- a/tests/routes/users.test.js +++ b/tests/routes/users.test.js @@ -1,7 +1,16 @@ const request = require('supertest'); const authService = require('../../services/auth_service'); +const userService = require('../../services/user_service'); +// const { verifyOwnership, verifyRole } = require("../middleware/authorization"); jest.mock('../../services/auth_service'); +jest.mock('../../services/user_service'); +jest.mock('../../middleware/authorization', () => ({ + //verifyOwnership: () => (req, res, next) => next(), + verifyRole: () => (req, res, next) => next(), +})); +jest.spyOn() + const app = require('../../appForTests'); const mockedUser = { firebaseId: '12345', email: 'test@example.com' }; @@ -77,4 +86,29 @@ describe('POST /users/login', () => { expect(res.statusCode).toBe(401); expect(res.body.error).toBe('Invalid Discord code'); }); +}); + +describe('GET /users', () => { + it('Return all users success', async () => { + const mockedUsers = [{ id: 1, name: 'Test User' }]; + userService.getAllUsers.mockResolvedValue(mockedUsers); + + const res = await request(app) + .get('/users'); + + expect(res.statusCode).toBe(200); + expect(res.body.users.lenght).toBeGreaterThan(0); + expect(res.body.users[0]['id']).toBe(1); + }); + + it('should handle errors when getting all users', async () => { + jest.spyOn(authService, 'getAllUsers').mockRejectedValue(new Error('Database error')); + + const res = await request(app) + .get('/users') + .set('Authorization', 'Bearer valid-token'); + + expect(res.statusCode).toBe(500); + expect(res.body.error).toBeDefined(); + }); }); \ No newline at end of file From 630fad275be29519c3842e3314d70c1f8ebd906e Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Mon, 30 Jun 2025 12:20:13 +0200 Subject: [PATCH 030/135] authorization middleware tests --- middleware/authorization.js | 2 +- tests/middleware/authorization.test.js | 117 +++++++++++++++++++++++++ tests/routes/users.test.js | 46 +++++++--- 3 files changed, 152 insertions(+), 13 deletions(-) create mode 100644 tests/middleware/authorization.test.js diff --git a/middleware/authorization.js b/middleware/authorization.js index 1e2023f..442f0c8 100644 --- a/middleware/authorization.js +++ b/middleware/authorization.js @@ -33,7 +33,7 @@ function verifyOwnership(entityTable) { return async (req, res, next) => { logger.debug(`Middleware invoked for: ${req.url}`); try { - const resourceId = req.params.id; + const resourceId = req.params?.id; const userId = req.userId; if (!userId) { return respondWithError(res, 401, "User not authenticated"); diff --git a/tests/middleware/authorization.test.js b/tests/middleware/authorization.test.js new file mode 100644 index 0000000..575182f --- /dev/null +++ b/tests/middleware/authorization.test.js @@ -0,0 +1,117 @@ +const { verifyRole, verifyOwnership, OWNED_ENTITIES } = require('../../middleware/authorization'); +const { ROLE_NAMES } = require("../../utils"); +const utils = require('../../utils'); + +jest.mock('../../routes/helpers', () => ({ + respondWithError: jest.fn((res, statusCode, message) => (res.statusCode = statusCode, res.body = { error: message }, res)), +})); + +jest.spyOn(utils, 'getRole').mockImplementation((roleName) => ("rolename")); + +describe('VerifyRole', () => { + it('Returns 401 on missing userId', async () => { + const res = {}; + const next = jest.fn(); + + await verifyRole(ROLE_NAMES.member)({}, res, next); + + expect(res.statusCode).toBe(401); + expect(res.body).toEqual({ error: "User not authenticated" }); + expect(next).not.toHaveBeenCalled(); + }); + + it('Returns 403 on insufficient permissions', async () => { + const req = { userId: 1 }; + const res = {}; + const next = jest.fn(); + + jest.spyOn(require('../../repositories/authorization_repository'), 'verifyRole').mockResolvedValue([]); + + await verifyRole(ROLE_NAMES.member)(req, res, next); + + expect(res.statusCode).toBe(403); + expect(res.body).toEqual({ error: "You're not allowed to access this resource" }); + expect(next).not.toHaveBeenCalled(); + }); + + it('Calls next on success', async () => { + const req = { userId: 1 }; + const res = {}; + const next = jest.fn(); + + jest.spyOn(require('../../repositories/authorization_repository'), 'verifyRole').mockResolvedValue([{ id: 1, role_id: 1 }]); + + await verifyRole(ROLE_NAMES.member)(req, res, next); + + expect(next).toHaveBeenCalled(); + }); +}); + +describe('VerifyOwnership', () => { + it('Returns 401 on missing userId', async () => { + const res = {}; + const next = jest.fn(); + + await verifyOwnership(OWNED_ENTITIES.USER)({}, res, next); + + expect(res.statusCode).toBe(401); + expect(next).not.toHaveBeenCalled(); + }); + + it('Returns 400 on missing resourceId', async () => { + const req = { userId: 1 }; + const res = {}; + const next = jest.fn(); + + await verifyOwnership(OWNED_ENTITIES.USER)(req, res, next); + + expect(res.statusCode).toBe(400); + expect(next).not.toHaveBeenCalled(); + }); + + it('Returns 403 on insufficient permissions for user entity', async () => { + const req = { userId: 1, params: { id: 2 } }; + const res = {}; + const next = jest.fn(); + + await verifyOwnership(OWNED_ENTITIES.USER)(req, res, next); + + expect(res.statusCode).toBe(403); + expect(next).not.toHaveBeenCalled(); + }); + + it('Returns 403 on insufficient permissions for mentor entity', async () => { + const req = { userId: 1, params: { id: 2 } }; + const res = {}; + const next = jest.fn(); + + jest.spyOn(require('../../repositories/authorization_repository'), 'verifyOwnership').mockResolvedValue({}); + + await verifyOwnership(OWNED_ENTITIES.MENTOR)(req, res, next); + + expect(res.statusCode).toBe(403); + expect(next).not.toHaveBeenCalled(); + }); + + it('Calls next on successful ownership verification for user entity', async () => { + const req = { userId: 1, params: { id: 1 } }; + const res = {}; + const next = jest.fn(); + + await verifyOwnership(OWNED_ENTITIES.USER)(req, res, next); + + expect(next).toHaveBeenCalled(); + }); + + it('Calls next on successful ownership verification for mentor entity', async () => { + const req = { userId: 1, params: { id: 2 } }; + const res = {}; + const next = jest.fn(); + + jest.spyOn(require('../../repositories/authorization_repository'), 'verifyOwnership').mockResolvedValue({ rows: [{ id: 2 }] }); + + await verifyOwnership(OWNED_ENTITIES.MENTOR)(req, res, next); + + expect(next).toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/tests/routes/users.test.js b/tests/routes/users.test.js index ebaf09b..2954d67 100644 --- a/tests/routes/users.test.js +++ b/tests/routes/users.test.js @@ -1,16 +1,16 @@ const request = require('supertest'); const authService = require('../../services/auth_service'); const userService = require('../../services/user_service'); -// const { verifyOwnership, verifyRole } = require("../middleware/authorization"); jest.mock('../../services/auth_service'); jest.mock('../../services/user_service'); -jest.mock('../../middleware/authorization', () => ({ - //verifyOwnership: () => (req, res, next) => next(), - verifyRole: () => (req, res, next) => next(), -})); -jest.spyOn() - +jest.mock('../../middleware/authorization', () => { + const actual = jest.requireActual('../../middleware/authorization'); + return { + ...actual, + verifyRole: jest.fn(() => (req, res, next) => next()), + }; +}); const app = require('../../appForTests'); const mockedUser = { firebaseId: '12345', email: 'test@example.com' }; @@ -19,6 +19,7 @@ function checkSuccessResponse(res, user) { expect(res.statusCode).toBe(200); expect(res.body.user).toEqual(user); expect(res.headers['set-cookie']).toBeDefined(); + expect(res.headers['set-cookie'][0]).toMatch(user.firebaseId); }; describe('POST /users/login', () => { @@ -97,18 +98,39 @@ describe('GET /users', () => { .get('/users'); expect(res.statusCode).toBe(200); - expect(res.body.users.lenght).toBeGreaterThan(0); + expect(res.body.users.length).toBeGreaterThan(0); expect(res.body.users[0]['id']).toBe(1); }); - it('should handle errors when getting all users', async () => { - jest.spyOn(authService, 'getAllUsers').mockRejectedValue(new Error('Database error')); + it('Unexpected error during users retrieval', async () => { + userService.getAllUsers.mockRejectedValue(new Error('Database error')); const res = await request(app) - .get('/users') - .set('Authorization', 'Bearer valid-token'); + .get('/users'); expect(res.statusCode).toBe(500); expect(res.body.error).toBeDefined(); }); +}); + +describe('GET /users/:id', () => { + it('Get user by ID success', async () => { + userService.getUserById.mockResolvedValue(mockedUser); + + const res = await request(app) + .get('/users/1'); + + expect(res.statusCode).toBe(200); + expect(res.body).toEqual(mockedUser); + }); + + it('Get user by ID not found', async () => { + userService.getUserById.mockResolvedValue(null); + + const res = await request(app) + .get('/users/999'); + + expect(res.statusCode).toBe(404); + expect(res.body.error).toBe('User not found'); + }); }); \ No newline at end of file From 116cfe8143e27e85c31afb8fe5f42a737c2bb19b Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Mon, 30 Jun 2025 12:30:00 +0200 Subject: [PATCH 031/135] updated eslint config and fixed linter issues --- eslint.config.mjs | 5 +- jest.config.js | 10 +- package-lock.json | 431 ++++++++++++++++++++++++- package.json | 1 + server.js | 1 + tests/middleware/authorization.test.js | 5 +- tests/routes/users.test.js | 3 +- 7 files changed, 439 insertions(+), 17 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index dabb134..f3e9d61 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -12,7 +12,10 @@ export default [ }, languageOptions: { sourceType: "commonjs", - globals: globals.node + globals: { + ...globals.node, + ...globals.jest, + } }, rules: { "no-console": ["warn"], diff --git a/jest.config.js b/jest.config.js index 2b0e473..11b54d9 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,6 @@ module.exports = { - testEnvironment: 'node', - testMatch: ['**/tests/**/*.test.js'], - coverageDirectory: 'coverage', - verbose: true, - }; \ No newline at end of file + testEnvironment: 'node', + testMatch: ['**/tests/**/*.test.js'], + coverageDirectory: 'coverage', + verbose: true, +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5dddc17..48172fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ }, "devDependencies": { "@stylistic/eslint-plugin-js": "^2.12.1", + "eslint-plugin-jest": "^28.14.0", "jest": "^29.7.0", "prisma": "^5.21.1", "supertest": "^7.1.1" @@ -1431,12 +1432,11 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -1456,7 +1456,6 @@ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2525,6 +2524,44 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -3618,6 +3655,224 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", + "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.35.0", + "@typescript-eslint/types": "^8.35.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", + "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", + "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", + "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", + "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.35.0", + "@typescript-eslint/tsconfig-utils": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", + "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", + "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.35.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vladfrangu/async_event_emitter": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", @@ -4927,6 +5182,32 @@ } } }, + "node_modules/eslint-plugin-jest": { + "version": "28.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.14.0.tgz", + "integrity": "sha512-P9s/qXSMTpRTerE2FQ0qJet2gKbcGyFTPAJipoKxmWqR6uuFqIqk8FuEfg5yBieOezVrEfAMZrEwJ6yEp+1MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "engines": { + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", @@ -4946,9 +5227,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5239,6 +5520,36 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -5291,6 +5602,16 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -7556,6 +7877,16 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -8596,6 +8927,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -8765,6 +9117,41 @@ "node": ">=14" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -9483,6 +9870,19 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-mixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", @@ -9545,6 +9945,21 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", diff --git a/package.json b/package.json index db6f51c..8f17d23 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ }, "devDependencies": { "@stylistic/eslint-plugin-js": "^2.12.1", + "eslint-plugin-jest": "^28.14.0", "jest": "^29.7.0", "prisma": "^5.21.1", "supertest": "^7.1.1" diff --git a/server.js b/server.js index 0581fe9..caa530a 100644 --- a/server.js +++ b/server.js @@ -21,6 +21,7 @@ const app = require('./app'); // Routes const routes = require("./routes/routes"); + app.use(routes); app.listen(8080,'0.0.0.0', () => { diff --git a/tests/middleware/authorization.test.js b/tests/middleware/authorization.test.js index 575182f..162696e 100644 --- a/tests/middleware/authorization.test.js +++ b/tests/middleware/authorization.test.js @@ -6,7 +6,7 @@ jest.mock('../../routes/helpers', () => ({ respondWithError: jest.fn((res, statusCode, message) => (res.statusCode = statusCode, res.body = { error: message }, res)), })); -jest.spyOn(utils, 'getRole').mockImplementation((roleName) => ("rolename")); +jest.spyOn(utils, 'getRole').mockImplementation(() => ("rolename")); describe('VerifyRole', () => { it('Returns 401 on missing userId', async () => { @@ -108,7 +108,8 @@ describe('VerifyOwnership', () => { const res = {}; const next = jest.fn(); - jest.spyOn(require('../../repositories/authorization_repository'), 'verifyOwnership').mockResolvedValue({ rows: [{ id: 2 }] }); + jest.spyOn(require('../../repositories/authorization_repository'), 'verifyOwnership') + .mockResolvedValue({ rows: [{ id: 2 }] }); await verifyOwnership(OWNED_ENTITIES.MENTOR)(req, res, next); diff --git a/tests/routes/users.test.js b/tests/routes/users.test.js index 2954d67..52d9442 100644 --- a/tests/routes/users.test.js +++ b/tests/routes/users.test.js @@ -13,6 +13,7 @@ jest.mock('../../middleware/authorization', () => { }); const app = require('../../appForTests'); + const mockedUser = { firebaseId: '12345', email: 'test@example.com' }; function checkSuccessResponse(res, user) { @@ -20,7 +21,7 @@ function checkSuccessResponse(res, user) { expect(res.body.user).toEqual(user); expect(res.headers['set-cookie']).toBeDefined(); expect(res.headers['set-cookie'][0]).toMatch(user.firebaseId); -}; +} describe('POST /users/login', () => { afterEach(() => { From de97a62258c565365412598665b40f3d556b4b48 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Mon, 30 Jun 2025 12:39:19 +0200 Subject: [PATCH 032/135] added a github workflow config to run unit tests --- .github/workflows/unittests.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/unittests.yml diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml new file mode 100644 index 0000000..af25aa4 --- /dev/null +++ b/.github/workflows/unittests.yml @@ -0,0 +1,26 @@ +name: Run Unit Tests + +on: + pull_request: + branches: + - main + - develop + +jobs: + eslint: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install dependencies + run: npm install + + - name: Run Jest tests + run: npm test From df0595e7b07467ee759700f22cfdc5081953968f Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Mon, 30 Jun 2025 12:47:07 +0200 Subject: [PATCH 033/135] fixed tests after develop merge --- tests/routes/users.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/routes/users.test.js b/tests/routes/users.test.js index 52d9442..081249b 100644 --- a/tests/routes/users.test.js +++ b/tests/routes/users.test.js @@ -29,7 +29,7 @@ describe('POST /users/login', () => { }); it('Firebase token auth success', async () => { - authService.handleFirebaseAuth.mockResolvedValue(mockedUser); + authService.handleFirebaseAuth.mockResolvedValue({ user: mockedUser, firebaseId: mockedUser.firebaseId }); const res = await request(app) .post('/users/login') .set('X-Firebase-Token', 'valid-firebase-token'); @@ -39,7 +39,7 @@ describe('POST /users/login', () => { }); it('Discord code auth success', async () => { - authService.handleDiscordAuth.mockResolvedValue(mockedUser); + authService.handleDiscordAuth.mockResolvedValue({ user: mockedUser, firebaseId: mockedUser.firebaseId }); const res = await request(app) .post('/users/login') .set('X-Discord-Code', 'valid-discord-code'); From 76abd43ddd2db87aa2a866f86d89e192d4d0f66e Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Mon, 30 Jun 2025 12:54:01 +0200 Subject: [PATCH 034/135] improved github workflows configuration --- .github/workflows/eslint.yml | 26 ------------------ .github/workflows/main.yml | 22 +++++++++++++++ .github/workflows/node-env-command.yml | 38 ++++++++++++++++++++++++++ .github/workflows/unittests.yml | 26 ------------------ 4 files changed, 60 insertions(+), 52 deletions(-) delete mode 100644 .github/workflows/eslint.yml create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/node-env-command.yml delete mode 100644 .github/workflows/unittests.yml diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml deleted file mode 100644 index b124ced..0000000 --- a/.github/workflows/eslint.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Run ESLint before merge - -on: - pull_request: - branches: - - main - - develop - -jobs: - eslint: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: '20' - - - name: Install dependencies - run: npm install - - - name: Run ESLint - run: npx eslint . diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..4297812 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,22 @@ +name: Run all checks before merge + +on: + pull_request: + branches: + - main + - develop + +jobs: + eslint: + uses: ./.github/workflows/node-env-command.yml + with: + node-version: '20' + commandDescription: 'Run ESLint' + command: 'npx eslint .' + + unittests: + uses: ./.github/workflows/node-env-command.yml + with: + node-version: '20' + commandDescription: 'Run Jest tests' + command: 'npm test' diff --git a/.github/workflows/node-env-command.yml b/.github/workflows/node-env-command.yml new file mode 100644 index 0000000..336a159 --- /dev/null +++ b/.github/workflows/node-env-command.yml @@ -0,0 +1,38 @@ +name: Setup Node.js Environment and Run Command + +on: + workflow_call: + inputs: + node-version: + description: 'Node.js version' + type: string + default: '20' + + commandDescription: + description: 'Description of the command to execute' + required: true + type: string + + command: + description: 'Command to execute' + required: true + type: string + +jobs: + setup: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: ${{ inputs.node-version }} + + - name: Install dependencies + run: npm install + + - name: ${{ inputs.commandDescription }} + run: ${{ inputs.command }} \ No newline at end of file diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml deleted file mode 100644 index af25aa4..0000000 --- a/.github/workflows/unittests.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Run Unit Tests - -on: - pull_request: - branches: - - main - - develop - -jobs: - eslint: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: '20' - - - name: Install dependencies - run: npm install - - - name: Run Jest tests - run: npm test From 4173eae0c940adc92d412a7193830a4b9561285e Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 6 Jul 2025 10:18:27 +0200 Subject: [PATCH 035/135] application routes tests --- tests/routes/applications.test.js | 228 ++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 tests/routes/applications.test.js diff --git a/tests/routes/applications.test.js b/tests/routes/applications.test.js new file mode 100644 index 0000000..f1655ac --- /dev/null +++ b/tests/routes/applications.test.js @@ -0,0 +1,228 @@ +const request = require('supertest'); +const applicationService = require('../../services/applications_service'); +const mailer_service = require('../../services/mailer_service'); + +const mockedUserId = 123; + +jest.mock('../../services/applications_service'); +jest.mock('../../services/mailer_service'); +jest.mock('../../middleware/authorization', () => { + const actual = jest.requireActual('../../middleware/authorization'); + return { + ...actual, + verifyRole: jest.fn(() => (req, res, next) => next()), + }; + }); + +jest.mock('../../middleware/authentication', () => { + return { + authenticate: jest.fn(() => (req, res, next) => { + req.userId = mockedUserId; + next(); + }), + }; +}); + +const app = require('../../appForTests'); + +describe('GET /applications', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Return all applications success', async () => { + const mockApplications = [{ id: 1, name: 'Test Application' }]; + applicationService.getAllApplications.mockResolvedValue(mockApplications); + + const res = await request(app).get('/applications'); + + expect(applicationService.getAllApplications).toHaveBeenCalled(); + expect(res.statusCode).toBe(200); + expect(res.body.applications).toEqual(mockApplications); + }); +}); + +describe('POST /applications', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + const applicationPayload = { + name : 'Test Application', + about: 'This is a test application', + email: 'test@mail.com', + video_link: 'http://example.com/video', + discord_nickname: 'TestUser' + }; + + it('Create new application success', async () => { + applicationService.createNewApplication.mockResolvedValue({ id: 1, ...applicationPayload }); + const res = await request(app) + .post('/applications') + .send(applicationPayload); + + expect(applicationService.createNewApplication).toHaveBeenCalledWith( + applicationPayload.name, + applicationPayload.about, + applicationPayload.email, + applicationPayload.video_link, + applicationPayload.discord_nickname + ); + expect(res.statusCode).toBe(201); + expect(res.body).toEqual({ id: 1, ...applicationPayload }); + }); + + it('Create new application unique constraint violation', async () => { + const constraintError = new Error('Unique constraint violation'); + constraintError.constraint = 'applications_email_key'; + constraintError.code = '23505'; + + applicationService.createNewApplication.mockRejectedValue(constraintError); + const res = await request(app) + .post('/applications') + .send(applicationPayload); + + expect(applicationService.createNewApplication).toHaveBeenCalledWith( + applicationPayload.name, + applicationPayload.about, + applicationPayload.email, + applicationPayload.video_link, + applicationPayload.discord_nickname + ); + expect(res.statusCode).toBe(409); + expect(res.body.error).toContain('already exists'); + }); + + it('Create new application notnull constraint violation', async () => { + const constraintError = new Error('Notnull constraint violation'); + constraintError.constraint = 'applications_email_key'; + constraintError.code = '23502'; + + applicationService.createNewApplication.mockRejectedValue(constraintError); + const res = await request(app) + .post('/applications') + .send(applicationPayload); + + expect(applicationService.createNewApplication).toHaveBeenCalledWith( + applicationPayload.name, + applicationPayload.about, + applicationPayload.email, + applicationPayload.video_link, + applicationPayload.discord_nickname + ); + expect(res.statusCode).toBe(400); + expect(res.body.error).toContain('email is required'); + }); + + it('Create new application unexpected error', async () => { + applicationService.createNewApplication.mockRejectedValue(new Error('Unexpected error')); + const res = await request(app) + .post('/applications') + .send(applicationPayload); + + expect(applicationService.createNewApplication).toHaveBeenCalledWith( + applicationPayload.name, + applicationPayload.about, + applicationPayload.email, + applicationPayload.video_link, + applicationPayload.discord_nickname + ); + expect(res.statusCode).toBe(500); + expect(res.body.error).toContain('Internal Server Error'); + }); +}); + +describe('PUT /applications/:id', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + const applicationId = '1'; + const updatePayload = { status: 'approved', comment: 'Looks good!' }; + + it('Change application status success', async () => { + const mockedApplication = { + id: applicationId, + name: 'Test Application', + email: 'test@test.com', + status: 'pending' + }; + applicationService.getApplicationById.mockResolvedValue(mockedApplication); + applicationService.updateApplicationStatus.mockResolvedValue({ id: applicationId, ...mockedApplication, ...updatePayload }); + + const res = await request(app) + .put(`/applications/${applicationId}`) + .send(updatePayload); + + expect(applicationService.getApplicationById).toHaveBeenCalledWith(applicationId); + expect(applicationService.updateApplicationStatus).toHaveBeenCalledWith( + applicationId, + updatePayload.status, + updatePayload.comment, + mockedUserId, + expect.any(Date) + ); + expect(mailer_service.sendEmailOnApplicationStatusChange).toHaveBeenCalledWith( + mockedApplication.email, + mockedApplication.name, + updatePayload.status + ); + expect(res.statusCode).toBe(200); + expect(res.body.id).toBe(applicationId); + expect(res.body.status).toBe(updatePayload.status); + }); + + it('Update application status not found', async () => { + applicationService.getApplicationById.mockResolvedValue(null); + + const res = await request(app) + .put(`/applications/${applicationId}`) + .send(updatePayload); + + expect(applicationService.getApplicationById).toHaveBeenCalledWith(applicationId); + expect(applicationService.updateApplicationStatus).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(404); + expect(res.body.error).toBe('Application not found'); + }); + + it('Update application status already rejected', async () => { + applicationService.getApplicationById.mockResolvedValue({ id: applicationId, status: 'rejected' }); + + const res = await request(app) + .put(`/applications/${applicationId}`) + .send(updatePayload); + + expect(applicationService.getApplicationById).toHaveBeenCalledWith(applicationId); + expect(applicationService.updateApplicationStatus).not.toHaveBeenCalled(); + expect(mailer_service.sendEmailOnApplicationStatusChange).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); + expect(res.body.error).toBe('Application is already in rejected status.'); + }); + + it('Update application invalid status provided', async () => { + const invalidPayload = { ...updatePayload, status: 'invalid_status' }; + + const res = await request(app) + .put(`/applications/${applicationId}`) + .send(invalidPayload); + + expect(applicationService.getApplicationById).not.toHaveBeenCalled(); + expect(mailer_service.sendEmailOnApplicationStatusChange).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); + expect(res.body.error).toBe('Invalid status provided'); + }); + + it('Update application rejected without comment', async () => { + const invalidPayload = { status: 'rejected' }; + + const res = await request(app) + .put(`/applications/${applicationId}`) + .send(invalidPayload); + + expect(applicationService.getApplicationById).not.toHaveBeenCalled(); + expect(applicationService.updateApplicationStatus).not.toHaveBeenCalled(); + expect(mailer_service.sendEmailOnApplicationStatusChange).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); + expect(res.body.error).toBe('Comment is required for rejected applications'); + }); +}); \ No newline at end of file From 0f8689cccaf402acacb8135435abe9650623f6f9 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 6 Jul 2025 10:21:13 +0200 Subject: [PATCH 036/135] fixed linter errors in applications routes tests --- tests/routes/applications.test.js | 384 +++++++++++++++--------------- 1 file changed, 192 insertions(+), 192 deletions(-) diff --git a/tests/routes/applications.test.js b/tests/routes/applications.test.js index f1655ac..18b6e99 100644 --- a/tests/routes/applications.test.js +++ b/tests/routes/applications.test.js @@ -1,26 +1,26 @@ const request = require('supertest'); const applicationService = require('../../services/applications_service'); -const mailer_service = require('../../services/mailer_service'); +const mailerService = require('../../services/mailer_service'); const mockedUserId = 123; jest.mock('../../services/applications_service'); jest.mock('../../services/mailer_service'); jest.mock('../../middleware/authorization', () => { - const actual = jest.requireActual('../../middleware/authorization'); - return { - ...actual, - verifyRole: jest.fn(() => (req, res, next) => next()), - }; - }); + const actual = jest.requireActual('../../middleware/authorization'); + return { + ...actual, + verifyRole: jest.fn(() => (req, res, next) => next()), + }; +}); jest.mock('../../middleware/authentication', () => { - return { - authenticate: jest.fn(() => (req, res, next) => { - req.userId = mockedUserId; - next(); - }), - }; + return { + authenticate: jest.fn(() => (req, res, next) => { + req.userId = mockedUserId; + next(); + }), + }; }); const app = require('../../appForTests'); @@ -43,186 +43,186 @@ describe('GET /applications', () => { }); describe('POST /applications', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - const applicationPayload = { - name : 'Test Application', - about: 'This is a test application', - email: 'test@mail.com', - video_link: 'http://example.com/video', - discord_nickname: 'TestUser' - }; - - it('Create new application success', async () => { - applicationService.createNewApplication.mockResolvedValue({ id: 1, ...applicationPayload }); - const res = await request(app) - .post('/applications') - .send(applicationPayload); - - expect(applicationService.createNewApplication).toHaveBeenCalledWith( - applicationPayload.name, - applicationPayload.about, - applicationPayload.email, - applicationPayload.video_link, - applicationPayload.discord_nickname - ); - expect(res.statusCode).toBe(201); - expect(res.body).toEqual({ id: 1, ...applicationPayload }); - }); - - it('Create new application unique constraint violation', async () => { - const constraintError = new Error('Unique constraint violation'); - constraintError.constraint = 'applications_email_key'; - constraintError.code = '23505'; - - applicationService.createNewApplication.mockRejectedValue(constraintError); - const res = await request(app) - .post('/applications') - .send(applicationPayload); - - expect(applicationService.createNewApplication).toHaveBeenCalledWith( - applicationPayload.name, - applicationPayload.about, - applicationPayload.email, - applicationPayload.video_link, - applicationPayload.discord_nickname - ); - expect(res.statusCode).toBe(409); - expect(res.body.error).toContain('already exists'); - }); - - it('Create new application notnull constraint violation', async () => { - const constraintError = new Error('Notnull constraint violation'); - constraintError.constraint = 'applications_email_key'; - constraintError.code = '23502'; - - applicationService.createNewApplication.mockRejectedValue(constraintError); - const res = await request(app) - .post('/applications') - .send(applicationPayload); - - expect(applicationService.createNewApplication).toHaveBeenCalledWith( - applicationPayload.name, - applicationPayload.about, - applicationPayload.email, - applicationPayload.video_link, - applicationPayload.discord_nickname - ); - expect(res.statusCode).toBe(400); - expect(res.body.error).toContain('email is required'); - }); - - it('Create new application unexpected error', async () => { - applicationService.createNewApplication.mockRejectedValue(new Error('Unexpected error')); - const res = await request(app) - .post('/applications') - .send(applicationPayload); - - expect(applicationService.createNewApplication).toHaveBeenCalledWith( - applicationPayload.name, - applicationPayload.about, - applicationPayload.email, - applicationPayload.video_link, - applicationPayload.discord_nickname - ); - expect(res.statusCode).toBe(500); - expect(res.body.error).toContain('Internal Server Error'); - }); + afterEach(() => { + jest.clearAllMocks(); + }); + + const applicationPayload = { + name : 'Test Application', + about: 'This is a test application', + email: 'test@mail.com', + video_link: 'http://example.com/video', + discord_nickname: 'TestUser' + }; + + it('Create new application success', async () => { + applicationService.createNewApplication.mockResolvedValue({ id: 1, ...applicationPayload }); + const res = await request(app) + .post('/applications') + .send(applicationPayload); + + expect(applicationService.createNewApplication).toHaveBeenCalledWith( + applicationPayload.name, + applicationPayload.about, + applicationPayload.email, + applicationPayload.video_link, + applicationPayload.discord_nickname + ); + expect(res.statusCode).toBe(201); + expect(res.body).toEqual({ id: 1, ...applicationPayload }); + }); + + it('Create new application unique constraint violation', async () => { + const constraintError = new Error('Unique constraint violation'); + constraintError.constraint = 'applications_email_key'; + constraintError.code = '23505'; + + applicationService.createNewApplication.mockRejectedValue(constraintError); + const res = await request(app) + .post('/applications') + .send(applicationPayload); + + expect(applicationService.createNewApplication).toHaveBeenCalledWith( + applicationPayload.name, + applicationPayload.about, + applicationPayload.email, + applicationPayload.video_link, + applicationPayload.discord_nickname + ); + expect(res.statusCode).toBe(409); + expect(res.body.error).toContain('already exists'); + }); + + it('Create new application notnull constraint violation', async () => { + const constraintError = new Error('Notnull constraint violation'); + constraintError.constraint = 'applications_email_key'; + constraintError.code = '23502'; + + applicationService.createNewApplication.mockRejectedValue(constraintError); + const res = await request(app) + .post('/applications') + .send(applicationPayload); + + expect(applicationService.createNewApplication).toHaveBeenCalledWith( + applicationPayload.name, + applicationPayload.about, + applicationPayload.email, + applicationPayload.video_link, + applicationPayload.discord_nickname + ); + expect(res.statusCode).toBe(400); + expect(res.body.error).toContain('email is required'); + }); + + it('Create new application unexpected error', async () => { + applicationService.createNewApplication.mockRejectedValue(new Error('Unexpected error')); + const res = await request(app) + .post('/applications') + .send(applicationPayload); + + expect(applicationService.createNewApplication).toHaveBeenCalledWith( + applicationPayload.name, + applicationPayload.about, + applicationPayload.email, + applicationPayload.video_link, + applicationPayload.discord_nickname + ); + expect(res.statusCode).toBe(500); + expect(res.body.error).toContain('Internal Server Error'); + }); }); describe('PUT /applications/:id', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - const applicationId = '1'; - const updatePayload = { status: 'approved', comment: 'Looks good!' }; - - it('Change application status success', async () => { - const mockedApplication = { - id: applicationId, - name: 'Test Application', - email: 'test@test.com', - status: 'pending' - }; - applicationService.getApplicationById.mockResolvedValue(mockedApplication); - applicationService.updateApplicationStatus.mockResolvedValue({ id: applicationId, ...mockedApplication, ...updatePayload }); - - const res = await request(app) - .put(`/applications/${applicationId}`) - .send(updatePayload); - - expect(applicationService.getApplicationById).toHaveBeenCalledWith(applicationId); - expect(applicationService.updateApplicationStatus).toHaveBeenCalledWith( - applicationId, - updatePayload.status, - updatePayload.comment, - mockedUserId, - expect.any(Date) - ); - expect(mailer_service.sendEmailOnApplicationStatusChange).toHaveBeenCalledWith( - mockedApplication.email, - mockedApplication.name, - updatePayload.status - ); - expect(res.statusCode).toBe(200); - expect(res.body.id).toBe(applicationId); - expect(res.body.status).toBe(updatePayload.status); - }); - - it('Update application status not found', async () => { - applicationService.getApplicationById.mockResolvedValue(null); - - const res = await request(app) - .put(`/applications/${applicationId}`) - .send(updatePayload); - - expect(applicationService.getApplicationById).toHaveBeenCalledWith(applicationId); - expect(applicationService.updateApplicationStatus).not.toHaveBeenCalled(); - expect(res.statusCode).toBe(404); - expect(res.body.error).toBe('Application not found'); - }); - - it('Update application status already rejected', async () => { - applicationService.getApplicationById.mockResolvedValue({ id: applicationId, status: 'rejected' }); - - const res = await request(app) - .put(`/applications/${applicationId}`) - .send(updatePayload); - - expect(applicationService.getApplicationById).toHaveBeenCalledWith(applicationId); - expect(applicationService.updateApplicationStatus).not.toHaveBeenCalled(); - expect(mailer_service.sendEmailOnApplicationStatusChange).not.toHaveBeenCalled(); - expect(res.statusCode).toBe(400); - expect(res.body.error).toBe('Application is already in rejected status.'); - }); - - it('Update application invalid status provided', async () => { - const invalidPayload = { ...updatePayload, status: 'invalid_status' }; - - const res = await request(app) - .put(`/applications/${applicationId}`) - .send(invalidPayload); - - expect(applicationService.getApplicationById).not.toHaveBeenCalled(); - expect(mailer_service.sendEmailOnApplicationStatusChange).not.toHaveBeenCalled(); - expect(res.statusCode).toBe(400); - expect(res.body.error).toBe('Invalid status provided'); - }); - - it('Update application rejected without comment', async () => { - const invalidPayload = { status: 'rejected' }; - - const res = await request(app) - .put(`/applications/${applicationId}`) - .send(invalidPayload); - - expect(applicationService.getApplicationById).not.toHaveBeenCalled(); - expect(applicationService.updateApplicationStatus).not.toHaveBeenCalled(); - expect(mailer_service.sendEmailOnApplicationStatusChange).not.toHaveBeenCalled(); - expect(res.statusCode).toBe(400); - expect(res.body.error).toBe('Comment is required for rejected applications'); - }); + afterEach(() => { + jest.clearAllMocks(); + }); + + const applicationId = '1'; + const updatePayload = { status: 'approved', comment: 'Looks good!' }; + + it('Change application status success', async () => { + const mockedApplication = { + id: applicationId, + name: 'Test Application', + email: 'test@test.com', + status: 'pending' + }; + applicationService.getApplicationById.mockResolvedValue(mockedApplication); + applicationService.updateApplicationStatus.mockResolvedValue({ id: applicationId, ...mockedApplication, ...updatePayload }); + + const res = await request(app) + .put(`/applications/${applicationId}`) + .send(updatePayload); + + expect(applicationService.getApplicationById).toHaveBeenCalledWith(applicationId); + expect(applicationService.updateApplicationStatus).toHaveBeenCalledWith( + applicationId, + updatePayload.status, + updatePayload.comment, + mockedUserId, + expect.any(Date) + ); + expect(mailerService.sendEmailOnApplicationStatusChange).toHaveBeenCalledWith( + mockedApplication.email, + mockedApplication.name, + updatePayload.status + ); + expect(res.statusCode).toBe(200); + expect(res.body.id).toBe(applicationId); + expect(res.body.status).toBe(updatePayload.status); + }); + + it('Update application status not found', async () => { + applicationService.getApplicationById.mockResolvedValue(null); + + const res = await request(app) + .put(`/applications/${applicationId}`) + .send(updatePayload); + + expect(applicationService.getApplicationById).toHaveBeenCalledWith(applicationId); + expect(applicationService.updateApplicationStatus).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(404); + expect(res.body.error).toBe('Application not found'); + }); + + it('Update application status already rejected', async () => { + applicationService.getApplicationById.mockResolvedValue({ id: applicationId, status: 'rejected' }); + + const res = await request(app) + .put(`/applications/${applicationId}`) + .send(updatePayload); + + expect(applicationService.getApplicationById).toHaveBeenCalledWith(applicationId); + expect(applicationService.updateApplicationStatus).not.toHaveBeenCalled(); + expect(mailerService.sendEmailOnApplicationStatusChange).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); + expect(res.body.error).toBe('Application is already in rejected status.'); + }); + + it('Update application invalid status provided', async () => { + const invalidPayload = { ...updatePayload, status: 'invalid_status' }; + + const res = await request(app) + .put(`/applications/${applicationId}`) + .send(invalidPayload); + + expect(applicationService.getApplicationById).not.toHaveBeenCalled(); + expect(mailerService.sendEmailOnApplicationStatusChange).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); + expect(res.body.error).toBe('Invalid status provided'); + }); + + it('Update application rejected without comment', async () => { + const invalidPayload = { status: 'rejected' }; + + const res = await request(app) + .put(`/applications/${applicationId}`) + .send(invalidPayload); + + expect(applicationService.getApplicationById).not.toHaveBeenCalled(); + expect(applicationService.updateApplicationStatus).not.toHaveBeenCalled(); + expect(mailerService.sendEmailOnApplicationStatusChange).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); + expect(res.body.error).toBe('Comment is required for rejected applications'); + }); }); \ No newline at end of file From ec7ac8a4c83e8f1978a814f0eb6ca899d07cac5a Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 6 Jul 2025 11:48:25 +0200 Subject: [PATCH 037/135] added tests for authentication middleware --- tests/middleware/authentication.test.js | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/middleware/authentication.test.js diff --git a/tests/middleware/authentication.test.js b/tests/middleware/authentication.test.js new file mode 100644 index 0000000..de25eab --- /dev/null +++ b/tests/middleware/authentication.test.js @@ -0,0 +1,72 @@ +const { authenticate } = require('../../middleware/authentication'); +const { respondWithError } = require('../../routes/helpers'); +const userService = require('../../services/user_service'); + +jest.mock('../../services/user_service'); +jest.mock('../../routes/helpers', () => ({ + respondWithError: jest.fn((res, statusCode, message) => { + if (statusCode && message) { + res.statusCode = statusCode; + res.body = { error: message }; + } else { + res.statusCode = 500; + res.body = { error: 'Internal Server Error' }; + } + return res; + }), +})); + +describe('Authenticate', () => { + it('Successful authentication sets user info in req', async () => { + const req = { cookies: { userUID: 'valid-uid' } }; + const res = {}; + const next = jest.fn(); + const user = { id: 42, email: 'test@test.com' }; + userService.getUserByFirebaseId.mockResolvedValue(user); + + await authenticate()(req, res, next); + + expect(userService.getUserByFirebaseId).toHaveBeenCalledWith('valid-uid'); + expect(req.userId).toBe(user.id); + expect(req.userEmail).toBe(user.email); + expect(next).toHaveBeenCalled(); + }); + + it('Missing UID in cookies continues request without authentication', async () => { + const res = {}; + const next = jest.fn(); + await authenticate()({}, res, next); + + expect(next).toHaveBeenCalled(); + expect(res.statusCode).toBeUndefined(); + expect(respondWithError).not.toHaveBeenCalled(); + }); + + it('Returns 401 if user not found in database', async () => { + const req = { cookies: { userUID: 'nonexistent-uid' } }; + const res = {}; + const next = jest.fn(); + userService.getUserByFirebaseId.mockResolvedValue(null); + + await authenticate()(req, res, next); + + expect(userService.getUserByFirebaseId).toHaveBeenCalledWith('nonexistent-uid'); + expect(res.statusCode).toBe(401); + expect(res.body).toEqual({ error: "User with provided UID not found" }); + expect(next).not.toHaveBeenCalled(); + }); + + it('Unexpected error handled properly', async () => { + const req = { cookies: { userUID: 'valid-uid' } }; + const res = {}; + const next = jest.fn(); + userService.getUserByFirebaseId.mockRejectedValue(new Error('Database error')); + + await authenticate()(req, res, next); + + expect(userService.getUserByFirebaseId).toHaveBeenCalledWith('valid-uid'); + expect(res.statusCode).toBe(500); + expect(res.body).toEqual({ error: 'Internal Server Error' }); + expect(next).not.toHaveBeenCalled(); + }); +}); \ No newline at end of file From d7399a440b39ba92d58e3ff8a11cf2c5ac216300 Mon Sep 17 00:00:00 2001 From: Anastasia Date: Sun, 20 Jul 2025 09:32:55 +0300 Subject: [PATCH 038/135] add users/logout endpoint --- routes/users.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/routes/users.js b/routes/users.js index ee777f7..26a7dfd 100644 --- a/routes/users.js +++ b/routes/users.js @@ -130,4 +130,13 @@ router.delete("/users/:id", verifyOwnership(OWNED_ENTITIES.USER), async (req, re } }); +router.post("/users/logout", async (req, res) => { + res.clearCookie("userUID", { + httpOnly: true, + secure: true, + sameSite: "none", + }); + res.status(200).json({ message: "Logged out successfully" }); +}); + module.exports = router; \ No newline at end of file From 1bde6abcb14256dec1f5ef9290b99649afcf23e7 Mon Sep 17 00:00:00 2001 From: Anastasia Date: Sun, 20 Jul 2025 09:40:35 +0300 Subject: [PATCH 039/135] fix --- routes/users.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/users.js b/routes/users.js index 26a7dfd..06e19a1 100644 --- a/routes/users.js +++ b/routes/users.js @@ -130,7 +130,7 @@ router.delete("/users/:id", verifyOwnership(OWNED_ENTITIES.USER), async (req, re } }); -router.post("/users/logout", async (req, res) => { +router.post("/users/logout", (req, res) => { res.clearCookie("userUID", { httpOnly: true, secure: true, From 9cd84966a531b7ffada108fbc7158cde8387f16c Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 31 Aug 2025 11:11:38 +0200 Subject: [PATCH 040/135] added makefile --- Makefile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..291946f --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +# import environment variables +include .env.local +export + + +lint: + npx eslint . + +test: + npm test + +connect-dev: + @if [ -z "$(DEV_PRIVATE_KEY_LOCATION)" ] || [ -z "$(DEV_HOST)" ]; then \ + echo "Error: DEV_PRIVATE_KEY_LOCATION and DEV_HOST environment variables must be set."; \ + exit 1; \ + fi + ssh -i $(DEV_PRIVATE_KEY_LOCATION) ec2-user@$(DEV_HOST) + +connect-prod: + @if [ -z "$(PROD_PRIVATE_KEY_LOCATION)" ] || [ -z "$(PROD_HOST)" ]; then \ + echo "Error: PROD_PRIVATE_KEY_LOCATION and PROD_HOST environment variables must be set."; \ + exit 1; \ + fi + ssh -i $(PROD_PRIVATE_KEY_LOCATION) ec2-user@$(PROD_HOST) + From 33e40321967781a158f15c6e80b6352592747cf9 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 31 Aug 2025 19:36:11 +0200 Subject: [PATCH 041/135] pr comments --- README.md | 11 ----------- config/test.json | 1 + jest.config.js | 1 + 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index db213c9..514d8fe 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,6 @@ Located in the `/docs` directory. `*.json` file is used for rendering, `*.yaml` Also published on GitHub pages: https://catbytes-community.github.io/webplatform-backend/ -### Monitoring -Service logs are sent to Grafana. Main dashboard: https://catbytes.grafana.net/goto/S3QCkuxHR?orgId=1 -Here are also some predefined queries to search in logs: - -| Environment | Url | -| -------- | ------- | -| DEV | https://catbytes.grafana.net/goto/dkl-MubNg?orgId=1 | -| PROD | https://catbytes.grafana.net/goto/cvZfGXbNR?orgId=1 | - -Insert value you're looking for in the 'Line contains' box. - ### Documentation for developers Located in the `/docs/how-tos` directory. All important information on local run, deployments, etc is stored there. diff --git a/config/test.json b/config/test.json index 7a73a41..4cae764 100644 --- a/config/test.json +++ b/config/test.json @@ -1,2 +1,3 @@ { + "dummy_key": "this is a default empty file for config loading using const config = require('config'); in test environment (when running unit tests). this is intended to be empty." } \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 11b54d9..68cb074 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,4 +3,5 @@ module.exports = { testMatch: ['**/tests/**/*.test.js'], coverageDirectory: 'coverage', verbose: true, + clearMocks: true, // equivalent to clearAllMocks between tests }; \ No newline at end of file From ddc378151348b3e7619c86410c91483c8fc30503 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 31 Aug 2025 20:32:53 +0200 Subject: [PATCH 042/135] updated makefile --- Makefile | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 291946f..8ac4664 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,14 @@ -# import environment variables include .env.local export - +# Development lint: npx eslint . test: npm test +# EC2 instances connection connect-dev: @if [ -z "$(DEV_PRIVATE_KEY_LOCATION)" ] || [ -z "$(DEV_HOST)" ]; then \ echo "Error: DEV_PRIVATE_KEY_LOCATION and DEV_HOST environment variables must be set."; \ @@ -23,3 +23,9 @@ connect-prod: fi ssh -i $(PROD_PRIVATE_KEY_LOCATION) ec2-user@$(PROD_HOST) +# Database migrations with knex +create-migration: + knex migrate:make $(name) + +apply-migrations: + knex migrate:latest \ No newline at end of file From 4decb47f2cbd00e930f4ce142593b539fbf8e5c5 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 31 Aug 2025 20:53:20 +0200 Subject: [PATCH 043/135] added deployment commands to the makefile, updated readme --- Makefile | 28 +++++++++++++++++++++++++++- README.md | 19 ++++++++++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 8ac4664..2748c8d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ include .env.local export +WARN_COLOR := $(shell tput setaf 208) +RESET_COLOR := $(shell tput sgr0) + # Development lint: npx eslint . @@ -28,4 +31,27 @@ create-migration: knex migrate:make $(name) apply-migrations: - knex migrate:latest \ No newline at end of file + knex migrate:latest + + +# EC2 deployment +stop-dev: + docker-compose -f docker-compose.dev.yml down + +stop-prod: + docker-compose -f docker-compose.prod.yml down + +deploy-env: + @read -p "$(WARN_COLOR)You're about to deploy to the $(env) environment. Please make sure you pulled recent changes, \ + installed new dependencies and verified the service starts using npm run as well as applied database migrations. \ + Please refer to the /docs/how-tos/aws_ec2_deployment.md for the instructions. \ + If you're sure, press Enter to continue.$(RESET_COLOR)" confirm; \ + + docker-compose -f docker-compose.$(env).yml build --no-cache + docker-compose -f docker-compose.$(env).yml up -d --force-recreate + +deploy-dev: + @$(MAKE) deploy-env env=dev + +deploy-prod: + @$(MAKE) deploy-env env=prod diff --git a/README.md b/README.md index 514d8fe..6f464e0 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,27 @@ Located in the `/docs/how-tos` directory. All important information on local run ### How to run project locally 1. Follow `/docs/how-tos/setup_local_postgresql`, step "Setting up Local PostgreSQL instance" -2. Update your .env accordingly. Here is sample .env structure: +2. Use `.env.local` file for environment variables (default `.env` is picked up by Docker so to avoid confusion we use explicit local file for local development). Update your `.env.local` accordingly. Here is sample structure or reach out for up-to-date .env to fellow devs: +``` DB_USER=marina_kim DB_HOST=localhost DB_NAME=mydatabase DB_PASS=password DB_PORT=5432 ENVIRONMENT=local +``` -Note: ENVIRONMENT should be set to local for running the project locally, otherwise you have to authenticate with AWS CLI first (ref. `/docs/how-tos/aws_local_db_connection.md`) +Note: ENVIRONMENT should be set to local for running the project locally, otherwise you have to authenticate with AWS CLI first (ref. `/docs/how-tos/local_run_and_configs.md`) -Once you are authenticated with AWS CLI or you have the local PostgreSQL setup, you should successfully run `npm start` to start the server +Once you are authenticated with AWS CLI or you have the local PostgreSQL setup, you can successfully run `npm start` (or use `nodemon`) to start the server. + +### Useful commands: Makefile + +Refer to the `Makefile` to see commands frequently used in development. +Example usage: + +```bash +make connect-dev +``` +This will execute the `connect-dev` command and connect you to the dev EC2 instance. +Make sure you have all required environment variables set. \ No newline at end of file From 242ed395234c18c5a0f2495fa3f3afda1ae349c2 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Mon, 1 Sep 2025 08:28:28 +0200 Subject: [PATCH 044/135] tiny updates after pr review --- Makefile | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Makefile b/Makefile index 2748c8d..2367372 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,12 @@ include .env.local export +# To make sure these targets are not confused with files of the same name +.PHONY: help lint test clean deploy-dev deploy-prod stop-dev stop-prod \ + connect-dev connect-prod create-migration apply-migrations rollback-migration + WARN_COLOR := $(shell tput setaf 208) +GREEN_COLOR := $(shell tput setaf 2) RESET_COLOR := $(shell tput sgr0) # Development @@ -33,6 +38,9 @@ create-migration: apply-migrations: knex migrate:latest +rollback-migration: + knex migrate:rollback + # EC2 deployment stop-dev: @@ -55,3 +63,29 @@ deploy-dev: deploy-prod: @$(MAKE) deploy-env env=prod + +help: + @echo "" + @echo "🛠 $(GREEN_COLOR)Makefile Help$(RESET_COLOR)" + @echo "" + @echo "🔧 Development:" + @echo " $(GREEN_COLOR)lint$(RESET_COLOR) Run eslint to check code quality" + @echo " $(GREEN_COLOR)test$(RESET_COLOR) Run unit tests with npm" + + @echo "" + @echo "🌐 EC2 Instances:" + @echo " $(GREEN_COLOR)connect-dev$(RESET_COLOR) SSH into the dev EC2 instance" + @echo " $(GREEN_COLOR)connect-prod$(RESET_COLOR) SSH into the prod EC2 instance" + + @echo "" + @echo "🛢 Database Migrations (Knex):" + @echo " $(GREEN_COLOR)create-migration$(RESET_COLOR) Create a new migration (usage: make create-migration name=xyz)" + @echo " $(GREEN_COLOR)apply-migrations$(RESET_COLOR) Apply the latest migrations" + @echo " $(GREEN_COLOR)rollback-migration$(RESET_COLOR)Rollback the last migration" + + @echo "" + @echo "🚀 Deployment:" + @echo " $(GREEN_COLOR)deploy-dev$(RESET_COLOR) Deploy the dev environment" + @echo " $(GREEN_COLOR)deploy-prod$(RESET_COLOR) Deploy the prod environment" + @echo " $(GREEN_COLOR)stop-dev$(RESET_COLOR) Stop the dev Docker Compose" + @echo " $(GREEN_COLOR)stop-prod$(RESET_COLOR) Stop the prod Docker Compose" \ No newline at end of file From 3a3f1cb62f1ddcb426201c6535df4f00796a5c44 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Mon, 1 Sep 2025 08:29:53 +0200 Subject: [PATCH 045/135] updated readme --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f464e0..415925e 100644 --- a/README.md +++ b/README.md @@ -36,4 +36,10 @@ Example usage: make connect-dev ``` This will execute the `connect-dev` command and connect you to the dev EC2 instance. -Make sure you have all required environment variables set. \ No newline at end of file +Make sure you have all required environment variables set. + +Run +```bash +make help +``` +to list all available Makefile commands. \ No newline at end of file From 7559ea62627ab8786cf374da0c0f1bd8ace646f5 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 14 Sep 2025 11:58:25 +0200 Subject: [PATCH 046/135] verifyRole -> verifyRoles --- middleware/authorization.js | 19 ++++++++++++------- repositories/authorization_repository.js | 10 +++++++++- routes/applications.js | 6 +++--- routes/discord.js | 4 ++-- routes/routes.js | 4 ++-- routes/users.js | 6 +++--- tests/middleware/authorization.test.js | 16 +++++++++------- tests/routes/applications.test.js | 2 +- tests/routes/users.test.js | 2 +- 9 files changed, 42 insertions(+), 27 deletions(-) diff --git a/middleware/authorization.js b/middleware/authorization.js index 442f0c8..b88c106 100644 --- a/middleware/authorization.js +++ b/middleware/authorization.js @@ -3,20 +3,25 @@ const repo = require('../repositories/authorization_repository'); const { respondWithError } = require('../routes/helpers'); const logger = require('../logger')(__filename); -function verifyRole(roleName) { +function verifyRoles(roleNames) { return async (req, res, next) => { try { const userId = req.userId; if (!userId) { return respondWithError(res, 401, "User not authenticated"); } + const userRoles = await repo.getRolesByUserId(userId); - const roleId = utils.getRole(roleName); - const userRole = await repo.verifyRole(userId, roleId); - if (userRole.length === 0){ - return respondWithError(res, 403, "You're not allowed to access this resource"); + const hasRole = roleNames.some(roleName => { + return userRoles.some(userRole => userRole.role_name === roleName); + }); + + if (hasRole) { + return next(); } - next(); + + return respondWithError(res, 403, "You're not allowed to access this resource"); + } catch (err) { logger.error(err, 'Error verifying role'); return respondWithError(res); @@ -64,4 +69,4 @@ function verifyOwnership(entityTable) { } -module.exports = { verifyRole, verifyOwnership, OWNED_ENTITIES }; \ No newline at end of file +module.exports = { verifyRoles, verifyOwnership, OWNED_ENTITIES }; \ No newline at end of file diff --git a/repositories/authorization_repository.js b/repositories/authorization_repository.js index 4eb3791..c3d2b14 100644 --- a/repositories/authorization_repository.js +++ b/repositories/authorization_repository.js @@ -5,9 +5,17 @@ async function verifyRole(userId, roleId) { return await knex("user_roles").where("user_id", userId).andWhere("role_id", roleId).select("*"); } +async function getRolesByUserId(id) { + const knex = getKnex(); + return await knex("roles") + .join("user_roles", "roles.id", "user_roles.role_id") + .where("user_roles.user_id", id) + .select("roles.role_name", "roles.id as role_id"); +} + async function verifyOwnership(entityTable, resourceId, userId) { const knex = getKnex(); return await knex(entityTable).where("id", resourceId).andWhere("created_by", userId).select("*"); } -module.exports = { verifyRole, verifyOwnership }; \ No newline at end of file +module.exports = { verifyRole, verifyOwnership, getRolesByUserId }; \ No newline at end of file diff --git a/routes/applications.js b/routes/applications.js index b0395f3..0cbc0ae 100644 --- a/routes/applications.js +++ b/routes/applications.js @@ -1,6 +1,6 @@ const express = require("express"); const applService = require("../services/applications_service"); -const { verifyRole } = require("../middleware/authorization"); +const { verifyRoles } = require("../middleware/authorization"); const { ROLE_NAMES, APPL_STATUSES } = require("../utils"); const { respondWithError, @@ -12,7 +12,7 @@ const logger = require('../logger')(__filename); const router = express.Router(); router.use(express.json()); -router.get("/applications", verifyRole(ROLE_NAMES.mentor), async (req, res) => { +router.get("/applications", verifyRoles([ROLE_NAMES.mentor]), async (req, res) => { try { const result = await applService.getAllApplications(); res.json({ applications: result }); @@ -40,7 +40,7 @@ router.post("/applications", async (req, res) => { } }); -router.put("/applications/:id", verifyRole(ROLE_NAMES.mentor), async (req, res) => { +router.put("/applications/:id", verifyRoles([ROLE_NAMES.mentor]), async (req, res) => { const { id } = req.params; const { status, comment } = req.body; const today = new Date(); diff --git a/routes/discord.js b/routes/discord.js index c4af329..3ff0186 100644 --- a/routes/discord.js +++ b/routes/discord.js @@ -1,7 +1,7 @@ const express = require('express'); const discordService = require('../services/discord_bot_service'); const { respondWithError } = require("./helpers"); -const { verifyRole } = require("../middleware/authorization"); +const { verifyRoles } = require("../middleware/authorization"); const { ROLE_NAMES } = require("../utils"); const logger = require('../logger')(__filename); @@ -10,7 +10,7 @@ const {discordAuth} = require ("../oauth.js"); const router = express.Router(); router.use(express.json()); -router.post('/generate-invite', verifyRole(ROLE_NAMES.member), async (req, res) => { +router.post('/generate-invite', verifyRoles([ROLE_NAMES.member]), async (req, res) => { try { const invite = await discordService.generateInviteLink(req.userId); res.json({ invite_link: invite }); diff --git a/routes/routes.js b/routes/routes.js index dd206f9..18382ae 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -6,7 +6,7 @@ const applRoutes = require("./applications"); const rolesRoutes = require("./roles"); const mailerService = require("../services/mailer_service"); const discordRoutes = require("./discord"); -const { verifyRole } = require("../middleware/authorization"); +const { verifyRoles } = require("../middleware/authorization"); const { ROLE_NAMES, APPL_STATUSES } = require("../utils"); router.use(express.json()); @@ -22,7 +22,7 @@ router.get("/", (req, res) => { }); // helper route to quickly trigger email sending for testing -router.post("/mail-test", verifyRole(ROLE_NAMES.mentor), async (req, res) => { +router.post("/mail-test", verifyRoles([ROLE_NAMES.mentor]), async (req, res) => { const { name, email} = req.body; await mailerService.sendEmailOnApplicationStatusChange(email, name, APPL_STATUSES.approved); res.json({"message": "email successfully sent"}); diff --git a/routes/users.js b/routes/users.js index 5442e0b..b229d9b 100644 --- a/routes/users.js +++ b/routes/users.js @@ -4,7 +4,7 @@ const router = express.Router(); const userService = require("../services/user_service"); const authService = require("../services/auth_service"); const { ROLE_NAMES } = require("../utils"); -const {verifyOwnership, verifyRole, OWNED_ENTITIES} = require("../middleware/authorization"); +const {verifyOwnership, verifyRoles, OWNED_ENTITIES} = require("../middleware/authorization"); const { isValidIntegerId, respondWithError, isUniqueConstraintViolation, isNotNullConstraintViolation, parseColumnNameFromConstraint } = require("./helpers"); @@ -39,7 +39,7 @@ router.post("/users/login", async (req, res) => { }); // Get all users -router.get("/users", verifyRole(ROLE_NAMES.member), async (req, res) => { +router.get("/users", verifyRoles([ROLE_NAMES.member]), async (req, res) => { try { const users = await userService.getAllUsers(); res.json({ users }); @@ -76,7 +76,7 @@ router.post("/users", async (req, res) => { }); // Get user by ID -router.get("/users/:id", verifyRole(ROLE_NAMES.member), async (req, res) => { +router.get("/users/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) => { const { id } = req.params; if (!isValidIntegerId(id)) { return respondWithError(res, 400, "Invalid user id supplied"); diff --git a/tests/middleware/authorization.test.js b/tests/middleware/authorization.test.js index 162696e..c5b88c9 100644 --- a/tests/middleware/authorization.test.js +++ b/tests/middleware/authorization.test.js @@ -1,4 +1,4 @@ -const { verifyRole, verifyOwnership, OWNED_ENTITIES } = require('../../middleware/authorization'); +const { verifyRoles, verifyOwnership, OWNED_ENTITIES } = require('../../middleware/authorization'); const { ROLE_NAMES } = require("../../utils"); const utils = require('../../utils'); @@ -8,12 +8,12 @@ jest.mock('../../routes/helpers', () => ({ jest.spyOn(utils, 'getRole').mockImplementation(() => ("rolename")); -describe('VerifyRole', () => { +describe('verifyRoles', () => { it('Returns 401 on missing userId', async () => { const res = {}; const next = jest.fn(); - await verifyRole(ROLE_NAMES.member)({}, res, next); + await verifyRoles([ROLE_NAMES.member])({}, res, next); expect(res.statusCode).toBe(401); expect(res.body).toEqual({ error: "User not authenticated" }); @@ -25,9 +25,10 @@ describe('VerifyRole', () => { const res = {}; const next = jest.fn(); - jest.spyOn(require('../../repositories/authorization_repository'), 'verifyRole').mockResolvedValue([]); + jest.spyOn(require('../../repositories/authorization_repository'), 'getRolesByUserId') + .mockResolvedValue([]); - await verifyRole(ROLE_NAMES.member)(req, res, next); + await verifyRoles([ROLE_NAMES.member])(req, res, next); expect(res.statusCode).toBe(403); expect(res.body).toEqual({ error: "You're not allowed to access this resource" }); @@ -39,9 +40,10 @@ describe('VerifyRole', () => { const res = {}; const next = jest.fn(); - jest.spyOn(require('../../repositories/authorization_repository'), 'verifyRole').mockResolvedValue([{ id: 1, role_id: 1 }]); + jest.spyOn(require('../../repositories/authorization_repository'), 'getRolesByUserId') + .mockResolvedValue([{ role_name: 'member', role_id: 1 }]); - await verifyRole(ROLE_NAMES.member)(req, res, next); + await verifyRoles([ROLE_NAMES.member])(req, res, next); expect(next).toHaveBeenCalled(); }); diff --git a/tests/routes/applications.test.js b/tests/routes/applications.test.js index 18b6e99..f2666b8 100644 --- a/tests/routes/applications.test.js +++ b/tests/routes/applications.test.js @@ -10,7 +10,7 @@ jest.mock('../../middleware/authorization', () => { const actual = jest.requireActual('../../middleware/authorization'); return { ...actual, - verifyRole: jest.fn(() => (req, res, next) => next()), + verifyRoles: jest.fn(() => (req, res, next) => next()), }; }); diff --git a/tests/routes/users.test.js b/tests/routes/users.test.js index 081249b..947dc95 100644 --- a/tests/routes/users.test.js +++ b/tests/routes/users.test.js @@ -8,7 +8,7 @@ jest.mock('../../middleware/authorization', () => { const actual = jest.requireActual('../../middleware/authorization'); return { ...actual, - verifyRole: jest.fn(() => (req, res, next) => next()), + verifyRoles: jest.fn(() => (req, res, next) => next()), }; }); From 4498c5deaadf34b9128f60baea6bc2cb4e299b44 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 14 Sep 2025 12:02:38 +0200 Subject: [PATCH 047/135] linter --- middleware/authorization.js | 1 - 1 file changed, 1 deletion(-) diff --git a/middleware/authorization.js b/middleware/authorization.js index b88c106..c601c32 100644 --- a/middleware/authorization.js +++ b/middleware/authorization.js @@ -1,4 +1,3 @@ -const utils = require('../utils'); const repo = require('../repositories/authorization_repository'); const { respondWithError } = require('../routes/helpers'); const logger = require('../logger')(__filename); From 4b284a0e2ffb46c9f4dce2f5a45be9ce13a6a0d6 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 14 Sep 2025 12:07:34 +0200 Subject: [PATCH 048/135] removed old method to get user roles from user service --- repositories/user_repository.js | 10 +--------- services/roles_service.js | 7 ++++++- services/user_service.js | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/repositories/user_repository.js b/repositories/user_repository.js index 1bdc5cb..ccbf5be 100644 --- a/repositories/user_repository.js +++ b/repositories/user_repository.js @@ -32,14 +32,6 @@ async function getUserByFields(fields) { return user; } -async function getUserRolesById(id) { - const knex = getKnex(); - return await knex("roles") - .join("user_roles", "roles.id", "user_roles.role_id") - .where("user_roles.user_id", id) - .select("roles.role_name", "roles.id"); -} - async function updateUserById(id, updates) { const knex = getKnex(); const [user] = await knex("users") @@ -57,5 +49,5 @@ async function deleteUserById(id) { return await knex("users").where("id", id).del(); } -module.exports = { getAllUsers, createNewUser, getUserInfoById, getUserRolesById, updateUserById, +module.exports = { getAllUsers, createNewUser, getUserInfoById, updateUserById, deleteUserById, getUserByFields }; diff --git a/services/roles_service.js b/services/roles_service.js index 93562df..f312060 100644 --- a/services/roles_service.js +++ b/services/roles_service.js @@ -1,5 +1,6 @@ const repo = require('../repositories/roles_repository'); const utils = require('../utils'); +const authRepo = require('../repositories/authorization_repository'); async function assignRoleToUser(userId, roleName) { try { @@ -10,8 +11,12 @@ async function assignRoleToUser(userId, roleName) { } } +async function getUserRoles(userId) { + return await authRepo.getRolesByUserId(userId) ?? []; +} + async function getAllRoles() { return await repo.getAllRoles(); } -module.exports = { getAllRoles, assignRoleToUser }; \ No newline at end of file +module.exports = { getAllRoles, assignRoleToUser, getUserRoles }; \ No newline at end of file diff --git a/services/user_service.js b/services/user_service.js index a8c869d..d13af94 100644 --- a/services/user_service.js +++ b/services/user_service.js @@ -16,7 +16,7 @@ async function createNewMemberUser(name, email, about, languages, discordNicknam async function getUserById(id) { const userInfo = await repo.getUserInfoById(id); if (userInfo) { - const roles = await repo.getUserRolesById(id); + const roles = await rolesService.getUserRoles(id); userInfo.roles = roles.length ? roles : []; } return userInfo; From dae1e4f5d677b2151c49c8ff9e68f1ef209e76e8 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 14 Sep 2025 13:17:14 +0200 Subject: [PATCH 049/135] added admin role --- .../20250706102201_add_admin_to_roles.js | 23 +++++++ routes/applications.js | 4 +- routes/routes.js | 2 +- tests/middleware/authorization.test.js | 66 +++++++++++-------- utils.js | 3 +- 5 files changed, 68 insertions(+), 30 deletions(-) create mode 100644 knex/migrations/20250706102201_add_admin_to_roles.js diff --git a/knex/migrations/20250706102201_add_admin_to_roles.js b/knex/migrations/20250706102201_add_admin_to_roles.js new file mode 100644 index 0000000..0fe2287 --- /dev/null +++ b/knex/migrations/20250706102201_add_admin_to_roles.js @@ -0,0 +1,23 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = async function(knex) { + await knex('roles') + .insert({ + role_name: 'admin', + is_privileged: true + }) + .onConflict('role_name') + .ignore(); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = async function(knex) { + await knex('roles') + .where('role_name', 'admin') + .del(); +}; \ No newline at end of file diff --git a/routes/applications.js b/routes/applications.js index 0cbc0ae..807c985 100644 --- a/routes/applications.js +++ b/routes/applications.js @@ -12,7 +12,7 @@ const logger = require('../logger')(__filename); const router = express.Router(); router.use(express.json()); -router.get("/applications", verifyRoles([ROLE_NAMES.mentor]), async (req, res) => { +router.get("/applications", verifyRoles([ROLE_NAMES.mentor, ROLE_NAMES.admin]), async (req, res) => { try { const result = await applService.getAllApplications(); res.json({ applications: result }); @@ -40,7 +40,7 @@ router.post("/applications", async (req, res) => { } }); -router.put("/applications/:id", verifyRoles([ROLE_NAMES.mentor]), async (req, res) => { +router.put("/applications/:id", verifyRoles([ROLE_NAMES.mentor, ROLE_NAMES.admin]), async (req, res) => { const { id } = req.params; const { status, comment } = req.body; const today = new Date(); diff --git a/routes/routes.js b/routes/routes.js index 18382ae..1956ad5 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -22,7 +22,7 @@ router.get("/", (req, res) => { }); // helper route to quickly trigger email sending for testing -router.post("/mail-test", verifyRoles([ROLE_NAMES.mentor]), async (req, res) => { +router.post("/mail-test", verifyRoles([ROLE_NAMES.admin]), async (req, res) => { const { name, email} = req.body; await mailerService.sendEmailOnApplicationStatusChange(email, name, APPL_STATUSES.approved); res.json({"message": "email successfully sent"}); diff --git a/tests/middleware/authorization.test.js b/tests/middleware/authorization.test.js index c5b88c9..39dee4d 100644 --- a/tests/middleware/authorization.test.js +++ b/tests/middleware/authorization.test.js @@ -20,32 +20,46 @@ describe('verifyRoles', () => { expect(next).not.toHaveBeenCalled(); }); - it('Returns 403 on insufficient permissions', async () => { - const req = { userId: 1 }; - const res = {}; - const next = jest.fn(); - - jest.spyOn(require('../../repositories/authorization_repository'), 'getRolesByUserId') - .mockResolvedValue([]); - - await verifyRoles([ROLE_NAMES.member])(req, res, next); - - expect(res.statusCode).toBe(403); - expect(res.body).toEqual({ error: "You're not allowed to access this resource" }); - expect(next).not.toHaveBeenCalled(); - }); - - it('Calls next on success', async () => { - const req = { userId: 1 }; - const res = {}; - const next = jest.fn(); - - jest.spyOn(require('../../repositories/authorization_repository'), 'getRolesByUserId') - .mockResolvedValue([{ role_name: 'member', role_id: 1 }]); - - await verifyRoles([ROLE_NAMES.member])(req, res, next); - - expect(next).toHaveBeenCalled(); + const roleCheckTestCases = [ + { + description: 'Calls next on success for member role', + rolesToVerify: [ROLE_NAMES.member], + userRoles: [{ role_name: 'member', role_id: 1 }], + expectedNextCalled: true, + }, + { + description: 'Calls next on success for admin role', + rolesToVerify: [ROLE_NAMES.mentor, ROLE_NAMES.admin], + userRoles: [{ role_name: 'admin', role_id: 3 }], + expectedNextCalled: true, + }, + { + description: 'Returns 403 when user does not have the required role', + rolesToVerify: [ROLE_NAMES.mentor, ROLE_NAMES.admin], + userRoles: [{ role_name: 'member', role_id: 1 }], + expectedNextCalled: false, + }, + ]; + + roleCheckTestCases.forEach(({ description, rolesToVerify, userRoles, expectedNextCalled }) => { + it(description, async () => { + const req = { userId: 1 }; + const res = {}; + const next = jest.fn(); + + jest.spyOn(require('../../repositories/authorization_repository'), 'getRolesByUserId') + .mockResolvedValue(userRoles); + + await verifyRoles(rolesToVerify)(req, res, next); + + if (expectedNextCalled) { + expect(next).toHaveBeenCalled(); + } else { + expect(res.statusCode).toBe(403); + expect(res.body).toEqual({ error: "You're not allowed to access this resource" }); + expect(next).not.toHaveBeenCalled(); + } + }); }); }); diff --git a/utils.js b/utils.js index 704a0d9..469451e 100644 --- a/utils.js +++ b/utils.js @@ -6,8 +6,9 @@ const logger = require('./logger')(__filename); let rolesCache = null; const ROLE_NAMES = { + admin: 'admin', mentor: 'mentor', - member: 'member' + member: 'member', }; async function getFirebaseSdkServiceAccount() { From f6729a4ffe5f527d5024dc9610377a9b9d76f316 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 14 Sep 2025 14:57:09 +0200 Subject: [PATCH 050/135] added endpoints to grant/revoke roles --- repositories/roles_repository.js | 12 +++- routes/admin.js | 45 +++++++++++++++ routes/routes.js | 2 + tests/routes/admin.test.js | 95 ++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 routes/admin.js create mode 100644 tests/routes/admin.test.js diff --git a/repositories/roles_repository.js b/repositories/roles_repository.js index 1e5c570..bb8823d 100644 --- a/repositories/roles_repository.js +++ b/repositories/roles_repository.js @@ -7,7 +7,15 @@ async function getAllRoles() { async function assignRoleToUser(userId, roleId) { const knex = getKnex(); - await knex("user_roles").insert({ role_id: roleId, user_id: userId }); + await knex("user_roles") + .insert({ role_id: roleId, user_id: userId }); } -module.exports = { getAllRoles, assignRoleToUser }; \ No newline at end of file +async function removeRoleFromUser(userId, roleId) { + const knex = getKnex(); + await knex("user_roles") + .where({ user_id: userId, role_id: roleId }) + .del(); +} + +module.exports = { getAllRoles, assignRoleToUser, removeRoleFromUser }; \ No newline at end of file diff --git a/routes/admin.js b/routes/admin.js new file mode 100644 index 0000000..fe56fd3 --- /dev/null +++ b/routes/admin.js @@ -0,0 +1,45 @@ +const express = require("express"); + +const router = express.Router(); +const rolesRepo = require("../repositories/roles_repository"); +const { ROLE_NAMES } = require("../utils"); +const { verifyRoles } = require("../middleware/authorization"); +const { respondWithError } = require("./helpers"); + +const logger = require('../logger')(__filename); + +router.use(express.json()); + +router.post('/admin/grant-role', verifyRoles([ROLE_NAMES.admin]), async (req, res) => { + const { userId, roleId } = req.body; + try { + if (!userId || !roleId) { + return res.status(400).json({ message: 'Missing userId or roleId' }); + } + + await rolesRepo.assignRoleToUser(userId, roleId); + + res.status(200).json({ message: 'Role assigned successfully' }); + } catch (err) { + logger.error(err, `Error assigning role ${roleId} to user ${userId}`); + respondWithError(res); + } +}); + +router.post('/admin/revoke-role', verifyRoles([ROLE_NAMES.admin]), async (req, res) => { + const { userId, roleId } = req.body; + try { + if (!userId || !roleId) { + return res.status(400).json({ message: 'Missing userId or roleId' }); + } + + await rolesRepo.removeRoleFromUser(userId, roleId); + + res.status(200).json({ message: 'Role removed successfully' }); + } catch (err) { + logger.error(err, `Error removing role ${roleId} from user ${userId}`); + respondWithError(res); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/routes.js b/routes/routes.js index 1956ad5..76f55b1 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -6,6 +6,7 @@ const applRoutes = require("./applications"); const rolesRoutes = require("./roles"); const mailerService = require("../services/mailer_service"); const discordRoutes = require("./discord"); +const adminRoutes = require("./admin"); const { verifyRoles } = require("../middleware/authorization"); const { ROLE_NAMES, APPL_STATUSES } = require("../utils"); @@ -16,6 +17,7 @@ router.use(usersRoutes); router.use(applRoutes); router.use(rolesRoutes); router.use(discordRoutes); +router.use(adminRoutes); router.get("/", (req, res) => { res.send("API Specification in Swagger: https://catbytes-community.github.io/webplatform-backend/"); diff --git a/tests/routes/admin.test.js b/tests/routes/admin.test.js new file mode 100644 index 0000000..bf72e18 --- /dev/null +++ b/tests/routes/admin.test.js @@ -0,0 +1,95 @@ +const request = require('supertest'); +const rolesRepo = require('../../repositories/roles_repository'); + +const app = require('../../appForTests'); + +jest.mock('../../repositories/roles_repository'); +jest.mock('../../middleware/authorization', () => { + const actual = jest.requireActual('../../middleware/authorization'); + return { + ...actual, + verifyRoles: jest.fn(() => (req, res, next) => next()), + }; +}); + +describe('POST /admin/grant-role', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Role assignment success', async () => { + rolesRepo.assignRoleToUser.mockResolvedValue(); + + const payload = { userId: 42, roleId: 1 }; + const res = await request(app) + .post('/admin/grant-role') + .send(payload); + + expect(rolesRepo.assignRoleToUser).toHaveBeenCalledWith(payload.userId, payload.roleId); + expect(res.statusCode).toBe(200); + expect(res.body.message).toBe('Role assigned successfully'); + }); + + it('Return 400 on incomplete payload data', async () => { + const res = await request(app) + .post('/admin/grant-role') + .send({ userId: 42 }); + + expect(rolesRepo.assignRoleToUser).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); + expect(res.body.message).toBe('Missing userId or roleId'); + }); + + it('Handle unexpected errors', async () => { + rolesRepo.assignRoleToUser.mockRejectedValue(new Error('Unexpected error')); + + const payload = { userId: 42, roleId: 1 }; + const res = await request(app) + .post('/admin/grant-role') + .send(payload); + + expect(rolesRepo.assignRoleToUser).toHaveBeenCalledWith(payload.userId, payload.roleId); + expect(res.statusCode).toBe(500); + }); +}); + +describe('POST /admin/revoke-role', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Role revocation success', async () => { + rolesRepo.removeRoleFromUser.mockResolvedValue(); + + const payload = { userId: 42, roleId: 1 }; + const res = await request(app) + .post('/admin/revoke-role') + .send(payload); + + expect(rolesRepo.removeRoleFromUser).toHaveBeenCalledWith(payload.userId, payload.roleId); + expect(res.statusCode).toBe(200); + expect(res.body.message).toBe('Role removed successfully'); + }); + + it('Return 400 on incomplete payload data', async () => { + const res = await request(app) + .post('/admin/revoke-role') + .send({ userId: 42 }); + + expect(rolesRepo.removeRoleFromUser).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); + expect(res.body.message).toBe('Missing userId or roleId'); + }); + + it('Handle unexpected errors', async () => { + rolesRepo.removeRoleFromUser.mockRejectedValue(new Error('Unexpected error')); + + const payload = { userId: 42, roleId: 1 }; + const res = await request(app) + .post('/admin/revoke-role') + .send(payload); + + expect(rolesRepo.removeRoleFromUser).toHaveBeenCalledWith(payload.userId, payload.roleId); + expect(res.statusCode).toBe(500); + }); +}); From f5336f105a1b5fde948dd5ed88392f7c20ef8899 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 14 Sep 2025 18:33:23 +0200 Subject: [PATCH 051/135] mentors application create and get all mentors + tests --- appForTests.js | 8 + errors.js | 17 +++ .../20250707101530_create_mentors_table.js | 49 ++++++ repositories/mentor_repository.js | 37 +++++ routes/mentors.js | 48 ++++++ routes/routes.js | 2 + services/mentor_service.js | 79 ++++++++++ tests/routes/mentors.test.js | 133 +++++++++++++++++ tests/services/mentor_service.test.js | 139 ++++++++++++++++++ 9 files changed, 512 insertions(+) create mode 100644 errors.js create mode 100644 knex/migrations/20250707101530_create_mentors_table.js create mode 100644 repositories/mentor_repository.js create mode 100644 routes/mentors.js create mode 100644 services/mentor_service.js create mode 100644 tests/routes/mentors.test.js create mode 100644 tests/services/mentor_service.test.js diff --git a/appForTests.js b/appForTests.js index 6fc2bfb..cb5bec5 100644 --- a/appForTests.js +++ b/appForTests.js @@ -3,6 +3,14 @@ const routes = require('./routes/routes'); // Setting the app up for tests - so it has all the routes // (we don't need to wait for the database connection in unit-tests) + +// to simulate authentication in tests, we read userId from headers +app.use((req, res, next) => { + if (!req.userId && req.headers['userid']) { + req.userId = Number(req.headers['userid']); + } + next(); +}); app.use(routes); module.exports = app; \ No newline at end of file diff --git a/errors.js b/errors.js new file mode 100644 index 0000000..dcdf923 --- /dev/null +++ b/errors.js @@ -0,0 +1,17 @@ +class MentorAlreadyExistsError extends Error { + constructor(message = 'Conflict') { + super(message); + this.name = 'MentorAlreadyExistsError'; + this.status = 409; + } +} + +class DataRequiresElevatedRoleError extends Error { + constructor(message = 'Data requires elevated role') { + super(message); + this.name = 'DataRequiresElevatedRoleError'; + this.status = 403; + } +} + +module.exports = { MentorAlreadyExistsError, DataRequiresElevatedRoleError }; \ No newline at end of file diff --git a/knex/migrations/20250707101530_create_mentors_table.js b/knex/migrations/20250707101530_create_mentors_table.js new file mode 100644 index 0000000..20b9d6d --- /dev/null +++ b/knex/migrations/20250707101530_create_mentors_table.js @@ -0,0 +1,49 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = async function(knex) { + //creating status enums + await knex.raw(` + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'mentor_status') THEN + CREATE TYPE mentor_status AS ENUM ( + 'pending', + 'rejected', + 'active', + 'inactive' + ); + END IF; + END$$; + `); + + await knex.schema.createTable('mentors', (table) => { + table.increments('id').primary(); + table.integer('user_id') + .notNullable() + .references('id') + .inTable('users') + .onDelete('CASCADE'); + table.string('contact', 100); + table.text('about'); + table.specificType('status', 'mentor_status').notNullable(); + table.timestamp('created_at') + .notNullable() + .defaultTo(knex.fn.now()); + table.integer('last_modified_by') + .references('id') + .inTable('users') + .onDelete('SET NULL'); + table.timestamp('last_modified_at'); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = async function(knex) { + await knex.schema.dropTableIfExists('mentors'); + await knex.raw('DROP TYPE IF EXISTS mentor_status'); +}; \ No newline at end of file diff --git a/repositories/mentor_repository.js b/repositories/mentor_repository.js new file mode 100644 index 0000000..db3a4a6 --- /dev/null +++ b/repositories/mentor_repository.js @@ -0,0 +1,37 @@ +const { getKnex } = require("../db"); + +async function getMentors(statusesFilter, selectedFields) { + const knex = getKnex(); + const query = knex("mentors") + .join("users", "mentors.user_id", "users.id") + .select(selectedFields); + + return await query.whereIn("mentors.status", statusesFilter); +} + +async function getMentorByUserId(userId) { + const knex = getKnex(); + return await knex("mentors") + .join("users", "mentors.user_id", "users.id") + .where("users.id", userId) + .first(); +} + +async function createMentor(mentorData) { + const knex = getKnex(); + const [mentor] = await knex("mentors") + .insert(mentorData) + .returning("*"); + + //get user name + const user = await knex("users") + .select("name") + .where("id", mentor.user_id) + .first(); + return { + ...mentor, + name: user.name + }; +} + +module.exports = { getMentors, createMentor, getMentorByUserId }; \ No newline at end of file diff --git a/routes/mentors.js b/routes/mentors.js new file mode 100644 index 0000000..d58c038 --- /dev/null +++ b/routes/mentors.js @@ -0,0 +1,48 @@ +const express = require("express"); + +const router = express.Router(); +const mentorService = require("../services/mentor_service"); +const { ROLE_NAMES } = require("../utils"); +const { verifyRoles } = require("../middleware/authorization"); +const { respondWithError } = require("./helpers"); +const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); +const logger = require('../logger')(__filename); + +router.use(express.json()); + +// Create new mentor application +router.post("/mentors", verifyRoles([ROLE_NAMES.member]), async (req, res) => { + try { + const mentorData = { + about: req.body.about, + contact: req.body.contact || null, + }; + + const newMentor = await mentorService.createMentor(req.userId, mentorData); + res.status(201).json({ mentor_id: newMentor.id}); + } catch(err) { + logger.error(`Error while creating mentor application for user ${req.userId}: ${err.message}`); + if (err instanceof MentorAlreadyExistsError) { + return respondWithError(res, 409, err.message); + } + return respondWithError(res); + } +}); + +// Get all mentors +router.get("/mentors", async (req, res) => { + const { status } = req.query; + try { + const userId = req.userId; + const mentors = await mentorService.getMentors(userId, status, !!userId); + res.json({ mentors }); + } catch (err) { + logger.error(`Error when trying to get all mentors, status=${status}: ${err.message}`); + if (err instanceof DataRequiresElevatedRoleError) { + return respondWithError(res, 403, err.message); + } + respondWithError(res); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/routes.js b/routes/routes.js index 76f55b1..3d42ef8 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -7,6 +7,7 @@ const rolesRoutes = require("./roles"); const mailerService = require("../services/mailer_service"); const discordRoutes = require("./discord"); const adminRoutes = require("./admin"); +const mentorsRoutes = require("./mentors"); const { verifyRoles } = require("../middleware/authorization"); const { ROLE_NAMES, APPL_STATUSES } = require("../utils"); @@ -18,6 +19,7 @@ router.use(applRoutes); router.use(rolesRoutes); router.use(discordRoutes); router.use(adminRoutes); +router.use(mentorsRoutes); router.get("/", (req, res) => { res.send("API Specification in Swagger: https://catbytes-community.github.io/webplatform-backend/"); diff --git a/services/mentor_service.js b/services/mentor_service.js new file mode 100644 index 0000000..6dd3b0c --- /dev/null +++ b/services/mentor_service.js @@ -0,0 +1,79 @@ +const repo = require('../repositories/mentor_repository'); +const rolesService = require('../services/roles_service'); +const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); +const { ROLE_NAMES } = require("../utils"); + +const MENTOR_STATUSES = { + active: 'active', + inactive: 'inactive', + rejected: 'rejected', + pending: 'pending', +}; + +const baseFields = [ + 'mentors.id as mentor_id', + 'mentors.user_id', + 'mentors.about', + 'mentors.status', + 'users.name', + 'users.img as img_link' +]; + +const privateFields = [ + 'mentors.contact', + 'users.discord_nickname' +]; + +const adminVisibleStatuses = [MENTOR_STATUSES.active, MENTOR_STATUSES.inactive, MENTOR_STATUSES.rejected, MENTOR_STATUSES.pending]; +const generalVisitbleStatuses = [MENTOR_STATUSES.active]; + +const allFields = [...baseFields, ...privateFields]; + +async function createMentor(userId, mentorData) { + const existingMentor = await repo.getMentorByUserId(userId); + if (existingMentor) { + throw new MentorAlreadyExistsError(`User already has a mentor profile: id ${existingMentor.id}`); + } + + const mentor = { + user_id: userId, + status: 'pending', + about: mentorData.about, + contact: mentorData.contact, + }; + const createdMentor = await repo.createMentor(mentor); + // todo: send email to admins + return createdMentor; +} + +async function getMentors(userId, status, includeAdditionalFields) { + const userRoles = userId ? await rolesService.getUserRoles(userId) : []; + const allowedStatuses = await getEligibleMentorStatuses(userRoles); + + const selectedFields = includeAdditionalFields + ? allFields + : baseFields; + + if (status && !allowedStatuses.includes(status)) { + throw new DataRequiresElevatedRoleError('Requested data requires elevated role'); + } + const statusesFilter = status ? [status] : allowedStatuses; + + return await repo.getMentors(statusesFilter, selectedFields); +} + +async function getEligibleMentorStatuses(userRoles) { + const isAdmin = userRoles.some(role => role.role_name === ROLE_NAMES.admin); + if (isAdmin) { + return adminVisibleStatuses; + } else { + return generalVisitbleStatuses; + } +} + +module.exports = { + getMentors, createMentor, + adminVisibleStatuses, generalVisitbleStatuses, + allFields, baseFields, privateFields, + MENTOR_STATUSES +}; \ No newline at end of file diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js new file mode 100644 index 0000000..793de0a --- /dev/null +++ b/tests/routes/mentors.test.js @@ -0,0 +1,133 @@ +const request = require('supertest'); +const mentorService = require('../../services/mentor_service'); +const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require('../../errors'); + +const defautlUserId = 42; +const defaultUserRoles = [{ role_name: 'member', role_id: 1 }]; +const mockedMentor = { + id: 13, + user_id: defautlUserId, + contact: 'sample@test.com', + about: 'I am a mentor', + status: 'pending', + created_at: '2024-01-01T00:00:00.000Z', + last_modified_by: 10, + last_modified_at: '2024-01-01T00:00:00.000Z', + name: 'Test User', +}; + +jest.mock('../../services/auth_service'); +jest.mock('../../services/mentor_service'); +jest.mock('../../middleware/authorization', () => { + const actual = jest.requireActual('../../middleware/authorization'); + return { + ...actual, + verifyRoles: jest.fn(() => (req, res, next) => { + req.userId = defautlUserId; + req.userRoles = defaultUserRoles; + next(); + }), + }; +}); + +const app = require('../../appForTests'); + +describe('POST /mentors', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Create mentor application success', async () => { + mentorService.createMentor.mockResolvedValue(mockedMentor); + + const payload = { + contact: 'sample@test.com', + about: 'I am a mentor', + }; + + const res = await request(app) + .post('/mentors') + .send(payload); + + expect(mentorService.createMentor).toHaveBeenCalledWith(mockedMentor.user_id, payload); + + expect(res.statusCode).toBe(201); + expect(res.body).toStrictEqual({ mentor_id: mockedMentor.id }); + }); + + it('Create mentor application - user already has mentor entity', async () => { + mentorService.createMentor.mockRejectedValue(new MentorAlreadyExistsError('User already has a mentor profile')); + + const res = await request(app) + .post('/mentors') + .send({ contact: 'sample@test.com', about: 'I am a mentor'}); + + expect(res.statusCode).toBe(409); + expect(res.body.error).toBe('User already has a mentor profile'); + }); + + it('Create mentor application - unexpected error', async () => { + mentorService.createMentor.mockRejectedValue(new Error("")); + + const res = await request(app) + .post('/mentors') + .send({ contact: 'sample@test.com', about: 'I am a mentor'}); + + expect(res.statusCode).toBe(500); + expect(res.body.error).toBe('Internal Server Error'); + }); +}); + +describe('GET /mentors', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Get all mentors success - not authenticated', async () => { + mentorService.getMentors.mockResolvedValue([mockedMentor]); + const res = await request(app) + .get('/mentors'); + + expect(mentorService.getMentors).toHaveBeenCalledWith(undefined, undefined, false); + + expect(res.statusCode).toBe(200); + expect(res.body.mentors.length).toBeGreaterThan(0); + }); + + it('Get all mentors success - authenticated', async () => { + mentorService.getMentors.mockResolvedValue([mockedMentor]); + const res = await request(app) + .get('/mentors') + .set('userId', defautlUserId); // simulate authenticated user + + expect(mentorService.getMentors).toHaveBeenCalledWith(defautlUserId, undefined, true); + + expect(res.statusCode).toBe(200); + expect(res.body.mentors.length).toBeGreaterThan(0); + }); + + it('Get pending mentors - unauthorized', async () => { + mentorService.getMentors + .mockRejectedValue(new DataRequiresElevatedRoleError("You're not allowed to see mentors with this status.")); + const res = await request(app) + .get('/mentors') + .query({ status: 'pending' }) + .set('userId', defautlUserId); // simulate authenticated user + + expect(mentorService.getMentors).toHaveBeenCalledWith(defautlUserId, 'pending', true); + + expect(res.statusCode).toBe(403); + expect(res.body.error).toBe("You're not allowed to see mentors with this status."); + }); + + it('Get mentors - unexpected error', async () => { + mentorService.getMentors.mockRejectedValue(new Error()); + + const res = await request(app) + .get('/mentors') + .set('userId', defautlUserId); // simulate authenticated user + + expect(res.statusCode).toBe(500); + expect(res.body.error).toBe('Internal Server Error'); + }); +}); \ No newline at end of file diff --git a/tests/services/mentor_service.test.js b/tests/services/mentor_service.test.js new file mode 100644 index 0000000..95097bf --- /dev/null +++ b/tests/services/mentor_service.test.js @@ -0,0 +1,139 @@ +const mentorService = require('../../services/mentor_service'); +const rolesService = require('../../services/roles_service'); +const repo = require('../../repositories/mentor_repository'); +const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require('../../errors'); + +jest.mock('../../repositories/mentor_repository'); +jest.mock('../../services/roles_service'); + +const defaultUserId = 42; + +describe('Mentor Service', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('createMentor', () => { + it('Create mentor in pending state success', async () => { + const mentorData = { about: 'I am a mentor', contact: 'mentor@example.com' }; + const createdMentor = { id: 1, ...mentorData, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.pending }; + repo.getMentorByUserId.mockResolvedValue(null); + repo.createMentor.mockResolvedValue(createdMentor); + + const result = await mentorService.createMentor(defaultUserId, mentorData); + + expect(repo.getMentorByUserId).toHaveBeenCalledWith(defaultUserId); + expect(repo.createMentor).toHaveBeenCalledWith({ + user_id: defaultUserId, + status: mentorService.MENTOR_STATUSES.pending, + about: mentorData.about, + contact: mentorData.contact, + }); + expect(result).toEqual(createdMentor); + }); + + it('Create mentor is not allowed if user already has a mentor', async () => { + const mentorData = { about: 'I am a mentor', contact: 'mentor@example.com' }; + const existingMentor = { id: 1, user_id: defaultUserId }; + repo.getMentorByUserId.mockResolvedValue(existingMentor); + repo.createMentor.mockResolvedValue(); + + await expect(mentorService.createMentor(defaultUserId, mentorData)).rejects + .toBeInstanceOf(MentorAlreadyExistsError); + + expect(repo.getMentorByUserId).toHaveBeenCalledWith(defaultUserId); + expect(repo.createMentor).not.toHaveBeenCalled(); + }); + }); + + describe('getMentors', () => { + it('Unauthenticated users only see active mentors', async () => { + const allowedStatuses = mentorService.generalVisitbleStatuses; + const mentor = { id: 1, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.active }; + + rolesService.getUserRoles.mockResolvedValue(); + repo.getMentors.mockResolvedValue([mentor]); + + await mentorService.getMentors(undefined, undefined, false); + + expect(rolesService.getUserRoles).not.toHaveBeenCalled(); + expect(repo.getMentors).toHaveBeenCalledWith(allowedStatuses, mentorService.baseFields); + }); + + it('Unauthenticated users cannot see non-active mentors', async () => { + rolesService.getUserRoles.mockResolvedValue(); + repo.getMentors.mockResolvedValue(); + + expect(mentorService.getMentors(undefined, mentorService.MENTOR_STATUSES.pending, false)).rejects + .toBeInstanceOf(DataRequiresElevatedRoleError); + + expect(rolesService.getUserRoles).not.toHaveBeenCalled(); + expect(repo.getMentors).not.toHaveBeenCalled(); + }); + + it('Member users only see active mentors', async () => { + const userRoles = [{ role_name: 'member' }]; + const allowedStatuses = mentorService.generalVisitbleStatuses; + const mentor = { id: 1, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.active }; + + rolesService.getUserRoles.mockResolvedValue(userRoles); + repo.getMentors.mockResolvedValue([mentor]); + + await mentorService.getMentors(defaultUserId, undefined, false); + + expect(repo.getMentors).toHaveBeenCalledWith(allowedStatuses, mentorService.baseFields); + }); + + it('Member users only see active mentors', async () => { + const userRoles = [{ role_name: 'member' }]; + const allowedStatuses = mentorService.generalVisitbleStatuses; + const mentor = { id: 1, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.active }; + + rolesService.getUserRoles.mockResolvedValue(userRoles); + repo.getMentors.mockResolvedValue([mentor]); + + await mentorService.getMentors(defaultUserId, undefined, false); + + expect(repo.getMentors).toHaveBeenCalledWith(allowedStatuses, mentorService.baseFields); + }); + + it('Member users cannot see non-active mentors', async () => { + const userRoles = [{ role_name: 'member' }]; + rolesService.getUserRoles.mockResolvedValue(userRoles); + repo.getMentors.mockResolvedValue(); + + expect(mentorService.getMentors(defaultUserId, mentorService.MENTOR_STATUSES.pending, false)).rejects + .toBeInstanceOf(DataRequiresElevatedRoleError); + + expect(repo.getMentors).not.toHaveBeenCalled(); + }); + + it('Admin users see all existing mentors', async () => { + const userRoles = [{ role_name: 'member' }, { role_name: 'admin' }]; + const mentorId = 1; + const allowedStatuses = mentorService.adminVisibleStatuses; + const mentor = { id: mentorId, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.active }; + + rolesService.getUserRoles.mockResolvedValue(userRoles); + repo.getMentors.mockResolvedValue([mentor]); + + await mentorService.getMentors(defaultUserId, undefined, true); + + expect(repo.getMentors).toHaveBeenCalledWith(allowedStatuses, mentorService.allFields); + }); + + it('Admin users see all existing mentors - filter by pending', async () => { + const userRoles = [{ role_name: 'member' }, { role_name: 'admin' }]; + const mentorId = 1; + const allowedStatuses = [mentorService.MENTOR_STATUSES.pending]; + const mentor = { id: mentorId, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.active }; + + rolesService.getUserRoles.mockResolvedValue(userRoles); + repo.getMentors.mockResolvedValue([mentor]); + + await mentorService.getMentors(defaultUserId, mentorService.MENTOR_STATUSES.pending, true); + + expect(repo.getMentors).toHaveBeenCalledWith(allowedStatuses, mentorService.allFields); + }); + }); +}); \ No newline at end of file From ef5453c5897610017378c2a92ec51ba1765a311d Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 14 Sep 2025 18:38:36 +0200 Subject: [PATCH 052/135] add mentor info to GET /user/id --- repositories/user_repository.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/repositories/user_repository.js b/repositories/user_repository.js index ccbf5be..b6d351a 100644 --- a/repositories/user_repository.js +++ b/repositories/user_repository.js @@ -16,7 +16,15 @@ async function createNewUser(values) { async function getUserInfoById(id, safeOutput = true) { const knex = getKnex(); - const user = await knex("users").where("id", id).first(); + const user = await knex("users") + .leftJoin("mentors", "users.id", "mentors.user_id") + .select( + "users.*", + "mentors.id as mentor_id", + knex.raw("CASE WHEN mentors.status = 'active' THEN TRUE ELSE FALSE END AS is_mentor_active") + ) + .where("users.id", id) + .first(); if (user !== undefined && safeOutput){ delete user["firebase_id"]; } From 23feb5ed16283577cccee0fb945cb7dd6293531e Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 14 Sep 2025 18:45:00 +0200 Subject: [PATCH 053/135] small update --- repositories/mentor_repository.js | 12 ++---------- routes/mentors.js | 2 +- tests/routes/mentors.test.js | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/repositories/mentor_repository.js b/repositories/mentor_repository.js index db3a4a6..96a03ae 100644 --- a/repositories/mentor_repository.js +++ b/repositories/mentor_repository.js @@ -22,16 +22,8 @@ async function createMentor(mentorData) { const [mentor] = await knex("mentors") .insert(mentorData) .returning("*"); - - //get user name - const user = await knex("users") - .select("name") - .where("id", mentor.user_id) - .first(); - return { - ...mentor, - name: user.name - }; + + return mentor; } module.exports = { getMentors, createMentor, getMentorByUserId }; \ No newline at end of file diff --git a/routes/mentors.js b/routes/mentors.js index d58c038..ecd0448 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -19,7 +19,7 @@ router.post("/mentors", verifyRoles([ROLE_NAMES.member]), async (req, res) => { }; const newMentor = await mentorService.createMentor(req.userId, mentorData); - res.status(201).json({ mentor_id: newMentor.id}); + res.status(201).json({ id: newMentor.id}); } catch(err) { logger.error(`Error while creating mentor application for user ${req.userId}: ${err.message}`); if (err instanceof MentorAlreadyExistsError) { diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index 793de0a..52c4758 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -52,7 +52,7 @@ describe('POST /mentors', () => { expect(mentorService.createMentor).toHaveBeenCalledWith(mockedMentor.user_id, payload); expect(res.statusCode).toBe(201); - expect(res.body).toStrictEqual({ mentor_id: mockedMentor.id }); + expect(res.body).toStrictEqual({ id: mockedMentor.id }); }); it('Create mentor application - user already has mentor entity', async () => { From bd29acdfbbb09f2449fc91a9577ddead831b3f0c Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 14 Sep 2025 18:47:36 +0200 Subject: [PATCH 054/135] linter --- repositories/mentor_repository.js | 4 ++-- services/mentor_service.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/repositories/mentor_repository.js b/repositories/mentor_repository.js index 96a03ae..25026f6 100644 --- a/repositories/mentor_repository.js +++ b/repositories/mentor_repository.js @@ -22,8 +22,8 @@ async function createMentor(mentorData) { const [mentor] = await knex("mentors") .insert(mentorData) .returning("*"); - - return mentor; + + return mentor; } module.exports = { getMentors, createMentor, getMentorByUserId }; \ No newline at end of file diff --git a/services/mentor_service.js b/services/mentor_service.js index 6dd3b0c..4d379ac 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -24,7 +24,8 @@ const privateFields = [ 'users.discord_nickname' ]; -const adminVisibleStatuses = [MENTOR_STATUSES.active, MENTOR_STATUSES.inactive, MENTOR_STATUSES.rejected, MENTOR_STATUSES.pending]; +const adminVisibleStatuses = [MENTOR_STATUSES.active, MENTOR_STATUSES.inactive, + MENTOR_STATUSES.rejected, MENTOR_STATUSES.pending]; const generalVisitbleStatuses = [MENTOR_STATUSES.active]; const allFields = [...baseFields, ...privateFields]; From 31db6c1827008788cd4a6d1ccbfbf00e18d42df1 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Mon, 15 Sep 2025 20:37:33 +0200 Subject: [PATCH 055/135] pr comments --- repositories/mentor_repository.js | 6 +++--- routes/mentors.js | 6 +++--- services/mentor_service.js | 13 +++---------- tests/routes/mentors.test.js | 2 +- tests/services/mentor_service.test.js | 23 ++++++++++++----------- utils.js | 12 +++++++++++- 6 files changed, 33 insertions(+), 29 deletions(-) diff --git a/repositories/mentor_repository.js b/repositories/mentor_repository.js index 25026f6..c82aa67 100644 --- a/repositories/mentor_repository.js +++ b/repositories/mentor_repository.js @@ -19,11 +19,11 @@ async function getMentorByUserId(userId) { async function createMentor(mentorData) { const knex = getKnex(); - const [mentor] = await knex("mentors") + const [res] = await knex("mentors") .insert(mentorData) - .returning("*"); + .returning("id"); - return mentor; + return res.id; } module.exports = { getMentors, createMentor, getMentorByUserId }; \ No newline at end of file diff --git a/routes/mentors.js b/routes/mentors.js index ecd0448..027df91 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -15,11 +15,11 @@ router.post("/mentors", verifyRoles([ROLE_NAMES.member]), async (req, res) => { try { const mentorData = { about: req.body.about, - contact: req.body.contact || null, + contact: req.body.contact, }; - const newMentor = await mentorService.createMentor(req.userId, mentorData); - res.status(201).json({ id: newMentor.id}); + const mentorId = await mentorService.createMentor(req.userId, mentorData); + res.status(201).json({ id: mentorId}); } catch(err) { logger.error(`Error while creating mentor application for user ${req.userId}: ${err.message}`); if (err instanceof MentorAlreadyExistsError) { diff --git a/services/mentor_service.js b/services/mentor_service.js index 4d379ac..f5434f0 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -1,14 +1,7 @@ const repo = require('../repositories/mentor_repository'); const rolesService = require('../services/roles_service'); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); -const { ROLE_NAMES } = require("../utils"); - -const MENTOR_STATUSES = { - active: 'active', - inactive: 'inactive', - rejected: 'rejected', - pending: 'pending', -}; +const { ROLE_NAMES, MENTOR_STATUSES } = require("../utils"); const baseFields = [ 'mentors.id as mentor_id', @@ -42,9 +35,9 @@ async function createMentor(userId, mentorData) { about: mentorData.about, contact: mentorData.contact, }; - const createdMentor = await repo.createMentor(mentor); + const mentorId = await repo.createMentor(mentor); // todo: send email to admins - return createdMentor; + return mentorId; } async function getMentors(userId, status, includeAdditionalFields) { diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index 52c4758..cd08309 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -38,7 +38,7 @@ describe('POST /mentors', () => { }); it('Create mentor application success', async () => { - mentorService.createMentor.mockResolvedValue(mockedMentor); + mentorService.createMentor.mockResolvedValue(mockedMentor.id); const payload = { contact: 'sample@test.com', diff --git a/tests/services/mentor_service.test.js b/tests/services/mentor_service.test.js index 95097bf..efb24dc 100644 --- a/tests/services/mentor_service.test.js +++ b/tests/services/mentor_service.test.js @@ -1,4 +1,5 @@ const mentorService = require('../../services/mentor_service'); +const { MENTOR_STATUSES } = require('../../utils'); const rolesService = require('../../services/roles_service'); const repo = require('../../repositories/mentor_repository'); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require('../../errors'); @@ -16,7 +17,7 @@ describe('Mentor Service', () => { describe('createMentor', () => { it('Create mentor in pending state success', async () => { const mentorData = { about: 'I am a mentor', contact: 'mentor@example.com' }; - const createdMentor = { id: 1, ...mentorData, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.pending }; + const createdMentor = { id: 1, ...mentorData, user_id: defaultUserId, status: MENTOR_STATUSES.pending }; repo.getMentorByUserId.mockResolvedValue(null); repo.createMentor.mockResolvedValue(createdMentor); @@ -25,7 +26,7 @@ describe('Mentor Service', () => { expect(repo.getMentorByUserId).toHaveBeenCalledWith(defaultUserId); expect(repo.createMentor).toHaveBeenCalledWith({ user_id: defaultUserId, - status: mentorService.MENTOR_STATUSES.pending, + status: MENTOR_STATUSES.pending, about: mentorData.about, contact: mentorData.contact, }); @@ -49,7 +50,7 @@ describe('Mentor Service', () => { describe('getMentors', () => { it('Unauthenticated users only see active mentors', async () => { const allowedStatuses = mentorService.generalVisitbleStatuses; - const mentor = { id: 1, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.active }; + const mentor = { id: 1, user_id: defaultUserId, status: MENTOR_STATUSES.active }; rolesService.getUserRoles.mockResolvedValue(); repo.getMentors.mockResolvedValue([mentor]); @@ -64,7 +65,7 @@ describe('Mentor Service', () => { rolesService.getUserRoles.mockResolvedValue(); repo.getMentors.mockResolvedValue(); - expect(mentorService.getMentors(undefined, mentorService.MENTOR_STATUSES.pending, false)).rejects + expect(mentorService.getMentors(undefined, MENTOR_STATUSES.pending, false)).rejects .toBeInstanceOf(DataRequiresElevatedRoleError); expect(rolesService.getUserRoles).not.toHaveBeenCalled(); @@ -74,7 +75,7 @@ describe('Mentor Service', () => { it('Member users only see active mentors', async () => { const userRoles = [{ role_name: 'member' }]; const allowedStatuses = mentorService.generalVisitbleStatuses; - const mentor = { id: 1, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.active }; + const mentor = { id: 1, user_id: defaultUserId, status: MENTOR_STATUSES.active }; rolesService.getUserRoles.mockResolvedValue(userRoles); repo.getMentors.mockResolvedValue([mentor]); @@ -87,7 +88,7 @@ describe('Mentor Service', () => { it('Member users only see active mentors', async () => { const userRoles = [{ role_name: 'member' }]; const allowedStatuses = mentorService.generalVisitbleStatuses; - const mentor = { id: 1, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.active }; + const mentor = { id: 1, user_id: defaultUserId, status: MENTOR_STATUSES.active }; rolesService.getUserRoles.mockResolvedValue(userRoles); repo.getMentors.mockResolvedValue([mentor]); @@ -102,7 +103,7 @@ describe('Mentor Service', () => { rolesService.getUserRoles.mockResolvedValue(userRoles); repo.getMentors.mockResolvedValue(); - expect(mentorService.getMentors(defaultUserId, mentorService.MENTOR_STATUSES.pending, false)).rejects + expect(mentorService.getMentors(defaultUserId, MENTOR_STATUSES.pending, false)).rejects .toBeInstanceOf(DataRequiresElevatedRoleError); expect(repo.getMentors).not.toHaveBeenCalled(); @@ -112,7 +113,7 @@ describe('Mentor Service', () => { const userRoles = [{ role_name: 'member' }, { role_name: 'admin' }]; const mentorId = 1; const allowedStatuses = mentorService.adminVisibleStatuses; - const mentor = { id: mentorId, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.active }; + const mentor = { id: mentorId, user_id: defaultUserId, status: MENTOR_STATUSES.active }; rolesService.getUserRoles.mockResolvedValue(userRoles); repo.getMentors.mockResolvedValue([mentor]); @@ -125,13 +126,13 @@ describe('Mentor Service', () => { it('Admin users see all existing mentors - filter by pending', async () => { const userRoles = [{ role_name: 'member' }, { role_name: 'admin' }]; const mentorId = 1; - const allowedStatuses = [mentorService.MENTOR_STATUSES.pending]; - const mentor = { id: mentorId, user_id: defaultUserId, status: mentorService.MENTOR_STATUSES.active }; + const allowedStatuses = [MENTOR_STATUSES.pending]; + const mentor = { id: mentorId, user_id: defaultUserId, status: MENTOR_STATUSES.active }; rolesService.getUserRoles.mockResolvedValue(userRoles); repo.getMentors.mockResolvedValue([mentor]); - await mentorService.getMentors(defaultUserId, mentorService.MENTOR_STATUSES.pending, true); + await mentorService.getMentors(defaultUserId, MENTOR_STATUSES.pending, true); expect(repo.getMentors).toHaveBeenCalledWith(allowedStatuses, mentorService.allFields); }); diff --git a/utils.js b/utils.js index 469451e..5c9b5fa 100644 --- a/utils.js +++ b/utils.js @@ -67,4 +67,14 @@ const APPL_STATUSES = { pending: "pending", }; -module.exports = { APPL_STATUSES, ROLE_NAMES, isRoleExists, loadRolesIntoMemory, getRole, getFirebaseSdkServiceAccount }; +const MENTOR_STATUSES = { + active: 'active', + inactive: 'inactive', + rejected: 'rejected', + pending: 'pending', +}; + +module.exports = { + APPL_STATUSES, MENTOR_STATUSES, ROLE_NAMES, + isRoleExists, loadRolesIntoMemory, getRole, getFirebaseSdkServiceAccount +}; From a8c228b820590bf7005637f98ef35f125f58c566 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Thu, 18 Sep 2025 18:46:14 +0200 Subject: [PATCH 056/135] updated swagger --- docs/catbytes_api_swagger.json | 112 +++++++++++++++++++++++++++++++-- docs/catbytes_api_swagger.yaml | 77 ++++++++++++++++++++++- 2 files changed, 183 insertions(+), 6 deletions(-) diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index 1f11067..cd0895b 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -297,6 +297,102 @@ } } }, + "/admin/grant-role": { + "post": { + "tags": [ + "roles" + ], + "summary": "✅ Grant role to user", + "description": "Grants a role to a user. Only accessible by users with admin privileges.", + "operationId": "grantRole", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "userId", + "roleId" + ], + "properties": { + "userId": { + "type": "integer", + "example": 42 + }, + "roleId": { + "type": "integer", + "example": 2 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Role successfully granted" + }, + "400": { + "description": "Missing required parameters" + }, + "403": { + "description": "User is not authorized to grant roles" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/admin/revoke-role": { + "post": { + "tags": [ + "roles" + ], + "summary": "✅ Revoke role from user", + "description": "Revokes a role from a user. Only accessible by users with admin privileges.", + "operationId": "revokeRole", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "userId", + "roleId" + ], + "properties": { + "userId": { + "type": "integer", + "example": 42 + }, + "roleId": { + "type": "integer", + "example": 2 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Role successfully revoked" + }, + "400": { + "description": "Missing required parameters" + }, + "403": { + "description": "User is not authorized to revoke roles" + }, + "500": { + "description": "Internal server error" + } + } + } + }, "/users": { "post": { "tags": [ @@ -433,7 +529,7 @@ "required": false }, { - "name": "X-Discord-Token", + "name": "X-Discord-Code", "in": "header", "description": "Authorization code returned by Discord OAuth2 flow", "schema": { @@ -599,6 +695,14 @@ "example": [ "mentee" ] + }, + "mentor_id": { + "type": "string", + "example": 10 + }, + "is_mentor_active": { + "type": "boolean", + "example": true } } } @@ -1571,7 +1675,7 @@ "tags": [ "mentors" ], - "summary": "Create a new mentor application", + "summary": "✅ Create a new mentor application", "description": "Creates a new mentor entity in 'pending' status. Available to all logged-in users. If user already has a mentor profile, creation is not allowed.\n", "operationId": "createMentor", "requestBody": { @@ -1614,7 +1718,7 @@ "tags": [ "mentors" ], - "summary": "Get mentor list", + "summary": "✅ Get mentor list", "description": "Returns a list of mentor applications. When filtering by 'pending' or 'rejected', or not filtering at all, user must have elevated (admin) role. Returns user_id and some aggregated data from user profile\n", "operationId": "getMentors", "parameters": [ @@ -1903,7 +2007,7 @@ } } }, - "/auth/discord": { + "/auth/discord": { "get": { "tags": [ "discord integration" diff --git a/docs/catbytes_api_swagger.yaml b/docs/catbytes_api_swagger.yaml index 0b86ac9..9503d21 100644 --- a/docs/catbytes_api_swagger.yaml +++ b/docs/catbytes_api_swagger.yaml @@ -201,6 +201,73 @@ paths: - id: 2 role_name: mentor is_privileged: true + /admin/grant-role: + post: + tags: + - roles + summary: ✅ Grant role to user + description: Grants a role to a user. Only accessible by users with admin privileges. + operationId: grantRole + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - userId + - roleId + properties: + userId: + type: integer + example: 42 + roleId: + type: integer + example: 2 + responses: + '200': + description: Role successfully granted + '400': + description: Missing required parameters + '403': + description: User is not authorized to grant roles + '500': + description: Internal server error + + /admin/revoke-role: + post: + tags: + - roles + summary: ✅ Revoke role from user + description: Revokes a role from a user. Only accessible by users with admin privileges. + operationId: revokeRole + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - userId + - roleId + properties: + userId: + type: integer + example: 42 + roleId: + type: integer + example: 2 + responses: + '200': + description: Role successfully revoked + '400': + description: Missing required parameters + '403': + description: User is not authorized to revoke roles + '500': + description: Internal server error + + /users: post: tags: @@ -405,6 +472,12 @@ paths: type: string example: - mentee + mentor_id: + type: string + example: 10 + is_mentor_active: + type: boolean + example: true '400': description: Invalid user id supplied content: @@ -1046,7 +1119,7 @@ paths: post: tags: - mentors - summary: Create a new mentor application + summary: ✅ Create a new mentor application description: | Creates a new mentor entity in 'pending' status. Available to all logged-in users. If user already has a mentor profile, creation is not allowed. operationId: createMentor @@ -1075,7 +1148,7 @@ paths: get: tags: - mentors - summary: Get mentor list + summary: ✅ Get mentor list description: | Returns a list of mentor applications. When filtering by 'pending' or 'rejected', or not filtering at all, user must have elevated (admin) role. Returns user_id and some aggregated data from user profile operationId: getMentors From 3d60d6362d26e9d02c86fc5e557076105bd7b1ed Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sat, 20 Sep 2025 18:45:01 +0200 Subject: [PATCH 057/135] get mentor by id --- middleware/authorization.js | 35 +++++++++++++++---- repositories/authorization_repository.js | 4 +-- repositories/mentor_repository.js | 11 +++++- routes/mentors.js | 23 +++++++++++-- services/auth_service.js | 2 -- services/mentor_service.js | 11 ++++-- tests/middleware/authorization.test.js | 3 +- tests/routes/mentors.test.js | 43 +++++++++++++++++++++++ tests/services/mentor_service.test.js | 44 ++++++++++++++++++++++++ 9 files changed, 158 insertions(+), 18 deletions(-) diff --git a/middleware/authorization.js b/middleware/authorization.js index c601c32..ebb2caf 100644 --- a/middleware/authorization.js +++ b/middleware/authorization.js @@ -16,6 +16,7 @@ function verifyRoles(roleNames) { }); if (hasRole) { + req.userRoles = userRoles; return next(); } @@ -33,6 +34,28 @@ const OWNED_ENTITIES = { MENTOR: 'mentors' }; + +async function isUserEntityOwner(entityTable, resourceId, userId) { + var ownershipField; + switch (entityTable) { + case OWNED_ENTITIES.USER: + ownershipField = "id"; + break; + case OWNED_ENTITIES.MENTOR: + ownershipField = "user_id"; + break; + default: + ownershipField = "created_by"; + break; + } + const result = await repo.verifyOwnership(entityTable, resourceId, userId, ownershipField); + return result.rows || result.length !== 0; +} + +async function verifyMentorOwnership(mentorId, userId) { + return isUserEntityOwner(OWNED_ENTITIES.MENTOR, mentorId, userId); +} + function verifyOwnership(entityTable) { return async (req, res, next) => { logger.debug(`Middleware invoked for: ${req.url}`); @@ -47,18 +70,16 @@ function verifyOwnership(entityTable) { return respondWithError(res, 400, `Invalid request: missing ${entityTable} ID or user information`); } - let resource; + let isOwner; if (entityTable === OWNED_ENTITIES.USER) { - resource = userId.toString() === resourceId.toString(); + isOwner = userId.toString() === resourceId.toString(); } else { - const result = await repo.verifyOwnership(entityTable, resourceId, userId); - resource = result.rows && result.length !== 0; + isOwner = await isUserEntityOwner(entityTable, resourceId, userId); } - if (!resource) { + if (!isOwner) { return respondWithError(res, 403, "You're not allowed to edit this resource"); } - next(); } catch (err) { logger.error(err, 'Error verifying ownership'); @@ -68,4 +89,4 @@ function verifyOwnership(entityTable) { } -module.exports = { verifyRoles, verifyOwnership, OWNED_ENTITIES }; \ No newline at end of file +module.exports = { verifyRoles, verifyOwnership, verifyMentorOwnership, OWNED_ENTITIES }; \ No newline at end of file diff --git a/repositories/authorization_repository.js b/repositories/authorization_repository.js index c3d2b14..3271900 100644 --- a/repositories/authorization_repository.js +++ b/repositories/authorization_repository.js @@ -13,9 +13,9 @@ async function getRolesByUserId(id) { .select("roles.role_name", "roles.id as role_id"); } -async function verifyOwnership(entityTable, resourceId, userId) { +async function verifyOwnership(entityTable, resourceId, userId, fieldToAssess) { const knex = getKnex(); - return await knex(entityTable).where("id", resourceId).andWhere("created_by", userId).select("*"); + return await knex(entityTable).where("id", resourceId).andWhere(fieldToAssess, userId).select("*"); } module.exports = { verifyRole, verifyOwnership, getRolesByUserId }; \ No newline at end of file diff --git a/repositories/mentor_repository.js b/repositories/mentor_repository.js index c82aa67..75e471d 100644 --- a/repositories/mentor_repository.js +++ b/repositories/mentor_repository.js @@ -17,6 +17,15 @@ async function getMentorByUserId(userId) { .first(); } +async function getMentorById(allowedStatuses, selectedFields, mentorId) { + const knex = getKnex(); + return await knex("mentors") + .join("users", "mentors.user_id", "users.id") + .where("mentors.id", mentorId) + .whereIn("mentors.status", allowedStatuses) + .select(selectedFields).first(); +} + async function createMentor(mentorData) { const knex = getKnex(); const [res] = await knex("mentors") @@ -26,4 +35,4 @@ async function createMentor(mentorData) { return res.id; } -module.exports = { getMentors, createMentor, getMentorByUserId }; \ No newline at end of file +module.exports = { getMentors, createMentor, getMentorByUserId, getMentorById }; \ No newline at end of file diff --git a/routes/mentors.js b/routes/mentors.js index 027df91..411cf06 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -3,8 +3,8 @@ const express = require("express"); const router = express.Router(); const mentorService = require("../services/mentor_service"); const { ROLE_NAMES } = require("../utils"); -const { verifyRoles } = require("../middleware/authorization"); -const { respondWithError } = require("./helpers"); +const { verifyRoles, verifyMentorOwnership } = require("../middleware/authorization"); +const { isValidIntegerId, respondWithError } = require("./helpers"); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); const logger = require('../logger')(__filename); @@ -45,4 +45,23 @@ router.get("/mentors", async (req, res) => { } }); +// Get mentor by ID +router.get("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) => { + const { id } = req.params; + if (!isValidIntegerId(id)) { + return respondWithError(res, 400, "Invalid user id supplied"); + } + try { + const isOwner = await verifyMentorOwnership(id, req.userId); + const mentorInfo = await mentorService.getMentorById(req.userRoles, id, isOwner); + if (!mentorInfo) { + return respondWithError(res, 404, "Mentor not found"); + } + res.json(mentorInfo); + } catch (err) { + logger.error(err); + respondWithError(res); + } +}); + module.exports = router; \ No newline at end of file diff --git a/services/auth_service.js b/services/auth_service.js index 7cdcc67..4f7c133 100644 --- a/services/auth_service.js +++ b/services/auth_service.js @@ -43,8 +43,6 @@ async function handleFirebaseAuth(firebaseToken) { } } -; - async function handleDiscordAuth(code){ try { // exchange code for access token diff --git a/services/mentor_service.js b/services/mentor_service.js index f5434f0..a8e23a5 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -56,9 +56,14 @@ async function getMentors(userId, status, includeAdditionalFields) { return await repo.getMentors(statusesFilter, selectedFields); } -async function getEligibleMentorStatuses(userRoles) { +async function getMentorById(userRoles, mentorId, isOwner) { + const allowedStatuses = await getEligibleMentorStatuses(userRoles, isOwner); + return repo.getMentorById(allowedStatuses, allFields, mentorId); +} + +async function getEligibleMentorStatuses(userRoles, isOwner) { const isAdmin = userRoles.some(role => role.role_name === ROLE_NAMES.admin); - if (isAdmin) { + if (isAdmin || isOwner) { return adminVisibleStatuses; } else { return generalVisitbleStatuses; @@ -66,7 +71,7 @@ async function getEligibleMentorStatuses(userRoles) { } module.exports = { - getMentors, createMentor, + getMentors, createMentor, getMentorById, adminVisibleStatuses, generalVisitbleStatuses, allFields, baseFields, privateFields, MENTOR_STATUSES diff --git a/tests/middleware/authorization.test.js b/tests/middleware/authorization.test.js index 39dee4d..b7ada50 100644 --- a/tests/middleware/authorization.test.js +++ b/tests/middleware/authorization.test.js @@ -53,6 +53,7 @@ describe('verifyRoles', () => { await verifyRoles(rolesToVerify)(req, res, next); if (expectedNextCalled) { + expect(req.userRoles).toBe(userRoles); expect(next).toHaveBeenCalled(); } else { expect(res.statusCode).toBe(403); @@ -101,7 +102,7 @@ describe('VerifyOwnership', () => { const res = {}; const next = jest.fn(); - jest.spyOn(require('../../repositories/authorization_repository'), 'verifyOwnership').mockResolvedValue({}); + jest.spyOn(require('../../repositories/authorization_repository'), 'verifyOwnership').mockResolvedValue([]); await verifyOwnership(OWNED_ENTITIES.MENTOR)(req, res, next); diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index cd08309..df51eab 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -27,6 +27,7 @@ jest.mock('../../middleware/authorization', () => { req.userRoles = defaultUserRoles; next(); }), + verifyMentorOwnership: jest.fn(() => false), }; }); @@ -127,6 +128,48 @@ describe('GET /mentors', () => { .get('/mentors') .set('userId', defautlUserId); // simulate authenticated user + expect(res.statusCode).toBe(500); + expect(res.body.error).toBe('Internal Server Error'); + }); +}); + +describe('GET /mentors/:id', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Get mentor by ID success', async () => { + mentorService.getMentorById.mockResolvedValue(mockedMentor); + + const res = await request(app) + .get(`/mentors/${mockedMentor.id}`) + .set('userId', defautlUserId); + + expect(mentorService.getMentorById).toHaveBeenCalledWith(defaultUserRoles, mockedMentor.id.toString(), false); + + expect(res.statusCode).toBe(200); + expect(res.body).toStrictEqual(mockedMentor); + }); + + it('Get mentor by ID - not found', async () => { + mentorService.getMentorById.mockResolvedValue(null); + const res = await request(app) + .get(`/mentors/9999`) + .set('userId', defautlUserId); + + expect(mentorService.getMentorById).toHaveBeenCalledWith(defaultUserRoles, "9999", false); + + expect(res.statusCode).toBe(404); + expect(res.body.error).toBe('Mentor not found'); + }); + + it('Get mentor by ID - unexpected error', async () => { + mentorService.getMentorById.mockRejectedValue(new Error()); + + const res = await request(app) + .get(`/mentors/${mockedMentor.id}`) + .set('userId', defautlUserId); + expect(res.statusCode).toBe(500); expect(res.body.error).toBe('Internal Server Error'); }); diff --git a/tests/services/mentor_service.test.js b/tests/services/mentor_service.test.js index efb24dc..ed3258b 100644 --- a/tests/services/mentor_service.test.js +++ b/tests/services/mentor_service.test.js @@ -137,4 +137,48 @@ describe('Mentor Service', () => { expect(repo.getMentors).toHaveBeenCalledWith(allowedStatuses, mentorService.allFields); }); }); + + describe('getMentorById', () => { + it('Returns mentor to member users only if active', async () => { + const mentorId = 1; + const allowedStatuses = mentorService.generalVisitbleStatuses; + const userRoles = [{ role_name: 'member' }]; + const mentor = { id: mentorId, user_id: defaultUserId, status: MENTOR_STATUSES.active }; + + repo.getMentorById.mockResolvedValue(mentor); + + const result = await mentorService.getMentorById(userRoles, mentorId, false); + + expect(repo.getMentorById).toHaveBeenCalledWith(allowedStatuses, mentorService.allFields, mentorId); + expect(result).toEqual(mentor); + }); + + it('Returns mentor to owner even if not active', async () => { + const mentorId = 1; + const allowedStatuses = mentorService.adminVisibleStatuses; + const userRoles = [{ role_name: 'member' }]; + const mentor = { id: mentorId, user_id: defaultUserId, status: MENTOR_STATUSES.pending }; + + repo.getMentorById.mockResolvedValue(mentor); + + const result = await mentorService.getMentorById(userRoles, mentorId, true); + + expect(repo.getMentorById).toHaveBeenCalledWith(allowedStatuses, mentorService.allFields, mentorId); + expect(result).toEqual(mentor); + }); + + it('Returns mentor to admin even if not active', async () => { + const mentorId = 1; + const allowedStatuses = mentorService.adminVisibleStatuses; + const userRoles = [{ role_name: 'member' }, { role_name: 'admin' }]; + const mentor = { id: mentorId, user_id: defaultUserId, status: MENTOR_STATUSES.pending }; + + repo.getMentorById.mockResolvedValue(mentor); + + const result = await mentorService.getMentorById(userRoles, mentorId, false); + + expect(repo.getMentorById).toHaveBeenCalledWith(allowedStatuses, mentorService.allFields, mentorId); + expect(result).toEqual(mentor); + }); + }); }); \ No newline at end of file From 4e17c8de73b0a411b598f731091c0c982522e8c9 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 28 Sep 2025 10:31:32 +0200 Subject: [PATCH 058/135] send email on new mentor application --- repositories/mentor_repository.js | 11 +- repositories/roles_repository.js | 11 +- services/mailer_service.js | 24 +- services/mentor_service.js | 9 +- services/roles_service.js | 6 +- .../email/new_mentor_application_email.hbs | 205 ++++++++++++++++++ 6 files changed, 258 insertions(+), 8 deletions(-) create mode 100644 templates/email/new_mentor_application_email.hbs diff --git a/repositories/mentor_repository.js b/repositories/mentor_repository.js index 75e471d..b52055d 100644 --- a/repositories/mentor_repository.js +++ b/repositories/mentor_repository.js @@ -28,11 +28,18 @@ async function getMentorById(allowedStatuses, selectedFields, mentorId) { async function createMentor(mentorData) { const knex = getKnex(); - const [res] = await knex("mentors") + + const [createdMentor] = await knex("mentors") .insert(mentorData) .returning("id"); - return res.id; + const result = await knex("mentors") + .join("users", "mentors.user_id", "users.id") + .select("mentors.id", "users.name", "mentors.about") + .where("mentors.id", createdMentor.id) + .first(); + + return result; } module.exports = { getMentors, createMentor, getMentorByUserId, getMentorById }; \ No newline at end of file diff --git a/repositories/roles_repository.js b/repositories/roles_repository.js index bb8823d..31c4aa8 100644 --- a/repositories/roles_repository.js +++ b/repositories/roles_repository.js @@ -18,4 +18,13 @@ async function removeRoleFromUser(userId, roleId) { .del(); } -module.exports = { getAllRoles, assignRoleToUser, removeRoleFromUser }; \ No newline at end of file +async function getEmailsByRole(role) { + const knex = getKnex(); + return await knex("user_roles") + .join("roles", "user_roles.role_id", "roles.id") + .join("users", "user_roles.user_id", "users.id") + .where("roles.role_name", role) + .pluck("users.email"); +} + +module.exports = { getAllRoles, assignRoleToUser, removeRoleFromUser, getEmailsByRole }; \ No newline at end of file diff --git a/services/mailer_service.js b/services/mailer_service.js index 75d1317..a5812c1 100644 --- a/services/mailer_service.js +++ b/services/mailer_service.js @@ -116,4 +116,26 @@ async function sendEmailOnApplicationStatusChange(email, name, status) { } } -module.exports = { initMailer, sendEmailOnApplicationStatusChange }; \ No newline at end of file +async function sendEmailOnNewMentorApplication(emails, mentorApplication) { + try { + const mailOptions = { + from: mailerConfig.user, + to: emails, + subject: "New Mentor Application Submitted", + template: "new_mentor_application_email", + context: { + name: mentorApplication.name, + about: mentorApplication.about, + reviewLink: `${webplatformUrl}applications`, + catbytesLink: webplatformUrl, + }, + }; + + await mailTransporter.sendMail(mailOptions); + } + catch (err) { + logger.error(`Error sending new mentor application email to ${emails}: ${err.message}`); + } +} + +module.exports = { initMailer, sendEmailOnApplicationStatusChange, sendEmailOnNewMentorApplication }; \ No newline at end of file diff --git a/services/mentor_service.js b/services/mentor_service.js index a8e23a5..7022763 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -1,5 +1,6 @@ const repo = require('../repositories/mentor_repository'); const rolesService = require('../services/roles_service'); +const mailerService = require('../services/mailer_service'); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); const { ROLE_NAMES, MENTOR_STATUSES } = require("../utils"); @@ -35,9 +36,11 @@ async function createMentor(userId, mentorData) { about: mentorData.about, contact: mentorData.contact, }; - const mentorId = await repo.createMentor(mentor); - // todo: send email to admins - return mentorId; + const mentorInfo = await repo.createMentor(mentor); + + const adminEmails = await rolesService.getAdminEmails(); + await mailerService.sendEmailOnNewMentorApplication(adminEmails, mentorInfo); + return mentorInfo.id; } async function getMentors(userId, status, includeAdditionalFields) { diff --git a/services/roles_service.js b/services/roles_service.js index f312060..235dc00 100644 --- a/services/roles_service.js +++ b/services/roles_service.js @@ -19,4 +19,8 @@ async function getAllRoles() { return await repo.getAllRoles(); } -module.exports = { getAllRoles, assignRoleToUser, getUserRoles }; \ No newline at end of file +async function getAdminEmails() { + return await repo.getEmailsByRole(utils.ROLE_NAMES.admin); +} + +module.exports = { getAllRoles, assignRoleToUser, getUserRoles, getAdminEmails }; \ No newline at end of file diff --git a/templates/email/new_mentor_application_email.hbs b/templates/email/new_mentor_application_email.hbs new file mode 100644 index 0000000..f2b5e44 --- /dev/null +++ b/templates/email/new_mentor_application_email.hbs @@ -0,0 +1,205 @@ + + + + + + + + + + New Mentor Application + + + + + + + + + + \ No newline at end of file From db42198acfde6878a7716682938bda275b554ac3 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 28 Sep 2025 10:35:00 +0200 Subject: [PATCH 059/135] updated tests --- tests/services/mentor_service.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/services/mentor_service.test.js b/tests/services/mentor_service.test.js index ed3258b..fc23036 100644 --- a/tests/services/mentor_service.test.js +++ b/tests/services/mentor_service.test.js @@ -1,11 +1,13 @@ const mentorService = require('../../services/mentor_service'); const { MENTOR_STATUSES } = require('../../utils'); const rolesService = require('../../services/roles_service'); +const mailerService = require('../../services/mailer_service'); const repo = require('../../repositories/mentor_repository'); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require('../../errors'); jest.mock('../../repositories/mentor_repository'); jest.mock('../../services/roles_service'); +jest.mock('../../services/mailer_service'); const defaultUserId = 42; @@ -17,7 +19,7 @@ describe('Mentor Service', () => { describe('createMentor', () => { it('Create mentor in pending state success', async () => { const mentorData = { about: 'I am a mentor', contact: 'mentor@example.com' }; - const createdMentor = { id: 1, ...mentorData, user_id: defaultUserId, status: MENTOR_STATUSES.pending }; + const createdMentor = { id: 1, name: 'Name', about: mentorData.about }; repo.getMentorByUserId.mockResolvedValue(null); repo.createMentor.mockResolvedValue(createdMentor); @@ -30,7 +32,9 @@ describe('Mentor Service', () => { about: mentorData.about, contact: mentorData.contact, }); - expect(result).toEqual(createdMentor); + expect(rolesService.getAdminEmails).toHaveBeenCalled(); + expect(mailerService.sendEmailOnNewMentorApplication).toHaveBeenCalled(); + expect(result).toEqual(createdMentor.id); }); it('Create mentor is not allowed if user already has a mentor', async () => { From ca14bd52578a8dc39b0385a0722a70398e190a97 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 28 Sep 2025 10:41:07 +0200 Subject: [PATCH 060/135] updated email template --- templates/email/new_mentor_application_email.hbs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/email/new_mentor_application_email.hbs b/templates/email/new_mentor_application_email.hbs index f2b5e44..a8a3424 100644 --- a/templates/email/new_mentor_application_email.hbs +++ b/templates/email/new_mentor_application_email.hbs @@ -139,7 +139,7 @@

New Mentor Application

A new candidate has applied to join the mentor team

- Notification Cat + Happy Cat

Dear Mentor,

@@ -149,12 +149,12 @@

- Applicant Name: {{applicantName}} + Applicant Name: {{name}}

About:
- {{applicantAbout}} + {{about}}

From ffbd76ff603965a38e04b8ea089ed94d91d5cdc4 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sun, 28 Sep 2025 19:53:57 +0100 Subject: [PATCH 061/135] adds update mentor status endpoint --- repositories/mentor_repository.js | 9 ++++++++- routes/mentors.js | 25 ++++++++++++++++++++++++- services/mentor_service.js | 12 +++++++++++- utils.js | 7 ++++++- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/repositories/mentor_repository.js b/repositories/mentor_repository.js index 75e471d..cf3b56a 100644 --- a/repositories/mentor_repository.js +++ b/repositories/mentor_repository.js @@ -35,4 +35,11 @@ async function createMentor(mentorData) { return res.id; } -module.exports = { getMentors, createMentor, getMentorByUserId, getMentorById }; \ No newline at end of file +async function updateMentorStatus(mentorId, status) { + const knex = getKnex(); + return await knex("mentors") + .where("id", mentorId) + .update({ status }); +} + +module.exports = { getMentors, createMentor, getMentorByUserId, getMentorById, updateMentorStatus }; \ No newline at end of file diff --git a/routes/mentors.js b/routes/mentors.js index 411cf06..cc3749f 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -2,7 +2,7 @@ const express = require("express"); const router = express.Router(); const mentorService = require("../services/mentor_service"); -const { ROLE_NAMES } = require("../utils"); +const { ROLE_NAMES, ALLOWED_MENTOR_STATUSES } = require("../utils"); const { verifyRoles, verifyMentorOwnership } = require("../middleware/authorization"); const { isValidIntegerId, respondWithError } = require("./helpers"); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); @@ -64,4 +64,27 @@ router.get("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) = } }); +// Update mentor status +router.patch("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) => { + const { id } = req.params; + const { status } = req.body; + if(!isValidIntegerId(id)) { + return respondWithError(res, 400, "Invalid user id supplied"); + } + if(!ALLOWED_MENTOR_STATUSES[status]) { + return respondWithError(res, 400, "Invalid status supplied"); + } + try { + const isOwner = await verifyMentorOwnership(id, req.userId); + const mentorId = await mentorService.updateMentorStatus(req.userRoles, id, status, isOwner); + res.json(mentorId); + } catch (err) { + logger.error(err); + if (err instanceof DataRequiresElevatedRoleError) { + return respondWithError(res, 403, err.message); + } + respondWithError(res); + } +}) + module.exports = router; \ No newline at end of file diff --git a/services/mentor_service.js b/services/mentor_service.js index a8e23a5..5401539 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -70,8 +70,18 @@ async function getEligibleMentorStatuses(userRoles, isOwner) { } } +async function updateMentorStatus(userRoles, mentorId, status, isOwner) { + const isAdmin = userRoles.some(role => role.role_name === ROLE_NAMES.admin); + if(isAdmin || isOwner) { + return repo.updateMentorStatus(mentorId, status) + } + else { + throw new DataRequiresElevatedRoleError('Action requires elevated role'); + } +} + module.exports = { - getMentors, createMentor, getMentorById, + getMentors, createMentor, getMentorById, updateMentorStatus, adminVisibleStatuses, generalVisitbleStatuses, allFields, baseFields, privateFields, MENTOR_STATUSES diff --git a/utils.js b/utils.js index 5c9b5fa..79d757a 100644 --- a/utils.js +++ b/utils.js @@ -74,7 +74,12 @@ const MENTOR_STATUSES = { pending: 'pending', }; +const ALLOWED_MENTOR_STATUSES = { + active: 'active', + inactive: 'inactive', +} + module.exports = { - APPL_STATUSES, MENTOR_STATUSES, ROLE_NAMES, + APPL_STATUSES, MENTOR_STATUSES, ALLOWED_MENTOR_STATUSES, ROLE_NAMES, isRoleExists, loadRolesIntoMemory, getRole, getFirebaseSdkServiceAccount }; From d02596486492fd2c9385a7741cc15b8e8bac9004 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Mon, 29 Sep 2025 09:03:38 +0100 Subject: [PATCH 062/135] resolves most PR comments --- repositories/mentor_repository.js | 6 ++--- routes/mentors.js | 3 --- services/mentor_service.js | 39 +++++++++++++++++++++++++++---- utils.js | 7 +----- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/repositories/mentor_repository.js b/repositories/mentor_repository.js index cf3b56a..92e5601 100644 --- a/repositories/mentor_repository.js +++ b/repositories/mentor_repository.js @@ -35,11 +35,11 @@ async function createMentor(mentorData) { return res.id; } -async function updateMentorStatus(mentorId, status) { +async function updateMentorById(mentorId, updates) { const knex = getKnex(); return await knex("mentors") .where("id", mentorId) - .update({ status }); + .update(updates); } -module.exports = { getMentors, createMentor, getMentorByUserId, getMentorById, updateMentorStatus }; \ No newline at end of file +module.exports = { getMentors, createMentor, getMentorByUserId, getMentorById, updateMentorById }; \ No newline at end of file diff --git a/routes/mentors.js b/routes/mentors.js index cc3749f..25f2095 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -71,9 +71,6 @@ router.patch("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) if(!isValidIntegerId(id)) { return respondWithError(res, 400, "Invalid user id supplied"); } - if(!ALLOWED_MENTOR_STATUSES[status]) { - return respondWithError(res, 400, "Invalid status supplied"); - } try { const isOwner = await verifyMentorOwnership(id, req.userId); const mentorId = await mentorService.updateMentorStatus(req.userRoles, id, status, isOwner); diff --git a/services/mentor_service.js b/services/mentor_service.js index 5401539..a13ea64 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -2,6 +2,9 @@ const repo = require('../repositories/mentor_repository'); const rolesService = require('../services/roles_service'); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); const { ROLE_NAMES, MENTOR_STATUSES } = require("../utils"); +const { updateUserById } = require('../repositories/user_repository'); +const { getUserById } = require('./user_service'); +const { assignRoleToUser, removeRoleFromUser } = require('../repositories/roles_repository'); const baseFields = [ 'mentors.id as mentor_id', @@ -71,13 +74,39 @@ async function getEligibleMentorStatuses(userRoles, isOwner) { } async function updateMentorStatus(userRoles, mentorId, status, isOwner) { + const mentorData = await getMentorById(userRoles, mentorId, isOwner); const isAdmin = userRoles.some(role => role.role_name === ROLE_NAMES.admin); - if(isAdmin || isOwner) { - return repo.updateMentorStatus(mentorId, status) - } - else { - throw new DataRequiresElevatedRoleError('Action requires elevated role'); + const allowedStatusesForOwner = [ + MENTOR_STATUSES.active, + MENTOR_STATUSES.inactive, + ]; + + if(isAdmin) { + // if admin approves mentor (pending -> active -> add mentor role) + if(mentorData.status === MENTOR_STATUSES.pending && status === MENTOR_STATUSES.active) { + repo.updateMentorById(mentorId, { status }); + assignRoleToUser(mentorData.user_id, 2); + return "Mentor application approved" + } else if(status === MENTOR_STATUSES.rejected) { + // if admin rejects mentor (change mentor status to rejected => remove mentor role) + repo.updateMentorById(mentorId, { status }); + removeRoleFromUser(mentorData.user_id, 2); + return "Mentor application rejected" + } else { + // any other status changes allowed without side effect actions + repo.updateMentorById(mentorId, { status }); + return "Mentor application status updated" + } + } + if(isOwner) { + if(allowedStatusesForOwner.includes(status)) { + if(![MENTOR_STATUSES.pending, MENTOR_STATUSES.rejected].includes(mentorData.status)) { + return repo.updateMentorById(mentorId, { status }) + } + } } + + throw new DataRequiresElevatedRoleError("You're not allowed to edit this resource"); } module.exports = { diff --git a/utils.js b/utils.js index 79d757a..5c9b5fa 100644 --- a/utils.js +++ b/utils.js @@ -74,12 +74,7 @@ const MENTOR_STATUSES = { pending: 'pending', }; -const ALLOWED_MENTOR_STATUSES = { - active: 'active', - inactive: 'inactive', -} - module.exports = { - APPL_STATUSES, MENTOR_STATUSES, ALLOWED_MENTOR_STATUSES, ROLE_NAMES, + APPL_STATUSES, MENTOR_STATUSES, ROLE_NAMES, isRoleExists, loadRolesIntoMemory, getRole, getFirebaseSdkServiceAccount }; From b2e1547b85a91694d973c49be1ec70705b575a63 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Mon, 29 Sep 2025 09:05:17 +0100 Subject: [PATCH 063/135] removes unused import --- routes/mentors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/mentors.js b/routes/mentors.js index 25f2095..a72d047 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -2,7 +2,7 @@ const express = require("express"); const router = express.Router(); const mentorService = require("../services/mentor_service"); -const { ROLE_NAMES, ALLOWED_MENTOR_STATUSES } = require("../utils"); +const { ROLE_NAMES } = require("../utils"); const { verifyRoles, verifyMentorOwnership } = require("../middleware/authorization"); const { isValidIntegerId, respondWithError } = require("./helpers"); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); From 62ab8dec24fd28c1ded7003c0f37c717af6b0bb0 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Wed, 1 Oct 2025 08:43:09 +0100 Subject: [PATCH 064/135] updates swagger and adds tests --- docs/catbytes_api_swagger.json | 9 +- docs/catbytes_api_swagger.yaml | 6 +- routes/mentors.js | 4 + services/mentor_service.js | 11 ++- tests/routes/mentors.test.js | 58 ++++++++++++- tests/services/mentor_service.test.js | 116 ++++++++++++++++++++++++++ 6 files changed, 190 insertions(+), 14 deletions(-) diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index cd0895b..bb9d4fd 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -1760,7 +1760,7 @@ "tags": [ "mentors" ], - "summary": "Get mentor by ID", + "summary": "✅ Get mentor by ID", "description": "Returns full mentor data if user is logged in. Pending/rejected mentors require admin role.\n", "operationId": "getMentorById", "parameters": [ @@ -1836,8 +1836,8 @@ "tags": [ "mentors" ], - "summary": "Update mentor status", - "description": "Only users with admin or mentor role can change mentor status.\n", + "summary": "✅ Update mentor status", + "description": "Available to users with elevated role (admin) only or to user owning the mentor card.\n", "operationId": "patchMentorStatus", "parameters": [ { @@ -1861,7 +1861,8 @@ "enum": [ "active", "rejected", - "inactive" + "inactive", + "pending" ] } } diff --git a/docs/catbytes_api_swagger.yaml b/docs/catbytes_api_swagger.yaml index 9503d21..87c74a9 100644 --- a/docs/catbytes_api_swagger.yaml +++ b/docs/catbytes_api_swagger.yaml @@ -1177,7 +1177,7 @@ paths: get: tags: - mentors - summary: Get mentor by ID + summary: ✅ Get mentor by ID description: | Returns full mentor data if user is logged in. Pending/rejected mentors require admin role. operationId: getMentorById @@ -1228,9 +1228,9 @@ paths: patch: tags: - mentors - summary: Update mentor status + summary: ✅ Update mentor status description: | - Only users with admin or mentor role can change mentor status. + Available to users with elevated role (admin) only or to user owning the mentor card. operationId: patchMentorStatus parameters: - name: mentor_id diff --git a/routes/mentors.js b/routes/mentors.js index a72d047..8da37b0 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -73,6 +73,10 @@ router.patch("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) } try { const isOwner = await verifyMentorOwnership(id, req.userId); + const mentorInfo = await mentorService.getMentorById(req.userRoles, id, isOwner); + if (!mentorInfo) { + return respondWithError(res, 404, "Mentor not found"); + } const mentorId = await mentorService.updateMentorStatus(req.userRoles, id, status, isOwner); res.json(mentorId); } catch (err) { diff --git a/services/mentor_service.js b/services/mentor_service.js index a13ea64..0813fdb 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -82,21 +82,20 @@ async function updateMentorStatus(userRoles, mentorId, status, isOwner) { ]; if(isAdmin) { + let updatedMentorId; // if admin approves mentor (pending -> active -> add mentor role) if(mentorData.status === MENTOR_STATUSES.pending && status === MENTOR_STATUSES.active) { - repo.updateMentorById(mentorId, { status }); + updatedMentorId = await repo.updateMentorById(mentorId, { status }); assignRoleToUser(mentorData.user_id, 2); - return "Mentor application approved" } else if(status === MENTOR_STATUSES.rejected) { // if admin rejects mentor (change mentor status to rejected => remove mentor role) - repo.updateMentorById(mentorId, { status }); + updatedMentorId = await repo.updateMentorById(mentorId, { status }); removeRoleFromUser(mentorData.user_id, 2); - return "Mentor application rejected" } else { // any other status changes allowed without side effect actions - repo.updateMentorById(mentorId, { status }); - return "Mentor application status updated" + updatedMentorId = await repo.updateMentorById(mentorId, { status }); } + return updatedMentorId; } if(isOwner) { if(allowedStatusesForOwner.includes(status)) { diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index df51eab..f1c87df 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -173,4 +173,60 @@ describe('GET /mentors/:id', () => { expect(res.statusCode).toBe(500); expect(res.body.error).toBe('Internal Server Error'); }); -}); \ No newline at end of file +}); + +describe('PATCH /mentors/:id', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Update mentor status success', async () => { + mentorService.getMentorById.mockResolvedValue({ ...mockedMentor }); + mentorService.updateMentorStatus.mockResolvedValue(mockedMentor.id); + + const res = await request(app) + .patch(`/mentors/${mockedMentor.id}`) + .send({ status: 'active' }) + .set('userId', defautlUserId); + + expect(mentorService.updateMentorStatus) + .toHaveBeenCalledWith(defaultUserRoles, mockedMentor.id.toString(), 'active', false); + + expect(res.statusCode).toBe(200); + expect(res.body).toStrictEqual(mockedMentor.id); + }); + + it('Update mentor status - not authorized', async () => { + mentorService.getMentorById.mockResolvedValue({ ...mockedMentor }); + mentorService.updateMentorStatus.mockRejectedValue( + new DataRequiresElevatedRoleError("You're not allowed to edit this resource") + ); + + const res = await request(app) + .patch(`/mentors/${mockedMentor.id}`) + .send({ status: 'rejected' }) + .set('userId', defautlUserId); + + expect(mentorService.updateMentorStatus) + .toHaveBeenCalledWith(defaultUserRoles, mockedMentor.id.toString(), 'rejected', false); + + expect(res.statusCode).toBe(403); + expect(res.body.error).toBe("You're not allowed to edit this resource"); + }); + + it('Update mentor status - mentor not found', async () => { + mentorService.getMentorById.mockResolvedValue(null); + + const res = await request(app) + .patch('/mentors/9999') // some non-existing mentorId + .send({ status: 'active' }) + .set('userId', defautlUserId); + + expect(mentorService.getMentorById) + .toHaveBeenCalledWith(defaultUserRoles, '9999', false); + + expect(mentorService.updateMentorStatus).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(404); + expect(res.body.error).toBe('Mentor not found'); + }); +}) \ No newline at end of file diff --git a/tests/services/mentor_service.test.js b/tests/services/mentor_service.test.js index ed3258b..abba316 100644 --- a/tests/services/mentor_service.test.js +++ b/tests/services/mentor_service.test.js @@ -2,9 +2,11 @@ const mentorService = require('../../services/mentor_service'); const { MENTOR_STATUSES } = require('../../utils'); const rolesService = require('../../services/roles_service'); const repo = require('../../repositories/mentor_repository'); +const rolesRepo = require('../../repositories/roles_repository'); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require('../../errors'); jest.mock('../../repositories/mentor_repository'); +jest.mock('../../repositories/roles_repository'); jest.mock('../../services/roles_service'); const defaultUserId = 42; @@ -181,4 +183,118 @@ describe('Mentor Service', () => { expect(result).toEqual(mentor); }); }); + + describe('updateMentorStatus', () => { + const mentorId = 13; + const userId = defaultUserId; + + it('Successful status change for mentor profile owner', async () => { + // mentor profile owner changes status from 'active' to 'inactive' + repo.getMentorById.mockResolvedValue({ + id: mentorId, + user_id: userId, + status: MENTOR_STATUSES.active, + }); + repo.updateMentorById.mockResolvedValue(mentorId); + + const result = await mentorService.updateMentorStatus( + [{ role_name: 'member' }], + mentorId.toString(), + MENTOR_STATUSES.inactive, + true // isOwner + ); + + expect(repo.updateMentorById).toHaveBeenCalledWith(mentorId.toString(), { status: MENTOR_STATUSES.inactive }); + expect(result).toBe(mentorId); + }); + + it('Successful status change for admin', async () => { + repo.getMentorById.mockResolvedValue({ + id: mentorId, + user_id: userId, + status: MENTOR_STATUSES.pending, + }); + + repo.updateMentorById.mockResolvedValue(mentorId); + + const result = await mentorService.updateMentorStatus( + [{ role_name: 'member' }, { role_name: 'admin' }], + mentorId.toString(), + MENTOR_STATUSES.active, + false // isOwner + ); + + expect(repo.updateMentorById).toHaveBeenCalledWith( + mentorId.toString(), + { status: MENTOR_STATUSES.active } + ); + expect(rolesRepo.assignRoleToUser).toHaveBeenCalledWith(userId, 2); + expect(rolesRepo.removeRoleFromUser).not.toHaveBeenCalled(); + expect(result).toBe(mentorId); + }); + + it('User with no ownership nor admin role cannot make changes', async () => { + repo.getMentorById.mockResolvedValue({ + id: mentorId, + user_id: userId, + status: MENTOR_STATUSES.pending, + }); + + await expect( + mentorService.updateMentorStatus( + [{ role_name: 'member' }], + mentorId.toString(), + MENTOR_STATUSES.inactive, + false // isOwner + ) + ).rejects.toBeInstanceOf(DataRequiresElevatedRoleError); + + expect(repo.getMentorById).toHaveBeenCalledTimes(1); + expect(repo.updateMentorById).not.toHaveBeenCalled(); + expect(rolesRepo.assignRoleToUser).not.toHaveBeenCalled(); + expect(rolesRepo.removeRoleFromUser).not.toHaveBeenCalled(); + }); + + it('User owning the profile cannot change status when current status is pending', async () => { + repo.getMentorById.mockResolvedValue({ + id: mentorId, + user_id: userId, + status: MENTOR_STATUSES.pending, + }); + + await expect( + mentorService.updateMentorStatus( + [{ role_name: 'member' }], + mentorId.toString(), + MENTOR_STATUSES.active, + true // isOwner + ) + ).rejects.toBeInstanceOf(DataRequiresElevatedRoleError); + + expect(repo.updateMentorById).not.toHaveBeenCalled(); + expect(rolesRepo.assignRoleToUser).not.toHaveBeenCalled(); + expect(rolesRepo.removeRoleFromUser).not.toHaveBeenCalled(); + }); + + it('User owning the profile cannot change status when current status is rejected', async () => { + repo.getMentorById.mockResolvedValue({ + id: mentorId, + user_id: userId, + status: MENTOR_STATUSES.rejected, + }); + + await expect( + mentorService.updateMentorStatus( + [{ role_name: 'member' }], + mentorId.toString(), + MENTOR_STATUSES.inactive, + true // isOwner + ) + ).rejects.toBeInstanceOf(DataRequiresElevatedRoleError); + + expect(repo.updateMentorById).not.toHaveBeenCalled(); + expect(rolesRepo.assignRoleToUser).not.toHaveBeenCalled(); + expect(rolesRepo.removeRoleFromUser).not.toHaveBeenCalled(); + }); + }); }); \ No newline at end of file From 97aebff56abd2f191be2c4822f61add856dee64f Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Wed, 1 Oct 2025 08:45:17 +0100 Subject: [PATCH 065/135] remove pending enum from swagger --- docs/catbytes_api_swagger.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index bb9d4fd..0854f36 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -1861,8 +1861,7 @@ "enum": [ "active", "rejected", - "inactive", - "pending" + "inactive" ] } } From 2578761402d7d3051adcebd1ab283bf4cd8923bb Mon Sep 17 00:00:00 2001 From: marinacatbytes Date: Wed, 1 Oct 2025 20:07:58 +0100 Subject: [PATCH 066/135] Update routes/mentors.js Co-authored-by: aliona matveeva --- routes/mentors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/mentors.js b/routes/mentors.js index 8da37b0..7036bd5 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -78,7 +78,7 @@ router.patch("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) return respondWithError(res, 404, "Mentor not found"); } const mentorId = await mentorService.updateMentorStatus(req.userRoles, id, status, isOwner); - res.json(mentorId); + res.json({id: mentorId}); } catch (err) { logger.error(err); if (err instanceof DataRequiresElevatedRoleError) { From 99baa77fd572b60dc4d5c634176c29279e69c968 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Wed, 1 Oct 2025 20:13:34 +0100 Subject: [PATCH 067/135] resolves lint issues --- routes/mentors.js | 2 +- services/mentor_service.js | 8 +++----- tests/routes/mentors.test.js | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/routes/mentors.js b/routes/mentors.js index 7036bd5..bf2b5f0 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -86,6 +86,6 @@ router.patch("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) } respondWithError(res); } -}) +}); module.exports = router; \ No newline at end of file diff --git a/services/mentor_service.js b/services/mentor_service.js index 0813fdb..4718377 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -2,8 +2,6 @@ const repo = require('../repositories/mentor_repository'); const rolesService = require('../services/roles_service'); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); const { ROLE_NAMES, MENTOR_STATUSES } = require("../utils"); -const { updateUserById } = require('../repositories/user_repository'); -const { getUserById } = require('./user_service'); const { assignRoleToUser, removeRoleFromUser } = require('../repositories/roles_repository'); const baseFields = [ @@ -100,10 +98,10 @@ async function updateMentorStatus(userRoles, mentorId, status, isOwner) { if(isOwner) { if(allowedStatusesForOwner.includes(status)) { if(![MENTOR_STATUSES.pending, MENTOR_STATUSES.rejected].includes(mentorData.status)) { - return repo.updateMentorById(mentorId, { status }) + return repo.updateMentorById(mentorId, { status }); } - } - } + }; + }; throw new DataRequiresElevatedRoleError("You're not allowed to edit this resource"); } diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index f1c87df..32fc1d3 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -229,4 +229,4 @@ describe('PATCH /mentors/:id', () => { expect(res.statusCode).toBe(404); expect(res.body.error).toBe('Mentor not found'); }); -}) \ No newline at end of file +}); \ No newline at end of file From 9ad1fe81209ed2b2aa2448f2b128a8c410708bff Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Wed, 1 Oct 2025 20:17:41 +0100 Subject: [PATCH 068/135] updates mentor route test --- tests/routes/mentors.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index 32fc1d3..dee43c8 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -193,7 +193,7 @@ describe('PATCH /mentors/:id', () => { .toHaveBeenCalledWith(defaultUserRoles, mockedMentor.id.toString(), 'active', false); expect(res.statusCode).toBe(200); - expect(res.body).toStrictEqual(mockedMentor.id); + expect(res.body.id).toStrictEqual(mockedMentor.id); }); it('Update mentor status - not authorized', async () => { From a8000cdc297031194aad7a139e32b2b69bf66650 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Thu, 2 Oct 2025 08:41:48 +0100 Subject: [PATCH 069/135] send email to applicant on application approval or rejection --- routes/mentors.js | 15 +- services/mailer_service.js | 69 ++++++- .../mentor_application_approved_email.hbs | 183 +++++++++++++++++ .../mentor_application_rejected_email.hbs | 193 ++++++++++++++++++ 4 files changed, 456 insertions(+), 4 deletions(-) create mode 100644 templates/email/mentor_application_approved_email.hbs create mode 100644 templates/email/mentor_application_rejected_email.hbs diff --git a/routes/mentors.js b/routes/mentors.js index bf2b5f0..c77e98a 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -2,10 +2,12 @@ const express = require("express"); const router = express.Router(); const mentorService = require("../services/mentor_service"); -const { ROLE_NAMES } = require("../utils"); +const { ROLE_NAMES, MENTOR_STATUSES } = require("../utils"); const { verifyRoles, verifyMentorOwnership } = require("../middleware/authorization"); const { isValidIntegerId, respondWithError } = require("./helpers"); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); +const { sendEmailOnMentorApplicationStatusChange } = require("../services/mailer_service"); +const { getUserById } = require("../services/user_service"); const logger = require('../logger')(__filename); router.use(express.json()); @@ -78,7 +80,16 @@ router.patch("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) return respondWithError(res, 404, "Mentor not found"); } const mentorId = await mentorService.updateMentorStatus(req.userRoles, id, status, isOwner); - res.json({id: mentorId}); + + if (status === MENTOR_STATUSES.active || status === MENTOR_STATUSES.rejected) { + const userInfo = await getUserById(mentorInfo.user_id); + if (!userInfo) { + return respondWithError(res, 404, "User not found"); + } + await sendEmailOnMentorApplicationStatusChange(userInfo.email, mentorInfo.name, status); + }; + + res.json({ id: mentorId }); } catch (err) { logger.error(err); if (err instanceof DataRequiresElevatedRoleError) { diff --git a/services/mailer_service.js b/services/mailer_service.js index a5812c1..32c994a 100644 --- a/services/mailer_service.js +++ b/services/mailer_service.js @@ -2,7 +2,7 @@ const nodemailer = require('nodemailer'); const path = require('path'); const config = require('config'); const { loadSecrets } = require("../aws/ssm-helper"); -const { APPL_STATUSES } = require("../utils"); +const { APPL_STATUSES, MENTOR_STATUSES } = require("../utils"); const discordService = require("../services/discord_bot_service"); const logger = require('../logger')(__filename); @@ -95,6 +95,50 @@ async function sendApplicationRejectedEmail(email, name) { return mailTransporter.sendMail(mailOptions); } +async function sendMentorApplicationApprovedEmail(email, name) { + const mailOptions = { + from: mailerConfig.user, + to: email, + subject: "You are now a CatBytes Mentor!", + template: "mentor_application_approved_email", + context: { + name: name, + catbytesLink: webplatformUrl, + }, + attachments: [ + { + filename: "happy-cat.png", + path: path.resolve("./templates/email/src/happy-cat.png"), + cid: "happycat", + }, + ], + }; + + return mailTransporter.sendMail(mailOptions); +} + +async function sendMentorApplicationRejectedEmail(email, name) { + const mailOptions = { + from: mailerConfig.user, + to: email, + subject: "Thank you for your CatBytes mentor application", + template: "mentor_application_rejected_email", + context: { + name: name, + catbytesLink: webplatformUrl, + }, + attachments: [ + { + filename: "sad-cat.png", + path: path.resolve("./templates/email/src/sad-cat.png"), + cid: "sadcat", + }, + ], + }; + + return mailTransporter.sendMail(mailOptions); +} + async function sendEmailOnApplicationStatusChange(email, name, status) { try { if (status === APPL_STATUSES.approved) { @@ -138,4 +182,25 @@ async function sendEmailOnNewMentorApplication(emails, mentorApplication) { } } -module.exports = { initMailer, sendEmailOnApplicationStatusChange, sendEmailOnNewMentorApplication }; \ No newline at end of file +async function sendEmailOnMentorApplicationStatusChange(email, name, status) { + try { + if (status === MENTOR_STATUSES.active) { + await sendMentorApplicationApprovedEmail(email, name); + } + else if (status === MENTOR_STATUSES.rejected) { + await sendMentorApplicationRejectedEmail(email, name); + } + else { + logger.warn(`Not sending aplication status change email, because status ${status} + is not in email-sending allow-list.`); + } + } + catch (err) { + // todo: add correct error processing: if "domain does not accept mail" - log it, + // put to some deadletter for future manual verification + // if some other error - we should retry sending email - add queue + logger.error(`Error sending email to ${email} on mentor application status change: ${err.message}`); + } +} + +module.exports = { initMailer, sendEmailOnApplicationStatusChange, sendEmailOnNewMentorApplication, sendEmailOnMentorApplicationStatusChange }; \ No newline at end of file diff --git a/templates/email/mentor_application_approved_email.hbs b/templates/email/mentor_application_approved_email.hbs new file mode 100644 index 0000000..4d5a447 --- /dev/null +++ b/templates/email/mentor_application_approved_email.hbs @@ -0,0 +1,183 @@ + + + + + + + + + + Email Template + + + + + + + + + + diff --git a/templates/email/mentor_application_rejected_email.hbs b/templates/email/mentor_application_rejected_email.hbs new file mode 100644 index 0000000..adb1d11 --- /dev/null +++ b/templates/email/mentor_application_rejected_email.hbs @@ -0,0 +1,193 @@ + + + + + + + + + + Email Template + + + + + + + + + + From 0726fe2e77db76283e6855304234afba1161b6e0 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Thu, 2 Oct 2025 09:24:54 +0100 Subject: [PATCH 070/135] resolves eslint and npm test --- services/mailer_service.js | 7 ++++++- tests/routes/mentors.test.js | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/services/mailer_service.js b/services/mailer_service.js index 32c994a..71da6e7 100644 --- a/services/mailer_service.js +++ b/services/mailer_service.js @@ -203,4 +203,9 @@ async function sendEmailOnMentorApplicationStatusChange(email, name, status) { } } -module.exports = { initMailer, sendEmailOnApplicationStatusChange, sendEmailOnNewMentorApplication, sendEmailOnMentorApplicationStatusChange }; \ No newline at end of file +module.exports = { + initMailer, + sendEmailOnApplicationStatusChange, + sendEmailOnNewMentorApplication, + sendEmailOnMentorApplicationStatusChange +}; \ No newline at end of file diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index dee43c8..031fa0b 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -18,6 +18,13 @@ const mockedMentor = { jest.mock('../../services/auth_service'); jest.mock('../../services/mentor_service'); +// added below so PATCH mentor success route test doesn't fail +jest.mock('../../services/user_service', () => ({ + getUserById: jest.fn().mockResolvedValue({ email: 'user@test.com', name: 'Test User' }), +})); +jest.mock('../../services/mailer_service', () => ({ + sendEmailOnMentorApplicationStatusChange: jest.fn().mockResolvedValue(), +})); jest.mock('../../middleware/authorization', () => { const actual = jest.requireActual('../../middleware/authorization'); return { From a5e7c40ecbec351d9b98e72ab97d96790dfbb7d8 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sun, 5 Oct 2025 07:31:28 +0100 Subject: [PATCH 071/135] adds PATCH method to cors config --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index 31dadfc..474afc7 100644 --- a/app.js +++ b/app.js @@ -26,7 +26,7 @@ app.use(cors({ } }, credentials: true, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Firebase-Token', 'X-Discord-Code'], })); app.options('*', cors()); From 5b8801cf67734a3a7c1416c7539b8495452c1cf8 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 5 Oct 2025 12:34:30 +0200 Subject: [PATCH 072/135] get mentors should return inactive mentors too --- services/mentor_service.js | 2 +- tests/services/mentor_service.test.js | 21 ++++++--------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/services/mentor_service.js b/services/mentor_service.js index e27d7e2..72dbb63 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -21,7 +21,7 @@ const privateFields = [ const adminVisibleStatuses = [MENTOR_STATUSES.active, MENTOR_STATUSES.inactive, MENTOR_STATUSES.rejected, MENTOR_STATUSES.pending]; -const generalVisitbleStatuses = [MENTOR_STATUSES.active]; +const generalVisitbleStatuses = [MENTOR_STATUSES.active, MENTOR_STATUSES.inactive]; const allFields = [...baseFields, ...privateFields]; diff --git a/tests/services/mentor_service.test.js b/tests/services/mentor_service.test.js index 571606c..433750a 100644 --- a/tests/services/mentor_service.test.js +++ b/tests/services/mentor_service.test.js @@ -54,7 +54,7 @@ describe('Mentor Service', () => { }); describe('getMentors', () => { - it('Unauthenticated users only see active mentors', async () => { + it('Unauthenticated users see active and inactive mentors with limited fields', async () => { const allowedStatuses = mentorService.generalVisitbleStatuses; const mentor = { id: 1, user_id: defaultUserId, status: MENTOR_STATUSES.active }; @@ -63,6 +63,8 @@ describe('Mentor Service', () => { await mentorService.getMentors(undefined, undefined, false); + expect(allowedStatuses).toContain(MENTOR_STATUSES.active); + expect(allowedStatuses).toContain(MENTOR_STATUSES.inactive); expect(rolesService.getUserRoles).not.toHaveBeenCalled(); expect(repo.getMentors).toHaveBeenCalledWith(allowedStatuses, mentorService.baseFields); }); @@ -78,20 +80,7 @@ describe('Mentor Service', () => { expect(repo.getMentors).not.toHaveBeenCalled(); }); - it('Member users only see active mentors', async () => { - const userRoles = [{ role_name: 'member' }]; - const allowedStatuses = mentorService.generalVisitbleStatuses; - const mentor = { id: 1, user_id: defaultUserId, status: MENTOR_STATUSES.active }; - - rolesService.getUserRoles.mockResolvedValue(userRoles); - repo.getMentors.mockResolvedValue([mentor]); - - await mentorService.getMentors(defaultUserId, undefined, false); - - expect(repo.getMentors).toHaveBeenCalledWith(allowedStatuses, mentorService.baseFields); - }); - - it('Member users only see active mentors', async () => { + it('Member users see active and inactive mentors', async () => { const userRoles = [{ role_name: 'member' }]; const allowedStatuses = mentorService.generalVisitbleStatuses; const mentor = { id: 1, user_id: defaultUserId, status: MENTOR_STATUSES.active }; @@ -101,6 +90,8 @@ describe('Mentor Service', () => { await mentorService.getMentors(defaultUserId, undefined, false); + expect(allowedStatuses).toContain(MENTOR_STATUSES.active); + expect(allowedStatuses).toContain(MENTOR_STATUSES.inactive); expect(repo.getMentors).toHaveBeenCalledWith(allowedStatuses, mentorService.baseFields); }); From faf7d31f952619bfbcbdd43c40fa6292ddc8f0af Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sun, 5 Oct 2025 11:58:46 +0100 Subject: [PATCH 073/135] adds PUT mentor and updateMentor service --- routes/mentors.js | 46 +++++++++++++++++++++++++++++++++++++- services/mentor_service.js | 12 +++++++++- utils.js | 4 +++- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/routes/mentors.js b/routes/mentors.js index c77e98a..a460c1a 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -2,7 +2,7 @@ const express = require("express"); const router = express.Router(); const mentorService = require("../services/mentor_service"); -const { ROLE_NAMES, MENTOR_STATUSES } = require("../utils"); +const { ROLE_NAMES, MENTOR_STATUSES, ALLOWED_MENTOR_UPDATES } = require("../utils"); const { verifyRoles, verifyMentorOwnership } = require("../middleware/authorization"); const { isValidIntegerId, respondWithError } = require("./helpers"); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); @@ -99,4 +99,48 @@ router.patch("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) } }); +// Update mentor information by user owning mentorship card +router.put("/mentors/:id", verifyRoles([ROLE_NAMES.mentor]), async (req, res) => { + const { id } = req.params; + const { updates } = req.body; + + if (!isValidIntegerId(id)) { + return respondWithError(res, 400, "Invalid user id supplied"); + } + + // check that only fields 'about' and 'contact' can be updated + const invalidFields = Object.keys(updates) + .filter(field => !ALLOWED_MENTOR_UPDATES.includes(field)); + if(invalidFields.length) { + return respondWithError(res, 400, "You are not allowed to edit this field"); + } + + try { + const isOwner = await verifyMentorOwnership(id, req.userId); + const mentorInfo = await mentorService.getMentorById( + req.userRoles, + id, + isOwner + ); + if (!mentorInfo) { + return respondWithError(res, 404, "Mentor not found"); + } + + // can edit is mentor status is active or inactive + if ( + mentorInfo.status === MENTOR_STATUSES.active || + mentorInfo.status === MENTOR_STATUSES.inactive + ) { + const updatedMentor = await mentorService.updateMentor(id, updates, isOwner); + res.json({ mentor: updatedMentor }); + } + } catch (err) { + logger.error(err); + if (err instanceof DataRequiresElevatedRoleError) { + return respondWithError(res, 403, err.message); + } + respondWithError(res); + } +}); + module.exports = router; \ No newline at end of file diff --git a/services/mentor_service.js b/services/mentor_service.js index e27d7e2..48b4633 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -109,8 +109,18 @@ async function updateMentorStatus(userRoles, mentorId, status, isOwner) { throw new DataRequiresElevatedRoleError("You're not allowed to edit this resource"); } +async function updateMentor(mentorId, updates, isOwner) { + if(isOwner) { + const updatedMentor = await repo.updateMentorById(mentorId, updates); + return updatedMentor; + } else { + throw new DataRequiresElevatedRoleError("You're not allowed to edit this resource"); + } +} + module.exports = { - getMentors, createMentor, getMentorById, updateMentorStatus, + getMentors, createMentor, getMentorById, + updateMentorStatus, updateMentor, adminVisibleStatuses, generalVisitbleStatuses, allFields, baseFields, privateFields, MENTOR_STATUSES diff --git a/utils.js b/utils.js index 5c9b5fa..3df31d4 100644 --- a/utils.js +++ b/utils.js @@ -74,7 +74,9 @@ const MENTOR_STATUSES = { pending: 'pending', }; +const ALLOWED_MENTOR_UPDATES = ['contact', 'about']; + module.exports = { - APPL_STATUSES, MENTOR_STATUSES, ROLE_NAMES, + APPL_STATUSES, MENTOR_STATUSES, ROLE_NAMES, ALLOWED_MENTOR_UPDATES, isRoleExists, loadRolesIntoMemory, getRole, getFirebaseSdkServiceAccount }; From fa48abec840de7059e54d456fda2ee3ddc2d184a Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 5 Oct 2025 16:53:23 +0200 Subject: [PATCH 074/135] prepare for s3 integration --- config/development.json | 6 +- config/production.json | 4 +- package-lock.json | 4966 +++++++++++++++++++++++++++++++++++---- package.json | 2 + 4 files changed, 4550 insertions(+), 428 deletions(-) diff --git a/config/development.json b/config/development.json index d4a4b35..3302cab 100644 --- a/config/development.json +++ b/config/development.json @@ -1,9 +1,11 @@ { "aws": { - "param_store_region": "us-east-1", + "aws_region": "eu-west-2", + "param_store_region": "us-east-1", "db_endpoint": "catbytes-web-platform-db.ct2ag4a86wsn.eu-west-2.rds.amazonaws.com", "db_name": "catbytes_webplatform", - "db_port": 5432 + "db_port": 5432, + "s3_bucket": "dev-catbytes-bucket" }, "cors": { "allowedOrigins": ["http://localhost:5173", "https://dev.catbytes.io"] diff --git a/config/production.json b/config/production.json index 9a5264b..c3b1cc1 100644 --- a/config/production.json +++ b/config/production.json @@ -1,9 +1,11 @@ { "aws": { + "aws_region": "eu-west-2", "param_store_region": "eu-west-2", "db_endpoint": "prod-catbytes-web-platform-db.ct2ag4a86wsn.eu-west-2.rds.amazonaws.com", "db_name": "catbytes_webplatform", - "db_port": 5432 + "db_port": 5432, + "s3_bucket": "prod-catbytes-bucket" }, "cors": { "allowedOrigins": ["https://catbytes.io"] diff --git a/package-lock.json b/package-lock.json index 48172fe..c6fb4ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@aws-sdk/client-s3": "^3.901.0", "@aws-sdk/client-ssm": "^3.699.0", + "@aws-sdk/s3-request-presigner": "^3.901.0", "@prisma/client": "^5.21.1", "axios": "^1.8.4", "config": "^3.3.12", @@ -51,6 +53,83 @@ "node": ">=6.0.0" } }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", @@ -176,367 +255,3940 @@ "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/client-ssm": { - "version": "3.699.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.699.0.tgz", - "integrity": "sha512-ROynEZI8RZC+NkQP/BBcdkhGHuk+RJkinemxmsTt8FGYqHlK/7hcOjWitziAGpmjWfPm4a6UAtqeNg/AX7nvlw==", + "node_modules/@aws-sdk/client-s3": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.901.0.tgz", + "integrity": "sha512-wyKhZ51ur1tFuguZ6PgrUsot9KopqD0Tmxw8O8P/N3suQDxFPr0Yo7Y77ezDRDZQ95Ml3C0jlvx79HCo8VxdWA==", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.699.0", - "@aws-sdk/client-sts": "3.699.0", - "@aws-sdk/core": "3.696.0", - "@aws-sdk/credential-provider-node": "3.699.0", - "@aws-sdk/middleware-host-header": "3.696.0", - "@aws-sdk/middleware-logger": "3.696.0", - "@aws-sdk/middleware-recursion-detection": "3.696.0", - "@aws-sdk/middleware-user-agent": "3.696.0", - "@aws-sdk/region-config-resolver": "3.696.0", - "@aws-sdk/types": "3.696.0", - "@aws-sdk/util-endpoints": "3.696.0", - "@aws-sdk/util-user-agent-browser": "3.696.0", - "@aws-sdk/util-user-agent-node": "3.696.0", - "@smithy/config-resolver": "^3.0.12", - "@smithy/core": "^2.5.3", - "@smithy/fetch-http-handler": "^4.1.1", - "@smithy/hash-node": "^3.0.10", - "@smithy/invalid-dependency": "^3.0.10", - "@smithy/middleware-content-length": "^3.0.12", - "@smithy/middleware-endpoint": "^3.2.3", - "@smithy/middleware-retry": "^3.0.27", - "@smithy/middleware-serde": "^3.0.10", - "@smithy/middleware-stack": "^3.0.10", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/node-http-handler": "^3.3.1", - "@smithy/protocol-http": "^4.1.7", - "@smithy/smithy-client": "^3.4.4", - "@smithy/types": "^3.7.1", - "@smithy/url-parser": "^3.0.10", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.27", - "@smithy/util-defaults-mode-node": "^3.0.27", - "@smithy/util-endpoints": "^2.1.6", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-retry": "^3.0.10", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.9", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@aws-sdk/core": "3.901.0", + "@aws-sdk/credential-provider-node": "3.901.0", + "@aws-sdk/middleware-bucket-endpoint": "3.901.0", + "@aws-sdk/middleware-expect-continue": "3.901.0", + "@aws-sdk/middleware-flexible-checksums": "3.901.0", + "@aws-sdk/middleware-host-header": "3.901.0", + "@aws-sdk/middleware-location-constraint": "3.901.0", + "@aws-sdk/middleware-logger": "3.901.0", + "@aws-sdk/middleware-recursion-detection": "3.901.0", + "@aws-sdk/middleware-sdk-s3": "3.901.0", + "@aws-sdk/middleware-ssec": "3.901.0", + "@aws-sdk/middleware-user-agent": "3.901.0", + "@aws-sdk/region-config-resolver": "3.901.0", + "@aws-sdk/signature-v4-multi-region": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@aws-sdk/util-user-agent-browser": "3.901.0", + "@aws-sdk/util-user-agent-node": "3.901.0", + "@aws-sdk/xml-builder": "3.901.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/core": "^3.14.0", + "@smithy/eventstream-serde-browser": "^4.2.0", + "@smithy/eventstream-serde-config-resolver": "^4.3.0", + "@smithy/eventstream-serde-node": "^4.2.0", + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/hash-blob-browser": "^4.2.0", + "@smithy/hash-node": "^4.2.0", + "@smithy/hash-stream-node": "^4.2.0", + "@smithy/invalid-dependency": "^4.2.0", + "@smithy/md5-js": "^4.2.0", + "@smithy/middleware-content-length": "^4.2.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-retry": "^4.4.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.0", + "@smithy/util-defaults-mode-browser": "^4.2.0", + "@smithy/util-defaults-mode-node": "^4.2.0", + "@smithy/util-endpoints": "^3.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/util-stream": "^4.4.0", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.696.0.tgz", - "integrity": "sha512-q5TTkd08JS0DOkHfUL853tuArf7NrPeqoS5UOvqJho8ibV9Ak/a/HO4kNvy9Nj3cib/toHYHsQIEtecUPSUUrQ==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.901.0.tgz", + "integrity": "sha512-sGyDjjkJ7ppaE+bAKL/Q5IvVCxtoyBIzN+7+hWTS/mUxWJ9EOq9238IqmVIIK6sYNIzEf9yhobfMARasPYVTNg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.696.0", - "@aws-sdk/middleware-host-header": "3.696.0", - "@aws-sdk/middleware-logger": "3.696.0", - "@aws-sdk/middleware-recursion-detection": "3.696.0", - "@aws-sdk/middleware-user-agent": "3.696.0", - "@aws-sdk/region-config-resolver": "3.696.0", - "@aws-sdk/types": "3.696.0", - "@aws-sdk/util-endpoints": "3.696.0", - "@aws-sdk/util-user-agent-browser": "3.696.0", - "@aws-sdk/util-user-agent-node": "3.696.0", - "@smithy/config-resolver": "^3.0.12", - "@smithy/core": "^2.5.3", - "@smithy/fetch-http-handler": "^4.1.1", - "@smithy/hash-node": "^3.0.10", - "@smithy/invalid-dependency": "^3.0.10", - "@smithy/middleware-content-length": "^3.0.12", - "@smithy/middleware-endpoint": "^3.2.3", - "@smithy/middleware-retry": "^3.0.27", - "@smithy/middleware-serde": "^3.0.10", - "@smithy/middleware-stack": "^3.0.10", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/node-http-handler": "^3.3.1", - "@smithy/protocol-http": "^4.1.7", - "@smithy/smithy-client": "^3.4.4", - "@smithy/types": "^3.7.1", - "@smithy/url-parser": "^3.0.10", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.27", - "@smithy/util-defaults-mode-node": "^3.0.27", - "@smithy/util-endpoints": "^2.1.6", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-retry": "^3.0.10", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/core": "3.901.0", + "@aws-sdk/middleware-host-header": "3.901.0", + "@aws-sdk/middleware-logger": "3.901.0", + "@aws-sdk/middleware-recursion-detection": "3.901.0", + "@aws-sdk/middleware-user-agent": "3.901.0", + "@aws-sdk/region-config-resolver": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@aws-sdk/util-user-agent-browser": "3.901.0", + "@aws-sdk/util-user-agent-node": "3.901.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/core": "^3.14.0", + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/hash-node": "^4.2.0", + "@smithy/invalid-dependency": "^4.2.0", + "@smithy/middleware-content-length": "^4.2.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-retry": "^4.4.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.0", + "@smithy/util-defaults-mode-browser": "^4.2.0", + "@smithy/util-defaults-mode-node": "^4.2.0", + "@smithy/util-endpoints": "^3.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.699.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.699.0.tgz", - "integrity": "sha512-u8a1GorY5D1l+4FQAf4XBUC1T10/t7neuwT21r0ymrtMFSK2a9QqVHKMoLkvavAwyhJnARSBM9/UQC797PFOFw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.696.0", - "@aws-sdk/credential-provider-node": "3.699.0", - "@aws-sdk/middleware-host-header": "3.696.0", - "@aws-sdk/middleware-logger": "3.696.0", - "@aws-sdk/middleware-recursion-detection": "3.696.0", - "@aws-sdk/middleware-user-agent": "3.696.0", - "@aws-sdk/region-config-resolver": "3.696.0", - "@aws-sdk/types": "3.696.0", - "@aws-sdk/util-endpoints": "3.696.0", - "@aws-sdk/util-user-agent-browser": "3.696.0", - "@aws-sdk/util-user-agent-node": "3.696.0", - "@smithy/config-resolver": "^3.0.12", - "@smithy/core": "^2.5.3", - "@smithy/fetch-http-handler": "^4.1.1", - "@smithy/hash-node": "^3.0.10", - "@smithy/invalid-dependency": "^3.0.10", - "@smithy/middleware-content-length": "^3.0.12", - "@smithy/middleware-endpoint": "^3.2.3", - "@smithy/middleware-retry": "^3.0.27", - "@smithy/middleware-serde": "^3.0.10", - "@smithy/middleware-stack": "^3.0.10", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/node-http-handler": "^3.3.1", - "@smithy/protocol-http": "^4.1.7", - "@smithy/smithy-client": "^3.4.4", - "@smithy/types": "^3.7.1", - "@smithy/url-parser": "^3.0.10", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.27", - "@smithy/util-defaults-mode-node": "^3.0.27", - "@smithy/util-endpoints": "^2.1.6", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-retry": "^3.0.10", - "@smithy/util-utf8": "^3.0.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.901.0.tgz", + "integrity": "sha512-brKAc3y64tdhyuEf+OPIUln86bRTqkLgb9xkd6kUdIeA5+qmp/N6amItQz+RN4k4O3kqkCPYnAd3LonTKluobw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@aws-sdk/xml-builder": "3.901.0", + "@smithy/core": "^3.14.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/signature-v4": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.699.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.699.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.699.0.tgz", - "integrity": "sha512-++lsn4x2YXsZPIzFVwv3fSUVM55ZT0WRFmPeNilYIhZClxHLmVAWKH4I55cY9ry60/aTKYjzOXkWwyBKGsGvQg==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.901.0.tgz", + "integrity": "sha512-5hAdVl3tBuARh3zX5MLJ1P/d+Kr5kXtDU3xm1pxUEF4xt2XkEEpwiX5fbkNkz2rbh3BCt2gOHsAbh6b3M7n+DA==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.699.0", - "@aws-sdk/core": "3.696.0", - "@aws-sdk/credential-provider-node": "3.699.0", - "@aws-sdk/middleware-host-header": "3.696.0", - "@aws-sdk/middleware-logger": "3.696.0", - "@aws-sdk/middleware-recursion-detection": "3.696.0", - "@aws-sdk/middleware-user-agent": "3.696.0", - "@aws-sdk/region-config-resolver": "3.696.0", - "@aws-sdk/types": "3.696.0", - "@aws-sdk/util-endpoints": "3.696.0", - "@aws-sdk/util-user-agent-browser": "3.696.0", - "@aws-sdk/util-user-agent-node": "3.696.0", - "@smithy/config-resolver": "^3.0.12", - "@smithy/core": "^2.5.3", - "@smithy/fetch-http-handler": "^4.1.1", - "@smithy/hash-node": "^3.0.10", - "@smithy/invalid-dependency": "^3.0.10", - "@smithy/middleware-content-length": "^3.0.12", - "@smithy/middleware-endpoint": "^3.2.3", - "@smithy/middleware-retry": "^3.0.27", - "@smithy/middleware-serde": "^3.0.10", - "@smithy/middleware-stack": "^3.0.10", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/node-http-handler": "^3.3.1", - "@smithy/protocol-http": "^4.1.7", - "@smithy/smithy-client": "^3.4.4", - "@smithy/types": "^3.7.1", - "@smithy/url-parser": "^3.0.10", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.27", - "@smithy/util-defaults-mode-node": "^3.0.27", - "@smithy/util-endpoints": "^2.1.6", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-retry": "^3.0.10", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/core": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.696.0.tgz", - "integrity": "sha512-3c9III1k03DgvRZWg8vhVmfIXPG6hAciN9MzQTzqGngzWAELZF/WONRTRQuDFixVtarQatmLHYVw/atGeA2Byw==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.901.0.tgz", + "integrity": "sha512-Ggr7+0M6QZEsrqRkK7iyJLf4LkIAacAxHz9c4dm9hnDdU7vqrlJm6g73IxMJXWN1bIV7IxfpzB11DsRrB/oNjQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.696.0", - "@smithy/core": "^2.5.3", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.7", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.4", - "@smithy/types": "^3.7.1", - "@smithy/util-middleware": "^3.0.10", - "fast-xml-parser": "4.4.1", + "@aws-sdk/core": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/util-stream": "^4.4.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.696.0.tgz", - "integrity": "sha512-T9iMFnJL7YTlESLpVFT3fg1Lkb1lD+oiaIC8KMpepb01gDUBIpj9+Y+pA/cgRWW0yRxmkDXNazAE2qQTVFGJzA==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.901.0.tgz", + "integrity": "sha512-zxadcDS0hNJgv8n4hFYJNOXyfjaNE1vvqIiF/JzZSQpSSYXzCd+WxXef5bQh+W3giDtRUmkvP5JLbamEFjZKyw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.901.0", + "@aws-sdk/credential-provider-env": "3.901.0", + "@aws-sdk/credential-provider-http": "3.901.0", + "@aws-sdk/credential-provider-process": "3.901.0", + "@aws-sdk/credential-provider-sso": "3.901.0", + "@aws-sdk/credential-provider-web-identity": "3.901.0", + "@aws-sdk/nested-clients": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.901.0.tgz", + "integrity": "sha512-dPuFzMF7L1s/lQyT3wDxqLe82PyTH+5o1jdfseTEln64LJMl0ZMWaKX/C1UFNDxaTd35Cgt1bDbjjAWHMiKSFQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.696.0", - "@aws-sdk/types": "3.696.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/types": "^3.7.1", + "@aws-sdk/credential-provider-env": "3.901.0", + "@aws-sdk/credential-provider-http": "3.901.0", + "@aws-sdk/credential-provider-ini": "3.901.0", + "@aws-sdk/credential-provider-process": "3.901.0", + "@aws-sdk/credential-provider-sso": "3.901.0", + "@aws-sdk/credential-provider-web-identity": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.696.0.tgz", - "integrity": "sha512-GV6EbvPi2eq1+WgY/o2RFA3P7HGmnkIzCNmhwtALFlqMroLYWKE7PSeHw66Uh1dFQeVESn0/+hiUNhu1mB0emA==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.901.0.tgz", + "integrity": "sha512-/IWgmgM3Cl1wTdJA5HqKMAojxLkYchh5kDuphApxKhupLu6Pu0JBOHU8A5GGeFvOycyaVwosod6zDduINZxe+A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.696.0", - "@aws-sdk/types": "3.696.0", - "@smithy/fetch-http-handler": "^4.1.1", - "@smithy/node-http-handler": "^3.3.1", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.7", - "@smithy/smithy-client": "^3.4.4", - "@smithy/types": "^3.7.1", - "@smithy/util-stream": "^3.3.1", + "@aws-sdk/core": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.699.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.699.0.tgz", - "integrity": "sha512-dXmCqjJnKmG37Q+nLjPVu22mNkrGHY8hYoOt3Jo9R2zr5MYV7s/NHsCHr+7E+BZ+tfZYLRPeB1wkpTeHiEcdRw==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.901.0.tgz", + "integrity": "sha512-SjmqZQHmqFSET7+6xcZgtH7yEyh5q53LN87GqwYlJZ6KJ5oNw11acUNEhUOL1xTSJEvaWqwTIkS2zqrzLcM9bw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.696.0", - "@aws-sdk/credential-provider-env": "3.696.0", - "@aws-sdk/credential-provider-http": "3.696.0", - "@aws-sdk/credential-provider-process": "3.696.0", - "@aws-sdk/credential-provider-sso": "3.699.0", - "@aws-sdk/credential-provider-web-identity": "3.696.0", - "@aws-sdk/types": "3.696.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.1", + "@aws-sdk/client-sso": "3.901.0", + "@aws-sdk/core": "3.901.0", + "@aws-sdk/token-providers": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.901.0.tgz", + "integrity": "sha512-NYjy/6NLxH9m01+pfpB4ql8QgAorJcu8tw69kzHwUd/ql6wUDTbC7HcXqtKlIwWjzjgj2BKL7j6SyFapgCuafA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.901.0", + "@aws-sdk/nested-clients": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.901.0.tgz", + "integrity": "sha512-yWX7GvRmqBtbNnUW7qbre3GvZmyYwU0WHefpZzDTYDoNgatuYq6LgUIQ+z5C04/kCRoFkAFrHag8a3BXqFzq5A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.901.0.tgz", + "integrity": "sha512-UoHebjE7el/tfRo8/CQTj91oNUm+5Heus5/a4ECdmWaSCHCS/hXTsU3PTTHAY67oAQR8wBLFPfp3mMvXjB+L2A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.901.0.tgz", + "integrity": "sha512-Wd2t8qa/4OL0v/oDpCHHYkgsXJr8/ttCxrvCKAt0H1zZe2LlRhY9gpDVKqdertfHrHDj786fOvEQA28G1L75Dg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.901.0.tgz", + "integrity": "sha512-Zby4F03fvD9xAgXGPywyk4bC1jCbnyubMEYChLYohD+x20ULQCf+AimF/Btn7YL+hBpzh1+RmqmvZcx+RgwgNQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@smithy/core": "^3.14.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.901.0.tgz", + "integrity": "sha512-7F0N888qVLHo4CSQOsnkZ4QAp8uHLKJ4v3u09Ly5k4AEStrSlFpckTPyUx6elwGL+fxGjNE2aakK8vEgzzCV0A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.901.0.tgz", + "integrity": "sha512-pJEr1Ggbc/uVTDqp9IbNu9hdr0eQf3yZix3s4Nnyvmg4xmJSGAlbPC9LrNr5u3CDZoc8Z9CuLrvbP4MwYquNpQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.901.0", + "@aws-sdk/nested-clients": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.901.0.tgz", + "integrity": "sha512-5nZP3hGA8FHEtKvEQf4Aww5QZOkjLW1Z+NixSd+0XKfHvA39Ah5sZboScjLx0C9kti/K3OGW1RCx5K9Zc3bZqg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-endpoints": "^3.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.901.0.tgz", + "integrity": "sha512-Ntb6V/WFI21Ed4PDgL/8NSfoZQQf9xzrwNgiwvnxgAl/KvAvRBgQtqj5gHsDX8Nj2YmJuVoHfH9BGjL9VQ4WNg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.901.0.tgz", + "integrity": "sha512-l59KQP5TY7vPVUfEURc7P5BJKuNg1RSsAKBQW7LHLECXjLqDUbo2SMLrexLBEoArSt6E8QOrIN0C8z/0Xk0jYw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.699.0" + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.699.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.699.0.tgz", - "integrity": "sha512-MmEmNDo1bBtTgRmdNfdQksXu4uXe66s0p1hi1YPrn1h59Q605eq/xiWbGL6/3KdkViH6eGUuABeV2ODld86ylg==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/abort-controller": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.0.tgz", + "integrity": "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/config-resolver": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.3.0.tgz", + "integrity": "sha512-9oH+n8AVNiLPK/iK/agOsoWfrKZ3FGP3502tkksd6SRsKMYiu7AFX0YXo6YBADdsAj7C+G/aLKdsafIJHxuCkQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/core": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.14.0.tgz", + "integrity": "sha512-XJ4z5FxvY/t0Dibms/+gLJrI5niRoY0BCmE02fwmPcRYFPI4KI876xaE79YGWIKnEslMbuQPsIEsoU/DXa0DoA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-stream": "^4.4.0", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/credential-provider-imds": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.0.tgz", + "integrity": "sha512-SOhFVvFH4D5HJZytb0bLKxCrSnwcqPiNlrw+S4ZXjMnsC+o9JcUQzbZOEQcA8yv9wJFNhfsUiIUKiEnYL68Big==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/fetch-http-handler": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.0.tgz", + "integrity": "sha512-BG3KSmsx9A//KyIfw+sqNmWFr1YBUr+TwpxFT7yPqAk0yyDh7oSNgzfNH7pS6OC099EGx2ltOULvumCFe8bcgw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/hash-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.0.tgz", + "integrity": "sha512-ugv93gOhZGysTctZh9qdgng8B+xO0cj+zN0qAZ+Sgh7qTQGPOJbMdIuyP89KNfUyfAqFSNh5tMvC+h2uCpmTtA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/invalid-dependency": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.0.tgz", + "integrity": "sha512-ZmK5X5fUPAbtvRcUPtk28aqIClVhbfcmfoS4M7UQBTnDdrNxhsrxYVv0ZEl5NaPSyExsPWqL4GsPlRvtlwg+2A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/middleware-content-length": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.0.tgz", + "integrity": "sha512-6ZAnwrXFecrA4kIDOcz6aLBhU5ih2is2NdcZtobBDSdSHtE9a+MThB5uqyK4XXesdOCvOcbCm2IGB95birTSOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/middleware-endpoint": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.0.tgz", + "integrity": "sha512-jFVjuQeV8TkxaRlcCNg0GFVgg98tscsmIrIwRFeC74TIUyLE3jmY9xgc1WXrPQYRjQNK3aRoaIk6fhFRGOIoGw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.14.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/middleware-retry": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.0.tgz", + "integrity": "sha512-yaVBR0vQnOnzex45zZ8ZrPzUnX73eUC8kVFaAAbn04+6V7lPtxn56vZEBBAhgS/eqD6Zm86o6sJs6FuQVoX5qg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/service-error-classification": "^4.2.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/middleware-serde": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.0.tgz", + "integrity": "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/middleware-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.0.tgz", + "integrity": "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/node-config-provider": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.0.tgz", + "integrity": "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/node-http-handler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.3.0.tgz", + "integrity": "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/property-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.0.tgz", + "integrity": "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/protocol-http": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz", + "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/querystring-builder": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.0.tgz", + "integrity": "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/querystring-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.0.tgz", + "integrity": "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/service-error-classification": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.0.tgz", + "integrity": "sha512-Ylv1ttUeKatpR0wEOMnHf1hXMktPUMObDClSWl2TpCVT4DwtJhCeighLzSLbgH3jr5pBNM0LDXT5yYxUvZ9WpA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.0.tgz", + "integrity": "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/signature-v4": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.0.tgz", + "integrity": "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/smithy-client": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.7.0.tgz", + "integrity": "sha512-3BDx/aCCPf+kkinYf5QQhdQ9UAGihgOVqI3QO5xQfSaIWvUE4KYLtiGRWsNe1SR7ijXC0QEPqofVp5Sb0zC8xQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.14.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-stream": "^4.4.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/url-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.0.tgz", + "integrity": "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-base64": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.2.0.tgz", + "integrity": "sha512-+erInz8WDv5KPe7xCsJCp+1WCjSbah9gWcmUXc9NqmhyPx59tf7jqFz+za1tRG1Y5KM1Cy1rWCcGypylFp4mvA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-body-length-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.0.tgz", + "integrity": "sha512-U8q1WsSZFjXijlD7a4wsDQOvOwV+72iHSfq1q7VD+V75xP/pdtm0WIGuaFJ3gcADDOKj2MIBn4+zisi140HEnQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.2.0.tgz", + "integrity": "sha512-qzHp7ZDk1Ba4LDwQVCNp90xPGqSu7kmL7y5toBpccuhi3AH7dcVBIT/pUxYcInK4jOy6FikrcTGq5wxcka8UaQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.0.tgz", + "integrity": "sha512-FxUHS3WXgx3bTWR6yQHNHHkQHZm/XKIi/CchTnKvBulN6obWpcbzJ6lDToXn+Wp0QlVKd7uYAz2/CTw1j7m+Kg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.3.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-endpoints": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.0.tgz", + "integrity": "sha512-TXeCn22D56vvWr/5xPqALc9oO+LN+QpFjrSM7peG/ckqEPoI3zaKZFp+bFwfmiHhn5MGWPaLCqDOJPPIixk9Wg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-middleware": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.0.tgz", + "integrity": "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-retry": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.0.tgz", + "integrity": "sha512-BWSiuGbwRnEE2SFfaAZEX0TqaxtvtSYPM/J73PFVm+A29Fg1HTPiYFb8TmX1DXp4hgcdyJcNQmprfd5foeORsg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-stream": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.4.0.tgz", + "integrity": "sha512-vtO7ktbixEcrVzMRmpQDnw/Ehr9UWjBvSJ9fyAbadKkC4w5Cm/4lMO8cHz8Ysb8uflvQUNRcuux/oNHKPXkffg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-waiter": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.0.tgz", + "integrity": "sha512-0Z+nxUU4/4T+SL8BCNN4ztKdQjToNvUYmkF1kXO5T7Yz3Gafzh0HeIG6mrkN8Fz3gn9hSyxuAT+6h4vM+iQSBQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ssm": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.699.0.tgz", + "integrity": "sha512-ROynEZI8RZC+NkQP/BBcdkhGHuk+RJkinemxmsTt8FGYqHlK/7hcOjWitziAGpmjWfPm4a6UAtqeNg/AX7nvlw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.699.0", + "@aws-sdk/client-sts": "3.699.0", + "@aws-sdk/core": "3.696.0", + "@aws-sdk/credential-provider-node": "3.699.0", + "@aws-sdk/middleware-host-header": "3.696.0", + "@aws-sdk/middleware-logger": "3.696.0", + "@aws-sdk/middleware-recursion-detection": "3.696.0", + "@aws-sdk/middleware-user-agent": "3.696.0", + "@aws-sdk/region-config-resolver": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@aws-sdk/util-endpoints": "3.696.0", + "@aws-sdk/util-user-agent-browser": "3.696.0", + "@aws-sdk/util-user-agent-node": "3.696.0", + "@smithy/config-resolver": "^3.0.12", + "@smithy/core": "^2.5.3", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/hash-node": "^3.0.10", + "@smithy/invalid-dependency": "^3.0.10", + "@smithy/middleware-content-length": "^3.0.12", + "@smithy/middleware-endpoint": "^3.2.3", + "@smithy/middleware-retry": "^3.0.27", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/protocol-http": "^4.1.7", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.27", + "@smithy/util-defaults-mode-node": "^3.0.27", + "@smithy/util-endpoints": "^2.1.6", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.9", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.696.0.tgz", + "integrity": "sha512-q5TTkd08JS0DOkHfUL853tuArf7NrPeqoS5UOvqJho8ibV9Ak/a/HO4kNvy9Nj3cib/toHYHsQIEtecUPSUUrQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.696.0", + "@aws-sdk/middleware-host-header": "3.696.0", + "@aws-sdk/middleware-logger": "3.696.0", + "@aws-sdk/middleware-recursion-detection": "3.696.0", + "@aws-sdk/middleware-user-agent": "3.696.0", + "@aws-sdk/region-config-resolver": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@aws-sdk/util-endpoints": "3.696.0", + "@aws-sdk/util-user-agent-browser": "3.696.0", + "@aws-sdk/util-user-agent-node": "3.696.0", + "@smithy/config-resolver": "^3.0.12", + "@smithy/core": "^2.5.3", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/hash-node": "^3.0.10", + "@smithy/invalid-dependency": "^3.0.10", + "@smithy/middleware-content-length": "^3.0.12", + "@smithy/middleware-endpoint": "^3.2.3", + "@smithy/middleware-retry": "^3.0.27", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/protocol-http": "^4.1.7", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.27", + "@smithy/util-defaults-mode-node": "^3.0.27", + "@smithy/util-endpoints": "^2.1.6", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.699.0.tgz", + "integrity": "sha512-u8a1GorY5D1l+4FQAf4XBUC1T10/t7neuwT21r0ymrtMFSK2a9QqVHKMoLkvavAwyhJnARSBM9/UQC797PFOFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.696.0", + "@aws-sdk/credential-provider-node": "3.699.0", + "@aws-sdk/middleware-host-header": "3.696.0", + "@aws-sdk/middleware-logger": "3.696.0", + "@aws-sdk/middleware-recursion-detection": "3.696.0", + "@aws-sdk/middleware-user-agent": "3.696.0", + "@aws-sdk/region-config-resolver": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@aws-sdk/util-endpoints": "3.696.0", + "@aws-sdk/util-user-agent-browser": "3.696.0", + "@aws-sdk/util-user-agent-node": "3.696.0", + "@smithy/config-resolver": "^3.0.12", + "@smithy/core": "^2.5.3", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/hash-node": "^3.0.10", + "@smithy/invalid-dependency": "^3.0.10", + "@smithy/middleware-content-length": "^3.0.12", + "@smithy/middleware-endpoint": "^3.2.3", + "@smithy/middleware-retry": "^3.0.27", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/protocol-http": "^4.1.7", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.27", + "@smithy/util-defaults-mode-node": "^3.0.27", + "@smithy/util-endpoints": "^2.1.6", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.699.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.699.0.tgz", + "integrity": "sha512-++lsn4x2YXsZPIzFVwv3fSUVM55ZT0WRFmPeNilYIhZClxHLmVAWKH4I55cY9ry60/aTKYjzOXkWwyBKGsGvQg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.699.0", + "@aws-sdk/core": "3.696.0", + "@aws-sdk/credential-provider-node": "3.699.0", + "@aws-sdk/middleware-host-header": "3.696.0", + "@aws-sdk/middleware-logger": "3.696.0", + "@aws-sdk/middleware-recursion-detection": "3.696.0", + "@aws-sdk/middleware-user-agent": "3.696.0", + "@aws-sdk/region-config-resolver": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@aws-sdk/util-endpoints": "3.696.0", + "@aws-sdk/util-user-agent-browser": "3.696.0", + "@aws-sdk/util-user-agent-node": "3.696.0", + "@smithy/config-resolver": "^3.0.12", + "@smithy/core": "^2.5.3", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/hash-node": "^3.0.10", + "@smithy/invalid-dependency": "^3.0.10", + "@smithy/middleware-content-length": "^3.0.12", + "@smithy/middleware-endpoint": "^3.2.3", + "@smithy/middleware-retry": "^3.0.27", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/protocol-http": "^4.1.7", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.27", + "@smithy/util-defaults-mode-node": "^3.0.27", + "@smithy/util-endpoints": "^2.1.6", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.696.0.tgz", + "integrity": "sha512-3c9III1k03DgvRZWg8vhVmfIXPG6hAciN9MzQTzqGngzWAELZF/WONRTRQuDFixVtarQatmLHYVw/atGeA2Byw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/core": "^2.5.3", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.7", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/util-middleware": "^3.0.10", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.696.0.tgz", + "integrity": "sha512-T9iMFnJL7YTlESLpVFT3fg1Lkb1lD+oiaIC8KMpepb01gDUBIpj9+Y+pA/cgRWW0yRxmkDXNazAE2qQTVFGJzA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.696.0.tgz", + "integrity": "sha512-GV6EbvPi2eq1+WgY/o2RFA3P7HGmnkIzCNmhwtALFlqMroLYWKE7PSeHw66Uh1dFQeVESn0/+hiUNhu1mB0emA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.7", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/util-stream": "^3.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.699.0.tgz", + "integrity": "sha512-dXmCqjJnKmG37Q+nLjPVu22mNkrGHY8hYoOt3Jo9R2zr5MYV7s/NHsCHr+7E+BZ+tfZYLRPeB1wkpTeHiEcdRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/credential-provider-env": "3.696.0", + "@aws-sdk/credential-provider-http": "3.696.0", + "@aws-sdk/credential-provider-process": "3.696.0", + "@aws-sdk/credential-provider-sso": "3.699.0", + "@aws-sdk/credential-provider-web-identity": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.699.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.699.0.tgz", + "integrity": "sha512-MmEmNDo1bBtTgRmdNfdQksXu4uXe66s0p1hi1YPrn1h59Q605eq/xiWbGL6/3KdkViH6eGUuABeV2ODld86ylg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.696.0", + "@aws-sdk/credential-provider-http": "3.696.0", + "@aws-sdk/credential-provider-ini": "3.699.0", + "@aws-sdk/credential-provider-process": "3.696.0", + "@aws-sdk/credential-provider-sso": "3.699.0", + "@aws-sdk/credential-provider-web-identity": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.696.0.tgz", + "integrity": "sha512-mL1RcFDe9sfmyU5K1nuFkO8UiJXXxLX4JO1gVaDIOvPqwStpUAwi3A1BoeZhWZZNQsiKI810RnYGo0E0WB/hUA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.699.0.tgz", + "integrity": "sha512-Ekp2cZG4pl9D8+uKWm4qO1xcm8/MeiI8f+dnlZm8aQzizeC+aXYy9GyoclSf6daK8KfRPiRfM7ZHBBL5dAfdMA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.696.0", + "@aws-sdk/core": "3.696.0", + "@aws-sdk/token-providers": "3.699.0", + "@aws-sdk/types": "3.696.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.696.0.tgz", + "integrity": "sha512-XJ/CVlWChM0VCoc259vWguFUjJDn/QwDqHwbx+K9cg3v6yrqXfK5ai+p/6lx0nQpnk4JzPVeYYxWRpaTsGC9rg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.696.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.901.0.tgz", + "integrity": "sha512-mPF3N6eZlVs9G8aBSzvtoxR1RZqMo1aIwR+X8BAZSkhfj55fVF2no4IfPXfdFO3I66N+zEQ8nKoB0uTATWrogQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-config-provider": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/node-config-provider": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.0.tgz", + "integrity": "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/property-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.0.tgz", + "integrity": "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/protocol-http": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz", + "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.0.tgz", + "integrity": "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.901.0.tgz", + "integrity": "sha512-bwq9nj6MH38hlJwOY9QXIDwa6lI48UsaZpaXbdD71BljEIRlxDzfB4JaYb+ZNNK7RIAdzsP/K05mJty6KJAQHw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@smithy/protocol-http": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz", + "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.901.0.tgz", + "integrity": "sha512-63lcKfggVUFyXhE4SsFXShCTCyh7ZHEqXLyYEL4DwX+VWtxutf9t9m3fF0TNUYDE8eEGWiRXhegj8l4FjuW+wA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-stream": "^4.4.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/core": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.901.0.tgz", + "integrity": "sha512-brKAc3y64tdhyuEf+OPIUln86bRTqkLgb9xkd6kUdIeA5+qmp/N6amItQz+RN4k4O3kqkCPYnAd3LonTKluobw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@aws-sdk/xml-builder": "3.901.0", + "@smithy/core": "^3.14.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/signature-v4": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/abort-controller": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.0.tgz", + "integrity": "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/core": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.14.0.tgz", + "integrity": "sha512-XJ4z5FxvY/t0Dibms/+gLJrI5niRoY0BCmE02fwmPcRYFPI4KI876xaE79YGWIKnEslMbuQPsIEsoU/DXa0DoA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-stream": "^4.4.0", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/fetch-http-handler": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.0.tgz", + "integrity": "sha512-BG3KSmsx9A//KyIfw+sqNmWFr1YBUr+TwpxFT7yPqAk0yyDh7oSNgzfNH7pS6OC099EGx2ltOULvumCFe8bcgw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/middleware-endpoint": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.0.tgz", + "integrity": "sha512-jFVjuQeV8TkxaRlcCNg0GFVgg98tscsmIrIwRFeC74TIUyLE3jmY9xgc1WXrPQYRjQNK3aRoaIk6fhFRGOIoGw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.14.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/middleware-serde": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.0.tgz", + "integrity": "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/middleware-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.0.tgz", + "integrity": "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/node-config-provider": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.0.tgz", + "integrity": "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/node-http-handler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.3.0.tgz", + "integrity": "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/property-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.0.tgz", + "integrity": "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/protocol-http": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz", + "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/querystring-builder": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.0.tgz", + "integrity": "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/querystring-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.0.tgz", + "integrity": "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.0.tgz", + "integrity": "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/signature-v4": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.0.tgz", + "integrity": "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/smithy-client": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.7.0.tgz", + "integrity": "sha512-3BDx/aCCPf+kkinYf5QQhdQ9UAGihgOVqI3QO5xQfSaIWvUE4KYLtiGRWsNe1SR7ijXC0QEPqofVp5Sb0zC8xQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.14.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-stream": "^4.4.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/url-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.0.tgz", + "integrity": "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-base64": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.2.0.tgz", + "integrity": "sha512-+erInz8WDv5KPe7xCsJCp+1WCjSbah9gWcmUXc9NqmhyPx59tf7jqFz+za1tRG1Y5KM1Cy1rWCcGypylFp4mvA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-middleware": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.0.tgz", + "integrity": "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-stream": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.4.0.tgz", + "integrity": "sha512-vtO7ktbixEcrVzMRmpQDnw/Ehr9UWjBvSJ9fyAbadKkC4w5Cm/4lMO8cHz8Ysb8uflvQUNRcuux/oNHKPXkffg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.696.0.tgz", + "integrity": "sha512-zELJp9Ta2zkX7ELggMN9qMCgekqZhFC5V2rOr4hJDEb/Tte7gpfKSObAnw/3AYiVqt36sjHKfdkoTsuwGdEoDg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.901.0.tgz", + "integrity": "sha512-MuCS5R2ngNoYifkVt05CTULvYVWX0dvRT0/Md4jE3a0u0yMygYy31C1zorwfE/SUgAQXyLmUx8ATmPp9PppImQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.696.0.tgz", + "integrity": "sha512-KhkHt+8AjCxcR/5Zp3++YPJPpFQzxpr+jmONiT/Jw2yqnSngZ0Yspm5wGoRx2hS1HJbyZNuaOWEGuJoxLeBKfA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.696.0.tgz", + "integrity": "sha512-si/maV3Z0hH7qa99f9ru2xpS5HlfSVcasRlNUXKSDm611i7jFMWwGNLUOXFAOLhXotPX5G3Z6BLwL34oDeBMug==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.901.0.tgz", + "integrity": "sha512-prgjVC3fDT2VIlmQPiw/cLee8r4frTam9GILRUVQyDdNtshNwV3MiaSCLzzQJjKJlLgnBLNUHJCSmvUVtg+3iA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/core": "^3.14.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/signature-v4": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-stream": "^4.4.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/core": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.901.0.tgz", + "integrity": "sha512-brKAc3y64tdhyuEf+OPIUln86bRTqkLgb9xkd6kUdIeA5+qmp/N6amItQz+RN4k4O3kqkCPYnAd3LonTKluobw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@aws-sdk/xml-builder": "3.901.0", + "@smithy/core": "^3.14.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/signature-v4": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/abort-controller": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.0.tgz", + "integrity": "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/core": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.14.0.tgz", + "integrity": "sha512-XJ4z5FxvY/t0Dibms/+gLJrI5niRoY0BCmE02fwmPcRYFPI4KI876xaE79YGWIKnEslMbuQPsIEsoU/DXa0DoA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-stream": "^4.4.0", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/fetch-http-handler": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.0.tgz", + "integrity": "sha512-BG3KSmsx9A//KyIfw+sqNmWFr1YBUr+TwpxFT7yPqAk0yyDh7oSNgzfNH7pS6OC099EGx2ltOULvumCFe8bcgw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/middleware-endpoint": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.0.tgz", + "integrity": "sha512-jFVjuQeV8TkxaRlcCNg0GFVgg98tscsmIrIwRFeC74TIUyLE3jmY9xgc1WXrPQYRjQNK3aRoaIk6fhFRGOIoGw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.14.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/middleware-serde": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.0.tgz", + "integrity": "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/middleware-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.0.tgz", + "integrity": "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/node-config-provider": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.0.tgz", + "integrity": "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/node-http-handler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.3.0.tgz", + "integrity": "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/property-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.0.tgz", + "integrity": "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/protocol-http": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz", + "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/querystring-builder": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.0.tgz", + "integrity": "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/querystring-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.0.tgz", + "integrity": "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.0.tgz", + "integrity": "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/signature-v4": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.0.tgz", + "integrity": "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/smithy-client": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.7.0.tgz", + "integrity": "sha512-3BDx/aCCPf+kkinYf5QQhdQ9UAGihgOVqI3QO5xQfSaIWvUE4KYLtiGRWsNe1SR7ijXC0QEPqofVp5Sb0zC8xQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.14.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-stream": "^4.4.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/url-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.0.tgz", + "integrity": "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-base64": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.2.0.tgz", + "integrity": "sha512-+erInz8WDv5KPe7xCsJCp+1WCjSbah9gWcmUXc9NqmhyPx59tf7jqFz+za1tRG1Y5KM1Cy1rWCcGypylFp4mvA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-middleware": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.0.tgz", + "integrity": "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-stream": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.4.0.tgz", + "integrity": "sha512-vtO7ktbixEcrVzMRmpQDnw/Ehr9UWjBvSJ9fyAbadKkC4w5Cm/4lMO8cHz8Ysb8uflvQUNRcuux/oNHKPXkffg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.901.0.tgz", + "integrity": "sha512-YiLLJmA3RvjL38mFLuu8fhTTGWtp2qT24VqpucgfoyziYcTgIQkJJmKi90Xp6R6/3VcArqilyRgM1+x8i/em+Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.696.0.tgz", + "integrity": "sha512-Lvyj8CTyxrHI6GHd2YVZKIRI5Fmnugt3cpJo0VrKKEgK5zMySwEZ1n4dqPK6czYRWKd5+WnYHYAuU+Wdk6Jsjw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@aws-sdk/util-endpoints": "3.696.0", + "@smithy/core": "^2.5.3", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.901.0.tgz", + "integrity": "sha512-feAAAMsVwctk2Tms40ONybvpfJPLCmSdI+G+OTrNpizkGLNl6ik2Ng2RzxY6UqOfN8abqKP/DOUj1qYDRDG8ag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.901.0", + "@aws-sdk/middleware-host-header": "3.901.0", + "@aws-sdk/middleware-logger": "3.901.0", + "@aws-sdk/middleware-recursion-detection": "3.901.0", + "@aws-sdk/middleware-user-agent": "3.901.0", + "@aws-sdk/region-config-resolver": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@aws-sdk/util-user-agent-browser": "3.901.0", + "@aws-sdk/util-user-agent-node": "3.901.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/core": "^3.14.0", + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/hash-node": "^4.2.0", + "@smithy/invalid-dependency": "^4.2.0", + "@smithy/middleware-content-length": "^4.2.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-retry": "^4.4.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.0", + "@smithy/util-defaults-mode-browser": "^4.2.0", + "@smithy/util-defaults-mode-node": "^4.2.0", + "@smithy/util-endpoints": "^3.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/core": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.901.0.tgz", + "integrity": "sha512-brKAc3y64tdhyuEf+OPIUln86bRTqkLgb9xkd6kUdIeA5+qmp/N6amItQz+RN4k4O3kqkCPYnAd3LonTKluobw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@aws-sdk/xml-builder": "3.901.0", + "@smithy/core": "^3.14.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/signature-v4": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.901.0.tgz", + "integrity": "sha512-yWX7GvRmqBtbNnUW7qbre3GvZmyYwU0WHefpZzDTYDoNgatuYq6LgUIQ+z5C04/kCRoFkAFrHag8a3BXqFzq5A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-logger": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.901.0.tgz", + "integrity": "sha512-UoHebjE7el/tfRo8/CQTj91oNUm+5Heus5/a4ECdmWaSCHCS/hXTsU3PTTHAY67oAQR8wBLFPfp3mMvXjB+L2A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.901.0.tgz", + "integrity": "sha512-Wd2t8qa/4OL0v/oDpCHHYkgsXJr8/ttCxrvCKAt0H1zZe2LlRhY9gpDVKqdertfHrHDj786fOvEQA28G1L75Dg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.901.0.tgz", + "integrity": "sha512-Zby4F03fvD9xAgXGPywyk4bC1jCbnyubMEYChLYohD+x20ULQCf+AimF/Btn7YL+hBpzh1+RmqmvZcx+RgwgNQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@smithy/core": "^3.14.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.901.0.tgz", + "integrity": "sha512-7F0N888qVLHo4CSQOsnkZ4QAp8uHLKJ4v3u09Ly5k4AEStrSlFpckTPyUx6elwGL+fxGjNE2aakK8vEgzzCV0A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.901.0.tgz", + "integrity": "sha512-5nZP3hGA8FHEtKvEQf4Aww5QZOkjLW1Z+NixSd+0XKfHvA39Ah5sZboScjLx0C9kti/K3OGW1RCx5K9Zc3bZqg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-endpoints": "^3.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.901.0.tgz", + "integrity": "sha512-Ntb6V/WFI21Ed4PDgL/8NSfoZQQf9xzrwNgiwvnxgAl/KvAvRBgQtqj5gHsDX8Nj2YmJuVoHfH9BGjL9VQ4WNg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.901.0.tgz", + "integrity": "sha512-l59KQP5TY7vPVUfEURc7P5BJKuNg1RSsAKBQW7LHLECXjLqDUbo2SMLrexLBEoArSt6E8QOrIN0C8z/0Xk0jYw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/abort-controller": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.0.tgz", + "integrity": "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/config-resolver": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.3.0.tgz", + "integrity": "sha512-9oH+n8AVNiLPK/iK/agOsoWfrKZ3FGP3502tkksd6SRsKMYiu7AFX0YXo6YBADdsAj7C+G/aLKdsafIJHxuCkQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/core": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.14.0.tgz", + "integrity": "sha512-XJ4z5FxvY/t0Dibms/+gLJrI5niRoY0BCmE02fwmPcRYFPI4KI876xaE79YGWIKnEslMbuQPsIEsoU/DXa0DoA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-stream": "^4.4.0", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/credential-provider-imds": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.0.tgz", + "integrity": "sha512-SOhFVvFH4D5HJZytb0bLKxCrSnwcqPiNlrw+S4ZXjMnsC+o9JcUQzbZOEQcA8yv9wJFNhfsUiIUKiEnYL68Big==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/fetch-http-handler": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.0.tgz", + "integrity": "sha512-BG3KSmsx9A//KyIfw+sqNmWFr1YBUr+TwpxFT7yPqAk0yyDh7oSNgzfNH7pS6OC099EGx2ltOULvumCFe8bcgw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/hash-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.0.tgz", + "integrity": "sha512-ugv93gOhZGysTctZh9qdgng8B+xO0cj+zN0qAZ+Sgh7qTQGPOJbMdIuyP89KNfUyfAqFSNh5tMvC+h2uCpmTtA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/invalid-dependency": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.0.tgz", + "integrity": "sha512-ZmK5X5fUPAbtvRcUPtk28aqIClVhbfcmfoS4M7UQBTnDdrNxhsrxYVv0ZEl5NaPSyExsPWqL4GsPlRvtlwg+2A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-content-length": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.0.tgz", + "integrity": "sha512-6ZAnwrXFecrA4kIDOcz6aLBhU5ih2is2NdcZtobBDSdSHtE9a+MThB5uqyK4XXesdOCvOcbCm2IGB95birTSOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-endpoint": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.0.tgz", + "integrity": "sha512-jFVjuQeV8TkxaRlcCNg0GFVgg98tscsmIrIwRFeC74TIUyLE3jmY9xgc1WXrPQYRjQNK3aRoaIk6fhFRGOIoGw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.14.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-retry": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.0.tgz", + "integrity": "sha512-yaVBR0vQnOnzex45zZ8ZrPzUnX73eUC8kVFaAAbn04+6V7lPtxn56vZEBBAhgS/eqD6Zm86o6sJs6FuQVoX5qg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/service-error-classification": "^4.2.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-serde": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.0.tgz", + "integrity": "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.0.tgz", + "integrity": "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-config-provider": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.0.tgz", + "integrity": "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.3.0.tgz", + "integrity": "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/property-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.0.tgz", + "integrity": "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/protocol-http": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz", + "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-builder": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.0.tgz", + "integrity": "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.0.tgz", + "integrity": "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/service-error-classification": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.0.tgz", + "integrity": "sha512-Ylv1ttUeKatpR0wEOMnHf1hXMktPUMObDClSWl2TpCVT4DwtJhCeighLzSLbgH3jr5pBNM0LDXT5yYxUvZ9WpA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.0.tgz", + "integrity": "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/signature-v4": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.0.tgz", + "integrity": "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/smithy-client": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.7.0.tgz", + "integrity": "sha512-3BDx/aCCPf+kkinYf5QQhdQ9UAGihgOVqI3QO5xQfSaIWvUE4KYLtiGRWsNe1SR7ijXC0QEPqofVp5Sb0zC8xQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.14.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-stream": "^4.4.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/url-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.0.tgz", + "integrity": "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-base64": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.2.0.tgz", + "integrity": "sha512-+erInz8WDv5KPe7xCsJCp+1WCjSbah9gWcmUXc9NqmhyPx59tf7jqFz+za1tRG1Y5KM1Cy1rWCcGypylFp4mvA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-body-length-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.0.tgz", + "integrity": "sha512-U8q1WsSZFjXijlD7a4wsDQOvOwV+72iHSfq1q7VD+V75xP/pdtm0WIGuaFJ3gcADDOKj2MIBn4+zisi140HEnQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.2.0.tgz", + "integrity": "sha512-qzHp7ZDk1Ba4LDwQVCNp90xPGqSu7kmL7y5toBpccuhi3AH7dcVBIT/pUxYcInK4jOy6FikrcTGq5wxcka8UaQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.0.tgz", + "integrity": "sha512-FxUHS3WXgx3bTWR6yQHNHHkQHZm/XKIi/CchTnKvBulN6obWpcbzJ6lDToXn+Wp0QlVKd7uYAz2/CTw1j7m+Kg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.3.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-endpoints": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.0.tgz", + "integrity": "sha512-TXeCn22D56vvWr/5xPqALc9oO+LN+QpFjrSM7peG/ckqEPoI3zaKZFp+bFwfmiHhn5MGWPaLCqDOJPPIixk9Wg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-middleware": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.0.tgz", + "integrity": "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-retry": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.0.tgz", + "integrity": "sha512-BWSiuGbwRnEE2SFfaAZEX0TqaxtvtSYPM/J73PFVm+A29Fg1HTPiYFb8TmX1DXp4hgcdyJcNQmprfd5foeORsg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-stream": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.4.0.tgz", + "integrity": "sha512-vtO7ktbixEcrVzMRmpQDnw/Ehr9UWjBvSJ9fyAbadKkC4w5Cm/4lMO8cHz8Ysb8uflvQUNRcuux/oNHKPXkffg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.696.0.tgz", + "integrity": "sha512-7EuH142lBXjI8yH6dVS/CZeiK/WZsmb/8zP6bQbVYpMrppSTgB3MzZZdxVZGzL5r8zPQOU10wLC4kIMy0qdBVQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.901.0.tgz", + "integrity": "sha512-G/0G5tL3beETs2zkI0YQuM2SkrAsYJSe2vN2XtouVSN5c9v6EiSvdSsHAqMhLebnSs2suUkq0JO9ZotbXkUfMQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/signature-v4-multi-region": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-format-url": "3.901.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/abort-controller": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.0.tgz", + "integrity": "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/core": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.14.0.tgz", + "integrity": "sha512-XJ4z5FxvY/t0Dibms/+gLJrI5niRoY0BCmE02fwmPcRYFPI4KI876xaE79YGWIKnEslMbuQPsIEsoU/DXa0DoA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-stream": "^4.4.0", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/fetch-http-handler": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.0.tgz", + "integrity": "sha512-BG3KSmsx9A//KyIfw+sqNmWFr1YBUr+TwpxFT7yPqAk0yyDh7oSNgzfNH7pS6OC099EGx2ltOULvumCFe8bcgw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/middleware-endpoint": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.0.tgz", + "integrity": "sha512-jFVjuQeV8TkxaRlcCNg0GFVgg98tscsmIrIwRFeC74TIUyLE3jmY9xgc1WXrPQYRjQNK3aRoaIk6fhFRGOIoGw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.14.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/middleware-serde": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.0.tgz", + "integrity": "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/middleware-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.0.tgz", + "integrity": "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/node-config-provider": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.0.tgz", + "integrity": "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/node-http-handler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.3.0.tgz", + "integrity": "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/property-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.0.tgz", + "integrity": "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/protocol-http": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz", + "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/querystring-builder": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.0.tgz", + "integrity": "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/querystring-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.0.tgz", + "integrity": "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.0.tgz", + "integrity": "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/smithy-client": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.7.0.tgz", + "integrity": "sha512-3BDx/aCCPf+kkinYf5QQhdQ9UAGihgOVqI3QO5xQfSaIWvUE4KYLtiGRWsNe1SR7ijXC0QEPqofVp5Sb0zC8xQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.14.0", + "@smithy/middleware-endpoint": "^4.3.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-stream": "^4.4.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/url-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.0.tgz", + "integrity": "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-base64": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.2.0.tgz", + "integrity": "sha512-+erInz8WDv5KPe7xCsJCp+1WCjSbah9gWcmUXc9NqmhyPx59tf7jqFz+za1tRG1Y5KM1Cy1rWCcGypylFp4mvA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-middleware": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.0.tgz", + "integrity": "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-stream": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.4.0.tgz", + "integrity": "sha512-vtO7ktbixEcrVzMRmpQDnw/Ehr9UWjBvSJ9fyAbadKkC4w5Cm/4lMO8cHz8Ysb8uflvQUNRcuux/oNHKPXkffg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.901.0.tgz", + "integrity": "sha512-2IWxbll/pRucp1WQkHi2W5E2SVPGBvk4Is923H7gpNksbVFws18ItjMM8ZpGm44cJEoy1zR5gjhLFklatpuoOw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/signature-v4": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/protocol-http": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz", + "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/signature-v4": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.0.tgz", + "integrity": "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/util-middleware": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.0.tgz", + "integrity": "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.696.0", - "@aws-sdk/credential-provider-http": "3.696.0", - "@aws-sdk/credential-provider-ini": "3.699.0", - "@aws-sdk/credential-provider-process": "3.696.0", - "@aws-sdk/credential-provider-sso": "3.699.0", - "@aws-sdk/credential-provider-web-identity": "3.696.0", - "@aws-sdk/types": "3.696.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.696.0.tgz", - "integrity": "sha512-mL1RcFDe9sfmyU5K1nuFkO8UiJXXxLX4JO1gVaDIOvPqwStpUAwi3A1BoeZhWZZNQsiKI810RnYGo0E0WB/hUA==", + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.696.0", - "@aws-sdk/types": "3.696.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.699.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.699.0.tgz", - "integrity": "sha512-Ekp2cZG4pl9D8+uKWm4qO1xcm8/MeiI8f+dnlZm8aQzizeC+aXYy9GyoclSf6daK8KfRPiRfM7ZHBBL5dAfdMA==", + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.696.0", - "@aws-sdk/core": "3.696.0", - "@aws-sdk/token-providers": "3.699.0", - "@aws-sdk/types": "3.696.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.1", + "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.696.0.tgz", - "integrity": "sha512-XJ/CVlWChM0VCoc259vWguFUjJDn/QwDqHwbx+K9cg3v6yrqXfK5ai+p/6lx0nQpnk4JzPVeYYxWRpaTsGC9rg==", + "node_modules/@aws-sdk/token-providers": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.699.0.tgz", + "integrity": "sha512-kuiEW9DWs7fNos/SM+y58HCPhcIzm1nEZLhe2/7/6+TvAYLuEWURYsbK48gzsxXlaJ2k/jGY3nIsA7RptbMOwA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.696.0", "@aws-sdk/types": "3.696.0", "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, @@ -544,17 +4196,15 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.696.0" + "@aws-sdk/client-sso-oidc": "^3.699.0" } }, - "node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/types": { "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.696.0.tgz", - "integrity": "sha512-zELJp9Ta2zkX7ELggMN9qMCgekqZhFC5V2rOr4hJDEb/Tte7gpfKSObAnw/3AYiVqt36sjHKfdkoTsuwGdEoDg==", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.696.0.tgz", + "integrity": "sha512-9rTvUJIAj5d3//U5FDPWGJ1nFJLuWb30vugGOrWk7aNZ6y9tuA3PI7Cc9dP8WEXKVyK1vuuk8rSFP2iqXnlgrw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.696.0", - "@smithy/protocol-http": "^4.1.7", "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, @@ -562,115 +4212,97 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.696.0.tgz", - "integrity": "sha512-KhkHt+8AjCxcR/5Zp3++YPJPpFQzxpr+jmONiT/Jw2yqnSngZ0Yspm5wGoRx2hS1HJbyZNuaOWEGuJoxLeBKfA==", + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", + "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.696.0", - "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection": { + "node_modules/@aws-sdk/util-endpoints": { "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.696.0.tgz", - "integrity": "sha512-si/maV3Z0hH7qa99f9ru2xpS5HlfSVcasRlNUXKSDm611i7jFMWwGNLUOXFAOLhXotPX5G3Z6BLwL34oDeBMug==", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.696.0.tgz", + "integrity": "sha512-T5s0IlBVX+gkb9g/I6CLt4yAZVzMSiGnbUqWihWsHvQR1WOoIcndQy/Oz/IJXT9T2ipoy7a80gzV6a5mglrioA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.696.0", - "@smithy/protocol-http": "^4.1.7", "@smithy/types": "^3.7.1", + "@smithy/util-endpoints": "^2.1.6", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.696.0.tgz", - "integrity": "sha512-Lvyj8CTyxrHI6GHd2YVZKIRI5Fmnugt3cpJo0VrKKEgK5zMySwEZ1n4dqPK6czYRWKd5+WnYHYAuU+Wdk6Jsjw==", + "node_modules/@aws-sdk/util-format-url": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.901.0.tgz", + "integrity": "sha512-GGUnJKrh3OF1F3YRSWtwPLbN904Fcfxf03gujyq1rcrDRPEkzoZB+2BzNkB27SsU6lAlwNq+4aRlZRVUloPiag==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.696.0", - "@aws-sdk/types": "3.696.0", - "@aws-sdk/util-endpoints": "3.696.0", - "@smithy/core": "^2.5.3", - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", + "@aws-sdk/types": "3.901.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.696.0.tgz", - "integrity": "sha512-7EuH142lBXjI8yH6dVS/CZeiK/WZsmb/8zP6bQbVYpMrppSTgB3MzZZdxVZGzL5r8zPQOU10wLC4kIMy0qdBVQ==", + "node_modules/@aws-sdk/util-format-url/node_modules/@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.696.0", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/types": "^3.7.1", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.10", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.699.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.699.0.tgz", - "integrity": "sha512-kuiEW9DWs7fNos/SM+y58HCPhcIzm1nEZLhe2/7/6+TvAYLuEWURYsbK48gzsxXlaJ2k/jGY3nIsA7RptbMOwA==", + "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/querystring-builder": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.0.tgz", + "integrity": "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.696.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.6.0", + "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.699.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/types": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.696.0.tgz", - "integrity": "sha512-9rTvUJIAj5d3//U5FDPWGJ1nFJLuWb30vugGOrWk7aNZ6y9tuA3PI7Cc9dP8WEXKVyK1vuuk8rSFP2iqXnlgrw==", + "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.696.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.696.0.tgz", - "integrity": "sha512-T5s0IlBVX+gkb9g/I6CLt4yAZVzMSiGnbUqWihWsHvQR1WOoIcndQy/Oz/IJXT9T2ipoy7a80gzV6a5mglrioA==", + "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.696.0", - "@smithy/types": "^3.7.1", - "@smithy/util-endpoints": "^2.1.6", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { @@ -721,6 +4353,71 @@ } } }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.901.0.tgz", + "integrity": "sha512-pxFCkuAP7Q94wMTNPAwi6hEtNrp/BdFf+HOrIEeFQsk4EoOmpKY3I6S+u6A9Wg295J80Kh74LqDWM22ux3z6Aw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", + "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1432,9 +5129,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -1475,14 +5172,14 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "peer": true, "dependencies": { - "@eslint/object-schema": "^2.1.5", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -1491,9 +5188,9 @@ } }, "node_modules/@eslint/config-array/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "peer": true, @@ -1517,10 +5214,24 @@ "license": "MIT", "peer": true }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", - "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1532,9 +5243,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "peer": true, @@ -1557,9 +5268,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "peer": true, @@ -1584,20 +5295,23 @@ "peer": true }, "node_modules/@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", "dev": true, "license": "MIT", "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1606,13 +5320,14 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", - "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", "dev": true, "license": "Apache-2.0", "peer": true, "dependencies": { + "@eslint/core": "^0.16.0", "levn": "^0.4.1" }, "engines": { @@ -1916,9 +5631,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -2790,11 +6505,88 @@ "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", + "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.0.tgz", + "integrity": "sha512-HNbGWdyTfSM1nfrZKQjYTvD8k086+M8s1EYkBUdGC++lhxegUp2HgNf5RIt6oOGVvsC26hBCW/11tv8KbwLn/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native/node_modules/@smithy/util-base64": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.2.0.tgz", + "integrity": "sha512-+erInz8WDv5KPe7xCsJCp+1WCjSbah9gWcmUXc9NqmhyPx59tf7jqFz+za1tRG1Y5KM1Cy1rWCcGypylFp4mvA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/config-resolver": { @@ -2848,6 +6640,148 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.0.tgz", + "integrity": "sha512-XE7CtKfyxYiNZ5vz7OvyTf1osrdbJfmUy+rbh+NLQmZumMGvY0mT0Cq1qKSfhrvLtRYzMsOBuRpi10dyI0EBPg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.6.0", + "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.0.tgz", + "integrity": "sha512-U53p7fcrk27k8irLhOwUu+UYnBqsXNLKl1XevOpsxK3y1Lndk8R7CSiZV6FN3fYFuTPuJy5pP6qa/bjDzEkRvA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.0.tgz", + "integrity": "sha512-uwx54t8W2Yo9Jr3nVF5cNnkAAnMCJ8Wrm+wDlQY6rY/IrEgZS3OqagtCu/9ceIcZFQ1zVW/zbN9dxb5esuojfA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.0.tgz", + "integrity": "sha512-yjM2L6QGmWgJjVu/IgYd6hMzwm/tf4VFX0lm8/SvGbGBwc+aFl3hOzvO/e9IJ2XI+22Tx1Zg3vRpFRs04SWFcg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.0.tgz", + "integrity": "sha512-C3jxz6GeRzNyGKhU7oV656ZbuHY93mrfkT12rmjDdZch142ykjn8do+VOkeRNjSGKw01p4g+hdalPYPhmMwk1g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/fetch-http-handler": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.1.tgz", @@ -2861,6 +6795,33 @@ "tslib": "^2.6.2" } }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.0.tgz", + "integrity": "sha512-MWmrRTPqVKpN8NmxmJPTeQuhewTt8Chf+waB38LXHZoA02+BeWYVQ9ViAwHjug8m7lQb1UWuGqp3JoGDOWvvuA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.0", + "@smithy/chunked-blob-reader-native": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/hash-node": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.10.tgz", @@ -2876,6 +6837,70 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.0.tgz", + "integrity": "sha512-8dELAuGv+UEjtzrpMeNBZc1sJhO8GxFVV/Yh21wE35oX4lOE697+lsMHBoUIFAUuYkTMIeu0EuJSEsH7/8Y+UQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/invalid-dependency": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.10.tgz", @@ -2898,6 +6923,70 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/md5-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.0.tgz", + "integrity": "sha512-LFEPniXGKRQArFmDQ3MgArXlClFJMsXDteuQQY8WG1/zzv6gVSo96+qpkuu1oJp4MZsKrwchY0cuAoPKzEbaNA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.6.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js/node_modules/@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/middleware-content-length": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.12.tgz", @@ -3351,6 +7440,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@stylistic/eslint-plugin-js": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.12.1.tgz", @@ -3910,9 +8011,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -4069,25 +8170,26 @@ } }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "node_modules/axios/node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -5122,23 +9224,24 @@ } }, "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -5146,9 +9249,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5209,9 +9312,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -5276,15 +9379,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5565,15 +9668,6 @@ "license": "MIT", "peer": true }, - "node_modules/fast-redact": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -6405,9 +10499,9 @@ "license": "ISC" }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "peer": true, @@ -8532,20 +12626,20 @@ } }, "node_modules/pino": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", - "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.13.1.tgz", + "integrity": "sha512-Szuj+ViDTjKPQYiKumGmEn3frdl+ZPSdosHyt9SnUevFosOkMY2b7ipxlEctNKPmMD/VibeBI+ZcZCJK+4DPuw==", "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", - "process-warning": "^4.0.0", + "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", + "slow-redact": "^0.3.0", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, @@ -8604,6 +12698,22 @@ "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", "license": "MIT" }, + "node_modules/pino/node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -9362,6 +13472,12 @@ "node": ">=8" } }, + "node_modules/slow-redact": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.1.tgz", + "integrity": "sha512-NvFvl1GuLZNW4U046Tfi8b26zXo8aBzgCAS2f7yVJR/fArN93mOqSA99cB9uITm92ajSz01bsu1K7SCVVjIMpQ==", + "license": "MIT" + }, "node_modules/sonic-boom": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", @@ -9619,9 +13735,9 @@ } }, "node_modules/superagent/node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 8f17d23..edc1be7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "author": "", "license": "ISC", "dependencies": { + "@aws-sdk/client-s3": "^3.901.0", "@aws-sdk/client-ssm": "^3.699.0", + "@aws-sdk/s3-request-presigner": "^3.901.0", "@prisma/client": "^5.21.1", "axios": "^1.8.4", "config": "^3.3.12", From f58674df6e5c7bd0360963b2739a1e14523535a6 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 5 Oct 2025 16:53:33 +0200 Subject: [PATCH 075/135] presigned URL + endpoint --- aws/s3_client.js | 50 +++++++++++++++++++++++++++++++++++++++++++++ routes/routes.js | 2 ++ routes/s3.js | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 aws/s3_client.js create mode 100644 routes/s3.js diff --git a/aws/s3_client.js b/aws/s3_client.js new file mode 100644 index 0000000..0ed5159 --- /dev/null +++ b/aws/s3_client.js @@ -0,0 +1,50 @@ +const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3"); +const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); +const config = require("config"); + +const awsConfig = config.aws; +const s3 = new S3Client({ region: awsConfig.aws_region }); + +const BUCKET_PREFIXES = { + applications: "videos/applications", + // images and-or other video categories will be added later +}; + +const MIME_EXTENSION_MAP = { + "video/mp4": "mp4", + "video/webm": "webm", + "video/ogg": "ogv", + "video/quicktime": "mov", +}; + +async function generateUploadUrl(bucketPrefix, filename, contentType) { + if (!Object.values(BUCKET_PREFIXES).includes(bucketPrefix)) { + throw new Error("Invalid bucket prefix"); + } + + filename = `${filename}.${MIME_EXTENSION_MAP[contentType]}`; + const fullKey = buildFullObjectKey(bucketPrefix, filename); + + const command = new PutObjectCommand({ + Bucket: awsConfig.s3_bucket, + Key: fullKey, + ContentType: contentType, + StorageClass: "INTELLIGENT_TIERING", // Cheapest option, auto-moves to colder storage based on access patterns + }); + + const url = await getSignedUrl(s3, command, { expiresIn: 3600 }); + return { + url, + filename, + }; +} + +function buildFullObjectKey(bucketPrefix, filename) { + return `${bucketPrefix}/${filename}`; +} + +module.exports = { + BUCKET_PREFIXES, + MIME_EXTENSION_MAP, + generateUploadUrl, +}; \ No newline at end of file diff --git a/routes/routes.js b/routes/routes.js index 3d42ef8..13ba73a 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -8,6 +8,7 @@ const mailerService = require("../services/mailer_service"); const discordRoutes = require("./discord"); const adminRoutes = require("./admin"); const mentorsRoutes = require("./mentors"); +const s3routes = require("./s3"); const { verifyRoles } = require("../middleware/authorization"); const { ROLE_NAMES, APPL_STATUSES } = require("../utils"); @@ -20,6 +21,7 @@ router.use(rolesRoutes); router.use(discordRoutes); router.use(adminRoutes); router.use(mentorsRoutes); +router.use(s3routes); router.get("/", (req, res) => { res.send("API Specification in Swagger: https://catbytes-community.github.io/webplatform-backend/"); diff --git a/routes/s3.js b/routes/s3.js new file mode 100644 index 0000000..e8e226a --- /dev/null +++ b/routes/s3.js @@ -0,0 +1,53 @@ +const express = require("express"); +const { respondWithError } = require("./helpers"); +const { generateUploadUrl, BUCKET_PREFIXES, MIME_EXTENSION_MAP } = require("../aws/s3_client"); +const { validate, version } = require('uuid'); +const logger = require('../logger')(__filename); + +const router = express.Router(); +router.use(express.json()); + +router.post('/presigned-url', async (req, res) => { + const { objectKey, contentType, objectType } = req.body; + + if (!objectKey || !contentType || !objectType) { + return respondWithError(res, 400, 'ObjectKey, contentType and objectType are required.'); + } + + try { + if (!Object.keys(MIME_EXTENSION_MAP).includes(contentType)) { + return respondWithError(res, 400, 'Unsupported contentType provided.'); + } + if (!validateObjectKeyIsUuid(objectKey)){ + return respondWithError(res, 400, 'objectKey must be a valid uuid v4.'); + } + + var bucketKey; + switch (objectType) { + case 'application_video': + // currently only application videos are supported + bucketKey = BUCKET_PREFIXES.applications; + break; + default: + return respondWithError(res, 400, 'Invalid objectType provided.'); + } + + result = await generateUploadUrl(bucketKey, objectKey, contentType); + res.json(result); + + } catch (err) { + logger.error(`Error when trying to get presigned url for object ${objectType}`, err); + respondWithError(res); + } +}); + +function validateObjectKeyIsUuid(objectKey) { + try { + return validate(objectKey) && version(objectKey) === 4; + } + catch (error) { + return false; + } +} + +module.exports = router; From 5c69aea86035632f839ff08d75f87bb8adea1edf Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 5 Oct 2025 17:45:05 +0200 Subject: [PATCH 076/135] updated swagger --- docs/catbytes_api_swagger.json | 90 ++++++++++++++++++++++++++++++++++ docs/catbytes_api_swagger.yaml | 75 ++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index 0854f36..0bf30d7 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -2028,6 +2028,96 @@ } } } + }, + "/presigned-url": { + "post": { + "tags": ["service endpoints"], + "summary": "✅ Generate presigned S3 upload URL", + "description": "Generates a presigned URL to upload a file to S3 (e.g. application video).\nCurrently supports only `application_video` objectType.\nRequires `objectKey` to be a valid UUIDv4 string. \nReturned filename should be sent to backend in a separate request to store in the database.", + "operationId": "generatePresignedUrl", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["objectKey", "contentType", "objectType"], + "properties": { + "objectKey": { + "type": "string", + "format": "uuid", + "example": "d41d8cd9-8f00-3204-a980-001e4bcd0000" + }, + "contentType": { + "type": "string", + "example": "video/mp4" + }, + "objectType": { + "type": "string", + "enum": ["application_video"], + "example": "application_video" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successfully generated presigned URL", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uploadUrl": { + "type": "string", + "format": "uri", + "example": "https://catbytes-bucket.s3.amazonaws.com/videos/applications/d41d8cd9-8f00-3204-a980-001e4bcd0000.mp4?X-Amz-Algorithm=..." + }, + "key": { + "type": "string", + "example": "d41d8cd9-8f00-3204-a980-001e4bcd0000.mp4" + } + } + } + } + } + }, + "400": { + "description": "Invalid input (missing fields, unsupported content type, invalid objectType, etc.)", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "objectKey must be a valid uuid v4." + } + } + } + } + } + }, + "500": { + "description": "Server error during URL generation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Failed to generate presigned URL" + } + } + } + } + } + } + } + } } }, "components": { diff --git a/docs/catbytes_api_swagger.yaml b/docs/catbytes_api_swagger.yaml index 87c74a9..77535bf 100644 --- a/docs/catbytes_api_swagger.yaml +++ b/docs/catbytes_api_swagger.yaml @@ -31,6 +31,9 @@ tags: description: Projects and collaborations - name: discord integration description: Discord related operations + - name: service endpoints + description: Enpoints for service operations + paths: /applications: post: @@ -1278,6 +1281,7 @@ paths: description: Not authorized '404': description: Mentor not found + /generate-invite: post: tags: @@ -1354,6 +1358,77 @@ paths: schema: type: string example: https://discord.com/api/oauth2/authorize?client_id=12345&redirect_uri=https%3A%2F%2Fdevapi.catbytes.io%2Fauth%2Fdiscord%2Fcallback&response_type=code&scope=identify%20email + + /presigned-url: + post: + tags: + - service endpoints + summary: ✅ Generate presigned S3 upload URL + description: | + Generates a presigned URL to upload a file to S3 (e.g. application video). + Currently supports only `application_video` objectType. + Requires `objectKey` to be a valid UUIDv4 string. + Returned filename should be sent to backend in a separate request to store in the database. + operationId: generatePresignedUrl + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - objectKey + - contentType + - objectType + properties: + objectKey: + type: string + format: uuid + example: d41d8cd9-8f00-3204-a980-001e4bcd0000 + contentType: + type: string + example: video/mp4 + objectType: + type: string + enum: + - application_video + example: application_video + responses: + '200': + description: Successfully generated presigned URL + content: + application/json: + schema: + type: object + properties: + uploadUrl: + type: string + format: uri + example: https://catbytes-bucket.s3.amazonaws.com/videos/applications/d41d8cd9-8f00-3204-a980-001e4bcd0000.mp4?X-Amz-Algorithm=... + key: + type: string + example: d41d8cd9-8f00-3204-a980-001e4bcd0000.mp4 + '400': + description: Invalid input (missing fields, unsupported content type, invalid objectType, etc.) + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: objectKey must be a valid uuid v4. + '500': + description: Server error during URL generation + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: Failed to generate presigned URL + components: schemas: MentorCreateRequest: From 597b51fdc8af75e058cd1b4fa01aa537370808c6 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 5 Oct 2025 17:59:50 +0200 Subject: [PATCH 077/135] added unit tests --- config/test.json | 5 ++- tests/routes/s3.test.js | 69 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 tests/routes/s3.test.js diff --git a/config/test.json b/config/test.json index 4cae764..9e0490d 100644 --- a/config/test.json +++ b/config/test.json @@ -1,3 +1,6 @@ { - "dummy_key": "this is a default empty file for config loading using const config = require('config'); in test environment (when running unit tests). this is intended to be empty." + "dummy_key": "this is a default incomplete file for config loading using const config = require('config'); in test environment (when running unit tests). this is intended to be incomplete.", + "aws": { + "region": "test" + } } \ No newline at end of file diff --git a/tests/routes/s3.test.js b/tests/routes/s3.test.js new file mode 100644 index 0000000..24f6be1 --- /dev/null +++ b/tests/routes/s3.test.js @@ -0,0 +1,69 @@ +const request = require('supertest'); + +const app = require('../../appForTests'); + +jest.mock('../../aws/s3_client', () => {; + const actual = jest.requireActual('../../aws/s3_client'); + return { + ...actual, + generateUploadUrl: jest.fn((bucketPrefix, filename, contentType) => { + return { + url: `https://mocked-s3-url/${bucketPrefix}/${filename}.mov`, + filename: `${filename}.mov`, + }; + }) + }; +}); + +describe('POST /presigned-url', () => { + it('Successful presigned URL creation', async () => { + const res = await request(app) + .post('/presigned-url') + .send({ + objectKey: '61a54a37-6c7a-466a-8100-a6b09daecded', + contentType: 'video/quicktime', + objectType: 'application_video' + }); + + expect(res.statusCode).toBe(200); + expect(res.body).toHaveProperty('url'); + expect(res.body.url).toBe('https://mocked-s3-url/videos/applications/61a54a37-6c7a-466a-8100-a6b09daecded.mov'); + expect(res.body).toHaveProperty('filename'); + expect(res.body.filename).toBe('61a54a37-6c7a-466a-8100-a6b09daecded.mov'); + }); + + it('Return 400 on incomplete payload data', async () => { + const res = await request(app) + .post('/presigned-url') + .send({ objectKey: '61a54a37-6c7a-466a-8100-a6b09daecded'}); + + expect(res.statusCode).toBe(400); + expect(res.body).toHaveProperty('error'); + }); + + it('Return 400 on invalid objectKey', async () => { + const res = await request(app) + .post('/presigned-url') + .send({ + objectKey: 'invalid-objectkey', + contentType: 'video/quicktime', + objectType: 'application_video' + }); + + expect(res.statusCode).toBe(400); + expect(res.body).toHaveProperty('error'); + }); + + it('Return 400 on unsupported objectType', async () => { + const res = await request(app) + .post('/presigned-url') + .send({ + objectKey: '61a54a37-6c7a-466a-8100-a6b09daecded', + contentType: 'video/quicktime', + objectType: 'something_weird' + }); + + expect(res.statusCode).toBe(400); + expect(res.body).toHaveProperty('error'); + }); +}); \ No newline at end of file From a724048994b8d0e8049a3e6f4b4f5db70fbf0217 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 5 Oct 2025 18:03:51 +0200 Subject: [PATCH 078/135] linter --- eslint.config.mjs | 2 +- routes/s3.js | 16 ++++++++-------- tests/routes/s3.test.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index f3e9d61..bb396f2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -21,7 +21,7 @@ export default [ "no-console": ["warn"], "eqeqeq": ["error", "always"], "prefer-const": "error", - "no-unused-vars": "error", + "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], "camelcase": ["error", {"properties": "never"}], "indent": ["error", 2], "semi": ["error", "always"], diff --git a/routes/s3.js b/routes/s3.js index e8e226a..a58556f 100644 --- a/routes/s3.js +++ b/routes/s3.js @@ -24,15 +24,15 @@ router.post('/presigned-url', async (req, res) => { var bucketKey; switch (objectType) { - case 'application_video': - // currently only application videos are supported - bucketKey = BUCKET_PREFIXES.applications; - break; - default: - return respondWithError(res, 400, 'Invalid objectType provided.'); + case 'application_video': + // currently only application videos are supported + bucketKey = BUCKET_PREFIXES.applications; + break; + default: + return respondWithError(res, 400, 'Invalid objectType provided.'); } - result = await generateUploadUrl(bucketKey, objectKey, contentType); + const result = await generateUploadUrl(bucketKey, objectKey, contentType); res.json(result); } catch (err) { @@ -45,7 +45,7 @@ function validateObjectKeyIsUuid(objectKey) { try { return validate(objectKey) && version(objectKey) === 4; } - catch (error) { + catch { return false; } } diff --git a/tests/routes/s3.test.js b/tests/routes/s3.test.js index 24f6be1..bef33e1 100644 --- a/tests/routes/s3.test.js +++ b/tests/routes/s3.test.js @@ -6,7 +6,7 @@ jest.mock('../../aws/s3_client', () => {; const actual = jest.requireActual('../../aws/s3_client'); return { ...actual, - generateUploadUrl: jest.fn((bucketPrefix, filename, contentType) => { + generateUploadUrl: jest.fn((bucketPrefix, filename, _contentType) => { return { url: `https://mocked-s3-url/${bucketPrefix}/${filename}.mov`, filename: `${filename}.mov`, From af86ecf57a39741a90d6bd0c8feff8c9b838749d Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 5 Oct 2025 18:05:42 +0200 Subject: [PATCH 079/135] update tests with s3_client call check --- tests/routes/s3.test.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/routes/s3.test.js b/tests/routes/s3.test.js index bef33e1..3b3b99a 100644 --- a/tests/routes/s3.test.js +++ b/tests/routes/s3.test.js @@ -1,5 +1,5 @@ const request = require('supertest'); - +const s3_client = require('../../aws/s3_client'); const app = require('../../appForTests'); jest.mock('../../aws/s3_client', () => {; @@ -24,6 +24,12 @@ describe('POST /presigned-url', () => { contentType: 'video/quicktime', objectType: 'application_video' }); + + expect(s3_client.generateUploadUrl).toHaveBeenCalledWith( + s3_client.BUCKET_PREFIXES.applications, + '61a54a37-6c7a-466a-8100-a6b09daecded', + 'video/quicktime' + ); expect(res.statusCode).toBe(200); expect(res.body).toHaveProperty('url'); @@ -37,6 +43,8 @@ describe('POST /presigned-url', () => { .post('/presigned-url') .send({ objectKey: '61a54a37-6c7a-466a-8100-a6b09daecded'}); + expect(s3_client.generateUploadUrl).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); expect(res.body).toHaveProperty('error'); }); @@ -50,6 +58,8 @@ describe('POST /presigned-url', () => { objectType: 'application_video' }); + expect(s3_client.generateUploadUrl).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); expect(res.body).toHaveProperty('error'); }); @@ -62,6 +72,8 @@ describe('POST /presigned-url', () => { contentType: 'video/quicktime', objectType: 'something_weird' }); + + expect(s3_client.generateUploadUrl).not.toHaveBeenCalled(); expect(res.statusCode).toBe(400); expect(res.body).toHaveProperty('error'); From 1229436485dfbf81f0e1f70f413ecc98ea358c5d Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sun, 5 Oct 2025 18:10:52 +0200 Subject: [PATCH 080/135] linter --- tests/routes/s3.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/routes/s3.test.js b/tests/routes/s3.test.js index 3b3b99a..f94c098 100644 --- a/tests/routes/s3.test.js +++ b/tests/routes/s3.test.js @@ -1,5 +1,5 @@ const request = require('supertest'); -const s3_client = require('../../aws/s3_client'); +const s3Client = require('../../aws/s3_client'); const app = require('../../appForTests'); jest.mock('../../aws/s3_client', () => {; @@ -25,8 +25,8 @@ describe('POST /presigned-url', () => { objectType: 'application_video' }); - expect(s3_client.generateUploadUrl).toHaveBeenCalledWith( - s3_client.BUCKET_PREFIXES.applications, + expect(s3Client.generateUploadUrl).toHaveBeenCalledWith( + s3Client.BUCKET_PREFIXES.applications, '61a54a37-6c7a-466a-8100-a6b09daecded', 'video/quicktime' ); @@ -43,7 +43,7 @@ describe('POST /presigned-url', () => { .post('/presigned-url') .send({ objectKey: '61a54a37-6c7a-466a-8100-a6b09daecded'}); - expect(s3_client.generateUploadUrl).not.toHaveBeenCalled(); + expect(s3Client.generateUploadUrl).not.toHaveBeenCalled(); expect(res.statusCode).toBe(400); expect(res.body).toHaveProperty('error'); @@ -58,7 +58,7 @@ describe('POST /presigned-url', () => { objectType: 'application_video' }); - expect(s3_client.generateUploadUrl).not.toHaveBeenCalled(); + expect(s3Client.generateUploadUrl).not.toHaveBeenCalled(); expect(res.statusCode).toBe(400); expect(res.body).toHaveProperty('error'); @@ -73,7 +73,7 @@ describe('POST /presigned-url', () => { objectType: 'something_weird' }); - expect(s3_client.generateUploadUrl).not.toHaveBeenCalled(); + expect(s3Client.generateUploadUrl).not.toHaveBeenCalled(); expect(res.statusCode).toBe(400); expect(res.body).toHaveProperty('error'); From 3696c6ea6210b21184c197193e5e24dab71e93a4 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sun, 5 Oct 2025 17:12:55 +0100 Subject: [PATCH 081/135] updates PUT mentor as per comments --- routes/mentors.js | 28 +++++++--------------------- services/mentor_service.js | 10 +++------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/routes/mentors.js b/routes/mentors.js index a460c1a..a707e59 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -3,7 +3,7 @@ const express = require("express"); const router = express.Router(); const mentorService = require("../services/mentor_service"); const { ROLE_NAMES, MENTOR_STATUSES, ALLOWED_MENTOR_UPDATES } = require("../utils"); -const { verifyRoles, verifyMentorOwnership } = require("../middleware/authorization"); +const { verifyRoles, verifyMentorOwnership, verifyOwnership, OWNED_ENTITIES } = require("../middleware/authorization"); const { isValidIntegerId, respondWithError } = require("./helpers"); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require("../errors"); const { sendEmailOnMentorApplicationStatusChange } = require("../services/mailer_service"); @@ -100,7 +100,7 @@ router.patch("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) }); // Update mentor information by user owning mentorship card -router.put("/mentors/:id", verifyRoles([ROLE_NAMES.mentor]), async (req, res) => { +router.put("/mentors/:id", verifyRoles([ROLE_NAMES.mentor]), verifyOwnership(OWNED_ENTITIES.MENTOR), async (req, res) => { const { id } = req.params; const { updates } = req.body; @@ -112,28 +112,14 @@ router.put("/mentors/:id", verifyRoles([ROLE_NAMES.mentor]), async (req, res) => const invalidFields = Object.keys(updates) .filter(field => !ALLOWED_MENTOR_UPDATES.includes(field)); if(invalidFields.length) { - return respondWithError(res, 400, "You are not allowed to edit this field"); + return respondWithError(res, 400, + `You can't edit ${invalidFields.length === 1 ? `field '${invalidFields[0]}'` : `fields '${invalidFields.join(', ')}'`}` + ); } try { - const isOwner = await verifyMentorOwnership(id, req.userId); - const mentorInfo = await mentorService.getMentorById( - req.userRoles, - id, - isOwner - ); - if (!mentorInfo) { - return respondWithError(res, 404, "Mentor not found"); - } - - // can edit is mentor status is active or inactive - if ( - mentorInfo.status === MENTOR_STATUSES.active || - mentorInfo.status === MENTOR_STATUSES.inactive - ) { - const updatedMentor = await mentorService.updateMentor(id, updates, isOwner); - res.json({ mentor: updatedMentor }); - } + const mentorId = await mentorService.updateMentor(id, updates); + res.json({ id: mentorId }); } catch (err) { logger.error(err); if (err instanceof DataRequiresElevatedRoleError) { diff --git a/services/mentor_service.js b/services/mentor_service.js index 48b4633..127a38e 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -109,13 +109,9 @@ async function updateMentorStatus(userRoles, mentorId, status, isOwner) { throw new DataRequiresElevatedRoleError("You're not allowed to edit this resource"); } -async function updateMentor(mentorId, updates, isOwner) { - if(isOwner) { - const updatedMentor = await repo.updateMentorById(mentorId, updates); - return updatedMentor; - } else { - throw new DataRequiresElevatedRoleError("You're not allowed to edit this resource"); - } +async function updateMentor(mentorId, updates) { + const updatedMentorId = await repo.updateMentorById(mentorId, updates); + return updatedMentorId; } module.exports = { From eb1a37e048b26a828e48756af0b9c53b2967260a Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sun, 5 Oct 2025 17:24:51 +0100 Subject: [PATCH 082/135] adds DELETE mentors route and service --- repositories/mentor_repository.js | 7 ++++++- routes/mentors.js | 18 ++++++++++++++++++ services/mentor_service.js | 8 +++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/repositories/mentor_repository.js b/repositories/mentor_repository.js index 2ad0a68..bd07ca2 100644 --- a/repositories/mentor_repository.js +++ b/repositories/mentor_repository.js @@ -49,4 +49,9 @@ async function updateMentorById(mentorId, updates) { .update(updates); } -module.exports = { getMentors, createMentor, getMentorByUserId, getMentorById, updateMentorById }; \ No newline at end of file +async function deleteMentorById(id) { + const knex = getKnex(); + return await knex("mentors").where("id", id).del(); +} + +module.exports = { getMentors, createMentor, getMentorByUserId, getMentorById, updateMentorById, deleteMentorById }; \ No newline at end of file diff --git a/routes/mentors.js b/routes/mentors.js index a707e59..31d3653 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -129,4 +129,22 @@ router.put("/mentors/:id", verifyRoles([ROLE_NAMES.mentor]), verifyOwnership(OWN } }); +// Delete mentorship card by owning user +router.delete("/mentors/:id", verifyOwnership(OWNED_ENTITIES.MENTOR), async (req, res) => { + const { id } = req.params; + if (!isValidIntegerId(id)) { + return respondWithError(res, 400, "Invalid user id supplied"); + } + try { + const result = await mentorService.deleteMentorById(id, req.userId); + if (result === 0) { + return respondWithError(res, 404, "Mentor not found."); + } + res.status(200).json({ mentor_id: id }); + } catch (err) { + logger.error(err); + respondWithError(res); + } +}); + module.exports = router; \ No newline at end of file diff --git a/services/mentor_service.js b/services/mentor_service.js index 127a38e..8a94fa7 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -114,9 +114,15 @@ async function updateMentor(mentorId, updates) { return updatedMentorId; } +async function deleteMentorById(mentorId, userId) { + const deletedMentorId = await repo.deleteMentorById(mentorId); + removeRoleFromUser(userId, 2); + return deletedMentorId; +} + module.exports = { getMentors, createMentor, getMentorById, - updateMentorStatus, updateMentor, + updateMentorStatus, updateMentor, deleteMentorById, adminVisibleStatuses, generalVisitbleStatuses, allFields, baseFields, privateFields, MENTOR_STATUSES From 413ce7e392dd909256125db871403850d2365621 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sun, 5 Oct 2025 19:28:00 +0100 Subject: [PATCH 083/135] updates swagger and adds PUT and DELETE mentor route and service tests --- docs/catbytes_api_swagger.json | 2 +- docs/catbytes_api_swagger.yaml | 4 +- routes/mentors.js | 7 ++- services/mentor_service.js | 11 +++-- tests/routes/mentors.test.js | 59 +++++++++++++++++++++- tests/services/mentor_service.test.js | 71 ++++++++++++++++++++++++++- 6 files changed, 145 insertions(+), 9 deletions(-) diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index 0854f36..b4729d3 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -1885,7 +1885,7 @@ "tags": [ "mentors" ], - "summary": "Delete mentor profile", + "summary": "✅ Delete mentor profile", "description": "Only the owner can delete their mentor card. Deletes the entity and removes mentor role from user.\n", "operationId": "deleteMentor", "parameters": [ diff --git a/docs/catbytes_api_swagger.yaml b/docs/catbytes_api_swagger.yaml index 87c74a9..97e6bcc 100644 --- a/docs/catbytes_api_swagger.yaml +++ b/docs/catbytes_api_swagger.yaml @@ -1202,7 +1202,7 @@ paths: put: tags: - mentors - summary: Update mentor profile + summary: ✅ Update mentor profile description: | Only the user who owns this mentor card can update it. Only selected fields can be changed. operationId: updateMentor @@ -1261,7 +1261,7 @@ paths: delete: tags: - mentors - summary: Delete mentor profile + summary: ✅ Delete mentor profile description: | Only the owner can delete their mentor card. Deletes the entity and removes mentor role from user. operationId: deleteMentor diff --git a/routes/mentors.js b/routes/mentors.js index 31d3653..68fcb0b 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -118,7 +118,12 @@ router.put("/mentors/:id", verifyRoles([ROLE_NAMES.mentor]), verifyOwnership(OWN } try { - const mentorId = await mentorService.updateMentor(id, updates); + const isOwner = await verifyMentorOwnership(id, req.userId); + const mentorId = await mentorService.updateMentor(req.userRoles, id, updates, isOwner); + if(mentorId === 0) { + // could not update mentor, as it was in pending or rejected state + return respondWithError(res, 400, 'You cannot update mentor when its status is pending or rejected') + } res.json({ id: mentorId }); } catch (err) { logger.error(err); diff --git a/services/mentor_service.js b/services/mentor_service.js index 8a94fa7..5eea81a 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -109,9 +109,14 @@ async function updateMentorStatus(userRoles, mentorId, status, isOwner) { throw new DataRequiresElevatedRoleError("You're not allowed to edit this resource"); } -async function updateMentor(mentorId, updates) { - const updatedMentorId = await repo.updateMentorById(mentorId, updates); - return updatedMentorId; +async function updateMentor(userRoles, mentorId, updates, isOwner) { + const mentorData = await getMentorById(userRoles, mentorId, isOwner); + if(mentorData.status === 'active' || mentorData.status === 'inactive') { + const updatedMentorId = await repo.updateMentorById(mentorId, updates); + return updatedMentorId; + } else { + return 0; + } } async function deleteMentorById(mentorId, userId) { diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index 031fa0b..32d6bca 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -1,6 +1,7 @@ const request = require('supertest'); const mentorService = require('../../services/mentor_service'); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require('../../errors'); +const { verifyOwnership } = require('../../middleware/authorization'); const defautlUserId = 42; const defaultUserRoles = [{ role_name: 'member', role_id: 1 }]; @@ -35,6 +36,10 @@ jest.mock('../../middleware/authorization', () => { next(); }), verifyMentorOwnership: jest.fn(() => false), + verifyOwnership: jest.fn(() => (req, res, next) => { + req.userId = defautlUserId; + next(); + }), }; }); @@ -236,4 +241,56 @@ describe('PATCH /mentors/:id', () => { expect(res.statusCode).toBe(404); expect(res.body.error).toBe('Mentor not found'); }); -}); \ No newline at end of file +}); + +describe('PUT /mentors/:id', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Update mentor success', async () => { + mentorService.updateMentor.mockResolvedValue(mockedMentor.id); + + const res = await request(app) + .put(`/mentors/${mockedMentor.id}`) + .send({ updates: { about: 'updated about field', contact: 'updated@email.com' } }); + + expect(mentorService.updateMentor).toHaveBeenCalledWith( + defaultUserRoles, + mockedMentor.id.toString(), + { about: 'updated about field', contact: 'updated@email.com' }, + false // verifyMentorOwnership is mocked to return false + ); + + expect(res.statusCode).toBe(200); + expect(res.body).toStrictEqual({ id: mockedMentor.id }); + }); + it('Update mentor - invalid field', async () => { + const res = await request(app) + .put(`/mentors/${mockedMentor.id}`) + .send({ updates: { name: 'new name' } }); + + expect(res.statusCode).toBe(400); + expect(mentorService.updateMentor).not.toHaveBeenCalled(); + }); + it('Update mentor - not authorized', async () => { + mentorService.updateMentor.mockRejectedValue( + new DataRequiresElevatedRoleError("You're not allowed to edit this resource") + ); + + const res = await request(app) + .put(`/mentors/${mockedMentor.id}`) + .set('userId', defautlUserId) + .send({ updates: { about: 'new about that will not be applied', contact: 'new contact' } }); + + expect(mentorService.updateMentor).toHaveBeenCalledWith( + defaultUserRoles, + mockedMentor.id.toString(), + { about: 'new about that will not be applied', contact: 'new contact' }, + false // verifyMentorOwnership is mocked to return false + ); + + expect(res.statusCode).toBe(403); + expect(res.body.error).toBe("You're not allowed to edit this resource"); + }); +}) \ No newline at end of file diff --git a/tests/services/mentor_service.test.js b/tests/services/mentor_service.test.js index 571606c..c519389 100644 --- a/tests/services/mentor_service.test.js +++ b/tests/services/mentor_service.test.js @@ -1,5 +1,5 @@ const mentorService = require('../../services/mentor_service'); -const { MENTOR_STATUSES } = require('../../utils'); +const { MENTOR_STATUSES, ROLE_NAMES } = require('../../utils'); const rolesService = require('../../services/roles_service'); const mailerService = require('../../services/mailer_service'); const repo = require('../../repositories/mentor_repository'); @@ -12,6 +12,7 @@ jest.mock('../../services/roles_service'); jest.mock('../../services/mailer_service'); const defaultUserId = 42; +const mockedMentorId = 1; describe('Mentor Service', () => { afterEach(() => { @@ -301,4 +302,72 @@ describe('Mentor Service', () => { expect(rolesRepo.removeRoleFromUser).not.toHaveBeenCalled(); }); }); + + describe('updateMentor', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Successful update of valid field', async () => { + repo.getMentorById.mockResolvedValue({ + id: mockedMentorId, + user_id: defaultUserId, + status: MENTOR_STATUSES.active, + }); + repo.updateMentorById.mockResolvedValue(mockedMentorId); + + const result = await mentorService.updateMentor( + [{ role_name: 'mentor' }], + mockedMentorId.toString(), + { about: 'new value for about field' }, + true // isOwner + ); + + expect(repo.updateMentorById).toHaveBeenCalledWith( + mockedMentorId.toString(), + { about: 'new value for about field' } + ); + expect(result).toBe(mockedMentorId); + }); + + it('Successful update of more than one valid field', async () => { + repo.getMentorById.mockResolvedValue({ + id: mockedMentorId, + user_id: defaultUserId, + status: MENTOR_STATUSES.inactive, + }); + repo.updateMentorById.mockResolvedValue(mockedMentorId); + + const result = await mentorService.updateMentor( + [{ role_name: 'mentor' }], + mockedMentorId.toString(), + { about: 'updated about text', contact: 'updated@example.com' }, + true // isOwner + ); + + expect(repo.updateMentorById).toHaveBeenCalledWith( + mockedMentorId.toString(), + { about: 'updated about text', contact: 'updated@example.com' } + ); + expect(result).toBe(mockedMentorId); + }); + + it('Failed update if mentor status is not active or inactive', async () => { + repo.getMentorById.mockResolvedValue({ + id: mockedMentorId, + user_id: defaultUserId, + status: MENTOR_STATUSES.pending, + }); + + const result = await mentorService.updateMentor( + [{ role_name: 'member' }], + mockedMentorId.toString(), + { about: 'trying to change' }, + true // isOwner + ); + + expect(repo.updateMentorById).not.toHaveBeenCalled(); + expect(result).toBe(0); + }); + }); }); \ No newline at end of file From 1b4473913c692791009a17bfbfe504fa31895c45 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sun, 5 Oct 2025 19:30:06 +0100 Subject: [PATCH 084/135] resolves lint issues --- routes/mentors.js | 2 +- tests/routes/mentors.test.js | 3 +-- tests/services/mentor_service.test.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/routes/mentors.js b/routes/mentors.js index 68fcb0b..bd46cc1 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -122,7 +122,7 @@ router.put("/mentors/:id", verifyRoles([ROLE_NAMES.mentor]), verifyOwnership(OWN const mentorId = await mentorService.updateMentor(req.userRoles, id, updates, isOwner); if(mentorId === 0) { // could not update mentor, as it was in pending or rejected state - return respondWithError(res, 400, 'You cannot update mentor when its status is pending or rejected') + return respondWithError(res, 400, 'You cannot update mentor when its status is pending or rejected'); } res.json({ id: mentorId }); } catch (err) { diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index 32d6bca..5834887 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -1,7 +1,6 @@ const request = require('supertest'); const mentorService = require('../../services/mentor_service'); const { MentorAlreadyExistsError, DataRequiresElevatedRoleError } = require('../../errors'); -const { verifyOwnership } = require('../../middleware/authorization'); const defautlUserId = 42; const defaultUserRoles = [{ role_name: 'member', role_id: 1 }]; @@ -293,4 +292,4 @@ describe('PUT /mentors/:id', () => { expect(res.statusCode).toBe(403); expect(res.body.error).toBe("You're not allowed to edit this resource"); }); -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/tests/services/mentor_service.test.js b/tests/services/mentor_service.test.js index c519389..aba6efb 100644 --- a/tests/services/mentor_service.test.js +++ b/tests/services/mentor_service.test.js @@ -1,5 +1,5 @@ const mentorService = require('../../services/mentor_service'); -const { MENTOR_STATUSES, ROLE_NAMES } = require('../../utils'); +const { MENTOR_STATUSES } = require('../../utils'); const rolesService = require('../../services/roles_service'); const mailerService = require('../../services/mailer_service'); const repo = require('../../repositories/mentor_repository'); From 44f3c93007438a85cebe130a95e1386f930cbf65 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sun, 5 Oct 2025 19:39:30 +0100 Subject: [PATCH 085/135] update sswagger.json --- docs/catbytes_api_swagger.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index b4729d3..8d47466 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -1797,7 +1797,7 @@ "tags": [ "mentors" ], - "summary": "Update mentor profile", + "summary": "✅ Update mentor profile", "description": "Only the user who owns this mentor card can update it. Only selected fields can be changed.\n", "operationId": "updateMentor", "parameters": [ From b0325312d20fdfbc4fed80e422199f6635aadc54 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Mon, 6 Oct 2025 06:43:40 +0100 Subject: [PATCH 086/135] replace gard coded with enum --- services/mentor_service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/mentor_service.js b/services/mentor_service.js index 5eea81a..17a5951 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -111,7 +111,7 @@ async function updateMentorStatus(userRoles, mentorId, status, isOwner) { async function updateMentor(userRoles, mentorId, updates, isOwner) { const mentorData = await getMentorById(userRoles, mentorId, isOwner); - if(mentorData.status === 'active' || mentorData.status === 'inactive') { + if(mentorData.status === MENTOR_STATUSES.active || mentorData.status === MENTOR_STATUSES.inactive) { const updatedMentorId = await repo.updateMentorById(mentorId, updates); return updatedMentorId; } else { From f4c9274a19dc96205cebd4587e4afa4f6f2afe63 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Thu, 16 Oct 2025 20:49:12 +0200 Subject: [PATCH 087/135] added video_filename to applications table --- docs/db/catbytes_database_schema.pdf | Bin 466498 -> 609996 bytes docs/db/schema.dbml | 1 + ...=> 20250706101530_create_mentors_table.js} | 0 ...016183916_add_videofile_to_applications.js | 20 ++++++++++++++++++ 4 files changed, 21 insertions(+) rename knex/migrations/{20250707101530_create_mentors_table.js => 20250706101530_create_mentors_table.js} (100%) create mode 100644 knex/migrations/20251016183916_add_videofile_to_applications.js diff --git a/docs/db/catbytes_database_schema.pdf b/docs/db/catbytes_database_schema.pdf index 3031382e2cf1aeb90f6dddf9409b0fb7094bc6f9..2b48cc14d51b883d29e8582b2bc0ed2e0d01bd6c 100644 GIT binary patch literal 609996 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π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 diff --git a/docs/db/schema.dbml b/docs/db/schema.dbml index db05fff..fe46939 100644 --- a/docs/db/schema.dbml +++ b/docs/db/schema.dbml @@ -7,6 +7,7 @@ Table applications { about text [not null] email email [unique, not null] video_link varchar + video_filename varchar discord_nickname varchar [unique, not null] created_at timestamp status enum diff --git a/knex/migrations/20250707101530_create_mentors_table.js b/knex/migrations/20250706101530_create_mentors_table.js similarity index 100% rename from knex/migrations/20250707101530_create_mentors_table.js rename to knex/migrations/20250706101530_create_mentors_table.js diff --git a/knex/migrations/20251016183916_add_videofile_to_applications.js b/knex/migrations/20251016183916_add_videofile_to_applications.js new file mode 100644 index 0000000..8218526 --- /dev/null +++ b/knex/migrations/20251016183916_add_videofile_to_applications.js @@ -0,0 +1,20 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = async function(knex) { + await knex.schema.alterTable('applications', function(table) { + table.string('video_filename', 255); + }); + }; + + /** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ + exports.down = async function(knex) { + await knex.schema.alterTable('applications', function(table) { + table.dropColumn('video_filename'); + }); + }; + \ No newline at end of file From e363f34e2b2558ef397f3683c0b1c0165a9d3880 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Thu, 16 Oct 2025 21:15:24 +0200 Subject: [PATCH 088/135] added get applications by id, building download link to s3 object --- aws/s3_client.js | 17 +++++++++++++++- repositories/applications_repository.js | 4 ++-- routes/applications.js | 26 ++++++++++++++++++------- services/applications_service.js | 14 ++++++++++--- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/aws/s3_client.js b/aws/s3_client.js index 0ed5159..7279e62 100644 --- a/aws/s3_client.js +++ b/aws/s3_client.js @@ -1,4 +1,4 @@ -const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3"); +const { S3Client, PutObjectCommand, GetObjectCommand } = require("@aws-sdk/client-s3"); const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); const config = require("config"); @@ -39,6 +39,20 @@ async function generateUploadUrl(bucketPrefix, filename, contentType) { }; } +async function getDownloadUrl(bucketPrefix, filename) { + if (!Object.values(BUCKET_PREFIXES).includes(bucketPrefix)) { + throw new Error("Invalid bucket prefix"); + } + + const command = new GetObjectCommand({ + Bucket: awsConfig.s3_bucket, + Key: buildFullObjectKey(bucketPrefix, filename) + }); + + const signedUrl = await getSignedUrl(s3, command, { expiresIn: 3600 }); + return signedUrl; +} + function buildFullObjectKey(bucketPrefix, filename) { return `${bucketPrefix}/${filename}`; } @@ -47,4 +61,5 @@ module.exports = { BUCKET_PREFIXES, MIME_EXTENSION_MAP, generateUploadUrl, + getDownloadUrl, }; \ No newline at end of file diff --git a/repositories/applications_repository.js b/repositories/applications_repository.js index d674290..199e7b9 100644 --- a/repositories/applications_repository.js +++ b/repositories/applications_repository.js @@ -6,9 +6,9 @@ async function getAllApplications() { return await knex("applications").select("*"); } -async function createNewApplication(name, about, email, videoLink, discordNickname) { +async function createNewApplication(payload) { return await knex('applications') - .insert({ name: name, about: about, email: email, video_link: videoLink, discord_nickname: discordNickname }) + .insert(payload) .returning('*'); } diff --git a/routes/applications.js b/routes/applications.js index 807c985..6c39f10 100644 --- a/routes/applications.js +++ b/routes/applications.js @@ -22,16 +22,28 @@ router.get("/applications", verifyRoles([ROLE_NAMES.mentor, ROLE_NAMES.admin]), } }); +router.get("/applications/:id", verifyRoles([ROLE_NAMES.mentor, ROLE_NAMES.admin]), async (req, res) => { + const { id } = req.params; + try { + const application = await applService.getApplicationById(id); + if (!application) { + return respondWithError(res, 404, "Application not found"); + } + res.json(application); + } catch (err) { + logger.error(err); + respondWithError(res); + } +}); + router.post("/applications", async (req, res) => { const payload = req.body; + if (!payload.video_link && !payload.video_filename) { + return respondWithError(res, 400, "Video link or filename is required"); + } + try { - const result = await applService.createNewApplication( - payload.name, - payload.about, - payload.email, - payload.video_link, - payload.discord_nickname - ); + const result = await applService.createNewApplication(payload); res.status(201).json(result); } catch (err) { diff --git a/services/applications_service.js b/services/applications_service.js index 6586575..7cab1ce 100644 --- a/services/applications_service.js +++ b/services/applications_service.js @@ -1,11 +1,15 @@ const repo = require('../repositories/applications_repository'); +const { getDownloadUrl, BUCKET_PREFIXES } = require("../aws/s3_client"); async function getAllApplications() { return await repo.getAllApplications(); } -async function createNewApplication(name, about, email, videoLink, discordNickname) { - return await repo.createNewApplication(name, about, email, videoLink, discordNickname); +async function createNewApplication(payload) { + payload.video_link = payload.video_link || ''; + payload.video_filename = payload.video_filename || ''; + + return await repo.createNewApplication(payload); } async function updateApplicationStatus(id, status, comment, modifiedBy, modifiedAt) { @@ -17,7 +21,11 @@ async function getApplicationByEmail(email) { } async function getApplicationById(id) { - return await repo.getApplicationByFields({id: id}); + application = await repo.getApplicationByFields({id: id}); + if (application?.video_filename) { + application.video_file = await getDownloadUrl(BUCKET_PREFIXES.applications, application.video_filename); + } + return application; } module.exports = { getAllApplications, createNewApplication, updateApplicationStatus, getApplicationByEmail, getApplicationById }; \ No newline at end of file From 603a4840fc20927a2bfce29b454265e83f640df5 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sat, 18 Oct 2025 11:27:46 +0100 Subject: [PATCH 089/135] removes verifyMentorOwnership from PUT mentors route and service --- routes/mentors.js | 3 +-- services/mentor_service.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/routes/mentors.js b/routes/mentors.js index bd46cc1..6f9455d 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -118,8 +118,7 @@ router.put("/mentors/:id", verifyRoles([ROLE_NAMES.mentor]), verifyOwnership(OWN } try { - const isOwner = await verifyMentorOwnership(id, req.userId); - const mentorId = await mentorService.updateMentor(req.userRoles, id, updates, isOwner); + const mentorId = await mentorService.updateMentor(req.userRoles, id, updates); if(mentorId === 0) { // could not update mentor, as it was in pending or rejected state return respondWithError(res, 400, 'You cannot update mentor when its status is pending or rejected'); diff --git a/services/mentor_service.js b/services/mentor_service.js index 17a5951..a766146 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -109,8 +109,8 @@ async function updateMentorStatus(userRoles, mentorId, status, isOwner) { throw new DataRequiresElevatedRoleError("You're not allowed to edit this resource"); } -async function updateMentor(userRoles, mentorId, updates, isOwner) { - const mentorData = await getMentorById(userRoles, mentorId, isOwner); +async function updateMentor(userRoles, mentorId, updates) { + const mentorData = await getMentorById(userRoles, mentorId); if(mentorData.status === MENTOR_STATUSES.active || mentorData.status === MENTOR_STATUSES.inactive) { const updatedMentorId = await repo.updateMentorById(mentorId, updates); return updatedMentorId; From 64704c4d411829da8b6b82325a1a96759178fe1b Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sat, 18 Oct 2025 11:38:06 +0100 Subject: [PATCH 090/135] updates tests PUT mentor --- tests/routes/mentors.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index 5834887..2ac093a 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -257,8 +257,7 @@ describe('PUT /mentors/:id', () => { expect(mentorService.updateMentor).toHaveBeenCalledWith( defaultUserRoles, mockedMentor.id.toString(), - { about: 'updated about field', contact: 'updated@email.com' }, - false // verifyMentorOwnership is mocked to return false + { about: 'updated about field', contact: 'updated@email.com' } ); expect(res.statusCode).toBe(200); @@ -285,8 +284,7 @@ describe('PUT /mentors/:id', () => { expect(mentorService.updateMentor).toHaveBeenCalledWith( defaultUserRoles, mockedMentor.id.toString(), - { about: 'new about that will not be applied', contact: 'new contact' }, - false // verifyMentorOwnership is mocked to return false + { about: 'new about that will not be applied', contact: 'new contact' } ); expect(res.statusCode).toBe(403); From 024bda1ddc7af0944126aceea1866ff01a8c234f Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sat, 18 Oct 2025 12:43:01 +0200 Subject: [PATCH 091/135] updated --- tests/routes/applications.test.js | 61 +++++++++++++++++-------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/tests/routes/applications.test.js b/tests/routes/applications.test.js index f2666b8..4f3e1f2 100644 --- a/tests/routes/applications.test.js +++ b/tests/routes/applications.test.js @@ -61,13 +61,7 @@ describe('POST /applications', () => { .post('/applications') .send(applicationPayload); - expect(applicationService.createNewApplication).toHaveBeenCalledWith( - applicationPayload.name, - applicationPayload.about, - applicationPayload.email, - applicationPayload.video_link, - applicationPayload.discord_nickname - ); + expect(applicationService.createNewApplication).toHaveBeenCalledWith(applicationPayload); expect(res.statusCode).toBe(201); expect(res.body).toEqual({ id: 1, ...applicationPayload }); }); @@ -82,13 +76,7 @@ describe('POST /applications', () => { .post('/applications') .send(applicationPayload); - expect(applicationService.createNewApplication).toHaveBeenCalledWith( - applicationPayload.name, - applicationPayload.about, - applicationPayload.email, - applicationPayload.video_link, - applicationPayload.discord_nickname - ); + expect(applicationService.createNewApplication).toHaveBeenCalledWith(applicationPayload); expect(res.statusCode).toBe(409); expect(res.body.error).toContain('already exists'); }); @@ -103,13 +91,7 @@ describe('POST /applications', () => { .post('/applications') .send(applicationPayload); - expect(applicationService.createNewApplication).toHaveBeenCalledWith( - applicationPayload.name, - applicationPayload.about, - applicationPayload.email, - applicationPayload.video_link, - applicationPayload.discord_nickname - ); + expect(applicationService.createNewApplication).toHaveBeenCalledWith(applicationPayload); expect(res.statusCode).toBe(400); expect(res.body.error).toContain('email is required'); }); @@ -120,16 +102,39 @@ describe('POST /applications', () => { .post('/applications') .send(applicationPayload); - expect(applicationService.createNewApplication).toHaveBeenCalledWith( - applicationPayload.name, - applicationPayload.about, - applicationPayload.email, - applicationPayload.video_link, - applicationPayload.discord_nickname - ); + expect(applicationService.createNewApplication).toHaveBeenCalledWith(applicationPayload); expect(res.statusCode).toBe(500); expect(res.body.error).toContain('Internal Server Error'); }); + + describe('Create new application - video field validation', () => { + const basePayload = { + name: 'Test Application', + about: 'This is a test application', + email: 'test@test.com', + discord_nickname: 'TestUser' + }; + + const testCases = [ + ['both missing', {}], + ['both empty strings', { video_link: '', video_filename: '' }], + ['video_link missing, video_filename empty', { video_filename: '' }], + ['video_link empty, video_filename missing', { video_link: '' }], + ]; + + test.each(testCases)( + 'should return 400 if %s', + async (_desc, videoFields) => { + const payload = { ...basePayload, ...videoFields }; + + const res = await request(app).post('/applications').send(payload); + + expect(applicationService.createNewApplication).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(400); + expect(res.body.error).toContain('Video link or filename is required'); + } + ); + }); }); describe('PUT /applications/:id', () => { From f60354419541ae4dda5aaaf6c816d04907b188f8 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sat, 18 Oct 2025 13:00:52 +0200 Subject: [PATCH 092/135] update swagger --- docs/catbytes_api_swagger.json | 4318 ++++++++++++++++---------------- docs/catbytes_api_swagger.yaml | 67 +- 2 files changed, 2271 insertions(+), 2114 deletions(-) diff --git a/docs/catbytes_api_swagger.json b/docs/catbytes_api_swagger.json index 0bf30d7..5985ed8 100644 --- a/docs/catbytes_api_swagger.json +++ b/docs/catbytes_api_swagger.json @@ -1,2219 +1,2319 @@ { - "openapi": "3.0.3", - "info": { - "title": "CatBytes Web Platform", - "description": "This is CatBytes Web Platform API specification.\n\n!\nPlease note, this is an initial draft of the API, all payloads and response bodies are very likely to change.\n\nEndpoints marked with ✅ emoji are already implemented and live and their specifications are up-to-date.\n\nSome useful links:\n- [CatBytes backend repository](https://github.com/catbytes-community/webplatform-backend)\n- [CatBytes API server](https://devapi.catbytes.io/)", - "version": "1.0.0" - }, - "externalDocs": { - "description": "Find out more about Swagger", - "url": "http://swagger.io" - }, - "servers": [ - { - "url": "https://devapi.catbytes.io/" - } - ], - "tags": [ - { - "name": "applications", - "description": "New users applications" - }, - { - "name": "roles", - "description": "Available roles" - }, - { - "name": "users", - "description": "User related operations" - }, - { - "name": "mentors", - "description": "Mentor related operations" + "openapi": "3.0.3", + "info": { + "title": "CatBytes Web Platform", + "description": "This is CatBytes Web Platform API specification.\n\n!\nPlease note, this is an initial draft of the API, all payloads and response bodies are very likely to change.\n\nEndpoints marked with ✅ emoji are already implemented and live and their specifications are up-to-date.\n\nSome useful links:\n- [CatBytes backend repository](https://github.com/catbytes-community/webplatform-backend)\n- [CatBytes API server](https://devapi.catbytes.io/)", + "version": "1.0.0" }, - { - "name": "projects", - "description": "Projects and collaborations" + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" }, - { - "name": "discord integration", - "description": "Discord related operations" - } - ], - "paths": { - "/applications": { - "post": { - "tags": [ - "applications" - ], - "summary": "✅ Create new user application", - "description": "Send new user application's data for review.", - "operationId": "createApplication", - "requestBody": { - "description": "Data user inserted on application", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "Jane Doe" - }, - "about": { - "type": "string", - "example": "I'm passionate about C++ programming" - }, - "email": { - "type": "string", - "example": "jane@example.com" - }, - "video_link": { - "type": "string", - "example": "http://example.com/video/1" - }, - "discord_nickname": { - "type": "string", - "example": "sample_username" - } - } - } - } - } + "servers": [ + { + "url": "https://devapi.catbytes.io/" + } + ], + "tags": [ + { + "name": "applications", + "description": "New users applications" }, - "responses": { - "201": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "example": { - "id": 42, - "name": "Jane Doe", - "about": "I'm passionate about C++ programming", - "email": "jane@example.com", - "video_link": "http://example.com/video/1", - "discord_nickname": "sample_username" - } - } - } - } - }, - "400": { - "description": "Invalid input", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "null value in column \"discord_nickname\" of relation \"applications\" violates not-null constraint" - } - } - } - } - } - }, - "409": { - "description": "Application with this email already exists", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "Application with this email already exists" - } - } - } - } - } - } + { + "name": "roles", + "description": "Available roles" + }, + { + "name": "users", + "description": "User related operations" + }, + { + "name": "mentors", + "description": "Mentor related operations" + }, + { + "name": "projects", + "description": "Projects and collaborations" + }, + { + "name": "discord integration", + "description": "Discord related operations" + }, + { + "name": "service endpoints", + "description": "Enpoints for service operations" } - }, - "get": { - "tags": [ - "applications" - ], - "summary": "✅ Get all (active?) applications", - "description": "", - "operationId": "getApplications", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "applications": { - "type": "array", - "items": { - "type": "object", - "example": [ - { - "id": 5, - "name": "jane doe", - "about": "im cool", - "video_link": "http://example.com/video/1", - "discord_username": "sample_username", - "created_at": "2024-11-23T16:37:04.115Z", - "email": "jane@example.com" - }, - { - "id": 6, - "name": "nina", - "about": "i like cats", - "video_link": "http://example.com/video/2", - "discord_username": "nina_disc", - "created_at": "2024-11-20T16:37:04.115Z", - "email": "nina@example.com" - } - ] - } - } - } - } - } - } - }, - "403": { - "description": "User is not allowed to view applications list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "You're not allowed to access this resource" - } - } + ], + "paths": { + "/applications": { + "post": { + "tags": [ + "applications" + ], + "summary": "✅ Create new user application", + "description": "Send new user application's data for review. Whether video_link or video_filename is required.", + "operationId": "createApplication", + "requestBody": { + "description": "Data user inserted on application", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Jane Doe" + }, + "about": { + "type": "string", + "example": "I'm passionate about C++ programming" + }, + "email": { + "type": "string", + "example": "jane@example.com" + }, + "video_link": { + "type": "string", + "example": "http://example.com/video/1", + "nullable": true + }, + "video_filename": { + "type": "string", + "example": "videofile.mov", + "nullable": true + }, + "discord_nickname": { + "type": "string", + "example": "sample_username" + } + }, + "oneOf": [ + { + "required": [ + "video_link" + ] + }, + { + "required": [ + "video_filename" + ] + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "example": { + "id": 42, + "name": "Jane Doe", + "about": "I'm passionate about C++ programming", + "email": "jane@example.com", + "video_link": "http://example.com/video/1", + "video_filename": "", + "discord_nickname": "sample_username" + } + } + } + } + }, + "400": { + "description": "Invalid input", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "null value in column \"discord_nickname\" of relation \"applications\" violates not-null constraint" + } + } + } + } + } + }, + "409": { + "description": "Application with this email already exists", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Application with this email already exists" + } + } + } + } + } + } } - } - } - } - } - } - }, - "/applications/{application_id}": { - "put": { - "tags": [ - "applications" - ], - "summary": "✅ Update application for membership", - "description": "This endpoint is used to either approve or reject an application to join the community (membership application)", - "operationId": "updateApplication", - "parameters": [ - { - "name": "application_id", - "in": "path", - "description": "is of application that needs to be updated", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "description": "If application is rejected, comment will be required and stored in database along with applications with rejected status). Application statuses is enum with options approved, rejected, pending", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "rejected" - }, - "comment": { - "type": "string", - "example": "it's a man!" - }, - "user_id": { - "type": "integer", - "example": 1 - } + }, + "get": { + "tags": [ + "applications" + ], + "summary": "✅ Get all (active?) applications", + "description": "", + "operationId": "getApplications", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "applications": { + "type": "array", + "items": { + "type": "object", + "example": [ + { + "id": 5, + "name": "jane doe", + "about": "im cool", + "video_link": "", + "discord_username": "sample_username", + "created_at": "2024-11-23T16:37:04.115Z", + "email": "jane@example.com", + "video_filename": "video1.mp4" + }, + { + "id": 6, + "name": "nina", + "about": "i like cats", + "video_link": "http://example.com/video/2", + "discord_username": "nina_disc", + "created_at": "2024-11-20T16:37:04.115Z", + "email": "nina@example.com", + "video_filename": "" + } + ] + } + } + } + } + } + } + }, + "403": { + "description": "User is not allowed to view applications list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "You're not allowed to access this resource" + } + } + } + } + } + } } - } } - } }, - "responses": { - "200": { - "description": "Successful operation" - }, - "400": { - "description": "Unknown action" - }, - "403": { - "description": "User is not allowed to edit applications" - }, - "404": { - "description": "Application not found" - } - } - } - }, - "/roles": { - "get": { - "tags": [ - "roles" - ], - "summary": "✅ Get all available server roles", - "description": "", - "operationId": "getRoles", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "roles": { - "type": "object", - "example": [ - { - "id": 1, - "role_name": "member", - "is_privileged": false - }, - { - "id": 2, - "role_name": "mentor", - "is_privileged": true + "/applications/{application_id}": { + "get": { + "tags": [ + "applications" + ], + "summary": "✅ Get membership application by id", + "description": "Returns application with extended information. Right now extended information only includes video_file - a link to uploaded video.", + "operationId": "getApplicationById", + "parameters": [ + { + "name": "application_id", + "in": "path", + "description": "The id of application that needs to be fetched. Use 42 for testing. ", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Application found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "about": { + "type": "string" + }, + "email": { + "type": "string" + }, + "discord_nickname": { + "type": "string" + }, + "video_link": { + "type": "string", + "nullable": true + }, + "video_filename": { + "type": "string", + "nullable": true + }, + "video_file": { + "type": "string", + "format": "uri", + "description": "Download URL for uploaded video" + } + } + } + } } - ] + }, + "404": { + "description": "Application not found" + }, + "500": { + "description": "Internal server error" } - } } - } - } - } - } - } - }, - "/admin/grant-role": { - "post": { - "tags": [ - "roles" - ], - "summary": "✅ Grant role to user", - "description": "Grants a role to a user. Only accessible by users with admin privileges.", - "operationId": "grantRole", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "userId", - "roleId" + }, + "put": { + "tags": [ + "applications" ], - "properties": { - "userId": { - "type": "integer", - "example": 42 - }, - "roleId": { - "type": "integer", - "example": 2 - } + "summary": "✅ Update application for membership", + "description": "This endpoint is used to either approve or reject an application to join the community (membership application)", + "operationId": "updateApplication", + "parameters": [ + { + "name": "application_id", + "in": "path", + "description": "is of application that needs to be updated", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "description": "If application is rejected, comment will be required and stored in database along with applications with rejected status). Application statuses is enum with options approved, rejected, pending", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "rejected" + }, + "comment": { + "type": "string", + "example": "it's a man!" + }, + "user_id": { + "type": "integer", + "example": 1 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation" + }, + "400": { + "description": "Unknown action" + }, + "403": { + "description": "User is not allowed to edit applications" + }, + "404": { + "description": "Application not found" + } } - } } - } }, - "responses": { - "200": { - "description": "Role successfully granted" - }, - "400": { - "description": "Missing required parameters" - }, - "403": { - "description": "User is not authorized to grant roles" - }, - "500": { - "description": "Internal server error" - } - } - } - }, - "/admin/revoke-role": { - "post": { - "tags": [ - "roles" - ], - "summary": "✅ Revoke role from user", - "description": "Revokes a role from a user. Only accessible by users with admin privileges.", - "operationId": "revokeRole", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "userId", - "roleId" + "/roles": { + "get": { + "tags": [ + "roles" ], - "properties": { - "userId": { - "type": "integer", - "example": 42 - }, - "roleId": { - "type": "integer", - "example": 2 - } + "summary": "✅ Get all available server roles", + "description": "", + "operationId": "getRoles", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "roles": { + "type": "object", + "example": [ + { + "id": 1, + "role_name": "member", + "is_privileged": false + }, + { + "id": 2, + "role_name": "mentor", + "is_privileged": true + } + ] + } + } + } + } + } + } } - } } - } }, - "responses": { - "200": { - "description": "Role successfully revoked" - }, - "400": { - "description": "Missing required parameters" - }, - "403": { - "description": "User is not authorized to revoke roles" - }, - "500": { - "description": "Internal server error" - } - } - } - }, - "/users": { - "post": { - "tags": [ - "users" - ], - "summary": "✅ Create user", - "description": "First time authenticated user creation.", - "operationId": "createUser", - "requestBody": { - "description": "Data user inserted on registration", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "Jane Doe" - }, - "email": { - "type": "string", - "example": "jane@example.com" - }, - "about": { - "type": "string", - "example": "Hello i am Jane" - }, - "languages": { - "type": "object", - "example": [ - "English", - "Russian" - ] - } + "/admin/grant-role": { + "post": { + "tags": [ + "roles" + ], + "summary": "✅ Grant role to user", + "description": "Grants a role to a user. Only accessible by users with admin privileges.", + "operationId": "grantRole", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "userId", + "roleId" + ], + "properties": { + "userId": { + "type": "integer", + "example": 42 + }, + "roleId": { + "type": "integer", + "example": 2 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Role successfully granted" + }, + "400": { + "description": "Missing required parameters" + }, + "403": { + "description": "User is not authorized to grant roles" + }, + "500": { + "description": "Internal server error" + } } - } } - } }, - "responses": { - "201": { - "description": "User created successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 42 - } - } - } - } - } - }, - "409": { - "description": "User with this email is already registered", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "User with this email is already registered" - } - } + "/admin/revoke-role": { + "post": { + "tags": [ + "roles" + ], + "summary": "✅ Revoke role from user", + "description": "Revokes a role from a user. Only accessible by users with admin privileges.", + "operationId": "revokeRole", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "userId", + "roleId" + ], + "properties": { + "userId": { + "type": "integer", + "example": 42 + }, + "roleId": { + "type": "integer", + "example": 2 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Role successfully revoked" + }, + "400": { + "description": "Missing required parameters" + }, + "403": { + "description": "User is not authorized to revoke roles" + }, + "500": { + "description": "Internal server error" + } } - } } - } - } - }, - "get": { - "tags": [ - "users" - ], - "summary": "✅ Get all users - general information", - "operationId": "getUsers", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "users": { - "type": "object", - "example": [ - { - "id": 1, - "name": "Jane", - "languages": [ - "English", - "Spanish" - ] - }, - { - "id": 2, - "name": "Nina", - "languages": [ - "English", - "Russian" - ] + }, + "/users": { + "post": { + "tags": [ + "users" + ], + "summary": "✅ Create user", + "description": "First time authenticated user creation.", + "operationId": "createUser", + "requestBody": { + "description": "Data user inserted on registration", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Jane Doe" + }, + "email": { + "type": "string", + "example": "jane@example.com" + }, + "about": { + "type": "string", + "example": "Hello i am Jane" + }, + "languages": { + "type": "object", + "example": [ + "English", + "Russian" + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "User created successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 42 + } + } + } + } + } + }, + "409": { + "description": "User with this email is already registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "User with this email is already registered" + } + } + } + } } - ] } - } } - } - } - } - } - } - }, - "/users/login": { - "post": { - "tags": [ - "users" - ], - "summary": "✅ User login", - "description": "Authenticates a user based on a provided Firebase token sent in the header. On successful authentication, sets a secure 'userUID' cookie.", - "operationId": "loginUser", - "parameters": [ - { - "name": "X-Firebase-Token", - "in": "header", - "description": "Firebase ID token used for Firebase authentication", - "schema": { - "type": "string" - }, - "required": false - }, - { - "name": "X-Discord-Code", - "in": "header", - "description": "Authorization code returned by Discord OAuth2 flow", - "schema": { - "type": "string" }, - "required": false - } - ], - "responses": { - "200": { - "description": "Successful login", - "headers": { - "Set-Cookie": { - "description": "Secure cookie containing 'userUID'", - "schema": { - "type": "string" + "get": { + "tags": [ + "users" + ], + "summary": "✅ Get all users - general information", + "operationId": "getUsers", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "users": { + "type": "object", + "example": [ + { + "id": 1, + "name": "Jane", + "languages": [ + "English", + "Spanish" + ] + }, + { + "id": 2, + "name": "Nina", + "languages": [ + "English", + "Russian" + ] + } + ] + } + } + } + } + } + } } - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "user": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 42 - }, - "name": { - "type": "string", - "example": "Jane" - }, - "email": { - "type": "string", - "example": "jane@example.com" + } + }, + "/users/login": { + "post": { + "tags": [ + "users" + ], + "summary": "✅ User login", + "description": "Authenticates a user based on a provided Firebase token sent in the header. On successful authentication, sets a secure 'userUID' cookie.", + "operationId": "loginUser", + "parameters": [ + { + "name": "X-Firebase-Token", + "in": "header", + "description": "Firebase ID token used for Firebase authentication", + "schema": { + "type": "string" }, - "about": { - "type": "string", - "example": "Hi I am Jane" + "required": false + }, + { + "name": "X-Discord-Code", + "in": "header", + "description": "Authorization code returned by Discord OAuth2 flow", + "schema": { + "type": "string" }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2024-12-15T17:27:56.146Z" + "required": false + } + ], + "responses": { + "200": { + "description": "Successful login", + "headers": { + "Set-Cookie": { + "description": "Secure cookie containing 'userUID'", + "schema": { + "type": "string" + } + } }, - "languages": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "English", - "Spanish" - ] + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 42 + }, + "name": { + "type": "string", + "example": "Jane" + }, + "email": { + "type": "string", + "example": "jane@example.com" + }, + "about": { + "type": "string", + "example": "Hi I am Jane" + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2024-12-15T17:27:56.146Z" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "English", + "Spanish" + ] + } + } + } + } + } + } + } + }, + "401": { + "description": "No token provided or invalid token.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "No token provided" + } + } + } + } + } + }, + "403": { + "description": "Invalid application status or email verification.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Email not verified" + } + } + } + } } - } } - } - } - } - } - }, - "401": { - "description": "No token provided or invalid token.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "No token provided" - } - } - } - } - } - }, - "403": { - "description": "Invalid application status or email verification.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "Email not verified" - } - } } - } } - } - } - } - }, - "/user/{user_id}": { - "get": { - "tags": [ - "users" - ], - "summary": "✅ Get user by user id", - "description": "", - "operationId": "getUserById", - "parameters": [ - { - "name": "user_id", - "in": "path", - "description": "The id of user that needs to be fetched. Use 42 for testing. ", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 42 + }, + "/user/{user_id}": { + "get": { + "tags": [ + "users" + ], + "summary": "✅ Get user by user id", + "description": "", + "operationId": "getUserById", + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "The id of user that needs to be fetched. Use 42 for testing. ", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 42 + }, + "name": { + "type": "string", + "example": "Jane" + }, + "email": { + "type": "string", + "example": "jane@example.com" + }, + "about": { + "type": "string", + "example": "Hi iam Jane" + }, + "created_at": { + "type": "string", + "example": "2024-11-23T17:27:56.146Z" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "English", + "Spanish" + ] + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "mentee" + ] + }, + "mentor_id": { + "type": "string", + "example": 10 + }, + "is_mentor_active": { + "type": "boolean", + "example": true + } + } + } + } + } }, - "name": { - "type": "string", - "example": "Jane" + "400": { + "description": "Invalid user id supplied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Invalid user id supplied" + } + } + } + } + } }, - "email": { - "type": "string", - "example": "jane@example.com" + "404": { + "description": "User not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "User not found" + } + } + } + } + } + } + } + }, + "put": { + "tags": [ + "users" + ], + "summary": "✅ Update user", + "description": "This can only be done by the logged in user. Any changes that are performed in user profile come here.", + "operationId": "updateUser", + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "id of user that need to be updated", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "description": "Update an existent user in the store", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "New Name" + }, + "about": { + "type": "string", + "example": "New Description" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "English", + "Spanish", + "Added Language" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 42 + }, + "name": { + "type": "string", + "example": "New Name" + }, + "email": { + "type": "string", + "example": "jane@example.com" + }, + "about": { + "type": "string", + "example": "New Description" + }, + "created_at": { + "type": "string", + "example": "2024-11-23T17:27:56.146Z" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "English", + "Spanish", + "Added Language" + ] + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "mentee" + ] + } + } + } + } + } }, - "about": { - "type": "string", - "example": "Hi iam Jane" + "400": { + "description": "Invalid user id supplied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Invalid user id supplied" + } + } + } + } + } }, - "created_at": { - "type": "string", - "example": "2024-11-23T17:27:56.146Z" + "403": { + "description": "You don't have the rights to edit this user", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "You're not allowed to edit this resource" + } + } + } + } + } }, - "languages": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "English", - "Spanish" - ] - }, - "roles": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "mentee" - ] + "404": { + "description": "User not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "User not found" + } + } + } + } + } + } + } + }, + "delete": { + "tags": [ + "users" + ], + "summary": "✅ Delete user by id", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "The id of user that needs to be deleted", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 42 + } + } + } + } + } }, - "mentor_id": { - "type": "string", - "example": 10 + "400": { + "description": "Invalid user_id supplied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Invalid user id supplied" + } + } + } + } + } }, - "is_mentor_active": { - "type": "boolean", - "example": true + "403": { + "description": "You don't have the rights to edit this user", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "You're not allowed to edit this resource" + } + } + } + } + } + }, + "404": { + "description": "User not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "User not found" + } + } + } + } + } } - } - } - } - } - }, - "400": { - "description": "Invalid user id supplied", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "Invalid user id supplied" - } - } } - } } - }, - "404": { - "description": "User not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "User not found" - } - } + }, + "/projects": { + "post": { + "tags": [ + "projects" + ], + "summary": "Create project", + "description": "Creates new project - available to mentors only", + "operationId": "createProject", + "requestBody": { + "description": "Data mentor fills in on project creation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string", + "example": "Example project name" + }, + "description": { + "type": "string", + "example": "Long detailed description of project" + }, + "description_short": { + "type": "string", + "example": "Short project summary" + }, + "duration": { + "type": "string", + "example": "1 month" + }, + "period": { + "type": "string", + "example": "December 1st - December 31st" + }, + "img_link": { + "type": "string", + "example": "https://exampleurllink.com/image1.png" + }, + "links": { + "type": "object", + "example": [ + { + "url": "https://exampleurl.com", + "description": "description of the link" + }, + { + "url": "https://anotherexample.url.com", + "description": "another url for the project" + } + ] + }, + "tags": { + "type": "object", + "example": [ + "javascript", + "nodejs", + "express" + ] + }, + "positions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "example": "Frontend" + }, + "level": { + "type": "string", + "example": "Middle" + }, + "description": { + "type": "string", + "example": "Responsible for developing frontend services." + }, + "is_open": { + "type": "boolean", + "example": true + } + } + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 42 + } + } + } + } + } + }, + "400": { + "description": "Invalid input", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "null value in column \"description\" of relation \"projects\" violates not-null constraint" + } + } + } + } + } + }, + "403": { + "description": "User is not allowed to create projects", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "You're not allowed to access this resource" + } + } + } + } + } + } } - } - } - } - } - }, - "put": { - "tags": [ - "users" - ], - "summary": "✅ Update user", - "description": "This can only be done by the logged in user. Any changes that are performed in user profile come here.", - "operationId": "updateUser", - "parameters": [ - { - "name": "user_id", - "in": "path", - "description": "id of user that need to be updated", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "description": "Update an existent user in the store", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "New Name" - }, - "about": { - "type": "string", - "example": "New Description" - }, - "languages": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "English", - "Spanish", - "Added Language" - ] - } + }, + "get": { + "tags": [ + "projects" + ], + "summary": "Get projects - general info", + "description": "This endpoint is used for displaying projects (general information only)", + "operationId": "getProjects", + "parameters": [ + { + "name": "user_id", + "in": "query", + "description": "To show 'my projects' you can pass user_id to filter the output. Not required.", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "applications": { + "type": "array", + "items": { + "type": "object" + }, + "example": [ + { + "id": 1, + "titile": "Example project", + "description_short": "A short summary of project", + "status": "open", + "duration": "1 month", + "period": "December 1st-December 31st", + "img_link": "https://example.com/image.png", + "created_by": 42, + "tags": [ + "javascript", + "nodejs", + "express" + ] + } + ] + } + } + } + } + } + }, + "400": { + "description": "Invalid query parameter", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "The user_id passed is invalid" + } + } + } + } + } + } } - } } - } }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 42 + "/projects/{project_id}": { + "get": { + "tags": [ + "projects" + ], + "summary": "Get projec by id - detailed information", + "description": "", + "operationId": "getProject", + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The id of project that needs to be fetched.", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 42 + }, + "created_by": { + "type": "integer", + "example": 1 + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2024-11-30T12:34:56Z" + }, + "title": { + "type": "string", + "example": "Project Title" + }, + "description": { + "type": "string", + "example": "A detailed description of the project." + }, + "description_short": { + "type": "string", + "example": "A short description of the project." + }, + "status": { + "type": "string", + "example": "active" + }, + "duration": { + "type": "string", + "example": "6 months" + }, + "period": { + "type": "string", + "example": "2024 Q1" + }, + "img_link": { + "type": "string", + "format": "uri", + "example": "https://example.com/images/project.jpg" + }, + "links": { + "type": "object", + "example": [ + { + "url": "https://exampleurl.com", + "description": "description of the link" + }, + { + "url": "https://anotherexample.url.com", + "description": "another url for the project" + } + ] + }, + "tags": { + "type": "object", + "example": [ + "javascript", + "nodejs", + "express" + ] + }, + "positions": { + "type": "object", + "example": [ + { + "id": 1, + "titile": "Frontend", + "level": "Middle", + "is_open": true, + "description": "Needs to do frontend" + }, + { + "id": 2, + "titile": "Backend", + "level": "Senior", + "is_open": true, + "description": "Needs to do backend" + } + ] + } + } + } + } + } }, - "name": { - "type": "string", - "example": "New Name" + "400": { + "description": "Invalid project id supplied" }, - "email": { - "type": "string", - "example": "jane@example.com" + "404": { + "description": "Project not found" + } + } + }, + "put": { + "tags": [ + "projects" + ], + "summary": "Update project", + "description": "This can only be done by the user that created the project.", + "operationId": "updateProject", + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "id of project that need to be updated", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "description": "Update an existing project info", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string", + "example": "Updated Project Title" + }, + "description": { + "type": "string", + "example": "An updated detailed description of the project." + }, + "description_short": { + "type": "string", + "example": "A short description of the project." + }, + "status": { + "type": "string", + "example": "active" + }, + "duration": { + "type": "string", + "example": "6 months" + }, + "period": { + "type": "string", + "example": "2024 Q1" + }, + "img_link": { + "type": "string", + "format": "uri", + "example": "https://example.com/images/project.jpg" + }, + "links": { + "type": "object", + "example": [ + { + "url": "https://exampleurl.com", + "description": "description of the link" + } + ] + }, + "tags": { + "type": "object", + "example": [ + "javascript", + "nodejs", + "express" + ] + }, + "positions": { + "type": "object", + "example": [ + { + "id": 1, + "titile": "Frontend", + "level": "Middle", + "is_open": true, + "description": "Needs to do frontend" + }, + { + "id": 2, + "titile": "Backend", + "level": "Senior", + "is_open": true, + "description": "Needs to do backend" + } + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation. Returns updated project info" }, - "about": { - "type": "string", - "example": "New Description" + "400": { + "description": "Invalid project id supplied" }, - "created_at": { - "type": "string", - "example": "2024-11-23T17:27:56.146Z" + "403": { + "description": "You don't have the rights to edit this project" }, - "languages": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "English", - "Spanish", - "Added Language" - ] - }, - "roles": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "mentee" - ] + "404": { + "description": "Project not found" } - } - } - } - } - }, - "400": { - "description": "Invalid user id supplied", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "Invalid user id supplied" - } - } } - } - } - }, - "403": { - "description": "You don't have the rights to edit this user", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "You're not allowed to edit this resource" - } - } - } - } - } - }, - "404": { - "description": "User not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "User not found" - } - } + }, + "delete": { + "tags": [ + "projects" + ], + "summary": "Delete project entity", + "description": "This can only be done by the logged in user that owns this project", + "operationId": "deleteProject", + "parameters": [ + { + "name": "project_id", + "in": "path", + "description": "The id of project that needs to be deleted", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Successful operation" + }, + "400": { + "description": "Invalid project_id supplied" + }, + "403": { + "description": "You don't have the rights to edit this project" + }, + "404": { + "description": "Project not found" + } } - } - } - } - } - }, - "delete": { - "tags": [ - "users" - ], - "summary": "✅ Delete user by id", - "description": "This can only be done by the logged in user.", - "operationId": "deleteUser", - "parameters": [ - { - "name": "user_id", - "in": "path", - "description": "The id of user that needs to be deleted", - "required": true, - "schema": { - "type": "integer" } - } - ], - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 42 - } - } + }, + "/projects/{project_id}/applications": { + "post": { + "tags": [ + "projects" + ], + "summary": "Create a new application for a project", + "description": "Adds a new application to the specified project position.", + "operationId": "createProjectApplication", + "parameters": [ + { + "name": "project_id", + "in": "path", + "required": true, + "description": "The ID of the project to add the application to.", + "schema": { + "type": "integer", + "example": 42 + } + } + ], + "requestBody": { + "description": "The details of the application to create.", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "I have a great experience and I will be a good fit" + }, + "file_link": { + "type": "string", + "example": "https://example.link/file.pdf" + }, + "position_id": { + "type": "integer", + "example": 2 + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Application successfully created.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 101 + } + } + } + } + } + }, + "400": { + "description": "Invalid input (wrong value or the applications are closed)" + }, + "403": { + "description": "You're not allowed to edit this resource (user is not logged in)" + }, + "404": { + "description": "Project not found." + } } - } } - }, - "400": { - "description": "Invalid user_id supplied", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "Invalid user id supplied" - } - } + }, + "/projects/applications": { + "get": { + "tags": [ + "projects" + ], + "summary": "Get list of applications for projects", + "description": "Shows list of applications for projects the logged in user owns", + "operationId": "getProjectsApplications", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "To show only approved or only rejected applications, filter by status. Not required", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "applications": { + "type": "array", + "items": { + "type": "object" + }, + "example": [ + { + "id": 1, + "user_id": 12, + "user_name": "Nastya", + "description": "I want to participate in the project", + "status": "pending", + "project_name": "Awesome project", + "project_id": 42, + "project_status": "planned", + "position_title": "Frontend", + "position_id": 2 + } + ] + } + } + } + } + } + }, + "400": { + "description": "Invalid project id supplied" + }, + "404": { + "description": "Project not found" + } } - } } - }, - "403": { - "description": "You don't have the rights to edit this user", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "You're not allowed to edit this resource" - } - } + }, + "/projects/applications/{application_id}": { + "put": { + "tags": [ + "projects" + ], + "summary": "Edit status of application", + "description": "Approve or reject an application to participate in project", + "operationId": "editProjectsApplication", + "parameters": [ + { + "name": "application_id", + "in": "path", + "required": true, + "description": "The ID of the application to make changes to.", + "schema": { + "type": "integer", + "example": 2 + } + } + ], + "requestBody": { + "description": "The details of the application to edit.", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "approved" + }, + "comment": { + "type": "string", + "example": "Welcome to the team! :)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Application successfully updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 2 + }, + "status": { + "type": "string", + "example": "approved" + } + } + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "403": { + "description": "You're not allowed to edit this resource" + }, + "404": { + "description": "Application not found." + } } - } } - }, - "404": { - "description": "User not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "User not found" - } - } + }, + "/mentors": { + "post": { + "tags": [ + "mentors" + ], + "summary": "✅ Create a new mentor application", + "description": "Creates a new mentor entity in 'pending' status. Available to all logged-in users. If user already has a mentor profile, creation is not allowed.\n", + "operationId": "createMentor", + "requestBody": { + "required": true, + "description": "Data provided by the user during application", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MentorCreateRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Mentor application created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 42 + } + } + } + } + } + }, + "400": { + "description": "User already has a mentor profile" + }, + "403": { + "description": "User is not authenticated" + } } - } - } - } - } - } - }, - "/projects": { - "post": { - "tags": [ - "projects" - ], - "summary": "Create project", - "description": "Creates new project - available to mentors only", - "operationId": "createProject", - "requestBody": { - "description": "Data mentor fills in on project creation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "title": { - "type": "string", - "example": "Example project name" - }, - "description": { - "type": "string", - "example": "Long detailed description of project" - }, - "description_short": { - "type": "string", - "example": "Short project summary" - }, - "duration": { - "type": "string", - "example": "1 month" - }, - "period": { - "type": "string", - "example": "December 1st - December 31st" - }, - "img_link": { - "type": "string", - "example": "https://exampleurllink.com/image1.png" - }, - "links": { - "type": "object", - "example": [ - { - "url": "https://exampleurl.com", - "description": "description of the link" - }, - { - "url": "https://anotherexample.url.com", - "description": "another url for the project" - } - ] - }, - "tags": { - "type": "object", - "example": [ - "javascript", - "nodejs", - "express" - ] - }, - "positions": { - "type": "array", - "items": { - "type": "object", - "properties": { - "title": { - "type": "string", - "example": "Frontend" - }, - "level": { - "type": "string", - "example": "Middle" - }, - "description": { - "type": "string", - "example": "Responsible for developing frontend services." - }, - "is_open": { - "type": "boolean", - "example": true + }, + "get": { + "tags": [ + "mentors" + ], + "summary": "✅ Get mentor list", + "description": "Returns a list of mentor applications. When filtering by 'pending' or 'rejected', or not filtering at all, user must have elevated (admin) role. Returns user_id and some aggregated data from user profile\n", + "operationId": "getMentors", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status of mentors to retrieve (e.g. active, pending, rejected)", + "required": false, + "schema": { + "type": "string", + "enum": [ + "active", + "pending", + "rejected", + "inactive" + ] } - } } - } + ], + "responses": { + "200": { + "description": "List of mentors", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MentorListResponse" + } + } + } + }, + "403": { + "description": "User is not allowed to view this list" + } } - } } - } }, - "responses": { - "201": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 42 - } - } - } - } - } - }, - "400": { - "description": "Invalid input", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "null value in column \"description\" of relation \"projects\" violates not-null constraint" - } - } + "/mentors/{mentor_id}": { + "get": { + "tags": [ + "mentors" + ], + "summary": "✅ Get mentor by ID", + "description": "Returns full mentor data if user is logged in. Pending/rejected mentors require admin role.\n", + "operationId": "getMentorById", + "parameters": [ + { + "name": "mentor_id", + "in": "path", + "required": true, + "description": "ID of the mentor to retrieve", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Mentor details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MentorDetails" + } + } + } + }, + "403": { + "description": "Access denied" + }, + "404": { + "description": "Mentor not found" + } } - } - } - }, - "403": { - "description": "User is not allowed to create projects", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "You're not allowed to access this resource" - } - } + }, + "put": { + "tags": [ + "mentors" + ], + "summary": "Update mentor profile", + "description": "Only the user who owns this mentor card can update it. Only selected fields can be changed.\n", + "operationId": "updateMentor", + "parameters": [ + { + "name": "mentor_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MentorUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Mentor profile updated" + }, + "403": { + "description": "Not authorized" + }, + "404": { + "description": "Mentor not found" + } } - } - } - } - } - }, - "get": { - "tags": [ - "projects" - ], - "summary": "Get projects - general info", - "description": "This endpoint is used for displaying projects (general information only)", - "operationId": "getProjects", - "parameters": [ - { - "name": "user_id", - "in": "query", - "description": "To show 'my projects' you can pass user_id to filter the output. Not required.", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "applications": { - "type": "array", - "items": { - "type": "object" - }, - "example": [ - { - "id": 1, - "titile": "Example project", - "description_short": "A short summary of project", - "status": "open", - "duration": "1 month", - "period": "December 1st-December 31st", - "img_link": "https://example.com/image.png", - "created_by": 42, - "tags": [ - "javascript", - "nodejs", - "express" - ] - } - ] - } - } + }, + "patch": { + "tags": [ + "mentors" + ], + "summary": "✅ Update mentor status", + "description": "Available to users with elevated role (admin) only or to user owning the mentor card.\n", + "operationId": "patchMentorStatus", + "parameters": [ + { + "name": "mentor_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "active", + "rejected", + "inactive" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Status updated" + }, + "403": { + "description": "Not authorized" + }, + "404": { + "description": "Mentor not found" + } } - } - } - }, - "400": { - "description": "Invalid query parameter", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "The user_id passed is invalid" - } - } + }, + "delete": { + "tags": [ + "mentors" + ], + "summary": "Delete mentor profile", + "description": "Only the owner can delete their mentor card. Deletes the entity and removes mentor role from user.\n", + "operationId": "deleteMentor", + "parameters": [ + { + "name": "mentor_id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Mentor deleted" + }, + "403": { + "description": "Not authorized" + }, + "404": { + "description": "Mentor not found" + } } - } } - } - } - } - }, - "/projects/{project_id}": { - "get": { - "tags": [ - "projects" - ], - "summary": "Get projec by id - detailed information", - "description": "", - "operationId": "getProject", - "parameters": [ - { - "name": "project_id", - "in": "path", - "description": "The id of project that needs to be fetched.", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 42 - }, - "created_by": { - "type": "integer", - "example": 1 - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2024-11-30T12:34:56Z" - }, - "title": { - "type": "string", - "example": "Project Title" - }, - "description": { - "type": "string", - "example": "A detailed description of the project." - }, - "description_short": { - "type": "string", - "example": "A short description of the project." - }, - "status": { - "type": "string", - "example": "active" + }, + "/generate-invite": { + "post": { + "tags": [ + "discord integration" + ], + "summary": "✅ Generate a Discord invite link", + "description": "Generates a unique invite link for a user to join the Discord server. Validates if the user already exists on the server and checks if 7 days have passed since the last invite. Requires authentication via a `userUID` cookie.", + "responses": { + "200": { + "description": "Invite link generated successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "invite_link": { + "type": "string", + "description": "The generated Discord invite link.", + "example": "https://discord.gg/abc123" + } + } + } + } + } }, - "duration": { - "type": "string", - "example": "6 months" + "400": { + "description": "User already exists on the server", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error message.", + "example": "This user already exists on the server" + } + } + } + } + } }, - "period": { - "type": "string", - "example": "2024 Q1" + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error message.", + "example": "User with provided UID not found" + } + } + } + } + } }, - "img_link": { - "type": "string", - "format": "uri", - "example": "https://example.com/images/project.jpg" - }, - "links": { - "type": "object", - "example": [ - { - "url": "https://exampleurl.com", - "description": "description of the link" - }, - { - "url": "https://anotherexample.url.com", - "description": "another url for the project" + "403": { + "description": "Invite generation on cooldown for this user. Next link generation is possible after 7 days has passed since last one", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error message.", + "example": "Invite generation is on cooldown for this user" + } + } + } + } } - ] }, - "tags": { - "type": "object", - "example": [ - "javascript", - "nodejs", - "express" - ] - }, - "positions": { - "type": "object", - "example": [ - { - "id": 1, - "titile": "Frontend", - "level": "Middle", - "is_open": true, - "description": "Needs to do frontend" - }, - { - "id": 2, - "titile": "Backend", - "level": "Senior", - "is_open": true, - "description": "Needs to do backend" + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error message.", + "example": "Failed to generate invite" + } + } + } + } } - ] } - } - } - } - } - }, - "400": { - "description": "Invalid project id supplied" - }, - "404": { - "description": "Project not found" - } - } - }, - "put": { - "tags": [ - "projects" - ], - "summary": "Update project", - "description": "This can only be done by the user that created the project.", - "operationId": "updateProject", - "parameters": [ - { - "name": "project_id", - "in": "path", - "description": "id of project that need to be updated", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "description": "Update an existing project info", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "title": { - "type": "string", - "example": "Updated Project Title" - }, - "description": { - "type": "string", - "example": "An updated detailed description of the project." - }, - "description_short": { - "type": "string", - "example": "A short description of the project." - }, - "status": { - "type": "string", - "example": "active" - }, - "duration": { - "type": "string", - "example": "6 months" - }, - "period": { - "type": "string", - "example": "2024 Q1" - }, - "img_link": { - "type": "string", - "format": "uri", - "example": "https://example.com/images/project.jpg" - }, - "links": { - "type": "object", - "example": [ - { - "url": "https://exampleurl.com", - "description": "description of the link" - } - ] - }, - "tags": { - "type": "object", - "example": [ - "javascript", - "nodejs", - "express" - ] - }, - "positions": { - "type": "object", - "example": [ - { - "id": 1, - "titile": "Frontend", - "level": "Middle", - "is_open": true, - "description": "Needs to do frontend" - }, - { - "id": 2, - "titile": "Backend", - "level": "Senior", - "is_open": true, - "description": "Needs to do backend" - } - ] - } } - } } - } }, - "responses": { - "200": { - "description": "Successful operation. Returns updated project info" - }, - "400": { - "description": "Invalid project id supplied" - }, - "403": { - "description": "You don't have the rights to edit this project" - }, - "404": { - "description": "Project not found" - } - } - }, - "delete": { - "tags": [ - "projects" - ], - "summary": "Delete project entity", - "description": "This can only be done by the logged in user that owns this project", - "operationId": "deleteProject", - "parameters": [ - { - "name": "project_id", - "in": "path", - "description": "The id of project that needs to be deleted", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Successful operation" - }, - "400": { - "description": "Invalid project_id supplied" - }, - "403": { - "description": "You don't have the rights to edit this project" - }, - "404": { - "description": "Project not found" - } - } - } - }, - "/projects/{project_id}/applications": { - "post": { - "tags": [ - "projects" - ], - "summary": "Create a new application for a project", - "description": "Adds a new application to the specified project position.", - "operationId": "createProjectApplication", - "parameters": [ - { - "name": "project_id", - "in": "path", - "required": true, - "description": "The ID of the project to add the application to.", - "schema": { - "type": "integer", - "example": 42 - } - } - ], - "requestBody": { - "description": "The details of the application to create.", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "description": { - "type": "string", - "example": "I have a great experience and I will be a good fit" - }, - "file_link": { - "type": "string", - "example": "https://example.link/file.pdf" - }, - "position_id": { - "type": "integer", - "example": 2 - } + "/auth/discord": { + "get": { + "tags": [ + "discord integration" + ], + "summary": "✅ Initiate Discord OAuth2 flow", + "description": "Redirects to Discord's OAuth2 authorization page to start the authentication process.", + "responses": { + "302": { + "description": "Redirect to Discord's authorization page", + "headers": { + "Location": { + "schema": { + "type": "string", + "example": "https://discord.com/api/oauth2/authorize?client_id=12345&redirect_uri=https%3A%2F%2Fdevapi.catbytes.io%2Fauth%2Fdiscord%2Fcallback&response_type=code&scope=identify%20email" + } + } + } + } } - } } - } }, - "responses": { - "201": { - "description": "Application successfully created.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 101 - } - } - } - } - } - }, - "400": { - "description": "Invalid input (wrong value or the applications are closed)" - }, - "403": { - "description": "You're not allowed to edit this resource (user is not logged in)" - }, - "404": { - "description": "Project not found." - } - } - } - }, - "/projects/applications": { - "get": { - "tags": [ - "projects" - ], - "summary": "Get list of applications for projects", - "description": "Shows list of applications for projects the logged in user owns", - "operationId": "getProjectsApplications", - "parameters": [ - { - "name": "status", - "in": "query", - "description": "To show only approved or only rejected applications, filter by status. Not required", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "applications": { - "type": "array", - "items": { - "type": "object" - }, - "example": [ - { - "id": 1, - "user_id": 12, - "user_name": "Nastya", - "description": "I want to participate in the project", - "status": "pending", - "project_name": "Awesome project", - "project_id": 42, - "project_status": "planned", - "position_title": "Frontend", - "position_id": 2 - } - ] - } - } + "/presigned-url": { + "post": { + "tags": [ + "service endpoints" + ], + "summary": "✅ Generate presigned S3 upload URL", + "description": "Generates a presigned URL to upload a file to S3 (e.g. application video).\nCurrently supports only `application_video` objectType.\nRequires `objectKey` to be a valid UUIDv4 string. \nReturned filename should be sent to backend in a separate request to store in the database.\n", + "operationId": "generatePresignedUrl", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "objectKey", + "contentType", + "objectType" + ], + "properties": { + "objectKey": { + "type": "string", + "format": "uuid", + "example": "d41d8cd9-8f00-4204-a980-001e4bcd0000" + }, + "contentType": { + "type": "string", + "example": "video/mp4" + }, + "objectType": { + "type": "string", + "enum": [ + "application_video" + ], + "example": "application_video" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successfully generated presigned URL", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uploadUrl": { + "type": "string", + "format": "uri", + "example": "https://catbytes-bucket.s3.amazonaws.com/videos/applications/d41d8cd9-8f00-4204-a980-001e4bcd0000.mp4?X-Amz-Algorithm=..." + }, + "key": { + "type": "string", + "example": "d41d8cd9-8f00-4204-a980-001e4bcd0000.mp4" + } + } + } + } + } + }, + "400": { + "description": "Invalid input (missing fields, unsupported content type, invalid objectType, etc.)", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "objectKey must be a valid uuid v4." + } + } + } + } + } + }, + "500": { + "description": "Server error during URL generation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Failed to generate presigned URL" + } + } + } + } + } + } } - } } - }, - "400": { - "description": "Invalid project id supplied" - }, - "404": { - "description": "Project not found" - } } - } }, - "/projects/applications/{application_id}": { - "put": { - "tags": [ - "projects" - ], - "summary": "Edit status of application", - "description": "Approve or reject an application to participate in project", - "operationId": "editProjectsApplication", - "parameters": [ - { - "name": "application_id", - "in": "path", - "required": true, - "description": "The ID of the application to make changes to.", - "schema": { - "type": "integer", - "example": 2 - } - } - ], - "requestBody": { - "description": "The details of the application to edit.", - "required": true, - "content": { - "application/json": { - "schema": { + "components": { + "schemas": { + "MentorCreateRequest": { "type": "object", "properties": { - "status": { - "type": "string", - "example": "approved" - }, - "comment": { - "type": "string", - "example": "Welcome to the team! :)" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Application successfully updated.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 2 + "contact": { + "type": "string" }, - "status": { - "type": "string", - "example": "approved" + "about": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } } - } - } - } - } - }, - "400": { - "description": "Invalid input" - }, - "403": { - "description": "You're not allowed to edit this resource" - }, - "404": { - "description": "Application not found." - } - } - } - }, - "/mentors": { - "post": { - "tags": [ - "mentors" - ], - "summary": "✅ Create a new mentor application", - "description": "Creates a new mentor entity in 'pending' status. Available to all logged-in users. If user already has a mentor profile, creation is not allowed.\n", - "operationId": "createMentor", - "requestBody": { - "required": true, - "description": "Data provided by the user during application", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MentorCreateRequest" - } - } - } - }, - "responses": { - "201": { - "description": "Mentor application created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 42 - } - } - } - } - } - }, - "400": { - "description": "User already has a mentor profile" - }, - "403": { - "description": "User is not authenticated" - } - } - }, - "get": { - "tags": [ - "mentors" - ], - "summary": "✅ Get mentor list", - "description": "Returns a list of mentor applications. When filtering by 'pending' or 'rejected', or not filtering at all, user must have elevated (admin) role. Returns user_id and some aggregated data from user profile\n", - "operationId": "getMentors", - "parameters": [ - { - "name": "status", - "in": "query", - "description": "Status of mentors to retrieve (e.g. active, pending, rejected)", - "required": false, - "schema": { - "type": "string", - "enum": [ - "active", - "pending", - "rejected", - "inactive" - ] - } - } - ], - "responses": { - "200": { - "description": "List of mentors", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MentorListResponse" } - } - } - }, - "403": { - "description": "User is not allowed to view this list" - } - } - } - }, - "/mentors/{mentor_id}": { - "get": { - "tags": [ - "mentors" - ], - "summary": "✅ Get mentor by ID", - "description": "Returns full mentor data if user is logged in. Pending/rejected mentors require admin role.\n", - "operationId": "getMentorById", - "parameters": [ - { - "name": "mentor_id", - "in": "path", - "required": true, - "description": "ID of the mentor to retrieve", - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Mentor details", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MentorDetails" - } - } - } - }, - "403": { - "description": "Access denied" - }, - "404": { - "description": "Mentor not found" - } - } - }, - "put": { - "tags": [ - "mentors" - ], - "summary": "Update mentor profile", - "description": "Only the user who owns this mentor card can update it. Only selected fields can be changed.\n", - "operationId": "updateMentor", - "parameters": [ - { - "name": "mentor_id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MentorUpdateRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Mentor profile updated" - }, - "403": { - "description": "Not authorized" - }, - "404": { - "description": "Mentor not found" - } - } - }, - "patch": { - "tags": [ - "mentors" - ], - "summary": "✅ Update mentor status", - "description": "Available to users with elevated role (admin) only or to user owning the mentor card.\n", - "operationId": "patchMentorStatus", - "parameters": [ - { - "name": "mentor_id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { + }, + "MentorUpdateRequest": { "type": "object", "properties": { - "status": { - "type": "string", - "enum": [ - "active", - "rejected", - "inactive" - ] - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Status updated" - }, - "403": { - "description": "Not authorized" - }, - "404": { - "description": "Mentor not found" - } - } - }, - "delete": { - "tags": [ - "mentors" - ], - "summary": "Delete mentor profile", - "description": "Only the owner can delete their mentor card. Deletes the entity and removes mentor role from user.\n", - "operationId": "deleteMentor", - "parameters": [ - { - "name": "mentor_id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Mentor deleted" - }, - "403": { - "description": "Not authorized" - }, - "404": { - "description": "Mentor not found" - } - } - } - }, - "/generate-invite": { - "post": { - "tags": [ - "discord integration" - ], - "summary": "✅ Generate a Discord invite link", - "description": "Generates a unique invite link for a user to join the Discord server. Validates if the user already exists on the server and checks if 7 days have passed since the last invite. Requires authentication via a `userUID` cookie.", - "responses": { - "200": { - "description": "Invite link generated successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "invite_link": { - "type": "string", - "description": "The generated Discord invite link.", - "example": "https://discord.gg/abc123" - } - } - } - } - } - }, - "400": { - "description": "User already exists on the server", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "description": "Error message.", - "example": "This user already exists on the server" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "description": "Error message.", - "example": "User with provided UID not found" - } - } - } - } - } - }, - "403": { - "description": "Invite generation on cooldown for this user. Next link generation is possible after 7 days has passed since last one", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "description": "Error message.", - "example": "Invite generation is on cooldown for this user" - } - } - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "description": "Error message.", - "example": "Failed to generate invite" - } - } - } - } - } - } - } - } - }, - "/auth/discord": { - "get": { - "tags": [ - "discord integration" - ], - "summary": "✅ Initiate Discord OAuth2 flow", - "description": "Redirects to Discord's OAuth2 authorization page to start the authentication process.", - "responses": { - "302": { - "description": "Redirect to Discord's authorization page", - "headers": { - "Location": { - "schema": { - "type": "string", - "example": "https://discord.com/api/oauth2/authorize?client_id=12345&redirect_uri=https%3A%2F%2Fdevapi.catbytes.io%2Fauth%2Fdiscord%2Fcallback&response_type=code&scope=identify%20email" + "contact": { + "type": "string" + }, + "about": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } } - } - } - } - } - } - }, - "/presigned-url": { - "post": { - "tags": ["service endpoints"], - "summary": "✅ Generate presigned S3 upload URL", - "description": "Generates a presigned URL to upload a file to S3 (e.g. application video).\nCurrently supports only `application_video` objectType.\nRequires `objectKey` to be a valid UUIDv4 string. \nReturned filename should be sent to backend in a separate request to store in the database.", - "operationId": "generatePresignedUrl", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { + }, + "MentorListResponse": { "type": "object", - "required": ["objectKey", "contentType", "objectType"], "properties": { - "objectKey": { - "type": "string", - "format": "uuid", - "example": "d41d8cd9-8f00-3204-a980-001e4bcd0000" - }, - "contentType": { - "type": "string", - "example": "video/mp4" - }, - "objectType": { - "type": "string", - "enum": ["application_video"], - "example": "application_video" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successfully generated presigned URL", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "uploadUrl": { - "type": "string", - "format": "uri", - "example": "https://catbytes-bucket.s3.amazonaws.com/videos/applications/d41d8cd9-8f00-3204-a980-001e4bcd0000.mp4?X-Amz-Algorithm=..." - }, - "key": { - "type": "string", - "example": "d41d8cd9-8f00-3204-a980-001e4bcd0000.mp4" - } - } - } - } - } - }, - "400": { - "description": "Invalid input (missing fields, unsupported content type, invalid objectType, etc.)", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "objectKey must be a valid uuid v4." - } - } + "mentors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MentorSummary" + } + } } - } - } - }, - "500": { - "description": "Server error during URL generation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "Failed to generate presigned URL" - } - } + }, + "MentorSummary": { + "type": "object", + "properties": { + "mentor_id": { + "type": "integer" + }, + "user_id": { + "type": "integer" + }, + "about": { + "type": "string" + }, + "status": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "img_link": { + "type": "string" + } } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "MentorCreateRequest": { - "type": "object", - "properties": { - "contact": { - "type": "string" - }, - "about": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "MentorUpdateRequest": { - "type": "object", - "properties": { - "contact": { - "type": "string" - }, - "about": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "MentorListResponse": { - "type": "object", - "properties": { - "mentors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MentorSummary" - } - } - } - }, - "MentorSummary": { - "type": "object", - "properties": { - "mentor_id": { - "type": "integer" - }, - "user_id": { - "type": "integer" - }, - "about": { - "type": "string" - }, - "status": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "type": "string" + }, + "MentorDetails": { + "allOf": [ + { + "$ref": "#/components/schemas/MentorSummary" + }, + { + "type": "object", + "properties": { + "contact": { + "type": "string" + }, + "discord_nickname": { + "type": "string" + } + } + } + ] } - }, - "name": { - "type": "string" - }, - "img_link": { - "type": "string" - } } - }, - "MentorDetails": { - "allOf": [ - { - "$ref": "#/components/schemas/MentorSummary" - }, - { - "type": "object", - "properties": { - "contact": { - "type": "string" - }, - "discord_nickname": { - "type": "string" - } - } - } - ] - } } - } } \ No newline at end of file diff --git a/docs/catbytes_api_swagger.yaml b/docs/catbytes_api_swagger.yaml index 77535bf..21e1efa 100644 --- a/docs/catbytes_api_swagger.yaml +++ b/docs/catbytes_api_swagger.yaml @@ -40,7 +40,7 @@ paths: tags: - applications summary: ✅ Create new user application - description: Send new user application's data for review. + description: Send new user application's data for review. Whether video_link or video_filename is required. operationId: createApplication requestBody: description: Data user inserted on application @@ -61,9 +61,17 @@ paths: video_link: type: string example: http://example.com/video/1 + nullable: true + video_filename: + type: string + example: videofile.mov + nullable: true discord_nickname: type: string example: sample_username + oneOf: + - required: [video_link] + - required: [video_filename] responses: '201': description: Successful operation @@ -77,6 +85,7 @@ paths: about: I'm passionate about C++ programming email: jane@example.com video_link: http://example.com/video/1 + video_filename: '' discord_nickname: sample_username '400': description: Invalid input @@ -120,10 +129,11 @@ paths: - id: 5 name: jane doe about: im cool - video_link: http://example.com/video/1 + video_link: '' discord_username: sample_username created_at: '2024-11-23T16:37:04.115Z' email: jane@example.com + video_filename: 'video1.mp4' - id: 6 name: nina about: i like cats @@ -131,6 +141,7 @@ paths: discord_username: nina_disc created_at: '2024-11-20T16:37:04.115Z' email: nina@example.com + video_filename: '' '403': description: User is not allowed to view applications list content: @@ -142,6 +153,52 @@ paths: type: string example: You're not allowed to access this resource /applications/{application_id}: + get: + tags: + - applications + summary: ✅ Get membership application by id + description: Returns application with extended information. Right now extended information only includes video_file - a link to uploaded video. + operationId: getApplicationById + parameters: + - name: application_id + in: path + description: 'The id of application that needs to be fetched. Use 42 for testing. ' + required: true + schema: + type: integer + responses: + '200': + description: Application found + content: + application/json: + schema: + type: object + properties: + id: + type: integer + name: + type: string + about: + type: string + email: + type: string + discord_nickname: + type: string + video_link: + type: string + nullable: true + video_filename: + type: string + nullable: true + video_file: + type: string + format: uri + description: Download URL for uploaded video + '404': + description: Application not found + '500': + description: Internal server error + put: tags: - applications @@ -1384,7 +1441,7 @@ paths: objectKey: type: string format: uuid - example: d41d8cd9-8f00-3204-a980-001e4bcd0000 + example: d41d8cd9-8f00-4204-a980-001e4bcd0000 contentType: type: string example: video/mp4 @@ -1404,10 +1461,10 @@ paths: uploadUrl: type: string format: uri - example: https://catbytes-bucket.s3.amazonaws.com/videos/applications/d41d8cd9-8f00-3204-a980-001e4bcd0000.mp4?X-Amz-Algorithm=... + example: https://catbytes-bucket.s3.amazonaws.com/videos/applications/d41d8cd9-8f00-4204-a980-001e4bcd0000.mp4?X-Amz-Algorithm=... key: type: string - example: d41d8cd9-8f00-3204-a980-001e4bcd0000.mp4 + example: d41d8cd9-8f00-4204-a980-001e4bcd0000.mp4 '400': description: Invalid input (missing fields, unsupported content type, invalid objectType, etc.) content: From b79d68d3aa0af0893f2dcaf6b21366e761220543 Mon Sep 17 00:00:00 2001 From: aliona matveeva Date: Sat, 18 Oct 2025 13:07:29 +0200 Subject: [PATCH 093/135] linter --- ...016183916_add_videofile_to_applications.js | 28 +++++++++---------- services/applications_service.js | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/knex/migrations/20251016183916_add_videofile_to_applications.js b/knex/migrations/20251016183916_add_videofile_to_applications.js index 8218526..d8ca72c 100644 --- a/knex/migrations/20251016183916_add_videofile_to_applications.js +++ b/knex/migrations/20251016183916_add_videofile_to_applications.js @@ -3,18 +3,18 @@ * @returns { Promise } */ exports.up = async function(knex) { - await knex.schema.alterTable('applications', function(table) { - table.string('video_filename', 255); - }); - }; - - /** - * @param { import("knex").Knex } knex - * @returns { Promise } - */ - exports.down = async function(knex) { - await knex.schema.alterTable('applications', function(table) { - table.dropColumn('video_filename'); - }); - }; + await knex.schema.alterTable('applications', function(table) { + table.string('video_filename', 255); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = async function(knex) { + await knex.schema.alterTable('applications', function(table) { + table.dropColumn('video_filename'); + }); +}; \ No newline at end of file diff --git a/services/applications_service.js b/services/applications_service.js index 7cab1ce..94c61ab 100644 --- a/services/applications_service.js +++ b/services/applications_service.js @@ -21,7 +21,7 @@ async function getApplicationByEmail(email) { } async function getApplicationById(id) { - application = await repo.getApplicationByFields({id: id}); + const application = await repo.getApplicationByFields({id: id}); if (application?.video_filename) { application.video_file = await getDownloadUrl(BUCKET_PREFIXES.applications, application.video_filename); } From 71053206570aa71695ddf4e82f6b8a1394272102 Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sat, 18 Oct 2025 12:34:58 +0100 Subject: [PATCH 094/135] changes req.body for PUT mentors and updates tests --- routes/mentors.js | 2 +- services/mentor_service.js | 2 -- tests/routes/mentors.test.js | 6 +++--- tests/services/mentor_service.test.js | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/routes/mentors.js b/routes/mentors.js index 6f9455d..d8aa5aa 100644 --- a/routes/mentors.js +++ b/routes/mentors.js @@ -102,7 +102,7 @@ router.patch("/mentors/:id", verifyRoles([ROLE_NAMES.member]), async (req, res) // Update mentor information by user owning mentorship card router.put("/mentors/:id", verifyRoles([ROLE_NAMES.mentor]), verifyOwnership(OWNED_ENTITIES.MENTOR), async (req, res) => { const { id } = req.params; - const { updates } = req.body; + const updates = req.body; if (!isValidIntegerId(id)) { return respondWithError(res, 400, "Invalid user id supplied"); diff --git a/services/mentor_service.js b/services/mentor_service.js index a766146..602da9d 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -114,8 +114,6 @@ async function updateMentor(userRoles, mentorId, updates) { if(mentorData.status === MENTOR_STATUSES.active || mentorData.status === MENTOR_STATUSES.inactive) { const updatedMentorId = await repo.updateMentorById(mentorId, updates); return updatedMentorId; - } else { - return 0; } } diff --git a/tests/routes/mentors.test.js b/tests/routes/mentors.test.js index 2ac093a..eacd903 100644 --- a/tests/routes/mentors.test.js +++ b/tests/routes/mentors.test.js @@ -252,7 +252,7 @@ describe('PUT /mentors/:id', () => { const res = await request(app) .put(`/mentors/${mockedMentor.id}`) - .send({ updates: { about: 'updated about field', contact: 'updated@email.com' } }); + .send({ about: 'updated about field', contact: 'updated@email.com' }); expect(mentorService.updateMentor).toHaveBeenCalledWith( defaultUserRoles, @@ -266,7 +266,7 @@ describe('PUT /mentors/:id', () => { it('Update mentor - invalid field', async () => { const res = await request(app) .put(`/mentors/${mockedMentor.id}`) - .send({ updates: { name: 'new name' } }); + .send({ name: 'new name' }); expect(res.statusCode).toBe(400); expect(mentorService.updateMentor).not.toHaveBeenCalled(); @@ -279,7 +279,7 @@ describe('PUT /mentors/:id', () => { const res = await request(app) .put(`/mentors/${mockedMentor.id}`) .set('userId', defautlUserId) - .send({ updates: { about: 'new about that will not be applied', contact: 'new contact' } }); + .send({ about: 'new about that will not be applied', contact: 'new contact' }); expect(mentorService.updateMentor).toHaveBeenCalledWith( defaultUserRoles, diff --git a/tests/services/mentor_service.test.js b/tests/services/mentor_service.test.js index aba6efb..806ed2f 100644 --- a/tests/services/mentor_service.test.js +++ b/tests/services/mentor_service.test.js @@ -363,11 +363,10 @@ describe('Mentor Service', () => { [{ role_name: 'member' }], mockedMentorId.toString(), { about: 'trying to change' }, - true // isOwner ); expect(repo.updateMentorById).not.toHaveBeenCalled(); - expect(result).toBe(0); + expect(result).toBe(undefined); }); }); }); \ No newline at end of file From 59951404937d781b9a8b8e91a5a4fe5308428ccf Mon Sep 17 00:00:00 2001 From: Marina Kim Date: Sat, 18 Oct 2025 13:18:52 +0100 Subject: [PATCH 095/135] adds return 0 --- services/mentor_service.js | 1 + tests/services/mentor_service.test.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/services/mentor_service.js b/services/mentor_service.js index f680f23..d1d5a57 100644 --- a/services/mentor_service.js +++ b/services/mentor_service.js @@ -115,6 +115,7 @@ async function updateMentor(userRoles, mentorId, updates) { const updatedMentorId = await repo.updateMentorById(mentorId, updates); return updatedMentorId; } + return 0; } async function deleteMentorById(mentorId, userId) { diff --git a/tests/services/mentor_service.test.js b/tests/services/mentor_service.test.js index 0092977..4dea1c5 100644 --- a/tests/services/mentor_service.test.js +++ b/tests/services/mentor_service.test.js @@ -357,7 +357,7 @@ describe('Mentor Service', () => { ); expect(repo.updateMentorById).not.toHaveBeenCalled(); - expect(result).toBe(undefined); + expect(result).toBe(0); }); }); }); \ No newline at end of file From 72b45fb1b4e1c4989dd380a3a885623f83fa32c4 Mon Sep 17 00:00:00 2001 From: Anastasia Date: Sun, 19 Oct 2025 10:56:31 +0300 Subject: [PATCH 096/135] update new_mentor_application_email template --- .../email/new_mentor_application_email.hbs | 124 ++++++++---------- 1 file changed, 54 insertions(+), 70 deletions(-) diff --git a/templates/email/new_mentor_application_email.hbs b/templates/email/new_mentor_application_email.hbs index a8a3424..2156d33 100644 --- a/templates/email/new_mentor_application_email.hbs +++ b/templates/email/new_mentor_application_email.hbs @@ -13,7 +13,7 @@ href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet" /> - New Mentor Application + Email Template @@ -138,42 +124,39 @@

New Mentor Application

-

A new candidate has applied to join the mentor team

+

A new candidate has applied to join the mentor team.

Happy Cat - -
- -