From 431c6c080a50fe6d79d42ac0efcb4fe60c90668a Mon Sep 17 00:00:00 2001 From: Jonas_Jones <91549607+J-onasJones@users.noreply.github.com> Date: Sat, 16 Dec 2023 05:26:43 +0100 Subject: [PATCH 01/36] Fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69f8ec2..4e2f4ca 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ $ cargo run If you want to run the API in a production environment, you will need to set the following environment variables. - API_PORT -- API_IP = +- API_IP - LASTFM_API_KEY - LASTFM_API_SECRET From 69ea09b2233952d4fa8afa9f904a3ddc07b7f416 Mon Sep 17 00:00:00 2001 From: Jonas_Jones <91549607+J-onasJones@users.noreply.github.com> Date: Sat, 16 Dec 2023 05:50:38 +0100 Subject: [PATCH 02/36] Added favicon route --- src/favicon.png | Bin 0 -> 40194 bytes src/server.rs | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 src/favicon.png diff --git a/src/favicon.png b/src/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..43d3b655e48c364a480e621a1e7baccdfa91b4fb GIT binary patch literal 40194 zcmeFZ_dnI`A2@y-M^@pO$tJ6;LS~%GsARP4RYGNtY>t!4k(Cx9l%%rB9_Lg-*(qe6 z5X#=iJm2daIk+F6Kj8a$eBM9Y-KVbWbzRTvxz}}t8R%*6qdPzcfk5`1IjwO40)c^_ zFbK^a@INHa;SlgYTG!Ji9uSC>5b-|>pEPwZ2!tPUM&raKAItGx+LCLRZ^?{n9WY>m z!QI#&pQqAh)zmz~5XL<6^I%ZC;=%KeMxtA#@zwMDY zLQ5sb4`tw=``&NrZWjM~wXdxBo!;}x4*w+kF=lNLX289u+#t4vP;h0!qa zBZ49S`|&>{{NKX~vh1C9HLC?GBStzG-8l*!PnSG3+0Ak^=3v?w0e{Vj=_uX zmk7MLOOwcx6(m1U4^IwLlp1(-cU~ivBrquy6jZ@`_MG9M1q)gKf)+>c_9zSl!9z7N zt$d35sP()Dui39DGZduxilW_8TnjNsWRX#qg`V7rZpwBQeWEp)rB)zZBA2$B2)jXDNrwTbsH(ul!982xT@R!rki6N1B zPAK_+wZM+kK~Q_Bf<#xwJ(1Mw8RBadLc_w?>A#DWcS;9$tCO^DasG zJD%sq9M2;$AgUe4%g3|P(XO0@v7k?6{@HtEAk@NMT#1N->!t{bls~`Pe8FEHvzB;Y zTTenS2p7c8$5BPb1F++t%wQfAc(_I1$0!9;?;CR<;>(X*kXj^Qn-)D z+e~2{B8?h>6w1k7QU(M=%|eZ6QQjIonH8|$BlO(+pR33Z(A!MDsv@2Pp^I9RvE4kl zB#o+yvhNbR^1JBYR2ynA2B{nfiQrnII9^C97c-9_AS_CfrblgKf(72)D$s()D!fb% z)ky3(jlA`UcxpPD5?BhBUnd6qC6a!ed7W^&dNOk4#cCleo9EBuftfzj8}(0#GixMp z%)v*Zqc*{yTf1CsDMlkFHa#>t_hR3oSu9a>Q!Y}(LOhjozk!VrqR>?>-sS6sJj!3R z4=liD?Sg%2>(dr4#2z6sNrWy|7-GW(UniagrAxITwBZh6hVG*`mtNUWhg&%+Qd@`; zhxA~H6fIv6XF~uGJaj$k?#k~zRlK^Sk4ul4-GDE*og^KZT?Dr`_-?C191+x~@Vbi| z{hV&PXJN`rok!g3e5|NTPos!K2iRR%u=l9-I{>&XdDtl}XN3tx^|wD*e6GF zPg{Y#zJ5@A^Y||lwt;$e1Pp0?1*OnYV>Xa7Q`;zuQ)L7_jVIHc1fH_a5yi;1Pg!; z`x=Uj2tkZMm#>A_dM2gL6WPuB6|@Pm{||PQod_A-II~jn0k^{2r8=cgv7&;#<=@Ym z6cKf!%pMARNW>M^c@l0e;9f9n=-v^8hH57Hb9}iRPnseK^gMjuy=<^-N(nYKd_;O( z8S1-dIQ8$V!xu@%3lG+T)Es4AIRU8E1?>o*NACaiN3L+>MA|rp=L@f~_?1#{qG0R* zRU$<${zwZY@hjuv$r!L($WZ1h*SBgio#2T=%7?ola?~};-n9tRICtobj}Up2KXn~2_dhmCsI-$PsENrnZ(-l?!Lw1Q-L znU8HeEGq3yLYN_f!rIs+izv@nHYx__Nr2d-7(({veXsP1EJ8^u2H6YC;v`P)d>QPb zuDcgH);_^Vq7uzsffR_$o%~}YLOVbePMiwHmaXpxC;_gSr6^lLtL{0p0cSK8!Q08 zlhp@EJ7m#}?IuSv@K80n6-G!j6z!~CbNc*C}zWfab zHut5$FZRFZcYPMzar+rb9zpB79{4n0)4y#=P!gdBEW`#_C==68WFg|;i4X#)`2i|I zWE}30|Aq8Jf=H>n4O5~b+L{V}>;!pxm0V*0&*v3~=R2nY)CvOB(iCWs_8|{puM$~x zEa%og0R9cg?7-EI4gUx9CYkHlF&V^>a?w$cDLX6jTr(0k-+I@&@=Dne0VfCD}v z`B`u!O?L-FvGb=uav`3&%yB5JS6+02#yCgbl$~27-oNHxeX~tkn_v~6+uFD0NMsYn z>gX4}%u}0eE+^_*nbr(nt!}Oq%;t@;Ixu99Uh@bFd!XJNQ76sRWU?~%b4FH3T{OT& zJgCcjQTbfTn_wuS6&35{0bNd><`HCwE^(-S0XIrG!CwAZ?dW3v$EqMs!s0WuzE;51 zKh4X@m`%;XbY0uJd-ShlteSeyk)YvNYTuS*>ZJnHS^B#YdjeDjF2ebu@rKp+OysTA zVKfa|)(JnyYbRwUTHbJTB%`zWPo6V7G%8$CWWXV#v;7^3%Tn$j zlA@MG;mMJ8@0Q~R>sv7xblu6jcd@3h+03U$n%x3r)lM}5Sud;z@3gy!3K@`=j!?Yv zYAUNgB53`xMiT0u`)5lvdxM)1jX$kn>7hPE-DFbugeqKHbEuM50w%&%S8rk`PdyQ8 zJLX{>dd5nzT?j;9`v#kv@8kV!rqZp@8*0Y~l(k4+8qFnm$<2c+chIU&sumctxmF3E z>V7v1id52Gd6M^ZqOVyuazBFUm^TO zClso%`stBK{FI|EVRGHJTK@n+-@lZR+qV{n2{-t;bkkT5cZCPD$8-acJr%B$QkGpz z4`re0k!@eL4xM@)tED#;R?-dIW4P&o>e38Z_#|`3D13f9OQDi{aJmCtP|@bf5|Z_Z zg?8=Ai;j~|Ix18Xm&*?G#n7)8@{z>eVPX<50an#Jnh-RYM zTHx$>ow|#A+>&nortLgWKV&h3Jin$sDH%B3yh^F+^<604;}q>s!3!SlqcYS|36#y8 zfA6M0Z>8tvJ+9iiI>@$x#9R9G1kDSjhY)$k-~#{DP-1%4vO#&p2eXfL7CAaq8bQzJ zqbv0)^y4ToNt8Jm>t6h;Om*6Ss$>SP=7dXDDGakPZg^{`sr}saWVHlbLq|0Y-}0>K z+fDc8x({ZYKiB&~B`>`rY>CzbN9TbH55dsMD~g~hQ)QFqLP7Gu&?9J2#_(hD=L?mUhKB2==ZzCHb35N%RAUTW{We8+>k;zY zt0V$Pk+L^O>y&JO*PGSbG0UD%y%*1#n%iL)_^q^&Tr<(|v6xI#em*$92`!rKr^EGL z0teTZj83X*K4l%+`A2d%&t{do5NoA5H9N}~U2oAVGr^mIIXrM9qq~9RRw1mFQ5A=s zKG)7QWONZ4{@nZ2zK>S%$B*K`IX}J|z8GYCi-WOII!oQyx)o3BYygi=KS6*y@U2pB zzj`1Mwvz<^W6=lk`a^iF4M`*OztrC`V>_@$IgSvWG!3K|%=U$=Mcy{Yy2QKv>e|u2|7^ zI`RTrU{e`;t|a?M7fXq~zeT$Z@DGMQ5S~-tI3pC!R2M>s*66tjkL0*M_aSCbMQ}Fg z06P>`B$z7+{5wP82MW)>5Gq^>-eqN0u%kX*O;y#MJ>~GT^H?fdr-Pf{YuOn|+KceM^1QOC&}e`=lb0g_^})mo{Dpcilu#9A-GG8vl3)4R-J?2u(5hvt2E`3KxrA6sF{nh1 zRfZ6|!4=*a+-TQzSazQ0G;+0d{*9$}{g;aa1QCB!=|D#ycGR!BJCySuO))vT3IP1lZEf)J4;zJ z5npcl&%WujuKnSBEf9GBy!(|#+Uz7I=oImMwVl19Wp0=5$WqiC;q^NLB}O;aOrBTX z6W36X^PT(5rCFgOIQ7~3h7RsYYwuXm58F!NTqe|7fR}rIn^!vl#rc1b2`KSM2AA!xLpsM~^hs%Yf1-}FHdyF4dL}kiWEfwk3 z4&i+oOUz$aE@>#NaZQc=r9AR{_Iv_6Gz|nl27Y*93>)hCK>wWST)^Jvwu#VUv@;J= zO!|U_a+B2GA9hdz9Eni_$v6@A61z;_tJSe0eIM_Kvrjh?UTRw}FS;DSRC!dJ2}&LC zHd=@xD!zNnfWvHr0%Y6oo!AhaZQMvSE3#?_vW=z7Zt>8QMo7Imj{Hh~!G< znw_%U%=pGOHVyS*GKghqDLAbz0=#SIOgl6Z| z48Jqho=}A--u1pwzqC^c^!Q;+mQqaeTwexFOMA+niE-F$$TQ&b*B|;Y6<&Aolf(pV zg>2bcE}Fi3%x~#)FWCn+_B2kNL4Oe7Q70vA7ah|l=A)%1n(Lj%ddEX+TK1sn?DQ~ z8y{PX_D~}qCtsH%)OJUyn;2^;Yv;bTKVouiMDh`F)GsRNbu5GS65N#*@gxQqSlF?k zIV3D88#X(vwiK>do{`z5Gu$#dD=O6E@VYXuZP1Nwh!f&%cIZt+LFg^N-wA|`8f5}z zr4enl7#kl!V}2e>Ei3!-9ROwI?H7iP)dUJ(ac#AlR-PaZg(BaMStAv^RS)EPsSZl@ z^72x7LuS@1?zLih%SVgH_|_Bc;bqsHE*u$?Kb!(-8E^T6XNCIS_u*aZx49qK>DdEa zKCyiKK7^j@6^C}cf|>Ey0g;v*td3wI*Fr+ysI14d`iemOyAbdDS#$SV?n5pf>GBze zN>S`zrTNpJNj2-{w%l3LAHNVa;qn;jd)6vy!6bFuBKf&{2SKg&vf&^cKQ(%mT}gVs ze(c%#>JP}UD;t*|TA@qqtDDDONwl2>xrR4#UG9w`gkmMUv^;$FaaFX&xZ$C(&%)(M zx%GFS<#TMI!Z2q(la0WzbG46Ng%D;UJ~JIJb&{KXfp%bMjXOrOR}y4gUVJVr7DH8t zC^2r#4ZA+|gPJsAjH;Xerl}d%_%`2d)>2$R%DC@iFPTCkx-2`NdoDuf2T6J0^x=gPB9E@6Ss;=wHKp{(zkN zq0)lL(V_SzRnzPhZP8nZyx2JOJ}M}SRHB<=>6sG zUhQP_BUW?m*HD7-BM1rwtKytkGps&CS=ez=wp{C|wsz0k^jAYiIZ!#(mPnz2h|=+1 zJ=z8nK+h(um--tZJs9x;R7lagyml%z%sgs&zMX|Lh2UGGQB`AZDhnwRp~*atdz{uW z8rxA4__=ZR9@00`+j7iFKqd@pWRoTV#gEH`J-wwR404(0_k}i{6L`>nwo2;b2LdZv zQ=#$V@i`NWZ(y?R7_=kID$wx{_BIOzbdasY(sN=E?kf^(Yl{keTEV@R<7gXMPc<8K zvQ}$|5XlbJ1=$Wcd2{7*HdMf#WiBD!6KL!Ks#%7vWxnN8?InRz2|T>C!@rrc!zTh? z{q6`ySk?z!s($2u=DMEmkbk$mkm^}CJ?!(TMQok)6zv23L4a_Ol|Vq38wp&3$7uG* z#!<%Bs9CbUQ1tsruV`h}99iPCr^qt3FTDZvxdG)}J=QT;pS|{Ch;~Mw!^dUr=aoGk zIJ3OyF^2h4BU#vMc2~yWx3m-1C@63s_^PmK*UR)#OB$xjzou&gUn`t9RJdQ5U*a_a zn3|aaYQv7d;yHN)?t25VU^@cA$I>M8ylcMmb2wQ6SD{_~wxBnw5*}9>>{4pyax?l9 zkpsupsnzy+eK4HlPy&=&q8m;kNUxpH=tDGoKz^X*|1Cw8pR&}79?=>~b%ifsDR3z3K_hrrf7Ie zUH0|jMIpcSVF?%O`FL~%^~oPco)+ij9HZ{z%~`4(CiE|UG^EPNe2bqI*?H@5i$AqNFc$;7N@t!3KFrBV*}& zsoVTIpWy5#!r9W5<3b)O1SMC)3XU}-{CI$auffpFYZ`mG1$CF&jlTKbxX|UeEA*)I zv_5p$H}_827YRO~HX$6C!O+0>KSLx9$9g|%;<}KemS5%6_dEcjE+ObLy;O&u(CV}5 z6+&41paLZN#sn>y-PWcX^?zLLdq4jNuC0X+P+?1pv=@7jlPV+_B=;$>iw*S_R&he_ zv;80i5$?-y->$;DSYs-wh^R_gLHf(SUvC7>$vyoYJX)73Gz+)(k4(D=Qoz_Zz&ZY* zuQ9aGyPtVxE@siIc+`kpE}1o^f6a_0;yiA(^{oVSs8UP<#x4cY6Vn1MC|j6&(C`Ps z)J4IT2e672{8ldj_DpL>v1Wy+U~*~T3^uTNm4%zxZ3$hmeA$xP0Yso!Q>xj_;8daf z{nH=N9_?kRoKD`6oy~!QvsiWHq)h61*FKal-5S4Fl4kH)gtb7f0f^e~&N>8eq7oBw z&^}OMP;VG&T!;$C8&``8RUL5ai4@|#y}*8Su2PZ_KX(ZkE7Qo?J2@igdUbrk8Gaw)@vE@y3`p^bJjw5S z09BH<;*Ck4H=&t7_@FtZiHj!axS&35g9}VAO3_KYDJt2w1}iNzI~%+9`f#j+=6DsW zLS*u)_INR?LPT<3y??y0yFg16P=81@!Q{Xs1yx|jB3K0TjdSCL`)g-K?kI#Q$_(Jr zN}Seu+~igArBHDC#QlNwR|7ZRGZ~|U86)Yhbc`N_Dv1)r2fjSsOhuwZfFUA0Mpr2P z<%FAjZ-8D`bK;MEE;Q?&w)X6E<8i@ku3w%TBB5sKM&b|bA=lvi^tSBK5PFcV)yw_> zXr@*rF%T`jU0oZ!#Gg#}U9C%D7H@iCAp~wkbKWrN?nHVf@-cwl$k(c@3ZUWP8m}=WwNT;fBY%QbE*>SUF zfRxatqB_P-!5|e^{&?gdFo;SYx+Zd{Hsm(uy;V0^gh&7;<3|YK?ku5QGH*C=!8N4irPTdlm^)Q7d{CQ2=`@b@9V?< z5Q{Vcj1r9CK}l=93)N1v z5Vd{QbOEaiv44BRO58OSoyxP8uy>odc2cehor7G0+_jB~MKuh?Suw9AsF^#Zx_tGk zDJTomN#&GH0Diz0qwh#ZDi)az%s#gS#Pd7lUt*3+9*AayZDwIe=f|2;%^nSI*5p?2 zFdVN?0l~?r^J$K!E$gYjQ`y_g?rql)zBAX=Qu1>DdL3L?6Zp=N4f1-RVCEr~cV^B3 z?$5kU8ichBicv!c!=&F13 ztu2UT->do!j`|yN_p3x@k5X-8-5qMLS)&lHSZY4ZY*`3OjfT2*A-XWH`|23!fdimm zvW0Y@39t;UH`VU1O48r3NY$nF3A%RilYCi|n$4d^J5QcAf$iM!T^KKOXZMDF2S$}Ws@qq|5ik`(gcY5Jyd{Ic*-6B7IU6f zk<$9or`B`!8{a=5Wo^}5}$#n#j|WfAjcVO1shE#raz%A&TpTWUq%_# ztQqaBUS|2HMGVxDW$s_O{*Fc8eY*d<&mV~`6e1*1rv!V2Zl+0ep8-+2%-hEc7pzy& zQ`$Wc>a96o$5b}F_565W>csOX$#Y;!`c#+vU#p6E7xx~|_$^J8@)UbqsJGIAxE2sS zk@NXYS}5(8WD9x)!YmmZPGHM6e@phh5!-JDA z`}S=HN8L_S8w?xWJgUdZ^(Zk>*Md4c&fP@E$Ap3k!gB2bMLI$MknN6UjX@@#?}}wF zIBY;Li#67|+Oys%T;}bkp+<$>!RiPusHld5%Qr2f1$P?2Y{|h8IB-H(4rvn=`7SJk}ZfgMqP5pjQ7IRA)}MJ-GB4@nsfl7=N?YjV?vejQ}!~p zWRL+YE>NFT;>p87?M|fTEVS6{1{H>k8i48V(9no7Z)JrzP=K`l-gxZ2OTqCDw;kMj z0c-C!hchC6|M4uePr{IS)_;3=kbU}DP|u*O@f0g$PpWg#i@^Aa(kQYHW8!~6te<|! zfvHkqH9+G0=1hHo5bOO0mhB}b=n@ZDV4TZ)JLXJi6>}4wTuWa4qH-@@C@?B3lZ>{% zH0GigdeijEj`qd}hp2eiD4~we>|o=TyV%0=x|7Tofdx7DxnsS?XiDuAEwQG)$#!o9 zay=sDY)cb^p~1^}q(4C|{vYe6Fj6E>tnROUn{NP#yPvZfS$W1&y_)&p*2iLkxPrhK zl-HhMlx0S+LRjI~pJb^G#+JYWiugKwBwi^rL5ykqWg{oQpVPnfcx!Q>^nb&8D6wnP zXax^z6I3{U7udngixztaJt%dtCcUoers<-hQ>;V4~aM00zo+!{Tuw*z_^XN zKpMPbf32e4lel;oug*BkEf>|S6zJE zdk|3gk=PH5k=mHDvsZd&VwJX5LGVG<(Q;CgLTo1Igg}Ki(lXKsVhA*?Uy*u zJ|MFl!V0SPX-PimqWvHS|t&{7%n?P*unY|<{l1O@Urge;7we*4JQ%m+@;P9MR zy{a+>g)q`veKP;pMbPzq2*L86yO%H6tttOw(niL~hwfwv;cr0&w-HqKSDT~*zA_14 zti+6I`-y60PO0er+;)GjoAT?gVw|g&b?slw$#MWA*Knr1@7I@@-h{Fy19vi9ur3ZC zkTP1NcH`v)g2DSPaB42)^weSauwMV51lc`99iy5ZM*3^7l{8L{EvOO=lYXz-(t6?P zvGu^$B8Rte-$-9k^8q<$qgV_J2v$J3 zxbX{zc6lL?D-Y~N9G3%|qR6p;W>bYC9`ddfP+7J*6@m&rS^Nf4#bXZwX`_5i z%Qx`68r*tSfa?dbJ!wk|8ZqHD*PQy%vL#H$de&yDu-8m=3UNGwlggjf9CtJ_Vm+by z;f?BQy;6=ACxCTe)AFrSvZKJ&!+nnh|EnsK9H=V*e?6g^=E{x*rVi(^$04TNg>DPW zw?~_%w1Bul3pXw-EbK z>S1#IMnacOnoa`qU10D&nzd6(Fh`|~l41Kauwf}z&-bp^s_&H+zd~&$*uI`W=>BQ+ zGl$^feoYt8+hm+0lA)rSrp4i|#C)r_vq>p6w&%Leq@pyH&$T`>v@I8#P|j5y0AtN_XJWVrIJ8D85FP-yy4^ z*BqTb)5w|m@_cI05G4+YqdG++2HpE2bKe%`QAw~q?k__M1cnX&13#!nfQH9Wh<&dR zM7;R$^w1W&VJ|^lBC_}famYibxLi(vi01wN3kbONX(uPLJiiy4PHz3v9|mdy;i-TT ztZawY+bTC;QuiP8^3FtUTzCZES`GqIC6*IlF?FsSe%xNj#cObA6U_Kd*)cNYH4@X( zt)-$`L-w}xs8SG9Hr)N8C(2|1i-UYQM3w?(j+t)D5SHx!OeY4BXtx@Rr5A(7?IVM88D_~Hrj~j(f0jEMxKKVd zC-tSu2mf>nIm6xfnYK_)Y|8`*Kv|UrowNIBN~jT(A(t}4Cut2Fv1z0(JFuSE!D()4 z!d)%@J8zXKt?z`q)M4`g3>eoG1Q_Se%bCmkHx zUeimO$a?YuxwH=3-_0&qz1zgmwXa*058f;+@B}L4{xWNg1wq6haovD;LQ3?199$% zE{jz*0x@gK8tZj-1U%*Ei?%J77GVVC&^#6$;bUWDa^|%T;2){b!gCk6!XXOL5sVU# z3N^O*5o8Ss(P*D;W93(Tu%|4*Cs#M+s1wAh2s#(BW{sp0$Xwt=W6s?h@Hm8UY>$q# zG;t=2A5)zm!q8$VOz}5PaJb4&Cr}-KU+*JbV!(D zN8mU|ER6Su`+QRMa!!T~i|00~dOP>S*!goU-Y@r|`;8IZ@ni!$YQ~T8{HncPSu`bQ z+mV62?5BIq5_H#SNic@t{Y)sr$mDO(!3i^LjA%MwPSF6{3VISHPU31>M@NLR3(+-& zWap~oIU>o{@0vy*^6SZzGXPN{p}Og!tabH}KE7X;94xm@h=pGg3arDS{b9(;Q`OG` zXh7Ek_Ra6swxuVqAg04Zx~5UaH#B#I9Jy>Lt7^;$hmI(VU`rWbK2yFE>sDv@r##%w zRsTAiHM&H`Dy)cqx(;8g@KB&71TY0-s#xd~m^16)0O6}0PYmaGgdqtu6+#_6MiqJM}H; z{J0MfBTl7g1D9c5@twP=9&Qb9Yq|2U*}ZT}@<2brC4S5T^0Y>f-Fv1{Jz(Q| z_r1L%!i29i9+{_YFCgEu0D*g~W|`i4j{I<1{;fSkeKGzJt2ra<0`q~98cH`$q65W5 zBu7UmM$@1yfgjZ}R`=CG7!_MfHaZZU0W$Q`t$7jSAcdzU;Pw+&REys zfg-srF9r6M-jN1Xt5ss+x%{?80Zd!rwfZS3{lK1Lhe@qar>P1@A*O({Tp69a_LK$h zzBYRpyOknKq;9BcF;L;7TB7{`|ZwT^W${; zS+_D#-&srezin6(zX#RQlz}((z8(}VE5opVP=F>Pd!Y%d_H!2PY(TFaiYiM~;J{y9 zJJq-Lc}x588|dI{3co#Zx&^@eD~s3=cXg{4H6WAmF$>M=zX`GNFQa1Y>QXR#0-#YH${Jr*3g1?dVfSU$FS>MF{pG*68&1(#&|% zw~oR({6Ke~qlCtSzJi!&)aKm{5_*^vtF+?t54x6ui1;T0cNC=~q<3d~c`Y2Z?a?P< zC}EJrxQN33hWhfvZQIv-SVo6e;yO@Z4I#i9N^vOeBlS}gw?}ZIk{^G$^b$s_<({Jg za}pVdHxo!=m=b~IB(4iq1t$VJqK4d042GWV90DnWS6j&V4p+qJy*nxlnxb_e4X~|! zH@O9+4CQD)bD3xs8^GlOdh^@lHHoIPl1`B_r6cA^hbhd>IjS*|7(1xFx74De5+~Be+{C&cpFXAG z!xP7|h5IgOa`ZgoTV4KDI%2Db%vblGcej6QASATyTE zV?n>>k#l6(uujF=oM##ZVMEFi{AoY9V8xE<SjvjbLRoa2WTitx1_$o8uxFX`AN&hmf35l+i}@-w!0ra4)%Z!1e$~l^FEUGu4$B^OgpC zZC=PBG6_gT^=l~?PaYQjc?l$=-zff-cdrV3wS4=)+piC|bh&jC)K?ovxL;C}*q!GO z^#IpK;tuCLmp*Mhb$#hMyj%KBU?VxzH!qrtd^PWtd}NiY^DL)0 z*%yT@wsV4e#auPsKZ%a@;+E$xdu=CbJ!Rl(Vn z1U-*Oh9kgJc+TkhV!kNSyr0R<_(ftGIVF<2AEh2`ms}S2y6~*^1eoYhT=3fmxQK)b z@p{VIHsRxBqcCcYn-apLdxL9Y$^bv3jtKhK1s8|bveBXJ4HjR?JBPD$io^$|iNe&gXq;qxVrzPP?mb~6k|BR3{829NX2PW+vHL9#3j}KTj z+NBJVkN5;O+wj9)YsfYu)bOl%_8`#q``jK(*kcrqn z?&#=bY4r5|CrMgHQvbjKckC1*=KFbPo}eqL<)v9bS^N?xwiq}^I%)D_W(cXEay9hl zyaNBWVoCwG6ohiXY(CDo=wg8Xe|2=H6V^HS;t?qxJRy}5=|T1^!0kgG!Hy-(0I7j6 zc!${I@=F{CL9Yfs{$r!1ynFM-q&}XQ^c8i{ft9<*Uy-vu24*;~n2p1NdXaUqltDrt ze^V5WRQKdtsTUvbK7y*Ja^^->j9eJfUB9HzxFb~ttQua^B*?_;szwqIXyl02N~8_P zN&tt)?!|S!1iGPxwN-8I{cas;Asc~ka()H`GI?0byx~W&0|WTwpJ+zSp+L$iZnv&# z|9nA|F8I9W2+!=~Qb$DR5y9GPBW|7!8I_$*Z<9OjCzorROA9jMWf!A5y@u91c2vu< zXohA!T+zwPYxXV0E(y+xbct#u!ol77WpGOASC-n)EU^RBGjn5b ztMEGid%roEo^k>^-azAC(EDo4Nr)l3t_?SLx3oSWKA+6uM4pj5477!>+8))xrUY9z zy|wi@cya{Zr#cUAB9oiB+5BkMDWM;N^wH=WlG1N2T^aXs(kC-B_#q~md+}PL)jPYP zhtvcUn9taS5~YDd>35*mLm!Tr1$N+9LxgzQ+=^+UFDEv&weS42Gaiv7>6)B|55+Gx zp*ysP+L+A`$$Y4A!zxpJV>kR_?@=Wr=u$P41J_o>TXDV0BKrv7NA4b!#2!__fl)M{ zW`)?*X8G~-?;@bW!suo^fAqF!bLSJjtWkRTK~Ka zw8z&rLB7T%(7i(WyIgQGcUGi*%bg}69fhKrrSB4z;3;3fxYm%KZE_4`Ks#4FJ4J#P zTF@?96%vBUE-qxnNc| z#VRcOYig*qPnoj3H3{3&I3y3ml(kb-dHoN%|3=8bhi4JM-&cJzV<iVcJ2kqo7{@g<-N}Gv*OSry_u6KD)GFTyt5pK(usx1z1@%8R}r2vAuLTpN- zgqJh6^-O175`&B%$earAMEY1tlC3i76sN!~bgSt5b@jF7W`5F;ab}^#59o^5pU6)3 zCP59EOFS~CyU-P=03)4ds^I5WU-fahoi-8@+0ixvV2vxr3cW-Qx3#zNfSuCl;(quw4c zUhNR=^ErtQ;#*7b*4o`uKs>+`td5PgLeh^W_QGT^iy+E0w->Veg$t&*G(=EqVr`>!rcUfga)C3*L?V)? zTE_G28L5ItY9QP)jWk`P2L-kj!#RG}RpXH>ESU;lFPln3dJbJ8I6lr--hH~`bQLlW zyQ)4{gBcYETRMtqazM8ziJ>230aMD z>pS@o)FT=1doJD-Ph+QtgzlN{H9gtBOKF2VmUVaC=n+ZVA6F@G(Pq&94jb|}L$BN2 z$b#f**QveYwBXsfJOq_Q<;?SCqWA3WEH(%)TdvCc+ZoBbPhW`*G5eBR2*swFp?>IZ z`9vhh#ROU!Y$1|tkj1dK9;d_p2%XtAd|eaA(Aqjy)bjHw9c0Oc;u7C(tUX5X8ugdn zjW9a|;r!Y!Tbdd5pLswEezMt5&_!|&z%X~WD970%-?Mb%SVZMn9 z)P%O@n3vp&XnUdi9;}7E{Gr7i-EKtfW+wsi)Wz{LYW7v-U<>bf-(j!QrjyS$+6GRA9fzvAmDn(f4{T?pod5&Hczj=faKQ3Q7n)%(VNGXpf$hM zz%0oM@#6VEt;$m?(Rz}?I`jzTUoP@)q#wr2C0%jCz%5c${*NM(q%DD2-*4>_DY8)i zym)LE=TSgU5u32YZcOYDH6rP#=p4(jMMjQgcXc3!=RSg43%e#@XF()l2W~3w{C6>a zkYllakoF%6l2Thc=kXgLfD`=hS&|(CTTdBm>THrogVz=is1r{GcW*?>BJF|)L42&3m^cGHN&oX#4biU2-O~w`#bgpGeC`ydzWISfKmY;lWuQ5>n>>l@B}Mu!$+bS z9mv75?!P9!OK>2hEa5gs5D&l#9ybt^sk=a~d~EKx^-9Awp#MYUrZA8&f?A*DlGD+w@Gg#9TL)N4(>nJidTS^k_2i{wki19W}tcZ_^u-IZSL^wpK(r z-E+99D``uRvS@pH40w0gy|AMGpjFQC&r&sFt}~xGa?0l~5I#q^Ili7~yPWO*?GfkXyFbIF%C1LPw21=RIkNg(UHf&}=Jzya&{$gw1A5`^V()TTD&7wo1!?t1 z^w+M`I(WNFI}o`5!2_B}-++APE?Gl;1gv+L+UoBv)-%usve0B)zoY32@CqGBXR`7J zyxncYcmR_t+enh$Wg&B`m;{hIpXoGmphjZPTY!82Miidg#l0!HK%9Pkh*jPtPTzs= z{6Ap-huQysvVnCKlxw(r_0OW(A`~%Tan#~|k;rZZ972<*e)_P3;)AM8Om9g&uUG{AA-iW8z0` zn9DAyl4rM-ME#*s<0!AI@6fJ%e*vN>&x$#E^UQPN<4j~|!0SuNjsi2lJ-o4;#Ew`X zzGb$pLBG-lyd(Lp73EY0fx9d#>H;)Yt4Oz}Bq-tDJ>gZqfNI?Xe+#ln0HE&Dj}r|A z@A{~APC1B6-mT_5pGfFm8!j7+QgNP8$rUE;V*DH{dUc^Z)A`Zo%*vo6joEL1RM6s( znyL=EX5E`Pe^$26TOAf1m9tNBBp$6}*1Iv{kjI=(VPGF?R;UY?rnJbYhJzK=n)cAp z4G7EL%*!NK3cP-}kLy@w`SeHn`e1X{4^+19ptJ~1CSlGWt>0fYZF#N})lLWLkE&2R zcE#Cn7fvXkbFBEO&Sl1tpIC{dUQ-Hw59G20#d!EG+z@X|W;ygY3nUa8Llh6a3d&`Y zjQkJ&>p|n7kB-iMNp^@71CHbUoy?qVrz0Gd_H@G>A zOUtrtyGg;XU+8nip>t(t`T*RorwKTckJoT`_TCh7dL-Z=&fAo-V|VOz-4Zut){ycW z3S!CWME2GN;%i{rV8pX2D*0`^X7}%Rr{J$-S?`tnI3~Mu0*##bv{CCAz1g_$7*((w z@@BP?F8+tN~aD5?%V0i(78_BWNaM9gm?J$3x&A>|)B6#N%A zZdUVU3VLV!2mfbaNyVNGN?*!SEz;AW2tka%K(cm5md*C*iA0FUa=iQ`?VQzS_SeOQ zD!5_h20VH6itEr$IiY~kvneW=B0V)Mp)G6QY$*7thp)iZor64gNd@tlHT>N5HI?vj ztVH)D$o;7KP9q8E0=qC?-dC`5~t;Im3wT303|Hn6f0N>atcyvTb zNk+`0y}QWSV9MXjk3>`x8vJT)I<4+P5y}{$fg|uH*#1wMZPYthgC;_sKbnK@Kuqi- z@6t=+kRe>9_`oFG-c#w}L*VXhvk|J0&QTSw?w6%<{Yv>+s$lMDeeIqj1OAeGqjpEU z*dD5;F!o7~naR@P_qaXp*dPyA@o9qCbauxa-<>-%8E0ajwXRhPs#SD4?``t~^6_;; zZXgLxIJ$T9sWtNU*4Ln-TFBX>Ge@l$j3gmL74_Q6ssaNi;ObmIHNNxgS~V^iTYI&` z8;$?A7Qeqg=`aW6Sd^BU0InTg8|mURzg=e}p3P1yS732L0gi#Yb?sg1uFhtKNO8U9 z)8dBDjH3f4Q^{QqBv4kE>tr!}+%p{T*!d@2*M0(PIQf_bM+INitOz=)=CC`kri8kL zsqyZQu@8>50Eti4c>)Vzz8vnWh3)v-w3l3IAu6+!KopDM-Bk@$iI+X%gapQ9D7Ia6 zUVG4}IMTJ(a3E1%1JiOj`!XiC7A0|k$q>?C)gaX*4T`sb{r z^Kv9%P$)3XhcX3j zPzB4UI2E1g6UB|Dfp)lZEpA@7Vsu3q|KOj4b0+?=5`)SM?}Wc5`5 zX?gf#;K3T@3oGQLoWU9X=5tMh(N+@c++zwu_L>AxAdwzO*KsplQxzYmCRU-uH=ZgH zdtE7R-L)8|mvuSU`l(U8C`>h<=7B;_-OCzXA!kmv476(ZmF{1@h1*$y6dhEBz;YP8 zKMFrAqCOYA+<>m2ueuAaKZ2V&b#hSOf=E?1(ZTS939+eA*ILjnc)x=0_6}Y~eW42C z&InQzOxEx^rx9f1ERbslLL>RMpRQEkHIb85CbmNlm()tUt8D~l;rDlDcCsa=`)BlX z!s`-xe;2Q^LuFZ9*B2YaCazPVSO%7I&~*udyi79#=wyQ(7`E>FODiXRjsas zS2#Q@0DLRP%d6&~`42e_!1u;d3aOQU+Y_|yTBe*rRYWYlfctBy9vP_T85T=}g91>* zbs0J@VHdQ|CBTGB5p`|PcVyE2IPfKtY?TTe5_8SqcBJxEvSl^&pH zaki1)W_3y0A&&{9iePg5!c%m`+oHQzYkO9Wlk8`jYf4v$_6hvtJGvbvqLyGEQ1Jh+z3+@`YU%n7 zA!ra4LRYFpibz$OfDolA9hBaph#(yll%5$ zG+x~UX+$!%c9r?OnL#20<(>ro(7Xcn=I`JU{hxCY4a29#R1U3}nUJOK=K+63!$ud? z;rW5{XeluF>>~%b%+8HQtZ%!ii!e^ccR^Y6d~ z3h_&iUVzpEFD^GaQKy8Vf)im(kd4JCb05v(W$T@xU_r+(H zQ`$E`G~U??KKCK1l4Nj!@1sPj)LZ4f<6IX zth0Z_M(t=>(x@*5VxrR># zvHGlRKGy&l28CLKDu*7V4{0se6otKF0O%l6ix(EyVk}1Q*&c`l3pD8WXb_9JvCDaY zZ_+s!uiJTHbf`3QtHZ&@n;b$1O@IWE6}76>GeM2EmZwhBVB+3b%lt@H|85gq?YI{= zU2xp}Vu5>YhoFbc&IiasUoJBrQNfrbNF8EN^@R>hX>4@Fe-F5faQG1A zGdi%ZA_KRkt-+#(=u2Tqh;>K&Z{G7{t8_>usf$~X9_0i3R+IYT7%C6{}<2s0%^g-zp=~Vjm1$Mr60Fj#+MMK^W zee=<+*ll}Fx(f}U6)!5~@xy6j7=dEh0_3l(+A7DtpDSG;7iVR?L@5!v&S%{PV5&cm z-ue3VZKZ$OIe_(oCWw(l?{K+d5}u9J9w6?G*pCjFk|qV%@f2mfH8T_z9$1g1!a13OyvNxt?FI+*^-xq z^oxm{BiKzmyb6SN|$uBm{K zx)kUHjH^0FmcInXbypIwcPH9D??gqUNF-GwD~o6W(xW@ETvEufso7Be$L3hlUt~c z!_jDysq+q0C1kzV*SbY2s^7uRTEfT>$*c%WxpF%NJKIM^LAoEoae$LN6ziE9%2i&s@GjVW8qCHuLplXFgWnYJ;bt&@E4$svQ(r-K&;)T#_#m#Q%% z-cq?-hB33in6JfL+?T>*OH@u<^H&_MUpwemu(@hZ^nEi_n@1@AndX<*$qhG!NbG|b zoGkIc+xi{}<$1TDf)ZGjeK zAph8ic2?i0GLD}C$_hD14ZTgL$hph67?csT);O{78ttF&vh)R$J0y@E8SQy=hnbG7 zf%0J7W~3p}R5*6t65~-?5%N~$G2XE>>eqaTf=^_3Il~;;`a~c_;eBP^@Qq!IK$z){ z>4e`EyE9rAdk7P z_+0_NRUw83<5+7wHUsBWrEr5LmO5|er+{@Uti%^Wag_^GEQlha?D5@6uF!r(oS5VE zxDoK8y;>cxaZlfjr?tuG5dq@9UD9NkW7Ful%w1L)i*<2Ol$Vv*w-s2121O+}VyBGL z>nHLV!D(YOB-+0Ms|*dA`eE&J1Vc_eX6a%-$FAxkdV5kD3Y>sEpmgd*dsk=zTP8-b zc4gwSc!?UkuEduHVg$B7_em;$&0So6+b);F8Q@qG177`Lu*Z(mc4u4xlybdmPUFrY zb{_H`pP0WJ@=2dTh;$_IuN8fL$aE*Zt&xiCNR>q4f}WnsxQMm*kWf9s2=%{G83MrG%j~*%Q3V+_$B?kM`&CDyQx${Jf zEMegM#}0tpaY+3W9UWRl$pf2? zs!E-2DUob6HaQ1FMnD91y7J;JYXH z5H&)&T;Vo-ZsQ@W|E=~90z{W?;rM^TH9)N1AZMaACZ@DGfb(CyALmf3`zDE@wcK2Z zeZi7)w8M@v_nmz=AU*c!(8Cu(&~zY%(+L)KT=2TnKkumQU&-A)*HBqX*cP>SgKGYd z8+z<4{{j-p=xcD;2}a-}EFR`06BB06+{yuUW$`#4?j`gH-0Hy!F~4HuKo1Q%;?QT# zKs|Ab>ZEv>>5x6LM+ePMMF%-ayRo59qfK})aC%_ZQOUhDT76;b9zymrkm(j)G!^<_ z{nN-9I=mOYCDWE-@kvNv$Ft9NRly}ntUshxkd*KJQd+!@)OuI*7l&Wu3}&wSs8j5Z zD~Mi`_)=93Bp6T1$|ykPW&y#rDK#$%vU?!$O5DO0>#%2_9wS((@@6)_2{==ss{^v_ZqrS$pH&=-#!#ezJq0H#7!K); zDj0fXkqh?jyh3HnOPNr`sE|8G$2Kldl&TVTPW_!;bafusWko6QZIv0sFEeDx4g91H zGLVN1%7tKK>C)uQVAY-zJ`RLd8bIqhYC4xdU4kPY}JE#D8j8 zHLHMwOClkoJ4zaQW<&YZnB9e4T@AAP0`v84d^nbSll+J@JE)B9iL1npP%LT$A%HoQ8KMrZ* zpg5fT(~sXLCWzA^Q$~*(Us8N%COHMeK=Y=zE6>En8vy4l^A`_GLoJ$Ylvo!yDd#gx zm`TpQ4DN1V=c8362*`8xb^fXtwh*br6?T70Ah#HU;Vb#BcRR@%^l zFP}#EP&4n*7LRDeLMG_L)^BvcJ>6{3@vHgz)L1`SXC~s!h_=R9vMq)h6~jJIOJgtH zPY-n=6*cKt*|}W+GLRW5$3XUvfc&p9R7(&N?OVUsQ#r$qvXwx(MR&cjYrGYe;h+7kE()NP&$Z? zqp(v=8rk0yqQM z!LSQw=4jD~tcC_Rep@<7VTtf9K2Xw<$*?e^O5q8HWG!Acs>2cPdYGDZ&X+})7RXV0 z7g}d7nyGApHery_m~^?!&iklCQ2L1D(b0W{_peR%6@l-22RMlalwZIzA+~xCfOa@! zmOOM|meX$XY(=G8iME=N5Sl9MZ{0cG$u(83uGuVZBZ}bUs-e3sEMG!c^u(1Yrn2?D z+~7+zt-*01iB%@9yaALEUEs5f_mrf@h?NG_r`iG4^#gfsr+kZlQbw=AZeo>}Pfjdl z31IzSeR+l$DLiqbMg}>00@5KTY?mF$%?!^`SA>x(>A+SN9w>(s;$NR#QU|bpH6@)t_|~|F=V=2H{YimK4P=JB8h^-e98cpI=t@ zOyL1AZ@!~$wpu|`HSj`L=_&pM2(bveyYw`es48)HtD`T1paDDQ(t*1Ei6CqF7K&^+ zTt?Rl{j!RDm|Ma}GES~lRc2fsc3G|q!GEG0tr!#$dRvugHBzpg_}t;OK6q22C*z2B zt+oB4^6cS{@?;ABgn7EIRsJk{IIj&(|K!m2&SNE@3So4Mthp0s}4HDbfbd7 zj)~0&)qw#P{3Gmx)C7wFG5g2KLlix)#hbbKaP4OqqAv@$zvX+r`3L2* z&U=UQ1(mryjzzt6+a)KxXmKT5_7@8MP*UIHLOLGw2)R(sDik^xy46C=Xu6Mk1vS1G z@!$rH^|eK1(Zp?v$SsS_e;l^Rq6%gLx$V(c*Gdj;YQCI(5$uwFpRxzMB}J%V*!(d> z)sCFkw$LN17 z9>BanYtC{Kmu`x|3`z5OtqF~m*{}(1?ILS=_(9`Q%# z3ay=JzP=wso1O#?58Cu1j-y%gd>|z+iLQn=baS|l+!{Uwdflb@-(tz&5*@yItMJR7 zAz+51o#5h)uNcy`RuQ28lT0|z@31{-Ct#6HBsKU&RIVHyCLPA=XTnc9SG77Ad6-?K zyoR!Z(ifk!>K7w9Uwx*!IF5S@6nRGDp<+L{f_P-&uwVf8aM30XwNk5-lDjLxhQa(x zO$_YClk#XF)UN?O_7JL!20Zg7>ZYgDU|zh)`|8mmT>emMSC2xlWial`#`0w;Cg8-Z z^Yr4Za^bo`sr)U)yK8C6MP~s(fzuvFRpqi_KVMn?O(qe|f%U7!INfwXemclf?&q8D`&XU{ z@4>yjB|YyeN*9%+gOvMxu3Y;ZJ8x9MFSS8XEW$X#I4^#ppx)C5Gk~Ejc?SJ@7WjcC|Et< z!zp#K^*&n?yheo%tTm5%e;VSN&u`|lhZNeQPWL8}=`_GD6Tz6YJ?>S@$T5k_m!b%E z!toOMxDD@%v~6~hfFR);pFLk1y)pMS&md2ipEixN?W?8Xc~3KvC3rQkf-}HbH1yrB z&jhln=b$ZY3E`KIGW1nrPca)bpv0K17)oz$ym+^#vz8>(PhDV&H9k_6mDH&5@4H6K zG0}(4yb9CiC;K+2tbmr;ua(<9oTRquR*O;gel{3T_9+*G&p&_XU7~dRpn|r; z@kaPr`SYN43y7pGNEV}p>Cm+y+uucT9u(5SrTFHvg(jsDu7qB=ea$^*OIOEJGbr>- z?s!DVkV38&%bs_?TJX+wcwJ~p`o;iG2L2tN5nR;TOm7oS(b^C!s4|w?nCl3<#Zn7H z-?Eaa-j~TCIv{#A^L2%GFj;os38FEwQ-SAH110T|B~O4&WPH5r4QN%@tdygs=pPpF zi{iy#P^Y0?p;wg#wGA|1oC9B$UiRENW6fh!cWUg*YQEual$WHNj?D+g2aTgi377Z6 z*NM+yVi7zOwi+4#GSrP}8hD-&t?!DDn%jvLyVF4?S8ik;N#{&dK6Q0YiQ};=MUm3b zO^jF5M^)F2az6VK2c~KMS7CLZiN^1dJh`Af;93W*(N&@KNrdu^eCXsuQ8ZQ7Ea4Zj zHucFzdyTYD8k0XM%%fj|v*4?;rR&sl6a~JU!;$KbV?hWX9#)Tw&o;Cu7(CP`=v*k2 z==bTKFz|w-sS5HNYdgJ*bLUSY6uhiTHz~AaK0e=wOfpgXYM`%c_~RT7w2PTeG}@?c zUDHAgnmnw!MNt$|$L33gCY~XDwi_dH=fUa1I(BnD0a^ohKi_ot(r-6e%FF{vir6E2?FYa!-gNNc>APn9|9-~7EU_Ug zAB(Zg_n9!8 z#740{WI3^c2GcO{xi$flL(R@XUMD|TXVTYN>aGspg)8KK{65KtOX906mI}Jj%kO^i z=~lScSm*kdG$K{qSvJ&Vyvs!(D5!g4&g(?mLkL!q_A6s#z1-NVkQtD_KW0o=J%V2h-;g|LRG1gUdmhO*arD z;c|jwISARV4+Rh2N=7F)SufZ(Ga-M=YM31cCMT{AD*F~Y5+!$fY&N7^ASi<;XjsYi zasE}J6}U9IwF?p*edq6qZI|XJv%2UKr~one*CXGf*Qv5H8n|trvr;wpIStgQ^ESR! z78_d+DOSTjwR@y!i_Q!Q<~tlA$II7fRSs%xp)v7B??%yjaAE?zSj+r4g+(^NShm6(8 zz!-b6?aUB8RlFzWvQHcCCLcki=`^3r5j2N%D2l{v`-6dlB9Z&MJNb_os4%srT29Jd zC9*FBKbCe)S)FB2-qJ$=>;^7mpzsAqCyN8fK#;^3=l3JFIU#r-PdiG(! z>>j2qh-FBz=(s2(BEQkmtxyd!pn;QE>D0=Ya7tH-8GCve>bfuvU%dli43(!D20_ z3`W0OP26wm9{tw6wiIFsukLYr=NBfaKg8bJvbb%hdSm2~vV`Zma`wfAU@Jm~KHHqM zEw6ZbTl-~a5_@-JFlvxSbS)vq%Qa$sRk$tTzYw<((&?J7OeRvJ z#b*btw>NR@4)zD}a@He0pXwe}`un4;%j~oz9sCX^<9nAo`9LupaBASAY(A-tt?2wu zGY|;p-HWQ{E_X#Bwx4!tthfum7u+|I-Z&^_EMsGvRVx`|zlK*+EzIE`aVyJ8rK@e` z3YKZcv=QBv-Z+3T9}S%;Y+kD_(q`;Ii_`V?ziebbJ+Jhey;DZtEPd_nPB_}i@5Z>!Kkkz-Uh`sPP3NKQ;s`F>5CJHhY=q;( zsA1HCQf!!me=n3Dt_xdLKJK{!J6v3@W@8^U=X$U(s@GZR+ndTO*gEF1`kl|p?b9aI zzlqRAkAJ8z4W<dH&HE^_rIjUAU$^0_+^2?C$Lci`#6w%RociDq>NwDu;gzOJ4l_6i_!EY{9KbiO50>*4 zT{)GA3%*)x%NJ;ySN}^DgX)FW#Q2ytt<@IYXAn z_(2)$1DxJ?avI)7v8D8Ie+jsSaKuLd6&Q;~#()(zpOXYRTw(fFGnF6KuE zdF%D!?n4H50nFotblz4cLi*}Rr)jH}i*9J&v-GheV`9pxH@|(*gRSS0s^8M<~ZaB^24~Wmr<2eD*B}+#gJcqw+qzDiXT@bTUZK%xP z@wk*|n}PakY4(wm&um$#e80^V8paccr;WZ= z0-J1r)tJCQ^OXtYnLg^K>V{p-Wbv?4cnd;7SmYRyhylIR3gP}IH9JxXLmxGN9I(>F zou&GDghPP1vh0!PxBB3Fwri2jwXrg@7Ck7hu^4;^R8R3@MxUW&si$ZMxj! zKqA3QtJ}r*dDw_ce}$yBU%b(i;>=X2jJ#T9VE=dlw#jD7rPC)kRzOI7Lc>G_T7udO z0kh;&(y0^@PUsSE|Fb((_Rxe6Z!=GC@K|bm-)6H`t~S2GfL)-i1Dee}{kt~iwO5Yk%E!2)G8F;9|Pywb`N+FSbF%t4DmtTAjc17e~rAadpg;*GSN@TPsa zu{!pADyv+xQ=4C?q{I??s~b_)ZS!qz-Ojr??Bl1S7nGE21Nb&?7@f>UJtiGv66V0R ze3QOhyiqcbh$kAZm_je+l1Jh;ZF(#<83F z9^^D^Znt7R2E<^yzPIN4h}KTzfR(ib-n}jE*&8XY=)B(qqXG3M%Ae}zj`mU7>^kx& z*l*B0_Z%zLtDx-^ytvdgF3U2EjRYyZKfvIA2E~!5m9(+8kX_|%@oYezVclWBSMFcn zrlF{)`?Xzdrhaa3es#7}WgLmkJm&n1PVQG@fUPts0ws^tP-{+7T{kW~8=Qt8dErK#<$g4RliTFFF21&f&2=%Zo1_2UY2O>xMR;eBHd?FLtj#;eRX))y!gZ& zA!v^8ua}Dhr9%A#ShmhM$GBJNq`vjjfe&FQF^n(KY0As@a$k|-vtDM+EMDMxGWVny z{;=fLn^fQ(n@aDsbiG%l=2#ql$G+yoJ7>O_=$0}Bue%<$_^qXGV^b^-EL9sKZz%(fCi`1gwA|GK+Jm`6K4>_E^O zV3pxvO9@Ji3HGPguP6WdYLfG!$^<*-TYslfV&9i2tR=(EZO>x2i}gl?xZE>ce=TiX z@eTI3{9e5F#a38A;wqp_MD>zXfID=feXAc4bTl^SY5zmE3)yv_h?kIJ_l=(6ZWvt` zy#eK5T|bAfnn{}BJup+RB~*Kvch?3|ZwD05!2tw;pzldS1Qsp;$1Gu5^Zhutx`D~^!)YStJ* z4D@N5Q?oofPv}vE2D1Rmy|3s)Ni5w&WSXZmaXNPDgZh#(^S$3p3z0GhOupyB0#D_A zaTLLN)6{VftZ=FP*f^f%f6z7KNK#JqU?6{ty~c$7gP`C)af|z6^M5&j!iWl+!K7#% z|AB4=tu1+$zQLI%n``$5M%a2glpFhBB21AbpRY;sy4^BanmI0x@KqvuZVlK=OG89o`VOAl^12}0M3@xd+~JU=G`=Pp`h5L}QY4Jai$p~jU?jjx}V zU)7vg+)fR_{VUUye2+o0KEt6x0LTXO-|?+o=N;xf*~*!NGqShNj1G=)q! z9bMP+`cG;r57C;sT_-!;>^BE zhsAr(3LIhHQQAb2F+ZfQct<<0Uc?=pyZ4G6@8|6_I|fVU8sA?WxxR>Eqjrt-4p16f zL;lX^pcN5v{~4n6vhvKvrOLN7x6jzjcQk#wovuXe47s^)v*FR2#GRW>D$6WX&~4gv zyHA<*k7%`#C6{JzIW!SKIgH}fN5STOQ}2bAoZ>-WR39}uFQGDB>?OWm0W+X!DhM)+ zo=&0q@Dw+mUE*KXbP1WkxPTF$%PcFPvu!NaRZ^HZ5;djclQslra4^7$r?Psm=w#7g z_p7DUFvQS7f!mLW?d_0WG4FIcCLY%6Mjyz&u+k>}yWPB1EfyqzpcoGldhYpfiw~nI zNUN_$*CP){tQUshPaT^(R$>)=IZ01RA&n&{gE^Kdh>5Td%c7Qbf~wK^QQYOjSOTmv zB5%p#ObvTXto@;h7aJBD6ZJHUlKva^zmuZBx6iCDxz?JfTe8H)Z>7x_hvI7@L2qeR z$g4Qxu5KEC0S;gK#NGzH;W0L-=Q(t5$S>rgHWq)n#nw8rv47&Uic*D;IMw^EL*PPR zHc%AME1k&|XvQ5K0Z%wFnxvDmElj$-AVLke8m7LQ3WMODl>(P5lcanZPTfZ{h60cI z6KNN+1R<|)Tq1k=bdeN!446>_^`3wh_&4Rm{RPCId_TojlmP5OWKnAiaq{`>1J;5)fl@&DNf zLj^SwOG{K%U`p zcbg$4WV!Pg5OYs-jYQ!867xUWiu}KciNnyia<~slkz}RH+bGs|X9Hcq270YE&Vm?i zcNI=FKo+$P+s9kt3vua&k$876(;PGfB7srR_$1FXFFze&u10!p0hnrNTbxurvn=ZF zGIB^?8A}Dnd;XsX7=1@orSl1Xi+|LsJMkrH3i>rbB;M`EER$wXjtSN4;>y}Q2e*_C zF?QGii86}=&RG}Rxm#zSsC!$MMUDsT6{5O2)v;&iw5tG7y4(QRPKU$o1NefGpPzNn z3{dO+!PEO9NfNRFB;?(k?Ga!vqg$Z@N+dsWMspNjBy6|?-RU6(%zqP-&H@J8WR+7m z>aH;o36=pWE4wBoTP@g03EB-Gp_@~lg!)PLiVG27fHKxtM&wIyyl0JxCp=tQ*F8FzK(A)H# z(Qk>Cc=WR5{YkhN2$l#F0=$}JuE8%0%}=*(yg#t?aHqU}8C>vy;qLx}^8;8UX~N1O z0hlmzwW6m|{@-=av`Lb77Qm3FX{{?ri*cvN7BBMZKgNZ|-UN4U{W<02N#Sg;~{Qu;e zsTqKW29ZCv)9@IbopzAxEf%E)wUOB<&aEQ#2*qN7s$svN;jm+NI4`>$gkL^B|1Hcj zapyLSgQ_6kLj42YK~fReT3BaDUn&juZyMJqJU6F@&Nm;;9fIr=0ISA&g?uNeBu{Mg z<&4JV83ws86OB<(4@rga_XR-u)8XusvtY)#7NaV$1Q#!+)&?jRd9u}DrX^lPYY;q?JK~8ih)QHNu30_K%&)@MXKyB65`bK&JoLM+#Tp`o< z${$Wir>mw#PfZAu)ED*yOo?Yp@n+ce?0K{{xc$I(Eu`8`JMxP$iC=^?fYUp<{EURZ z*N;O5YB?hHAK`*dZ-^IaO@guvtOv-&p)I$ckv1y#;^@SM)lg}Ek=`dXMPakvq16r3 zBK$kuWrIn6a8|9xIBB2?oCVTv4Vvoq0a#Tz2`=J4tp&08)$d zS8cL&x?mioMu!(F+j;|+C7yhhN*WORjShfj*8`s3BYoElJ#|5eC$-WAAmn)Jai{r$ z$2BrZPXKEy1HcY@0NYMlUX9LDw(a-mlkx=1HYiW@1fggGHo3!2;{Z*MyR)gO1teTQ zAIBQIegxt7eY4vGHxC8@Rw*%`g9dgu}8?=<9ND#A=hQwMNjT$xT);VZb$6e|Ex2N1%Sr? z!DTQ5zqK$OBqXW>MI|4%^Sx+Mo5++Q?E|j_J8%sSK0hh719Aq1KpMIAd}Er1uzeyi z<5J>gk{dkkf;Wx=K5_(hy1r9aJ1I>z4l3jyZ9fW8XzBMaEJBSA#oA`PxcY|)0C~z%sssfIFsvTU3EwkK-qu?F~x2V#E z0n4Yj+kE*`+N?QY+WLk&a|{HmH$JCwghVD|>^O44Cj8E|LdYU_1kUF}TrjeTTRZ&E z0^rUl5FI znF7>Bx#F+QgsZ0$`eJAT5s`kc+K4f=7Y27W+X_sec_-N!VZ|$WWxtyLjoDBKVlGT9 z@{5w_SdT%4x$Vv-I)WFCZZ}SlwmSm165DnY8Ff+s8G6uc)c!;DHnNl56It=ch$;ZO z9hbZ#+=ejM2-OU>g;GPtxE5ZT&M@P%{9|9wRqAGwzK#L7_6#Wc4(U(SMQD}Asmusd z>6E86{Vo^l2ZO|X?zde*B(01vi;@NdJGJv8Quwbcs?n0gBL`hF%3h6(BG$s~b$x?? zK#*U;p=i8Ay#xGSw`J1YeZ^)Fcw~HvhMH^S5hX28dU*!!%o3!`$Jh8)I>lb1oqb(5 zB4w!l(Vy@9^Fg#FbieA&u9t8$;#!!byUOfRC45cuXs?r{6Nxgr-z+gkx33R^t4uPk zGRn2fzFifadjD6~+rGZfPJeur-FM$H^eKFlYvaR|Q++u?tsLFh_%h;~D7ijl=c}eR zqcQ8z@uINH^$hN6%Rw2x8!M71IZDzy3F7EuS!tKnbLu&ff`qw{z9RfLQI%a9?T*sD z9zD+I8lCLTccan6ES)J}s|7L0PWXqCyDN<5T^|gg1MP2189Xm7kAFbuMfWw$lfgrO z7CA#Vam|Am@&?&i#V(Qe7UQ2VB8e!DaDUX;E3-2Y92Xlmq=Q5Q3^{?BHJJ9rgnXArwTmRQ{B%ya&R(f0kvT`l6x$;cEb+MTg1L zY;Yr&pV$O)EAXg?chj0cY&>!1x!mxdzQkGPfGIzF*Ej8Os2Y|ldzMR}Gyq>?)>Mhe zH!mn}dQu$ubTM^%2Dw reply with return from handle_path - let routes = get_v1_routes() + let routes = favicon.or(get_v1_routes()) .recover(handle_rejection); From 3c1d88bd42f2fd94e6ebc1cbc9a5d92c76d1f2b8 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Mon, 1 Jan 2024 11:46:39 +0100 Subject: [PATCH 03/36] Added last_update object --- src/v1/projects/mod.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/v1/projects/mod.rs diff --git a/src/v1/projects/mod.rs b/src/v1/projects/mod.rs new file mode 100644 index 0000000..e306caf --- /dev/null +++ b/src/v1/projects/mod.rs @@ -0,0 +1,37 @@ + + + +pub fn get_project_routes() -> impl warp::Filter + Clone { + warp::path("v1").and(warp::path("projects")) + + .and(warp::path("last_update").and(warp::get()).and_then(last_update) + .or(warp::path("start_update").map(|| "Not implemented yet")) + .or(get_kcomebacks_upcoming_routes())) +} + +// get json data from https://https://cdn.jonasjones.dev/api/projects/projects.json +pub async fn fetch_data() -> Result { + let url = "https://https://cdn.jonasjones.dev/api/projects/projects.json"; + let response = reqwest::get(url).await?; + + if response.status().is_success() { + // Parse the JSON response + let json_data: serde_json::Value = response.json().await?; + return Ok(json_data); + } else { + // Handle non-successful status codes + Err(response.error_for_status().unwrap_err()) + } +} + +async fn last_update() -> Result { + + // get the value of last_update of the first element of the json that fetch_data() returns + let last_update_value = fetch_data().await.unwrap()[0]["last_update"].clone(); + + // get the value from last_update_value and return it as a json if it's Ok, otherwise return an InternalServerError + match last_update_value { + serde_json::Value::String(last_update) => Ok(warp::reply::json(&last_update)), + _ => Err(warp::reject::custom(InternalServerError)), + } +} \ No newline at end of file From 5c7c57264171a8ce52e0145d813e0c9d60843b2c Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Mon, 1 Jan 2024 17:18:08 +0100 Subject: [PATCH 04/36] Added error 501 --- src/error_responses.rs | 4 ++++ src/server.rs | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/error_responses.rs b/src/error_responses.rs index 792e4d5..5a8b897 100644 --- a/src/error_responses.rs +++ b/src/error_responses.rs @@ -26,3 +26,7 @@ impl warp::reject::Reject for UnauthorizedError {} pub struct ForbiddenError; impl warp::reject::Reject for ForbiddenError {} +#[derive(Debug)] +pub struct NotImplementedError; +impl warp::reject::Reject for NotImplementedError {} + diff --git a/src/server.rs b/src/server.rs index 5567503..7aa133e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -5,7 +5,7 @@ use reqwest::StatusCode; use warp::Filter; use warp::reply::Reply; -use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError}; +use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError, NotImplementedError}; use crate::v1::get_v1_routes; use crate::{Logger, parse_ip}; @@ -24,7 +24,7 @@ pub async fn serve() { let routes = favicon.or(get_v1_routes()) .recover(handle_rejection); - + async fn handle_rejection(err: warp::Rejection) -> Result { let (code, message) = if err.find::().is_some() { (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error") @@ -32,6 +32,8 @@ pub async fn serve() { (StatusCode::BAD_REQUEST, "Bad Request") } else if err.is_not_found() || err.find::().is_some() { (StatusCode::NOT_FOUND, "Not Found") + } else if err.find::().is_some() { + (StatusCode::NOT_IMPLEMENTED, "Not Implemented") } else { (StatusCode::INTERNAL_SERVER_ERROR, "Unhandled Rejection") // Default case }; @@ -40,7 +42,7 @@ pub async fn serve() { code: code.as_u16(), message: message.into(), }); - + Ok(warp::reply::with_status(json, code)) } From ce7aa20a571f500481b536c5afcb03bfbd55d4e5 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Wed, 3 Jan 2024 00:48:58 +0100 Subject: [PATCH 05/36] code cleanup --- src/v1/kcomebacks/filter/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/v1/kcomebacks/filter/mod.rs b/src/v1/kcomebacks/filter/mod.rs index 797d9bc..158bac2 100644 --- a/src/v1/kcomebacks/filter/mod.rs +++ b/src/v1/kcomebacks/filter/mod.rs @@ -323,8 +323,6 @@ async fn filter_type_handler(params: HashMap) -> Result Result { - println!("Received parameters - parameters: gettypes"); - // fetch the data let data = fetch_data().await.unwrap(); From 5aefb1a7c79364cb1a9cd4ffee44813bbe7a9faf Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Wed, 3 Jan 2024 00:49:29 +0100 Subject: [PATCH 06/36] Added /v1/projects api branch --- src/v1/mod.rs | 3 + src/v1/projects/filter/mod.rs | 562 ++++++++++++++++++++++++++++++++++ src/v1/projects/mod.rs | 50 ++- 3 files changed, 613 insertions(+), 2 deletions(-) create mode 100644 src/v1/projects/filter/mod.rs diff --git a/src/v1/mod.rs b/src/v1/mod.rs index b1f4cd3..cd12a16 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -1,11 +1,13 @@ mod builtin; mod debug; mod kcomebacks; +mod projects; mod updates; pub use builtin::get_builtin_routes as get_v1_builtin_routes; pub use debug::get_debug_routes as get_v1_debug_routes; pub use kcomebacks::get_kcomebacks_routes as get_v1_kcomebacks_routes; +pub use projects::get_project_routes as get_v1_project_routes; pub use updates::get_updates_routes as get_v1_updates_routes; use warp::Filter; @@ -14,5 +16,6 @@ pub fn get_v1_routes() -> impl warp::Filter impl warp::Filter + Clone { + warp::path("filter") + .and((warp::path("getall").and(warp::get()).and(warp::query::>()).and_then(filter_getall_handler)) + .or(warp::path("lastupdaterange").and(warp::get()).and(warp::query::>()).and_then(filter_lastupdaterange_handler)) + .or(warp::path("title").and(warp::get()).and(warp::query::>()).and_then(filter_title_handler)) + .or(warp::path("description").and(warp::get()).and(warp::query::>()).and_then(filter_description_handler)) + .or(warp::path("search").and(warp::get()).and(warp::query::>()).and_then(filter_search_handler)) + .or(warp::path("status").and(warp::get()).and(warp::query::>()).and_then(filter_status_handler)) + .or(warp::path("statuscolor").and(warp::get().and(warp::query::>()).and_then(filter_statuscolor_handler))) + .or(warp::path("category").and(warp::get().and(warp::query::>()).and_then(filter_category_handler))) + .or(warp::path("language").and(warp::get().and(warp::query::>()).and_then(filter_language_handler))) + .or(warp::path("getlangs").and(warp::get().and_then(filter_getlangs_handler))) + .or(warp::path("getstatuses").and(warp::get().and_then(filter_getstatuses_handler))) + .or(warp::path("getcolors").and(warp::get().and_then(filter_getcolors_handler))) + .or(warp::path("getcategories").and(warp::get().and_then(filter_getcategories_handler)))) +} + +async fn filter_getall_handler(params: HashMap) -> Result { + // Access the parameters from the HashMap + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if params.len() > 2 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() + || offset.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .collect() + } + _ => Vec::new(), + }; + + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap() + 1).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + + +async fn filter_lastupdaterange_handler(params: HashMap) -> Result { + // Access the parameters from the HashMap + let start = params.get("start").unwrap_or(&"".to_string()).to_string(); + let end = params.get("end").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // Parse the start and end dates (from unix time) to i64 integers + let start = start.parse::().unwrap_or(-1); + let end = end.parse::().unwrap_or(-1); + + // check if the parameters are valid + if params.len() > 4 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() + || offset.is_empty() + || start == -1 + || end == -1 + || end < start + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.last_update >= start && project.last_update <= end) + .collect() + } + _ => Vec::new(), + }; + + println!("{:?}", filtered_data); + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) + + +} + +async fn filter_title_handler(params: HashMap) -> Result { + // Access the parameters from the HashMap + let title = params.get("title").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() + || offset.is_empty() + || title.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.title.to_lowercase().contains(title.to_lowercase().as_str())) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_description_handler(params: HashMap) -> Result { + // Access the parameters from the HashMap + let description = params.get("description").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() + || offset.is_empty() + || description.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.description.to_lowercase().contains(description.to_lowercase().as_str())) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_search_handler(params: HashMap) -> Result { + // Access the parameters from the HashMap + let search = params.get("search").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() + || offset.is_empty() + || search.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.title.to_lowercase().contains(search.to_lowercase().as_str()) || project.description.to_lowercase().contains(search.to_lowercase().as_str())) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_status_handler(params: HashMap) -> Result { + // Access the parameres from the HashMap + let status = params.get("status").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if status.is_empty() + || params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() || offset.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.status == status) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_statuscolor_handler(params: HashMap) -> Result { + // Access the parameres from the HashMap + let statuscolor = params.get("statuscolor").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if statuscolor.is_empty() + || params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() || offset.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.statuscolor == statuscolor) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_category_handler(params: HashMap) -> Result { + // Access the parameres from the HashMap + let category = params.get("category").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if category.is_empty() + || params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() || offset.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.categories.iter().any(|cat| cat == category.as_str())) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_language_handler(params: HashMap) -> Result { + return Err(warp::reject::custom(NotImplementedError)); + return Ok(warp::reply::html("placeholder for compiler to be happy")); + // Access the parameres from the HashMap + let language = params.get("language").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if language.is_empty() + || params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() || offset.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + //.filter(|project| project.languages.iter().any(|lang| lang.contains_key(language))) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + //Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_getlangs_handler() -> Result { + return Err(warp::reject::custom(NotImplementedError)); + return Ok(warp::reply::html("placeholder for compiler to be happy")); + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .collect() + } + _ => Vec::new(), + }; + + // filter the data + /*let all_language_keys: Vec<&String> = filtered_data + .iter() + .flat_map(|project| project.languages.iter().flat_map(|lang_map| lang_map.keys())) + .collect();*/ + + // get the total number of results + //let total_results = all_language_keys.len(); + + // return the data + //Ok(warp::reply::json(&create_json_response(all_language_keys, total_results))) +} + +async fn filter_getstatuses_handler() -> Result { + // fetch the data + let data = fetch_data().await.unwrap(); + + let mut all_statuses: Vec = Vec::new(); + for i in 1..data.as_array().unwrap().len() { + if !data[i]["status"].as_str().unwrap().to_string().is_empty() { + all_statuses.push(data[i]["status"].as_str().unwrap().to_string()); + } + } + + // remove duplicates + all_statuses.sort(); + all_statuses.dedup(); + + // get the total number of results + let total_results = all_statuses.len(); + + // json response + let json_response = json!({ + "results": all_statuses, + "total_results": total_results, + }); + + Ok(warp::reply::json(&json_response)) +} + +async fn filter_getcolors_handler() -> Result { + // fetch the data + let data = fetch_data().await.unwrap(); + + let mut all_colors: Vec = Vec::new(); + for i in 1..data.as_array().unwrap().len() { + if !data[i]["statuscolor"].as_str().unwrap().to_string().is_empty() { + all_colors.push(data[i]["statuscolor"].as_str().unwrap().to_string()); + } + } + + // remove duplicates + all_colors.sort(); + all_colors.dedup(); + + // get the total number of results + let total_results = all_colors.len(); + + // json response + let json_response = json!({ + "results": all_colors, + "total_results": total_results, + }); + + Ok(warp::reply::json(&json_response)) +} + +async fn filter_getcategories_handler() -> Result { + // fetch the data + let data = fetch_data().await.unwrap(); + + let mut all_categories: Vec = Vec::new(); + for i in 1..data.as_array().unwrap().len() { + if !data[i]["categories"].as_array().unwrap().is_empty() { + for j in 0..data[i]["categories"].as_array().unwrap().len() { + if !data[i]["categories"][j].as_str().unwrap().to_string().is_empty() { + all_categories.push(data[i]["categories"][j].as_str().unwrap().to_string()); + } + } + } + } + + // remove duplicates + all_categories.sort(); + all_categories.dedup(); + + // get the total number of results + let total_results = all_categories.len(); + + // json response + let json_response = json!({ + "results": all_categories, + "total_results": total_results, + }); + + Ok(warp::reply::json(&json_response)) +} diff --git a/src/v1/projects/mod.rs b/src/v1/projects/mod.rs index e306caf..d6f30ae 100644 --- a/src/v1/projects/mod.rs +++ b/src/v1/projects/mod.rs @@ -1,17 +1,26 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{Value, json}; +use warp::Filter; +use reqwest::Error; +use std::collections::HashMap; +mod filter; +use filter::get_project_filter_routes; + +use crate::error_responses::InternalServerError; pub fn get_project_routes() -> impl warp::Filter + Clone { warp::path("v1").and(warp::path("projects")) .and(warp::path("last_update").and(warp::get()).and_then(last_update) .or(warp::path("start_update").map(|| "Not implemented yet")) - .or(get_kcomebacks_upcoming_routes())) + .or(get_project_filter_routes())) } // get json data from https://https://cdn.jonasjones.dev/api/projects/projects.json pub async fn fetch_data() -> Result { - let url = "https://https://cdn.jonasjones.dev/api/projects/projects.json"; + let url = "https://cdn.jonasjones.dev/api/projects/projects.json"; let response = reqwest::get(url).await?; if response.status().is_success() { @@ -34,4 +43,41 @@ async fn last_update() -> Result { serde_json::Value::String(last_update) => Ok(warp::reply::json(&last_update)), _ => Err(warp::reject::custom(InternalServerError)), } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Project { + pub title: String, + pub description: String, + pub status: String, + pub statuscolor: String, + pub categories: Vec, + pub languages: HashMap, + pub gh_api: String, + pub version: String, + pub backgroud: String, + pub links: HashMap, + pub visible: bool, + pub last_update: i64, +} + +pub fn create_json_response(items: Vec<&Project>, total_results: usize) -> Value { + // Serialize the vector of items to a JSON array + let results_array: Vec = items.into_iter().map(|item| json!(item)).collect(); + + // Build the final JSON object with "results" and "total_results" fields + let json_response = json!({ + "results": results_array, + "total_results": total_results, + }); + + json_response +} + +pub fn parse_item(item: &Value) -> Project { + // Parse the item into a struct + let item: Project = serde_json::from_value(item.clone()).unwrap(); + + // Return the parsed item + item } \ No newline at end of file From 169be592ab636456b0e0168a534d5d49585e4ec2 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Wed, 3 Jan 2024 00:56:18 +0100 Subject: [PATCH 07/36] Removed dollar signs in front of bash commands --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4e2f4ca..5cbba9f 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,16 @@ As of now, the project has no proper production build and unless the proper envi Clone the repository and install the dependencies. ```bash -$ git clone git@github.com:J-onasJones/jonas_jones-api.git -$ cd jonas_jones-api -$ cargo build +git clone git@github.com:J-onasJones/jonas_jones-api.git +cd jonas_jones-api +cargo build ``` ## Usage To run the API, simply run the following command. ```bash -$ cargo run +cargo run ``` If you want to run the API in a production environment, you will need to set the following environment variables. @@ -29,8 +29,8 @@ If you want to run the API in a production environment, you will need to set the - LASTFM_API_SECRET ```bash -$ export API_PORT={port} -$ export API_IP={ip_address} -$ export LASTFM_API_KEY={lastfm_api_key} -$ export LASTFM_API_SECRET={lastfm_api_secret} +export API_PORT={port} +export API_IP={ip_address} +export LASTFM_API_KEY={lastfm_api_key} +export LASTFM_API_SECRET={lastfm_api_secret} ``` From 2188f2acd5dcbaf2fab12748f078a07add78b6a2 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Wed, 3 Jan 2024 00:59:22 +0100 Subject: [PATCH 08/36] code cleanup --- src/v1/kcomebacks/mod.rs | 4 +--- src/v1/kcomebacks/upcoming/mod.rs | 2 +- src/v1/projects/filter/mod.rs | 25 ++++++++++++------------- src/v1/projects/mod.rs | 8 -------- src/v1/updates/minecraft/mods/mod.rs | 20 ++++++++++---------- 5 files changed, 24 insertions(+), 35 deletions(-) diff --git a/src/v1/kcomebacks/mod.rs b/src/v1/kcomebacks/mod.rs index 22926f2..6ddb102 100644 --- a/src/v1/kcomebacks/mod.rs +++ b/src/v1/kcomebacks/mod.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; use warp::Filter; @@ -10,7 +8,7 @@ mod upcoming; use filter::get_kcomebacks_filter_routes; use upcoming::get_kcomebacks_upcoming_routes; -use crate::error_responses::{InternalServerError, BadRequestError, NotFoundError}; +use crate::error_responses::InternalServerError; pub fn get_kcomebacks_routes() -> impl warp::Filter + Clone { warp::path("v1").and(warp::path("kcomebacks")) diff --git a/src/v1/kcomebacks/upcoming/mod.rs b/src/v1/kcomebacks/upcoming/mod.rs index 813f7bc..0e29b44 100644 --- a/src/v1/kcomebacks/upcoming/mod.rs +++ b/src/v1/kcomebacks/upcoming/mod.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, ops::Add}; use warp::Filter; -use crate::{v1::kcomebacks::filter::filter_daterange_handler, error_responses::BadRequestError}; +use crate::v1::kcomebacks::filter::filter_daterange_handler; pub fn get_kcomebacks_upcoming_routes() -> impl warp::Filter + Clone { warp::path("upcoming") diff --git a/src/v1/projects/filter/mod.rs b/src/v1/projects/filter/mod.rs index 35ba6ee..42c0afd 100644 --- a/src/v1/projects/filter/mod.rs +++ b/src/v1/projects/filter/mod.rs @@ -1,10 +1,9 @@ -use std::{collections::{HashMap, HashSet}, vec}; +use std::collections::HashMap; -use reqwest::StatusCode; use serde_json::{Value, json}; use warp::Filter; -use crate::{error_responses::{BadRequestError, InternalServerError, NotImplementedError}, v1::projects::{fetch_data, create_json_response, Project as EntryProject}}; +use crate::{error_responses::{BadRequestError, NotImplementedError}, v1::projects::{fetch_data, create_json_response, Project as EntryProject}}; pub fn get_project_filter_routes() -> impl warp::Filter + Clone { warp::path("filter") @@ -452,16 +451,16 @@ async fn filter_getlangs_handler() -> Result let data = fetch_data().await.unwrap(); // filter the data - let filtered_data: Vec = match data { - Value::Array(items) => { - items - .iter() - .filter_map(|item| serde_json::from_value::(item.clone()).ok()) - .filter(|project| project.visible) - .collect() - } - _ => Vec::new(), - }; + // let filtered_data: Vec = match data { + // Value::Array(items) => { + // items + // .iter() + // .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + // .filter(|project| project.visible) + // .collect() + // } + // _ => Vec::new(), + // }; // filter the data /*let all_language_keys: Vec<&String> = filtered_data diff --git a/src/v1/projects/mod.rs b/src/v1/projects/mod.rs index d6f30ae..61daf55 100644 --- a/src/v1/projects/mod.rs +++ b/src/v1/projects/mod.rs @@ -73,11 +73,3 @@ pub fn create_json_response(items: Vec<&Project>, total_results: usize) -> Value json_response } - -pub fn parse_item(item: &Value) -> Project { - // Parse the item into a struct - let item: Project = serde_json::from_value(item.clone()).unwrap(); - - // Return the parsed item - item -} \ No newline at end of file diff --git a/src/v1/updates/minecraft/mods/mod.rs b/src/v1/updates/minecraft/mods/mod.rs index f96fcb4..1959f92 100644 --- a/src/v1/updates/minecraft/mods/mod.rs +++ b/src/v1/updates/minecraft/mods/mod.rs @@ -16,14 +16,14 @@ fn handle_path(modname: String, loadername: String, version: String, remote_ip: format!("modname: {}, loadername: {}, version: {}, IP: {}", modname, loadername, version, remote_ip.unwrap_or(std::net::SocketAddr::from(([0, 0, 0, 0], 0))).ip()) } -fn handle_with_headers( - headers: warp::http::HeaderMap, -) -> String { - // Iterate through the headers and print them - for (name, value) in headers.iter() { - println!("Header: {}: {}", name, value.to_str().unwrap_or("Invalid UTF-8")); - } +// fn handle_with_headers( +// headers: warp::http::HeaderMap, +// ) -> String { +// // Iterate through the headers and print them +// for (name, value) in headers.iter() { +// println!("Header: {}: {}", name, value.to_str().unwrap_or("Invalid UTF-8")); +// } - // Respond with a message or perform other actions as needed - "Headers received".to_string() -} \ No newline at end of file +// // Respond with a message or perform other actions as needed +// "Headers received".to_string() +// } From bc09ff5f3cb73e10ef85d989fd9f80224db024db Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Wed, 3 Jan 2024 11:28:36 +0100 Subject: [PATCH 09/36] version bump that I forgot about --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b978b8..d8110eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,7 +520,7 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jonas_jones-api" -version = "0.1.0" +version = "0.2.0" dependencies = [ "chrono", "dotenv", diff --git a/Cargo.toml b/Cargo.toml index e29489b..f0854b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jonas_jones-api" -version = "0.1.0" +version = "0.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 83e1b858112f390c87d30680811e8579cdc70cef Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Wed, 3 Jan 2024 11:29:35 +0100 Subject: [PATCH 10/36] Updated dependencies --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8110eb..7bf42ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -546,9 +546,9 @@ dependencies = [ [[package]] name = "lastfm" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e452f27c6dfa6d94388d410f9f9cfa0be2d865997f13711f84054a4b74b680cb" +checksum = "4b2b8e3a1ad7d64fae5e5542beccb5596402aafeb69c7259e3baa2dfdfa364e1" dependencies = [ "async-stream", "chrono", diff --git a/Cargo.toml b/Cargo.toml index f0854b6..eec45f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.35", features = ["macros"] } dotenv = "0.15.0" -lastfm = "0.6.1" +lastfm = "0.7.0" log = "0.4.20" chrono = "0.4.31" toml = "0.8.8" From 5eb608069463f0da803372da626dd23549e4c336 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Wed, 3 Jan 2024 12:00:32 +0100 Subject: [PATCH 11/36] Fixed getlangs & language filter It was a placeholder in v0.2.0 as not implemented --- src/v1/projects/filter/mod.rs | 53 +++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/v1/projects/filter/mod.rs b/src/v1/projects/filter/mod.rs index 42c0afd..0e036a0 100644 --- a/src/v1/projects/filter/mod.rs +++ b/src/v1/projects/filter/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use serde_json::{Value, json}; use warp::Filter; @@ -398,8 +398,6 @@ async fn filter_category_handler(params: HashMap) -> Result) -> Result { - return Err(warp::reject::custom(NotImplementedError)); - return Ok(warp::reply::html("placeholder for compiler to be happy")); // Access the parameres from the HashMap let language = params.get("language").unwrap_or(&"".to_string()).to_string(); let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); @@ -428,7 +426,7 @@ async fn filter_language_handler(params: HashMap) -> Result(item.clone()).ok()) .filter(|project| project.visible) - //.filter(|project| project.languages.iter().any(|lang| lang.contains_key(language))) + .filter(|project| project.languages.contains_key(&language)) .collect() } _ => Vec::new(), @@ -441,38 +439,45 @@ async fn filter_language_handler(params: HashMap) -> Result().unwrap()).take(limit.parse::().unwrap()).collect::>(); // return the data - //Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) } async fn filter_getlangs_handler() -> Result { - return Err(warp::reject::custom(NotImplementedError)); - return Ok(warp::reply::html("placeholder for compiler to be happy")); // fetch the data let data = fetch_data().await.unwrap(); - // filter the data - // let filtered_data: Vec = match data { - // Value::Array(items) => { - // items - // .iter() - // .filter_map(|item| serde_json::from_value::(item.clone()).ok()) - // .filter(|project| project.visible) - // .collect() - // } - // _ => Vec::new(), - // }; + //filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .collect() + } + _ => Vec::new(), + }; // filter the data - /*let all_language_keys: Vec<&String> = filtered_data - .iter() - .flat_map(|project| project.languages.iter().flat_map(|lang_map| lang_map.keys())) - .collect();*/ + let mut languages_set: HashSet = HashSet::new(); + + for project in filtered_data { + for language in project.languages.keys() { + languages_set.insert(language.clone()); + } + } // get the total number of results - //let total_results = all_language_keys.len(); + let total_results = languages_set.len(); + + // json response + let json_response = json!({ + "results": languages_set, + "total_results": total_results, + }); // return the data - //Ok(warp::reply::json(&create_json_response(all_language_keys, total_results))) + Ok(warp::reply::json(&json_response)) } async fn filter_getstatuses_handler() -> Result { From 8e61e81f0620aec474d3afeb33c1e13327eb9f33 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Wed, 3 Jan 2024 12:00:54 +0100 Subject: [PATCH 12/36] bumped version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bf42ae..5a8ba3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,7 +520,7 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jonas_jones-api" -version = "0.2.0" +version = "0.2.1" dependencies = [ "chrono", "dotenv", diff --git a/Cargo.toml b/Cargo.toml index eec45f7..81222a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jonas_jones-api" -version = "0.2.0" +version = "0.2.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From e8d1b934d7d78f33b806c85ce8e0edc2a7227ef3 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Wed, 3 Jan 2024 12:01:22 +0100 Subject: [PATCH 13/36] code cleanup --- src/v1/projects/filter/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v1/projects/filter/mod.rs b/src/v1/projects/filter/mod.rs index 0e036a0..d200078 100644 --- a/src/v1/projects/filter/mod.rs +++ b/src/v1/projects/filter/mod.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use serde_json::{Value, json}; use warp::Filter; -use crate::{error_responses::{BadRequestError, NotImplementedError}, v1::projects::{fetch_data, create_json_response, Project as EntryProject}}; +use crate::{error_responses::BadRequestError, v1::projects::{fetch_data, create_json_response, Project as EntryProject}}; pub fn get_project_filter_routes() -> impl warp::Filter + Clone { warp::path("filter") From 5c6a2ac53f6f410e51506a1a4759903860880aae Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Thu, 4 Jan 2024 00:48:38 +0100 Subject: [PATCH 14/36] Added mcmod update branch --- Cargo.lock | 39 +++++ Cargo.toml | 1 + src/v1/updates/minecraft/mods/mod.rs | 151 +++++++++++++++--- .../updates/minecraft/mods/telemetry/mod.rs | 46 ++++++ 4 files changed, 219 insertions(+), 18 deletions(-) create mode 100644 src/v1/updates/minecraft/mods/telemetry/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 5a8ba3e..3d51ac3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -527,6 +536,7 @@ dependencies = [ "lastfm", "log", "parking_lot", + "regex", "reqwest", "serde", "serde_json", @@ -886,6 +896,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "reqwest" version = "0.11.22" diff --git a/Cargo.toml b/Cargo.toml index 81222a9..0d5ca30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ chrono = "0.4.31" toml = "0.8.8" reqwest = { version = "0.11.22", features = ["json"] } serde_json = "1.0.108" +regex = "1" diff --git a/src/v1/updates/minecraft/mods/mod.rs b/src/v1/updates/minecraft/mods/mod.rs index 1959f92..6f2faaf 100644 --- a/src/v1/updates/minecraft/mods/mod.rs +++ b/src/v1/updates/minecraft/mods/mod.rs @@ -1,29 +1,144 @@ +use std::collections::HashMap; +use std::net::IpAddr; +use reqwest::Error; +use serde_json::json; +use serde::{Deserialize, Serialize}; use warp::Filter; +use regex::Regex; + +use crate::error_responses::BadRequestError; pub fn get_mods_paths() -> impl warp::Filter + Clone { // any path that starts with /v1/updates/minecraft/mods/{modname}/{loadername}/{version} calls handle_path warp::path("v1").and(warp::path("updates")).and(warp::path("minecraft")).and(warp::path("mods")) - .and(warp::path::param()) - .and(warp::path::param()) - .and(warp::path::param()) - .and(warp::path::end()) - .and(warp::addr::remote()) - .map(handle_path) + .and(warp::get().and(warp::path::param()).and(warp::path::param()).and(warp::path::param()).and(warp::path::param()).and(warp::path::end()).and(warp::filters::header::headers_cloned()).and(warp::query::>()).and_then(handle_path)) } -fn handle_path(modname: String, loadername: String, version: String, remote_ip: Option) -> String { - format!("modname: {}, loadername: {}, version: {}, IP: {}", modname, loadername, version, remote_ip.unwrap_or(std::net::SocketAddr::from(([0, 0, 0, 0], 0))).ip()) +#[derive(Debug, Deserialize, Serialize)] +struct ModData { + package: String, + name: String, + versions: Vec>>, } -// fn handle_with_headers( -// headers: warp::http::HeaderMap, -// ) -> String { -// // Iterate through the headers and print them -// for (name, value) in headers.iter() { -// println!("Header: {}: {}", name, value.to_str().unwrap_or("Invalid UTF-8")); -// } +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ModVersion { + recommended: String, + latest: String, + all: Vec, +} -// // Respond with a message or perform other actions as needed -// "Headers received".to_string() -// } +// get json data from https://https://cdn.jonasjones.dev/api/mcmods/mcmod_metadata.json +pub async fn fetch_data() -> Result { + let url = "https://cdn.jonasjones.dev/api/mcmods/mcmod_metadata.json"; + let response = reqwest::get(url).await?; + + if response.status().is_success() { + // Parse the JSON response + let json_data: serde_json::Value = response.json().await?; + return Ok(json_data); + } else { + // Handle non-successful status codes + Err(response.error_for_status().unwrap_err()) + } +} + +fn is_valid_ip(ip_str: &str) -> bool { + if let Ok(ip) = ip_str.parse::() { + match ip { + IpAddr::V4(_) => true, + IpAddr::V6(_) => true, + } + } else { + false + } +} + +fn is_valid_minecraft_version(version: &str) -> bool { + // Define the regex pattern for the Minecraft version + let pattern = Regex::new(r"^1\.\d{1,2}(\.\d)?$").unwrap(); + + // Check if the provided version matches the pattern + pattern.is_match(version) +} + +fn get_header_forward_for_ip(headers: warp::http::HeaderMap) -> String { + // check if the header X-Forward-For exists and return the ip, if not, return an empty string + if let Some(forwarded_for) = headers.get("X-Forwarded-For") { + if let Ok(ip) = forwarded_for.to_str() { + // Extract the first IP address from the comma-separated list + if let Some(first_ip) = ip.split(',').next() { + return first_ip.trim().to_string(); + } + } + } + String::new() +} + +async fn handle_path(modpackage: String, loadername: String, mcversion: String, modversion: String, headers: warp::http::HeaderMap, params: HashMap) -> Result { + // Retrieve the IP from the header and check if it's valid + let mut client_ip = get_header_forward_for_ip(headers); + if !is_valid_ip(&client_ip) { + client_ip = params.get("ip").unwrap_or(&"".to_string()).to_string(); + if !is_valid_ip(&client_ip) { + client_ip = "Not valid".to_string(); + } + } + + // check if the minecraft version is valid + if !is_valid_minecraft_version(&mcversion) { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + // convert the raw list of data into a list of ModData and ModVersion + let mods_data: Vec = serde_json::from_value(data).unwrap(); + + // get the mod data from the requested mod + let mod_data: ModData = mods_data.into_iter().find(|mod_data| mod_data.package == modpackage).unwrap(); + + + // get the version data from the requested loader and remove the other loaders + let version_data: HashMap = mod_data.versions.into_iter().find(|version_data| version_data.contains_key(&loadername)).unwrap().remove(&loadername).unwrap(); + + // turn version_data into an object of String: ModVersion key value pairs + let version_data: HashMap = version_data.into_iter().map(|(key, value)| (key, value)).collect(); + + // get the version data for the current minecraft version + let version_data: ModVersion = version_data.get(&mcversion).unwrap().clone(); + + // get recommended and latest version + let recommended_version = version_data.recommended.clone(); + let latest_version = version_data.latest.clone(); + + // determine whether the client is up to date + let mut up_to_date = false; + if modversion == recommended_version { + up_to_date = true; + } + + // determine if telemetry is enabled by checking if the client_ip is valid + let mut telemetry = false; + if is_valid_ip(&client_ip) { + telemetry = true; + } + + // create the response + let response = json!({ + "promos": { + "latest": latest_version, + "recommended": recommended_version + }, + "upToDate": up_to_date, + "telemetry_enabled": telemetry + }); + + //TODO: Add way to process telemetry data + + // return the data + return Ok(warp::reply::json(&response)); +} diff --git a/src/v1/updates/minecraft/mods/telemetry/mod.rs b/src/v1/updates/minecraft/mods/telemetry/mod.rs new file mode 100644 index 0000000..dae1d5c --- /dev/null +++ b/src/v1/updates/minecraft/mods/telemetry/mod.rs @@ -0,0 +1,46 @@ +use std::fs::OpenOptions; +use std::io::{self, Write}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::path::Path; + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +struct IpInfo { + region: String, + // Add other fields as needed +} + +fn get_ip_hash(ip: &str) -> u64 { + let mut hasher = DefaultHasher::new(); + ip.hash(&mut hasher); + hasher.finish() +} + +pub fn log_ip_info(ip_address: &str, file_path: &str, mod_package: &str) -> io::Result<()> { + let ip_hash = get_ip_hash(ip_address); + + let ip_info = match get_ip_info(ip_address) { + Ok(info) => info, + Err(err) => { + IpInfo { region: "Unknown".to_string() } // Default to "Unknown" in case of an error + } + }; + + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(file_path)?; + + writeln!(file, "{} {} {}", ip_hash, ip_info.region, mod_package)?; + + Ok(()) +} + +fn main() { + let file_path = "ip_log.txt"; // Replace with your desired file path + + // Example usage + match log_ip_info("8.8.8.8", file_path) { + Err(err) => eprintln!("Error: {}", err), + } +} From 5f2026af0d413cbb5f3bf92e05f1e28ef869d28b Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Thu, 4 Jan 2024 00:49:32 +0100 Subject: [PATCH 15/36] version bump --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d51ac3..dd7d37c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -529,7 +529,7 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jonas_jones-api" -version = "0.2.1" +version = "0.3.0" dependencies = [ "chrono", "dotenv", diff --git a/Cargo.toml b/Cargo.toml index 0d5ca30..1321e14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jonas_jones-api" -version = "0.2.1" +version = "0.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From f201bc7fda0d564102214b7d2c7de798e1914ad3 Mon Sep 17 00:00:00 2001 From: Jonas_Jones <91549607+JonasunderscoreJones@users.noreply.github.com> Date: Sun, 28 Jan 2024 03:10:41 +0100 Subject: [PATCH 16/36] Added new branch /v1/update --- .gitignore | 1 + Cargo.lock | 157 ++++++++++++++++++++++++++- Cargo.toml | 3 +- src/v1/mod.rs | 6 +- src/v1/update/mod.rs | 248 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 409 insertions(+), 6 deletions(-) create mode 100644 src/v1/update/mod.rs diff --git a/.gitignore b/.gitignore index c371942..5fa16cd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /.vscode .env +/resources diff --git a/Cargo.lock b/Cargo.lock index dd7d37c..26fde4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,6 +135,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -346,6 +347,21 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "git2" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" +dependencies = [ + "bitflags 2.4.1", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + [[package]] name = "h2" version = "0.3.22" @@ -459,6 +475,20 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -527,12 +557,22 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "jonas_jones-api" version = "0.3.0" dependencies = [ "chrono", "dotenv", + "git2", "lastfm", "log", "parking_lot", @@ -556,9 +596,9 @@ dependencies = [ [[package]] name = "lastfm" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2b8e3a1ad7d64fae5e5542beccb5596402aafeb69c7259e3baa2dfdfa364e1" +checksum = "aed1b6ed29669ba37e7a2a8a939b7e2ff456883f0eb71a173635fae0ddc1cb5b" dependencies = [ "async-stream", "chrono", @@ -588,6 +628,46 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libgit2-sys" +version = "0.16.1+1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -940,6 +1020,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "hyper-tls", "ipnet", "js-sys", @@ -949,20 +1030,38 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -982,6 +1081,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -991,6 +1102,16 @@ dependencies = [ "base64", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.15" @@ -1018,6 +1139,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -1274,6 +1405,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -1464,6 +1605,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -1615,6 +1762,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 1321e14..6d174f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,11 @@ parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.35", features = ["macros"] } dotenv = "0.15.0" -lastfm = "0.7.0" +lastfm = "0.8.1" log = "0.4.20" chrono = "0.4.31" toml = "0.8.8" reqwest = { version = "0.11.22", features = ["json"] } serde_json = "1.0.108" regex = "1" +git2 = "0.18.1" diff --git a/src/v1/mod.rs b/src/v1/mod.rs index cd12a16..49c86a4 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -2,17 +2,17 @@ mod builtin; mod debug; mod kcomebacks; mod projects; -mod updates; +mod update; pub use builtin::get_builtin_routes as get_v1_builtin_routes; pub use debug::get_debug_routes as get_v1_debug_routes; pub use kcomebacks::get_kcomebacks_routes as get_v1_kcomebacks_routes; pub use projects::get_project_routes as get_v1_project_routes; -pub use updates::get_updates_routes as get_v1_updates_routes; +pub use update::get_update_routes as get_v1_updates_routes; use warp::Filter; -pub fn get_v1_routes() -> impl warp::Filter + Clone { +pub fn get_v1_routes() -> impl warp::Filter + Clone { return get_v1_builtin_routes() .or(get_v1_debug_routes()) .or(get_v1_kcomebacks_routes()) diff --git a/src/v1/update/mod.rs b/src/v1/update/mod.rs new file mode 100644 index 0000000..68e97dc --- /dev/null +++ b/src/v1/update/mod.rs @@ -0,0 +1,248 @@ +use std::fs; +use std::io::BufRead; +use std::process::{Stdio, Command}; + +use serde::{Deserialize, Serialize}; +use serde_json::json; +use tokio::sync::mpsc; +use tokio::task; +use warp::Filter; +use crate::error_responses::InternalServerError; +use crate::Logger; + +// DiscographyQuery +#[derive(Debug, Deserialize, Serialize)] +pub struct DiscographyQuery { + artists: String, +} + +pub fn get_update_routes() -> impl warp::Filter + Clone { + warp::path("v1").and(warp::path("update")) + // update/kcomebacks + // update/projects + // update/makediscography?artists=artist1,artist2,artist3 + // update/synclikedsongs + .and((warp::path("kcomebacks").and_then(update_kcomebacks)) + .or(warp::path("projects").and_then(update_projects)) + .or(warp::path("makediscography").map(||"Not implemented yet")) + .or(warp::path("synclikedsongs").and_then(sync_liked_songs)) + ) +} + +async fn update_kcomebacks() -> Result { + // check if the local repository exists, if not, clone it + if !fs::metadata("./resources/turbo_octo_potato").is_ok() { + setup().unwrap(); + }; + + if let Err(err) = run_kcomebacks_command() { + // Handle the error here + eprintln!("Error: {}", err); + // Return an appropriate response or error + return Err(warp::reject::custom(InternalServerError)); + } + + Ok(warp::reply::json(&json!({"status": "updating..."}))) + +} + +async fn update_projects() -> Result { + // check if the local repository exists, if not, clone it + if !fs::metadata("./resources/turbo_octo_potato").is_ok() { + setup().unwrap(); + }; + + if let Err(err) = run_projects_command() { + // Handle the error here + eprintln!("Error: {}", err); + // Return an appropriate response or error + return Err(warp::reject::custom(InternalServerError)); + } + + Ok(warp::reply::json(&json!({"status": "updating..."}))) + +} + +async fn sync_liked_songs() -> Result { + // check if the local repository exists, if not, clone it + if !fs::metadata("./resources/turbo_octo_potato").is_ok() { + setup().unwrap(); + }; + + if let Err(err) = run_likedsongs_command() { + // Handle the error here + eprintln!("Error: {}", err); + // Return an appropriate response or error + return Err(warp::reject::custom(InternalServerError)); + } + + Ok(warp::reply::json(&json!({"status": "updating..."}))) + +} + +fn setup() -> Result<(), git2::Error> { + let repository_url = "https://github.com/JonasunderscoreJones/turbo-octo-potato.git"; + let local_directory = "resources/turbo_octo_potato"; + + git2::Repository::clone(repository_url, local_directory)?; + + Ok(()) +} + +// fn run_command() -> Result<(), std::io::Error> { +// let (tx, mut rx) = mpsc::channel(1); + +// task::spawn_blocking(move || { +// let mut child = Command::new("python3") +// .arg(&py_file) +// .arg(&args) +// .current_dir("resources/turbo_octo_potato") +// .stdout(Stdio::piped()) +// .spawn() +// .expect("failed to execute child"); + +// let stdout = child.stdout.as_mut().unwrap(); + +// let mut reader = std::io::BufReader::new(stdout); + +// let mut line = String::new(); + +// loop { +// let len = reader.read_line(&mut line).unwrap(); +// if len == 0 { +// break; +// } +// tx.blocking_send(line.clone()).unwrap(); +// line.clear(); +// } + +// child.wait().unwrap(); +// }); + +// task::spawn(async move { +// while let Some(line) = rx.recv().await { +// println!("{}", line); +// } +// }); + +// Ok(()) +// } + +// run_command with python file and args as parameters + + +fn run_kcomebacks_command() -> Result<(), std::io::Error> { + let (tx, mut rx) = mpsc::channel(1); + + task::spawn_blocking(move || { + let mut child = Command::new("python3") + .arg("rpopfetch.py") + .arg("--cdn") + .current_dir("resources/turbo_octo_potato") + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + + let stdout = child.stdout.as_mut().unwrap(); + + let mut reader = std::io::BufReader::new(stdout); + + let mut line = String::new(); + + loop { + let len = reader.read_line(&mut line).unwrap(); + if len == 0 { + break; + } + tx.blocking_send(line.clone()).unwrap(); + line.clear(); + } + + child.wait().unwrap(); + }); + + task::spawn(async move { + while let Some(line) = rx.recv().await { + Logger::info(&format!("[/v1/kcomebacks/update]: {}", line)); + } + }); + + Ok(()) +} + +fn run_projects_command() -> Result<(), std::io::Error> { + let (tx, mut rx) = mpsc::channel(1); + + task::spawn_blocking(move || { + let mut child = Command::new("python3") + .arg("update_projects.py") + .arg("--cdn") + .current_dir("resources/turbo_octo_potato") + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + + let stdout = child.stdout.as_mut().unwrap(); + + let mut reader = std::io::BufReader::new(stdout); + + let mut line = String::new(); + + loop { + let len = reader.read_line(&mut line).unwrap(); + if len == 0 { + break; + } + tx.blocking_send(line.clone()).unwrap(); + line.clear(); + } + + child.wait().unwrap(); + }); + + task::spawn(async move { + while let Some(line) = rx.recv().await { + Logger::info(&format!("[/v1/projects/update]: {}", line)); + } + }); + + Ok(()) +} + +fn run_likedsongs_command() -> Result<(), std::io::Error> { + let (tx, mut rx) = mpsc::channel(1); + + task::spawn_blocking(move || { + let mut child = Command::new("python3") + .arg("likedsongsync2.py") + .current_dir("resources/turbo_octo_potato") + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + + let stdout = child.stdout.as_mut().unwrap(); + + let mut reader = std::io::BufReader::new(stdout); + + let mut line = String::new(); + + loop { + let len = reader.read_line(&mut line).unwrap(); + if len == 0 { + break; + } + tx.blocking_send(line.clone()).unwrap(); + line.clear(); + } + + child.wait().unwrap(); + }); + + task::spawn(async move { + while let Some(line) = rx.recv().await { + Logger::info(&format!("[/v1/synclikedsongs]: {}", line)); + } + }); + + Ok(()) +} \ No newline at end of file From 462ee687636333f8f65ce0b82afac3ab1eeb92b2 Mon Sep 17 00:00:00 2001 From: Jonas_Jones <91549607+JonasunderscoreJones@users.noreply.github.com> Date: Sun, 28 Jan 2024 03:10:49 +0100 Subject: [PATCH 17/36] Fixed warnings --- src/v1/builtin/mod.rs | 2 +- src/v1/debug/mod.rs | 2 +- src/v1/kcomebacks/filter/mod.rs | 2 +- src/v1/kcomebacks/mod.rs | 2 +- src/v1/kcomebacks/upcoming/mod.rs | 2 +- src/v1/projects/filter/mod.rs | 2 +- src/v1/projects/mod.rs | 5 +++-- 7 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/v1/builtin/mod.rs b/src/v1/builtin/mod.rs index f975734..5ddb2de 100644 --- a/src/v1/builtin/mod.rs +++ b/src/v1/builtin/mod.rs @@ -1,6 +1,6 @@ use warp::Filter; -pub fn get_builtin_routes() -> impl warp::Filter + Clone { +pub fn get_builtin_routes() -> impl warp::Filter + Clone { warp::path("v1") .and((warp::path("help").map(|| "Please refer to the wiki at https://wiki.jonasjones.dev/Api/")) .or(warp::path("ping").map(|| "pong")) diff --git a/src/v1/debug/mod.rs b/src/v1/debug/mod.rs index 44256a0..db85e7a 100644 --- a/src/v1/debug/mod.rs +++ b/src/v1/debug/mod.rs @@ -1,6 +1,6 @@ use warp::Filter; -pub fn get_debug_routes() -> impl warp::Filter + Clone { +pub fn get_debug_routes() -> impl warp::Filter + Clone { warp::path("debug") .and(warp::path("headers").and(warp::header::headers_cloned().map(handle_with_headers))) } diff --git a/src/v1/kcomebacks/filter/mod.rs b/src/v1/kcomebacks/filter/mod.rs index 158bac2..5d6fb1b 100644 --- a/src/v1/kcomebacks/filter/mod.rs +++ b/src/v1/kcomebacks/filter/mod.rs @@ -6,7 +6,7 @@ use warp::Filter; use crate::{error_responses::{BadRequestError, InternalServerError}, v1::kcomebacks::{fetch_data, create_json_response, parse_item, Item as EntryItem}}; -pub fn get_kcomebacks_filter_routes() -> impl warp::Filter + Clone { +pub fn get_kcomebacks_filter_routes() -> impl warp::Filter + Clone { warp::path("filter") .and((warp::path("id").and(warp::get()).and(warp::query::>()).and_then(filter_id_handler)) .or(warp::path("getall").and(warp::get()).and(warp::query::>()).and_then(filter_getall_handler)) diff --git a/src/v1/kcomebacks/mod.rs b/src/v1/kcomebacks/mod.rs index 6ddb102..0a985a3 100644 --- a/src/v1/kcomebacks/mod.rs +++ b/src/v1/kcomebacks/mod.rs @@ -10,7 +10,7 @@ use filter::get_kcomebacks_filter_routes; use upcoming::get_kcomebacks_upcoming_routes; use crate::error_responses::InternalServerError; -pub fn get_kcomebacks_routes() -> impl warp::Filter + Clone { +pub fn get_kcomebacks_routes() -> impl warp::Filter + Clone { warp::path("v1").and(warp::path("kcomebacks")) .and(warp::path("last_update").and(warp::get()).and_then(last_update) diff --git a/src/v1/kcomebacks/upcoming/mod.rs b/src/v1/kcomebacks/upcoming/mod.rs index 0e29b44..6f23aff 100644 --- a/src/v1/kcomebacks/upcoming/mod.rs +++ b/src/v1/kcomebacks/upcoming/mod.rs @@ -4,7 +4,7 @@ use warp::Filter; use crate::v1::kcomebacks::filter::filter_daterange_handler; -pub fn get_kcomebacks_upcoming_routes() -> impl warp::Filter + Clone { +pub fn get_kcomebacks_upcoming_routes() -> impl warp::Filter + Clone { warp::path("upcoming") .and((warp::path("today").and(warp::get()).and(warp::query::>()).and_then(upcoming_today_handler)) .or(warp::path("week").and(warp::get()).and(warp::query::>()).and_then(upcoming_week_handler)) diff --git a/src/v1/projects/filter/mod.rs b/src/v1/projects/filter/mod.rs index d200078..952b81c 100644 --- a/src/v1/projects/filter/mod.rs +++ b/src/v1/projects/filter/mod.rs @@ -5,7 +5,7 @@ use warp::Filter; use crate::{error_responses::BadRequestError, v1::projects::{fetch_data, create_json_response, Project as EntryProject}}; -pub fn get_project_filter_routes() -> impl warp::Filter + Clone { +pub fn get_project_filter_routes() -> impl warp::Filter + Clone { warp::path("filter") .and((warp::path("getall").and(warp::get()).and(warp::query::>()).and_then(filter_getall_handler)) .or(warp::path("lastupdaterange").and(warp::get()).and(warp::query::>()).and_then(filter_lastupdaterange_handler)) diff --git a/src/v1/projects/mod.rs b/src/v1/projects/mod.rs index 61daf55..55689b1 100644 --- a/src/v1/projects/mod.rs +++ b/src/v1/projects/mod.rs @@ -10,12 +10,13 @@ use filter::get_project_filter_routes; use crate::error_responses::InternalServerError; -pub fn get_project_routes() -> impl warp::Filter + Clone { +pub fn get_project_routes() -> impl warp::Filter + Clone { warp::path("v1").and(warp::path("projects")) .and(warp::path("last_update").and(warp::get()).and_then(last_update) .or(warp::path("start_update").map(|| "Not implemented yet")) - .or(get_project_filter_routes())) + .or(get_project_filter_routes()) + ) } // get json data from https://https://cdn.jonasjones.dev/api/projects/projects.json From c18ab8a8da4e604bb291a67dbc6cfb95eb8a655a Mon Sep 17 00:00:00 2001 From: Jonas_Jones <91549607+JonasunderscoreJones@users.noreply.github.com> Date: Sun, 28 Jan 2024 03:27:38 +0100 Subject: [PATCH 18/36] reverted changes --- src/v1/builtin/mod.rs | 2 +- src/v1/debug/mod.rs | 2 +- src/v1/kcomebacks/filter/mod.rs | 2 +- src/v1/kcomebacks/mod.rs | 2 +- src/v1/kcomebacks/upcoming/mod.rs | 2 +- src/v1/mod.rs | 2 +- src/v1/projects/filter/mod.rs | 2 +- src/v1/projects/mod.rs | 2 +- src/v1/update/mod.rs | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/v1/builtin/mod.rs b/src/v1/builtin/mod.rs index 5ddb2de..f975734 100644 --- a/src/v1/builtin/mod.rs +++ b/src/v1/builtin/mod.rs @@ -1,6 +1,6 @@ use warp::Filter; -pub fn get_builtin_routes() -> impl warp::Filter + Clone { +pub fn get_builtin_routes() -> impl warp::Filter + Clone { warp::path("v1") .and((warp::path("help").map(|| "Please refer to the wiki at https://wiki.jonasjones.dev/Api/")) .or(warp::path("ping").map(|| "pong")) diff --git a/src/v1/debug/mod.rs b/src/v1/debug/mod.rs index db85e7a..44256a0 100644 --- a/src/v1/debug/mod.rs +++ b/src/v1/debug/mod.rs @@ -1,6 +1,6 @@ use warp::Filter; -pub fn get_debug_routes() -> impl warp::Filter + Clone { +pub fn get_debug_routes() -> impl warp::Filter + Clone { warp::path("debug") .and(warp::path("headers").and(warp::header::headers_cloned().map(handle_with_headers))) } diff --git a/src/v1/kcomebacks/filter/mod.rs b/src/v1/kcomebacks/filter/mod.rs index 5d6fb1b..158bac2 100644 --- a/src/v1/kcomebacks/filter/mod.rs +++ b/src/v1/kcomebacks/filter/mod.rs @@ -6,7 +6,7 @@ use warp::Filter; use crate::{error_responses::{BadRequestError, InternalServerError}, v1::kcomebacks::{fetch_data, create_json_response, parse_item, Item as EntryItem}}; -pub fn get_kcomebacks_filter_routes() -> impl warp::Filter + Clone { +pub fn get_kcomebacks_filter_routes() -> impl warp::Filter + Clone { warp::path("filter") .and((warp::path("id").and(warp::get()).and(warp::query::>()).and_then(filter_id_handler)) .or(warp::path("getall").and(warp::get()).and(warp::query::>()).and_then(filter_getall_handler)) diff --git a/src/v1/kcomebacks/mod.rs b/src/v1/kcomebacks/mod.rs index 0a985a3..6ddb102 100644 --- a/src/v1/kcomebacks/mod.rs +++ b/src/v1/kcomebacks/mod.rs @@ -10,7 +10,7 @@ use filter::get_kcomebacks_filter_routes; use upcoming::get_kcomebacks_upcoming_routes; use crate::error_responses::InternalServerError; -pub fn get_kcomebacks_routes() -> impl warp::Filter + Clone { +pub fn get_kcomebacks_routes() -> impl warp::Filter + Clone { warp::path("v1").and(warp::path("kcomebacks")) .and(warp::path("last_update").and(warp::get()).and_then(last_update) diff --git a/src/v1/kcomebacks/upcoming/mod.rs b/src/v1/kcomebacks/upcoming/mod.rs index 6f23aff..0e29b44 100644 --- a/src/v1/kcomebacks/upcoming/mod.rs +++ b/src/v1/kcomebacks/upcoming/mod.rs @@ -4,7 +4,7 @@ use warp::Filter; use crate::v1::kcomebacks::filter::filter_daterange_handler; -pub fn get_kcomebacks_upcoming_routes() -> impl warp::Filter + Clone { +pub fn get_kcomebacks_upcoming_routes() -> impl warp::Filter + Clone { warp::path("upcoming") .and((warp::path("today").and(warp::get()).and(warp::query::>()).and_then(upcoming_today_handler)) .or(warp::path("week").and(warp::get()).and(warp::query::>()).and_then(upcoming_week_handler)) diff --git a/src/v1/mod.rs b/src/v1/mod.rs index 49c86a4..80e064a 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -12,7 +12,7 @@ pub use update::get_update_routes as get_v1_updates_routes; use warp::Filter; -pub fn get_v1_routes() -> impl warp::Filter + Clone { +pub fn get_v1_routes() -> impl warp::Filter + Clone { return get_v1_builtin_routes() .or(get_v1_debug_routes()) .or(get_v1_kcomebacks_routes()) diff --git a/src/v1/projects/filter/mod.rs b/src/v1/projects/filter/mod.rs index 952b81c..d200078 100644 --- a/src/v1/projects/filter/mod.rs +++ b/src/v1/projects/filter/mod.rs @@ -5,7 +5,7 @@ use warp::Filter; use crate::{error_responses::BadRequestError, v1::projects::{fetch_data, create_json_response, Project as EntryProject}}; -pub fn get_project_filter_routes() -> impl warp::Filter + Clone { +pub fn get_project_filter_routes() -> impl warp::Filter + Clone { warp::path("filter") .and((warp::path("getall").and(warp::get()).and(warp::query::>()).and_then(filter_getall_handler)) .or(warp::path("lastupdaterange").and(warp::get()).and(warp::query::>()).and_then(filter_lastupdaterange_handler)) diff --git a/src/v1/projects/mod.rs b/src/v1/projects/mod.rs index 55689b1..777e69e 100644 --- a/src/v1/projects/mod.rs +++ b/src/v1/projects/mod.rs @@ -10,7 +10,7 @@ use filter::get_project_filter_routes; use crate::error_responses::InternalServerError; -pub fn get_project_routes() -> impl warp::Filter + Clone { +pub fn get_project_routes() -> impl warp::Filter + Clone { warp::path("v1").and(warp::path("projects")) .and(warp::path("last_update").and(warp::get()).and_then(last_update) diff --git a/src/v1/update/mod.rs b/src/v1/update/mod.rs index 68e97dc..abb148f 100644 --- a/src/v1/update/mod.rs +++ b/src/v1/update/mod.rs @@ -16,7 +16,7 @@ pub struct DiscographyQuery { artists: String, } -pub fn get_update_routes() -> impl warp::Filter + Clone { +pub fn get_update_routes() -> impl warp::Filter + Clone { warp::path("v1").and(warp::path("update")) // update/kcomebacks // update/projects From a851b18875c5cb25585c9d025568e87e9e52c6fe Mon Sep 17 00:00:00 2001 From: Jonas_Jones <91549607+JonasunderscoreJones@users.noreply.github.com> Date: Sun, 28 Jan 2024 03:31:10 +0100 Subject: [PATCH 19/36] changed update to run path --- src/v1/mod.rs | 2 +- src/v1/update/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/v1/mod.rs b/src/v1/mod.rs index 80e064a..d0ff506 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -8,7 +8,7 @@ pub use builtin::get_builtin_routes as get_v1_builtin_routes; pub use debug::get_debug_routes as get_v1_debug_routes; pub use kcomebacks::get_kcomebacks_routes as get_v1_kcomebacks_routes; pub use projects::get_project_routes as get_v1_project_routes; -pub use update::get_update_routes as get_v1_updates_routes; +pub use update::get_run_routes as get_v1_updates_routes; use warp::Filter; diff --git a/src/v1/update/mod.rs b/src/v1/update/mod.rs index abb148f..ffa3153 100644 --- a/src/v1/update/mod.rs +++ b/src/v1/update/mod.rs @@ -16,8 +16,8 @@ pub struct DiscographyQuery { artists: String, } -pub fn get_update_routes() -> impl warp::Filter + Clone { - warp::path("v1").and(warp::path("update")) +pub fn get_run_routes() -> impl warp::Filter + Clone { + warp::path("v1").and(warp::path("run")) // update/kcomebacks // update/projects // update/makediscography?artists=artist1,artist2,artist3 From 852e184b89ac6a6ed5511d0c85a70fb5f2b6dc97 Mon Sep 17 00:00:00 2001 From: Jonas_Jones <91549607+JonasunderscoreJones@users.noreply.github.com> Date: Sun, 28 Jan 2024 03:38:11 +0100 Subject: [PATCH 20/36] removed console spam --- src/v1/update/mod.rs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/v1/update/mod.rs b/src/v1/update/mod.rs index ffa3153..15a7f01 100644 --- a/src/v1/update/mod.rs +++ b/src/v1/update/mod.rs @@ -161,11 +161,12 @@ fn run_kcomebacks_command() -> Result<(), std::io::Error> { child.wait().unwrap(); }); - task::spawn(async move { - while let Some(line) = rx.recv().await { - Logger::info(&format!("[/v1/kcomebacks/update]: {}", line)); - } - }); + // task::spawn(async move { + // while let Some(line) = rx.recv().await { + // Logger::info(&format!("[/v1/kcomebacks/update]: {}", line)); + // } + // }); + Logger::info("kcomebacks updated"); Ok(()) } @@ -200,11 +201,12 @@ fn run_projects_command() -> Result<(), std::io::Error> { child.wait().unwrap(); }); - task::spawn(async move { - while let Some(line) = rx.recv().await { - Logger::info(&format!("[/v1/projects/update]: {}", line)); - } - }); + // task::spawn(async move { + // while let Some(line) = rx.recv().await { + // Logger::info(&format!("[/v1/projects/update]: {}", line)); + // } + // }); + Logger::info("projects updated"); Ok(()) } @@ -238,11 +240,12 @@ fn run_likedsongs_command() -> Result<(), std::io::Error> { child.wait().unwrap(); }); - task::spawn(async move { - while let Some(line) = rx.recv().await { - Logger::info(&format!("[/v1/synclikedsongs]: {}", line)); - } - }); + // task::spawn(async move { + // while let Some(line) = rx.recv().await { + // Logger::info(&format!("[/v1/synclikedsongs]: {}", line)); + // } + // }); + Logger::info("liked songs synced"); Ok(()) } \ No newline at end of file From 4c62ba284508075bb20f3417154eaee073504c2e Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Sun, 11 Feb 2024 06:28:44 +0100 Subject: [PATCH 21/36] added /status route --- src/server.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/server.rs b/src/server.rs index 7aa133e..cdf5e6d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -20,9 +20,13 @@ pub async fn serve() { let favicon = warp::path("favicon.ico").and(warp::fs::file("./src/favicon.png")); + // /status => 200 OK + let status = warp::path("status") + .map(|| warp::reply()); + // GET (any) => reply with return from handle_path - let routes = favicon.or(get_v1_routes()) - .recover(handle_rejection); + let routes = favicon.or(status.or(get_v1_routes()) + .recover(handle_rejection)); async fn handle_rejection(err: warp::Rejection) -> Result { From bdfd5a74a69502d3771518eae1f8f1b291ac5997 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Sun, 11 Feb 2024 08:44:20 +0100 Subject: [PATCH 22/36] Added docker compose instructions --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 5cbba9f..f4f1748 100644 --- a/README.md +++ b/README.md @@ -34,3 +34,24 @@ export API_IP={ip_address} export LASTFM_API_KEY={lastfm_api_key} export LASTFM_API_SECRET={lastfm_api_secret} ``` + +## Docker Compose + +`docker-compose.yaml`: +```yaml +version: '3.8' +services: + arch-linux: + image: archlinux:latest + container_name: jonas_jones-api + ports: + - "3030:3030" + volumes: + - /home/jonas_jones/jonas_jones-api:/home/jonas_jones/jonas_jones-api + command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl cargo && cd /home/jonas_jones/jonas_jones-api && /usr/bin/cargo run"] +``` + +run container: +```sh +docker-compose up -d +``` From 7bee1f5bae27aeebc31332eea9ed613dfc6dc17f Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Mon, 12 Feb 2024 03:46:08 +0100 Subject: [PATCH 23/36] added update runner thread --- src/main.rs | 35 ++++++++++++++- src/v1/mod.rs | 9 +++- src/v1/{update => run}/mod.rs | 84 +++++++++++++++++------------------ 3 files changed, 82 insertions(+), 46 deletions(-) rename src/v1/{update => run}/mod.rs (77%) diff --git a/src/main.rs b/src/main.rs index 86eb624..a55572c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,7 @@ use dotenv::dotenv; +use tokio::time::{sleep, Duration}; +use v1::{run_kcomebacks_command, run_likedsongs_command, run_projects_command}; +use std::fs; pub mod v1; pub mod logger; @@ -10,10 +13,38 @@ pub use logger::Logger; pub use tools::parse_ip; +async fn periodic_script_runner() { + loop { + Logger::info("Running periodic scripts..."); + // Run all Functions + let _ = run_kcomebacks_command(); + let _ = run_projects_command(); + let _ = run_likedsongs_command(); + + // Sleep for 6 hours + sleep(Duration::from_secs(6 * 60 * 60)).await; + } +} + #[tokio::main(flavor = "current_thread")] async fn main() { - // load .env file + // Load .env file dotenv().ok(); - server::serve().await; + // Start the api + let server_task = tokio::spawn(async { + server::serve().await; + }); + + // periodic script runner + let second_task = tokio::spawn(async { + // check if the local repository exists, if not, clone it + if !fs::metadata("./resources/turbo_octo_potato").is_ok() { + v1::run_setup().unwrap(); + }; + periodic_script_runner().await; + }); + + // Wait for both tasks to complete + let _ = tokio::try_join!(server_task, second_task); } diff --git a/src/v1/mod.rs b/src/v1/mod.rs index d0ff506..ff4bf8f 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -2,13 +2,18 @@ mod builtin; mod debug; mod kcomebacks; mod projects; -mod update; +mod run; + +pub use run::setup as run_setup; +pub use run::run_kcomebacks_command; +pub use run::run_projects_command; +pub use run::run_likedsongs_command; pub use builtin::get_builtin_routes as get_v1_builtin_routes; pub use debug::get_debug_routes as get_v1_debug_routes; pub use kcomebacks::get_kcomebacks_routes as get_v1_kcomebacks_routes; pub use projects::get_project_routes as get_v1_project_routes; -pub use update::get_run_routes as get_v1_updates_routes; +pub use run::get_run_routes as get_v1_updates_routes; use warp::Filter; diff --git a/src/v1/update/mod.rs b/src/v1/run/mod.rs similarity index 77% rename from src/v1/update/mod.rs rename to src/v1/run/mod.rs index 15a7f01..849c133 100644 --- a/src/v1/update/mod.rs +++ b/src/v1/run/mod.rs @@ -1,10 +1,10 @@ use std::fs; -use std::io::BufRead; +// use std::io::BufRead; use std::process::{Stdio, Command}; use serde::{Deserialize, Serialize}; use serde_json::json; -use tokio::sync::mpsc; +// use tokio::sync::mpsc; use tokio::task; use warp::Filter; use crate::error_responses::InternalServerError; @@ -80,7 +80,7 @@ async fn sync_liked_songs() -> Result { } -fn setup() -> Result<(), git2::Error> { +pub fn setup() -> Result<(), git2::Error> { let repository_url = "https://github.com/JonasunderscoreJones/turbo-octo-potato.git"; let local_directory = "resources/turbo_octo_potato"; @@ -131,8 +131,8 @@ fn setup() -> Result<(), git2::Error> { // run_command with python file and args as parameters -fn run_kcomebacks_command() -> Result<(), std::io::Error> { - let (tx, mut rx) = mpsc::channel(1); +pub fn run_kcomebacks_command() -> Result<(), std::io::Error> { + // let (tx, mut rx) = mpsc::channel(1); task::spawn_blocking(move || { let mut child = Command::new("python3") @@ -143,20 +143,20 @@ fn run_kcomebacks_command() -> Result<(), std::io::Error> { .spawn() .expect("failed to execute child"); - let stdout = child.stdout.as_mut().unwrap(); + // let stdout = child.stdout.as_mut().unwrap(); - let mut reader = std::io::BufReader::new(stdout); + // let mut reader = std::io::BufReader::new(stdout); - let mut line = String::new(); + // let mut line = String::new(); - loop { - let len = reader.read_line(&mut line).unwrap(); - if len == 0 { - break; - } - tx.blocking_send(line.clone()).unwrap(); - line.clear(); - } + // loop { + // let len = reader.read_line(&mut line).unwrap(); + // if len == 0 { + // break; + // } + // tx.blocking_send(line.clone()).unwrap(); + // line.clear(); + // } child.wait().unwrap(); }); @@ -171,8 +171,8 @@ fn run_kcomebacks_command() -> Result<(), std::io::Error> { Ok(()) } -fn run_projects_command() -> Result<(), std::io::Error> { - let (tx, mut rx) = mpsc::channel(1); +pub fn run_projects_command() -> Result<(), std::io::Error> { + // let (tx, mut rx) = mpsc::channel(1); task::spawn_blocking(move || { let mut child = Command::new("python3") @@ -183,20 +183,20 @@ fn run_projects_command() -> Result<(), std::io::Error> { .spawn() .expect("failed to execute child"); - let stdout = child.stdout.as_mut().unwrap(); + // let stdout = child.stdout.as_mut().unwrap(); - let mut reader = std::io::BufReader::new(stdout); + // let mut reader = std::io::BufReader::new(stdout); - let mut line = String::new(); + // let mut line = String::new(); - loop { - let len = reader.read_line(&mut line).unwrap(); - if len == 0 { - break; - } - tx.blocking_send(line.clone()).unwrap(); - line.clear(); - } + // loop { + // let len = reader.read_line(&mut line).unwrap(); + // if len == 0 { + // break; + // } + // tx.blocking_send(line.clone()).unwrap(); + // line.clear(); + // } child.wait().unwrap(); }); @@ -211,8 +211,8 @@ fn run_projects_command() -> Result<(), std::io::Error> { Ok(()) } -fn run_likedsongs_command() -> Result<(), std::io::Error> { - let (tx, mut rx) = mpsc::channel(1); +pub fn run_likedsongs_command() -> Result<(), std::io::Error> { + // let (tx, mut rx) = mpsc::channel(1); task::spawn_blocking(move || { let mut child = Command::new("python3") @@ -222,20 +222,20 @@ fn run_likedsongs_command() -> Result<(), std::io::Error> { .spawn() .expect("failed to execute child"); - let stdout = child.stdout.as_mut().unwrap(); + // let stdout = child.stdout.as_mut().unwrap(); - let mut reader = std::io::BufReader::new(stdout); + // let mut reader = std::io::BufReader::new(stdout); - let mut line = String::new(); + // let mut line = String::new(); - loop { - let len = reader.read_line(&mut line).unwrap(); - if len == 0 { - break; - } - tx.blocking_send(line.clone()).unwrap(); - line.clear(); - } + // loop { + // let len = reader.read_line(&mut line).unwrap(); + // if len == 0 { + // break; + // } + // tx.blocking_send(line.clone()).unwrap(); + // line.clear(); + // } child.wait().unwrap(); }); From 5e9ed3738c3418969d13ebb7916ec3b377eaad98 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Mon, 12 Feb 2024 03:49:41 +0100 Subject: [PATCH 24/36] fixed logs --- src/v1/run/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/v1/run/mod.rs b/src/v1/run/mod.rs index 849c133..9861d53 100644 --- a/src/v1/run/mod.rs +++ b/src/v1/run/mod.rs @@ -166,7 +166,7 @@ pub fn run_kcomebacks_command() -> Result<(), std::io::Error> { // Logger::info(&format!("[/v1/kcomebacks/update]: {}", line)); // } // }); - Logger::info("kcomebacks updated"); + Logger::info("Updating kcomebacks..."); Ok(()) } @@ -206,7 +206,7 @@ pub fn run_projects_command() -> Result<(), std::io::Error> { // Logger::info(&format!("[/v1/projects/update]: {}", line)); // } // }); - Logger::info("projects updated"); + Logger::info("Updating projects..."); Ok(()) } @@ -245,7 +245,7 @@ pub fn run_likedsongs_command() -> Result<(), std::io::Error> { // Logger::info(&format!("[/v1/synclikedsongs]: {}", line)); // } // }); - Logger::info("liked songs synced"); + Logger::info("Syncing liked songs..."); Ok(()) } \ No newline at end of file From 48da0d7f95a3c2e28e50be6aa839151420d299ae Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Mon, 12 Feb 2024 04:05:44 +0100 Subject: [PATCH 25/36] Fixed docker compose and python lib requirements --- README.md | 2 +- requirements.txt | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 requirements.txt diff --git a/README.md b/README.md index f4f1748..43d7ecd 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ services: - "3030:3030" volumes: - /home/jonas_jones/jonas_jones-api:/home/jonas_jones/jonas_jones-api - command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl cargo && cd /home/jonas_jones/jonas_jones-api && /usr/bin/cargo run"] + command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl python3 python-pip cargo && pip install -r requirements.txt && cd /home/jonas_jones/jonas_jones-api && /usr/bin/cargo run"] ``` run container: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..560c864 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +os +sys +json +time +requests +datetime +python-dotenv +spotipy +praw +re +spotipy +pylast +typing \ No newline at end of file From a63f933dd94f8c244dfbe75a4189ca9605e9a4a7 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Mon, 12 Feb 2024 04:21:05 +0100 Subject: [PATCH 26/36] Fixed dependencies --- requirements.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 560c864..ce908f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,9 @@ -os -sys -json -time requests datetime python-dotenv spotipy praw -re spotipy pylast -typing \ No newline at end of file +typing +markdown \ No newline at end of file From 4ecbd7aa98d774c457ac56e9171457cdb3de4bc5 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Mon, 12 Feb 2024 04:32:37 +0100 Subject: [PATCH 27/36] fixed docker compose --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 43d7ecd..6b0acca 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ export LASTFM_API_SECRET={lastfm_api_secret} ## Docker Compose -`docker-compose.yaml`: +`docker-compose.yaml` (folder paths need adjusting): ```yaml version: '3.8' services: @@ -48,7 +48,8 @@ services: - "3030:3030" volumes: - /home/jonas_jones/jonas_jones-api:/home/jonas_jones/jonas_jones-api - command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl python3 python-pip cargo && pip install -r requirements.txt && cd /home/jonas_jones/jonas_jones-api && /usr/bin/cargo run"] + - /home/jonas_jones/.config/rclone/:/root/.config/rclone/ + command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl python3 python-pip rclone cargo && python3 -m venv api-venv && source api-venv/bin/activate && cd /home/jonas_jones/jonas_jones-api && pip install -r requirements.txt && /usr/bin/cargo run"] ``` run container: From 1cdb34503d82470215f004c1aec1722cfb485c1e Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Mon, 12 Feb 2024 04:34:34 +0100 Subject: [PATCH 28/36] Added docker-compose file --- docker-compose.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 docker-compose.yaml diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..3a3650d --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,11 @@ +version: '3.8' +services: + arch-linux: + image: archlinux:latest + container_name: jonas_jones-api + ports: + - "3030:3030" + volumes: + - /home/jonas_jones/jonas_jones-api:/home/jonas_jones/jonas_jones-api + - /home/jonas_jones/.config/rclone/:/root/.config/rclone/ + command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl python3 python-pip rclone cargo && python3 -m venv api-venv && source api-venv/bin/activate && cd /home/jonas_jones/jonas_jones-api && pip install -r requirements.txt && /usr/bin/cargo run"] \ No newline at end of file From 3b5378c6f280c20677bb4b1561734888a8936709 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Mon, 12 Feb 2024 04:41:23 +0100 Subject: [PATCH 29/36] Fixed version (0verdue!!!) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6d174f1..c2c6cb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jonas_jones-api" -version = "0.3.0" +version = "0.4.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From d2a89d506713a686f4254772031da1b9cc347368 Mon Sep 17 00:00:00 2001 From: Jonas_Jones Date: Wed, 14 Feb 2024 03:41:25 +0100 Subject: [PATCH 30/36] roadmap draft --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 6b0acca..4c7b7b1 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,12 @@ run container: ```sh docker-compose up -d ``` + +## Roadmap + +- analytics backend. track request origin through IP from header (store IP hash, region and time) +- rewrite all scripts in rust +- DB implementation for projects, kcomebacks, minecraft mod versions +- session backend, auth token system +- implementation for dashboard front-end with analytics/config +- complete minecraft mod implementation \ No newline at end of file From 41fa898a6729c86f850a71ca9e20bfcff04d5a77 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Wed, 15 May 2024 13:10:18 +0200 Subject: [PATCH 31/36] Updated dependencies --- Cargo.lock | 267 +++++++++++++++++++++++++++++++++++++++++++++-------- Cargo.toml | 4 +- 2 files changed, 233 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26fde4a..b15b2cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,6 +90,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -373,7 +379,26 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 1.1.0", "indexmap", "slab", "tokio", @@ -393,10 +418,10 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64", + "base64 0.21.5", "bytes", "headers-core", - "http", + "http 0.2.11", "httpdate", "mime", "sha1", @@ -408,7 +433,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http", + "http 0.2.11", ] [[package]] @@ -428,6 +453,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.5" @@ -435,7 +471,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -461,9 +520,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.22", + "http 0.2.11", + "http-body 0.4.5", "httparse", "httpdate", "itoa", @@ -475,6 +534,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.4", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -482,8 +561,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", + "http 0.2.11", + "hyper 0.14.27", "rustls", "tokio", "tokio-rustls", @@ -491,15 +570,38 @@ dependencies = [ [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper", + "http-body-util", + "hyper 1.3.1", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -568,7 +670,7 @@ dependencies = [ [[package]] name = "jonas_jones-api" -version = "0.3.0" +version = "0.4.1" dependencies = [ "chrono", "dotenv", @@ -577,7 +679,7 @@ dependencies = [ "log", "parking_lot", "regex", - "reqwest", + "reqwest 0.12.4", "serde", "serde_json", "tokio", @@ -596,16 +698,16 @@ dependencies = [ [[package]] name = "lastfm" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aed1b6ed29669ba37e7a2a8a939b7e2ff456883f0eb71a173635fae0ddc1cb5b" +checksum = "6e1c7d19b3dabcb2ccef2ffe389604248cdcbc063e3455e9079992e4ec336158" dependencies = [ "async-stream", "chrono", "dotenv", "lazy_static", "rand", - "reqwest", + "reqwest 0.11.22", "serde", "serde_json", "thiserror", @@ -741,7 +843,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 0.2.11", "httparse", "log", "memchr", @@ -1011,17 +1113,58 @@ version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.22", + "http 0.2.11", + "http-body 0.4.5", + "hyper 0.14.27", "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.4", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -1030,22 +1173,20 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", + "rustls-pemfile 2.1.2", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", - "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", - "winreg", + "winreg 0.52.0", ] [[package]] @@ -1099,9 +1240,25 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.5", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1255,9 +1412,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -1296,6 +1453,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "system-configuration" version = "0.5.1" @@ -1486,6 +1649,28 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -1539,7 +1724,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.11", "httparse", "log", "rand", @@ -1659,15 +1844,15 @@ dependencies = [ "futures-channel", "futures-util", "headers", - "http", - "hyper", + "http 0.2.11", + "hyper 0.14.27", "log", "mime", "mime_guess", "multer", "percent-encoding", "pin-project", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "scoped-tls", "serde", "serde_json", @@ -1949,3 +2134,13 @@ dependencies = [ "cfg-if", "windows-sys 0.48.0", ] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] diff --git a/Cargo.toml b/Cargo.toml index c2c6cb0..4676fb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,11 @@ parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.35", features = ["macros"] } dotenv = "0.15.0" -lastfm = "0.8.1" +lastfm = "0.10.0" log = "0.4.20" chrono = "0.4.31" toml = "0.8.8" -reqwest = { version = "0.11.22", features = ["json"] } +reqwest = { version = "0.12.4", features = ["json"] } serde_json = "1.0.108" regex = "1" git2 = "0.18.1" From cdcf27b0fa58d6f5e5950c1047e415fe0ae21b87 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Fri, 28 Jun 2024 05:00:38 +0200 Subject: [PATCH 32/36] added request logging --- Cargo.lock | 193 ++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 3 +- src/iplookup.rs | 14 ++++ src/main.rs | 1 + src/server.rs | 25 ++++++- 5 files changed, 223 insertions(+), 13 deletions(-) create mode 100644 src/iplookup.rs diff --git a/Cargo.lock b/Cargo.lock index b15b2cb..3c750e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,12 +201,57 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "digest" version = "0.10.7" @@ -355,9 +400,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git2" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ "bitflags 2.4.1", "libc", @@ -380,7 +425,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.11", - "indexmap", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -399,13 +444,19 @@ dependencies = [ "futures-sink", "futures-util", "http 1.1.0", - "indexmap", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.3" @@ -442,6 +493,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.11" @@ -627,6 +684,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -637,6 +700,17 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.1.0" @@ -644,7 +718,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", + "serde", +] + +[[package]] +name = "ip2location" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c1511093b9295abf32d5b63cfefa2c59cd237fde31c339fabf9710a54c16c69" +dependencies = [ + "memmap", + "serde", + "serde_json", + "serde_with", ] [[package]] @@ -675,6 +762,7 @@ dependencies = [ "chrono", "dotenv", "git2", + "ip2location", "lastfm", "log", "parking_lot", @@ -732,9 +820,9 @@ checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libgit2-sys" -version = "0.16.1+1.7.1" +version = "0.17.0+1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" dependencies = [ "cc", "libc", @@ -798,6 +886,16 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "mime" version = "0.3.17" @@ -870,6 +968,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.17" @@ -1015,6 +1119,12 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1381,6 +1491,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.1.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1442,6 +1582,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.39" @@ -1513,6 +1659,37 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1642,7 +1819,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap", + "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", diff --git a/Cargo.toml b/Cargo.toml index 4676fb9..7e3e587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,5 @@ toml = "0.8.8" reqwest = { version = "0.12.4", features = ["json"] } serde_json = "1.0.108" regex = "1" -git2 = "0.18.1" +git2 = "0.19.0" +ip2location = "0.5.0" diff --git a/src/iplookup.rs b/src/iplookup.rs new file mode 100644 index 0000000..ddfafba --- /dev/null +++ b/src/iplookup.rs @@ -0,0 +1,14 @@ +use ip2location::{DB, Record}; + +pub fn ip_lookup(ip: &str) -> String { + let database_path = "resources/IP2LOCATION-LITE-DB5.IPV6.BIN/IP2LOCATION-LITE-DB5.IPV6.BIN"; + + let mut db = DB::from_file(database_path).unwrap(); + + let geo_info = db.ip_lookup(ip.parse().unwrap()).unwrap(); + + let record = if let Record::LocationDb(rec) = geo_info { + Some(rec) + } else { None }; + return record.unwrap().country.unwrap().short_name.to_string(); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index a55572c..e845d29 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ pub mod logger; pub mod tools; pub mod server; pub mod error_responses; +pub mod iplookup; pub use logger::Logger; pub use tools::parse_ip; diff --git a/src/server.rs b/src/server.rs index cdf5e6d..3c675a8 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,13 +1,16 @@ use std::convert::Infallible; +use std::net::SocketAddr; use std::env; -use reqwest::StatusCode; +use lastfm::reqwest::StatusCode; +use warp::filters::path::FullPath; use warp::Filter; use warp::reply::Reply; use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError, NotImplementedError}; use crate::v1::get_v1_routes; use crate::{Logger, parse_ip}; +use crate::iplookup::ip_lookup; pub async fn serve() { @@ -24,9 +27,23 @@ pub async fn serve() { let status = warp::path("status") .map(|| warp::reply()); + // Middleware filter to log request details + let log_request = warp::any() + .and(warp::method()) + .and(warp::path::full()) + .and(warp::addr::remote()) + .and(warp::header::optional::("x-forwarded-for")) + .map(|method, path: FullPath, addr: Option, fwd_for: Option| { + let client_ip = fwd_for.unwrap_or_else(|| addr.map(|a| a.ip().to_string()).unwrap_or_else(|| String::from("unknown"))); + let path_str = path.as_str(); + + Logger::info(&format!(" {} {} from {}", method, path_str, ip_lookup(&client_ip))); + }); + // GET (any) => reply with return from handle_path - let routes = favicon.or(status.or(get_v1_routes()) - .recover(handle_rejection)); + let routes = log_request + .clone().untuple_one().and(favicon.or(status.or(get_v1_routes()) + .recover(handle_rejection))); async fn handle_rejection(err: warp::Rejection) -> Result { @@ -47,7 +64,7 @@ pub async fn serve() { message: message.into(), }); - Ok(warp::reply::with_status(json, code)) + Ok(warp::reply::with_status(json, StatusCode::from_u16(code.as_u16()).unwrap())) } From 350c9f722adf6e23f0d6f59dea91c4f3911bfae2 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Fri, 28 Jun 2024 05:12:47 +0200 Subject: [PATCH 33/36] added IP in brackets --- src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.rs b/src/server.rs index 3c675a8..f658c9a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -37,7 +37,7 @@ pub async fn serve() { let client_ip = fwd_for.unwrap_or_else(|| addr.map(|a| a.ip().to_string()).unwrap_or_else(|| String::from("unknown"))); let path_str = path.as_str(); - Logger::info(&format!(" {} {} from {}", method, path_str, ip_lookup(&client_ip))); + Logger::info(&format!(" {} {} from {} ({})", method, path_str, ip_lookup(&client_ip), client_ip)); }); // GET (any) => reply with return from handle_path From 2c8a9db949893f08d40532cc6e6b93eb256750f8 Mon Sep 17 00:00:00 2001 From: J-onasJones Date: Sat, 29 Jun 2024 23:27:05 +0200 Subject: [PATCH 34/36] added request logger --- Cargo.lock | 110 +++++++++++++++++++++++++++++++++++----- Cargo.toml | 3 +- src/main.rs | 1 + src/request_logger.rs | 68 +++++++++++++++++++++++++ src/server.rs | 3 +- src/v1/analytics/mod.rs | 8 +++ src/v1/auth/mod.rs | 7 +++ 7 files changed, 186 insertions(+), 14 deletions(-) create mode 100644 src/request_logger.rs create mode 100644 src/v1/analytics/mod.rs create mode 100644 src/v1/auth/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 3c750e2..be3a161 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,6 +345,12 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + [[package]] name = "futures-sink" version = "0.3.29" @@ -364,8 +370,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-core", + "futures-io", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -620,9 +628,26 @@ dependencies = [ "futures-util", "http 0.2.11", "hyper 0.14.27", - "rustls", + "rustls 0.21.10", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls 0.23.10", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", ] [[package]] @@ -767,9 +792,10 @@ dependencies = [ "log", "parking_lot", "regex", - "reqwest 0.12.4", + "reqwest 0.12.5", "serde", "serde_json", + "sha2", "tokio", "toml", "warp", @@ -1232,7 +1258,7 @@ dependencies = [ "http 0.2.11", "http-body 0.4.5", "hyper 0.14.27", - "hyper-rustls", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -1240,14 +1266,14 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.21.10", "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", @@ -1259,13 +1285,14 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2 0.4.4", @@ -1273,6 +1300,7 @@ dependencies = [ "http-body 1.0.0", "http-body-util", "hyper 1.3.1", + "hyper-rustls 0.27.2", "hyper-tls", "hyper-util", "ipnet", @@ -1340,10 +1368,23 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.4", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -1379,6 +1420,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.15" @@ -1532,6 +1584,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1588,6 +1651,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.39" @@ -1601,9 +1670,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "system-configuration" @@ -1751,7 +1820,18 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.10", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.10", + "rustls-pki-types", "tokio", ] @@ -2321,3 +2401,9 @@ dependencies = [ "cfg-if", "windows-sys 0.48.0", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 7e3e587..17ea181 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,9 @@ lastfm = "0.10.0" log = "0.4.20" chrono = "0.4.31" toml = "0.8.8" -reqwest = { version = "0.12.4", features = ["json"] } +reqwest = { version = "0.12.5", features = ["json", "blocking"] } serde_json = "1.0.108" regex = "1" git2 = "0.19.0" ip2location = "0.5.0" +sha2 = "0.10.8" diff --git a/src/main.rs b/src/main.rs index e845d29..abc4dd1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ pub mod tools; pub mod server; pub mod error_responses; pub mod iplookup; +pub mod request_logger; pub use logger::Logger; pub use tools::parse_ip; diff --git a/src/request_logger.rs b/src/request_logger.rs new file mode 100644 index 0000000..e559389 --- /dev/null +++ b/src/request_logger.rs @@ -0,0 +1,68 @@ +use chrono::Utc; +use reqwest::blocking::get; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use sha2::{Digest, Sha256}; +use std::fs::OpenOptions; +use std::io::{BufReader, BufWriter}; +use std::path::Path; + +#[derive(Serialize, Deserialize)] +struct RequestLog { + timestamp: String, + method: String, + pathname: String, + ip_country_code: String, + ip_hash: String, +} + +fn get_ip_country_code(ip: &str) -> Result> { + let url = format!("http://ip-api.com/json/{}", ip); + let response: Value = get(&url)?.json()?; + if let Some(country_code) = response["countryCode"].as_str() { + Ok(country_code.to_string()) + } else { + Err("Could not fetch country code".into()) + } +} + +fn hash_ip(ip: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(ip); + format!("{:x}", hasher.finalize()) +} + +pub fn log_request(ip: &str, pathname: &str, method: &str, file_path: &str) /*-> Result<(), Box>*/ { + let timestamp = Utc::now().to_rfc3339(); + let ip_country_code = get_ip_country_code(ip)?; + let ip_hash = hash_ip(ip); + + let log_entry = RequestLog { + timestamp, + method: method.to_string(), + pathname: pathname.to_string(), + ip_country_code, + ip_hash, + }; + + let path = Path::new(file_path); + let file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path)?; + + let mut logs: Vec = if path.exists() { + let reader = BufReader::new(&file); + serde_json::from_reader(reader)? + } else { + Vec::new() + }; + + logs.push(log_entry); + + let writer = BufWriter::new(&file); + serde_json::to_writer_pretty(writer, &logs)?; + + //Ok(()) +} \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index f658c9a..90bb641 100644 --- a/src/server.rs +++ b/src/server.rs @@ -9,7 +9,7 @@ use warp::reply::Reply; use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError, NotImplementedError}; use crate::v1::get_v1_routes; -use crate::{Logger, parse_ip}; +use crate::{parse_ip, request_logger, Logger}; use crate::iplookup::ip_lookup; @@ -37,6 +37,7 @@ pub async fn serve() { let client_ip = fwd_for.unwrap_or_else(|| addr.map(|a| a.ip().to_string()).unwrap_or_else(|| String::from("unknown"))); let path_str = path.as_str(); + request_logger::log_request(&client_ip, path_str, method, "requests.json"); Logger::info(&format!(" {} {} from {} ({})", method, path_str, ip_lookup(&client_ip), client_ip)); }); diff --git a/src/v1/analytics/mod.rs b/src/v1/analytics/mod.rs new file mode 100644 index 0000000..98bbf68 --- /dev/null +++ b/src/v1/analytics/mod.rs @@ -0,0 +1,8 @@ +use warp::Filter; + +pub fn get_analytics_routes() -> impl warp::Filter + Clone { + warp::path("v1").and(warp::path("analytics")) + .and((warp::path("logcdnrequest").map(|| "Please refer to the wiki at https://wiki.jonasjones.dev/Api/")) + .or(warp::path("ping").map(|| "pong")) + .or(warp::path("version").map(|| warp::reply::json(&[option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")])))) +} \ No newline at end of file diff --git a/src/v1/auth/mod.rs b/src/v1/auth/mod.rs new file mode 100644 index 0000000..0a37d4f --- /dev/null +++ b/src/v1/auth/mod.rs @@ -0,0 +1,7 @@ +use warp::Filter; + +pub fn get_auth_routes() -> impl warp::Filter + Clone { + warp::path("v1").and(warp::path("auth")) + .and((warp::path("requestsession").and(warp)) + .or(warp::path("login"))) +} \ No newline at end of file From 9ef64b5b82eaa7c62cd24a68f5f78da14d4289d6 Mon Sep 17 00:00:00 2001 From: Jonas_Jones Date: Sun, 30 Jun 2024 00:34:55 +0200 Subject: [PATCH 35/36] added more request logging code --- src/request_logger.rs | 17 ++++++++++------- src/server.rs | 21 +++++++++++++++++---- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/request_logger.rs b/src/request_logger.rs index e559389..e742d4a 100644 --- a/src/request_logger.rs +++ b/src/request_logger.rs @@ -1,10 +1,12 @@ use chrono::Utc; +use reqwest::Client; use reqwest::blocking::get; use serde::{Deserialize, Serialize}; use serde_json::Value; use sha2::{Digest, Sha256}; use std::fs::OpenOptions; use std::io::{BufReader, BufWriter}; +use warp::http::Method; use std::path::Path; #[derive(Serialize, Deserialize)] @@ -16,7 +18,7 @@ struct RequestLog { ip_hash: String, } -fn get_ip_country_code(ip: &str) -> Result> { +async fn get_ip_country_code(ip: &str) -> Result> { let url = format!("http://ip-api.com/json/{}", ip); let response: Value = get(&url)?.json()?; if let Some(country_code) = response["countryCode"].as_str() { @@ -26,20 +28,21 @@ fn get_ip_country_code(ip: &str) -> Result> { } } -fn hash_ip(ip: &str) -> String { +async fn hash_ip(ip: &str) -> String { let mut hasher = Sha256::new(); hasher.update(ip); format!("{:x}", hasher.finalize()) } -pub fn log_request(ip: &str, pathname: &str, method: &str, file_path: &str) /*-> Result<(), Box>*/ { +pub async fn log_request(client: &Client, ip: &str, pathname: &str, method: &Method, file_path: &str) -> Result<(), Box> { + let method_str = method.as_str(); let timestamp = Utc::now().to_rfc3339(); - let ip_country_code = get_ip_country_code(ip)?; - let ip_hash = hash_ip(ip); + let ip_country_code = get_ip_country_code(ip).await?; + let ip_hash = hash_ip(ip).await; let log_entry = RequestLog { timestamp, - method: method.to_string(), + method: method_str.to_string(), pathname: pathname.to_string(), ip_country_code, ip_hash, @@ -64,5 +67,5 @@ pub fn log_request(ip: &str, pathname: &str, method: &str, file_path: &str) /*-> let writer = BufWriter::new(&file); serde_json::to_writer_pretty(writer, &logs)?; - //Ok(()) + Ok(()) } \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index 90bb641..51ec63b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -4,9 +4,12 @@ use std::env; use lastfm::reqwest::StatusCode; use warp::filters::path::FullPath; +use warp::http::Method; use warp::Filter; use warp::reply::Reply; +use reqwest::Client; + use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError, NotImplementedError}; use crate::v1::get_v1_routes; use crate::{parse_ip, request_logger, Logger}; @@ -33,12 +36,22 @@ pub async fn serve() { .and(warp::path::full()) .and(warp::addr::remote()) .and(warp::header::optional::("x-forwarded-for")) - .map(|method, path: FullPath, addr: Option, fwd_for: Option| { + .map(|method: Method, path: FullPath, addr: Option, fwd_for: Option| { let client_ip = fwd_for.unwrap_or_else(|| addr.map(|a| a.ip().to_string()).unwrap_or_else(|| String::from("unknown"))); - let path_str = path.as_str(); + let path_str = path.as_str().to_string(); // Convert to owned String + let method_clone = method.clone(); + let method_str = method_clone.clone().as_str().to_string(); + let client_ip_clone = client_ip.clone(); // Clone for use outside the async block + let path_str_clone = path_str.clone(); - request_logger::log_request(&client_ip, path_str, method, "requests.json"); - Logger::info(&format!(" {} {} from {} ({})", method, path_str, ip_lookup(&client_ip), client_ip)); + /*tokio::spawn(async move { + let client = Client::new(); + if let Err(e) = request_logger::log_request(&client, &client_ip, &path_str, &method_clone, "requests.json").await { + eprintln!("Failed to log request: {:?}", e); + } + });*/ + + Logger::info(&format!("{} {} from {} ({})", method_str, path_str_clone, ip_lookup(&client_ip_clone), client_ip_clone)); }); // GET (any) => reply with return from handle_path From c25f2032d7b49b2ea6c0d16af4707295ef529d4e Mon Sep 17 00:00:00 2001 From: Jonas_Jones Date: Wed, 2 Apr 2025 23:18:53 +0200 Subject: [PATCH 36/36] migrated to new version of sync script --- src/main.rs | 6 ++---- src/v1/mod.rs | 4 +--- src/v1/run/mod.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index abc4dd1..e8a62ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use dotenv::dotenv; use tokio::time::{sleep, Duration}; -use v1::{run_kcomebacks_command, run_likedsongs_command, run_projects_command}; +use v1::{run_sync_all_command}; use std::fs; pub mod v1; @@ -19,9 +19,7 @@ async fn periodic_script_runner() { loop { Logger::info("Running periodic scripts..."); // Run all Functions - let _ = run_kcomebacks_command(); - let _ = run_projects_command(); - let _ = run_likedsongs_command(); + let _ = run_sync_all_command(); // Sleep for 6 hours sleep(Duration::from_secs(6 * 60 * 60)).await; diff --git a/src/v1/mod.rs b/src/v1/mod.rs index ff4bf8f..199d74d 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -5,9 +5,7 @@ mod projects; mod run; pub use run::setup as run_setup; -pub use run::run_kcomebacks_command; -pub use run::run_projects_command; -pub use run::run_likedsongs_command; +pub use run::run_sync_all_command; pub use builtin::get_builtin_routes as get_v1_builtin_routes; pub use debug::get_debug_routes as get_v1_debug_routes; diff --git a/src/v1/run/mod.rs b/src/v1/run/mod.rs index 9861d53..4402b6b 100644 --- a/src/v1/run/mod.rs +++ b/src/v1/run/mod.rs @@ -26,6 +26,7 @@ pub fn get_run_routes() -> impl warp::Filter Result { } +async fn sync_all() -> Result { + // check if the local repository exists, if not, clone it + if !fs::metadata("./resources/turbo_octo_potato").is_ok() { + setup().unwrap(); + }; + + if let Err(err) = run_sync_all_command() { + // Handle the error here + eprintln!("Error: {}", err); + // Return an appropriate response or error + return Err(warp::reject::custom(InternalServerError)); + } + + Ok(warp::reply::json(&json!({"status": "syncing..."}))) + +} + pub fn setup() -> Result<(), git2::Error> { let repository_url = "https://github.com/JonasunderscoreJones/turbo-octo-potato.git"; let local_directory = "resources/turbo_octo_potato"; @@ -247,5 +265,20 @@ pub fn run_likedsongs_command() -> Result<(), std::io::Error> { // }); Logger::info("Syncing liked songs..."); + Ok(()) +} + +pub fn run_sync_all_command() -> Result<(), std::io::Error> { + task::spawn_blocking(move || { + let mut child = Command::new("python3") + .arg("script_interval_runner.py") + .current_dir("resources/turbo_octo_potato") + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + child.wait().unwrap(); + }); + Logger::info("Running all Sync Scripts..."); + Ok(()) } \ No newline at end of file