PqucT&+uus-=&o$Tc5xB_QuWTpjA`S@75A)u|HRz+gfpE7xC~lsXTvCy-{3u
zpP(yJxzyIDBZ!@QVKDSu@N#Yk(g7*^q-y8MlUK<(tpgx|>7u&uvf9`H87zgUR0uJV
z7jDh8?4
zHUfBwjNK9UvBr|VYu;!IdnFHW_r8{t6s3RnE$gpjBUDRZcH{Ic%dXlSaRM0Ds4%|4
zyvPq%D(NlI580m|&pLb7#T~(Er$EPcJ}<{iV%^em`%X$jV^slqE9WCqCS_~ih-`R;
z7)+MM=Q1sn2usjGPUPutKl_m#E{cHa^m%&stp;f&NU1P$v^+FSz(fOCjsesX^G7p}
zBj)S}(Uqo!7f-WU@9Zn)^G*UNQ%d|5E~V2V*q7ONXM>J7#)-w>m8w60%S|Oi@VRGC
zo4iy^70ys@E6&0V$=iV}=8bO6c;7hKY1~p){uvY%5Nbj2GvXotQq>@i_Vsj?cfYmF_iwP4;uIx;5hl5h0
z(PpOcY?D&14t{sqNty8NlG`1_5e5=BMho(H1N%M6YJszRo{9R9t4pP)emT|byl!QT
zT`qekc;RMT;NVVdgY!1gFBztkj!q?HiS6S8Tbzesl{=TvhYK>>kTFVyKWVuNI#rBk
zI`{cl3
zCebRBYj6E&kbY0}
z7F%I`FZ*zu9##junY9Xf%=)pi!6TdK^4FYmdXDff^srQD;op^+@k8z`9kQx6^5EYV
z9g~oy4d=sek5?VyBw03tr{@+EcKnWcZwbS(i+;#A{0Qg^`nQ_32_ZBVbV0_aPSZZ?
zY
z=N;BD!&g||Fyw0iY>|^+xeK^FJTkbMK6^woSybz_t3SJtE;@f4R*2DLjX!*C`}rP)
zzk?O@Hb*L2IbKq!&lTVoZQsMU`Q|Hdak10y^c}FJ$W}|gHuNiyLk9f2_$id#<0Hvz
zjsuD&t4}ns9yq_1Z*%gy%-p{gO1d|m@tANl^G$S7pi0B%gMR}1huk%?EK9bJ5?xSv
ztD^C!jDl-HP&_2cG`rV{W`X!&Q-a+Kat+YjGJX$8b2L@*&Srr{WgrUOD}Pgt0;ACN
zM8oTp!=R|WV*^FW%t@*=o>BrU)%yZ2B
zTdZM4g7>NlVWOiu=ldsn6DMBgHq7mnow^Ky!jc{b+dc9eit#&{=w=jAH)Gr=gSC21
z;8nb5MLY;LPg~IVP}Mjls|IvwfvsqhZ&CV@QKdV&WQ`SiEOfJ?d$(lgNs0(_a35{Z
z(MWePBJf3!AYfkos&S=G+H>(lTfopm;3PhG@RF$PsPRpP{i38rd=;CVDdIegZM9>T
zOnA}W^g8UU{0{tiLEX{6ukygfnm&}Mdf-uF>2Q;>w+9rB>U5sE5|bl5_t@;79BBRt
z0p*n~&AWg=aQJ)y?eq9+?ikJBQBqA}NB;(Y`MHoKA-j3`Bo*W`US)C*81)
z4&VW8BXIE5YMvB9pQ6ldb5D}+w`?Nrr$icZ?Z8Xl=hRIG-0dI~9tOWz-kme0CBUdM
zZ~D;OY?|i*H$5WWv&U(wsvF+-VdlQ89gsEA28%5ORrRPImSe6g*Tj8iL^NF%20K4P
zlx#@ur+Yl}AUEHYqnh5~0M0ShI2(Po(Xaw>NZ=K#-jCFED!W=u2=6O7DaNkwGq}Hc
zwJaKl7FlctA7tTHFkG!6qJoX969XmE!?>ndcT#z=VwEHlOV)+nYlJ|EO5qX$EBO0_
zjZaz~1rKRgz&~+kThjEWhf#Ag?zof;)4$Z)JmjCF(XKy^SJWIpqz
z>G8WP4FlBc*AnUAyXs*YWFbml(rk=}pyC~j780sVi#w)1fSz(0I!{ucdK{i$Aw}GcR%P-r@5*NJ}F?)KCb)aq#Ch++s-)wgGS-Ew+L!4ARBe)~mIjy!B!&MDu>abNK+`q^N+_ey{PNQE`7
zQ&ATmQSpC)O7#`d)RfHw&9L2*=^apQXO!c4C5n(+Zo
zv?RB
zu$Spu$@+;V*F9T2&~u58loC@oTT-hRpXcEC>#8ls>>ptt&f--yzgEawX`ZQtaO-u|
zF|`!yNw=IwG|OKf;{%s=KjG78S^F;@1jP5;|7Gf9-S1tZ%ve4N@0VY~Gj8Ix4X{G-
zd4JP(XWn2^Ip)~vMnpj4+yE(~$a#aldngj49K|c4m1%dZIK`2(_lkY~AbKnVZ(5G9
zf3#BTVPfFed2C`L2gqw!$sfFI`jWsc!h0@Ua`nWFKdrpmd_`*tING&`-BTzxU*4-E
zCql_%Yw1b{>Gso9?GJvk@~==)b~#t%UF-2D0pCRw3LmncGz>34cJp<&V4dtoj6a%L
zu`;{Fls83@CPX{|nUz_-tjCDqyQYjqhIcNpQxPO}Hs+RadK8g!=%0lIh&>@HNIhXy^zpXi>wq=PU25M%
zvrZjZLbnPg1p!P=UbmDfY|YUnyGrZ6kojaJ%~-B0KQaA{U+IBx&2(GY-biN$vB$>F
zzq;<&;@!24Dn)$o*!}p`FpsXL9DX6O{2!c$P|WIm`jxeTf(}f@d6%$DR+S20U09_H
z$Ahv?-{Bsj@uQtLV4D0OJYP(3{&-zQs)}WtJLHmp3_077p%kU|YU?hk-t*;7ETod5
zg1lhS#(@KnPwiaGry|RzmL}iHX()RZR^`XcJxG;9%5R(oPC$;{(i~V1
zV*vUz&vBa@%#_TbS}uJ%u)I|GPL>a02Q?tT*<*
z+T0#7A8Dws7GOM4Kcehv+@ny@h%1fkle5p@-fpPtf)19onUXrA^N~YYFYciR;57}a
zg~T3zfx_c7;mLCh1vsQUT$-fU4-NG(x?tO6PzSD_D0n2b*|jRbv(QhZclK@kE=gVv
zhDaVPG>{kk)!E(YrU;W+@P2N?YRk!xTW693Mx!bp_HYs6^|swwd!kLQo&rku`NCoTQndW+pwTRLiOOCSQu4AzjXhi;kf
z4h-?R@SMWn@1?28DVs`%HX)Io@+(;?0#))ijW4eodrh^OldoXK%X_vA=#S**l|cI^
z{yAKoeIrO;qkg|9rZvxOB(IU{KaaD@uwM3WCiNQw_>HXmLL2<2?yNnC2W>p7=Ogs&
zTXnR}e?=u?HE#d-{-n*dY1uDlq?-6T-)P~^)iq+^<5`t|?&>~FnX<234n`*6vCgy~
zt7ZcPV$+w)bJ#k`j2;Zno}>1DKeTn~++{N*rPC`l7JQp*8oDB7Iu!=WSs%lxW)igD
z7GySQ4P1#Y3f>uXEUd~A4&vT$X_ssV*+7IF%CFslw@-DptH|^1Ks!|TTE~yWj%_<{
zoN$|(kGM3hs7_fUEQ;^MI(q0>hqB;UUnB2nQxK)_`&}!N7P;(EIB{p~Z(LOlR+qKO
z=v&`Xx$*L^1*2-zV~j}d%HbH4Vl`oJ2}SOoy`3^&$D!`^O|xpGWyW;vl;T7)`$l@x
z0)OGkPetDY<)6@?Kg`-c3Fjrx!k2fNMYA&d2C5fut%gd{A;!8j(ZTrF<%1eaYln+b
zi-)TDp1g7?k7{J!iF}WwkgYtD5!SgyUoTf1!TmpNcP&Al19R^t@$89t>&Dmt_Xpgf
zqF2)gC6^V1l(6|zN@bi>>P-)8na$W=8GgEi@t@C(Z1nZm?lXU7y9kO&v?#UQOl($5
zwM4Hs6jue`6n7U{j_huiN1>pvX$hNVRj
zdG@laX=Rp}r>Fbb$-da9>trmz%SC8lDNhIMkUtoNoNh1|^4W0e?lq{C#qKOSQU9i%
zRBZ7a6F$Un@T+Qg-kAf~k~;mF$#k{bLd7P6>UqF!2)p*BRxKQ7h%UCKx&|6Fd
zPxH$1i7t_$Kt-{xrk$Q;ohtIDdC+P-=Av2=SO0o-x~}N~?M)A0$pTUf4CB0uiJG3Q
zhzPv?FL%yA8S7^3=I0xfxwCOwzwOSUe%J6N
zqSB{gXB=6AqO!=RlF?17b)B%~)9{%=2DkOR#OQz@qAeMAQ53Mo*3%1n2$UYCq{Z|X
zZf(gu&^8mNn*!8YRB79`fN|76f658}irar`
zsno1g%XPsDV&LqYKk)#OV?9QR@O04=PQjlkP*2LbAVUhW(vyF_Qu(T8PCabgcQwe5W+spfv#2cDc;s{S+NJdTSY
zHF-~~%*@~bIl18Kcu08AD$7#X6%N!`L1VO95i;+i}8&z)1bVg@61n?IKi$@ksRO3
zu{D_Xo3%DSC~_G=iHmHxo4DL7{t$m{uTq}qa*%MLV!`wX!v=7pa(gZehPnGSH=cDm
z!7X~~$$nNU!p!K$PimM|%>H!lo$(nLL5?lWv8SQ6Jb*mj1XqZ)NgxS}BEp7co9
zHgqC=I=wQ$|IRHQO+bcx;rHpS8_!V^IGEqqm=yPQPjq{Km0?joVD7d
z(gq+l1t>eM55y(SRE9K5H`jgGz5mrZ*pc|@G?#|2
z4M7?ceW}u@Zd$g3Rzu#2U^_){T|wJ&xwWy%4Gc=L)BHio3HXFvZAfz^y>T;PmuIiM
zv83k1d1jT0V;D))-nyeP(D)^(tnbPn(NhUUTv7g$)7zg}PQ}Omz^h+UyG+HO8vBj6
zP;H{C%DHgY<)~`%BmV`s>LSTv_As>2yaS
zEM^&@e4>bMCFgkz_X^>!dEh~;rSi=2v!LZzD?TwzK$`mH)q39La$CR&W7RhZ;)vDk
z3HXo9H&XTPhT}rh^45*1a}qq~mt>1GzUl6JMU(4YsQSz%>udh_xj%
zMXiX%-;LB-!g*!_dVwd1!JUbHl3jmtY|OSYG?t4;2tbjgmfO-wm+a$8unv6UngGky
zYiqYq6<9xC?8KxzV$`=DuF{D~;ZpUDM?^Dg3Kx>mYOuzZ_uWlJgqnKu
z&umBTe(0ssfKgj4yi5W7kz@iJ5mMtq+O=@8srPH>o(4#b*w*sIahKLsqLfg}?a(WH
z>}jJLF&bRcI!+HU-o(&&bnJI`$Q@^5t@Y{&
zctbOngmIg8qeB|<>C1Bd$8a{(tC}h3qk+aSjC&~-{y)%&@j3iswk?fKzZk+DVVPW`
zJd1B@m}sh^i;ON_fZ=zmxUxQ6s=1C=^J`n8LTR5qtn>EApJG=j1&wMKm^5GWuNox)
zmfz#N|C+H4$el?tabV;x3Ez^y|B8HNPmN2^i^Y%9Omx
zfp~OJ4yXXhJ-%a*w_LsJ?4HbRKkfk%gIL~d?u|f?Kh(y){+%r+V0&%AZ+FMSU5iPNLU0q04CrYedB1G>|q685kS`bULs8OSZ
z)gnY${6y#5e1E-n&UwG6ehwVsN;G7=elt_R{|=fs6e+|35$yE+s1^AtfeFz<*-U
zVulhG06_1ggHSaNoZl@7PM@Ck>sP+G+;jLf8fi)@NlgAGJe)y;UFCh5&L?-Oga;wL
zt}pc_%d2-SbQa3ovc=r4J=!K1Qc%zoX^;SjxTKPa50xG_s7|U%=A7N%A#rlNaJ3e&
zUnwK8*m_d9+#ylD+-R1FY3&RhJzLt}&uWm8CXJ}Alqa|x
za?sx_a=;{FC5tjzcP1*8uKlKenpE!r#!2Pzy@f|1+Qe(ZMT+y~6((6VP(D7Ufp$qJ
z`2pRsrw@$m9Vztf6pP^$H(pj-xklSwXlpW8tw3V!^v{YK5!}vVB6?bt9EKAy>G`3J3tu$uFj%0L`YCHloBCf_H#R3+
zE1(xU#{gUZi&ot5O#<;tRX_IMKq2d-=Cq|F7_Z2lj>c^W&^mJ4k|Iogy0C!Fhyg^t
z-}?@OgGIUM{>+Tv{N?tZz2&iPi{v@?U<|H2v-!1gH(4gJ$PCq7h;HKjHJQY>Tdoga
zxpw1*djhNxmSO(P2}RjIXo4E2cE5p9Qjn@yO_^a#+qG?%t%h6@u8AlSWBe{}QeK;)
zPndUpq83`_N>iz79OW$TShUk+Y>kMSgHm^wSw%EzghCW`z;^5r}(-lSaAF9c^eWV!7
zcM%U387N65B}?&-AJ__lR#Bg%d_NjT$cEcNwj
z7wc(F^nB6RG3zYlBm03>*rI$0;CXvTUb)|Ka%b+kjv~7QV_{VYMsU*l^t^G?nA;WP
zL`fqu__a4RAEDGOBjh`4NRFiNqB5mC^**hx<9qF_7fiRuF?mcszsvo^q`;0FAWW)+
zFYUs*VxBoa4Gz!FQnfkx<{w7WpsCM(u%{^fiYJ%XBWeK?N1f?VE8ZrqqFX}IZ#7o!
z8eN3P0KuUwwJOdJDhp+6>Ckx?zQ6{}Gu3sbKJ&M+O1}IrS+t$P6UoZY>@Dmc4?2Y^
z+zLqA4I^6Q={in}PCv0ZGUy%Lz&a5T(a@Mv9EOQ;h`4$!Pokt~YZsNm{&QoCMCeC9*eaTE1%oy-1*ON&y~rwS6TyeM?UiIw0_v!{i8!!C}|Fq0SF@k$FgnbG(%?{6BNOvJ;f>!bUyn}=xO465uqXJg5BuHQ
zuBoOAY50aAZy)x}#mX2tF0#0QiH0G6mvBxvq}cse3y|CmVV1Dp{KFtTyF+Fa#H^4$
z{~$Je#T}y?QWt{A(5h1B{Ssiaw@KV_KJyL?3Z_{$Rxr=djnovc8MH_V{W80Eo7+IH
zw0}R$u8$O@!_B=&X{LOMG@H)EH3o`O8wG2izow5q8-;{NueEF|jye4%-ZD^{1R_CI^J+Z7qDlJ<|2E0^><`Fm~A4pW1y%I-Qh&gkK=zYukz@*AM5Qur=nTM45EqFw3nz}c#n5SU_
z@+UG(s3>lAqOVNTsJJYfqdks4mC-`7vG~)8M;gDM@D;G`=f;yMg(Y|
z*8g;irk)%ub-AUvub|w?DtWg5lO8RP^a%b#*S#Q)TvBMFBYDFm{AXAF@QW{RJ)wN5
zSITwcK=AG);!U$X2@v?%8B|^#)Kc+AnvhX^7j7?%YaSvuR<%{oMk+G^is!O+XXaAQ
zw4Neb&Oepo9oX|xQ44i(8ny=267%luGk`j%eFfiv}pw@81k
z!-pRVQw+ep?a5vykay*Rj_9aoU@O0@!h3x%bh<{l|zw};gh{>p}dl)SX
z$)FbSCNUKHhSEw@+j0te)fT>13p~M4vhKz+p?MkGf2JAewmj)1tI##u;Exg-LNGCnwKbBiun$Gj^btH0vUv%5i3Zvu_22A_m>LX&qtrU(eb#CIHgo1&
zm{iQ}WOu(xuU`+O25dPP7
z=h;(^Aw0{dBsy|hNp&8{@$Qz7fKAWoe+VLa`GLf
z$KO)cPW0FPh`aPws(_IS{e5~^X%BzxmE%wK^^&&(7<4)ecPxRm6)0Mc-ueV+Z8H1j
zPW2ZfjJ90#Nw1(LeN>`MqU#ekkCqp7S`PUzbmGVtfDXYl72fUHSg=7%37yr=S7S}Hty*u5GblhAohP$5c9w$h#lfOnxsFAFuWt3DlviuTo8@{w
z-UXY267>nb%2xZ{Pt$U?g77l1pM^7GDGPI
zzC=?Px_!K2C$uwe>_O>>Kb09*OL47_MhDx9h-E2BxOaviku+@7hy@
zXZU_00DQgKShAYrRcf$l$l7wxZs%MA!qbB-_UbS6x!#j%o?`J^O
zBE0Uk(Xw0xv5}$KYen$9!v2AA8wY|?9nldJZO*-V46%00sWV4Rb;XbNUs
z4i}6KF+7;VHWD-OR1Pp8!rmNJXf)9RzrgL#w_I}T`MQ?a3Z54MV&rMOjjms2QRw+2Js}H
zi+lK!EDZF-!KVs(|^9z(m&z^NEAjV7LnPzJD=
zLAhw|VV8l64MJE4N$4aZDGyiP{iXlVx0?m{6ZNcBu=mdj|E%djk^oPqPZ(cBle)AK
z4fmTBnZ$zS&bOud)!ggoRxFeu7AIkv^O&92eE4W7txGz>W!ldq%`!gaf}#9G#&m^+
zEA7cpV#L5{PiNgAT|;5Qz^m%JNxUrlDXk*rd9fY&xb6hk)ZK&oi)rfH(WtH;9u1(l
zP$(!C>GY@tdq38E{-<)79lGO5wR4bMBBIs4VK1
z!OO{Nul)gu@EV#^eL|D>kRvxSFG$RA!;)==ip0fzW^!TP))f1tmybPHK|&*ixa>}A
z18en#&>6jLtU+z1h_gSssRz~yG0J4sf=Wnn%krusRrjw8+GR~R(#-KC|F_q%S?K$H
z@B^E%rn<4{v(4M8y2UmEv|SB%1N{A=bw4xNkzc9@@mmgpTc;zUCAkl;Q|(+|8&8YF
zQ9!0IeDI~rP*ZdGn#WR7MxCAQH;{wkX+pp^&6QYzoA?6Mv|AJfB9ONrCRQSgI4h(`
zU@C@6$ipcX8K}JBU4W&K7`7<*FOx1HS
z4HW+1fFugJ>yCs;Zajw$fOIUJNt`s$p)*<{H5#)fJDXsWkOnV8MlruVNa^^z5mZ_O
zj2R7{GKXf3dza?M}RxRj~lUH)j$LfK=yb35_aYJ|Q9TrP$V)lu3Jm7X%m>z=AK~_DaqT8
zX%k3c!jQ87bf&RlcH(u!otHwkgqMU|;vK(bMunD}3BrhTt(^RkDX+X`_7#~2bM3HT
zPSf1CKtWi{jFlOy-hp>8hy%}c*pF0
z+%ss~U}{HyQ1yH2j@*Xdk4eYQ7u$}7yJwcdlu7yHgn`Si4X_{;|=I#*YWx3LHcIV)WK
zVT6Z-rrH619Nlfijr8`8o>zjBUlD=|WvY6>8k&iV5t3LEu~+O)Yz?_<$}YI`uuZ|M
zi1|Ur^J25#4WuM3ZZxj6+3E+zJ5}N@&{0<-3=$tNqLuXUUmq*(XLgfEG3uXV*m^4Q
zCKl$4Q@2l!xw0Gseoqn@rkrUgd3@yaPe)#>;&Eid3
zLU4HZ-%mq_Gl9!CvM*>u3+=)YREG0CmPVWMKIO(amh8VK8SaP^42G%VpOyPn?Vg1o
z^WWc@ogY{JwIqB_)&-~q13hz~ep!Nhn5L-e;UrSo`FRT1#4-6*3@+xM!;*m3jMZB)
z_R(3Bu^Gp)c{%uOC?&29Iz+c&tmzyqTn!`>xJ~1=)?=I|J4cl_h?4GWJ
zH3Zv?_WK-jkJ;m|rB9s4_krZtHVpH~;?Hg414{a)&oF{Hg!0qvjiU>ibH1+Q$8r*L
zPt|5;r}qVF3|c(4wY!#WXOySGSLt?ugOI7Y)>-iQb$1glkB4(GjI_RoYLXcQ
P6963zeMGIAUBv$Y4kLO$
literal 0
HcmV?d00001
diff --git a/server/api/applications/[id].delete.ts b/server/api/applications/[id].delete.ts
new file mode 100644
index 0000000..fc2af4d
--- /dev/null
+++ b/server/api/applications/[id].delete.ts
@@ -0,0 +1 @@
+// Placeholder for DELETE /api/applications/:id - Needs refactoring
diff --git a/server/api/applications/[id].get.ts b/server/api/applications/[id].get.ts
new file mode 100644
index 0000000..1bf136e
--- /dev/null
+++ b/server/api/applications/[id].get.ts
@@ -0,0 +1 @@
+// Placeholder for GET /api/applications/:id - Needs refactoring
diff --git a/server/api/applications/[id].put.ts b/server/api/applications/[id].put.ts
new file mode 100644
index 0000000..d7ec22e
--- /dev/null
+++ b/server/api/applications/[id].put.ts
@@ -0,0 +1 @@
+// Placeholder for PUT /api/applications/:id - Needs refactoring
diff --git a/server/api/applications/index.get.ts b/server/api/applications/index.get.ts
new file mode 100644
index 0000000..a527e84
--- /dev/null
+++ b/server/api/applications/index.get.ts
@@ -0,0 +1 @@
+// Placeholder for GET /api/applications - Needs refactoring
diff --git a/server/api/applications/index.post.ts b/server/api/applications/index.post.ts
new file mode 100644
index 0000000..68e0b81
--- /dev/null
+++ b/server/api/applications/index.post.ts
@@ -0,0 +1 @@
+// Placeholder for POST /api/applications - Needs refactoring
diff --git a/server/api/auth/login.post.ts b/server/api/auth/login.post.ts
new file mode 100644
index 0000000..56c09e5
--- /dev/null
+++ b/server/api/auth/login.post.ts
@@ -0,0 +1 @@
+// Placeholder for POST /api/auth/login - Needs refactoring from authController.js
diff --git a/server/api/auth/logout.post.ts b/server/api/auth/logout.post.ts
new file mode 100644
index 0000000..6af32f4
--- /dev/null
+++ b/server/api/auth/logout.post.ts
@@ -0,0 +1 @@
+// Placeholder for POST /api/auth/logout - Needs refactoring from authController.js
diff --git a/server/api/auth/user.get.ts b/server/api/auth/user.get.ts
new file mode 100644
index 0000000..ed2fa96
--- /dev/null
+++ b/server/api/auth/user.get.ts
@@ -0,0 +1 @@
+// Placeholder for GET /api/auth/user - Needs refactoring from authController.js
diff --git a/server/api/password/forgot.post.ts b/server/api/password/forgot.post.ts
new file mode 100644
index 0000000..6035007
--- /dev/null
+++ b/server/api/password/forgot.post.ts
@@ -0,0 +1 @@
+// Placeholder for POST /api/password/forgot - Needs refactoring
diff --git a/server/api/password/reset.post.ts b/server/api/password/reset.post.ts
new file mode 100644
index 0000000..60f151c
--- /dev/null
+++ b/server/api/password/reset.post.ts
@@ -0,0 +1 @@
+// Placeholder for POST /api/password/reset - Needs refactoring
diff --git a/server/api/stats/index.get.ts b/server/api/stats/index.get.ts
new file mode 100644
index 0000000..e1dc2c7
--- /dev/null
+++ b/server/api/stats/index.get.ts
@@ -0,0 +1 @@
+// Placeholder for GET /api/stats - Needs refactoring
diff --git a/server/api/webhooks/deploy/[id].post.ts b/server/api/webhooks/deploy/[id].post.ts
new file mode 100644
index 0000000..e43cc20
--- /dev/null
+++ b/server/api/webhooks/deploy/[id].post.ts
@@ -0,0 +1 @@
+// Placeholder for POST /api/webhooks/deploy/:id - Needs refactoring
diff --git a/server/app.legacy.js b/server/app.legacy.js
new file mode 100644
index 0000000..40460fa
--- /dev/null
+++ b/server/app.legacy.js
@@ -0,0 +1,68 @@
+const fs = require('fs')
+const express = require('express')
+const session = require('express-session')
+const sessionstore = require('sessionstore')
+const cookieParser = require('cookie-parser')
+const logger = require('morgan')
+
+const authRouter = require('./routes/auth')
+const passwordResetRouter = require('./routes/password')
+const appsRouter = require('./routes/applications')
+const webhooksRouter = require('./routes/webhooks')
+const statsRouter = require('./routes/stats')
+
+const app = express()
+
+app.use(
+ session({
+ secret: 'super-secret-cookie', // TODO add this to .env
+ resave: false,
+ saveUninitialized: true,
+ name: 'deployer_session',
+ store:
+ process.env.NODE_ENV === 'testing'
+ ? null
+ : sessionstore.createSessionStore({
+ type: 'redis',
+ }),
+ })
+)
+
+app.use(logger('dev'))
+app.use(express.json())
+app.use(express.urlencoded({ extended: false }))
+app.use(cookieParser())
+
+app.use(webhooksRouter)
+app.use('/auth', authRouter)
+app.use('/password', passwordResetRouter)
+app.use('/applications', appsRouter)
+app.use('/stats', statsRouter)
+
+app.use(function (err, req, res, next) {
+ const date = new Date()
+ fs.writeFileSync(
+ `logs/${date.getFullYear()}-${date.getMonth()}-${date.getDate()}.log`,
+ date.toUTCString() + '\n',
+ { flag: 'a' }
+ )
+ fs.writeFileSync(
+ `logs/${date.getFullYear()}-${date.getMonth()}-${date.getDate()}.log`,
+ err.message + '\n',
+ { flag: 'a' }
+ )
+ fs.writeFileSync(
+ `logs/${date.getFullYear()}-${date.getMonth()}-${date.getDate()}.log`,
+ err.stack,
+ { flag: 'a' }
+ )
+ fs.writeFileSync(
+ `logs/${date.getFullYear()}-${date.getMonth()}-${date.getDate()}.log`,
+ '\n-----------------------------\n',
+ {
+ flag: 'a',
+ }
+ )
+ res.status(500).json('Something broke!')
+})
+module.exports = app
diff --git a/server/database/migrations/20210502190643_create_users_table.js b/server/database/migrations/20210502190643_create_users_table.js
new file mode 100644
index 0000000..7df2d37
--- /dev/null
+++ b/server/database/migrations/20210502190643_create_users_table.js
@@ -0,0 +1,23 @@
+/**
+ * Run the migrations
+ * @param {import('knex')} knex
+ * @returns
+ */
+exports.up = function (knex) {
+ return knex.schema.createTable('users', (table) => {
+ table.bigIncrements('id')
+ table.string('name')
+ table.string('email').unique()
+ table.string('password')
+ table.timestamps(true, true)
+ })
+}
+
+/**
+ * Rollback the migrations the migrations
+ * @param {import('knex')} knex
+ * @returns
+ */
+exports.down = function (knex) {
+ return knex.schema.dropTableIfExists('users')
+}
diff --git a/server/database/migrations/20210502191523_create_applications_table.js b/server/database/migrations/20210502191523_create_applications_table.js
new file mode 100644
index 0000000..beb9f5a
--- /dev/null
+++ b/server/database/migrations/20210502191523_create_applications_table.js
@@ -0,0 +1,27 @@
+/**
+ * Run the migrations
+ * @param {import('knex')} knex
+ * @returns
+ */
+exports.up = function (knex) {
+ return knex.schema.createTable('applications', (table) => {
+ table.bigIncrements('id')
+ table.string('name')
+ table.string('full_name')
+ table.string('description').defaultTo('No description provided')
+ table.string('clone_url')
+ table.string('deploy_branch').defaultTo('main')
+ table.string('deploy_directory').defaultTo('/var/www/')
+ table.text('deploy_script').defaultTo('echo "Hello World"')
+ table.timestamps(true, true)
+ })
+}
+
+/**
+ * Rollback the migrations
+ * @param {import('knex')} knex
+ * @returns
+ */
+exports.down = function (knex) {
+ return knex.schema.dropTableIfExists('applications')
+}
diff --git a/server/database/migrations/20210502191524_create_deployments_table.js b/server/database/migrations/20210502191524_create_deployments_table.js
new file mode 100644
index 0000000..da40441
--- /dev/null
+++ b/server/database/migrations/20210502191524_create_deployments_table.js
@@ -0,0 +1,29 @@
+/**
+ * Run the migrations
+ * @param {import('knex')} knex
+ * @returns
+ */
+exports.up = function (knex) {
+ return knex.schema.createTable('deployments', (table) => {
+ table.bigIncrements('id')
+ table.bigInteger('application_id').unsigned()
+ table.text('log')
+ table.boolean('successful')
+ table.timestamps(true, true)
+
+ table
+ .foreign('application_id')
+ .references('id')
+ .inTable('applications')
+ .onDelete('CASCADE')
+ })
+}
+
+/**
+ * Roll back the migrations
+ * @param {import('knex')} knex
+ * @returns
+ */
+exports.down = function (knex) {
+ return knex.schema.dropTableIfExists('deployments')
+}
diff --git a/server/database/migrations/20211222163547_password_reset_table.js b/server/database/migrations/20211222163547_password_reset_table.js
new file mode 100644
index 0000000..ae45586
--- /dev/null
+++ b/server/database/migrations/20211222163547_password_reset_table.js
@@ -0,0 +1,22 @@
+/**
+ * Run the migrations
+ * @param {import('knex')} knex
+ * @returns
+ */
+exports.up = function (knex) {
+ return knex.schema.createTable('password_resets', (table) => {
+ table.increments('id').primary()
+ table.string('email').notNullable()
+ table.string('token').notNullable()
+ table.timestamp('created_at').defaultTo(knex.fn.now())
+ })
+}
+
+/**
+ * Roll back the migrations
+ * @param {import('knex')} knex
+ * @returns
+ */
+exports.down = function (knex) {
+ return knex.schema.dropTableIfExists('password_resets')
+}
diff --git a/server/database/seeds/create_applications.js b/server/database/seeds/create_applications.js
new file mode 100644
index 0000000..360f0d8
--- /dev/null
+++ b/server/database/seeds/create_applications.js
@@ -0,0 +1,13 @@
+exports.seed = function (knex) {
+ // Deletes ALL existing entries
+ return knex('table_name')
+ .del()
+ .then(function () {
+ // Inserts seed entries
+ return knex('table_name').insert([
+ { id: 1, colName: 'rowValue1' },
+ { id: 2, colName: 'rowValue2' },
+ { id: 3, colName: 'rowValue3' },
+ ])
+ })
+}
diff --git a/server/database/seeds/create_users.js b/server/database/seeds/create_users.js
new file mode 100644
index 0000000..360f0d8
--- /dev/null
+++ b/server/database/seeds/create_users.js
@@ -0,0 +1,13 @@
+exports.seed = function (knex) {
+ // Deletes ALL existing entries
+ return knex('table_name')
+ .del()
+ .then(function () {
+ // Inserts seed entries
+ return knex('table_name').insert([
+ { id: 1, colName: 'rowValue1' },
+ { id: 2, colName: 'rowValue2' },
+ { id: 3, colName: 'rowValue3' },
+ ])
+ })
+}
diff --git a/server/knexfile.js b/server/knexfile.js
new file mode 100644
index 0000000..a3350bf
--- /dev/null
+++ b/server/knexfile.js
@@ -0,0 +1,61 @@
+require('dotenv').config()
+module.exports = {
+ development: {
+ client: process.env.TEST_DB_CLIENT || 'mysql',
+ connection: {
+ host: process.env.DB_HOST || 'localhost',
+ database: process.env.DB_SCHEMA || 'my_db',
+ user: process.env.DB_USER || 'username',
+ password: process.env.DB_PASSWORD,
+ },
+ migrations: {
+ tableName: 'migrations',
+ directory: './backend/database/migrations',
+ },
+ seeds: {
+ directory: './backend/database/seeds',
+ },
+ },
+
+ staging: {
+ client: process.env.DB_CLIENT || 'mysql',
+ connection: {
+ host: process.env.DB_HOST || 'localhost',
+ database: process.env.DB_SCHEMA || 'my_db',
+ user: process.env.DB_USER || 'username',
+ password: process.env.DB_PASSWORD,
+ },
+ pool: {
+ min: 2,
+ max: 10,
+ },
+ migrations: {
+ tableName: 'migrations',
+ directory: './backend/database/migrations',
+ },
+ seeds: {
+ directory: './backend/database/seeds',
+ },
+ },
+
+ production: {
+ client: process.env.DB_CLIENT || 'mysql',
+ connection: {
+ host: process.env.DB_HOST || 'localhost',
+ database: process.env.DB_SCHEMA || 'my_db',
+ user: process.env.DB_USER || 'username',
+ password: process.env.DB_PASSWORD,
+ },
+ pool: {
+ min: 2,
+ max: 10,
+ },
+ migrations: {
+ tableName: 'migrations',
+ directory: './backend/database/migrations',
+ },
+ seeds: {
+ directory: './backend/database/seeds',
+ },
+ },
+}
diff --git a/server/middleware/logger.ts b/server/middleware/logger.ts
new file mode 100644
index 0000000..8024031
--- /dev/null
+++ b/server/middleware/logger.ts
@@ -0,0 +1,4 @@
+export default defineEventHandler((event) => {
+ const url = event.node.req.url; // Get original URL
+ console.log('New request: ' + url);
+});
diff --git a/server/tsconfig.json b/server/tsconfig.json
new file mode 100644
index 0000000..b9ed69c
--- /dev/null
+++ b/server/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../.nuxt/tsconfig.server.json"
+}
diff --git a/server/utils/applicationController.legacy.js b/server/utils/applicationController.legacy.js
new file mode 100644
index 0000000..37ae6e0
--- /dev/null
+++ b/server/utils/applicationController.legacy.js
@@ -0,0 +1,95 @@
+/* eslint-disable camelcase */
+const { DB } = require('mevn-orm')
+const Application = require('../models/application')
+const Controller = require('./controller')
+
+class ApplicationController extends Controller {
+ /**
+ * Show all the applications. (15 per page)
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ * @param {import('express').NextFunction} next
+ */
+ async index(req, res, next) {
+ try {
+ const apps = await DB('applications').limit(15)
+ res.json(apps)
+ } catch (error) {
+ next(error)
+ }
+ }
+
+ /**
+ * Create a new event
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ * @param {import('express').NextFunction} next
+ */
+ async create(req, res, next) {
+ const {
+ name,
+ full_name,
+ description,
+ clone_url,
+ deploy_branch,
+ deploy_directory,
+ deploy_script,
+ } = req.body
+ try {
+ const exists = await DB('applications')
+ .where('name', name)
+ .orWhere('full_name', full_name)
+ .first()
+ if (exists) {
+ return res
+ .status(422)
+ .json({ errors: { name: ['The application already exists'] } })
+ }
+ const app = await Application.create({
+ name,
+ full_name,
+ description,
+ clone_url,
+ deploy_branch,
+ deploy_directory,
+ deploy_script,
+ })
+
+ res.status(201).json(app)
+ } catch (error) {
+ next(error)
+ }
+ }
+
+ /**
+ * Show an application with a given ID
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ */
+ show(req, res) {}
+
+ /**
+ * Update information about a particular event
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ */
+ update(req, res) {}
+
+ /**
+ * Delete an application from the database
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ * @param {import('express').NextFunction} next
+ */
+ async delete(req, res, next) {
+ const { id } = req.params
+ try {
+ const response = await this._DB('applications').where({ id }).delete()
+ return res.json(response)
+ } catch (error) {
+ next(error)
+ }
+ }
+}
+
+module.exports = new ApplicationController()
diff --git a/server/utils/authController.legacy.js b/server/utils/authController.legacy.js
new file mode 100644
index 0000000..8f6aa30
--- /dev/null
+++ b/server/utils/authController.legacy.js
@@ -0,0 +1,77 @@
+const { compareSync } = require('bcrypt')
+const User = require('../models/user')
+const Controller = require('./controller')
+
+class AuthController extends Controller {
+ /**
+ * Attempt to authenticate a user with email and pass
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ * @param {import('express').NextFunction} next
+ */
+ async attempt(req, res, next) {
+ const failedResponse = {
+ errors: { email: ['These credentials do not match our records'] },
+ }
+ const { email, password } = req.body
+ try {
+ const user = await User.where({ email }).first()
+ if (!user) {
+ return res.status(422).json(failedResponse) // The mail does not match
+ }
+ const passwordMatches = compareSync(password, user.password)
+ if (passwordMatches) {
+ req.session.userId = user.id
+ delete user.password
+ return res.json(user) // Login successful
+ }
+ return res.status(422).json(failedResponse) // login failed
+ } catch (error) {
+ return res.status(500).json(error) // A server error occoured
+ }
+ }
+
+ /**
+ * Get the current authenticated user
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ * @param {import('express').NextFunction} next
+ */
+ async getUser(req, res, next) {
+ const failedResponse = {
+ message: 'You are not authenticated',
+ }
+
+ const { userId } = req.session
+ try {
+ const user = await User.find(userId)
+ if (!user) {
+ return res.status(401).json(failedResponse)
+ }
+ delete user.password
+ return res.json({ user })
+ } catch (error) {
+ next(error)
+ }
+ }
+
+ /**
+ * End a user's session
+ * @param {*} req
+ * @param {*} res
+ */
+ logout(req, res) {
+ const userId = req.session
+ if (!userId) {
+ return res.json({
+ message: 'You are not authenticated',
+ })
+ }
+ req.session = null
+ return res.json({
+ message: "You've been logged out",
+ })
+ }
+}
+
+module.exports = new AuthController()
diff --git a/server/utils/mail/mailer.js b/server/utils/mail/mailer.js
new file mode 100644
index 0000000..6227512
--- /dev/null
+++ b/server/utils/mail/mailer.js
@@ -0,0 +1,23 @@
+require('dotenv').config()
+const transport = require('nodemailer')
+const email = require('../config/config').email
+
+let auth
+if (email.auth.user && email.auth.password) {
+ auth = {
+ user: email.auth.user,
+ pass: email.auth.password,
+ }
+} else {
+ auth = false
+}
+
+const mailer = transport.createTransport({
+ host: '127.0.0.1',
+ port: email.port,
+ secure: email.secure,
+ auth,
+ from: email.from,
+})
+
+module.exports = mailer
diff --git a/server/utils/models/application.js b/server/utils/models/application.js
new file mode 100644
index 0000000..c50cbd1
--- /dev/null
+++ b/server/utils/models/application.js
@@ -0,0 +1,5 @@
+const { Model } = require('mevn-orm')
+
+class Application extends Model {}
+
+module.exports = Application
diff --git a/server/utils/models/user.js b/server/utils/models/user.js
new file mode 100644
index 0000000..a092da3
--- /dev/null
+++ b/server/utils/models/user.js
@@ -0,0 +1,16 @@
+const { Model } = require('mevn-orm')
+const { hashSync } = require('bcrypt')
+
+class User extends Model {
+ /**
+ * Register a new user
+ * @param {*} details
+ */
+ static register(details = []) {
+ const { password } = details
+ const hash = hashSync(password, 10)
+ details.password = hash
+ return this.create(details)
+ }
+}
+module.exports = User
diff --git a/server/utils/statsController.legacy.js b/server/utils/statsController.legacy.js
new file mode 100644
index 0000000..078be34
--- /dev/null
+++ b/server/utils/statsController.legacy.js
@@ -0,0 +1,90 @@
+const Controller = require('./controller')
+
+class Statscontroller extends Controller {
+ /**
+ * Get the data for the stats page
+ *
+ * @param {import('express').Request} req - the request object
+ * @param {import('express').Response} res - the response object
+ * @param {import('express').NextFunction} next - next callback
+ */
+ async getStats(req, res, next) {
+ try {
+ const applicationCount = await this._DB('applications').count(
+ 'id AS appCount'
+ )
+ const failedDeployments = await this._DB('deployments')
+ .where({
+ successful: false,
+ })
+ .count('id AS failedDeploymentCount')
+ const successfulDeployments = await this._DB('deployments')
+ .where({
+ successful: true,
+ })
+ .count('id AS successfulDeploymentCount')
+
+ const stats = {
+ applicationCount: applicationCount[0].appCount,
+ failedDeployments: failedDeployments[0].failedDeploymentCount,
+ successfulDeployments:
+ successfulDeployments[0].successfulDeploymentCount,
+ totalDeployments:
+ successfulDeployments[0].successfulDeploymentCount +
+ failedDeployments[0].failedDeploymentCount,
+ }
+
+ res.json(stats)
+ } catch (error) {
+ next(error)
+ }
+ }
+
+ /***
+ * Get the deployments list for the deployments page
+ * @param {import('express').Request} req - the request object
+ * @param {import('express').Response} res - the response object
+ * @param {import('express').NextFunction} next - next callback
+ * @returns {Promise}
+ */
+ async getDeployments(req, res, next) {
+ try {
+ const deployments = await this._DB('deployments')
+ .join('applications', 'applications.id', 'deployments.application_id')
+ .select('deployments.*', 'applications.name AS applicationName')
+ .orderBy('deployments.created_at', 'DESC')
+ .limit(15)
+
+ res.json(deployments)
+ } catch (error) {
+ next(error)
+ }
+ }
+
+ /***
+ * Get a single deployment by id
+ * @param {import('express').Request} req - the request object
+ * @param {import('express').Response} res - the response object
+ * @param {import('express').NextFunction} next - next callback
+ * @returns {Promise}
+ **/
+ async getDeployment(req, res, next) {
+ try {
+ const deployment = await this._DB.raw(
+ `SELECT deployments.log, deployments.created_at, deployments.successful, deployments.id, TIMEDIFF(deployments.updated_at, deployments.created_at) AS duration, applications.name AS applicationName FROM deployments JOIN applications ON applications.id = deployments.application_id WHERE deployments.id = ${req.params.id} limit 1`
+ )
+
+ if (!deployment[0]) {
+ res.status(404).json({
+ message: 'Deployment not found',
+ })
+ }
+
+ res.json(deployment[0][0])
+ } catch (error) {
+ next(error)
+ }
+ }
+}
+
+module.exports = new Statscontroller()
diff --git a/server/utils/webhookController.legacy.js b/server/utils/webhookController.legacy.js
new file mode 100644
index 0000000..e96fd7f
--- /dev/null
+++ b/server/utils/webhookController.legacy.js
@@ -0,0 +1,162 @@
+/* eslint-disable camelcase */
+const { execFile, exec } = require('child_process')
+const path = require('path')
+const Controller = require('./controller')
+
+class WebhookController extends Controller {
+ /**
+ * Handle the github webhook
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ * @param {import('express').NextFunction} next
+ */
+ async github(req, res, next) {
+ if (!req.body.repository) {
+ return res.status(400).jsonp('No repository provided')
+ }
+ const { full_name } = req.body.repository
+ if (!full_name) {
+ return res.status(400).jsonp('Repository name is missing')
+ }
+ res.status(201).json('') // Return 201 to Github
+ const app = await this._DB('applications')
+ .where({ full_name: req.body.repository.full_name })
+ .first()
+ if (app) {
+ const [deploymentId] = await this._DB('deployments').insert({
+ application_id: app.id,
+ log: '',
+ successful: 0,
+ })
+ const deploy = execFile(
+ path.resolve(__dirname, '../../../bin/deploy.sh'),
+ [app.deploy_directory, app.clone_url, app.deploy_branch]
+ )
+ // create a write stream and pipe the output to it
+ let output = ''
+ let errorOutput = ''
+ deploy.on('close', async (code) => {
+ if (code !== 0) {
+ return await this._DB('deployments')
+ .where({ id: deploymentId })
+ .update({
+ application_id: app.id,
+ log: errorOutput,
+ successful: 0,
+ updated_at: new Date(),
+ })
+ }
+ // run the application post deploy script
+ const postDeploy = exec(
+ `cd ${app.deploy_directory}${app.name} && ${app.deploy_script} && echo "$(date): Deployment successful"`,
+ (_err, output, warning) => {
+ if (warning) {
+ output += warning
+ }
+ }
+ )
+ postDeploy
+ .on('error', (err) => {
+ errorOutput += err
+ })
+ .on('message', (m) => {
+ output += m
+ })
+
+ postDeploy.stdout.on('data', (c) => {
+ output += c
+ })
+
+ postDeploy.stderr.on('data', (c) => {
+ errorOutput += c
+ })
+
+ postDeploy.on('close', async (code) => {
+ if (code !== 0) {
+ return await this._DB('deployments')
+ .where({ id: deploymentId })
+ .update({
+ application_id: app.id,
+ log: errorOutput,
+ successful: 0,
+ updated_at: new Date(),
+ })
+ }
+ return await this._DB('deployments')
+ .where({ id: deploymentId })
+ .update({
+ application_id: app.id,
+ log: output,
+ successful: 1,
+ updated_at: new Date(),
+ })
+ })
+ })
+
+ deploy.stdout.on('data', (c) => {
+ output += c
+ })
+
+ deploy.stderr.on('data', (c) => {
+ errorOutput += c
+ })
+
+ deploy.on('error', (err) => {
+ errorOutput += err
+ })
+
+ deploy.on('message', (m) => {
+ output += m
+ })
+ }
+ }
+
+ /**
+ * Handle the webhook and send the progress to socket.io connection
+ * Return a 201 status code to Github
+ * Save the log in the database
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ * @param {import('express').NextFunction} next
+ * @returns {Promise}
+ * @private
+ * @memberof WebhookController
+ * @static
+ * @method github
+ * @alias WebhookController.github
+ * @since 0.0.1
+ * @category webhook
+ * @description Handle the github webhook
+ * @example
+ */
+ async handle(req, res, next) {
+ const app = await this._DB('applications')
+ .where({ full_name: req.body.repository.full_name })
+ .first()
+ if (app) {
+ const deploy = exec(app.command, async (_err, output, warning) => {
+ await this._DB('deployments').insert({
+ application_id: app.id,
+ log: output,
+ successful: 1,
+ })
+ })
+ deploy
+ .on('error', (err) => {
+ // eslint-disable-next-line no-console
+ console.log(err.stack)
+ })
+ .on('message', (m) => {
+ // eslint-disable-next-line no-console
+ console.log(m)
+ })
+
+ deploy.stdout.on('data', (c) => {
+ this.io.emit('deployment', c)
+ })
+ }
+ res.status(201).json('') // Return 201 to Github
+ }
+}
+
+module.exports = new WebhookController()
diff --git a/stores/README.md b/stores/README.md
new file mode 100644
index 0000000..1972d27
--- /dev/null
+++ b/stores/README.md
@@ -0,0 +1,10 @@
+# STORE
+
+**This directory is not required, you can delete it if you don't want to use it.**
+
+This directory contains your Vuex Store files.
+Vuex Store option is implemented in the Nuxt.js framework.
+
+Creating a file in this directory automatically activates the option in the framework.
+
+More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).
diff --git a/stores/main.ts b/stores/main.ts
new file mode 100644
index 0000000..110f4c7
--- /dev/null
+++ b/stores/main.ts
@@ -0,0 +1,33 @@
+// Example Pinia store structure in stores/main.ts
+// You need to refactor your Vuex store logic (state, mutations, actions, getters) here.
+import { defineStore } from 'pinia';
+
+export const useMainStore = defineStore('main', {
+ state: () => ({
+ // exampleProperty: null,
+ // isAuthenticated: false,
+ // user: {},
+ }),
+ getters: {
+ // exampleGetter: (state) => state.exampleProperty,
+ // getIsAuthenticated: (state) => state.isAuthenticated,
+ // getUser: (state) => state.user,
+ },
+ actions: {
+ // setExampleProperty(value) {
+ // this.exampleProperty = value;
+ // },
+ // login(user) {
+ // this.isAuthenticated = true;
+ // this.user = user;
+ // },
+ // logout() {
+ // this.isAuthenticated = false;
+ // this.user = {};
+ // }
+ },
+});
+
+// Usage:
+// import { useMainStore } from '~/stores/main';
+// const mainStore = useMainStore();
diff --git a/tests/unit/example.test.ts b/tests/unit/example.test.ts
new file mode 100644
index 0000000..69f1ada
--- /dev/null
+++ b/tests/unit/example.test.ts
@@ -0,0 +1,14 @@
+import { describe, it, expect } from 'vitest'
+// import { mount } from '@vue/test-utils' // For component testing
+// import MyComponent from '~/components/MyComponent.vue'
+
+describe('Example Test Suite', () => {
+ it('should pass this basic test', () => {
+ expect(true).toBe(true)
+ })
+
+ // it('renders a component', () => {
+ // const wrapper = mount(MyComponent, { props: { msg: 'Hello Vitest' } })
+ // expect(wrapper.text()).toContain('Hello Vitest')
+ // })
+})
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..61c1031
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ // https://nuxt.com/docs/guide/concepts/typescript
+ "extends": "./.nuxt/tsconfig.json",
+ "exclude": [
+ "project_backup_20250606222332"
+ ]
+}
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..3cb39ee
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,20 @@
+import { defineConfig } from 'vitest/config'
+import vue from '@vitejs/plugin-vue'
+import { resolve } from 'pathe'
+
+export default defineConfig({
+ plugins: [vue()],
+ test: {
+ globals: true,
+ environment: 'jsdom', // Changed from happy-dom due to Node version incompatibility
+ coverage: {
+ provider: 'v8', // Changed from 'c8' due to TS error, c8 uses v8 reports
+ reporter: ['text', 'json', 'html'],
+ },
+ // setupFiles: ['tests/setup.ts'], // Optional setup file
+ alias: {
+ '~/': resolve(__dirname, '.'),
+ '@/': resolve(__dirname, '.'),
+ }
+ },
+})
From 2fe272ccad2d9e0e9c85ddf80a6ac1850c5487dd Mon Sep 17 00:00:00 2001
From: Stanley Masinde
Date: Thu, 17 Jul 2025 01:54:19 +0300
Subject: [PATCH 2/6] Update server/database/seeds/create_users.js
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
server/database/seeds/create_users.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/server/database/seeds/create_users.js b/server/database/seeds/create_users.js
index 360f0d8..677341e 100644
--- a/server/database/seeds/create_users.js
+++ b/server/database/seeds/create_users.js
@@ -1,10 +1,10 @@
exports.seed = function (knex) {
// Deletes ALL existing entries
- return knex('table_name')
+ return knex('users')
.del()
.then(function () {
// Inserts seed entries
- return knex('table_name').insert([
+ return knex('users').insert([
{ id: 1, colName: 'rowValue1' },
{ id: 2, colName: 'rowValue2' },
{ id: 3, colName: 'rowValue3' },
From 7929c1745a0060d17096b3e90b19d5741ab6bc73 Mon Sep 17 00:00:00 2001
From: Stanley Masinde
Date: Thu, 17 Jul 2025 01:54:39 +0300
Subject: [PATCH 3/6] Update server/utils/statsController.legacy.js
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
server/utils/statsController.legacy.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/server/utils/statsController.legacy.js b/server/utils/statsController.legacy.js
index 078be34..5464391 100644
--- a/server/utils/statsController.legacy.js
+++ b/server/utils/statsController.legacy.js
@@ -71,7 +71,8 @@ class Statscontroller extends Controller {
async getDeployment(req, res, next) {
try {
const deployment = await this._DB.raw(
- `SELECT deployments.log, deployments.created_at, deployments.successful, deployments.id, TIMEDIFF(deployments.updated_at, deployments.created_at) AS duration, applications.name AS applicationName FROM deployments JOIN applications ON applications.id = deployments.application_id WHERE deployments.id = ${req.params.id} limit 1`
+ `SELECT deployments.log, deployments.created_at, deployments.successful, deployments.id, TIMEDIFF(deployments.updated_at, deployments.created_at) AS duration, applications.name AS applicationName FROM deployments JOIN applications ON applications.id = deployments.application_id WHERE deployments.id = ? limit 1`,
+ [req.params.id]
)
if (!deployment[0]) {
From 12617a476e0b1ff489e1607364aab7bb04770ccd Mon Sep 17 00:00:00 2001
From: Stanley Masinde
Date: Thu, 17 Jul 2025 01:54:54 +0300
Subject: [PATCH 4/6] Update
project_backup_20250606222332/pages/password/new.vue
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
project_backup_20250606222332/pages/password/new.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/project_backup_20250606222332/pages/password/new.vue b/project_backup_20250606222332/pages/password/new.vue
index 398dfbd..0bf4f07 100644
--- a/project_backup_20250606222332/pages/password/new.vue
+++ b/project_backup_20250606222332/pages/password/new.vue
@@ -4,7 +4,7 @@
-
+
Reset password
Sign in
From 246d3af86875d85f6a9971b1a7e1460e8e7aca3a Mon Sep 17 00:00:00 2001
From: Stanley Masinde
Date: Thu, 17 Jul 2025 01:55:09 +0300
Subject: [PATCH 5/6] Update
project_backup_20250606222332/pages/home/applications.vue
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
project_backup_20250606222332/pages/home/applications.vue | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/project_backup_20250606222332/pages/home/applications.vue b/project_backup_20250606222332/pages/home/applications.vue
index 078a1b1..e209fad 100644
--- a/project_backup_20250606222332/pages/home/applications.vue
+++ b/project_backup_20250606222332/pages/home/applications.vue
@@ -224,6 +224,10 @@ export default {
name: null,
full_name: null,
command: null,
+ clone_url: null,
+ deploy_branch: null,
+ deploy_directory: null,
+ deploy_script: null,
},
}
},
From 63e6ecd8fc5ef6be7a731d2c3d91bde68bd7f668 Mon Sep 17 00:00:00 2001
From: Stanley Masinde
Date: Thu, 17 Jul 2025 01:55:33 +0300
Subject: [PATCH 6/6] Update
project_backup_20250606222332/assets/variables.scss
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
project_backup_20250606222332/assets/variables.scss | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/project_backup_20250606222332/assets/variables.scss b/project_backup_20250606222332/assets/variables.scss
index eb5424a..bb398ee 100644
--- a/project_backup_20250606222332/assets/variables.scss
+++ b/project_backup_20250606222332/assets/variables.scss
@@ -1,3 +1,3 @@
-$card-border-radius: 10px!important;
-$menu-content-border-radius: 50%!important;
+$card-border-radius: 10px;
+$menu-content-border-radius: 50%;
$btn-text-transform: 'capitalize';