From 5633bb9b72aab434c9af27678c1cb6a10e0d1203 Mon Sep 17 00:00:00 2001 From: Mike Carman Date: Tue, 15 Sep 2026 08:31:56 -0400 Subject: [PATCH] Added V2.1 --- .../UserInterfaceState.xcuserstate | Bin 30301 -> 71421 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 30 +- .../V2.1/FuelEntryControllerV2_1.swift | 794 +++++++++++++++++- Sources/App/DTO/V2.1/FuelEntryDTO.swift | 67 ++ Sources/App/Helpers/PageHelper.swift | 5 + Sources/App/Migrations/CreateFuelEntry.swift | 14 + Sources/App/Models/FuelEntry.swift | 61 ++ Sources/App/configure.swift | 1 + Sources/App/routes.swift | 2 + 9 files changed, 966 insertions(+), 8 deletions(-) diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate index fd9a40b25d789b5f8661ded4265023047615e719..27ae840318fbef36cc70f1b12a437fdb79cfd7b1 100644 GIT binary patch literal 71421 zcmeFa2Y3`!_c%Ov?rh&FTN8R9bVwzQDya}UB%wpxB%2UOBb!hZb*>dF7Q~8@4Ip3v zL8OTyf})~AC<>w?*cHT%{X2JNXOpm@=*#(`zHLQm~^{x5_$#gLa(8B&_1*uy^G#M zU!X71SLkc>4LXHRqqFE&^c(t}#Vp6_*f2JnZN;``+pulfc5HjLE8C6j&h}>eunBBG zwm+N6X0b!qq3j5D3_F&c#7<_**c!H$ox@(m6808$A$u>ogngLxv-Rvo_IY*_yP4g> zZe_QzFR(k<*VsMmK6XF*F8dyPf<4K8!hXtr$(~|Qv){8nus^fEVFv558@I=jOt=aIP&E$@SuTb5UF}m%{bs`f-D}G;SC-oEyQ7E#e;HmU4C6YHkg;p4-5^#O>zZ;tp^pxl`O}?py8*cb5B=`%P!nNxCpyJ6(HS zcU^B?tS&{@SC^_w)1~Y3bmMgsbQ5)xbdz;cbW?Q&xAwy{|i_`#^V6_lfRP-DkSLbbsp^J<_v! ztmpJPy@~b)c-Qd0;UmN6hA#|X8on}oZ}`FRmr-vt7>!2B zXfxW44x`i9*4WP2#TaFbHpUo}jVZ>##-YY+<5=T3V~%l(vCvp#^cu^J6~;NnYmL_# zZ!s=3-fFzZ_>l2oqu*F>TyA{I_?&U0@ptmGDcdy6G|rS`$~8?l6_|=m6{bp4m1&-7zUdm% zO{UvTkC+}eEi3P#0)9a=;OmCX@n%*+KZFOgz2Q| zbJN$RA51@*&YJ!Jw`4#gn^KSDy<^$$;%^#VMn?E*xY5vN>TJ#p) zB3MLAm?hlO%F@x&$r5Ravcy>uElHL^mMbh1EK@86mYJ4WmNH9?rPgwt_e(TfL zXRVv9JFG8TcUt#a_gN2Gk6AykerWy7`nmOQ9`k12!aH~;@8Ub~k$g8khL7dr_;|h_ z-=9zAC-KwxBHqiF^A&t0KbN1!U(esn-^Sn0-^VZJALJk9m+>q4C-_zTQ~Y}Vd43E3 z3crir&A-a;<=^7>^9T8({QLYz{7L>x{ww~JfCWy_33|aG7zLAH7A%5Q-~~Yt1xc_8 zcA>q{L5L7K3Y~;Vp|j9M=p`fyNkX!aBJ>sd2^m7BkS&Z9rU}!9eBnyLBNPZ;p;#yt zt`V*kt`lT|2-gcY2sa8h2{#Lm3QL8@gvW(tLY?3j>V@UP3Sp!0ys$~wENl_B3fqL& zggwIR!W+Vy!aKq~;jr+o@V;>>6Pqs0E=05Mfe6SKr|Vvaajyi)XtUa?f1Db5lrMW0wB){67RtHi6tTf~Lp zt>SIs?cyEco#I{MBJmNiPV|eb#3#k4#P#Av@p*BR_>#Cod|BKj?iN1~KNLR_kBc9R zC&ZKDC*r5#XX5AL58{vFPvXzwFA|nGNhj$ggTzaM$B=we}q-ZHk zN|!REOesqmA`O+YrD4)=X@ry`O|1kjkWTsal#N-7MWAEtGDRZj)}8 z?vU=3?vfsq9+DoG9+6f_Tcww!ozff9+tNGI0qLOho^(X|K>ASnNIEHfB7Gr!DSah< zZNoOsrnBj72Ak1lvYBlbo7KkKY_@P)gsr2kldZQc$`);lvGuVf+pe$;whgsq+lJYO z+eX_a*{-zBu$9@$ZL@9FHlJ;-?P}X~wi|3W+8(tnwLNBg+_ubCXY>ArwY_HBW7})nXM4|f#P+f6gzcp56WeLqPqx48tR348cB9>Dm+Ve^ zxV^2tojuW>WKXuI*!$Z1+56iE*azAN*{`st+lSf5*>mi<_Nn%1_7eLHd#Qb<)*+=?HhUb#!)g zaddS=J7OHEjx6q)7=a}yxj_VzZ z9S=GlcdT%%bgXkc=h*Dn;dt5cnq!aSb;lcyeUAN(2FC%%2aZo1-#E@VzH|KK_}THB z<1Z)c)H@B%4$cT?M`tHzq_eZLi?gel}SoadCCH#%=}-s-%~d6)BE=Mv|`&PSXZoXN$2OzZ=GkHXPv(~e{=rs(z&cIhbzn#?rQ6b zaCLTdclB_ky3$HLhCM9M@H@ zYh5?HZgDMi-Rio}^{}hXwZgU1^@Qt5*IL&G*Cy9y*FM*NSA*+->!9n9>#*xx*L$ub zuA{CGU7xy6xlX&jb^YQx>t@~9&AD}Mlbd(D+-`T6JKWvL-NPO4PI4!^`@64j4|Zp` zhq_0&$GFG3OWiZwv)pCwa(9Kh(p}}A?XGtF+;iR6x^H&h;$G;!*L|ORvHO1aL++*S z74DVpXWVPu>)g+}x4Cz^Uvak7BQXxf zJz-Q%^epdu-(>h5@EgW=E-dr-e168p*kxA6e#XhTWRBpW^2B_}nVpe2p|X0GugX*C z&8RG_E%#Q`irPNna)fXrYqA;*2#L=ARA?qY?dvuRpwr1xfkX-uC^k> z>#HfP@YI0+r8rhK;BnPfd&ighN()NMN^9olOI?a9s|&qpwWVc6&`xSuSyMn?cJU~$ zx5!%r4w^QO38fW9m2+Vzj?4;AL76vqMzz;dG{jR@=B<_gE6I7R@8Xs)u>a1W}NDa_>a-Pp>a;R1Oe;A8t>ZI4Wnf;#fBwu}2wQSiUs#KN%fGTy-Tyv+tZyHr^Q@ zpC6|VxM}~zO8>3$rJQ+D^9n1AyivZw@~FZYltO$_B`WE_qUGn$1)hb4DXgrTf2k|j zw4+%{M{V+@`lcPt0LrPXE-m~opyi5c?;K#J%8KkFpv}_aQg8LZ#1wCz#KII$yf?1U z(WWbZ7@RDjjsmm!|$F9gHk3saCXsFD4x%V~sZ`XhM6uRq3%?zBErmV^OIO zG_JS2plm)M&{yLroRwc(4T9*3GEZ$q;fx%wkFvrzZ}C7+N?~k~CoVR&s{)oqp@w>V zGX}=RCnO{#rzFKC^^K2@?;9H%*SAj-8130YuQw%Dm3enCcY?sB`eeSJxr3Respmz^ z{Y=sd=5FR5=3eGLX0hB;?j`q@qvYrn%o64S=0WBm=3zO8AVg4Kf>sf>71gbY6R7}- z2K!)mX-#x<6&s!5EvxZ>(g8{xuMn!H^zx;+3q6-zQCd^#DQn(ZzLb8UXH>x-U{PwT zfZR(5xtFer#m1idU8t2Y-V%?m);FrMA`28x&V103Y=l}u7S zQxD2mt`6sZ^r7%Lk1 zW@ak|-y$d0G27%M3T|r#NNGU>eu;UNNm|M5U|wc+GOsYZnB8)+oFey?`^o*~0V|o; zm_5wv%p1&`%~6ZPVHpux$c28=nRfD$=Vy(Rgt}Y0ZqR(rO==S-z^uiXv}yzSMU9=;C6Zx8^)O zI153yd#b>$YHY@*&H4~?gmKq1hnaVo_vAtH74qPE<|y+%b4*T^uasv|leNd6O@7YWmbj;LI{#qxGW!wP^|zA|zEm;RN$3GYi7kMyZc}ZzR8bseMS(K48KEPj&R%(xRFf4X6zxqPD2rR50>uYfFm?=7ZuNElGRX zjgI?X&5T|>BWGJRmv^6>r>H5^0Y!`lGdwRl!;d;J(;84Gcs?@p`4P{(dhczUGVk2- z%Hr_@zkK9JIWKZ>(K3eqr#93Lb%#D1snw6V1(hD^iK2kYQ7_b6o-9xCGq0ls&V8$$yyW4KVrhI8^P#vh#Zzkpfq9yvmIwdLq)?A39 zerO;aS%2U^5AYuv1pHUfGXH4=kdB658mFPrOcWX$BAsw1)D~1WJYD<)S(? zQTF~LDx8Y0452~~DgY`hmP;rVHlSiy#F9`FTKCVQm3zO3iG$v;0N=V?Q)K(keZRsljwDMEdK(3$eYb5Ln(TdX}Y4_!?k znGY*nMpt?PtaSN>SDK((FL9;sgq2=}UX71Y5 zyB9t3$*(Wp_W6j$m3|7XRag2MSm_$N($B(5*M_dNaiz*P&og7{&?b4V+&y2qQdLD& z*t9KP-txr48sH7**(iOzO}f`T7eQRpLmlJXOrZ|>(~@|**_8V>;QH!&^0@d9mHNC*U5gl zzMf5G(?HNKmskAH4LdfQ5!qp(nxnOy?|;qCQOy1Ohu%87_mk5>$*>~*iq zI-|p=E$a@qIex8i_-Q#WU&{CgmQ|za*|IUkj$`vEdjDc`7D8YviZpwLxQwEoZA# z(y9c~dXkb>HIUX*p`>M)q>|EHN?P;eXEf5fx<%5`#(6z^BPFaGjrZBD%7^7MlpOp0Cq`bkrVn4hywk?f5qGBS^B0Z;1@o>Scfnod_v9ljiL(D<&;a+s zeLy_o-Z%6;Ut`_#M}}UvG}q4i4t>DVr>@} zkD($cR7{0Rx@Lkfw25;KvzFpzmu~o7KuBvC%fLevfrqm|;GF=0hqFN~pA1pUk`@KW zqwrWtYoqZP`BV8bKOTp3`=D2^eE05&>QA8tf*6Z6socz7b7LOYm*C(B>RUp5)i z6xiJMW<%6@p7-34ls26LTnsW57vdu9mA{t1kx$j*5S_7g|G7-Xm5hk1LIvft zP2u~FaHG0!==EFs>ytlT4Kfw`0JJ6)H2=>B>sK5bZT8<+K5OawQOAO21fGkpqRTc9 z%!u#6jKEjpF7o%leSnv9{UvR+WW12_Nj&ff<&-$!m{|EZ@CfCUkAP!71mh0h2ApyS zueL9)?C`Y2u& zg4a6i2fY3wpH)mWyb>1diO>am?5*Al-uXFo($Y`yE_weSJL%f40njHyL3`a9K4I(h zp~*`}wR3%36ZdW7+CGDyRY`IkkmPTaBsTz;{2t0BNE`5GybYc}z3^7xt3S{GgrY?- zm?d?1r~EfnW&eS|RvUZuT$P01!23bL;5YGJ{1$#2zr&0sh$V<4NKcTFK#0BTO5A`C z;Dh)OK8)Wb$Vw1TP!EE764Z-8Sh(rd96m<3<_N<1(&%|nrRpYSlvmk>iqb;Gu$5a- z=Bbz!ybzO~-X}99Aw4cCHa$5lDj_)`B`PgBAwDW2E;S=1zE4_gd~9Nq4M6;{+KcYr zZWVj__DzIMye8Ygm%df(Nlr=dq{KEI&S&ay4F7D>Dar8(Nhyhaq7u>*G@&Ba^gjS)+;a3&%1^Yw%h8EB+1tj{m@a;=l0U9K*prx6^Tf<@1!Q>2_5dsTQoP|jwC|vb332IGHSFqtn64VVYLF6t}>0MM|jszMm_f)mC z{S~Uy|BYo3s@5YLo%KM>(^p8dpwtL%rLv>i;&_@sQFYi-Y^s?zbrBOJbhUzhoFYxQ zRv=9{Smag#7m90lAwQh!%yk2~$93Vl64ZvEwtlWV2V$fhL6MhD8*tHFJZJ+hhKuFm z2x?DI2ZAE%xjtM1rJ9Zeb^4#HdagesasxtjjOnV?qt=?U8`ihYK0IM}LmH?f?g{`M z915EBL8-6k!L&6`^cnw7^w2LFg6fD%=du)a#AV7~5Y*X^PI5!#w+ZSJs*r|9?ubyM z`T@@5Msd`BL%p~$@??U#U*KWwi3!}qX1vTz;ifjfkk4J&{6ZmD@h`c(GQjP=0JqPj z+#aQIJLKI2xILM2yGo)E8LHzJ5EQME=yew)(VIEgweoYfa0>~FB`D6%-NxNcP&`42 zmybm6<`%a^qW5!4D2etVC_%AWxQ7AvkA&jByv@B)50Ie?mkj!`A?}`tSV|_30qEl` zfDYT`zGry)v31;f->|s0)C`tap&kaQ9jB`>h@fiK zk)G4Uk>)<(zMyFPl>3bPoS-WR8ca}XJ@+N|756nkX#|1gR7|PlGVGvS@{Y(q-E7gY z`5gx-C*X~9Kgf#+O80X=aX%B3K~N^0!MRSs<&8CSzw0pdEq(`Wf0je|501fqD+(Ue zJm~u{Mbm?VcPXy8PR}Ijj5-q&MO_PmvgGLm4JC+%D<>*kuH%8rb@1en0GI1*7vgeV zxDG-hb-GprW!LFI6Aim0-VQ9Cu7fVJ1@_f-)^(xmJDi{q3j6AMFruz!C{KQu{o3p< zFO00a>#K^d_o5GH0{iNs05m!j^w`wct4ezgw{BXP{_Ar$@wq|v)y3%&6lT=*q0Bf6 zm=Shk-fm$xMjLcLosui@mu>*X_ZYu!kPaw#EJ5S``}o#n=tcm(b(y*>-4NYSUAAtR z4wfm0pj?9T2pUh&1cD|KH0cT5NZlwVN;d`^O4vCd2qsrRab5(trn_!Ho0k$8xrkwN zaWgH{^tq^^7~E0|eKu$|wKV#3tw;@H({%ZiNP*U;)akAyXzD*A(jwgqAX1%ISF9@` zXc|G#PJX?vR5z0nDKzK#pPNCt*+BKxp;X^O6j@gbpmRb&L!-#r>dn_JP#NTE${>Z{ zWS=L?MHL{|Qvq@VL0(OO+}z@3fY$n*y1NzWsar(3sRX2_?p{TD&S;67w5OKnU^C|l zD)hj``CW5xRG9~^4LCS}8tT`8*wZc3)zP?$ZaD)HNP+ej(uIfBaSbk7r1{*NeXtL{ZA_Q0&>VpOXd%B&?yGk3y z-d^21Dpud7Se;G99@W&LJB2D%4>4ovb%%BD>fR&BM^Ftxwf~130{PO!7Iz*RGjv)^ z+L>1fIf1n$CEn_oCa)6Z&xwcJoq}0aHT2Yr%V1-uEA%0(5PDg6T=%hjH9>O;nn!be zMyg&xgdbqv+GSDGxTPHZVt!{J=jjm^@3icKLp)E5V$_{6Sh8`k~@HJy<6W3I8Ps@ z4=3n0f^PTgTkFAcx`Uv*s1&&@(S3b{9xN-rz9V%??gXaU-&X69l68Zi4Qq*GKDPs3--IeBb|E%juICQJ)+tj-?`!XJe7m>lTu) zzz?XlM+kaU`J@}}7I@P?LK|hJzM7Imm3}rsj}x@aulMOej@J=XfBEQczMdKjc#{5V z8Z+@<)SA{`ufLhhA!A-WiI}+%E={%(*Ll z@U!}*`y%gpgzo9+?*`C&LP4{li{9MyLdG@AKfa;xz_$LYg1S(@SpR^E@Ff)CPh5P^ zOZ9$m$MIfp#z!+lAjA*ecvtYp_4WGY`W5<>1c505X2GijQYf|UWBqFVbN`Y|HU`LK zOMpzaQ!-hjk;zj*GI_3%Oz2*RekVasYGkteLI#a~ub!F%`nU8D-+G#$XZ-qo`uzm0 zC20L6nKl=cTN(u()*q#~J}024e_wx$%9wQo*o>xZ>yHBhKMqA;3qd{oCjk0sDCl{E zdipQ)U#kTA6(!IOfa~)G^|Vocr&s*@Uiu#>b!@zZCw?>N{vDzX;Ga`d)nHZ;Z4fA; zH))987DV)}AfgR+K(qm3`I`fXHn;)NhA=}o{UK-zL0i>NkOzrU*TAd{9Sk%F(GX#P zIPMDsZTA}@4V?*kkwBWo<kYiTff1O z3ZQABpmA~K!#|B5{Mk0kkwZV1C&4=a+88noLt)^kmthFS`EJ0uA&Q26cZ3XFdt#Jf z(!a#{|Cj@f5d?sEO!cFTdQD5o9+&<{dEPc8msX~WIw4T}awe91)|9M;IG#|F(QIKBj|I#5gR#z zV7RB?)h~E~q`D@@TTv7MH=2xA3T`%92>Ozsulz>d2m#rz3HnB!m`isfbKqr9IG;&- zncC+z1jO`(*Tm#HC!YgIXR(zsy5^T#^qg*I!su9-5MADN8d#-~+Sg5I>ND0577l0R z8ath9r0GLVmRI+yaRD?$SPX4Fr?n*;Z)gr?ywP|QVReMn16U_%y#{!j5t6E#b$XX^ z5n+vlH8*y8uaV{rHKnK}bOI)I0z0&}9x>7k=_X?{K5kq_SPNmT(CK99c@1!daTNt@ zLeQ&?YX~b4*4EhR)5dk@wDzoVJz+({O3+$~v{P$slW}WvKveVN{L}cC@oy7D*zSZ)A#7j5`Ura?VS!ZqgoTydN!V8iyNj@g2z!#SKM?juf-%8x zijs~x9lG@iOIo2>4zNr{ff5(6{0m zPrZpMo>fI(Gxm8so{ECvqT=dOU&)Nh8HLp|XVwPZU`k9m2oZ4=h6=BIp2#> zjSDBL9l>cRh5t!MO<(%D!29$~r|49Bh7(V6|C64FddmQtKDx4A_3zNtY^8Pu$DjNk z9556edccsWy*hv{|13?aPew{wVq%}nD9FfyG^w;ceWFq_lKMm?CC0_YWuzyjBqlb^ zb2CM%y}*$u|0Lxrt8Z3TTtZr0R7z@MR#ZYg4mXVg(PtWAg4Uu58{;>PHjN={EMfaxHp9@A zXPVTaVQ89SnhJ&?8%Nl9FbqxkjA*(t)Y=;{@$Fe}@Y(AZ-+0%``#SV*2Zo`k5I~DU zK`XuMf9!nKsQ$|b__i#P-r5p03{54bnRKEv!1rVm!1rXUA+P_b7F^HB!M#<(P+Owe zCh9JsUM3${g={i4DO=te(H^+Ubak`59MiQX(lo)sbiL^Y!uBK3j1~+s-E3N@C0Lkl zC2ar3J9n7wqX`!8!wa?A>$Eq*3$@zow5G+T`)PuO=|S+N*#U$d1aq24*xB$)4H23i zrKGWxumk@Ow`jDrs53z}Oufl(sy8hsEYRm*!lu@nR+^qL!680rgaveXDW(0_@-i;N zj*d2&XG}C9*|Zk@Mc8!6_BO3IZ6GXg*F-vlb5lFCr#6{hg3U?OX44kaR?{}q3#RR+ z7YUn5*et>hA?#4XW)pT8Vc{r}MAOTroxt$BXw(OiG1w6V-Wmkf98Y&BX(#`13p_Lh z@**4fp*aBO?xMFeh9+D17ta6)9YK?A7;Vw^nZTEF5vhpsnM{oLx3W%Qxyq6l#9lr+RN6lZ@yS%wdW@P4IkY<*0(`4W#vyS_T zuv1{S{{q}(Hk*lB^unt0B`4!7&B{Q~HU(FJG&r6Kg52h5@RJWe9%|+gb_V?@ryr2| z5MXO_ks01Isxx~DTUuu>A@JHWNW_c2>Y_b8%Uns9@|-jaa}{!#s|h=c8X{$MDIp`q z+(JgoyV;9(joUH&;d|D0y{Sv#cdh7B&Iiz|LYLCBs{aChT6T?pH8Cw*7=8vo6;iu~ zlG?R|t;l4GcS)$elx-*TtL8U=4$j@$Hos}!OIhj~ z!d|Pg)ILD&{!rxZaXd3*%6r2L?whx6_t9AW5K0FJ0rXHP=%BxtN$YUhzPsDJb1d%e zcB30v>OC_kD2Pm(k3yJ|l|ew8KafE*k&q1q8**u@bHYqdMMJ&JpHO|K0qDfy&RnDb}@I5*92}NCDVF+4H_2+ASE+Zs7>4B|umVfOd;fUu-d2 zAZm0A-G_!xmL@>-m281QS|ke`3hlSpEOx@)M%df^7N-ReaR*@+QKVm%1PDuOOFL!3 zEp6$7-wC{A=>R)8EaX_6dpJ`UcmYX?)LJ@Qy0;*FEj=tfDW%*^*n1R8u|zYXB_Pj&;PSmFV+PbjDqJz+}hKqN~qAL7d`^aKn%$x=xX|LFOJ zs5YuO76{3PXk5$HmIV~)j}i8973nfy5?--6_ei-GG7u~`0_aVlpy$m%uq?EwlHa08 z{<@0~`X0-p{}R{^QTb&a@QO>qqo-sKvup9WkT z_gdD%&$BeV%C1m8R|2Dtm-4lhecrN#;(L>2GhtT|cD3KK)v}GS&=8~&1WglUmre?u2U=~;o01^*|B5}dthEyqe z=+_&zbsx6Wx@9|M@^=CBy-?7}k58NO*2WPJZY;X@g`vNHnlI-`d$my#@hS$?K(_E~uMifucCtoN7T-?b>=J5J zTe~r$wR4Au0OSb?0eC)==IgbF!t-S!WcPMD)qOVhTrKGO7tCw~B;GMsI zLlexpHk0etSS#H)L%po=6!R}%U_kUFfBfI6%&_E$CPm#KyD+vB0ggxrFZnuK5|2|o3ifhZ0AECHDeqLOEVg2D>;`+w` zr=Kd>- z{RB?e4v*xmyayghazvJ4p zQ&pAogDB^_^SxlAcoN@}>Y~$tJ)UNXvEPPZ&nBLhrfWm*!zU?7=MyQ?zrVm^+7knK zkoQfGMAvSl@gx5oHSnWUYT(Bi*73Pa6hEF)13h^}vG~tYGY~UC4Hz|2!(>VgQwaOB zMh(+}8Vq6lm5^!($w;8+A!B0;-Ky#?g=s@9=4ti>U&7BI?5~9V&Ck!|XA$;y!v1;L z>X1`Y4abE~ zpqtY0TZ!H9)YylrOgNap#6QiirQ`?K>iBg8TLSw`{04rba+P4Ka`Y{~iQhauc$BSD z^r4>L)THbKzm=zNKJnZ57x?W23j~VJXIfDQaGhC~dy_pz>VTmY!=4Pdm^M^coMZO4spw2)5VpZxHN|7X(*} ze;W|~4!@696sjpL_eQ4f>AiN+*4o<$cB<>qz#o94fFXUt>*pJS=YaEv_;+cehY1F> zyhm_YlSZM*WBdpFhm)XDpMo8N!}F!yvG89~Y(i`l{EknEi%XA9O^tQCzL`%>UV9PpYWg2A$>}4>pK2(g4@vdh5lKm z%5r?o?^Bmy+XJUz8MNk4^Jf|NYW`dP4F4VfJ^us$BmWcsGye<0?Fep9a0h}T2<}L5 zCxRmh?o4o()l4D}YKs4Z|C9fV|65?-?Y-#)PC5gb)Q#Zo1VbsVo&@&-`808IsV}pv zw1gh74>hSEqprjYm8<5I7JA`4`x+>cRh}=on_kZLO(=!xPjKpSz9gwPax#XG_f(g9 zXsNMgO^m24RLZX8OLi5qX_2i-!H4rDSJNJp?2>diZaSoAn%hzhb+N{)n9EasK{17* zIWE%OYpVMxUug-9JO#az)#u`qq7x>J^c2P=^-c+_v)~ZI;l%{O zDYyi;5Jqqm!O;ZA5FEQ)XeG23+6YjpERNuKg8LAx)YfnVLo~0&m0MavmoYr##&}32 zQK}$>-Wpv+%QL`Y#)dov2Tap4U~oI9w!9qDj8w|Y@s`l~V!k}3dfE86Eg(Pr6Oh=l?tLX;3Kz|@imP9`{|UWgOog;oUjCAc3< zk`gdw_U3bI*7um|Qm8CN>!|g*FkqUea8{1D$^+$hDxnrfj|)SqeT?@&f>Y#R)w*ZK zYOmo4{e>%97`*upAyt6g@p}m#KrkG)P{>4dVVQ^qA&U`(A;M59Wa#PSqivj}K&#h{ zSj_44hJRnD+i1$IuB@zqBHdCei`C(5+pb5?*na&7L=H^N9yWZ$$ealir{)Jr`tgD& z+3ZlFEHFQ{ei?+rQfoj?70}X3P|7b{#*=bt3!wxMyytkp1ytCo4ITSA1pw>9Wv;HZ ztdkueAhV!0+2QsbB0541*UnwKcIyt;a!RlALSMyMWu6irEvevzlY2_5VEv(tAe=BY zf!28dII6{hF1BrS zVM6G&4YaPcOl#>?RyP)dfe8oi=FYG3Hr|mKN#8JP^q8?N6{l-n%wo*AV1+t(A~)|` zWuH5K9N=0}{GfW&xza^frUFk)nhetR+|~6@8P~#9fSxuzKpXyP?HOlPJC6Awk_vzZahSSE+5WNMiOP(=O?<{{=0W+{}9Uk!!hH#4s= zyP4OR*P-&yTTn9oW9AFyJLWHBg7PM1Z0d3N3(C?ihe>kD|w*9>Vi*9R80`*!_1nQvGi@6x~FRa%Lsg&N^8)lyV;h<=e-x6W9WF z7CVoSRqEp5pop)I+);8kf8*p5uCnK z7>_py6NO251KvPzhWtChnFMFSNqoOksoLqn;*V0-MS);U0MzZmEm5GHn)y^2T?8^0 z%r6Bpa#^U)Lny9Y2X3~ zJhl3pw@A?|zO3rX@=-ZZq6e6q){RgOG^2>lmj=-Ww7M3#o`S~g)BN=0gp8!bG&s91 zD)8tlUTyo#Ih`59l_=$~;fI@Q-afxy9 z5%8;TJbc6^#s>B2Ey6ua(rRI$aI0{eaJz7aaHnvWut>O@;2eU1y7LGgPw)hSClc@u zF}PV%z_b+Z6BY~i3rmCtga-jl4^!++CHN_VpC5ykp42C^D|}UoFm413KB;Ii6-+&!PJxL_o=R(n zDWzB8@Pftx1e*s9GtygLSv`M9wWn%^2BuHyYQm$lCXo|B7(gH7ZY5vT-YhRBJ2`&5GB!&{V>-Mk%bTzduBfr3*T!Ox|Z zbhCDIDuIB&NY-HYLtNNhCo~W|t4=sbFl4M;;Hd_XdG85Fgrg8cgENq+WEewFxqu?O z)m|T%+lnJYa0S8Tqiy)Bb1cb%VxYA$m})8*a8N9=h)Uo|rL-y`Ey6ZUP5Xlyb8y8s z9Skx!N^wl34=BE}q7Qh#TlCDg1J_EO4K`=1HXR~5b?(-qS5!<~pTv~D0|pIF%g7ow zeAJk6xf3T(oqlD5(PWl#t2{L`CP7VN@4TT<=68j~3Rb4(U{44fi=6|8+>}Ns26d#p zb0cV_GjFxTG$6Y}n(m!f`xQoN^Z~c9iPe@ONYo@(f(;BGJKi6?5RcZn)W9yLpQV zZuSgc4mU-(-aB~R4%efldbkdkV*{5Lb{hwmYKJC8s4@oWkdU&&&JgQ=JYhT>G#%g$sg*&6m5_Imaf_73)b_5m;$>)BV> zSJ~Iu1}JRsA^W*Hy`Z63u71}(*)yt@_<`^t==aQdRb{H#FuE2F!Kr|f;@-JRP-702 z7y+?WFMK2%XQo{;e7;nWpIb8|eSBO}#`xUEK0s*4CpCU;(j~PNeaV6D^2g`o_lawS zQ#d&XG;T#nqpzZ+76>PV&o3z2ziJfiRf=fWQZ9=rQ0g>CzK8rMNtxMqFr5kYYDz?h3FF9Ft>0KVnH&&H_)lwNbpT` zYSiRbh7NS*0j;BIGN{1{*VC2P&}v4UQw`Zm-%V4kfH;5}>X`$F<0}P&%jRpXq>sr{ zZ7`Tt`i#=DB6vnw6E%>n+-q*70GlaFR5hX%fHAZhd1bBdoK;mE60xHgStmkpln{J9 z)rSf@i(SQT;GhRe$3Zlq9aV{)|c(K|p(HmFLn|kd5Woh3>XoB+n^IN4nkUBgL&2K@MO8Cv`LNkd~i#s#WA{A zYqPN_nIZTN`tbn4w-daiY3XQ{vICfl&Yb5htfhueCKPt9uB@QJOtmL^n~x6~TAOO9@^C261dep;lA8dw#zj zD$WbULNKKP1C0V(4TLC9kMWdN`^6$Ewl4^yjiFd95y2_AhhT_2E*hWf@y&{elSXQ{ zJJBHm>XZ>_7pv5Z_XmaK)OyPs%mdNUSC{7Kj8ihIoy5t$3XX za^VqzA0-%8`myEW_2LcUjnoZ#oZw{y*99D)PC@^pnZu&^Dbd+JU#(Kh_d-AlV*|rn z2nMiMTS&u!P0O2~*PT*3Z!GBuz9xvTVEY$xlrG|bU0!!jTlTv}DD>X44<;@VUJ)M@ zA0l|U;)AUucy;hKIPp;t-bN2NJ|f^{g*I6)bUqiTdtbQSzajF?ky`CT`&{0mZG1)6Rq|Zf)r5Qzkqme@2#LV zRZ&`PO^s6K9lXm%miNg%wX;Ee3f=GtE(%kp^>O)P+s|9 z$*FLl>H^AG5$?*2m&e4$MO+jL#BF0Uxpd_kTL`_Utg6!bc>=i8m_nukg1FZ+4>Qj& z?=btB1I!`jUFHb$A@e2k74r=QZvQ|!u-R$oRt?;CMTw{{8iWRe&7OgZp~x?_(AT54 zRVylJHODu%s?b*&71xXKR-;CYK&MXxtwwQ;Vl}S1{8r=UnSCo;U#iu(P#yWdvl_*x z#kGyoTSxGk#_2t$Oz+A6&h+|U>huEc+drHh_PtIP6Bt{el%%?1l3Pih^M~MdAi4vu9_3yzSTwG98Sa5{Sh$g;8yQovO(d zwC=TWyedMam^I>S;vVsJ@eT1!aj*E6__p|txR20?JFNLef}baN6TzDa-a;^pcN@Vk ztPvYP_Z}1viHF5^#rMP`;!*K^g11vme2m}^2>y`ZFNy9X(S1X7r-<%bIxmM}t`&Qr zn!BQp)Hr0%G%sjsC|h3+`!fH|%PBvtc0BJc&8D5aZfumM>EyQg$F zSFJ!bxhZsHm3m7HX%^0)r${tc@Sw1@+g^{-o+1@sac#;VA?~SMrLHN2A~U?o_{Y#4 zXdiV$nzsgzMQ8^0In4&lqM8}gM|$R!mcx#mYMyGi_(D8I?Se1Guf(s#ZwP*o;Fk#A zLGa7V#na-qc!LODBE$(_quU^Gn%~3`-r^c{RMeXY4l=m3lhqZW3pZ(8X~_%)SnKVq z2xaS*i@%D$iNA|~5c~?ky9nM*@T<$=72w{m0Y$5%Ero+Rl@nXrC>aT$KOk)GolEU( z-#E`)MLfckTQ|R}L{4FLg`llqJ(}HA9Fej98Od(xg@s~M(~>i z?33y($McyC|A_w1kWm689 zA|)t{653mL=&dKgaD*Dc4f4cByg&mpnl%t3rNCB?6f4C^@lqct0nW@zl9CCA(CZCXf1$rq97YC5=w`3zm!~7X*#GB5;d+m1SPagn?sM zGF1yfp?`I|r$uPG$K|W>lvg!}?|Ht~$7E;Z!o6mV_dXx+NNm9%vnis2i#(t@U`c5s~f>4)KD$SH;O{Rf& zz!wB82>ycL&-0~ZrO3TcJ4rj{{DePM5`?;qH=8vY_E><&IX4~sdo?h!#9Nrui=19 z*x+fRy{0uPvba9IK;Eg})t%4)i;CGk^?N{5!pyYB@>OsNYeIC91gCk_OLt56NZ?`r zNH9bhf3BAnOZQ7l2>yj|hluV36|8n>(wFKR3hE7PiGcUu(v%w9Y!r{MO*N*pqQYC$ z?5a;)PB{EyjkHvHOnO{eCe=xPsa{$xt&qTIJxjRmgd0n^s|a_PaDNe9SE3t1bUvbk z%Hl_))zTX2N$Dx+X&{nkq_xsI=~-#Lv_X1K+9*9QZ8Dyawt&5^5oR+to^mUgMa2+w zp_|ZDiv+S4o9k&4Cu5vEC!>bg$Yc^BBO0#EZ?@bv7 z+SEqbXp;r~9D@HOoQZIt(S9XRK#S`}xGr#77vap!#ltq~1w}l(#PlYdfnZ=O+;5fVo9G;iA6Z0BAotj&dLJuR;6q< zscR7BtE{aq1QLu2MnY)FMrB4#K1Pr6mUw)%J}tIV6tr}{k$z#^Yot@sY3W<(jP#xK zz4U|hqx6&XGvTa+;|V7aP9&T}I2+;YgmVziNjTRU5JB)Z9enb(CAcKuQb* zq>&O3=@by@PU*%R9cy&V`StCBNR+dog6`mP|zYpx0btXEm|ExN`Olxw_dun6n1lvWspF{1XRW#WkF>O zf}N~KBMkO5e_Gv&R7R2&K)3NDiJ%Yy!TIM;01F@FcCT;c)+1FQFb+cF)^wai5rT-S zs5HoJ5?59OZ8KFQKnqaN3{*r+R9#X;LP|_sS!Uf;LFyv)fCUBM-WbW>Sln!+KG}2n z?qlDvk4Qtha-=bmgfyY6MjqO=NLL3VfT;ksMG^+>7!ii+wgspWfg9lWwgqX9wD@LQ z#9;*3oP4z{>y`p(3oHfF4rz~cKsq9wU<5ym5QKq7J`lx5h<&vbNW%>_3#_vlnAfj{ z;&;XY>4o$rBVGswO1&V?Pu`q*_|xs!B>eA8!{#r4Zx%Lx`#WRs#R4G1H!Og}7Yp!z zMV4`0q5&C&1l5s6$kWI($g{|E$Y|twWDGJE8HbFA5mGQh8b-*#2m*|dg%NTvLLNpa zz=(q|LJ^>vLK1nALJ)a*L*W9MyuQ`8f)Pq&RIle=uPa<2$lJHSiRynrNI4R8S;|IMAS+=6 z5k{zHBdd`$FhUJR==^i2ZbY__QQd^RjckSy>M%kBMrh_BTakCjsMdlJ+W&b}cLVg_B`ko>$}&JHQ%aO5D5@+0)H>Bce`;lAaWO$Ppsh(1SCtkgs)&D&uxo&@ z50DR$j{sqxP-u_|Yr0NYux^cEMCdwULx9@u9|8Ll^7%Kw9)%I4Er5MPVVH+}i~I%o z4*4GW0r?R|03Y@cj5q=#%wPogW)|NlyS71D(=U|$>x`r?q(mV$o^*KqN$dGkn+*M( ztG@Zm|007?`!*PC`Gvv%4Pvg=U&UP4*^FXA0qyW26f24iwI9Wf;y`htxKP|E9u(Mi z0^?%?BWz)W9gMJt5e_iI5k>&B<_sfTicowT5j2$0hW-o+2%^FWSF#gLX0!Wd9XcXt z1I>TQ)qla{tx)v`*d;qwMDHMQ%I6Hct^ZwUMkxU_qm*I9X7f9gDnRpJO8KC4P-Gcd zlr9QPj|YtK%tjfYfV<-bBYenc{zo*NP^PFO>u5q9CZow4fMz=<@FG*TlvjTt6Uqu@ z2Uw1>M%kdio#qQ8{9uHC4$2xNn6lB~B5=2yhFD)TP6qJ!vRu&Wo zZSJMTRh7j>C1t)tYt#wUNmMur2C=Pb)Cmer)M*$o1|wh?5dkBPkwabNSj!mDNJpId zU8n_h7Iki&)<&pU7!eMGHq&1tv51rF{xB+@e3K@?h#$UE8Fg_zq>4E4_d=>)f~VGW z1W;HhWJ@j%6LJV32XHnWvCZ7|Z`l>0Uy|1~K>I;QaxVk z4bTITbU?JtcPvER04F-AA{cQd3snpw&aR*6{O%pd=Pa9-m!WDv6c%+0RgS7aRidg; z)iB~5jEIIoBX2|ui~w^LmxroF)q!KH9#kVmH;e#Y_yAa;bH3oQr3R++M8d$14Y&iM0KLN zP~e+^TkislNX$X?qWVz%FybPNxB(;XkZ0UiV^d$|2fYgol(=%xyU}~ldtpQli~v}>5XPav#r}fG%MK-7XFnTRQ(F{jT!tM48L$^Yv5S$%OhW~ zygBYXXtGu?nwPxD0A4R=qxsPSFroxTtT$ErOZ!ISaH0TtRgFcG3 zM1yT-J&b685sf)$8?-HXIW@tEb@{meXfzk?0@jG@);01Sb#b%@xYTp&rGKg}j`l&5 z)pfS*sxCR(RF@zS1=VHLK+hQoNzmCwnII1Ssjdz-W&{u4y&fxDh7tE*1UU5uyRVNhXh!viU>SNIz3?qqwg@9Yg46F$ zBhYK$Gy;7e{Q&(C{RsUSMohto85l7KBj#bm!q*)cdTb-)HT@+{Mz^K!(8h@}`X%&^ zd>kCrKU4?rEm4m@ zqm9vlL8k`98yN962cw74CtHkPV8pxsxVMinrJ%(e+G>Kf3Zjl+%)q7QTQA)zv${Dg zmKd9LYlX2UTdNO$F=8g<%soI@NpVSW5h77qkRYzCDkveQE-t7dp(-ONDM^r4R+pAi zkroyG4q-7abmbT~j624It{US-p$`ZPfgRdz7}^6PeuW_}GGQUkaLzvh7UPHU{|Z=W z7YtGS5wMt009Z^I<{0KU<_F9P7}^FylrRLr&<+^d`4zAjmvz8G+rI!7O9W}80ghi| zdA~dK#hl(lPC0~tLVpmT>~HGG|0&QWt%DW<8Nwhd(VFfkIsZ#mSom)z5^ij{{Dna- zlJK`Gcj51UsSS;O%r#6t8MtYfbW8>&6O)C>#^hjfG1oD9Ftitjs9=a1hG<}j7KZ3x z2nj=4^PM(RDu1S8 zi+PB7LLTHtKph9#542OM1wkMeXxJXz;_Yva*bB_-4dA{4z=b#faDnbEFi~4|Z;yha zF&|q$M_zBQ^&}k`F==rb5eYRxae}CXpakgB2n2|v#RQ3J1T}SnjD)JBsQUN7`w@Wm zGv-$;1zk0EJB1z@ynO566@ejuTwwOVpW5NIcjeGrBev#}P~qcEfdLn{B6 zw~DpJI)FE!G{f2hZxvDoDA-9x!M2t^g9p|P>jiL&b;o*OJzvYet(R^Uecqgwlh{-1@WjGocxwH{h*^GJ#N_IK zauSW2nyQ4dh?JltksKHh2Yqj4WYoa{t*9D7TvS|?AS0>v9eQG;>B_M&*jQ{Fpl1Sw zE}5Q&>-0PfL*SkQhva~szTsQq0@zE~%iri}3PV8P?)Py4Y$}}_l_wguaYy@PFop(8K^Rv4H|3m5|H%MymHU2x}5HeZ3f|G&?!&(-kfJ{R^K_7fSK z@39}SA7RK1hTLJuBM18#`zslnfTUjkactstP|)IbZe`(j+UMZ*fJ^soy>x5)oG;UY zqroB9v5BK2W7CJc$)_j#oLl?;m~SlLFU%xLNfK05q}2q8QWC)96p>ICB&bWN2r7%p zsHqVpWW;48Rlmbb+&;Q;93zei$4pm^W2I0hGc$Odnc*-L07Kw>8!$5^92A25KC>Ig zf#dwfOt6s;{3A1QAo7%lHWJxP2S@{opVA zebm>TK9HUJo%i|sY{M^H#mTSN5kX-abwp&Xx_^kcIK2(xp8P`G!~YfHf{!IkmYWm) z&bQleTye%YO8{UT31@;c#T~*O#vQ?#;mmOsI6zz&hEBmy1Pn#OP!tTEhM_YsbQXrr z!BF&i+BeRILJ()S(OVtoM0Q+39WZpB?6}5$?X3=7{risVzwk}}(y#sB<+$Pk0D*CV zFckBJz_<`V;P1Do-(2b^abyn(2aqfV2~-zaG7MsJqhsiPmcIDXC)t3v;p8kGJuo+{0U%#>O8K=L<1LR3anUF3ThF2_~iDsfe~ zYJlN#iW~}X6J)_qE(~3Vp)?p;TE}o!xJWqP?-B}ejkqQfyoRv5~Gp^PsShSFgu zd!u{EdPVo&FYOi;+o;3$1KE+3iAzlWuVI8>AEf);pn$H0i(EaaIB0QgQ2`E9Eel_4Vv#K32r`N8aE5F z{ctldRFH+6gCQWU^tGP(OXA9Jvi)#NxQ8Iy54Vh4!L8!faQAT!V5kU&Zop7643)r8 zDGc4r!#%=1CTIKMo&tvjDubbU;IjbRwFv(DAD!)odyD%(2Es46cewX3bPI;cVW=Vp z_YwCA_ZfyNVWhi1{@bYaw~9OeZ`SL#8MPZm8t9?__E{U{SKpqnc@g7pFZ#Os z`(o!eEFm6&-wDRlzfhriP&c^S;gA<4v7+TLW_+yR{xJCXvkq=LU$AMG{JT0CM zkHn+!XgmfF=vNCvz}=~bp#~UggrO!Fx}8t)GdXsSX8=FopfX%{BWT`CwxOMWW9#yF z!a@IxW&FNv{s$6|zAP&|4<1xGWZ`*Xs3i-}2Se+rU4J(<4=;oV-Qjca!gvup2)NyW zA+UqIn}Zj}OOWjjz)Ht|-0tAzC}{EWTa8KI_A2zn4mF`d7gB>C!t^5Od4*R5mnv<& zRJXDtF}g-Awyy2cA%#b&ufN(IJQ1(HZbtBGWHZtQ%*dZqhFa4Z{Tr!y`s>y!Byjn)c zt7Y>QUGQ$?d&w1s2D0#AM+cN}{(hP#-Ww0{vbNq!0r)`jy)*ST|^4vk?z4jjWv&r-M1Gx0W*7~%d&3*QMG7kn2CfqWfnI-3ov;^QeCLQZ5B_5so?pi)36#6EblcLaEZtaG-m zcH?OeT2I+}frmGL<*k=Ou@7V|5b6acpG4fk-5_Ve!Lc5tAyU38?$KjGn}JU@5S1IA$EE zKj2<3s}5Z+Ciw(|BX@B410tAULk^L}cD@+NdHf=9eeeq~w3>xqf}yo_*9Yj+d`G$p zzXqhM@b~e+-MJ4#kG`qU;2-0kfjR!8bQS(53Q7D672-vFsLoNvP{mPQr|P4cqIymB zhUyop_f#LLK2uXrZ>L63@1WjAy$7@>rJ+7R%}*^zEkZ3uEkP|!O`w*iR-{&;R-@LW z)~414y(%NAFHqNlR(UUJcF}Or5J0z_2+%F0n5K%Rho+BafM$qhgl3Fp0`yv#rkSOg zr&*+VO7k=AHd;y$Z`ny}OY24(2y|v6Y0uFn(q_x#GS@n#9hH%#iioXa2a5;bqCju>%{fo`fvka zJMa_mfj{FZ@Y{jwxdS+xSUeuT56_5a#Eve~y2N ze~15o|AhaQejB|s{ZaZb`ZM$~^l|hF^q1+A=#%MF=}YL_=!fVR=$GhM=-22U&_AMo zLjRQhBmL)n6#KUCL+snJZ`Zy(`>6IE*r&J8d*8)<<@<*BJ=^!wzUTWsF;FmUXMh-X zGVEp$U?4CkF{m)8GN?1?F&HoyF_0Kc8BQ>iF$^E@ zGpaG_Gg>g(FnTijF$OROF&<<5f$=0G%y@?J3S$OiCSx|^b;dl#_e?vPsF-M&=$Nof zc&2?!j7)+|vP@b`I!t;@224gwBqmd)!%R+0E=+Dr9!y?LK1_a00Zc(m5lp9<&N7{6 zie-vtDrahD>R{?(>S5|*8f6-1y2mudG{dyY^nw||yn~sRnTc7LS(N!8Gm%-1S%X=Z zS)bXE*_hdu*@4-YIfyxgIgI%vGt3;p9L1c(oXcFnT*X|&T*utN+{E0>+{!$}JizK5Nb9m$Sn$FlRVE3j*`o3UH4Te4fT+p^oU zJF+{o2eF5+hp``LKfxZ(eu_Pk{WSY^_HOq39J@HUI8-@YI08AsIF56i;E3Re;yA-` zj^ipvDn~9yK1U(P4US5VCXQx~R*p7~c8*StZjK3#DUKPAd5%SnWlo3_#kr4@k&~H| zm6My3my?fEfK!O`Fy}eWBF+lV7S2)5an5wW5h$^G37bTW5#og=PFMX<}uUOcZHuPUzwuNJQk zuL-XuuQjhNuRX6LuQRVJZxHV>-XC~R@<#HW<~_?B&6~`d$6LsIgSV8ojJKS3oOhY` z5$_Y;r@TM$zTy3a_dV}N-p>bk4wxMXI}mXo;XuxT>jz2?G#nT>FvN%FW8>rC1j6UmIUL-w5A0-#xx*zFEF`z9)RY@V)2z$oH9_ zf`2AYddw5-=4wEMO+!BH$+AA>bw8Bj6_xAP^)FB9J0*TVPIb zn;?szf}pvev!I8dm!OYepkT0IsNgZdXu%l4WWjX7Ou=lye8ED&8-gW*J%UdK-wXaK zv`vUoXqV6)Au1snAqJrXLQ+BsLW)AlLPQ~LAzdMTAwwZ!p<_ZNLIXnggjR*#3cVBF zE=()TF3c$`Dl8=|BP=VdD6A|@6jl>HEbJicFB~WwEF3C)O!x=klftlYf^eenCE+W= zSA|oAuL-9MX9|}HmkU=4R|_`?HwiZjw+gR{Y!jgo!HVET_K7fxu#0etaEtJY@QH|v zD2b3nOhv3j+(k}^gp0(AB#K-TxgwG(k|vTNk|k0mQZCXga#y57q)Vh?^WMTJ42PEJ%oR8dq{)J*iKsI{n_sH3QhsGDf8Xqe~^qT!+uqNhdAiJlj| zDw-mCO*BLFh3IE73bE~CkXX1_tXRC*1+hzF_r+d`{VeuY?7g_IxS62 z-jVE-?2+u39F%-1MJvT5#UaHlbwEl$N?b}xiXbH?r6A=bbyccP>WtlTMe;lrE7jmA)x`OZv9-sPu&Nl=Q6hg7mWVn)E~IC(_TPU&!o`*)6kIhFXSJ z1|@@)!OQHEVUl5yVUsx^BOoIzBPt^+LzGdI(U8%S(UH-UF_*EHv6FF>ag*_s@saV9 zxhzvI(=5{=(=F2{Gbl45GbJ-CGcU6!^GN0mVK-qf0YlhNkRV7Ahy+c74ndD#OfV%J zCYTXi2!Vt!!g0b$LL}id;T+*SA%&1nC?k{;DhbtuT0$$~E}@gqL+B?A5@rbtgeAf% z;XdIZ;b+2o!bieqSxVU*vb$yX%F@d+$}-Ec$#TeY$%@G;$|}neWz}RgWVK{$kWQB^5XJJ@M@QH30Z zGKFS^I|}UzT?)Ml0}8_mV+tz@_Z1!~JW+V2@Lb`g!fS;$2N@0$4q6-xIhb^?^5Epb zwSx~2J~{a8;ERK=6mg0iihPQKiXw{Qin59dib{$qimHm9iW!P6irtD6ijNeZD85$O zri4+#DRC(AD)A`^Dv2pcC`l>FC}}7eDv^{9DIHO=RdQ4EQ1SxG)_zI>O0d#trL#)s zl@gRLDqT^!s#K&@qtv0)t<4VZImRbACZHAb~ubwc%LHH@05nyi|Fnv$A|nvR-@+F>H`N=}2i1qwr_~>*zgPdL0cq^iz-a8#VANpI;LzaG z;L$jsA)}$DVXk4RVWVNM;iTcJ;i2KJ;inO(5uy>Rk)V;NaY^HfMzY2=jdYDnjU0{Z z8u=P!8WkGV8nqg&8a*2Q8bcbR8WS3m8q*rH8uJ=YG@fg`(s-lsLF2RLHcd)RHcg_Y zp{A+kVNG*QYfW2Cdre2p08Lo)tmb*mIL!;1S2a^K(=;@`WvOMY<)H;@MQWYV ziq?wNO3=Efbww*#>zY=ER;5;rR-IOZR+CnXR-0D4R;N~vR-e{@);+ChtvRg)t%q7K zv|efbto2swoz@5KUE0*zblPZbdTmB+7Hu|dqPC5;r*?pLuy&aC589F1XSAcWW3=P6 zleBZSYqaaM?`RKdFKe%AztDc8{fqVo9SWW8I*`sz9h?q_4zCWMj-Za1j)ac14nap# z$5h8n$5Y2g$6qH%CsgOS&Pkn9I#D`jb<%V)b+UDGb@Fryb&7RLb;@)qbgFb}bXs)U zbUJjpbcS`Nb>?&ybyjrl>pawXtn;JJJDtzE+jJ4SyLG8_X?2mhLb^t}R=W1OPP#6- zp1MA|e!2m=5xS>!FY2b~rs-zt=I9pbmgtu0mg`pPHtF{0&gm}bKGgkLZecAg>ow}N>ka4)>5b@(=}qWO>aFQL)_bb=T<>SSU-UlcebQ&s zSJ2niH_|uJKdf)AZ>w*w@1*al@2(%959?pjzoMU^U##D(->N^LKdL{jKdC>bzo5UY zzpDSrfWiPW*lDl_oP8k;Fa|gSE(0+G4FhchJp)4nlEEPZGlQcB)&_P4js{@{KNy@e zfDIxHP8*yvh%tyWxL|P6;IcuwL6$+TL7u@)gIa?IgWCqJ26qiQ47v?^4JHld3>FPm z3?3RhF?eS1+z@RjVyIxKYN%nTZK!8xVtB~V%Z` z9fnYeBbzy@sGyOj6aYlNR*@ z5|V@_VM%xrC+Pr5fFw+kAW4&CN%EwFBps3=i9|X?vLIQJY)SScA5tJGgmjD)PKqF% zCY>cklaff+NExJTQa-7OR6@E*x<#rWwUF9LU8G*p5NVV&L7F73ksgztlAe=(CjCPC zK>9@b)r86fX@W7qn=qNMny{O2nsA%&nn;?+nkbkknW&j)n&_D5nV6eco7kB+nz)&G zn)sObnFN>wnM9bJH92n*XL8Zxib=9bs!5tjk;zSya+4~PdXpxT7Lz+BgC=7p_e`cu z7EG2+)=VClJTiG>^1F|}qR}ZHietG!U zBioKp9@%lk|48_eQ%53?oH;Ue6WV2MWG_y>z9J4&L0<#-tC1y9xYRwwVZkx53b(@WtjhjuH&6v%bEt)Nx zt(v_sdt>&_?4$WMbA@URHAIBs#m0=9^>IBjv!BGV$|ZzdRzKg23m$#p0PY<8Dkl5dBO6MKI;MNVe1L&N$Y9rS?ed(udUx&zqkHq1KH5n(Al7D zFg7?FdK+sS4;x>b0GlA2V>TyjV4Db=%Qo3Iw`?kHYHaFlnrvEZ+H5*(#%=D|OxeuX z%-by4tk~SQd1&*(=C#dRn|HR`ZTH$z+tS)1ZPB(^TXtI>+XJ=&wxYJ;wvx8ew(7Pd z+e5ZywnuHPZEbBMZ7le0lR~CI(FuEmUcFF_I6HoK)%t=+wO#2xZNqcD7!Ou(RML*@pc#N z((SVBa_#c%itLK*2JL3;R_yNEJ+k}J?v>peyI<@+*nP6!VUM=ww&%4MwLfTYWKXg` zYHw|CXYXL|X&+=CVjpII-2Q}pxP7dBqWvZNB>QXj>GqlS+4i^W>+PHDo9$ce`|PLe zpV_~#e{KKP{=NMt2MPyDhaC>P9jF}kJ8(L1JMcR2IS4w4IEXn&I7mAX9ON8S9W)%Y z9dsQ`9jqK|9UL5-9o!r|9K0NS9Q+(kIz&30afo(^cSv-&?2zP8?{Lpy)!~W5Glv%r zuN^)(e0JRCNa+YU(l|0Y3OR~65**bX%^WQpT^v0fy+Pl+V8>9$oqe1moTHrMoKu}MowJ>SMoF6}NOF85rfTxMOCTvlAxTpqZ*aiw(K<4WyH=Zbb^aAkI7 zb7gnsbQN@!b=7k6Mg6kF6tF9@o*IaM7*1Gn)4!MrHPPk6F z&bltRF1xO|K6HKJM&U;32D$BY+wDf>M(c)h!?@wy_PH^-ak}xi@wo}ONxB_$Q+88z z({R&v({K(o|c|Yp8lR8 zo?)Ipc%Jf%^gQEv&hx70b za`W=`^79Jx3iJBGE8OdpSBBReuR*UduX|ooUh`f{UMpT}Ua!60dLz8|dee9#y)oX5 z-Ynkxy*a$OyoJ2wy!E{ez0JIxyhFW@c}IGm_P*$S)jP#I%{$vW*E`?4(7V>V-Fw=5 z&U?{&#rwYZBkv!*fAW6m{j>KkKGZ&RJ}4iI56)+w50ejz51S8%50?**kBE=BkCcy$ zkFt-pkFJltkD-sT59rG5Gv7(8TFa)ne$onS@v1; zdEoQh=aVm;FUptEm)BR$SHV}?*TC1<*TmP%_o%OxuZ^#_ub=Np-$>unzUO@7d=q>x z`d;?U^}XfW;oI%o=R4>-;ydm;={w^)@4Mu?>if?3qwi-w3cu}skl!x9y?)ewbbcs5 zj32Wfn;(ZCm!FWIw4bb>f}fHf(NE1!!_UOe%kxZkATwBL%~bAL*I$e+fa5mX+C_$&CU_^bMB`0M)X`y2U_{Ov&NLTAwO zIn@7{{|WyH|0w^n{?Y!){(1g|{x|$f{mcBz{hR&A{O|cs`_K6=`mgxk_kZO7qyJC- zF9UW4>pb4N0KnLIg_60BoumrFLum|u52nC1+hzH0As0C;S=mh8o7y&JB(*U~w z=K!|=&j7!Gz<`i|uz;k1`hd=WzJS4i;ed&NseqY)xqu%7-UU(wQU>k_+#N_4hz`UB z(g!jGas-M55(Cu&^#d&ey#jp#{Q^%0Mh2b^JQo-jm=Jg|@N!^o;H|*&z>2`Cz&n9$ zfp-Ht0!IVK0>=aI1+E4@2z(s)H1K)g%OHxNT|vkoOb{-JK8P!bH;6AtFi0dwEJ!Zs zV31M}F-SMaBFHYtJ18h9H0XHH$)M9g=YnE_;)5;(l?OczMhCM6a|Ck-9|#r*77CUM zRt(k%HVh^O9}Tt-b_os)4hcRMd?FYQjtD*7KYvotq;8uIv6?`Iukk{x)iz^`ZV-; z=&R5-q3=RJgki#j!?eSG2#W}d4vPu99Cj@%BP=`YdRReNRaj$KbJ(4*_OQON!LX6A z@vwWxD2@poGe2g3%<-7tvCw13kDWYr>e#tsF~{PMB_6wUEcICKvEq{rCvTtZJUM=H z_T!z&5ZQ>vjQsrXY>r|z5@KQ(!3=G6SDrBkb?9-Ml7>glQH5tI>7#IA_F5i}9V2uuV%VqXMP z1WSZyglfc*2=|DS5s48Q5# zqe7!DMP)_hM&(BpMU_O|jOvLRjd~vSD(cO-)pJjyana1t>d`l%YolAE??!h<_eA$c zPee~e&qgmqFGsINzleSv{Wki2^rz@w&+j_F_dLz{w(~>h=gu#lUpasO{G;w_Qp`h2*zl{*v1@>IUkc3QxS7JrZwhnOlM3_On=N|%uLLD%wo(+ z%>9^$u@te~vF5SIV$)*tVhdx7V{gV*#5Tru#}36##4g4@ihUgWH1>TQ6t^pGZyZe= zG7b~R8pj^T8OI%WAWk4oDDF_4b(}+-bDUe8XIyYxcw9u>>9})o*>P2IcjIQ`ev024 zPaRJekBY~})5kN$8^j-pw~cp*caC?9_l)XR3A+>M5>N@O3G4}+2|NiB3DOCK1o?zR3DyZN2^SNtCbT8YB+Ml|PI#5@CgEMe z$An)m?7Fb`0?h^F17tUO0zOa^vNaRU0NwiILOY}_iN%T()N<5i(DlsbY zY~uOE*u<2?w8YHBoW#7ug3An-c`ox^7Q8HSS^To(<%5@%FRNaDcV)+w-B+lt&|X1Z z!CYa!!ght@%H)-$E6=XHy7K1AyDOiQD3U0Xpd{fWQqs{R%Osm5*QB7N^GR_@7m_X| zB_*XKr6px1PYHN>Ps3-8c7;YnoOEWnoD|hmF+6=s{Yl0t1(wI zueM#ifA#0&?a6zSX_Ar2m}Go1TQWy7cQS7>f3je*RI+}uS+aeyPjYDT@#K@qr;?+R zN2DK2*GMO&o2NUahor;lk?CjBqtlbpbJO$Fi_%Nd%hD^- ztJB-k+ta(!d(#Kfhto&X$J2k#*p@++!H^-8p_E~qVUl5+;gaE=;g#W=5t4B%<3t9W z5s`5jDU%|TG84+=%T&*F%RHW$l3A8{H*+ZS zS>}t(*O_lK-)DZxqR67m+L5(8i#>}gi#LluODIb;OFYXl%PT81>v-15tW#N0S!c7N zvy!q>veL3LvuD*IB=0L)p8s_h!>%HM4cH^|C9nyR+Zr?8srvVaeH_!>CSnQOPed1YnB_5o0MCb+mzdq+m_p&JCJ)XcRF`2cQJP* zcP;m2?%Uk=xu33YzYbmBb$!ou!Ry-B4_~*sZhPI~y7P6f>%P|mt_NKYxt?`>^!n@T zpYrzPG3T-7@#ab9Y36C?9nL$NXO(B0=alD?=a%P@cP#I0UTj``USeKSUUFV)URqvp z-krQhc`x&K=hNhKn!JY!D0@?zm0@ecd0FUG1%m}61>*&i1v3Tn1xp311@8(z6nrYADBNBM z749nBTS#3k!mZZO?oy|MoW#|_;ZCvIfi zD7aB|qw7Y`jnNy6H(uTNxp;f=&f?w0RK>_*bTO`&zL=+2qL@%DSA4LTSgcm8S*%@b zR%}yzrntEHW^s9ORdH=`L-Fn6*5bC}j^c&l<>IyChs95dpA|nZepj-iWOoTw32h0g z1Y1I1!cf9f!dD_tB3xonaJ`A{ADs_3S~-V#4^n?oihD0!!pmZgtD}{G@UA#s0arv;oT-SZh_6VjxKvS8u~hM_;zh;linkT- zD?V1zRWeudRSH&$REk$hRT3)YE43_dTs2$usOnkOi>lXE@2Wml{aU@P8eM&$TB=&DTC-ZG zTEE(;+NAn$wRyE`wR^Q!wNJHwbzpT!by)TB>dfk<>V+E08ul9H8mk)T8n+tH8t
*Y_-O< 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 PX7~{l82_3+E2jSs59xTe diff --git a/.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 5a07562..0776a4d 100644 --- a/.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -14,8 +14,8 @@ filePath = "Sources/App/Models/FuelEntry.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "215" - endingLineNumber = "215" + startingLineNumber = "240" + endingLineNumber = "240" landmarkName = "init(id:timestamp:userID:latitude:longitude:product:make:model:serialNumber:tankID:fuelAmount:fuelType:isOverride:tabletserial:)" landmarkType = "7"> @@ -30,8 +30,8 @@ filePath = "Sources/App/Models/FuelEntry.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "195" - endingLineNumber = "195" + startingLineNumber = "220" + endingLineNumber = "220" landmarkName = "init(id:timestamp:userID:latitude:longitude:image:product:make:model:serialNumber:tankID:fuelAmount:fuelType:isOverride:tabletserial:)" landmarkType = "7"> @@ -40,14 +40,14 @@ BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> @@ -148,5 +148,21 @@ landmarkType = "7"> + + + + diff --git a/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift b/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift index 6ee914a..044107d 100644 --- a/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift +++ b/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift @@ -1,7 +1,799 @@ // -// FuelEntryControllerV2.1.swift +// FuelEntryControllerV2_1.swift // Fueling // // Created by Michiel Carman on 9/15/26. // +import Vapor +import FluentKit +import FluentSQL + +struct FuelEntryV2ControllerV2_1: RouteCollection { + func boot(routes: RoutesBuilder) throws { + let fuelEntries = routes.grouped("fuel_entries") + let tokenAuthMiddleware = Token.authenticator() + let guardMiddleware = User.guardMiddleware() + let tokenAuthGroup = fuelEntries.grouped(tokenAuthMiddleware, guardMiddleware) + tokenAuthGroup.get(use: filterLoadDate) + tokenAuthGroup.get("main", use: filterLoadDate) + tokenAuthGroup.get("timestamp", use: filterTimeStamp) + tokenAuthGroup.get("images", use: filterImages) + tokenAuthGroup.get("test", use: filtered) + tokenAuthGroup.get("rpm", use: rpm) + tokenAuthGroup.get("maxim", use: filtered) + tokenAuthGroup.post(use: create) + tokenAuthGroup.put( use: softdelete) + tokenAuthGroup.group(":fuelEntryID") { fuelEntry in + fuelEntry.delete(use: delete) + } + + } + @Sendable + func index(req: Request) async throws -> Response { + return try await FuelEntry.query(on: req.db).all().map{ + try FuelEntry.V2_1_Output.init(from: $0) + }.encodeResponse(for: req) + } + + struct filteredreturn: Content{ + var fuelentries: [FuelEntry] + var pages: Int + } + + struct dload: Content{ + var id: UUID + } + + @Sendable + func softdelete(req: Request) async throws -> Response { + let ids = try req.content.decode([dload].self) + var msg: [String] = [] + for id in ids { + guard let fe = try await FuelEntry.find(id.id, on: req.db) else { + msg.append("create error message for item") + continue + } + fe.downloaded = true + try await fe.save(on: req.db) + } + let response = Response(status: .ok, body: "good") + return response + } + + fileprivate func queryParamExists(_ req: Request, _ param: String) async throws -> Bool { + let x = try req.query.get(Bool.self, at: param) ? true : false + + print ("x: \(x)") + return x + + } + + fileprivate func parseDate2(_ dateString: String, _ start: Bool = true) throws -> Date? { + let currentDate = Calendar.current.startOfDay(for: Date()) + if(dateString.count == 0 && start){ + if(start) { + return Calendar.current.date(byAdding: .day, value: -2, to: currentDate) + } else { + return currentDate + } + } else { + let parts = dateString.split(separator: "-") + if(parts.count != 3) { + return nil + } + let year = Int(parts[0]) ?? nil + if(year == nil) { + return nil + } + let month = Int(parts[1]) ?? nil + if(month == nil) { + return nil + } + let day = Int(parts[2]) ?? nil + if( day == nil ) { + return nil + } + var dateComponents = DateComponents() + dateComponents.year = year + dateComponents.month = month + dateComponents.day = day + if(start) { + dateComponents.hour = 0 + dateComponents.minute = 0 + dateComponents.second = 0 + } else { + dateComponents.hour = 23 + dateComponents.minute = 59 + dateComponents.second = 59 + } + let calendar = Calendar(identifier: .gregorian) + return calendar.date(from: dateComponents) + } + } + + fileprivate func parseDate(_ dateString: String, _ start: Bool = true) throws -> Date? { + + if(dateString.count == 0){ + return nil + } else { + let parts = dateString.split(separator: "-") + if(parts.count != 3) { + throw Abort(.custom(code: 400,reasonPhrase: "Value at path '\(start ? "startdate" : "enddate")' was not of format 'yyyy-MM-dd'. Data found at '\(start ? "startdate" : "enddate")' was not properly formatted.")) + } + let year = Int(parts[0]) ?? nil + if(year == nil) { + throw Abort(.custom(code: 400,reasonPhrase: "Value at path '\(start ? "startdate" : "enddate")' was not of format 'yyyy-MM-dd'. Data found at '\(start ? "startdate" : "enddate")' was not properly formatted. Year component must be in the format yyyy and must be an integer")) + } + let month = Int(parts[1]) ?? nil + if(month == nil) { + throw Abort(.custom(code: 400,reasonPhrase: "Value at path '\(start ? "startdate" : "enddate")' was not of format 'yyyy-MM-dd'. Data found at '\(start ? "startdate" : "enddate")' was not properly formatted. Month component must be in the format MM and must be an integer")) + } + let day = Int(parts[2]) ?? nil + if( day == nil ) { + throw Abort(.custom(code: 400,reasonPhrase: "Value at path '\(start ? "startdate" : "enddate")' was not of format 'yyyy-MM-dd'. Data found at '\(start ? "startdate" : "enddate")' was not properly formatted. Day component must be in the format dd and must be an integer")) + } + var dateComponents = DateComponents() + dateComponents.year = year + dateComponents.month = month + dateComponents.day = day + if(start) { + dateComponents.hour = 0 + dateComponents.minute = 0 + dateComponents.second = 0 + } else { + dateComponents.hour = 23 + dateComponents.minute = 59 + dateComponents.second = 59 + } + let calendar = Calendar(identifier: .gregorian) + return calendar.date(from: dateComponents) + } + } + + fileprivate func generateSQLNoImage(_ startdate: Date? = nil, _ enddate: Date? = nil) -> SQLQueryString { + if(startdate == nil) { + return SQLQueryString("select count(*) as total from \"fuel_entries\";") + } + if(enddate != nil) { + return SQLQueryString("select count(*) as total from \"fuel_entries\" where \"loadDate\" >= \(bind: startdate) and \"loadDate\" <= \(bind: enddate) ;") + } else { + return SQLQueryString("select count(*) as total from \"fuel_entries\" where \"loadDate\" >= \(bind: startdate);") + } + } + + fileprivate func generateSQLImage(_ startdate: Date? = nil, _ enddate: Date? = nil) -> SQLQueryString { + var sql: String = "" + if(startdate == nil) { + return SQLQueryString("select count(*) as total from \"fuel_entries\" where \"image\" is not null;") + } + if(enddate != nil) { + sql = "select count(*) as total from \"fuel_entries\" where \"loadDate\" >= \(String(describing: startdate)) and \"loadDate\" <= \(String(describing: enddate)) and \"image\" is not null;" + return SQLQueryString("select count(*) as total from \"fuel_entries\" where \"loadDate\" >= \(bind: startdate) and \"loadDate\" <= \(bind: enddate) and \"image\" is not null;") + } else { + sql = "select count(*) as total from \"fuel_entries\" where \"image\" is not null and \"loadDate\" >= '\(startdate ?? Date())';" + return SQLQueryString(sql) + } + } + + struct CountRecords: Content { + let total: Int + } + + fileprivate func getCount(_ req: Request, _ sql: SQLQueryString) async throws -> Int { + print(sql) + if let db = req.db as? SQLDatabase { + let answer = try await db + .raw(sql) + .first(decoding: CountRecords.self) + return answer?.total ?? 0 + } else { + return 0 + } + } + + fileprivate func getPaging(_ page: Int, _ per: Int, _ records: Int) async throws -> [String: Any] { + var vals: [String: Any] = [:] + vals["page"] = page + vals["per"] = per + vals["records"] = records + let lastpage = Int(ceil(Double(records) / Double(per))) + vals["lastpage"] = (page == lastpage) ? true : false + vals["offset"] = ( page * per) - per + return vals + } + + fileprivate func getParams(_ req: Request) async throws -> [qparam] { + let urlComponents = URLComponents(string: req.url.description)! + var params: [qparam] = [] + for queryItem in urlComponents.queryItems ?? [] { + params.append(qparam(name: queryItem.name, value: queryItem.value ?? "")) + } + return params + } + + fileprivate func getMaximError(_ req: Request, _ message: String) async throws -> returnObjV2_1 { + let rt = req.route!.description.replacingOccurrences(of: "GET ", with: "").replacingOccurrences(of: "PUT ", with: "").replacingOccurrences(of: "POST ", with: "").replacingOccurrences(of: "DELETE ", with: "") + let method = req.method.rawValue + let baseurl = "https://fueling.prelub.com" + let data : [FuelEntry.V2_1_Output] = [] + let meta = Meta(page: 0, per: 0, total: 0, lastpage: true, error: true, message: message, method: method, baseurl: baseurl, route: rt, querystring: "" ) + return returnObjV2_1(data: data, meta: meta) + + } + + @Sendable + func filterLoadDate(req: Request) async throws -> Response { + let rt = req.route!.description.replacingOccurrences(of: "GET ", with: "").replacingOccurrences(of: "PUT ", with: "").replacingOccurrences(of: "POST ", with: "").replacingOccurrences(of: "DELETE ", with: "") + let method = req.method.rawValue + let baseurl = "https://fueling.prelub.com" + + let qstring = req.query + let components: Dictionary = try req.query.decode(Dictionary.self) + + var startdate : Date? + var qs: [String] = [] + for component in components { + qs.append( "\(component.key)=\(component.value ?? "nil")") + } + if(try qstring.get(String?.self, at: "startdate") != nil) { + startdate = try parseDate2(qstring.get(String?.self, at: "startdate") ?? "") + if(startdate == nil) { + return try await(getMaximError(req, "startdate is invalid format. YYYY-MM-DD")) .encodeResponse(for: req) + } + } else { + //throw error + return try await getMaximError(req, "startdate is required").encodeResponse(for: req) + } + var enddate : Date?//= try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false) + if(try qstring.get(String?.self, at: "enddate") != nil) { + enddate = try parseDate2(qstring.get(String?.self, at: "enddate") ?? "", false) + if(enddate == nil) { + return try await getMaximError(req, "enddate is invalid format. YYYY-MM-DD").encodeResponse(for: req) + } + } + let emptyData: Data? = Data() + let page = try qstring.get(Int?.self, at: "page") ?? 1 + let per = try qstring.get(Int?.self, at: "per") ?? 10 + let qsStr = qs.joined(separator: "&") + + let records = try await getCount(req, generateSQLNoImage(startdate ?? nil, enddate ?? nil) ) + + let paging = try await getPaging(page, per, records) + let meta = Meta(page: page, per: per, total: records, lastpage: (paging["lastpage"] != nil) ? paging["lastpage"] as! Bool : false, error: false, message: "", method: method, baseurl: baseurl, route: rt, querystring: "?\(qsStr)") + let query = try await FuelEntry.query(on: req.db) + .field(\.$id) + .field(\.$timestamp) + .field(\.$userID) + .field(\.$product) + .field(\.$make) + .field(\.$model) + .field(\.$serialNumber) + .field(\.$tankID) + .field(\.$fuelAmount) + .field(\.$fuelType) + .field(\.$isOverride) + .field(\.$latitude) + .field(\.$longitude) + .field(\.$downloaded) + .field(\.$loaddate) + .field(\.$tabletserial) + .group(.and) { + grp in + grp.filter(\.$loaddate >= startdate) + if( enddate != nil) { + grp.filter(\.$loaddate <= enddate!) + } + } + .sort(\.$loaddate, .ascending) + .range(lower: (paging["offset"] as! Int), upper: (paging["offset"] as! Int) + (paging["per"] as! Int)) + .all().map{ + try FuelEntry.V2_1_Output.init(from: $0) + } + let response = returnObjV2_1(data: query, meta: meta) + return try await response.encodeResponse(for: req) + } + + @Sendable + func filterImages(req: Request) async throws -> Response { + let rt = req.route!.description.replacingOccurrences(of: "GET ", with: "").replacingOccurrences(of: "PUT ", with: "").replacingOccurrences(of: "POST ", with: "").replacingOccurrences(of: "DELETE ", with: "") + let method = req.method.rawValue + let baseurl = "https://fueling.prelub.com" + let qstring = req.query + let components: Dictionary = try req.query.decode(Dictionary.self) + var startdate : Date? + var qs: [String] = [] + for component in components { + qs.append( "\(component.key)=\(component.value ?? "nil")") + } + if(try qstring.get(String?.self, at: "startdate") != nil) { + startdate = try parseDate2(qstring.get(String?.self, at: "startdate") ?? "") + if(startdate == nil) { + return try await(getMaximError(req, "startdate is invalid format. YYYY-MM-DD")) .encodeResponse(for: req) + } + } else { + //throw error + return try await getMaximError(req, "startdate is required").encodeResponse(for: req) + } + var enddate : Date?//= try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false) + if(try qstring.get(String?.self, at: "enddate") != nil) { + enddate = try parseDate2(qstring.get(String?.self, at: "enddate") ?? "", false) + if(enddate == nil) { + return try await getMaximError(req, "enddate is invalid format. YYYY-MM-DD").encodeResponse(for: req) + } + } + let page = try qstring.get(Int?.self, at: "page") ?? 1 + let per = try qstring.get(Int?.self, at: "per") ?? 5 + var records : Int = 0 + if(enddate == nil) { + records = try await FuelEntry.query(on: req.db) + .field(\.$id) + .group(.and) { + grp in + grp.filter(\.$loaddate >= startdate) + .filter(\.$image != nil) + } + .all() + .count + } else { + records = try await FuelEntry.query(on: req.db) + .field(\.$id) + .group(.and) { + grp in + grp.filter(\.$loaddate >= startdate) + .filter(\.$image != nil) + .filter(\.$loaddate <= enddate) + } + .all() + .count + } + let qsStr = qs.joined(separator: "&") + + let paging = try await getPaging(page, per, records) + let meta = Meta(page: page, per: per, total: records, lastpage: (paging["lastpage"] != nil) ? paging["lastpage"] as! Bool : false, error: false, message: "", method: method, baseurl: baseurl, route: rt, querystring: "?\(qsStr)") + let query = try await FuelEntry.query(on: req.db) + .field(\.$id) + .field(\.$timestamp) + .field(\.$userID) + .field(\.$product) + .field(\.$make) + .field(\.$model) + .field(\.$serialNumber) + .field(\.$tankID) + .field(\.$fuelAmount) + .field(\.$fuelType) + .field(\.$isOverride) + .field(\.$latitude) + .field(\.$longitude) + .field(\.$downloaded) + .field(\.$loaddate) + .field(\.$image) + .field(\.$tabletserial) + .group(.and) { + grp in if(enddate != nil) { + grp.filter(\.$loaddate >= startdate) + .filter(\.$image != nil) + .filter(\.$loaddate <= enddate!) + } else { + grp.filter(\.$loaddate >= startdate) + .filter(\.$image != nil) + } + } + .sort(\.$loaddate, .ascending) + .range(lower: (paging["offset"] as! Int), upper: (paging["offset"] as! Int) + (paging["per"] as! Int)) + .all().map{ + try FuelEntry.V2_1_Output.init(from: $0) + } + let response = returnObjV2_1(data: query, meta: meta) + return try await response.encodeResponse(for: req) + } + + @Sendable + func filterTimeStamp(req: Request) async throws -> Response { + let rt = req.route!.description.replacingOccurrences(of: "GET ", with: "").replacingOccurrences(of: "PUT ", with: "").replacingOccurrences(of: "POST ", with: "").replacingOccurrences(of: "DELETE ", with: "") + let method = req.method.rawValue + let baseurl = "https://fueling.prelub.com" + let qstring = req.query + let components: Dictionary = try req.query.decode(Dictionary.self) + var startdate : Date? + var qs: [String] = [] + for component in components { + qs.append( "\(component.key)=\(component.value ?? "nil")") + } + if(try qstring.get(String?.self, at: "startdate") != nil) { + startdate = try parseDate2(qstring.get(String?.self, at: "startdate") ?? "") + if(startdate == nil) { + return try await(getMaximError(req, "startdate is invalid format. YYYY-MM-DD")) .encodeResponse(for: req) + } + } else { + //throw error + return try await getMaximError(req, "startdate is required").encodeResponse(for: req) + } + var enddate : Date?//= try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false) + if(try qstring.get(String?.self, at: "enddate") != nil) { + enddate = try parseDate2(qstring.get(String?.self, at: "enddate") ?? "", false) + if(enddate == nil) { + return try await getMaximError(req, "enddate is invalid format. YYYY-MM-DD").encodeResponse(for: req) + } + } + let emptyData: Data? = Data() + let page = try qstring.get(Int?.self, at: "page") ?? 1 + let per = try qstring.get(Int?.self, at: "per") ?? 10 + let records = try await getCount(req, generateSQLNoImage(startdate ?? nil, enddate ?? nil) ) + let paging = try await getPaging(page, per, records) + let meta = Meta(page: page, per: per, total: records, lastpage: (paging["lastpage"] != nil) ? paging["lastpage"] as! Bool : false, error: false, message: "", method: method, baseurl: baseurl, route: rt, querystring: "?\(qs.joined(separator: "&"))") + let query = try await FuelEntry.query(on: req.db) + .field(\.$id) + .field(\.$timestamp) + .field(\.$userID) + .field(\.$product) + .field(\.$make) + .field(\.$model) + .field(\.$serialNumber) + .field(\.$tankID) + .field(\.$fuelAmount) + .field(\.$fuelType) + .field(\.$isOverride) + .field(\.$latitude) + .field(\.$longitude) + .field(\.$downloaded) + .field(\.$loaddate) + .field(\.$tabletserial) + .group(.and) { + grp in + grp.filter(\.$timestamp >= startdate ?? Calendar.current.startOfDay(for: Calendar.current.date(byAdding: .day, value: -2, to: Date())!)) + if( enddate != nil) { + grp.filter(\.$timestamp <= enddate!) + } + } + .sort(\.$loaddate, .ascending) + .range(lower: (paging["offset"] as! Int), upper: (paging["offset"] as! Int) + (paging["per"] as! Int)) + .all().map{ + try FuelEntry.V2_1_Output.init(from: $0) + } + let response = returnObjV2_1(data: query, meta: meta) + return try await response.encodeResponse(for: req) + } + + @Sendable + func filtered(req: Request) async throws -> Response { + let qstring = req.query + let startdate = try parseDate(qstring.get(String?.self, at: "startdate") ?? "") + let enddate = try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false) + let image = try qstring.get(Bool?.self, at: "image") ?? false + let serial = try qstring.get(String?.self, at: "serial") ?? nil + let tank = try qstring.get(String?.self, at: "tank") ?? nil + let previous = try qstring.get(Bool?.self, at: "previous") ?? false + + + + var query = FuelEntry.query(on: req.db) + if(image){ + query = query + .field(\.$id) + .field(\.$timestamp) + .field(\.$userID) + .field(\.$product) + .field(\.$make) + .field(\.$model) + .field(\.$serialNumber) + .field(\.$tankID) + .field(\.$fuelAmount) + .field(\.$fuelType) + .field(\.$isOverride) + .field(\.$latitude) + .field(\.$longitude) + .field(\.$image) + .field(\.$downloaded) + .field(\.$loaddate) + .field(\.$tabletserial) + //.filter(\.$image != nil) + + } else { + query = query + .field(\.$id) + .field(\.$timestamp) + .field(\.$userID) + .field(\.$product) + .field(\.$make) + .field(\.$model) + .field(\.$serialNumber) + .field(\.$tankID) + .field(\.$fuelAmount) + .field(\.$fuelType) + .field(\.$isOverride) + .field(\.$latitude) + .field(\.$longitude) + .field(\.$downloaded) + .field(\.$tabletserial) + .field(\.$loaddate) + } + + query = query.group(.and){ + grp in + if( startdate != nil) { + grp.filter(\.$loaddate >= startdate) + } + if( enddate != nil) { + grp.filter(\.$loaddate <= enddate!) + } + if( serial != nil) { + grp.filter(\.$serialNumber == serial!) + } + if( tank != nil) { + grp.filter(\.$tankID == tank!) + } + if(!previous){ + grp.filter(\.$pulldate == nil) + } + + } + query = query.sort(\.$loaddate, .ascending) + + if(image){ + //query = query.filter(\.$image) + let wimage = try await query.paginate(for: req).map{ + img in FuelEntry.wimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, isOverride: img.isOverride, image: img.image == nil ? Data(): img.image, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: previous == true ? img.pulldate : Date()) + } + if(!previous){ + wimage.items.forEach{ + _ = $0.toFuelEntry().save(on: req.db) + } + } + + return try await wimage.encodeResponse(for: req) + } else { + let woimage = try await query.paginate(for: req).map{ + img in FuelEntry.woimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, image: nil, isOverride: img.isOverride, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: previous == true ? img.pulldate : Date(), useremail: img.tabletserial) + } + if(!previous) { + woimage.items.forEach { + _ = $0.toFuelEntry().save(on: req.db) + } + } + + return try await woimage.encodeResponse(for: req) + } + } + + @Sendable + func rpm(req: Request) async throws -> Response { + let qstring = req.query + let startdate = try parseDate(qstring.get(String?.self, at: "startdate") ?? "") + let enddate = try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false) + let image = try qstring.get(Bool?.self, at: "image") ?? false + let serial = try qstring.get(String?.self, at: "serial") ?? nil + let tank = try qstring.get(String?.self, at: "tank") ?? nil + let previous = try qstring.get(Bool?.self, at: "previous") ?? false + + + var query = FuelEntry.query(on: req.db) + if(image){ + query = query + .field(\.$id) + .field(\.$timestamp) + .field(\.$userID) + .field(\.$product) + .field(\.$make) + .field(\.$model) + .field(\.$serialNumber) + .field(\.$tankID) + .field(\.$fuelAmount) + .field(\.$fuelType) + .field(\.$isOverride) + .field(\.$latitude) + .field(\.$longitude) + .field(\.$image) + .field(\.$downloaded) + .field(\.$loaddate) + + + } else { + query = query + .field(\.$id) + .field(\.$timestamp) + .field(\.$userID) + .field(\.$product) + .field(\.$make) + .field(\.$model) + .field(\.$serialNumber) + .field(\.$tankID) + .field(\.$fuelAmount) + .field(\.$fuelType) + .field(\.$isOverride) + .field(\.$latitude) + .field(\.$longitude) + .field(\.$downloaded) + .field(\.$loaddate) + } + + query = query.group(.and){ + grp in + if( startdate != nil) { + grp.filter(\.$loaddate >= startdate!) + } + if( enddate != nil) { + grp.filter(\.$loaddate <= enddate!) + } + if( serial != nil) { + grp.filter(\.$serialNumber == serial!) + } + if( tank != nil) { + grp.filter(\.$tankID == tank!) + } + if(!previous){ + grp.filter(\.$pulldate == nil) + } + + } + query = query.sort(\.$loaddate, .ascending).sort(\.$timestamp).sort(\.$serialNumber) + + if(image){ + //query = query.filter(\.$image) + let wimage = try await query.paginate(for: req).map{ + img in FuelEntry.wimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, isOverride: img.isOverride, image: img.image == nil ? Data(): img.image, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: Date()) + } + return try await wimage.encodeResponse(for: req) + } else { + let woimage = try await query.paginate(for: req).map{ + img in FuelEntry.woimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, image: nil, isOverride: img.isOverride, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: Date(), useremail: img.tabletserial) + } + return try await woimage.encodeResponse(for: req) + } + } + + + + func create(req: Request) async throws -> [FuelEntry] { + let fuelEntry = try req.content.decode([FuelEntry].self) + for entry in fuelEntry { + let data = entry.image ?? nil + let length = data?.count + if(data == nil || length == 0){ + entry.image = nil + } + if(entry.id != nil){ + let dbentry = try await FuelEntry.find(entry.id!, on: req.db) + if(dbentry != nil){ + entry._$idExists = true + } + + } + entry.save(on: req.db).whenFailure({ error in + + }) + } + +// try await fuelEntry.create(on: req.db) + return fuelEntry + } + + + + func delete(req: Request) async throws -> HTTPStatus { + guard let fuelEntry = try await FuelEntry.find(req.parameters.get("fuelEntryID"), on: req.db) else { + throw Abort(.notFound) + } + try await fuelEntry.delete(on: req.db) + return .noContent + } + + @Sendable + func getrpm(req: Request) async throws -> Response { + let qstring = req.query + let startdate = try parseDate(qstring.get(String?.self, at: "startdate") ?? "") + let enddate = try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false) + let image = try qstring.get(Bool?.self, at: "image") ?? false + let serial = try qstring.get(String?.self, at: "serial") ?? nil + let tank = try qstring.get(String?.self, at: "tank") ?? nil + let previous = try qstring.get(Bool?.self, at: "previous") ?? false + + + var query = FuelEntry.query(on: req.db) + if(image){ + query = query + .field(\.$id) + .field(\.$timestamp) + .field(\.$userID) + .field(\.$product) + .field(\.$make) + .field(\.$model) + .field(\.$serialNumber) + .field(\.$tankID) + .field(\.$fuelAmount) + .field(\.$fuelType) + .field(\.$isOverride) + .field(\.$latitude) + .field(\.$longitude) + .field(\.$image) + .field(\.$downloaded) + .field(\.$loaddate) + + + } else { + query = query + .field(\.$id) + .field(\.$timestamp) + .field(\.$userID) + .field(\.$product) + .field(\.$make) + .field(\.$model) + .field(\.$serialNumber) + .field(\.$tankID) + .field(\.$fuelAmount) + .field(\.$fuelType) + .field(\.$isOverride) + .field(\.$latitude) + .field(\.$longitude) + .field(\.$downloaded) + .field(\.$loaddate) + } + + query = query.group(.and){ + grp in + if( startdate != nil) { + grp.filter(\.$loaddate >= startdate!) + } + if( enddate != nil) { + grp.filter(\.$loaddate <= enddate!) + } + if( serial != nil) { + grp.filter(\.$serialNumber == serial!) + } + if( tank != nil) { + grp.filter(\.$tankID == tank!) + } + if(!previous){ + grp.filter(\.$pulldate == nil) + } + + } + query = query.sort(\.$loaddate, .ascending).sort(\.$timestamp).sort(\.$serialNumber) + + + + if(image){ + //query = query.filter(\.$image) + let wimage = try await query.paginate(for: req).map{ + img in FuelEntry.wimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, isOverride: img.isOverride, image: img.image == nil ? Data(): img.image, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: Date()) + + } + return try await wimage.encodeResponse(for: req) + } else { + let woimage = try await query.paginate(for: req).map{ + img in FuelEntry.woimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, image: nil, isOverride: img.isOverride, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: Date(), useremail: img.tabletserial) + } + return try await woimage.encodeResponse(for: req) + } + } +} + +//struct custMeta: Content { +// let per: Int +// let page: Int +// let total: Int +// let status: String? +//} +// +//struct PageRequest: Content { +// var page: Int +// var size: Int +// var status: String? +// // Add any other properties needed for pagination +// +// init(page: Int, size: Int, status: String? = nil) { +// self.page = page +// self.size = size +// self.status = status +// } +//} + + + + + + + + + + diff --git a/Sources/App/DTO/V2.1/FuelEntryDTO.swift b/Sources/App/DTO/V2.1/FuelEntryDTO.swift index e69de29..1247463 100644 --- a/Sources/App/DTO/V2.1/FuelEntryDTO.swift +++ b/Sources/App/DTO/V2.1/FuelEntryDTO.swift @@ -0,0 +1,67 @@ +// +// FuelEntryDTO.swift +// Fueling +// +// Created by Michiel Carman on 9/15/26. +// + +import Vapor + +extension FuelEntry { + struct V2_1_Input: Content { + var id: UUID? + var tabletSerial: String? + var fuelAmount: Double? + var fuelType: String? + var timeStamp: Date? + var appVersion: String? + var longitude: Double? + var product: String? + var latitude: Double? + var serialNumber: String? + var isOverride: Bool? + var make: String? + var model: String? + var tankId: String? + var image: Data? + } + + struct V2_1_Output: Content { + var id: UUID? + var tabletSerial: String? + var fuelAmount: Double? + var fuelType: String? + var timeStamp: Date? + var appVersion: String? + var longitude: Double? + var product: String? + var latitude: Double? + var serialNumber: String? + var isOverride: Bool? + var make: String? + var model: String? + var tankId: String? + var image: Data? + init(from decoder: FuelEntry, img: Bool = false) throws { + self.id = decoder.id + self.tabletSerial = decoder.tabletserial + self.fuelAmount = decoder.fuelAmount + self.fuelType = decoder.fuelType + self.timeStamp = decoder.timestamp + self.appVersion = decoder.appVersion + self.longitude = decoder.longitude + self.product = decoder.product + self.latitude = decoder.latitude + self.serialNumber = decoder.serialNumber + self.isOverride = decoder.isOverride + self.make = decoder.make + self.model = decoder.model + self.tankId = decoder.tankID + if img { + self.image = decoder.image + } + } + } + + +} diff --git a/Sources/App/Helpers/PageHelper.swift b/Sources/App/Helpers/PageHelper.swift index 2c18690..4982050 100644 --- a/Sources/App/Helpers/PageHelper.swift +++ b/Sources/App/Helpers/PageHelper.swift @@ -24,6 +24,11 @@ final class PageHelper { } +public struct returnObjV2_1: Content{ + let data: [FuelEntry.V2_1_Output] + let meta: Meta +} + public struct returnObj: Content{ let data: [FuelEntry.Output] let meta: Meta diff --git a/Sources/App/Migrations/CreateFuelEntry.swift b/Sources/App/Migrations/CreateFuelEntry.swift index 7c178f5..a579dfa 100755 --- a/Sources/App/Migrations/CreateFuelEntry.swift +++ b/Sources/App/Migrations/CreateFuelEntry.swift @@ -96,6 +96,20 @@ struct AddDownloadedBool: AsyncMigration { } } +struct AddAppVersion: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema("fuel_entries") + .field("app_version", .string) + .update() + } + + func revert(on database: Database) async throws { + try await database.schema("fuel_entries") + .deleteField("app_version") + .update() + } +} + struct AddLoadDateAndPullDate: AsyncMigration { func prepare(on database: Database) async throws { try await database.schema("fuel_entries") diff --git a/Sources/App/Models/FuelEntry.swift b/Sources/App/Models/FuelEntry.swift index fdf7941..eda0fd5 100755 --- a/Sources/App/Models/FuelEntry.swift +++ b/Sources/App/Models/FuelEntry.swift @@ -164,12 +164,37 @@ final class FuelEntry: Model, Content { @Field(key: "tablet_serial") var tabletserial: String? + @Field(key: "app_version") + var appVersion: String? + @Timestamp(key: "loadDate", on: .create) var loaddate: Date? @Timestamp(key: "pullDate", on: .none) var pulldate: Date? // var imageData: Data? init() {} + + init(id: UUID? = nil, timestamp: Date, userID: String, latitude: Double, longitude: Double, image: Data?, product: String, make: String, model: String, serialNumber: String, tankID: String, fuelAmount: Double, fuelType: String, isOverride: Bool, downloaded: Bool? = nil, tabletserial: String? = nil, appversion: String? = nil) { //imageData: Data? + self.id = id + self.timestamp = timestamp + self.userID = userID + self.latitude = latitude + self.longitude = longitude + self.image = image ?? nil + self.product = product + self.make = make + self.model = model + self.serialNumber = serialNumber + self.tankID = tankID + self.fuelAmount = fuelAmount + self.fuelType = fuelType + self.isOverride = isOverride + self.downloaded = downloaded ?? nil + self.image = image ?? nil + self.tabletserial = tabletserial ?? nil + self.appVersion = appversion ?? nil +// self.imageData = imageData + } init(id: UUID? = nil, timestamp: Date, userID: String, latitude: Double, longitude: Double, image: Data?, product: String, make: String, model: String, serialNumber: String, tankID: String, fuelAmount: Double, fuelType: String, isOverride: Bool, downloaded: Bool? = nil, tabletserial: String? = nil) { //imageData: Data? self.id = id @@ -229,4 +254,40 @@ final class FuelEntry: Model, Content { self.tabletserial = tabletserial ?? nil // self.imageData = imageData } + +// struct V2_1_Input: Content { +// var id: UUID? +// var tabletSerial: Date? +// var fuelAmount: Double? +// var fuelType: String? +// var timeStamp: Date? +// var appVersion: String? +// var longitude: String? +// var product: String? +// var latitude: String? +// var serialNumber: String? +// var isOverride: Bool? +// var make: String? +// var model: String? +// var tankId: String? +// var image: Data? +// } + + init(from decoder: FuelEntry.V2_1_Input) throws { + self.id = decoder.id + self.timestamp = decoder.timeStamp ?? Date() + self.fuelAmount = decoder.fuelAmount ?? 0 + self.fuelType = decoder.fuelType ?? "" + self.appVersion = decoder.appVersion ?? "" + self.longitude = decoder.longitude ?? 0 + self.product = decoder.product ?? "" + self.latitude = decoder.latitude ?? 0 + self.serialNumber = decoder.serialNumber ?? "" + self.isOverride = decoder.isOverride ?? false + self.make = decoder.make ?? "" + self.model = decoder.model ?? "" + self.tankID = decoder.tankId ?? "" + self.image = decoder.image + + } } diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index a07321d..92901ff 100755 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -51,6 +51,7 @@ public func configure(_ app: Application) async throws { app.migrations.add(UpdateUser3_RemoveTokenfield()) app.migrations.add(AddLoadDateAndPullDate()) app.migrations.add(AddDeviceSerial()) + app.migrations.add(AddAppVersion()) try await app.autoMigrate().get() // register routes diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index 6579183..7d42f12 100755 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -4,6 +4,8 @@ func routes(_ app: Application) throws { app.routes.caseInsensitive = true app.routes.defaultMaxBodySize = "64mb"; + let V2_1 = app.grouped("api").grouped("v2.1") + try V2_1.register(collection: FuelEntryV2ControllerV2_1()) let v2 = app.grouped("api").grouped("v2") try v2.register(collection: FuelEntryV2Controller()) try v2.register(collection: UserController())