From 12d57d020f5d7defceca5ffbbd981bd127848156 Mon Sep 17 00:00:00 2001 From: Tobias Reisinger Date: Thu, 4 Nov 2021 23:37:16 +0100 Subject: [PATCH] Start rust rewrite --- .editorconfig | 15 +++ .env | 1 + .gitignore | 11 ++ Cargo.lock | Bin 0 -> 47245 bytes Cargo.toml | 20 ++++ README.md | 1 + diesel.toml | 5 + emgauwa-core.conf | 17 +++ migrations/2021-10-13-000000_init/down.sql | 8 ++ migrations/2021-10-13-000000_init/up.sql | 128 +++++++++++++++++++++ sql/cache.sql | 10 ++ sql/migration_0.sql | 83 +++++++++++++ sql/migration_1.sql | 28 +++++ src/db.rs | 62 ++++++++++ src/db/errors.rs | 30 +++++ src/db/models.rs | 20 ++++ src/db/schema.rs | 93 +++++++++++++++ src/db/types.rs | 97 ++++++++++++++++ src/handlers/mod.rs | 1 + src/handlers/v1/mod.rs | 1 + src/handlers/v1/schedules.rs | 24 ++++ src/main.rs | 38 ++++++ 22 files changed, 693 insertions(+) create mode 100644 .editorconfig create mode 100644 .env create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 diesel.toml create mode 100644 emgauwa-core.conf create mode 100644 migrations/2021-10-13-000000_init/down.sql create mode 100644 migrations/2021-10-13-000000_init/up.sql create mode 100644 sql/cache.sql create mode 100644 sql/migration_0.sql create mode 100644 sql/migration_1.sql create mode 100644 src/db.rs create mode 100644 src/db/errors.rs create mode 100644 src/db/models.rs create mode 100644 src/db/schema.rs create mode 100644 src/db/types.rs create mode 100644 src/handlers/mod.rs create mode 100644 src/handlers/v1/mod.rs create mode 100644 src/handlers/v1/schedules.rs create mode 100644 src/main.rs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f303022 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: +# https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.yml] +indent_size = 2 diff --git a/.env b/.env new file mode 100644 index 0000000..6075b6a --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=emgauwa-core.sqlite diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f575be --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +target/ + +tests/testing/ + +emgauwa-core.conf.d +emgauwa-core.sqlite + + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..a9b38132ae7a6ad894b667276e5559baa6b73b8f GIT binary patch literal 47245 zcmciLTW=gmk|p5x{uM&a<1W>4zbvr80{gV@`!*N|ag}mPA~n2JwffiZXOYZgW-^je zHG(?>w8&(Vl^*VHZg$SG8~yZg^~AOH4mAO6o{y!H0ZKevz5?dZqz-TjAe-R)EN^}{so9(T8&KmP5H z>HcANcl+Uge#rm!w}1cl@7?fw_j&q{|M=T&_igg(Z{6^?`^U|2H%`NEC-43(`Ivu9 ze*4?Q-P8TByzzeeynA@O|MB0xJU)JZ_#c1&`{&)`m#6;YaQE%+_fHRxH($Hk&wn4{ zlRkXh-QDbbrhokQx8ch){QmItE&f||Gp0?R_j!{yV?VWRGS11I&Q0BSb5$h6T%|=d zHEoktRX0yX;_dC2_DPaXW$ojP)Atzb?Xa63{KbF&+lSx!-Q)bV`~2|Re~)i|_^*FR zK9(P=<@@>R@#%hgFuwcg?1hKj?eEJAU++FI|9;=y-h98m`_E~3^!vrftXW=uy!(B3 z_wv=v)8p=|Z~x=%hk1MJ&zwEZ>|?U|<4o(i$?Gmnnkt{uvhSKP@1}ej(roUAH1T}f zw#?dT%#x(ZyR4qadCH19Z_+Ut&G*XVJY5g*MEBp{KQ7N;|INd6|Hp3FKhOOu@l^H` zFi!WoKc-LL4$phCNT&Pcmk)FC{0Kk%77J%2rEz_y4X0Oe8RhC7XHDy|l^*HDCF!h}; zl4MTWD$AznFFgCR|?7rl{{8rm9$ebiQ{y^RK&X|AUKt zG~C$-`xzhZzW?|-J^XaKZPL3@4El7xf2&RUIPL%XVI>S-Iz!uk%HwN?m2-IExLoFc zeCZy(EFbU7{_h+YP<+wdKkPr?5_rCK`=zyeyz7=g& zNuO19H#dXz+>cdJk5e*M>6BD;(N|NIrcF5vbyv38JU2~UXZYf8?(0U<~py6c1q`=Y4a)1^I@19o4l^;GM)OiG`hMR zleSFrw5*Gvnv1+Bv-Oa=97>P(_QEo+$5nd>d#~5b+mGV>WJ%5U*3m63@_87tHqFd$ zI!;+X%*B+=b(V}-S0zK%RAtunMOwF2Q+DYzrDZ!MbDxiWmYJ2SC(HASeF;n#(dsO` z9Ady)pg6{bhi~$vKcp}5HxK9an@=zfu#3eMTe;YHF%@Nz)RS#dXG7=MZHm6@s;VB! zG0&#i0X+=k*o@_r^ra2ePIJ>$^<0c|Unk99IU|1geEy{;dRUm>o>L$H-aL)_Fn;^= z?bGo6Y5&c`Ayz%y4Zly15tV=X;3IQ5-Ny0ctBs$0l9XAImt)zaec4ZqFVoZt?(=?_ zEZU*z=Cm1`dK^9SK_)skrfDi2=}p%6tLyjh=#C5T^e8x{jBk!#avEv*tMPBWe~UWH z+o8*wS}^PDVw$S1>DppUhpCZBr$wK2Ltf2UVN)lvm%6kv=c-Mox=q)^*gn;JwSQkN zz2|iAb*^|=R=>DdP6ET}dWphh>+?;5+|EfcCW)M_=p6uKUKe?iXOiYVsjAevh*($l zv#_66P3Bp)-H;7KGUe-q$z>{hSU;!1>{Ol-9&Rf050)wI@CIOQisCcXJI6U3w-TCG=jQ9M1cra6&WSP3Utg1Y7jMi=43Nb=YF(+kPcUjXldD68* zI@I$tG<}w5we(_6tG?`3gV6eed9?=*<=J_Hf05L$)Mlq^^YBEErSjp0V*osC=+!_d z1KytwZ%)OZlLhbsQv7Cbf6O*M4OBbnSafNfP4(P$%H=F8(k@Hpxqv>DU7rY&Lsp9c zQk$_T0TOjxG)s=e6w zFK^ke_m}TI^ab&OEbZU*=RSG5L{Baw*r5mQzQj z{g{t!14Jpuwrl30@0+xo)SjbaV^);(Q(riQ+q}!mSthv-YJRy3$gg|Y4Zoj-m*ivp zF>N-EFX;r}nWgb{Rn{FysGRE}8!Kt$(3N%5l#K*`fW(bM-OS>DIt^3hn>m z-==TFm-{zX=fjWN;pXwa+ntw#QT~^k=aWUAHd9?!<6JdqT6VIbaf%57&N|a_8PQmG zMOR9%hoM-2uP)EqJjy^yEx-g;R!~Fg^^f2~Go{nSPOj%R=O0`f{!_e50b7AOt zrK+ojyn(5XQ?2GG@~Hx{HeFE_j_>uQDb}9Gkx0q5PGL2QWuI%6~NpTb=OX6fYk-Lo_Smxc(0DLV(oxLz}fpDF!rB$8mrmB-PxfNss`|wJqO&m51E)PsVzQV78m* z5JhSUAtd7X89QiPn|4!b9-KLOS(l#w*c7n6$`REn-IBTJi!O(XWNkgcz*dsog@W7- zH{Jbx_v8AR)GrLe&BKrL;wmcktrLf$D9Ul_AhE_GH5f(&;V`uGq`IuSsTliiYCt?v z2&qGDNe*e>mt#|P-85KgE5jU~iGW=nUt9|X&os&DVtkJgCWG_G@q)^+)ls75kd}54 zxN0bYtOEpM&Re{mQl;xN`z}NNwNuxWWpxETJei>=2fn^N(x-3VyRUa=Q7`}4Y`$ch zIh`_~a^49bNmmb$l{70V5W8(Q1iO}YP1>dNlr+jRq(TeCQZQFV-&fAl^{D5Zxx>Mn z@3~aozV&&V`jAaKl)`q~v|wL}-#B;mGz?W<0(^y)rs^g2S)Zp_SN2LwOA%dLbycd2 zxH^E#ofO3OwxoO_V^c{qbArRn)D~?ZI(ZEWO_Ldx)71(5v>6&@NFBLsH$t@~uPP1@ z<1c^sAS$-5n53GuOq+h7_oSNua7(C5-F122VrF7aT0a=dw8-)yt?{zbANi{{C-a*h ze$UvPZu5PLvhCw<6UThtrR~(#y>FUoO4SL8ast63S3}*8Z95i2f;ULJF}MPR!Zg%F z0nuFzWcxyX|G=*XGSTjGaP`)OL){LUiU9}M7FAN&w!Mn3%F_YLYGuts*L6lCIY=d< zdC=*7T2`a%V4B8j8NohfJp1@j_ry-xRIp+rl>G=bZ!3U#Q~2M0bj&2#WIx-{V-L)m z5C?ftRI-_}kRf!CP=Tz;9D1u8d*$Q5$I>FIQKc2iuzCTo`yr7qPWj-AG)txlrhs-r z-IjS@OmjcO{?oc2Gc{~-H65czczFCY-(EcaV)JZxZnI(Te2}tglRihmN@8YQ8txh5 z>`OYA((5Irw$1val!(n0=l~F1O<2hFr+;@Vjeq(0AM~tS-5;)bBWh@sH&}T`OR$e1=kyT>HSEJLauHuN#J(pOY7U9_SH0;O| zUp#}4#0HzjfG!*7wo33mP1W`p!nTCcxA`1fYwF7)6A-381qCP$6w^R~Cd+}!lb_~} z>-AOm{Yj~`sM9VM;r3#swnR4+im}RaD+R6`o-<2}9W_Nw9+oO?D$bW-+kQ;9PSlE6P~&K}z)+K7#!;CieLs~c z8H&UiMh62(=WH~2U6FzY@-k2B6h+yL>oB8W{t^tMB)E>sg%EG}bU;A%G;I6^AoBV) z9SJolyiT7^WZ+u^Cw8`vP?kDx+JGqqF-r|Z7EJ}F4c=Y0&bLd z^^Ib2b%jS3wJ#uInER1@+Ryo(U9klo{_E@RamtsPH{SL1v>WM1D`Dtr;&t(tE}zZT zIT$#)RF_!s&4b%&ve3c$>-%wtc;69)zu_4Xv1(3TqUJFd2X6uO~%F4be+c6tTiX0h{^BGBP zeY6s9&tY}+FQ5NYsITvDyJx=bLdD}Ex+K_6 zs63Vl7Ux(HCCV|%Qc)8hK`K*8Y6;%(6QO)q9se)lLF4XW=I9hsGr56D30EZ{d*ry2Q!V5F)neTm_QVjbm`XE{a7HI2>~;HW_% zSn+pY?GC9t;PQ|sf_YKTEmYCe!j!TS>W{UYVPIEszf0onC25aM@toj)`0d;7cK6Ru zcmLQediE>F*?bWx<1uWl&d%pnTGC=&a>_+;lC&kndMQ+IfJzRHR0epFDNr3}mc!7& z&*Z>e-(-WtYBkR5oBDRD$G@D-trM~m44}>m!gUVip`vR}zv4sCOq zit&L9jCol);go9YMYV=874tqXT`91y&DzuOFMO7lLr>tzjp&+VhM7^Qcz#tBFSxRG9t%UtEjoW-Spy#ywmj=H|TW!r0 z$Ur-5lawNZQU&e_V9Yy3DdZDpRVN-pGbC2_nAY?#8PtJxM7&Gpy1RB*J~QO6<}clP z2jXQ2=mOWEMS@Y6gPn*mC0kcOlnqQYscS%cmlH=Ig9m31_`O8|SM(CA6L{qJe?><2 zEJ;87&MR@^WB0I+=!bW_EeJil>mYnQ-TP7EZvF(zu>HAJ5edaMB(5?jyIyQ)~bZU`Ejr&|}E1x9h6Qw|}d!QhS*X1R>NJGnwA-K{V5lzd|#*1X?=8J~oi`V{=4Dql(mxcW9 z^P5*wmtqvsJjsehZG%nhLyr^rMP%6s36;wL)i>{+M^g>JxDn0O59!qe<9UfLvFY6h zkt+E$nilA@zRn$~O-Gs57U+^@5VGOdpk1ZElXjx&LqfsiwCAeMOg z?t{=lTjAT}JMfvVvL=J%5ie6ZsH&-gE=-Zr5@V!w+qFnif`QEcf#jR@9O-n89v0Ri z@JGgbeC;xfUK~36eR}#E*N>s2juW){B5uA3)DTV>9gOjJ;zutk?U!O?ZU5Zd!i^4X zqi;&=pF3vHCu@6pVbJ|}H?&nmS6MPgK9R6y4}> zQfC9Kx*|?C&{QS((OQ))VGa=9SvL%Wi3Rbj4*3FHI4Ly$B-pkofS~^^zPx$3?Y=*J zxqD;>F#WNc{)`6ct7~EXp=6tjeN4J^otz=pV@T9Z+Mvy!2H0uZHbsk%w#H@mNvWWr zz*XDTFbKf=nAPPvoc_~il8P07`Jrf&Ki?JGH%!~lHDXaMQ-G3} zi*?j$qwMQZk9{vqYJt!}zm)kpDs$O~64=6Nv^@%e4*~mC_s z=f>fKy@zk_yW!BV{rvUK^MCF7A`E{53cEiq3+WW^xiKKrQ2{{+h`eu|;QG#V`Gth%NvSn#m(HM2dE=K#+LId}<#aY``Q)*KpQpW z>jC3!_WjGOEVSTsioT)+eW9)&jKo zRCkU&c%{BxbPom@*5hzfEl$}KqA?t^b(|Ljt6MfSM7e z!cfN8sEEE3-X9X$Na6{zDe4Fay7lw=w61zN1Fz<4JNm)y_LBM*dAB|!Wf@R&|X2Hi^-m7UEeA&5jrojVJ*}^?E*N=1clcZIcj=3BSCapTB*y?7kxk z=G}S!UF_MdCvPU-VbY;l18I=Rb3iT@BI0KbHb3SJmR@A7{yfk)K~xPwjfuo}A%j-; z?2Glphu{9hXEC%pWO*0IOZ@znGtc#tW{Jz^GyD$1raF}hAUUf{D^jPn6j22%M3}zc z9NX^M;I=4}2ZDhNTAai27#NxPakSu8!JOY|R;I6cwnBPrKa+ue(!pv)ik5oX8nh2y zL(!;a9%Og4NNuiq#8=a|Av&bBggHa}4`63?CXdq|fAkwfh>DT37bvxeeI) z>}@~t;Hn$8KUKK?km0C*P$ex2MWO)LPLc3DFmzvNR?tFPf)^nDJP@UVIH4=Ecj_8) zI)wMoPem7kvQDqZKu4)3F`2WOc9NWOL|8D)5`@Ue@t4H0Gjf*bpoAi)>X9n+1*;2i z2{NajJwiyDYz<)Ws*;n;@Ihxg6*hs2DuVn$EYY~!g6AnHbF(n%LyvSoW=bi`%uAJ0 zUrSni`cvWG#W=QKN16^*LM@msZy=L@pX z8L)BPM?!=M0ynS#EZO9#06UwhC|M+d#?*e))`idk{{cqmFNX&qjB`m=Mv%NZx?jtz zzTQ9G3>~e*#VEJOSaLyuvJLSgfubat$crWgx^h7;S>(-==4O~M8z}JviCZy12q|2n z*1h7Q_4Q&OU7g3JyjY6g^VwX~Z<~sXs81#-u}mMJL=0Ne&GaOb`kK0~=L9&>jXIQS zK*b0Q4k9U1NCg8BCt8MAL;RsWy8QTC<12b=@>(@YEr~KNqI&7kb~Sl?=)E;9s>3Vt zPmL@MuGaZA>6>kxx>{vJ8@2xN_XZrNpFX2c-W)+moRfLmhxbX1xZ{vKOO3+2K_eMd z2+CZ}dK(bpv+PJ2qss=840sh1ucZhlLgW{P^66M2SJ-+ia#N0(3dU|ubS7vbO}Gyy zVVnketOyP>ZH*0=3@v9(+g3E8sPhsL8Pd@8sPNoDaDAT~wd9|kCD)h3+`2Mpq4;y^ zOm1n+&^?QL-b4yx=L`xFAeaasdj*U*!UktRmUc9lln)y2*UQS)30tVaf6c#Vu~a&J z5{sH;>-=gl1Lbb4BpGVKQGrIDD9)GGCTY1295l0bJ&mAzVGEt?BwdMQF|f@@?br9- ziQ@2l4BNN3sj1L&Qwy_;?&b;|ZpQGJO-GN7<lXK%0zN!-*b)Jy+kYymai7+2bSeJ7=Y6yl{knUu<6P{#^dn?cQSa z=qz_A`W7NRs=(`yc=0d2e9T*SA$27Bx(}$5!n_c18`vJ~YBV4iv{3{-G8?;p{Whl1OBalp}hsRQm~&3ksKR9fEt#gim(Xaqf?g z;!PIh=pTAIe%o|=&mRaQ_gt70h(pcECl$WP`BFo?sn(e})5J03g?>;>@s};7V&_zMqkO(W#zN|aN$_wh_0$|t^-x0IUQKlux*H@ zdGsFNzA%@F42Vk$K^a~|Zd!!GQ@V5Iz&o{gJ)JTuVEeiT6UE=M;VuInv483OXcL0K z!p$LC@it-70IjpEt=+^O6JsSqRk?wh*Gtm_sK3nDp~#MJPKNgBnYh0)k+0r){v?{s zYxGfJ-XH*4=V2#n$*f2ym&#$be3{RHP!zB66YuEN@RERBTTSmhI^9>-?nM>*;kUCV zvp3xN@Z0?~-u>efr=+;#WjWl=Y|MXcm%q~apFS{f+UD#L=8qWz3`4r->ChzS*pgoR zwI(m!N1Fbb0RU&%-^g@fY`7QhJ;6Cl2&HSQ|FRJ>KKs@w5CMId#YjjCg`gG-Djk#w z!b|fEsA6Q_Kh=RViDro=N&8u<#}^BM!r)_dvY&>L2jg-x#gJy93yp$sB%)FX`&1jKQcFpk51U#k5q6V?4J=fcWPGyUST2jXK zd_~S*9Cow?ZJl;GQK$(Wz8U2i0?1Z2w8c;-I1)t};+L(C<^C6yPUlqjqnBAwTm5$bMmhy~!C;(E#( zUumFD; zWZJqQC7hj0g1jbUM(DB`wuNbP4MshoY0gK$RB>x$i;x+_t6?5h>j`IctZvW{PQw{f z$}3t(5%t%GL067rhUcfqOg4pjzSC<^>i6$|EQ0s2Qom68q%z`7XJHc3ah2kp$L{VA zu!qR+ULqF|uzb+XLD-{%UZXuIb>?i;I`}5KNM}3(>Dn?nqHudj{`oklt+qzB^$#W3 zNU#C*wepIU4K22K4(wKNYvV>C&0G;fh^AXbttm~lv4at`Uw%H0=Qe`#k^Op1G^iP%?S5z{5 zy)=CBa_)-Uy|9%a2W~Ffq43b_OYqoABA=Ims_4B8*Js!!ZhxQm|E!k1g2$8flt}An2k2)vpOG$7z=`seTQz<#fMxE+MZt)4M ze|3YN99scooDTnum9Pgn&Ii1`8kZo%fyKo+uBc-P&IE$spfb@YLXis3zA%WCV=&r8 ztj(n$T9WKkOw@YLyRsbK8mP%XA0{vRjW@MBtPpKdB(786zzeAueh_YBPU0@ zvj8yFT%B1T<%pl9bE{=x7Lb)7dr5$U5FV02m{DT zT(oxT1u9rKIZAk|>Y{d&V1dM!%1F_KYiH{qbza?@-RTV%i{RZFj9dtVLP;MY zb@yvsIa~L4;x00-i@mtLbR3WpTkWkeah6uA`18 zvW`PJuKH~lcj+FFvLhtr0j?#=iIqV=Oy6Xz&fKtj`Hy~YqjXsbD3AlQjvEu)kEn4@o$E#lvRoX0TwtLjWTB*a>B>b`*nCpXBhQIB!LC@oJm%93C)`FpiZb>KOR!i&wWFk5j zKuYoxR>`_HYc{H^b)#fPX0I;L@4;-nu_9ehaW*{>`IwJvoJA`QsIFWi;wFl4{3Jop z`Gpe!KZ+gzqXKXYR#G<;0M*f%O;IkVhQUCeR)@AXMm#ztUdgh~-gO=!-(#ehJ>FBI zJEfL!B5Z>OU=5-zMi}I09sIp3at2VRmUXv`lnP`L7MH3Zi&jT^4gy4)@=F(&pEyTe z4!qi$0@Ghlg+N74hXG_J@?wfO@I8#aT#XEXE0Sbv2cvexmqdky0gA-cODqJ(bqegc z@l@sOe2)BV8PQLc^ED&AeQ@4(%T6v?VQ{G{f&mX#NOdkXhTAddlJu$I@)`H&{%|b- zffeU=dzbeB00kzet!#C+FWmrR;D7l^eCck~Ehn&K9I0DRdfYM-3ww;a5mF~dXm1ns zFxt1!@}f_I8V$)XIf9hNCcby*YL`%75u~mT|Ln4=82c+dgWJE4&h_zzac^WCU!F;E zu7|xZ-Q~lnHqVyOV#Z$1nm)CU@Ktk}kbun!U++>}s)WIasIHI}>98`QeYl)Bs^qlE z)of|^nc4Hrm%F>)Z`7GP4TL|=Ci-1^un_qW0RG)fcJX0Ug{**GmZBK*S*gTTTp1`N z!T-B}Q0pAf%iWr!hp6B6%Ig{CyB4>@Sm=BKZ{H)Mhsow-9zr9I!X-4q;k$!GzKtsb zV?i@b1qD*PLoWjTKE`0FPMp9>gjK-#sH8dl{O{3Bt?kGmifPx>*Smzwo$HC7)Hyyo z7`BvxGdzJ}L`xh%Rx#<=(I-!FEK3xB)<6IAwY8@oe^GDyg-s!IABG$cOP(uWU5Y@k zsEZ7|oH_b2$iQ--Y22ek7wuL&jt5*E!le^;$?Iq}fV_$qM?Cr0L`~5f9=DubUffC( zv^6z_Q`Xn$dNI9puI*S(3wM}m!h}-6IEbD2CeyjPb1au7NiJA->anh8w=G_Q-JXN~ z&+adIcpSs2`h1RQYPQb(aL)3a;11%71Pjt}A6M4XK}Bl@uX{JQ>ou`N$GFNA`$oGZ zDFmw`gzT{u0OMfn9x?y&T8O}4;`RLi$ujSEe@v&Z95h7xONsZ6wa;%p{^EO*G2zR9B;(A2R7M8?0)lv--Z$ILKClr!~JArkPk`n{su{Jzv2Z58+>XP|K71 z&j&Wg%Y%$0WclK+Jix=cJYQc>^Nk~O7EF1{9oav)J=dqIYOp@YU5MF2})Ryeh8V-~; zgKMNPG%bTbwvR3) z-7=3~?xpqL@9r+I8)`OhbmXf7{H7&RUqV|Im~^IL02r!pU}fB7Dz#8@yA`k}P_skX zn#T=$WG~%1D{$=DxLP4Gn%bwa%q88rWYN7$NtV5x(SlOC;W>MDb;e^fW)XOsY;gzuxSF zIsH554-P`Vaoj^6Oqz;`ap7(FB8MMS26k?0g9=KaGDn@Dak*xWjL|eCmoR(E%($s) zUO)9-_QH!5?8%^h77kt>4LFREG|nA7wwFZdR)<23MCqXQ<)+RhN!8e*jTNJ_eP!P zAD)b8>%5D^I1i)!Ht;hcoN$rS2H&anb81tHQZw>l!Xie)`l#U(h)V5?UvsbUdhB~; zXZq}Y4Pm-`QwwQ__#Z}iu_F8u7~9@ZeMWpC`w zqklP;-u)i`#mC+G`MMwz8;8=xw2k7Bt^mHGx^m!T_KZi)09-r0DB>(a&W67^rwTV9 zxdz&4#mP=T;%Zv)(`k6+CVOZ=e(UKT9AvMQr3W9`^OdWoZ|j;XB*LK|l7%f-bFj?=q z5zuJo^#FFV{y!`wuG(1@~uBW`_TtnnR=)?%a>eaB(DLMvCe%&sZ+!a3L{2H`)%ZpJo z>L~n8-|NC&-OBb3&zzYP zP8lFUoQX5022<)0z0*w*tkiKQN*Jw$r)r4$Qz8$zGNF_kX@O(w72tE+SwzVPXNmn) z$?__@8cW^~F0}W-5ZkO+Xb(Z(wd* z2&Q;I-vdvuB%SIESdRgX{=c~5a!{AmkT)r1+?RJHxjJ^So(>=$KIVT@Xo%J^kfC7N zz@UIZJ%}J@=qW7|S(fvg24N2KZ`Lt!?qeFTyV0t{PvYw5f9|AM+;`8%kZ&JkIKNON z>6<4VQ|46Gd6>!K2mF6@;aKOI4MVs&xzWf?Poc^RoWw0zS8wY)z*y&o!RLdqrnXKS z*cb510``lQOcLpj!=EJXkR+!sCaJG8kp&Ep&Q)#HS&LB};Z8KU^z(YiJk(WJH_TzM zXJ+lEV@*Fcn^$4{)!}dQ`j7}X5>e&w4VNC%g=mvhwCaY|{f~uY!%h~$^cYm<;K04Q zrtRA1IceiL9m0amY`lN;x2T!z^4V>^u>u$K$Ix(4;_CG9uRB;%iVxm`@L)n+yp_XU zhLcm6UmJo$<@BQ~tTwmvMZ*(wo3>eN3ac}>PlE#4fXKzr1AeH*(8abSm@+kr zRNV?#IP%5Svw2mkaX4@f{&NxUUpXaQUz#rPK6@DZ%e=RZgN7K+Ag((LSv+*#C7z=9 zsCOaZpde!lL3|jJgWYuax#b9s+EG>0FkqHfAI9aOd8HzW8*DEI7`NGOJ3xxg0iLg2 zk@BAtjK*wjSe)SWC23YS;VNf09VWyId=$Zr=9=JNbB8_HuY=&H?I}l=@tGOBGT6PT z>FL^zZMk{VsYY_VX{W)g?)3snHz!T@LTN7<*PzT)7UKo*a3IJumNoa-swiJpmkE;J)InQQ&m3JF-YM!>YoD2jFbalhoq&6cpq7;0$2 zF`w5I(&*bux79Z;_}O+Dh7rj)W3qwRE^ZPP`40Nnz_^SQaLRCK@{91QYJ?kWaAJ9> zsj40uA;{^r3P!CA>8<;DwjC2AH}Xw)3Q~5()mlhalLbgq#IhcQ&MRqY;>1;5!n^_8 zrJ^}_ZU<|v9M&!ZZzDRj*f1{nNMa-Pe7+c&+Mu13zE*aG>Cc?BeF( zOUJS37oXnRHJ0zNhBy(C_>z||_k}j@+YE)DSfsejyKq(}Qo^)aW6`1m__5=KFgP=R zkSnR{i?;V{{L7GT9-h8`zq@}!(EY~my!%oLLobd0HWDsDj9W0QCazdbq6te&mfBF{ z4znf|dc58<@^xF3v)YARt2xDATqcKo6Dil(v-nHv=lRX={={@>=uybRl68*zdE6Tr zmlsPuWhCxl0u*^x@C%LqIv8_pnK%K@nc6R$9j~1ZFZW8E524un$PU|14WTK8$jIdh z1;KMq3l~B~Cna`To-Jff0~=mf!#fOJ)u)!rDc~Io*GCXHExn3}7@*pFUXPZO>shthrI3 zVt)Y9Vs73_=m25TIk)PzB0_X7OqAOEANK2^o*H(Wc~mW#8VZ^k%ZlH0T3$~)LIx3}Io#Zs2HU{rMk zh&e=o7@hvhsUB_&fLf^|CpM=VH5kpZPOz(+DwgExgE=43_Mn?mnzN64lW>)CbyXT< znUAHC4Qw(+hkZ#`A4*?@H$G5Xp%hJLS9G;SKi7H^xu2EwxqR<2@%C{FKtZt44uBLY zr}+*<&iR8hN2=xSM&5|BW5kGD6M2iVC3VS}v|RN}w?5;_obykwn|kA|hm?in#{sWZ z6$HX>NfsIR9P1|}<5xI~(A@gtx+DCC`g$@?LAWachIPf?)~( literal 0 HcmV?d00001 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..760a6c4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "emgauwa-core" +version = "0.1.0" +edition = "2021" +authors = ["Tobias Reisinger "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +#[profile.release] +#panic = 'abort' + +[dependencies] +actix-web = "3" +diesel = { version = "1.4", features = ["sqlite", "uuid"] } +diesel_migrations = "1.4" +dotenv = "0.15" +serde = "1.0" +serde_json = "1.0" +serde_derive = "1.0" +uuid = { version = "0.8", features = ["serde", "v4"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a43b28 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +[![Build Status](https://ci.serguzim.me/api/badges/emgauwa/core/status.svg)](https://ci.serguzim.me/emgauwa/core) \ No newline at end of file diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..71215db --- /dev/null +++ b/diesel.toml @@ -0,0 +1,5 @@ +# For documentation on how to configure this file, +# see diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/db/schema.rs" diff --git a/emgauwa-core.conf b/emgauwa-core.conf new file mode 100644 index 0000000..d433d74 --- /dev/null +++ b/emgauwa-core.conf @@ -0,0 +1,17 @@ +database = "emgauwa-core.sqlite" +content-dir = "/usr/share/webapps/emgauwa" + +[not-found] +file = "404.html" +content = "404 - NOT FOUND" +content-type = "text/plain" + +[bind] +http = "127.0.0.1:5000" +mqtt = "127.0.0.1:1883" + +[logging] +level = "debug" +file = "stdout" + +# vim: set ft=toml: diff --git a/migrations/2021-10-13-000000_init/down.sql b/migrations/2021-10-13-000000_init/down.sql new file mode 100644 index 0000000..2547da8 --- /dev/null +++ b/migrations/2021-10-13-000000_init/down.sql @@ -0,0 +1,8 @@ +DROP TABLE macro_actions; +DROP TABLE macros; +DROP TABLE junction_relay_schedule; +DROP TABLE junction_tag; +DROP TABLE tags; +DROP TABLE schedules; +DROP TABLE relays; +DROP TABLE controllers; diff --git a/migrations/2021-10-13-000000_init/up.sql b/migrations/2021-10-13-000000_init/up.sql new file mode 100644 index 0000000..0159598 --- /dev/null +++ b/migrations/2021-10-13-000000_init/up.sql @@ -0,0 +1,128 @@ +CREATE TABLE controllers +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT + NOT NULL, + uid VARCHAR(36) + NOT NULL + UNIQUE, + name VARCHAR(128), + ip VARCHAR(16), + port INTEGER, + relay_count INTEGER, + active BOOLEAN + NOT NULL +); + +CREATE TABLE relays +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT + NOT NULL, + name VARCHAR(128), + number INTEGER + NOT NULL, + controller_id INTEGER + NOT NULL + REFERENCES controllers (id) + ON DELETE CASCADE +); + +CREATE TABLE schedules +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT + NOT NULL, + uid BLOB + NOT NULL + UNIQUE, + name VARCHAR(128) + NOT NULL, + periods TEXT + NOT NULL +); +--INSERT INTO schedules (uid, name, periods) VALUES (x'6f666600000000000000000000000000', 'off', x'00'); +--INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000009F05'); +INSERT INTO schedules (uid, name, periods) VALUES (x'00', 'off', '00'); +INSERT INTO schedules (uid, name, periods) VALUES (x'01', 'on', '010000009F05'); + +CREATE TABLE tags +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT + NOT NULL, + tag VARCHAR(128) + NOT NULL + UNIQUE +); + +CREATE TABLE junction_tag +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT + NOT NULL, + tag_id INTEGER + NOT NULL + REFERENCES tags (id) + ON DELETE CASCADE, + relay_id INTEGER + REFERENCES relays (id) + ON DELETE CASCADE, + schedule_id INTEGER + REFERENCES schedules (id) + ON DELETE CASCADE +); + +CREATE TABLE junction_relay_schedule +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT + NOT NULL, + weekday SMALLINT + NOT NULL, + relay_id INTEGER + REFERENCES relays (id) + ON DELETE CASCADE, + schedule_id INTEGER + DEFAULT 1 + REFERENCES schedules (id) + ON DELETE SET DEFAULT +); + +CREATE TABLE macros +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT + NOT NULL, + uid VARCHAR(36) + NOT NULL + UNIQUE, + name VARCHAR(128) +); + +CREATE TABLE macro_actions +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT + NOT NULL, + macro_id INTEGER + NOT NULL + REFERENCES macros (id) + ON DELETE CASCADE, + relay_id INTEGER + REFERENCES relays (id) + ON DELETE CASCADE, + schedule_id INTEGER + REFERENCES schedules (id) + ON DELETE CASCADE, + weekday SMALLINT + NOT NULL +); diff --git a/sql/cache.sql b/sql/cache.sql new file mode 100644 index 0000000..3e4ce21 --- /dev/null +++ b/sql/cache.sql @@ -0,0 +1,10 @@ +-- a key-value table used for the json-cache + +CREATE TABLE cache ( + key STRING + PRIMARY KEY, + value TEXT + NOT NULL, + expiration INT + DEFAULT 0 +); diff --git a/sql/migration_0.sql b/sql/migration_0.sql new file mode 100644 index 0000000..939b907 --- /dev/null +++ b/sql/migration_0.sql @@ -0,0 +1,83 @@ +-- base migration + +CREATE TABLE controllers +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT, + uid BLOB + NOT NULL + UNIQUE, + name VARCHAR(128), + ip VARCHAR(16), + port INTEGER, + relay_count INTEGER, + active BOOLEAN + NOT NULL +); + +CREATE TABLE relays +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT, + name VARCHAR(128), + number INTEGER + NOT NULL, + controller_id INTEGER + NOT NULL + REFERENCES controllers (id) + ON DELETE CASCADE +); + +CREATE TABLE schedules +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT, + uid BLOB + NOT NULL + UNIQUE, + name VARCHAR(128), + periods BLOB +); + +CREATE TABLE tags +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT, + tag VARCHAR(128) + NOT NULL + UNIQUE +); + +CREATE TABLE junction_tag +( + tag_id INTEGER + NOT NULL + REFERENCES tags (id) + ON DELETE CASCADE, + relay_id INTEGER + REFERENCES relays (id) + ON DELETE CASCADE, + schedule_id INTEGER + REFERENCES schedules (id) + ON DELETE CASCADE +); + +CREATE TABLE junction_relay_schedule +( + weekday SMALLINT + NOT NULL, + relay_id INTEGER + REFERENCES relays (id) + ON DELETE CASCADE, + schedule_id INTEGER + DEFAULT 1 + REFERENCES schedules (id) + ON DELETE SET DEFAULT +); + +INSERT INTO schedules (uid, name, periods) VALUES (x'6f666600000000000000000000000000', 'off', x'00'); +INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000009F05'); diff --git a/sql/migration_1.sql b/sql/migration_1.sql new file mode 100644 index 0000000..5078763 --- /dev/null +++ b/sql/migration_1.sql @@ -0,0 +1,28 @@ +-- migration to add macros + +CREATE TABLE macros +( + id INTEGER + PRIMARY KEY + AUTOINCREMENT, + uid BLOB + NOT NULL + UNIQUE, + name VARCHAR(128) +); + +CREATE TABLE macro_actions +( + macro_id INTEGER + NOT NULL + REFERENCES macros (id) + ON DELETE CASCADE, + relay_id INTEGER + REFERENCES relays (id) + ON DELETE CASCADE, + schedule_id INTEGER + REFERENCES schedules (id) + ON DELETE CASCADE, + weekday SMALLINT + NOT NULL +); diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..c3b4c2c --- /dev/null +++ b/src/db.rs @@ -0,0 +1,62 @@ +pub mod errors; +pub mod models; +pub mod schema; +mod types; + +use diesel::prelude::*; + +use diesel::dsl::sql; +use dotenv::dotenv; +use std::env; + +use models::*; +use schema::schedules::dsl::*; + +use diesel_migrations::embed_migrations; +use errors::DatabaseError; +use types::EmgauwaUid; + +embed_migrations!("migrations"); + +fn get_connection() -> SqliteConnection { + dotenv().ok(); + + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + SqliteConnection::establish(&database_url) + .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) +} + +pub fn run_migrations() { + let connection = get_connection(); + embedded_migrations::run(&connection).expect("Failed to run migrations."); +} + +pub fn get_schedules() -> Vec { + let connection = get_connection(); + schedules + .limit(5) + .load::(&connection) + .expect("Error loading schedules") +} + +pub fn create_schedule(new_name: &str) -> Result { + let connection = get_connection(); + + let new_schedule = NewSchedule { + uid: &EmgauwaUid::default(), + name: new_name, + periods: "", + }; + + diesel::insert_into(schedules) + .values(&new_schedule) + .execute(&connection) + .or(Err(DatabaseError::InsertError))?; + + let result = schedules + .find(sql("last_insert_rowid()")) + .get_result::(&connection) + .or(Err(DatabaseError::InsertGetError))?; + + Ok(result) +} diff --git a/src/db/errors.rs b/src/db/errors.rs new file mode 100644 index 0000000..edf7414 --- /dev/null +++ b/src/db/errors.rs @@ -0,0 +1,30 @@ +use serde::ser::SerializeStruct; +use serde::{Serialize, Serializer}; + +pub enum DatabaseError { + InsertError, + InsertGetError, +} + +impl Serialize for DatabaseError { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_struct("error", 2)?; + s.serialize_field("code", &500)?; + s.serialize_field("description", &String::from(self))?; + s.end() + } +} + +impl From<&DatabaseError> for String { + fn from(err: &DatabaseError) -> Self { + match err { + DatabaseError::InsertError => String::from("error inserting into database"), + DatabaseError::InsertGetError => { + String::from("error retrieving new entry from database (your entry was saved)") + } + } + } +} diff --git a/src/db/models.rs b/src/db/models.rs new file mode 100644 index 0000000..4f61870 --- /dev/null +++ b/src/db/models.rs @@ -0,0 +1,20 @@ +use super::types::EmgauwaUid; +use serde::Serialize; + +use super::schema::schedules; + +#[derive(Serialize, Queryable)] +pub struct Schedule { + pub id: i32, + pub uid: EmgauwaUid, + pub name: String, + pub periods: String, +} + +#[derive(Insertable)] +#[table_name = "schedules"] +pub struct NewSchedule<'a> { + pub uid: &'a EmgauwaUid, + pub name: &'a str, + pub periods: &'a str, +} diff --git a/src/db/schema.rs b/src/db/schema.rs new file mode 100644 index 0000000..50e04a1 --- /dev/null +++ b/src/db/schema.rs @@ -0,0 +1,93 @@ +table! { + controllers (id) { + id -> Integer, + uid -> Text, + name -> Nullable, + ip -> Nullable, + port -> Nullable, + relay_count -> Nullable, + active -> Bool, + } +} + +table! { + junction_relay_schedule (id) { + id -> Integer, + weekday -> SmallInt, + relay_id -> Nullable, + schedule_id -> Nullable, + } +} + +table! { + junction_tag (id) { + id -> Integer, + tag_id -> Integer, + relay_id -> Nullable, + schedule_id -> Nullable, + } +} + +table! { + macro_actions (id) { + id -> Integer, + macro_id -> Integer, + relay_id -> Nullable, + schedule_id -> Nullable, + weekday -> SmallInt, + } +} + +table! { + macros (id) { + id -> Integer, + uid -> Text, + name -> Nullable, + } +} + +table! { + relays (id) { + id -> Integer, + name -> Nullable, + number -> Integer, + controller_id -> Integer, + } +} + +table! { + schedules (id) { + id -> Integer, + uid -> Binary, + name -> Text, + periods -> Text, + } +} + +table! { + tags (id) { + id -> Integer, + tag -> Text, + } +} + +joinable!(junction_relay_schedule -> relays (relay_id)); +joinable!(junction_relay_schedule -> schedules (schedule_id)); +joinable!(junction_tag -> relays (relay_id)); +joinable!(junction_tag -> schedules (schedule_id)); +joinable!(junction_tag -> tags (tag_id)); +joinable!(macro_actions -> macros (macro_id)); +joinable!(macro_actions -> relays (relay_id)); +joinable!(macro_actions -> schedules (schedule_id)); +joinable!(relays -> controllers (controller_id)); + +allow_tables_to_appear_in_same_query!( + controllers, + junction_relay_schedule, + junction_tag, + macro_actions, + macros, + relays, + schedules, + tags, +); diff --git a/src/db/types.rs b/src/db/types.rs new file mode 100644 index 0000000..8e43cbc --- /dev/null +++ b/src/db/types.rs @@ -0,0 +1,97 @@ +use diesel::backend::Backend; +use diesel::deserialize::FromSql; +use diesel::serialize::{IsNull, Output, ToSql}; +use diesel::sql_types::Binary; +use diesel::sqlite::Sqlite; +use diesel::{deserialize, serialize}; +use serde::{Serialize, Serializer}; +use std::fmt::{Debug, Formatter}; +use std::io::Write; +use uuid::Uuid; + +#[derive(AsExpression, FromSqlRow, PartialEq, Clone)] +#[sql_type = "Binary"] +pub enum EmgauwaUid { + On, + Off, + Any(Uuid), +} + +impl Default for EmgauwaUid { + fn default() -> Self { + EmgauwaUid::Any(Uuid::new_v4()) + } +} +impl Debug for EmgauwaUid { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + EmgauwaUid::On => "on".fmt(f), + EmgauwaUid::Off => "off".fmt(f), + EmgauwaUid::Any(value) => value.fmt(f), + } + } +} +impl ToSql for EmgauwaUid { + fn to_sql(&self, out: &mut Output) -> serialize::Result { + match self { + EmgauwaUid::On => out.write_all(&[1])?, + EmgauwaUid::Off => out.write_all(&[0])?, + EmgauwaUid::Any(_) => out.write_all(Uuid::from(self).as_bytes())?, + } + Ok(IsNull::No) + } +} +impl FromSql for EmgauwaUid { + fn from_sql(bytes: Option<&::RawValue>) -> deserialize::Result { + match bytes { + None => Ok(EmgauwaUid::default()), + Some(value) => match value.read_blob() { + [0] => Ok(EmgauwaUid::Off), + [1] => Ok(EmgauwaUid::On), + value_bytes => Ok(EmgauwaUid::Any(Uuid::from_slice(value_bytes).unwrap())), + }, + } + } +} + +impl Serialize for EmgauwaUid { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + EmgauwaUid::On => "off".serialize(serializer), + EmgauwaUid::Off => "on".serialize(serializer), + EmgauwaUid::Any(value) => value.serialize(serializer), + } + } +} +impl From for EmgauwaUid { + fn from(uid: Uuid) -> EmgauwaUid { + match uid.as_u128() { + 0 => EmgauwaUid::Off, + 1 => EmgauwaUid::On, + _ => EmgauwaUid::Any(uid), + } + } +} + +impl From<&EmgauwaUid> for Uuid { + fn from(emgauwa_uid: &EmgauwaUid) -> Uuid { + match emgauwa_uid { + EmgauwaUid::On => uuid::Uuid::from_u128(1), + EmgauwaUid::Off => uuid::Uuid::from_u128(0), + EmgauwaUid::Any(value) => *value, + } + } +} + +impl From<&EmgauwaUid> for String { + fn from(emgauwa_uid: &EmgauwaUid) -> String { + match emgauwa_uid { + EmgauwaUid::On => String::from("off"), + EmgauwaUid::Off => String::from("on"), + EmgauwaUid::Any(value) => value.to_hyphenated().to_string(), + } + } +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..a3a6d96 --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1 @@ +pub mod v1; diff --git a/src/handlers/v1/mod.rs b/src/handlers/v1/mod.rs new file mode 100644 index 0000000..68f52e6 --- /dev/null +++ b/src/handlers/v1/mod.rs @@ -0,0 +1 @@ +pub mod schedules; diff --git a/src/handlers/v1/schedules.rs b/src/handlers/v1/schedules.rs new file mode 100644 index 0000000..ee3042c --- /dev/null +++ b/src/handlers/v1/schedules.rs @@ -0,0 +1,24 @@ +use crate::db; +use actix_web::{HttpResponse, Responder}; + +pub async fn index() -> impl Responder { + let schedules = db::get_schedules(); + HttpResponse::Ok().json(schedules) +} + +pub async fn get() -> impl Responder { + "hello from get schedules by id" +} + +pub async fn add() -> impl Responder { + let new_schedule = db::create_schedule("TEST"); + + match new_schedule { + Ok(ok) => HttpResponse::Ok().json(ok), + Err(err) => HttpResponse::InternalServerError().json(err), + } +} + +pub async fn delete() -> impl Responder { + "hello from delete schedule" +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cdd5ca1 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,38 @@ +mod db; +mod handlers; + +#[macro_use] +extern crate diesel; +#[macro_use] +extern crate diesel_migrations; +extern crate dotenv; + +use actix_web::{web, App, HttpServer}; + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + db::run_migrations(); + + HttpServer::new(|| { + App::new() + .route( + "/api/v1/schedules", + web::get().to(handlers::v1::schedules::index), + ) + .route( + "/api/v1/schedules", + web::post().to(handlers::v1::schedules::add), + ) + .route( + "/api/v1/schedules/{id}", + web::get().to(handlers::v1::schedules::get), + ) + .route( + "/api/v1/schedules/{id}", + web::delete().to(handlers::v1::schedules::delete), + ) + }) + .bind("127.0.0.1:5000")? + .run() + .await +}