From 957e6a5162edd4eb46ca2ef9cb06e6f613f0c060 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 10:48:51 +0100 Subject: [PATCH 01/83] Added schema graph generator --- .gitignore | 1 + cpanfile | 5 +++++ script/schema/graph.pl | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100755 script/schema/graph.pl diff --git a/.gitignore b/.gitignore index 41ff8f3..b7fbab8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ hypnotoad.pid *.swp cover_db/ +schema.png diff --git a/cpanfile b/cpanfile index 6e21422..e38cdc8 100644 --- a/cpanfile +++ b/cpanfile @@ -21,3 +21,8 @@ requires 'MooX::Options::Actions'; requires 'Module::Runtime'; requires 'DBIx::Class::DeploymentHandler'; requires 'DBIx::Class::Fixtures'; + +on 'schema-graph' => sub { + requires 'GraphViz'; + requires 'SQL::Translator'; +}; diff --git a/script/schema/graph.pl b/script/schema/graph.pl new file mode 100755 index 0000000..f51921c --- /dev/null +++ b/script/schema/graph.pl @@ -0,0 +1,36 @@ +#! /usr/bin/env perl +use strict; +use warnings; + +use feature "say"; + +use FindBin qw/ $Bin /; +use lib "$Bin/../../lib"; + +use SQL::Translator; +use Pear::LocalLoop::Schema; + +say "Setting up Translator and Schema"; + +my $schema = Pear::LocalLoop::Schema->connect; +my $tr = SQL::Translator->new( + from => "SQL::Translator::Parser::DBIx::Class", + to => 'GraphViz', + debug => 1, + trace => 1, + producer_args => { + out_file => "$Bin/../../schema.png", + output_type => 'png', + width => 0, + height => 0, + show_constraints => 1, + show_datatypes => 1, + show_sizes => 1, + }, +); + +say "Translating Schema to image"; + +$tr->translate( data => $schema ); + +say "Finished"; From 88b98d956171ff6c0a3f05cac09aab1066bb0f86 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 10:50:09 +0100 Subject: [PATCH 02/83] Added image of schema version 5 --- share/schema_graphs/schema-v005.png | Bin 0 -> 157057 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 share/schema_graphs/schema-v005.png diff --git a/share/schema_graphs/schema-v005.png b/share/schema_graphs/schema-v005.png new file mode 100644 index 0000000000000000000000000000000000000000..585d9f85e2a1e1ae62d723db94bf9b7e525db699 GIT binary patch literal 157057 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%0ApAO6M(gjCwV`jQC66n*x^rJ5seiVYedP} zJ7@`kU*9eXCTWs}}ee)94kyZ>Ec*ySa9J0*|9+p)9X zNpH_O8FcN?r+HVo;|c&Lv6O?02?8bj75HKL+n1Tk+g|ED357pI8_E78Y3p z_|-e4xC1(!+P_~@ORE+?QtZq{l6)$_HLfPIuV_=u!=CexQ~W~#1OLAN^l29X1Ifuy zpvfSUPUlD@xQ}GFsEDKc1JPNWf&2xk)r|iei)^3Xm6{m4V8*D~D7axUqQ?gPSA4S2 zzlM_uo}Qs5M{f=mxBNv|(g2&2B zz5Wt^h-}m%3qB&MdgzhxS)Ch`6kTZgk2cv~}dsAcKuumiG z=>1QeJgKnEq7WG$I*SK)ybz+Ie8}xxS*vevxE_%Z7;Vw%*SsR+iY%!J_2h|IV+r3% z?9l%Uzis1W8~^&1epH0BVP~+Wm)pXZs5U+vDeI>r^UI3MC-S9e(}<5B&R~hMC4W_R zV7K!A%eR$)9Pv=7fH;juZt{zBc+2@|_pA0Ea5CoC!L+c3ldgu@M(D{%j1m~8BUXdU zyzK#AYSUGUR*dI};#+E4`e_snlENLDYLARM!kJIqt5a749?hHRz?Y2vEbk>B$9A!0<3rO>?pfl^1ZWQaW{Nm zjO0~(cndST-t(~J$8JwqQ(2)qO^5VGdWjoP$K?B%)9Nz^49yW?wbp*l-{!_u)U(`4 z-~cNmfs7N5a+-M>@O)ANy(p4A;-40Wghb5)QbSMl`O_z?CbW`@$E5^-r?@yoNx>l@ zVodqU=m5?CR_J+>&>g!bY0sY4!o;i<*2~M`BmoT|O}lBiAXU=Kr-;ojd<{y-JKWws zuoHa9vH~9Vlh)WH&riS61~tOIS_jEB{wAF%YrQmIjH2u8PmY?tReA|a{7G7;`m;*=(?jE1-A|r?a zSg<03@SD_{H*1z?ujU#e3Y^bSFICN<1kwc?BvvtN*UAcvLCNuMwPYiZD-VmMePvZk zGyWwmC4@KT0msbEx4Ww3fsBcx$Z5w$ch#zUoxx1G9CKIFD4|}dIbjaGkSwe^)m!{l z5?WcH66)_bfS@^#jpQ~AJ3t{sWwObBrNr5S>_?g&u0%T1eTy7`xn=zdd+RSP=%%Ig zv5H2V)ISK>yz%C!ufd`#(+uLBMS1qiudD$+de-a*-JxpvW zs8AC*G(tE{q-H@dG>{O^yiGwLa?E=pz&sRD_TwV7?cBN`z`2JHt@_n)o_m(qZunde zJW#KTH0!SAQK6gX_ZPCCJi*AUA zWFv?Q=Ns%slfjA*u@?STe(@o*uw9c{VlGQJCimB=Y-^I#vTABgLe-5INfk-XhETfM zwN=Q1v^ZH32T%Hq=JC>X3vdSJJet|pyec!?#NF0l;fyrUmgowr-EO?6m1MwQzD&p( zPm?ep;nH6ueBFKWWGRb+3Zw3V$hqsx0;DEUfrRaN5tU>JS-acE#Ax+Cea{nqjCv6EXe_5>f3_~*5F&uFT;B}-(I6T`u12{QI-SiE-k@POoz7g(AZ z+U+fJTPZ6~CLxQ_+!&QA9vg8lxI%8&+>UK0-k_fot| z2|6)o#+jWi)+&IjTxjN#cvdUzgWj5}qxFKQ%+&v+ESt8h z{W`bn{Opm@R4~qYyYM~p2*fsRAeh6%bV9$9$l?`X)eB8;L7Feu& zLO16cSq7A6)bG)+?EbP3I?1ZOd)o>2?(W5W?%R9pT6eUkvOu|jyNXrECqQ~aO$<8q z|Dt3v0@2}pOiX6bvz_oFJ&gs7Yq9W!v!kP7*RDtQ?UNQ#(F{<+EwIzOi0lu&0@ytz z7$^n0?8zBH1|L6RF{4J_=K7XOZqlT?QUa^f+iQ5PqxMSDy{Oa*U9W}0~V1*gT zaf6pE0S$x4$edk>|Jg;SXs3kkfd#D6!piH!{7#@2;11G%AZ9s#P`5DHb1Ii%od?_# zz$SwhKlQg1eEwC~Nw4tGkJ00z;;JUoO6OcB@$LnIQ}tY86vfKJH!nFEJuwKv61+s? zq}*2kHf*ZJ&z>!33W4sO6|#;x;(IvcU}R~j5%y`Qenab~tj-$4K6QHX8_4=t{(3)t zKh!8JN;%)q~ceHao=uN3kAWb#{lm@DRP<)#QEMSyQyF3?`WbadGT$FvtiFk zXQJ%ab@JOb8b2Spi4Dtx?CZwIw_=?TeSt2ZI?2@pK8NBzsOsGZnV`nag}!pX9Q2)U zaE*X)Tw9u6$aS0Z=*j|99w*h3s)`DB2td^HFvY9SIZ5(3T`=M%SdKT4#Q+gX_UXlz zTIm|$3!Ov{51Q)gYhFr(oMMl^b<7v#^$p&4VMrODt5Ll z4$ix@$fc#p#Kp$O{%|n$tIYMcll8NQ-5KfP(j8sXw{Nhq`AJgSU4`wg z+yX*&vdTbYp!@xY!PNX0q>BN#8YkO2nhO7-Vvl^;0bnc;CD10Z0>+IfmZwh<)#(<% zE65lPZ#AF%FVI)|Bn3fHMdbvv@~BY?0`E9`hv`qN#NOL)im!~e(TRaFsHqVjms^D) z)V16`g}}~=P*+zKA^X;?zq~h63lkTl<7W1s5ZCi%XMWu7PNW(zAOd}MJ;%J3(i_^L z6H5GJfI-z&=xN{_?N;6HIcda*IaanA`MP{4R%_0C<)=qEz$5Q<^xwuxPIF`>n;Q)p zl*d>@hf` zEIrAaQ;4KD*Lt8LSxi3qb}TO%QSwZ&$^T8&^I^IukM8ECxXK6-tM zZQ^vSiW>zxDsz!wG|P-DPdG9|;%{lN@#s(pGK1u};@i5n8;?m8zxqDBT7F8HQ10k+ ze*jy`qtzXsMbA~O)wq9t&vq)C4_?Q;Z`DbG>#Xc!LDza;rTLlg=o);^F8FGa6;j61tqgPT?lsxy>ALix;tzvdrN{jR&LaVj4``X(Y<|ly3 zGWTgN{vw!R8g5pd$1`KSAYDM#T8CmYk2rNap;$E_{V$BL>$-_`WHaMEEjr=TKit}d2Avn z;SZXFew5pg3!Oi0K70#wVvlo!R@;|3 z7(wt@+^_Cz(C+Gg7h!_%513R~S~`fxe7G=kHCckj~Jkio||Gp@6G&FRqPn0-qqFh=<;|9s-+{1FN!HQSM zPoKs@OU%gYVHa2Hq(Q52{s7Ti=%`qw;1=PY6i>BJ_9mJG3;o+`tlgDDbD57pg5Ap{-QcH`K{CYybA`lPH;%Fxq=yw4=ItpD@*n zKdZ}~UVi_mCxjz~OI2eEHwn7;@B5#Q^xgI+EG$TO!uauWLh<{M(1arYW&mE?+myb# z8)&))9cZaqDHAt#R|PyKgmvf$urc11Ew(MBe^kC4bR>?7s{$w)Y~|UD7d`j-@-dnk z!FE`&;Az#x=%N0I%itLwJ!*U57jQ1o8)(ipY`DuogEH4g%to<3Aw}@Gp5C3a!(q&P zeDqJe#k@jHTG%w%xekrw+<|Tld3^oZv&*O;GT&0dhy%!dbEB9yoVJxj#9x=IvSb8+ zLy|Pot`Bu99u(|T{7U!Ej=mp3EIUOdTIqcFwzpc*?(26|-$M*ivF0zUFUSejG8?N; z^G!Mws>;{qV$6}8ri?+1l;L)Vq@sK;&F!vDk_X>B#w{3fe{%Pb&1Xu-kcCZZr3Ja2 zh<_IJAu3x=*k!wX%bwTeKei;)jQ`$#z0C#g{KM$|HC8qVW!jgM_d{({o*=|;dg%o4 z38DDp(xM&(o8;SB-w9cqe|?>i2baDzXI=AOz7vU+0*`~WemN@5X4NS}U;pOtc)y?~ zD;6rzaul92FVy?zRVA9pmLI;MzNp2}PR$M|s~?Ke(ffx;^};{6Iy)wc){aO8h7hydYQ3$dj4)lW?7USw z3u6)7Lu3iN2UOCKo3yQ$y5BWED7XP~%OBmye!Adckgv&wkuUpo&*Jhk?YG_gYcNpp zp+j$ZhGcP3KMYQq?W9xE7))YT-qWIoOx7V-J1dQmYx(dOe`IN{CTHu8{%0X0%G&Z$ ziCjaof9&W{YDSJY><whF{wi9#zDV}`1b=51D_R9|zBVRb4sk=CGP|>4s zfp#vA3@y)%rc2~Cl8gCx%j9_z6DvZUougRrWX+MW`@V-^yKZ(mDno*U(>SMwTJLsa z2oALt6H2s`;?yI;v%~kZH%^TFAFA`q)oL^>ZQE|eU&z*K9uYY?AoI$V&@T}~Rc7%U zVq^-Ap>u}$Mz0Ta+iSVR;Ky19lFM#9neiX;I>ls7xoq31HJk3d5YxlB`l&O4L~LT8 z?02y_EjTub1DsLOhW_nRNg>zrz~*>g+x0t zaPA%^V*1Q!9ZB z3idN+R+243*M@vgHpFwn1cj_qWx3`zSvcr}7K@hu47I^QP9_`qq$JW?3HCldJ`_dE zmJJZ>hYnSEC_A@De&{jP^dO>{*1DQJ!Z%{oNmElsU_ea8Bj@_{(q26tS|x4W8n8S1 zV{w_0QQyAfvk!I%>+vv33WLEF0_j*r=~RlOzCL&WF!`A-4RLw=LuG?rDz}f-Q$X+> zTZfq0sHYF~EF7+Rd(iXEKef-?+4gUpol+CubF<CWLTXn=VS>D|26U(Yur|Q4<&1 z3tGmB)tE!M82@G>vIM!Y#@gFU~{Gz<7 zp5vcaR0K^4u48MMb8h6NUTtZN==^Fj=fDQA6n=&GW;owp)m}#b@RW~bN$t49I&a9cnk;K0p=wiVNyW( z^wwCe8DMqpT*!X9xkT6^&Y52lo^^b2eE7!8mRB9?;#)r* z#g{wbhAEwFCY3G&~x;1-x5 z0b!X44}U+b&1LmgXPZZ!eo7$LY=j+b4t6x3m9729vEbAbj<9-B8%DZl4Y6$WeEx6i zLTBj>NF!;4iS%{_h_QTZ-G25EEc)tLx+(kzT@} zY{L6FOdC!0A}IZ0k>GNOl6EOM88@-`pGUL3SC=jdLUz^~)n2bx)K;oXmbqz#`}`!> zY9r~NmC+H3I)?<-R2z#Z_c3EGk${0cZ4j*W!(82S=aujp^|sAwzwT!GxXLHwf00Un z$}+k`h`x%v5zq!=rl1LrsD99?;1oqRR!~}Jx#9g9y?1Y}z8^msxQuoH8==k01{%U2 zykf{D$Rpi>G8QTyBmGQaGNc3+=Pz|b9jV!kjg5Oqi774Z6eFYfukzP<9Ma*Sw&X9q zy}r2zFa#v!2a8iuKzouA#P{&ic|q|W&Zd6 z)^)Pofefv(*uZ1>%NaL2DXh`a*oNVg5N!%|XTzMAqp^{)C4O`kDFaY#tqlHxyrX6 zKMt473>mQd#WKLG_{797>!gbdwy$4bqGw>^8@;sc-hMH@EZ0J;52WVLo-Gb^eg32C z)Ag^eysD@^?lWr^V{r*)^_e@ji8P)pkk8sZQoGZm%8?MQw2IborEpo>rXuy%N~VO>cL$AjdFOO47qdh%$?Ae3sR66U>^N(M?Y}xc_(kPEM zt%Yzh@2I=3T)6@T0|bPKh09bBAQ<1=mMbiktXO1MZl#tvy4o}{2bbs$%jqucc!nhb z%gP?<_`9_3hXw{b4OH$gJv)qUQ)yDi8AaPilWrk5fyy|%{OGP-ySVg-7skB9Am&+p zb|iH)kQkPO`qCe;N;JmbzQLamrMY=|kwyMu405ktUwG&gGIiC}6QR21yq|@Mfia!O zH#1$A8N1)ZJD;0?ZZYPbC(HZ~VjPZC`2$t^Lz58r9U%E;uK}6gq zU-)!*PnfjyJ7=lcXGd*a{U*=MmdHs;U1auX1w|@_)M#0ivEeQ@3Im%8F(REhW#YNU z!4PE6z@VHLOes$X6=S06S3L>%8}g^bGo16Fuy9z(4oyRUq#Q^pFvj?rK|(9GZjG>a zMp)ROt8!{K9TGYODX%3|h55xW_{4EiCnL-PzFK?tF6b-kk79*KVryn*W^61gq@v&j zmxRlF`|cfou}kO9Sd#+)&p2#}a}y^e@W%IE-CHo~zSGwAbz78Pj(Q(5;Lcf+;ZS}M z?WVCj1=&bOnA_fmm25=kgT72#Ick(ded?Ksw;u z8z=jjUR~MPuVB|(nu`tc7jnP49T@#iIDCGbR>4lPN<`iA zl7|};&5=#dHZL$R_Is?iY&Tz_>+P>)cS}p%Qrw*vokY4AIBl9t#W{i$n?PBi*vPvt z&PQHvdV+Z9Exo3;Z`e#N#|!B+3K<_h`=I=3PWJKxT&APOk9Tgs2P%I`IVbK}$DaRF z(GfK8{{1XQ)w3qvzi+3db-$z}qQdIqWZgT~qr;!--s?Q-C;N_qHcA?T69~z-X&QGj z%`fzHa-P-+jXOQ`2gS`URWIE(MBn)u`7RAD_q2;t91q{~J(Ae`(P#m|u4pGSZY=K; zQQ!CVTX5`^$TaAPZ*I#O&{XqA`L~cfQ!U1q%rKlt*c863BZjG(l&_}pSUn7)yl^XH zr#+Vo&ueeADlq!o^E)>RTOjHr0e=h|dOXLA$+ismh9$#`uC@Fp%kXOB7g&q6Yh5Q! z)H%MQKJ#G0@~mU(qSsM;eJ^!l#(Q{pp}3?%{iUUxVHmL{p?pcKV&swJs>)O9okst< z+uZoF1rda*$w*T(v-Gqy-`%S}j+?aPI#dU^aj24dkW#@{pi)e$F#+?hso%D53`Uu# z(7gNE%p`{;ef-KKdwwX%?wb(zp~$SewNpuucKmG$E$nsL@q<=|H;#;YcX>FXLBhhD zs|>&f(Y26Dy*Y2ZRJ*uUU6+;C$YvhYNL2iMGo^NTtdW2JfzV5tw|4clC%+IaIy#g* zC(G?8h!5JoKQD94sn*%i^9+Zov^``fX(E7+YU0A#vmH3A1hK&);^4$tPGp`EBc3%+ zEX7!k+s8iY(Y=!dG~u67!|Ek*XM z15qwORRe-j0tP$!`j#VHu~K56kQO%FaDCX%H*KXZZkNcYE*sHthRVU^!@~!b?ff%HX6;YAp9Ub(A1PyeWK+qYnX@1p${hbY}Kr|LHODJD-l1b z=o)_(fEp%{L=#3~ds7k%Gv~}9ae(*5E`eLy&RBF@NbF`(VYtgEGnhO#ju6=o2eEux zSv|gedpy{y;2gxO7vP*MwV_?{{*IjG~S0KNmn_4#iXhNQAgnr(#}^mN9A`tmVQ&9C>TNiyoYRp=au6n_7{Xnm`=0H+-Ja!zAzO*}ua#=euo4s}edU@?KEA(R8EJ#aeC z|3DMpgwarFv~=Hu40wU8bIcgae*M~52gk`}9#rf9c%Iq0vMgYF zpum|k>2JU(G-G_N5}ovl#l)ypQ#;VkPxkT(BUeHY<}6K3%B9If4=f0Lc^k7C5KK@U zgz+uMoHotfU7}K`*P%m?*GEN;=u~vQUZTGIGVcdWoxZU?VL_6^H+{~C2Um=`TPsE- z#l6ewjvQQchY-ugJ;wv2dd>~jJ#}jO1An_F+zC(n%I>=ww@y#Na^YLo>7$M6uYo!-D7Mg=UYfwS=V=VMygA#AN}N-s z*OZLxZ>G-;`mSnTVK6G^)+CYU?KUspa&MR}5E~Qb_$9T$Ol4*#XBFMbOlb>e^-*?e zDg}uZ_5a9at+oyg|CM^_k|)uJ9(lnw=H`<2j?#0D=NmZ-pFEy$r%MR{0w>P8k6Wi2 z93gM8ysy;kKTJl>k8ji-G6Km*cU#ui)K;X5=6JCivhHq|t#wph5z!^Z%^%CT^4k@OPaP zYa9J?&Q-3snSGwFYH57g={thRr)LA!9X|Yy)13~72)pyloX?q^J^qaOQdvRx`{?w} zpF1H8?2+Dn`AYPT1SV{Waa2+e*m~cTlh3Sb3Bw3U?UNip|4K@K9o~L}QKCxZOQ?&J ziYz5G?O1UX0Z&Me4h-Gciexs`mU>nk^y8#~=YQgdJVysX^{;CcBx+lU&iV26tCaM- ze?7x?Q$kK0Jz8XNtRTjIuR~#?aDGup2>wO1eLYQ05pxIv7t!jSwX+Y{tp7c#fOret zH(_C6sGOLmh;|3R&Xg;m!Fw*F?fA5JUcw)ir1sdn|MbL{&3;yT_m2oSt^ZE%BUi+x zrsKeV9I4E}p(0M7o&6@q=0#^|N!VI60XBVGKBvE=d1pAVTp!dJQQo_vtmRa@?Rs>^ zH(!ui)z7A7sHhLoF{pj$&jyqhTz3yRUx2DG`M0aPjg}U6CuahUYVQuw*zk|ecU6?) zknpGf2UGLkTGdzW_NYhL*GdI{tx&1lcxn&IUbP!p5+^xeB8~WS@VnKEE`2_^Fyh}X zFAiW)ld{wUjm-Z~cf0KP-?&Ys1gbRuYyD0^Il8&EIgwI5Z5Jl9AM+LfHB_-$a|mA{1*;W* zggXWuaj&VD#*MqA{%ot_X%#}|3~FjVsQ(7mKAH05C zFO+)7!npWSBIQ5G$I1&cncStV)J<2|d-yN`%()uULIyNYAMx$*ivY-S=9xhOP@@5W zK=w)t=!&@M(B_p-)i2Xp14AbW8TSGV8P@bpo@K6`2ej4fOhQiG4_Q$b= zI}wluX%A1rU_zR1UN|oNq9uF~;eN-B#0{3~|KMW?HCB~61 z;KFWFlcT<``0@q0(d}f*i>(#!%{twnu|P}rfz>G$#}je?k4r7~TjuH0fduvQ{n6Bb z1yE0u>Q(mf<7Ikm1Wwe>GNOwV3-TIYolU}>W*AC}2L|X>SEE|Qylc?Aw|Lb7-P_u} zc>df5V2;jj&mg3Yd`=KxKeN{!)3~=qMewJYVhKbNV5(0JFM|};4{zRV-n~1Mqq%qQ zvk3ec_uBxXZoVM(>eW>k_t18V5=|i+u{&DJXe~A2F_$4X6_^M`t&~8GCq6Eed*owY z##xXB%0le$0EWLH%tonHifxxa_=%HK1zJz@+z}{LNwVNx<cV;?!=RBFe@Vg+(MCr_At7Jc5CSgB6Qs%hul7Jk%A47_ zXAO3{we^uO))3SjncAJ5{ns6ST6}IY?VGFXO?K;~Ooo-=^aH9(YcYyuszcXaz0?Hv zNt51JRA7ex&H_4fyN$N?RHoO={(_kkkd`O`!i_4e!cXZ!tsunZk^FevcHf=q6%DPv z)oV9Pjri2to$m^TSY5?3C6odc0L1yu@exJ-J9h451T*go@eFGh&${%C091zRkvVWG zmq^^_p=U%>NmH{~_3O77gKG67HO4V8ClK7c5#wC>VmG)>!5#(Zm8A-*8jU;B|0thw z)+tyJcDK#c{hS|408!Tc)GH_0%IebhV`F_AG{*S6lpnZH|F_cV*Q&`zA5OI^I^lU9 zLALVS!#Y+p-M}7?^`0wMKgd~6c8eqzPzysT0EWC{MpE{`9k!NQ(gxiDG6!2$#m#Ou z+$Ck%vTVejk%!h#n-l+`$Zl`%xFmC(wG_Vn?Ckcu+xe_oAD8uo3)ma9g=~LIkT~v? zDk8WMtm>s<4IeS$c)CNUXkYzKa{HFKsqK8%Q^D}!+x$T?7gr_OtlEdOmQ#US?32^q z!Ow9>z+w05mH6UxcUSOIl{{iDCYAceC2bvtA3d+1_k%w&s7W_gSgExt@hz6su$+j^ zDkG?TwI~oz|B)>>rs&qKp72zhIr={p=QA*T>C%haP81l_^_8)h((bkPBSqiJvqe#J zPa3$)oqK{*(wDoer018@oFw*{?9AwR9EU>x-BJR!STt6D9ES~Ny7RAUSZ6-H6D`q< z$?pxWk~GlKamkYY&&|KzFuSTa%(#nCnWg?oXfG>oJct0tc*MNWG)NJ)yE04^b%n~D zI}`tS?tZj6!(l~lhXTnIWrm%do=^-ErKbm^!*R~$u*Bi#{!Ly_|0*LKUJ@} zZ4#IP4-a=MFqPf*=Q~v1+Rc=$VH82j&pj>7OjJ=)TCikE)lSJSJG{bD0-;7S0(O>3 z!HYqx>HNX$p06tBGzJmlKHx6BemT4+ zDKI0~&L?`+#lM4X3=MM;qL-A25Ol5f`n8+%7#Eq5fj@2ge&QDB?szc#iMm_yMnh8V zB7uk_vST+TaU;_p{EpE8aKs9;_P1B?a!{QURS8Ya|7%0^)sffJovk z97EJ%kS=c>a11`&d$BI zJb`%_`Q{QLFb^tX0dZ*(~*I_~ym@BO;!$?|U*T@!1+ zwrnr2_R*Ff*XO!{VtmHm+r~S~PFfeywY#7B^=0~o2}9jCjh2ls*GOUtoTAc@_>UKL z){WG%QAb-aEM(m31>J6Ri_H^cY=1TG534)ed+5Dv6_w&DyU>yc<44)O7<=d5c>d4k zEn7};E0dX^t5)#$-y)5Lf}dqN_2h(r{bQQ9>}zZKS;>EK7VOmYWf}=J|{SEGr<^tw|`pI`+NSg&Nw&7Gm zxuU@Fd!?UkcWGD9jx=e33g+P$)ZYY7o(h@PdjW3L4GL z2Nl*TAO zQXjCS`->YUhr$1uiw%OfHGfj=V=7tL3~o*_^>Vp^)Wzs+OD4S=zOv^4={GN;L)_xKIJF$TdsUVM9jBoT zS*2t9GP@@?h*tQ`ekzO39ya=HTVt^18q|oxWz!Mw4&1XJ?79BO?38=PsM&>Zrp8IS zRUWV6U!Q(`uJ_pO2;k*rouA_xajwVS$`!+xYbX_#o*rpCr9o|rO7zp2&1yStMA}$y zy>YPD-9rO6$Fu(lx`K4+e3!NzxWxM0x(A@?lQ-|j6q%Ve0rxgD0xmBatM$2E!P4(M z8;}qf))11oX#g*zr44s=ox2f-(@MdV>cA=OlMHilXZmyY2{1?qOZIILUdK8?=WK*`bfy zQiSOjyb52u*nQ3K7M1Ft2X?O>zP@!UC96%xh?*~}8!pXT-Jn6qtdg*gq)P_hO+7jI zoya3S=x%P_Rrz1vIPRu=_j3>Z_uVsgIvZH55Fk7I*1kKpYy0}TTXCq;DC0fZxb(5L zx!TpOP9@4)7Rz0)T+!{$ueXZ!x4RZ?=+`l2cs+lp zGL@-{LQHGDM73Bl`x&+bcK<#P0QKCL`2-%(6(Y%<(hF@?9c44OJ3V?<*sn|OUD3_S z?=alCb?eZTH+0ovZ_R+=GQ)tz5hEla92_^!I7O!2hdCwcU8{N>sG9n!sCd;et*w<` z1WmnIse)i#1)g$dDaAr<$BU{6RnvgqS!PmOCz2a&u;=N&+T`4q}+JbGv&Gi7ptsX#&B;C zESiz?w2liGGVwk>BnN3XLTT8o_}Wq*kA!7aU0o7ulAgp|3@Vo%{CktrUJCYK2L|g7 z?cL~ZDj&bgvcnOLktQK0F(iI?wtnV@2ag`DLzKpXagNhv)DR7qzV%(p>9hBp33*v> zwPMgJy6x(2xzYJ4((i56Uf#Q>86Dz#_6bvrOG?D(IlfwdwpTcfV9LbN0u$u6@7R%0 zQO|Dw&1#9Gd%?KTFLkk6vAHH)E7NDz1pAVo8|sX;k}|z_F3YIxXnJe@EO+-Ll$X4o zRW#6`S|n5{c{D8 zB`|IHMGbc&RDn?_%88NK8sD}M=S0?bGR<89Q{PFl`f|l6W#nBtr8(?3m!gPoEirjcd2XVXDV%4()RUSjZr4}_?pKa z%y3?E`10Bc2$!Q7_S5eLM<4AhgwwAC4|7fCgftFt8aoE$b+DUr)djU1OS(GV`H0i4WOtErKOX}RJm z7gIr(Zdjf=>FDpfcTQg6oH{uEtQVg%QIMsypsZk*fiaaZj4qJeMCTjsGi11mi7qZH z^V#(B*|VKGaR;6lBcx*#8#6Yk^lUo8%pXWg8=dUBea8{g7fW;%3cj+q7pxlf%6)Wg z_gL@Iqa7C4E2`w_cI;S^5uTD_a52xc%^Ku6SBd|vX=@o(qnqXOb8>=Ls_O*(7e6<= z8~)nhh1sDec?YX}q8$xx&3_Nq%G@n!V=G-ZDnEs)O>+yWabc8?Y$ERw?4-#0?Af#N zJ8NodoqKM?($MFavViP>^TRPkbO-VCuH4X0te{N}TCf0=7}}_l^#alJ8Xv4@Y3omw z;_&@_n+hvXjq`k&=@HitRVbx*cerG$An=-nAjXRrs*hZ~`SHb#hi-fs-u0+<;;T{~ z+sN__&s^SJQP3W<@Y2Iw)7z_A%bm2QmD$vl5VR>o3a~l$PfEXA>vML@(J(m~nU$Bm zTdBESjGWh{xP$JXSD+eDuJzu54CxKTjY>AL<<+=tPOX&|BxSV{cu2=dypsxbBm6gr`m?<_MC@*)n=YYdV)R32O z&O61aVT+H~4^tC`suoD4r#gw2|DbnDi(F}{RsmC|Kmc%~gDcO)gH96^Pww8PAq zN61GeXk^CiUBhLkZr$$IW>`wA#0reYA4v@14|#ccefjjshOlkg9mml5^G{)t2h>5% zKRY1c2byew(|?qk+yBSddw^rv_y6M;O_C&`vO`yPsE|EM*?T1;3L!*AR)vfbAsJ;R zCCL_r$ZeGD3P~AR$xdYaUT59U{65G3_+Q8IJoj@y$9>Cno}cl4zt$T>Ih;f2ww6vO z`JBQ@gMS~_5(2@{Jp-JaqA1~Mlmt*R zUUiS68ZP8dA1T>o!&0uwP3SSoG1V$$7P}FVm-vAQSP3zbix9a64mYo_ zlatJU5}Gnk;wyRrQXS(;m{hhR+6So~)knqFqrbpmzxZQ(6A3*Aj$vS?_-a@tmlmdZ zZdGE69?cG2Gc$h1Fx!m&*$j@*8Z30{fnL&VHW*W)lvoH+v^ zawpMvL~Ox$K_cxQ(kIfL`1r%h%2;Sb`|b~&uPc@3&SUb1cT%I}IhbX<3L+zQ03-tc zg|J>w?}~c57@gcOb{~Z$ejECcwK5kef~BLy`DOR*%LOP)&D2fHVB3QCs@zc};)n_t z;>x;81aLH;K010O+muUV>_hQc_Jl@UD9~0nrzk-J(Z4%K6#!8|7N>RNtw?(bR0}9| zKo@{T$DP4KI|TYjIS8Z<%#yQm)B(g*W?wJ!;W5M9C!J`P_3Ps^yGr>+c(-|!Q4T!L z8cp8CJ$#qeg3?WO>%dh^%`- z%_~cb4nn8Um4#tR4&f`+PjM#3RSS_b4|R1Sa&mUoePn(Z$_VK3H>FoXd9HdU0gws0 zuNEtSxEO&-n9ne5h!EO#)=eL|&aB`o3TOq@mgzbB{dJTN)l#&VqIflK1;=!c3chx{ zOLt+QuaA<10-z?&m4&4?=0G?|5O5+HF1h3KI*b|zt^s!>ah!GXcYM}yMdChBYXR+s z4I3E40=v{v#*zRLsF*8Pb<`+SZ@Hf}XShZUiY3pWw3% z3EM@Mse`_UJi5`ZdN3~2Y$L~MmIqgzBa$yjd2``N<-v?hKxy1>xel_?)2F7S5Samx z^wdSY`Q16*`6)p|({o*TCDc6#gg#yqJ3BkhHGE4zn8$_`@^xFB1Bv8Dj3MN-Y6Wc= z8T*Mhutnf_pC8B?YtNSd?&h|>R;X7Y;fx9M&P+>FQf{zI3TnTPOq3D;&~V=P%yk$! zmFiI&3m95k_c2*`gBmr|)lDVm(zGs3_^l!(#@Ogg%X-s7yF5wAJJyj7-`J^=uKh+N z_UUeFHEty<*5PwPiv?kh5w8bznIc{G&nh%7VqHPag1uefA+E?xkF(wmiX*#QtC+Wk z+6Tj-mcfV=od~>qIpuU)IB!%{_jV_@t1sHVY=aydfyywDo#66;F&RYhwy+xP_e(z~ zt;z<)L`3QWcGOx~4%3`i-abNumLD#fyJ+5UB#&c^f3{3J84@QbU( z@)7s$$s!pGs5NAWsgjjW_kDKi#?pt?z~BqM3Sto>%MbbJRf*KwloYG_TRfq+>NzwZ z`obP6GYvb?{75BAA>rLNvo8KM)MGF?s;jDk2KhX75$r9F5fn7(La8+1o{k9UD0A=G z1N?{1>==Gs5st1F8{NZYsXnu&*D649jwmCa4=Nj~z$)aswOluTRaq$?8y|r@1PoE-g11>^t-JjmSCq3LR=8ND( z?SXS2h4kdZt^Th~_i9iS2GP?|>&12v#_oj~s1e{`3^x^Qr62bH9{jNc5S*&II?{R$ z_;AuV-A61Tniccxz&v^OQ#s~k87}qik0AjrD4j!aIa`O>m#VQv;`_-^#-2Ur*|q zW$SuQ2eEBj=)Ys#-w){^hCTp+M0t-f_dlrY5h${GGkO&3{7#hMkPBkcFhZ8ck`V)7 z00730Pho?~K6gF&n!bK)N@D)JM7Y%*fY}I+aAje~x<#jixoSFWIEo1V^6)jFT6P9A zP)??zI~F2`XEBjBO|9qJNWezQ-cGtB0~bc5Y}E1aq;N51>Ea$-5XCgMLJC zp2KJfYqmIGB07>2AD@X~G(G8QVWGy+qn?4kfjo z9uX4&`L7SsSU_bVNS-*+lPjgBTd6K1TR%H?tiRdDypOwuZgj28n*T1Ey&kHeotJ)zjg zo|^Sl4?3V{VuGRU?>kJ%*KwnuQJ^5Ota)7j2vUWx212QR46+(@kw8gg;WHP5F*Q&u zqM2PZ=F8`x7GL8g5E<@4s>BWh+*^Rem_9xh+C4Bo;KXcIL8SxHEBA4YtodKVExT=2 zz!pH*wThhqC#-xTF(teo>xVH6@`GTFa}*prJnJ;_5g9bxT|9vV3aKCMmzW@6#E~-r zl+kMw_SGGX-ZVNh<%bJ(V4)H_3)4ookl=6yi-cow5&qt7*AS2~T(aw%6D@-Be`Vg+ zKYkp?+7D1iw+`C-;z)!ODe~*5PoIbs87YrZ5T=(;OyNStF^rOydgI37_c`p1No7om z6=4scYi8{Z{^;YZ_mY+`jRsiq`H4?AR(c`I$L`cnSH}y~w2`N{13VQ1s02jp>Fc|H zuB0f~)QzpD!qoz03b03Fpn?4tC`?h4z%g-9R8+BW=RUArf7tH3vOYUEA6r~lpr}OI z6B+qvqd=ng%s9022o|mCE>TwG1%3xn3Gk) z;ZRrRk&wegtF1S^VRTiY7% zI;&yvW(mz&nF`TQ95gn}*R+!5{j%sJXLau&;G*kf$$BxtV#aR7~ltpo8MN*UAtie4vrZBMZ*z|r*1Ou ztAcjAy}h8nYCE%bBF#xdcd&sYZ+zD&3155Y*ppiSN@9u4|Cq;={;?gNsLi$FL{+$Q zp$H-7(lCxeF37O!FCG7&vzZ(GOId1aCCl9gk+B)8O%@Oo#CpMpo{&o+hO@n=$EPZQ z3Jonc*X`G8Od-lhx`1M3%f^kU4d|yjDEym-Fs2=9PJ|}Ndd+U+!5spD44V4C^~Y%0 zltzFF;IFvC5dc7h*~98ME1sX^yLc4HYi;cw8JT4?pWd?UBU&j#I2B5c3x(ChAG$zf z;@qr7nSpqiwa|BRr2T$o$;M|NatYqjOiNC#?Cw4UQvnbj%@t(OLD+$im2%IS!=0sb z>p@Gxxm>HWo_yO6O+B?mVE;uZ&CrGtVVAC7oc6OgFy~tX$u=ofi3)N3$+sz~3G&|~ zFHA8ctnWtMx8;_JJ}c()Kfak@EXYH>2dUA9WlgQ?Tv=8T7=_9kPG&g9-AaOBG<$h^&W*HPo1=dgOyWph4o{^ zOROpssxI)A7#ZEfbhTa%&Em`7LKv7YY*3}hePulbZ3v`>A+*nqe~;i)!dRfe`zM(X zK-G;*EQVozVc{*CgHjyH9PeQ3#U;ze#Pl9p6h(n}Z9VYlSFhIAx<;y||6ZwfEVT&< z3HV8{ffH!4G?*n4#LmG{)<_C7K+eek^vO=G?6t0!Iy!-7GC=iNd=+ttpyN_(iABKV$=Jy-O5UBnFZ!NN>I3V5#d|@-} z{ja{uH1Xv6I1$>U>j0x5TUfBZ)<|ZZ>e*hAcR9Rbl<3As(7WPrJ))G?EHZ# z@cL|81X}}$dfvCGPh@s}z#PYv85-LYfvT~pI;zIbD&fUsHKCD^y5o8mFmTdy1X6R7 zJez?6=ew5GVJc{&QQ>` zm+9s}_(CU;S~8D^c~`}Cgvh_VbBC;@`0Jk5kpZy+YO(O@%4XW=7U~N35qi?bK*buI zTh~>@$XjAYjYLG6uc03pKA#y~p&}NJOJN_)>-u^eH~0TMp*bGn3B{#yg!g0TDMMQR z7@~!T4?}3}e;6Yk(pZ3Tr?>z={~8hw!BH;N7Z_j=x+~h3fylYMjU6Lgv2;r!tn>$B z5g_5jtk`FO`tR0~CoZeW$r$XawQo9}CcJViYvFH(O-LF7QO^;$dno)NuG>nVKBSCA zzF}Dm;$0HPeNgxl^B)mjfXjCa6%}S@DXIy9-lih$K73t%0qpk2o*opSoDD54x#i{e ztdCVl%d~U?>T&Ka8V7cVtJxsyW~$l%(OzVVqLs(Sdh>2O!P?-{6K+`c7Z`T@pWtKh zCD2AA3_Sh&a!UY9QHbWe$ti^p%qYE/rbbTysf>t835pGl)UCjtF<7{)PY_dSkh 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%0D1K)-s>N9cRL?yTpF>NPL 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 St==iV_0eoAN!o{wTmJ*@60XYt literal 0 HcmV?d00001 From 36e91866c976cb356cfb3a15da4d5d0b46f108d6 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 11:26:30 +0100 Subject: [PATCH 03/83] Remove Administrator column --- .../LocalLoop/Schema/Result/Administrator.pm | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 lib/Pear/LocalLoop/Schema/Result/Administrator.pm diff --git a/lib/Pear/LocalLoop/Schema/Result/Administrator.pm b/lib/Pear/LocalLoop/Schema/Result/Administrator.pm deleted file mode 100644 index 1fd2bfc..0000000 --- a/lib/Pear/LocalLoop/Schema/Result/Administrator.pm +++ /dev/null @@ -1,28 +0,0 @@ -package Pear::LocalLoop::Schema::Result::Administrator; - -use strict; -use warnings; - -use base 'DBIx::Class::Core'; - -__PACKAGE__->table("administrators"); - -__PACKAGE__->add_columns( - "user_id", - { - data_type => "integer", - is_foreign_key => 1, - is_nullable => 0, - }, -); - -__PACKAGE__->set_primary_key("user_id"); - -__PACKAGE__->belongs_to( - "user", - "Pear::LocalLoop::Schema::Result::User", - { id => "user_id" }, - { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, -); - -1; From 736ffb89cf1009bfa214c02f246a145364b00f2c Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:16:38 +0100 Subject: [PATCH 04/83] Added entity table --- lib/Pear/LocalLoop/Schema/Result/Entity.pm | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 lib/Pear/LocalLoop/Schema/Result/Entity.pm diff --git a/lib/Pear/LocalLoop/Schema/Result/Entity.pm b/lib/Pear/LocalLoop/Schema/Result/Entity.pm new file mode 100644 index 0000000..121d560 --- /dev/null +++ b/lib/Pear/LocalLoop/Schema/Result/Entity.pm @@ -0,0 +1,54 @@ +package Pear::LocalLoop::Schema::Result::Entity; + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + +__PACKAGE__->table("entities"); + +__PACKAGE__->add_columns( + "id" => { + data_type => "integer", + is_auto_increment => 1, + is_nullable => 0, + }, + "type" => { + data_type => "varchar", + size => 255, + is_nullable => 0, + }, +); + +__PACKAGE__->set_primary_key("id"); + +__PACKAGE__->might_have( + "customer", + "Pear::LocalLoop::Schema::Result::Customer" => "entity_id", +); + +__PACKAGE__->might_have( + "organisation", + "Pear::LocalLoop::Schema::Result::Organisation" => "entity_id", +); + +__PACKAGE__->might_have( + "user", + "Pear::LocalLoop::Schema::Result::User" => "entity_id", +); + +__PACKAGE__->has_many( + "purchases", + "Pear::LocalLoop::Schema::Result::Transaction", + { "foreign.buyer_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + +__PACKAGE__->has_many( + "sales", + "Pear::LocalLoop::Schema::Result::Transaction", + { "foreign.seller_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + +1; From 6a720b0a46989ea98e556e935817a33d9ea56914 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:17:05 +0100 Subject: [PATCH 05/83] Added Entity to customer --- lib/Pear/LocalLoop/Schema/Result/Customer.pm | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/Pear/LocalLoop/Schema/Result/Customer.pm b/lib/Pear/LocalLoop/Schema/Result/Customer.pm index 4ce92e2..147f945 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Customer.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Customer.pm @@ -13,6 +13,11 @@ __PACKAGE__->add_columns( is_auto_increment => 1, is_nullable => 0, }, + "entity_id" => { + data_type => "integer", + is_nullable => 0, + is_foreign_key => 1, + }, "display_name" => { data_type => "varchar", size => 255, @@ -36,11 +41,10 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key("id"); -__PACKAGE__->might_have( - "user", - "Pear::LocalLoop::Schema::Result::User", - { "foreign.customer_id" => "self.id" }, - { cascade_copy => 0, cascade_delete => 0 }, +__PACKAGE__->belongs_to( + "entity", + "Pear::LocalLoop::Schema::Result::Entity", + "entity_id", ); 1; From 64cab5f17cee91d00f84c67303c5a3dcacbb8c60 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:18:07 +0100 Subject: [PATCH 06/83] Fixed whitespace in Feedback table --- lib/Pear/LocalLoop/Schema/Result/Feedback.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Pear/LocalLoop/Schema/Result/Feedback.pm b/lib/Pear/LocalLoop/Schema/Result/Feedback.pm index 7d990b6..3c8004d 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Feedback.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Feedback.pm @@ -14,7 +14,11 @@ __PACKAGE__->load_components(qw/ __PACKAGE__->add_columns( "id", - { data_type => "integer", is_auto_increment => 1, is_nullable => 0 }, + { + data_type => "integer", + is_auto_increment => 1, + is_nullable => 0 + }, "user_id" => { data_type => "integer", is_foreign_key => 1, From 7042020969ac16fdbe11ec5ed790b18e15b7a3cb Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:19:47 +0100 Subject: [PATCH 07/83] Change LB Value to link to entity instead of user --- lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm b/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm index 00833f4..7374246 100644 --- a/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm +++ b/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm @@ -13,7 +13,7 @@ __PACKAGE__->add_columns( is_auto_increment => 1, is_nullable => 0, }, - "user_id" => { + "entity_id" => { data_type => "integer", is_foreign_key => 1, is_nullable => 0, @@ -55,9 +55,9 @@ __PACKAGE__->belongs_to( ); __PACKAGE__->belongs_to( - "user", - "Pear::LocalLoop::Schema::Result::User", - { "foreign.id" => "self.user_id" }, + "entity", + "Pear::LocalLoop::Schema::Result::Entity", + { "foreign.id" => "self.entity_id" }, { is_deferrable => 0, join_type => "LEFT", From 4f99ef82f6ba74e1c1ad72b06e3e8f396d2c8bfe Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:29:06 +0100 Subject: [PATCH 08/83] Update organisation with extra information required for entity upgrade --- .../LocalLoop/Schema/Result/Organisation.pm | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/Pear/LocalLoop/Schema/Result/Organisation.pm b/lib/Pear/LocalLoop/Schema/Result/Organisation.pm index 5967644..a178e07 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Organisation.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Organisation.pm @@ -15,6 +15,11 @@ __PACKAGE__->add_columns( is_auto_increment => 1, is_nullable => 0, }, + entity_id => { + data_type => 'integer', + is_nullable => 0, + is_foreign_key => 1, + }, name => { data_type => 'varchar', size => 255, @@ -34,27 +39,33 @@ __PACKAGE__->add_columns( size => 16, is_nullable => 1, }, + country => { + data_type => 'varchar', + size => 255, + is_nullable => 1, + }, sector => { - data_type => "varchar", + data_type => 'varchar', size => 1, is_nullable => 1, }, + pending => { + data_type => 'boolean', + default_value => \"0", + is_nullable => 0, + }, + submitted_by_id => { + data_type => 'integer', + is_nullable => 1, + }, ); __PACKAGE__->set_primary_key('id'); -__PACKAGE__->has_many( - "transactions", - "Pear::LocalLoop::Schema::Result::Transaction", - { "foreign.seller_id" => 'self.id' }, - { cascade_copy => 0, cascade_delete => 0 }, -); - -__PACKAGE__->might_have( - "user", - "Pear::LocalLoop::Schema::Result::User", - { "foreign.organisation_id" => 'self.id' }, - { cascade_copy => 0, cascade_delete => 0 }, +__PACKAGE__->belongs_to( + "entity", + "Pear::LocalLoop::Schema::Result::Entity", + "entity_id", ); 1; From 8acf3af28a068d545f13101f4f1a3d335f8c0dc6 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:30:37 +0100 Subject: [PATCH 09/83] Removed pending org and transaction --- .../Schema/Result/PendingOrganisation.pm | 70 ------------------- .../Schema/Result/PendingTransaction.pm | 69 ------------------ 2 files changed, 139 deletions(-) delete mode 100644 lib/Pear/LocalLoop/Schema/Result/PendingOrganisation.pm delete mode 100644 lib/Pear/LocalLoop/Schema/Result/PendingTransaction.pm diff --git a/lib/Pear/LocalLoop/Schema/Result/PendingOrganisation.pm b/lib/Pear/LocalLoop/Schema/Result/PendingOrganisation.pm deleted file mode 100644 index 5cf1f51..0000000 --- a/lib/Pear/LocalLoop/Schema/Result/PendingOrganisation.pm +++ /dev/null @@ -1,70 +0,0 @@ -package Pear::LocalLoop::Schema::Result::PendingOrganisation; - -use strict; -use warnings; - -use base 'DBIx::Class::Core'; - -__PACKAGE__->load_components( qw/ - InflateColumn::DateTime - TimeStamp -/); - -__PACKAGE__->table("pending_organisations"); - -__PACKAGE__->add_columns( - id => { - data_type => 'integer', - is_auto_increment => 1, - is_nullable => 0, - }, - name => { - data_type => 'varchar', - size => 255, - is_nullable => 0, - }, - street_name => { - data_type => 'text', - is_nullable => 1, - }, - town => { - data_type => 'varchar', - size => 255, - is_nullable => 0, - }, - postcode => { - data_type => 'varchar', - size => 16, - is_nullable => 1, - }, - submitted_by_id => { - data_type => "integer", - is_foreign_key => 1, - is_nullable => 0, - }, - submitted_at => { - data_type => "datetime", - is_nullable => 0, - set_on_create => 1, - }, -); - -__PACKAGE__->set_primary_key('id'); - -__PACKAGE__->has_many( - "transactions", - "Pear::LocalLoop::Schema::Result::PendingTransaction", - { - "foreign.seller_id" => "self.id", - }, - { cascade_copy => 0, cascade_delete => 1 }, -); - -__PACKAGE__->belongs_to( - "submitted_by", - "Pear::LocalLoop::Schema::Result::User", - { id => "submitted_by_id" }, - { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, -); - -1; diff --git a/lib/Pear/LocalLoop/Schema/Result/PendingTransaction.pm b/lib/Pear/LocalLoop/Schema/Result/PendingTransaction.pm deleted file mode 100644 index 5637181..0000000 --- a/lib/Pear/LocalLoop/Schema/Result/PendingTransaction.pm +++ /dev/null @@ -1,69 +0,0 @@ -package Pear::LocalLoop::Schema::Result::PendingTransaction; - -use strict; -use warnings; - -use base 'DBIx::Class::Core'; - -__PACKAGE__->load_components( qw/ - InflateColumn::DateTime - TimeStamp -/); - -__PACKAGE__->table("pending_transactions"); - -__PACKAGE__->add_columns( - "id" => { - data_type => "integer", - is_auto_increment => 1, - is_nullable => 0, - }, - "buyer_id" => { - data_type => "integer", - is_foreign_key => 1, - is_nullable => 0, - }, - "seller_id" => { - data_type => "integer", - is_foreign_key => 1, - is_nullable => 0, - }, - "value" => { - data_type => "decimal", - size => [ 16, 2 ], - is_nullable => 0, - }, - "proof_image" => { - data_type => "text", - is_nullable => 0, - }, - "submitted_at" => { - data_type => "datetime", - is_nullable => 0, - set_on_create => 1, - }, - "purchase_time" => { - data_type => "datetime", - timezone => "UTC", - is_nullable => 0, - set_on_create => 1, - }, -); - -__PACKAGE__->set_primary_key("id"); - -__PACKAGE__->belongs_to( - "buyer", - "Pear::LocalLoop::Schema::Result::User", - { id => "buyer_id" }, - { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, -); - -__PACKAGE__->belongs_to( - "seller", - "Pear::LocalLoop::Schema::Result::PendingOrganisation", - { id => "seller_id" }, - { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, -); - -1; From 2954297b358a75024a8f91e1b9a655c2bef973e0 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:30:52 +0100 Subject: [PATCH 10/83] Change transaction to use entity table --- lib/Pear/LocalLoop/Schema/Result/Transaction.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Pear/LocalLoop/Schema/Result/Transaction.pm b/lib/Pear/LocalLoop/Schema/Result/Transaction.pm index 6ac0223..7b87a16 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Transaction.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Transaction.pm @@ -54,14 +54,14 @@ __PACKAGE__->set_primary_key("id"); __PACKAGE__->belongs_to( "buyer", - "Pear::LocalLoop::Schema::Result::User", + "Pear::LocalLoop::Schema::Result::Entity", { id => "buyer_id" }, { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, ); __PACKAGE__->belongs_to( "seller", - "Pear::LocalLoop::Schema::Result::Organisation", + "Pear::LocalLoop::Schema::Result::Entity", { id => "seller_id" }, { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, ); From fd98670333b3a88caf6c72819232e629c6d8050d Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:39:10 +0100 Subject: [PATCH 11/83] Updated user to use new Entity table --- lib/Pear/LocalLoop/Schema/Result/User.pm | 84 +++++------------------- 1 file changed, 16 insertions(+), 68 deletions(-) diff --git a/lib/Pear/LocalLoop/Schema/Result/User.pm b/lib/Pear/LocalLoop/Schema/Result/User.pm index 75a1dde..7c57d40 100644 --- a/lib/Pear/LocalLoop/Schema/Result/User.pm +++ b/lib/Pear/LocalLoop/Schema/Result/User.pm @@ -21,15 +21,10 @@ __PACKAGE__->add_columns( is_auto_increment => 1, is_nullable => 0, }, - "customer_id" => { + "entity_id" => { data_type => "integer", is_foreign_key => 1, - is_nullable => 1, - }, - "organisation_id" => { - data_type => "integer", - is_foreign_key => 1, - is_nullable => 1, + is_nullable => 0, }, "email" => { data_type => "text", @@ -51,59 +46,21 @@ __PACKAGE__->add_columns( }, passphrase_check_method => 'check_password', }, + "is_admin" => { + data_type => "boolean", + default_value => \"0", + is_nullable => 0, + }, ); __PACKAGE__->set_primary_key("id"); -__PACKAGE__->add_unique_constraint(["customer_id"]); - __PACKAGE__->add_unique_constraint(["email"]); -__PACKAGE__->add_unique_constraint(["organisation_id"]); - -__PACKAGE__->might_have( - "administrator", - "Pear::LocalLoop::Schema::Result::Administrator", - { "foreign.user_id" => "self.id" }, - { cascade_copy => 0, cascade_delete => 0 }, -); - __PACKAGE__->belongs_to( - "customer", - "Pear::LocalLoop::Schema::Result::Customer", - { "foreign.id" => "self.customer_id" }, - { - is_deferrable => 0, - join_type => "LEFT", - on_delete => "NO ACTION", - on_update => "NO ACTION", - }, -); - -__PACKAGE__->belongs_to( - "organisation", - "Pear::LocalLoop::Schema::Result::Organisation", - { "foreign.id" => "self.organisation_id" }, - { - is_deferrable => 0, - join_type => "LEFT", - on_delete => "NO ACTION", - on_update => "NO ACTION", - }, -); - -__PACKAGE__->has_many( - "pending_organisations", - "Pear::LocalLoop::Schema::Result::PendingOrganisation", - { "foreign.submitted_by_id" => "self.id" }, - { cascade_copy => 0, cascade_delete => 0 }, -); - -__PACKAGE__->has_many( - "pending_transactions", - "Pear::LocalLoop::Schema::Result::PendingTransaction", - { "foreign.buyer_id" => "self.id" }, - { cascade_copy => 0, cascade_delete => 0 }, + "entity", + "Pear::LocalLoop::Schema::Result::Entity", + "entity_id", ); __PACKAGE__->has_many( @@ -113,13 +70,6 @@ __PACKAGE__->has_many( { cascade_copy => 0, cascade_delete => 0 }, ); -__PACKAGE__->has_many( - "transactions", - "Pear::LocalLoop::Schema::Result::Transaction", - { "foreign.buyer_id" => "self.id" }, - { cascade_copy => 0, cascade_delete => 0 }, -); - __PACKAGE__->has_many( "feedback", "Pear::LocalLoop::Schema::Result::Feedback", @@ -144,22 +94,20 @@ sub generate_session { sub name { my $self = shift; - if ( defined $self->customer_id ) { - return $self->customer->display_name; - } elsif ( defined $self->organisation_id ) { - return $self->organisation->name; + if ( defined $self->entity->customer ) { + return $self->entity->customer->display_name; + } elsif ( defined $self->entity->organisation ) { + return $self->entity->organisation->name; } else { return; } } +# TODO Deprecate this sub? sub type { my $self = shift; - if ( defined $self->customer_id ) { - return "customer"; - } - return "organisation"; + return $self->entity->type; } 1; From 27d06fbe371e53afaed0136cf7f56082d09fd673 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:39:36 +0100 Subject: [PATCH 12/83] Bump schema version number to 6 --- lib/Pear/LocalLoop/Schema.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Pear/LocalLoop/Schema.pm b/lib/Pear/LocalLoop/Schema.pm index 739cc56..b4bf402 100644 --- a/lib/Pear/LocalLoop/Schema.pm +++ b/lib/Pear/LocalLoop/Schema.pm @@ -6,7 +6,7 @@ use warnings; use base 'DBIx::Class::Schema'; -our $VERSION = 5; +our $VERSION = 6; __PACKAGE__->load_namespaces; From b2312f7dbc220db1f19c08d972f8637cb58613ff Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:40:42 +0100 Subject: [PATCH 13/83] Fix unique constraint on LB values --- lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm b/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm index 7374246..1125c35 100644 --- a/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm +++ b/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm @@ -40,7 +40,7 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key("id"); -__PACKAGE__->add_unique_constraint([qw/ user_id set_id /]); +__PACKAGE__->add_unique_constraint([qw/ entity_id set_id /]); __PACKAGE__->belongs_to( "set", From 6b9e1763b8e239c778d53f2a1f4165021934fd00 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 12:54:41 +0100 Subject: [PATCH 14/83] Added schema graph for v6 --- share/schema_graphs/schema-v006.png | Bin 0 -> 122511 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 share/schema_graphs/schema-v006.png diff --git a/share/schema_graphs/schema-v006.png b/share/schema_graphs/schema-v006.png new file mode 100644 index 0000000000000000000000000000000000000000..1e9bc884bb4344e2168341f346bb7a466b792516 GIT binary patch literal 122511 zcmd?RcR1F6|39p)fe4k65+bW%Wv0#|QY0fYGqN(WS0y7+63R|VW$&z1G9n{;Q}#|~ z=KVP7`d-&}-rwKvIPSmh^E!?$^>Lo(d%RxH=VLwJKJu5O$ac`}AR!?kyC6-tLPD}R zn}lQ&E9pl33wfYt6#j3kuB;S+WR>{8=z_RF5|V=?7YLG9?ZZbqj5VlLS_QlFNGLVj z&u`hX`5dK(5Zlzz9n7M8&XtO>#2i0Nb$6$af}{t#$9x&Hxp&^yJqABdY-p#_r;0f* z#X@zFpmCaVi-FI3kM)wlr0YN3e#}ZddJ>Y;jG6xAYd;MqXyyqB3TD52>CSfi z#EI4%>l-6&K5H*y7m*shQzFC{XR4&A>}I@Sknp*v$lA(ESa^qhL3nuhuV1%f`Dpg+ zv0h%d9m}^xtY&7UDQG2mm;KsT*5uebIB=_`MB~$&y#4*P-rstWrk0+X>f`UPtZY*A zGNU+Z=lTbekhlw%QEgZ|rfBjs!rBicZ|-qXuKzTAaP9m5`D@W8Z{EJmu^m&BlKOs+ znwpw$?%aJ35BB&^wY9ah^;A3Vm+y$?P@~ls{QD8Yc~72Dxw*M%YIb}LJYr|VC3eoy z(ee8A?!}2hx6$TQWdS86B^oL!T|K>%*FU|pSH=gKj{$??KQq3bFqS-pMPe0T5b2oXvup|X^KoT zxysBhB(C37_i;8sLA%+}7CDE__0!xNqC3{F$>8S`Cr*ejj-K4P(-*e{FEOXthm0b@ z-dY8Yw|{ki%CnoeX=0*X=-To1>({0xm2>A}Q>|Xe?_*#{c=zromCtx*!D0!$#pcbM zNjKl+EnKQCD!S)wAL;hzci)d9uckzGDFT&GFRs%gU*b#l!i8(8y2Y`wAzWNsA66Dd zj_Z{W&YyoB8*6N0;$&~nAZ#NoxkQ$2%JsyN#%{Fv$G||gRbRPXmy6x+-miUqeR;NH zj<{YCB6c=5Hds$x_B~`rvAMGl1qra^0NxViUGQB~o?#@lnWUH=$-k2!bk+O=%+4&Ao6*jNV# zhvkKlWWADy+js6v&&p!EA0kuqxD(%)Gd4HUl$?6dX`1FUanqcRlO!l8D9Ckne*5;V zzW(WeLxA+F#6&U2X`{#ilbbgg7#a8SYH`u}Iz&G5_C6S6Jy6B3yz;wG_Tc;GLcnOe;T&cSa7#$;la5(6z)6m~qvInv$6)w`|>Nc;iNCaWQ2;Vq)U->2H;l zVfJOU+%!_U=H^w*96UTLr4lOvp`qmhfzm8IJUl}~L#E%qe^l8}&KF0ZZ*5m>BtTM6`bc6PRZ-u^H^lY#%{)9Gvi z^LH#w6_tye9k}{QmNh_`;i%6!~1aGiS~e-8ht*n)=`{gV3#ml=r>7e2ag! zWm}|s#0@#B1;6QLsVE?QbU;joP*~i ztznn`%owc8*VeY+Fa$L~%ZxSQl+{IF%E2@3@cs!o$s-n3R;1n8+Fv<+k$N z`{Bcf%>o6EGgj8tit>@qo=FqZUJi|pjrk0QXuB%p+xhqCyNwJEq7G&B-wLJrns6<> z#GkQZy!$!5pt-7=T4$bJ@-@l>qV~UvC?uwT{}y&#n*ZRkn30j8w#OkmZtd`^qa-d9 z4omqzHGdrw^SkwRvjA@POgU;trEC6Fl}Kb{WVS`ucwHO?zw6sFvbQ>mJMO-Ubh&@s zUl9lI&ck)+(5Le92V@0$FFAeQynp}9`+&H!szpypc}s+)t!-8YL0db@J0ml*?dbS< zKGr9UBDN@+W*xaoYFD(hB7=fh7timfEU-Nv{%2|+?$xV{&rkHtb-87yr;AwidY|x* zZo6{pNuQPnb{Bz#dPi78y-DiGr;i^`PsTC}zVf8rd_in+$9k`+xu27hW62be60EYJz&L|K?V0n*@wWK71C6~ z=HgyAjeYU?TPS};HSsmkwYnn6nVON2kdW}SlD@9Cc6O{S&OYwYp+km-hS->*4!>Wi za*>mg8tG<;1>Ozhnq$_d82s7Ovwoa9B@g%iz43CAkL!z)clVKB-$J}@jnRf5LfChc zUcGuXNi)xO{+G_IK@JwIw%c-0<@9`Z;j&rR2N$e@Khr~X6%`dzQ&WAxDp#+nUc0uR zj!vfQw;I;Ps|$fQZ{9@Boa?ajDXXsLJ$zViqO-s>qmx2nNnTyuL|69=!PJo=a)4~t zF7G$FxuFAp4xRNhx_PrR_x4Dp*TKBlpVBBDmk7G4?_a;}w{Tkw%gUOq3=?#SOioHN zOb&FJ{w`Jeh-q$a4pmd%B7npD)<^dZqkZR7RTEym)YH?eo35*xdpJkc(j&fS&mOHG z7lNHABgI|Rl$37QL`zC6PuIn(B=czIemU=jD|XcAW^;-T;Y)5J3bX%;58 zrPP-%U#fB`J7awf4b{cXv|he+$(&32LxYNRp0V$U)QsEeoLd{`p|va5)SO`)n4X>< z6r{8#n~Be;rXgMhU#+R3ft%#|r!NG%23^8+{)ZRJu%Ja}zWwCS?oan97=#Y)CS91D zD7^mZu4f-{TUlBan{ScBlq@DEi1Y-8qpO^%?NL{i_dN?&W_c^UDnRCHR~%6 zDqNloD=1j5d2yCkD}Ns&qa5Mr(W4e0{`BMRmm-AE5m2oHk6b2hHhlZS?AYRLn+2!1 z$A(Q?rHM`H_wV1oe*MDd?Fq=uT__7UoSB)4&uqhcsJ@5cgH{$NBwUw-#Kl)?V`a~u zKfgFP(bUk;QRuc>xH6Mm!o0LJRjsG1tM$RzVPV?%&gow>P1=Gc9%BVgbNK$)r%pr9 zp6z3{vb8mLQIwfRKkM%9?(NloA^#>Z(agXg)98DQ=aJ`hIiyWVnj+Ty-%!UUTG9tb zk_!(A-Rj0&GW_;T$H)kaV_`gRqS54?-9)E)wmG^w7v=W*pFiE(MzQ~dhc~*f$C)2K zdyY_1QDL2<-^`77#J_v@Za&6r-xYL8TU!AqQ+Bhinwnp3D+6x5Ta9&f({zeF8#NA% zz2;70^0)YGw1&8hm>5dWojbRN)HdDu2U5E-9(&C${V9EPPV3KwPfkAAEF?8q?@MqY z3SYk5`Df{vYwC{@U9~NaVNukH1D9l}tRMK~W+9k-^>3`=46z z7GRM)d-kj`dy`mjdb+iZO$HVHRYhHO;#b!=YH4W+2naATZpkkJ^eieW3Tg=T^YaS{ z2}x7Cci@~)(+lb~FzH@p@bimLRaF)7S3RwydiU?&|Dx`NLF-RRzRv#uElEiJR|wQG z98lAre1kB_0%IuY+G(8@ra)7D)^%EeWlJwh}zG}_u--0E2j zr>yk*zdcX*g67`Y*;!K3kFId>!Ubbh1uojQ=H{zPN)4RVWo6B21{Lp=;)VQsgpxGL zW8|W{oD2&&89#&&@q7*0(8$Qh@Ngq%eNBxjwgw;*f!SF3T#l8Ml_xo4>pOP!PxbXS zZCPfcbD!_;T$~vZ)cxYUb1%>3sI^-OJnB)>hH@z|T)kMuymyu;vXa zr2ObIU6&oRK4DX|jvRbSvD|rbQFG|m8XS+NU%(a-G;OX)(NI)W1UlJ5GxD{(e4+au zh1vn___uEx#Ks2OvUw@rzI&H;&c$W9BhRj>zFxX+`&;z4U5vsu162`zeX;=0W_v6| zBn#)hxvB`SFOeEiw2t%})vhZ`4pQ4{*-=oK9VdU@H`Td$a~PYoprD|xuFh>~vXp#B z;?A>$a}4W9B$p;t6%cJ~c=#xz%@Hb}jT<*ACus~#Oic9jC@LsSwmuN8CRXQ)5MDt( zKI|PED=XZ;w5+TT)&t~}=hgr$w%9j--v(}8UJ7z@TWf1{@Amq)iY!BRRbQmxM$Y_&{^>d-RSiM-H7j_1v57awM0!efP%= zmoHzov$MN%dN(a?<6T3MlhYzIWv&*SqQo_9Lr0*dp=l^T|G?MRr>wm_e83@9Rn(xI z8eqtW@OjGF(2$M(Mcn)U7D)14Y80=YqVoAtT+Ava7QrnnEId3kwEg~>$N@l}*qE3r zSFhseSC$uj$|@@2Q;*)ie;)+TbX42OXz>2dz3m+x73JlxA~ z1q5hGH$P&M2sW2jRUI53uX0`d^M_IarKKpik_cYC82$x&4m4)pbG7hto$z>id%0}* z-2f3&MXO(t2`X=Onv>;AvGZ85_4|@&flUEH|yDlf~kA}O|&&NHRz-u?ZH&mI~TogXxN_i_rI&S}~B^2G}sQ`3CY zmNb0!SM%Kr3|w*)EAu^%It!f88{X?wXbQ^`3bW4^+MO{AVAcH#luAB-K4UwH>oFtn zIHGQK>CY5_&#G72*yn-F%L#^t#eMFh%%zsNjHCXAk^jVRKaQ%ax6RMbzke@uHAQm4hvASg4^4X$rS%8nPzO3v`j>s_kpj}R`OQ9X< z`Xbl7s5ke;3jB{U04xWsh`v~H4T32tD17P<*Z-oM`tIG$)2H3<+_{sNw}@8wyFJpF)vTv}x=>JMZg7tLPKya~edV5< z<>L?+*Bat(YHUnn0*43+3QEiFFg7+uTRqI2s_Kk4W6_SaWk-oQ*)A{4a52ft$8NEI z9^gghQ~cAcEi32!`!d?9C<(Xjk9UY13lOlvw)N|awR*HOa!pmR5ga9dpPwJ@-I%Cu zhrWnA3VIQ`Y^@oSy>slqIvuKR3DMy-kOOt z$4iCT+xtHBuW2NSl8KQ8uk-VraTP`{@rsISuv4!LNUS~=b+B4pSw?3OG;8H^_}xpx zsjQK2pPH4WELn`BO7992Fgwc4O-4a+!*`@H(P?J50Vlj=V5s z8dL<+(9)*;j&fai+i8}2`k;{O();$KI9c3m({x~{u6zf@PuHGQ>HY>JkSUz{;JUnk zWmslTgd1XF1$Q=W1GtgTTKe|<LpxPb)&&j?Pj|UVit!ee>v{ zPlJQiu3f`|kqZ+*fs)hIEO>cVapOZFvx{@%bC*c}9&JTUQxl{GHz}zNV`R&nWZmoF z=D$QInx5F9%_LYywRe>?cdjc_gYSg=iA`l@rUNY+B8f_Z>L*KvQcKwo9ywWAy*3Rf zFP-@g45ck?ZBZh2JfH&=!QAo6iMr~xrb{(kfPC56*-Xc-yQk58L!(dC+eUeTdH^`K6MAT={HgVRz{4j-eiUZNjeLhUsmL1cJW)D}dureiL*hGgDJ2Ow7#8z?S4p;`ug1SD3-_EpOd= zhEt0#wrrPJnWVr?FOJ7=hXCN{m69te)-`&)+t)9&MiqDtb8?z|eZsnZ`*yR|yP;|w zWQq93>B6+>>1l6il!~mBABHtwjnU(Wh6?N_V`k)HV`I6mrLnNGp3~G66A@ViDLryI zQa|3XGL+wCVH)f;g=PvTE+pr;?ZDnFDvXfbHif*c%gp?D=yypK4v4)6LZ zDk~$zoU&B8#y`@&0>p}4-2#mcGK;ynx#_Q)=H}ygU#tluBjYx5hG!umrCIIZW+=|p z;itf_diwj>Tm;@+r#XolMS1uIeF`JRrBlDOh*In6xldC|P`R?5|5%+pTZATq zHo?u!ZDKM4aVmuj*AUvAfN67z{iN=;ZC+@F%s0>t<#y zC^Z25Sj2Mb>P}1ZleJAt-f!~rMTLZ#D=W>~-&-oiT?!K5;lxLBbC0#8-#BpKz>b>` z8_~3^7zA%rh5!7i3m6JU^p-+hC;6e8w9J{=d5cXW<Qq0MlT|gk18`2)HTE?5yW7s8Bd-k*pf_K#b zZNJaUE3}{d{QbKn7_M&WvjK--ok$To|JC%SrRC-Hv^4b9%LH7o%A5HK38Tw%T}N<{ zS?2WFSXkb~#~)#3{Ys0by>-i$)Y9*il$3HBx2O8$vPRgZbt*Df7L5Ngk0Z6^Tmkzb zrSaXtyB8J~hKGkuCqa@&&ny2!m%AVsSee|si=lQa3dxp@8y30exFwBc71OWzO^I6Q>c%7{a?dd|*4JMQ<>NSU;>x(1 zfbDdpV3+C7qXMS4L58f<6@#NQQ)>cm??>AV4X}(ze(A}>WTC~5{F*bI9{$g01@SeCvB^F21aW@(Z zKVgrsiSPxyO-vj^r{z>1zoi-5)$;U(xVZT9=g&(@O7IExBZ=A6UN2%|&JchsOeY7T z+}N0d)2I}ZwF=tX+x6`}Wua5<=QDcD_nA%L5}`U$jQ)hacYAl?{Lh`+wr#_S=a`@j z_V@M8{Q6PEmQ?%wyG~wUo7xS}xiyO;_u1lV;l&O6JmC|K#_G^dgf~b^bBH&;F)JQ3Rh8*05 z5)KbmUdJHG@r2$C`K0}2%E+{zX%lqN6xVD*4nE-TS2?@6!pYM~k zJkn%Bk6$$6MqosyDpb(pDlfH;CEww%m{^s;HwrrC3n zLz=hcB&~wsfq}VJhL`k%EX!31z@=C$76miGqpDpcpRbFcryFP&PESpV0LH&njC*}4 zVxiM%f(TS`W#cYK8K$P59qB5Rpe%6ybA|5K9{MB9PELy`RWkI4Vo+U7J91|V<~sEp zk_pTXzq;?B9QIX&0I9EhJrecsfLLx?+V7r65=7_i(IX0?1tE|nGmKtJ;4`j$r96Fe zo5Hw>|60wZs~>JLdG*Lnz^wJEni}1K168!}e&&W7;*$$k)DG^3r8JAbpgHa)-Hd9Q z=QNj;oh?ZDHa=ea{jDFROm6yn1aQW=Z{8ST?T7TL~ZdgY&2g6TEzI_u$9~ zuXrEubhi(d~R00I` zSBBlt)jfgk3Ak0by0Qq;VxpQIR;g9zu%LMppa7&bjg_qE8BPp`+2XI9ti>HMKx7J2 z1J$rHQXuHZ$D2q>-cjZ1{NN(eW`#F_gVk;egNlT8BxkYoyRU*k&BRnqi`4@!j-E{h zrUJ~3l(e+ry142mtg@n_qC8sp>|(TA;$M+72t~)lfSR?`YMDLBdRE}slh=Lh8m^}M z_=BGJZ{EDwvXj=v=Ah=LQp^7S{z{h%kD#_**X}TCNdl1G%cJq;sgrSI2V|f`iyfgx z(5Ufiu*YuJ6#&~yX=|^>Cx5_SGKy#Jt(T7IohFl}f|V{kl(~VRGVkBN7l?3~UYHp{ z^8?B-&C|?a%wN-oNHkS#ZHYGwigTySoaXGO)5X9GRpF-;uUsLzE4HK*!bdpUi(H zw1vJyxp{fk{eUDpwXG=$5v6{5XTE8jI^>_nZGw&9=U3^zVG~`O;b)KSsN7@wX>4TI zA+^e_Sbh}{27g8oZ)xTL4=*qEYu9kd7C=#8H)-iiV;y-Rft1wLuk4k~%o3B6EzHf6 zeim3W#Kpw?92v<2JuC8}NLEd~%b#X5RGX2(msN>QF;E#+Y8ejMImdbcR}n}k?PkN# ztk|@wv+1{F{!pL8(p4LxQJa64&RfE zUaRfurAt-l&_RsXoqX5bacHhV^Geg^x6HMjV)-kAI8^|mRD;DC5q3K@aY|tc~gK|7`PWMTmbVn;o8NYnHw$({a``+ z8f<#PtK?)}_2ygYwtwCH)(e2^qb=#hx|R?L$$qpq9uAP!(9l48ZtiZtpWyg1O#lSg zqQ`i7ADQJq96=e1JY|gzS^{dgU@HIVs3Gb)F3PXT$y4^fuA9W*!UU~kpZvJa2Rq7P z?s2$8456Ze4X0TMf|C%LWmi{MX{i)!^n`?m-sKe)_wL>6Xm3Zs7Q8)NPsI9uaIjCG z{s>UrG7COzH3uIS8y*T5G#iJ=RWRFNUTt$DBUR{Xy}hquV-3vADqG=~T$GhX?SJm& zwJpA_Imsci3+oN+09D6vZk*eN^`bd+PUpUIn#-4~ai{tQ21JB}_R-UaJbjw+6~kUSIyH55SprzoRTY&-4)gU%_&a+fB|IB69~H%&Y$-T46MV7#XTGC?tUB^+N!z@t_^HtSlE?y zb=KI1>RG11owWtMy}cKs#5HAQO|awOR~p`zvk(;!m__||TV3qBb`FXV9E^$s_oZ3V zGBa6YLU^?=LFj`zkDCt#6}^po*Dn1wc2!m)&%GtDj zs}+mBr6p%(tSnMJasa^dBy+CQ9FMv)E{DiOzM{H1Jw1IzRRgHy%2d>9xR7NGcq+_8 zs1`dMUx7}{?p6SJQ2$m{rIlesxo8GS_U6sM5=wQM+-5>M8UOLhr;xR8Y`u2FFlQJvhLL!c}ofxT`q>M-Ls61f%BQxR8~C z>c-CQtvT>7wBohRgLW!ohXN|w1HA&Ave%lb-L|}J?WLSqk~wYkGP84zA#R z45dmcDpR7`^75w%dyZVXs&W6m3N~?hdJ`c+Le?1@0V+XWo{0LcUAvyL-5%9Qt*e-F zwzn6egvfC5(@f?O2ehB3XuJ)IH|6)` z#krOkX%=Bfy%#QQUmc*SNRxtjieq~Fwk4s&AyV-8@$Zd|!A>!_8*Fl+)F`5M-1H&~ z{b3dYmNfKP!mzV*5^sO!qFmRJa`*YJHYVh5qc||ymYodCDKqmnGAW?Ns07HRjC33Z zBo7lS+K&iCMiSKJZ2y`5kr2&Zp-WhxEL8jn4b4eI)i)lEo?;0rgBzq^Nkbb6pr3w8 zdT(vJYg1E`cCO8kV3%_s&s;Qlox`{L#N89V% zSkY}LA#7PSgmIvwGynX2IWsVfY$ONB04}1Q7gf4h6}8*FY&`0KMC{B z<|H5Xg9p1ol(3c1%pn(3kdbw~w}iOfp|x|NqqNj(;13*INT3(Pg%oVlj`;Ic?_e=?IQyWxjj&fIDr#ew0Um zh2_h)Z==|DD67=I#TzzkSXb1c*90kFmLiUj(SbgOeAyvGPxy(RkE*ds)%hx?+m^sQUBjC6O7k!XJ=Zu>!V ztYsiPCTi;GA6`r)rKRcH_D)Vt`uqF;`0)dN8qxYkC4>kE)ExNV%J-=DVlE3A$;rtt zUcA7104hV4!UsrH3O)Euw$zZuThjp6H=@<)PL^59EL0#CFuMQA!2HHSy#*{f8R+Rn z;E*E>VPb4dw(r<=z#xE)9q-WP_9&Q{ndx2rmMhj`&B;AIGvoL0q4UD@+tF^F^IWv) z?Tff^w6wGkpixB4MyJa&9cPA7a*10e?Io+MjEu(_9bH{rM@Qkj*(ZU4z|O{4aT65%L3oB!y&A;nTf6_Z{z{K?tshM^rgh7tg~|kCsupjdk|L! zYr|!tU{1A@Ge8>hLUdxH3$k?s)lqK0zaBxqOG-#sf@=jnFxb;`mH;B@m-xhSW$6^9 zTqxgLpAKrM+t7u8rB(s*fL~Bdv434%U6U&xw*^^gSrD_CBnAf!zG(&GXpmV+yc{Lo z*w_dh24xAzQyd^nwi7)(%ee0KWr<}QsMEmKKO9zo%BK1&_mFOeW(19zlZ#7#Z0Uct zJZ$YCml0R-TrgHZn4pDWA#4$l#&W~+hhl)V01k$6`Wx?j?COeYFf(mJ{AAB!)%&Oa z;&#+d>;WHE6ZVq{8}>+Vsu#(=qWJpa9~4`C+2#y(gvxLQKvb)N0%c`oTi%s8zB&#R zsiXK^#$|b?Y2%&iKyR=L#BO!FpFa4P_R-tcz}vv3l>ndv-cnI%f^3wkn(wk`o7D$B z8#V3~g+M`mKG02Q=R8WP54*T{Bsb77%D%uiPc$)VN=h9Ag9hM5R@q>CdwV!N3XWVm zlrJy}-8y~l94pkv@jr(gtQ*>41mi;Dy--f+#hvGgVN!5k&)XumX6gJbev_`7FDCzD zz6@Q*%H=(J^!M~M3$qUaz88X)2E4qe!7={iW2=P!5sBLVlAIMal&@I{q1#n0lKU$!vzMd zk&c7WW@}|3197SI1S|loLpb@O)vFLkQ2r!rM^&Y~8VxjZtorIMOC+M|a&n>oY87Or zSv;^M*o?vLg+f~g)~)R_qlEe$)ai=9NK2QY6lP{*P@4uvL|_fw+_dQ)1oaRXnX9S5 z#*k&7K7A_cI4$k)Nr2+76Qcp92YJ{Y@CqUojLCKPXAG)ehe)gf4=Pe;$=5HbZI z1M+HBoj>&VP;zU3aK;{ln#{P@Fl@0xdxeUDA>VD)6<$PN7Ge$7SX>A{9KA%Tek{SR z{*A)uxh_awnW7&W|7Kw#NIPXmkLH#;`P7X4DEOOLHd6l|Q_O#}dQ~7uK0WXK{WoeI z1`zW}?Cb6Q^5qMAs^s9Q_4LNOc7cF(Ta_997cMehjEtoDkpTe#X|1sDKYhun(M7zZ z+-&!HnI>_s+Nr(6*v+2zzl%)&S9F_%9qezLN)DOfQ%W8~C>CxT4DRBu5>6eU<>};UH^a_zCj)2+&bS_)yB}qULBPF9+XcOfe?(9ovCEaE954YXuR6uQa{@*=nI=u1US4)K)Fg>N`}#7i2QCf8 zsBQBkhEhms8v6SCFM)d@s*hcDdgoYs&PhQ*m8`!*6Q|=5QmO}yd-;;Y=mpYej;zlL zwi4g@f4pA?C3o!Dfzl4a`)R{R0J|p=H7ktt_ z%O@B}eo$6cZiG0RJ5uORv|HT;1Q8;?abo}#4#wyy#HO(yI>aBH71_LAx7{P-k+MJl z68dVcHGa?klD+->v*UW+?1vBcq*sflr=(!c0xw{#YWU+m(kxWSgakc(IygA!O^A8$ zV8<4@Yieq6$snmgI@xs5&4j`w4cEl9EEswmR$B<0sgg)s?JWSaSF3L2hoMtbv}w9KepSJUjL5M3?sX zAD=aWrmnGZ_SY{VoT1+BpbnKW?`_Ce$gZ1$xLO&fn6dYU2iZx4Ppd3(N)f=xMXP05 zsIdK}r)`wS2M6E8#T}vE0mcaP`n#<5vV7T&~JlXc%^03LP=;)8%zL^3g z!{L8S|1V+wqlU)D0NE8cVd^DEN1y)mCm^z@WH>ABQLAE+$v)y1%t%bKU){`&8seCc zG_P_g^hcRXA`kq|fj{`Za|FUKQC?bK?9PVzdizKyHSCnI*kr;GLD1X&>@Gs=unB9` zxMX21>iR-aVP-}W4yMZ5D1iB&e~bdq?Oe;VUo}O-h(LYd01vG%JUwpe9W)GA3%F8s zIt-_!q)hC|^wu(5B<=@>LGQr4W~@B1K=yq94{R5{pc=EV+lnZqAH%6Dt#=fvdswqt>uwjr-PTbdDn-CwJ^UPKZLopFuyU^E6*`b(krPDz~JvR!IW!l&Bgu& z2K^*ikY%no+id(dQZV@9AA~lml+ECRv@~o2U6_qCBc;lJ!(FDzipjCb$(ISxR=Fa; z(vq@e)YbD+AJbjg{Yn?;_VR@b&rcYXL!U9fal@{+EZ{ZM)uPfWxdikGS}UYjOp&i@&-73Y!Cq;0!p%BM?cC3v06D?p3b; zOk5vO$SfRFH1FsiFR)hl{Y)9Q#cCr>SF2q+*T0(iGIg#!uR z=;(t2ckA2QL|9qx-%mw`8&a;YhzKt<3#8bQ%^|i?rK?w=gJFDbhC+P)BV>Nm_@*X1 zB=$piG$Ex0(m#P@3yeFQiA?%D&Ri5G`1|J8R^emEZo$AJCnxW6T}(vx(;K*fQvebt zj#o68#q*f%W#FCQ;KprDgAnBI9rN;~i11^9fS{mE{jXFxf!zhRW`aeQ8dtAUkC?k3 zh;>>Gx)&M!gFm6s@)H_@joEq<*L+m_H4QGC`)sMDhF*LvOjRM(gWt3;G<8r(OX~mw z!)Sj$EbRC2FL90#YbHJ{ngd*;P(f6l-iwQXZ5bIN{hujMl;kF*0tM=s$Gs>3w4Ct0pVAT$L+w0Q7Eo zX{p)obB1vpE)}Q>$lduz2Q+a|`}V!p|M~>t7cNl+L_L>!-yR`r>nUXU)6WB-Vf7s3 zR!C7EABU**2Ku{!!3?CCm>6dNo=%_#`RMT2#KTWXdDIt@Ut#I7**swZMZgu&<&b34;3~uwo zJ38&(W-EUhCqE$CoNb}a%gc-Xihd~Y`~aG5-r5MxX~NaDKxwh~VYUWa>uUDq4v=X$ zf3Gh-{|%Pf*3v>8%<|L4jefjy{iFicE<+#ko3X6x=};%LVjvIN1f`U`$B%a-whJX8 z5{XJK+HIRR^UdtTo1bo2qaO#EL;pJ?or9PgV`h#VXnCw4F!V;61@a{m+IBK(@Cx;-c6>8JMaBFeoW5 zu8a_wL#_yG4ENUUckgyez=XtW>HQEO{`~nvRKuCD8j!yPA}smx1vYjT$S<}E;b}-n zHl!eonMqHo<>VcXL2Amx#02B85D?(2U_r#4y}XMgUked2Ow1uKSaSxO0*ow;b)E`< zFoXLWW!Kqm*?l#7n4TcvV79pp9FVqp+3&{sz(eAd33B>q6XSUb=l@#-`OnGNT=(^f z1IHcCM3UF4VQ$VNeu|9bm)ah-iz(UJ=nQ$LI;gp-s?AVN0s{l#kJWM73SfycU?Nf& zI|i#7X8SVc&=^YPo}Y+?sZHlcBooVfrt>BBqHL&hYwJ$3(k;{`4OS_{rku>8Y(^IJaXg)6#B#m zX*3t*R0DQ^LZzm@UpKw$zaFBB|+ffir3KcZgOi-~N01gBg7T17eKu z(4iOiCo@As)ih_nW6v-H1sV84@xl%~aMJJ_{On-^A7ok(qZ|a|lUNu$Oha?Z;#-4L z32CC&U%NWD5Y8gt2GSMzFE<%K_3_zG*&yqQC>q{5G&Y9p$O^(1w(>?|e@B?z$=Uwl z{KY={j+xbUS8#cG*~Qrz^-W7X;D22%Q*xoY!63t0>?!dJ6TW@4?IFf=|D8ivxqRV& zFbKO@SdJXo^BwNI!A=Zebwer{ALrRk3QJ3^hAU$;1!`+>cm(k!BD}-MdTlV_y8?*~ ziNSIZCl3!Bv$eH#&Z83hG|!LfOdFhd0^c}Go43qdXIcobw}6xLgIcD%5f_r;C= zAC4ykG-aFzimhIsDi@A~)v>Yyc@Z;k8JHWN-E8s2n-Xm}IkC(#9J0!tI~yoreF9{I z)}Mt$mEGlPqRM&v_#|o+R9eI8?ZgeMoC0@H}BH7ieJL0 zF)^TcJ{-Ct;+!+_TB1G!|HuL)f6#q?Q&H4;ULL%)tjq{I4%v&HN4I-7 zf~%M#Ua8QZ!;FK4QR~M-MLb?CdP+SO_6 z|35B~GavQ^2LxCmzle0MEk)eC=PnXqh6-miG#?|3GD1S0otfe1m*1G5}+34i0~S?h({GZd}WPXC&anqJ%8ZnA5Cb^ytx1Q|8~ZvzIV_S3b#h30YRXp?iK{P7{{sBTw3wT;{yoE$Kjo8qF{guv5j^KNp$%(1a}&A+`top7 zau;;NzAB3sPXp_o&cBR_QMKDN%|14zk|YM2mH*~VSzTSAH{#H)t}7@lurDAnsMVPk z6&?IT0VX?B?!1*b;?hF(bPEv~+HBBG6+(Iw?4+|ZG6>>}ySqE0NehdMi}UlCo6gT- zuAb_0nd<;(AR{9~5Hun@956p2F|lPn_Fp0Y|93s?*Z$)qMg{+vESX*|A+BwN1yQ>b zEks2`h!Z);sC-4;buj^`!Os}qa6#&PME%|>@d*$atZPqDz!)2HRu|V*8$4xzL-92R zjAU4-A_t(>v@AJ2;FMu`0_kivap2>}kE5fb7zksAxoMy-(>CCs#T)?Y4LL6z>tQ{F zQZ}^&s|42x_YD2I&`p98VJm*+*p!rG$Bze8a_*s{bNumni=EvZ#4AtB@^MWyT|+}< zb#?WtS8cGQF@1n3nbGj`Vj6VFgCc3@Qy?C4uj)x5QB_09DF8?c?k(z zF{{;`5!=pWuC8u!dlVqEQ}NJIgXHg*4ECD@Q?<(H4hF~MhFoARA7i63->LRJi6$ub zO3M?2hg>fRv)orMuq~eGpDX>-`RC6;1)<=kp5*@U=W`BK&%Wh{Pq`LQ-uu+u-#;EF ziUN)khX(+tr)Qym;yHnt80LqH3^ftvI~EXJ31E#5iB}R-5SLI_P@@V)xlvC}96t^h zeit86nEb^V?L-D*`C^-h?>;)Z)`7$6^nC$ed(on2`;;+Tnk6b0_!Toq($dlxMX{Hc ze_T)?ro7E|>T%S^;jCDZz^_Yy2f*%a7JSE!Jv(9W072p`Zvm3)Mt|lfUltY?;*l`g zWU3(JdmiZ?+vh?PeZ-YV-)+~%)_X_6Qa;wWD~aQDNMFF8qxAWLCF14fg&AB?w-p9; zf!qoKyD=v3-MV&TCWYBKIr|T>u(PXzyGBO(?}pY8`1tWH46+Lf7DAEIc3aK_utE6e zpa;?SV_-lSU=cKJetsTxeDp_$=w_3Z&UmdcEK|}KI7gm2M17qgAT2E|jS2c65K}|` z{nPbu;oI*9wrwQYwH3$)3I&SA(#+dp%=cj)2)zl;-}s-Gh|fVOf;-8X)r zy5F|pyv4Sm-*@hj-8qePOf!bz5u>?(e+zIH_7as3h$uJ}Fr3;4=N#y7j7!|Mvidv@4Sg9V&4id#eNyQVag^Gu)kCbjvr`=7R?A|*rwEuxAoGJ{ zdPdhu_oq#$v>r9UDIiYGZ7)hWNS5CvJBI7J(G1s&%n#HzHR<`~IntAai7$_AaEwto zgr^_j#?J1!=R9#I0%HGkule$ZL#Q?-AAAzq!9hA##4oa0H@s8A4&b7dRaM=02=njm zV|c=mdb*(>CW1k7y#iA2+q&(Go^pEjad`GFEv{!YJwoy88hsT)i!c`Omx_v_O9|Lb zNFL(FdgKTKAX_(Y?qLdgm#bJ7>sW>^z%-mufthe2Y}-XDDWpiNzTzsfft zDdZUoB__9TzKfj@5THe0gNqBIysw|% za*(8}+CY?3$i!wkKQ=76g9mF?IMP+QkPX~=&vK|XwyLh%Y5u2;E5+G4Ivl-NEd*^< z)p;n=P)6~>BpF&zLeUY36`;x<9$#8BO;XF{uUvT&60+B+`xqxZegV^Z8cIs{J9@jj zp8?TejPTjB=41+g&1bCxj<;h+l2P3;7c=jpoP-kba&Dpv4-`1@{9^t+8%(arDJY~C zl{7XgL;Bu$=N1$_b4Qoe&2NZ=%H~B*&M8=RK40Dk>xEmuR98k8v`cYdD zKjGjw1-ug#K6`94oPa|fNU}n=5;FX@kJc9xN5oj9__f#+ARL^bv@{VkKY8+`P)H5g z==BV0Zg^4)(>f~!h)abo1ZN6@=5>sOAgXhCU~I!_CgO19^z^stYPYF=GSEmbuhM*n zsoiIQN)dp8QiNbKUI-vf-`%ov_Zch!+17hXFN8X{r{C=GG;!EO{g;!Hc04?Y0v9KkZQx!tyzHwL8f^O)5VMi1P0 zd*_Zl{|0vJ<>3Kl3p!-8da-csBWN&&MFd&aUi|zGwY88Ati&2{8Q4<5vY zy8nTjlJc$tQHoMc0M$i^0?-XzmL9X@Wo4KTp<8^mo0=LsCnan~hw_^HgyzrkO44&%uNwg;j<+RAnh=gLwoUQ~Llw=P~6UhtmYElfu+F=I=!K*!d32H8=CzKyX z1_t72%F&|_B8Gqb{27XY089tqF!A3ov4j%fwtTB7L1uH;J>lEQ@aJTurIAo?N!9ZL z$(&sVXRmsGvJ}c_Q4xy&!jYA6l8@Kxn3Qxw_Vi-5@)}n#W{VM+U%QMr2^0b@+7Z-k z(iiX_XXWv{7*&UwO8I7&wR5LdzMA6p$H~!=*#luBB#XRvALZq}(KQ7CM|ue9&WUrw zqOO96^6>nC5?2F9e(A++;LM|RWV1YoKO!tdp6ooA{46U=;LhofD<_b=k@9#P90Y+O*NOHr+2Iv&T-$wPAb#?je8<8frhDnY#$;>pg8a{ zo4-JMWO}*U_@p?6S8~*leN;Sx*Ii%xQW}PjfwORcm(VOICrv3ZBt{Ai(>2Aa3-6aTV7xT+NP-ZYM3=R9Chf26C zcU=0jIPSd z?|T;8MzW8jU|0ip2fzg5n#4%s?0L4p3oKNqgTO+t%x>G*0DIgUL)M=_dVJr*s>0^H z{li|z@vy+%#LvfZoh8MH1626zWgY+$5BZP);So0d>Y1+xJ4X6xM8KNy%E@uS1K)5Q zkSMbD^(5z)iRtU;e0S)SyoScop1g%{d_UkM zo|1)ND^mKHa{$Uk{|fw?f9uEH4$h*Bc>dBEBP_Sg8zrTrwjJ`w?9)$GZO+L=2h}2*Ig8;C(ifn`x;MyEbDYQQbZ*q< zKCE}hFh4V^5w)w9b(W*XT%i&;a!IrXsk2a{*I9vH(~V6X*GQD{jYlQw#94 zGAX1pq*-Pg+%b-T)qEpW^&;C8o@Ikof>|37P0)p6HIeHWzUWw~>jI5j)I^5_*1?I0 zUA`+^DZ6wIot%P6+~(FToa4tT6;7~?)H5;Thv*hBCP;L=SI`RBlZmzJg@enessc~#rxNwSfAB~;3 z0`%rJiS%*d-n2!Pts;S^;39UC*RI@2SPg!5W7V9Ris}rhPT($BLjc5Ki0Ci-y7iV( zDkyx%{47wx)AKK(OOiX;Cu{Xq)vQ*@e$lOdcD%=FuJj40N=i1Qgbj!I5dXzUOIG0# z8$71UU1#UWDLkv;_UPgLd7<~=wp?Tj3k}W9$ytWMrdpW!=`BV47gQPX{oAC#M^t;} z#rLD^mp3=>TlCijlqQawia9m5w$horwZmxDs5#mhkY>`VrM0zQh+D_#yMzP{JlY9X z8AZ{wLr}9MmjCI~B(sGiPIrj|6IZW7SX{(Y`d|(>yLoMmc5?Zx&Vxr0m6z{W)T@Le z3-1jT0}V%xIRKkB%c2X3-y@@ufEjE7(g0H4K0c=ql<*8r#=Li2~c zKR4QQ=sxXE32`y^8kH0ranJipT6~&oI=!gbl>a* zgl$B*@g&jff(Z7jXluJcaK{r!ySv@RW}SFlf%!2IyjYlkXZVodDQ-7UW8A+W2E=EnBG^KgD$YCQ}ekn{xrl{Q0MN=nVQ zZwVFRR%a_fNAYr7o2LuJG4MTz+iB`O9gqQKi8T6DeJev%y zoqFws#7D|b7X2*7GgX1oQTUtX$jSW?HQ~f0laJ5xLN^+Cp}6#o z(XYV;TRMo3K-$*aeg+l(Xpqbcd1;m>ckh0Njt~?Tg*gOEsDc0*sBu7{(Cwg*V0ivf zFBRn>8-(isK%m(nsCm!>`G(^NHgCLxftUv_o1rR%`TCB4blC{IKzG3e(oqhMYneA` zczGuvH)E_l#S)y97;iJEpv6(c7X_@h5m!dc8IOx3-TWC(=Sbm(!v)a*<|*k5;549q zJOl6}2FwsM7ZZ#4s{a8G3jqlwem*ZR9y>ruOPho}ot%uZ%3iE?TYR}`%L!~h(>|Vy z$a4Y5!wwJOx&{Hx#8j_(8=hq5iqk|wDrMUh_NS-`S#Go_G{_~AzCbTQ@~q!}@Z^|ZBPjB1)!Y~=M(;(-Z2sm)w1NQ_CiqW;PdDTfrV9fX z{~ylYJgmpRYyZ6}k|duJLX#w!5<&`%Qi+NXp}`bF#v(&$9t;iUsbn6@P-sw+d58>U zNJT1BA(f=|>*Bee=lA>Vee7c&`}@cJJoo*jKA-D)zt=j~xz2U2FP}e4EGWSWA5h{l zYI-p>Rklr=*>TUQm%FH|ugAuKzyt=JH+QZ_#aohJav1iC-+%m={id~XcDJkU?X)kr zoJRct@cI0f>qn&O-}%&)D@7H}CCLR#HNTFnmDMm_*05L;Yy5%gvctQxGXJqlyR4m*G>UPK7y`{ldfZ zlUx0q7YtA(Th_mC^Yq*g<&6*dS@aN+k<62?yxST{Nd@wy)J!+*vrh7uWGvM%CO>Hr zMbq&@8mK5$BCHNM>hT?^el#?$S^p*c6!!5g;mX8uXNyGl0rOEF_Zv8H>D;-iAX*8~ zoH;RyVYIF$Nf7K6&yvz zi1>_}8ZLOkgs=Xcj8e}&1qTqt=;%NUB~MCM)r~8fJLEpk4e155s#7q{$j{%FIcnIj z=ZH2y0%OODiut$qu%YM@?8uZh8PmTxdaaR>MYxU5>Y{rURJt%i9*>TH{vH^oJN{$- zoy@&63xi#r`|P{d8!k*=f6L@*SeS6pY&?ozjpzUi)orn{oN_cN06|hsj!A#&B0Z6e zO7JB}ka8CoPUm|4t&e~<@w~Q}m~XYUfFzx`J3TWB>|3hxPmM<#Ll(Mqs{)=A2ByAV zBczizQVc@ce}VmaFVMb1hYrUkp4hLMmz#Tmugh`9T#WR%Gjsj_QUW|%K$Q65Fk{M; zVYn|-gS>sKY0}Zf*_lC=r&%SRPlpBd3~trBHMQDP4>QmEegC5cc*n0RzP9mg^AXGp zCQqEWaN|LM-}mp!>&|nqBcD>_K3*qts;jc1qtt{_Jys5CTRWH+oSY_N$mGq`;nk%jay{nGEf>es+nCE?{hgkk zj!e55ob{KQh4|OdI6yRH=&PE9Ot_{ADQd!#J%X?uvOq^(<)2=N7fG}PFU-$>9K4S! zOC$z=|1pz8xtsy2;@DXy&18__%9UvxnRli>M}*4WJaYy;VJVl9;;x+)423q+Q@wZN zszO5fJ=-ULYt1f=w$x{4)T#sv%9fNBK~y_Pd*7lEglgpiny-&{?O{67i#H=6NwM|k3?ZxxN^jtDUfz;Cv3y2QO-JV1NhxIdF4?2cf*gcsH9;n=x#>)zde zv>&=p3q39d5bXYaaP31VUIH?3g-SQAT3vcAjYzp3 zDiTS)W)N5hCA$%=Q#vo1%iv61dcD;gt2PqDSGec$s;Z)MV;>h3bd*RgW8u-E+Ez<~ z)mDmTee%?4(`=6w5Gdd@?d;_1(xpvujw7T0y04XW_=d&-FXG(hQjmgWW)AbYS#GvI zwv9r@!)lMqjLH}s8npX9MYG3T(aM9zgc1-xSX#XmA3k8&K6qJ~RmPA_UmNh=`t%9+ z+p$|{M?2}MVAuobsw?PGxpFOE4O^VX1x<;In2e^g!#{F~U@4+!cz8IcmUpzZ`*480 z#wk_mWrYjz<}GVlnllmCU7HpA@{~+_7Yzd;!LF4MZ3JJ5hO2M{A^SGKtZhf#{@`+u zfQtMWX53xZc2}6kqiUe539SfGYJASyY0{)130$j2@v)T@gqU|NojrR6Er+_9uD|WL z+fZj#R;`-O9QH>jIM+7|?ut*wKMnK&@w_KvlG)L;@G3*sx6!Kk1Q~{84C`r<6|aO; zQJ7Akq3sOWHj~0)`t-G^oO!1dvjc_ zsXwi^wsVsb+!Gin#2l9_h#xWLp|U+7-7g~VAO_>DxiP3OXz^S7gxA)BO*2c=h`AXI#flg?xDIb8u&2Ki&WlnVdymNbJa{sfm07 zZGg?zixw}=Gf{{A{_eWv&Kz3b>kzCkp4`7PIIP2`Yaw5@6BRpknw#PRJcQcE3OpyZ zXE2Oq^2W4Fmv-;jBf{0;;7l|@CM^TMzL2?e4L1Dxbo<$^yX+xG6)nPd?u4JXc=&KD zu}KE*nlf|d{vA6)C89mrvlUY7j{uNPS_#?7d2ghp?0K4Pv1_wUn6 z;QdAUUR645deX{3&U|~rD3@2=3F(x7J%Vp;)jW6V)F(`oot=Yy4fBULzE69NtVqd% zllOT5Q2BG&gTJEnofuKXzqUEVsNOs>Vb$S*Ay@@wyMIFBg?gTmy~~RT3J~wH-xBkt>z%Z-o$Q*)qJy(m-f5 zutP&{GG6p@V>9iqE!1ZN5FqsLPbj5kpnk?X_rs@8khJu1Gd%MmQ7^2T)znd;gZka; ziipjWEEHsu?ya6d5kLk8T;iiZj;dN744kCTMANwLSr`BE!a&pfbE*NB0bg^5_ZG4p zC)00X9(n0fRFM5Rw{Ty83mhhxZiD)~8}px@U*<@R`EyiKDBuW(hiN~lo6+?1!;NEP>>Ep9+HG)G7@pqY}t+&ymLKg{q2qv-9T%uUhq!{wNTS z;-~%lz?W~^|9Pp#p?y7ov zdf8|1F?YTOYbfp^wKS3@aCynzf)a{Ot78Qa7QcV$`ShAt|JOe58DiSs)e=VbMW*J- z{gM|RLsVhkvH#p5=XciUqXO;ClQ zYNS68^M>arK2N13CCQ92fR;vaW0BD?KxNE~mBsOk7oXm}+gDeYf@B<~@_Bh7TemLN z>HujmwC)YZ9i_v^4C(@)8(Y>_JXGgo z1#iP>8s3(5F;8+Uq#La&>J6MDgaGrCiHU)for^W#tCCNI0>Eo}Ex64fIsbgV_U+Zn zmrUq9D=f50bZo5F%lr>2Yz2&4M+SpbFfXL!YC8y!x#=i-s(nvIP=`%Tdo z&jO`9g*N{?Sr7=GWWD*iiMrYGY64b`G!WK3dG>Gt|Aqy05`X`e^k3cKDo+WM)NRlR zpPmn(L6PyU=^zwb`|iUOQXO2l>ws17SE&qiuGqKFpnOgxe|K|K6fUax)%uWrIe*{; zI+VwVgEWOQ3O0NFr;;440~Fz8CKXhP2UcTLUwwTPmUsnxnopLo6=>hIaTn`scJ zb+%k^kLBg$O4K_F25M%XOa);y<4gcWx|=CiLWm5UYx3city}Toq%uuSP7Y93TG>-y ze}cKW2c}5~8636iwjo~LY<=(k{lkuWuquAHq)Z(sLhO!+LBiP?Nb(nXwmIE30-I+x zmlR7$36|Z(7c9lQQ&mzjKQfUtJ9o|;5)rU~8m^wC3QpCaItGla-4>up$5;M&7PxF7 z3NF5~U1$9nxIJd)H_)3z5-V}ySub3WTd4Duv9?b9?FHvc8)5iJ+x#= z7Ush2@p5T?-`jrgFx>8^g9;{1BDe14Yp)O5yVn&!!NwRe0lZ#(Ow1LUJcLZqS1xHb zs)WDaz3qKO!L>c>?(gQOc8bFhv9o6%-nj9eY$blWAwzOa9!iFB zU~$(0>*8+2gF+RkEY~W-GB9fpY6e2ks8N{!@`9>bWR=`WXl(Zf*~nL^Z%S{q7b-Qx z$1S>Y?AWs>vQrpPqwr)tN`mQ(NUyvnTsxGJF~iO68qIKm!I&|vtBou*Vr*8fSkbXd z7o_r2+1fxk0dR`))yv-*ZlxV!fx{KX=!YJA(YBS7mp8^=B|Q8#!*vvk)D&BNt%Yo@ z)>H9{ZnP#x`S>IsIZ{sEr!r%73jQYLp*%fY@UElwCbyUa2lRXQCcmbke$Z3;ozGar zfst#JH8jwgHPhDR{cb=g$4leOMny&(X0Lg9h3j0eQK20VV-@?&<;ip(2Jm~SMAJ%90{ zO`A5b#gb|xcP-*^!>?Z`^r_Ca-I>GJL3RS%$XLZLEC@35*sNw}rVHd*Dbh)-|OV!&e3@@BsLoySE&_frh6favl=l0|XxkO|h zr>QRiIn6exFHlG&hx)Pz@oAtQ3kK-;@|IY87Zk&$(^=vVP6ZDyNvN` z)Ab?moqvjkCj!)KEdb+F)EHG34U9M%91wER(IJ7&`LADl<5i1hA0aM`V8**zCaRT{ zETFQOETl{}UiC5l(xsur%d!I~i~$nu2)y$03%$Iy#l`UinDKoF;rnfitiV&Xkh^4B z(7Lg^*q~~1p;g8cmX?uu_wF515&*ksS&3d833^6Spa+nBAq6-wggZnG=1way(FuuZA^8%b+Mc^p>7#PxaCONoi-D9WHUsYjjrPR8mwKi`MHfl@Koq^ICsVuoZX z+m|YqH|nGpPh*gKPl+=cAGbq?uG6M>aOhxXmA?_bg9yQYQd=ijskuA%{Yl)R4ef%a zR1}Xa%Qowd%Jjg2SQ< z7RY=!3H4EHMQB~UQV3{n&2K1aQR&jfS6{JczM|LyQk1g}2JSO?0h!E6S8JSK;#G8h zE-@S40EQ)}(K<%I4;R0ew=t29(N^?yRrExzM4XHmwNQ z&DO<7yN6T|em)Ngb5z?Tl{;s!xb2~Te~R4|^s^|sj@2KR|(j_oI?}!X6ck!S z@n=Ctbb>HyxtNwl48CQeE+j0?zGWcvY!`P2D#>8T5Edo86g__NnMSe7GKyb~jT~$X zQ_BEm_Xp$D0=R_frJGB*X@OM3$v3Q#VU9o zpuJD8zCx}HVK!QY=*rAC%YkL;r(LyCg2}a(=HGy1fFf{s*0gC6 zzG?=Q(cOmHF?qy@0uh=+JbuE29dU84uu@yMI$&N5ts-QKXFYe)s#TZRpK$c(2ioP$ zzbZ7DW`IM0J@GEG%qWlwkXTSIbsy%9#GLbpeN2vtVnYgaN`co!o-;+(>(?`Q!sZJh zB1lSkQw!RK1PDLLwA{y?i&o5Ij9R3I~AR_?c6u za!tAkC2im}+>P8*T&R)=e-IE1d@Kgi6ONd=QxYNF-T%+1!@dok3{Z?)+UgF=-GNBpi+I2Utq8&G5`gAJ)Zq;5wK=r1+N&$&q z(30@H$lp_^OgZ}4%en7R=4vsx#5sYslix9!ge0C<>((LMQ{(-VFHyc~HE~2iHq*@E zelU)MF9q+=(EV6v;tB~*qf3`G^5W;Go+2UkSP;~~JlWfV2Y6xQVo0^m@1$!%+vAi%ue$dT(HV<5&46;x3pfN8GL$)FT_&f!AKas4_y^iI`Q?p`I>n&6TFG1j32 z+dTfj@}x`>Z4>guN7;h&xT3}Ts$gah?iyY>CeAAwitUxKpI+7 zCLy_P#GI6#5_2->0YsVyW)JBHZxao$_#92_bqk<=o;+F3UxrVkz1qJx!pv=Y+LD=n zX}L)10pJgBPtsFIr}l;0Mn*)NXpo@y9XmG6UKh~}qJwwp?E{qB6FX(oqoWnPL&`WR z4|Wae2^~T=9>0C+UO-04Eusrfa7YKmQiqCEIxqlWUQg<&TxlGXgH{iQnA@Sc77`Ph zjShi$1ukB!`}&58+v>YkI2y%u-ad%JP<}p!C)xVm4Cb5UVvT(tIWj@(0 zGYkyri_fyP6@{>XWxT<7i+}K^9SOHYNPVc!?Ndc{Y714!?Iw@VTn6EK08oRK7z-wqypilZY+Z~9&?sMCv=nO)m! zxL~4-%PwvKG8aHF87|a-4(#c<^&Eamy*?_DRLD$_7}`p^XN&L6{qd70W!2RbW7@rX zb&^PuIVPYcbo!sFs&dAxb-%p^FHSxzL2kwjwc0o8BWQxsqw!*+9MUXIvp6=1)LHlF z22D7IL?rIc9B5?n3TUa6gaqH54|4 z;t@zP2@K33K+>Jy8_24}*wlXe#z8^{*{1&yxyE1;IpiNrW4s>8l*h*J1;*~!y*t8J zUkDun`pceVXBWx8L{LTY1&!ec!&8}Z+C^$eha3rn80?+6ptht$6gM-fR=QuVB$wf5 z8u(CpKtrGHT9q9^5L|FrNB$EGNTq>HGYShXlP*GT$8{ZB3vCFGyb_cWvD>%9sUa2f zhDk+t5M+ZWu3z`$1`^o`+Q}b!sLG;|g{WW=Af$cr&2a)5x{*m7 z!*1i*f^+`7GO4-&k(kScd33Gqc`}zTHxL!cTd?}DSfsOT1F>%;6FlJ; zcsO|#7A|9jl)-xrfLV*!PgGPq>ZnH-ikC}?sjsJJmJ_V{oJV^}=j3;z_xqFjFH>ODu$2B+oaDWC!XH-U5na{1KdC9}&6eAw#*r^i~ z__~(nQN>pT)xqT0gryMTV#dFHn{izXPI@5?KuJRJ?^hw6P@Y^KFC z<^kqXxY`{-`J2%(uvGNxb~)34oh&^;I<;uebJmOz)=CqPH?)1e&-$-ww+ey>76q_H zq4JIyWD?V#GTLNdehya9>Z2vcoK8IDPl+1@pb3`36cUI(AXb1r-;XyUOaloXUO-%t z$@k*pxjTxf?I31Ubgahe#_@`C@#8*LkWvuQN?MAfUb_=!oxH>K@R~2bAqH}in?$Pv z25}r!K0mCxafDcmfnG{)TNLD44}LU*_xErE}+EwAUmh2=~>- zO5%$c$doB47`c#z$)x<=5K3J;bsBT501KFJUtSI8=PLyg49V4`B4pYI253=V7Z+zS zcPt)KWATu-L*_YZ`TN@dI9g)!mModYxF6gjy$)_m{x!7mI6Nc3r>k&w#uaJKtJ^dt zN*UvZ7-i^{3ZMI6Eq{6n!fkQH4s-|m!xIM(MG*#Nl?a2CF_^ERlSS(Yo!??5R2qFqu9R+}w;U1C>@xw>dvQgJi@lz3ia!9+;M%qcz)o2v;Z8n7N=>` z7C`aq>wm4TMwl8yGXglXnP!!ET#lm@9y;{Wn5fRUY){xbtUb@~u`XHtgCp5}uh+vcE3_v;i%Hvk!9g4m+Eq)Nlb|{T^k!-k?E@nZ?>25d%9v+~@BP ze{OoCu|RF4pX5}Bza$lps$1M0ijDIrDI<`9Gm-!>D2~XsvC>h-*`pT2C#>tSxNgh$ zu_CELV2WKZ0vh~1dnps!_Eqh{&+a)Ls+4ZJC`r{c#VBj4i;LI%`E7o5$hnQ`0hZ({ zr39M1?o%>^25pHPq!>GeS+-%ctV~QJ;lo%oNx_I#02dp+yFaWnFE)pl@kf!im&K*j zRKhCY3}Ex6IFp(YSSLfk5o2=$>H@Z99eM0U(M@%D!$eV#>oEtzF0c!53_zN3As}Xp zmG*vYIk1PN;mL>^k|~Dg|Nag+eupw;c>n1aZ*v_T9C#EJ2pAFZiU&d_Fwb*UF}z}* zfdgA0`QYE=Lk8EJ5E>4cwls;87?hH);Q$$j;p^D+*44%Iv;j9`o-VT6i6@?d`%Bg3 z12&MtRi|+R;9r2SVBomjyQ|*48?x%7v-qma5WPVKars{^J^a`U)*VO$cQ$dN@;Pr| z-PVYRI{*;0y=2grPo8Y@RTH-1o(3f?iiQBtTqEFKLq}QF4dR>0LzK>-EL0-*Ar5pZ zk!T^M@YNG!<5_o1iAGaFgd_u~E#L|Gh4iPG1!%Y_Q{oRUKM297rk24g{2Q2s00>s9 zE}ZN6vEnjMCOjTjt>m-k?@ZXJuG$RH5tlSUnNiN6RiZl7uSR{;012WSHzNBRvJCekfBsCOhS=FD`jXIl%8g82kMQ1Yy?SH*8fgr{%^Qj2;b;D zc_uKrjoGy_s!q29w>(q$CLOe6@a=-ZV7bzvlP8JM{&-bT{*>(P7@z^8xHU3Tv{-!n zn7kFZVYd4=hTL*E*u+{|)dxTX`^m&NZnUa)6q@>?Nlix=Q zZ`(zS`dNgx&Q30=uHKq?WDxg*6g+z=tq>NT9E5;@A#VJ6t%tCF5z=Pz4fmfwgXG?QO1S|d{5y0$43i3ESPeiKmSGP zIXvnHP#WYvd$xGt!t4E11yc`LDQxR^u6$4qR(jJJNsqAss$OL~B@wVVQOGW$4?|Kd@tumt;AVq(pFN6A(0rU!t8G5+dw$LPTjd9j$=@NqRGKjjX-dD zp^U-^@iz#2lg{H|Oxz~w^xb|pN2+?UAhEy<$?{8Yz4ObKFV_xco@f=Ikf{+xKYd}t zGfEZUCpZe1XA5aGlYJ5uyhc)Wu5U zH;*1Ih0gH{lXg#L)k_8=dSMZRK*~S}h&fmwP!Zkoz=(ABaOKXOw?sxF9HPZcS(wXP zQK!%+OacV3W`~=g_{_B*W8h4R4^2LNScfBqO9AF2U|6Kx3e{GEf<8#uN?Ymo@YUaO zN*C>~!63xe$BgC9RnBo7j@pmjGk}msK~*U}IDO<(rQ!36xfE(7vxB=K z%Xtn&BFjWasB%t$Sr?Sn498=AhAKz2SpW1XN<7a35xJI)^q$;7;zDt8F>9D8as7Hr zn4|<5{`6NnDSEbXnAdtISPRt-Kl% zl1EjCYrK1(E(W`DUbHrd=RAHc&U8hVVXu1)7jpVrKAEq#X=!O0Ksz7whyGvS)_$n8 zxK=$QrWf-7Tbjo~6k zn85w<%a=cuk9#7N0jM7kn{RkM)pYDYFYWck1(Oac3>{1XsXFsfgnsu0S>W786pm&n01PDMw<`&I3Tuu``nGU06_yt&oFpE$sx;~~<&$rhdqNU|d zSo+OJ-P+N z1ul+wysLpAFo^UKOikUat#w(X&FQ|FEA1}%H=#O|1GBdOhcVQkQB&yb)A7yoWEL3Cl|XWg z$M`?fti4Z*0 zpIpD@s)9k2!n^t(+k|tbUpb8YuPDIa9)f&33mZzP-RK6ec=9QXPR+x?HeE^b-MW?S z_nZ^>$iTn`#}W21#+YfjxVj=`YM-qX_ATuyT`BEg5_ys9S+{FjB%*stiVIqzzs;5zj4a%vUSBUuNcjDEJ6W_eA8k}7-B>L?mOX-Z}Aa>yF-?OKirY7%4 zws@M*!0J3)CWdRrG%%LM9B8Tf{QSVa%Sv)O9dkDjd;EmocN8m!XwzoEN7PI(y!hD5 zFgTx@rO-7wY8X8>C5yr*a$mZKc^-R{JjsXhZj2nOm^{K3V?j)*SniyvT^dCv0L!1vYDAbbAfbvpHAO$u;oeIRI@e1H4XenITbmuGPk+gkNP2bW?%nGqB{nx6 zJWy9w9t=B%n6E)8AZID_QRrc~a5NrpR|Z(CJ{H4*4fGjT4{aquA76<%=oewe7s;sPZ_qCM6oNQJIY!UO8B%I6Ze-DehGZNqC z`rHlG8J_?7s5A@6DUHRmU{C@TWC}%6UrlW!okYer#D4ZWp+<%|7Eto|@r(Jy06wum z>O!(gX1WQ^kFAeG_D=}Aq~G`))dbH7{mzfl+&13#$k{x7FuX&sBR${7x1Jb&%m%j6 z;eh{+LYedM;XJpq)3|K2W=%+X49sSDRbs(o;pfmtJU(+l*igkhjgUX-sF+@rmzD+& zSdzk)M9vO(ZuOAUe>L1Ao`1iX>-fg>#FFnTy9xnrY)hnl@TB}Kg|f}KaUFa#vWKx` zlJ*@6GG@}~s)?aWQx$y+dtKSW!eF|%`_av9D;+jvitQS3D^(a$Pg+!L#l*2?T}H?D z!-+#oJ+T53Wr|2-@|6h*tf}K{bHY=ihCw><|3|)Yn>8zoMj|;+oCk4s-UkK1yjaS# z$uxjUN=tt+_)PzU311qt&CS27`D3(Ldj|7kAVIMaShHq?#qFG&L=@LNH#z?AVB{TO zE>@__<>z+0n2XXb)B1;j&rFt z+DQ~~)kc>GiS2&$5GCq!S_Q6%5F98QFkb1er$=k8VMlnt1GwR*Pvh5rgvFSR02g|M zPJ4r_jC;+HVZ&}dU*y4rAr0&h zq!-|zvmZb*aaMuCr0oo^$J($TEf67LqSnma;S0SIW1kXXosm?HID!t-=OQypA;t#h zliWedmF~b(3P7yl@}EBK{||MV!Z8?a+}J|Krma*&Crn>iN=D|SRX`AAH}Yv({lLoK zoKe#mR|SQ`l;EFVUiyUOhu;9*1C0*w!K+3$b;2ki!Jixb<(Mv1C0#7F9{~@ON8AS+ zOqnz(S9gGn!EHb;T?xK8wU;;39G`bA7iu*pWXP~p9?uv0HC#T*)td4J5(Wi~*sNCE zM^F_9-;%A<<$7T8#g1<{@pU}|AEHTND2If>908uAJy1)F@KRJ^V=Rg|L~p=6ZT=qCYj1`6SL)#|Uh)FM*iq z>Zr`n>%C#bqqGYbEC=l)f`|r5h}^iFQ8!NJxyJ|N#s*={m=!qi(^k`F z$>>Y?LZLYXTBoM1oTNo0r+z5OO5)C)IkVZ=+sjLJ=tAeEpBfGXjg3&vTfrzK=wq$F zzsNgnm$YIP=*7PSlxcu!aANT@3FF9DMP~t8GXkPPN{x?;R~f8~RvYo1BgcEf3x*yx zh|y9hLyTI)k^0ots?l@cGf+eQz{Yd>v}u^&BwYuxj~(cT^Z8dpu+#GWI%DalCp#UToOYXT%;I!7%v#Af4&}u^e%*skqrbmN zUu;rbudY1d(_dS`dX4HxzZ%y2VsOp$=RH789{kk@Ju&VtrIcm_&n-NkU zj=h9O914-ISTXx{`eaN`+T%izcBZlb4n@MUa@8vTF{hekW-W_!(&1kc0uwhJYgo5| zTn;mZq%eM6czBNcVRK0^!({rTu=^eeQ}G`(mqG^vXH{Z$E%boZ_@JlTzNq1Q7=ywYXT7d zI}MtPVI8mp!AZk^Og{#%__i^zvER$`STzzY#|N&?IX~+D@4q_+Sol<2JRx$ie#tjK zfDsIL=Am8E(^wCE+j=hNonTx1AwDH$Kz2F}D~}&N znu-amd64YSvs3=GB2wsu{uO5$ev7aY%-Y=r7*Ltr(0I{~FJho-OlxB)#xDmBj0TYT z)gK(gwPBa4zwJog*ERalTmG9rV^F{ZQePZDaoxaZH(;*;ulipaX zS)-?vB>ow!m#vDgLtWGC`NyiWvf@KU1qEOIj!;$c3Ff;mSwi=Zf0m%Rvl#8mBc^ML z-uT?YupApROUrkAmx_}yEJ3`KlHy^Y`gC7+3ud;Bj4r1=oumTg1fN!UfftMeY4yi~ zjoC?7(bC~twru!T*4kWBDfwfwo=Av%kz9%ZF?VJ+@$25*iXa@74R)Q7;|kT!6H?Pe zmD|tMg+v!hW6Fc2*Z$Chz2KQA4~XvT;?6hZ21C<5OYgQ?iDyiGz`x;}@dSQGIEGiZ z@rw_y>dU|3M)a23bO$@Z`@8^1zdye!4pJl(vnM1f@8jau+ZCH8h#w@rD^_PDn?KYd zpcGkQ^g+!|o$}s&TkATI$4=li!uJP76;FU)$JR@JsA@D7NnPr~nR(BiZNK&IQd-(T zmVKwKsv6rgTWJLYB`iEJU1%*A6ca+vCFWkabT*Jq92e!yQan>y@sCD9X>JEoBpj6~ z_g3A8*`atM!u6X0cgh|wvEJbKF37a^w4D3*gecHm{by`W7#7X;6=ZW+*+KNr%v?_| zgMg~?Xq&Ahn6YgV$(XY3ojP}J#vT~RAR-kZ{MN!;_hSD>tGCsba0Li}R?nQ^<5NR4 zY!)|Hkm~`kum;sv?d>(k^f(ZDsDIo6T;ug{U*gk)vN%b^Sq!Vif!42|R%`0meypq% zvU!B-;q8j29INhe?5F(v3ztBx9?mX z!z(X~Ld#%e?woUbAqb$0kCM!u6o+cYAj*B%0`J))t^*p=jh&KG*$jCcw$(tXcqmIc zTKY>7cN!n$9*5Q*nDx0&ZMdGU|J#k%qyp$*_h=8k6`r#)W>$5dCz7f5H?y|JD2+b2 zk&Xmgu4+@Ych$EVv!+|&;6bCdzJ4B6-^g7fE}AZol+zWfSE6`Iq zJoE(0`P;Y67LBIo!%_}}H%uPp{Zz>^p6u!EeZS%dQ;4vvJSygA0Kk(IuU>u0tmb;_ zoae0K(9>VTm2K#4<5aRf;_$XJc>mZL(tDjv)fTI>X5;}za+u&S{=6P?jn?P!u_ngG zOhCuz^wAH~xl>oSnkWv9vq{S)*6M_c{;Uj}$W_{$R)%SDr1`ivCeXTbf zkXq!=5oYGUl{@un6J)OUu}ddc-J=yTuhxFkQ!1!%N_9G9rj?+UML$k3>!>2f2U##x zX^BnjdtA;=>_JKPV4gTHVp+OSxqb$KFHUC!I7~K(MZ(sxbEwmV`%Lm=wVjAB{EiQ@ zy|ySTL2FR20LGw5x||hdtqWZmU}+b5d2t*f#@$S^YQ1S}#Nl0MBKP{DLq%|98{5hp zt--|KzZa~1OJ_z1$Ssw$`i3KLt+n+bHN*MMwN;azZoBo4g68zuvlC_vD$UwDD+%=P zIG=$qDS={Ux!&^%F$pc_XYb02;>u0k$AO$(W~y{9V8=>r1{4(pG~7vcI@XJomg+6P z?QVe5Kxf{kyAK}@!*iyp5cl+&C9zWDjS^;V;>FaC-?o-laDG&Di_D12z$r#ek%E9l z4^Q~!mjU?-E>Jn+i;Trjbn&2*iE8uVEY75S*@sMe9|Qt%nLH{nj+I_;_x!pnR5k6p z8Qw-bjHL9$2^7ua_Ut2s<7g1m)5($~hUHL1pE)4|cgDmp!xE~HXqRvI6zFs(QLHN5 z4Ze%u#lFI?(V?;*jdy`$ZCPKHTfUsi)GF2F(7I@-arAar16cX*L)-Rxnyj|pqjqsi zErQ4W6Wg|*%LhK8JZx`!0RXU^NH1chbtO*PvIUzHrrjX^^qnzyXcbFlA!jh?Sn|=i zjhD-n|7ZaiVW2rIxk1vUm=b9eV4sRg@ai#73}a+shxsa+OQ@x$;VtWvc;Uhi4ueP< z%XqtA?SAr@4VHjBg8K(eHH$O#64T}jqC-eZ$~RidAfxdTd@jNbM-VV9e?HzcQJ^$E z^4)ygB!vfRhk6N^vM={blO$XqgzVZdj>=!8m%h9_q8ErnclpJS&%2bD-99!aOs~nK zt}SRP$jQL#ebDj;_<)}}e!P{eEOj^2bwq2LSkeOK7Zct}1lUgV+w;fFfBf-0bsE!1 z2}Y4jGEvR)-a%OO>R+ZV-iziB;#LR_3j--Ko#n>*P~;}`tB#Kt9|t*Gc{`;uuA^NA z#);9xGEM@!!{7H2eSS&x3?eW)H|LrJBp>WAZ!@@>!-)Xc; zN9)yi)D2QOpRQyXJBJUo^i;ls)5*%AGxUe+>B+6qR}7<<>VwrERZtJhP*qh`SS@to zp8P=A)QTU!4BI2edR)P6$HZ!dk=n}2>S`R^)HF3Q3B>}^=@o?r1#IW2ym=Qg|Jsr` z{i2k!3Z2jNG2#u+j`}_4 zKGU?kUM_CfV+LGNAcVo;IFHyeE6L2Kwp*;ui38dXGSD5}^-Mv;xfV=E@`Hb&N^N18 zuwiBg0SlQ6G!8k5L~LA*EcWc<%lw;q+1OBuzUnHxs`G27QvLqTN#Cd(AfpKzEFu04lz2H60YJ70H;S z7u>PJ6nEQyAWk6x<j}n28h6sq4i3W#ZZ|YebCs44`+|JC*blQ(vLS^#u|IYmuYvgz< z3}gX?`SolF;X*B4xDeW};Gbp~DF!mxit=)&kISySMq`6ipLW;-W>ba_->DVOlqrQa z^YM5YT&ES5ot+@2l2aNRcc_+^L9Ytgr9Q_u+oM5;AaJ^UvJup$sd*3qIz7a*;GE!rpj?Nq=Vk-T+ zY>CoOFHdNsB!Q&mUc$7Zld%YSGGlH;U)wl`q{D`Etf=<8D#&&!`^0esa#6q-jJEUlrzdoV2@G?Ij;;3Z%YNUqZDaLbb)P(`As=5YsueBCHXYQd z;dQ$M@(pXLiJV5RT*;L2TyJj-=n8?ol2(Bk3)R*46Rnv~kP1N8!CX4kAFU_>Rcm6X zbf7zh2y^+BEQp)JC@98+J$qWh@C#&={{}w27Cv*x?l+;=#A(y65J^GETng5Z(b`2x zmC6Ke>a1NF(&vowYR3rYjJ`AjGRtad^9TVq7YL$~pPout&&NJ! zGpLJstMs0R4IN5J7vaqD76J&vj=l6+o)O1|fIYF+3f&BC8XOr$*yBU{Mnp%yu49c+ zI`idRl@WHf zr>S8OE8Cb1p)U9}H4Mr;)!(VtvR{UFPm<1_g@3=<@~3(uUGI?^Z0anbEFE*~@XYDf zUK$hJK3n}+8&7++oj^nBlXE|&F#pZOw6MOFU_(Qsg$N>s*NY+eDiSLw_Iqmc0V-1(@FlJ^}x zefm`J_j!j|-%-T$?ipKnnJi=~`+ZrN#rAuC3l`*$+?Q1t(fCtu8M7xs@}d{|7v)t1 zi0?=HP1;n?YjP~?;J5R_E*)Fvw@9}TxM6g!E4S%olU?RgvvZbQ_T%jsWRnR%M0V`y zkU@iZ8AUWxCZm%YTYgX3e9sis=xEYkeX6_|T4N4QxT0Tpt8!}ox;K>XR(cbv2BjBcA_)epfDut<)9C33p)h!IZZgJ(_udra1! z@7UHMqAqRyG)jA~^;OrJf1VTD6A%H^^E32!>fGTD%VZ;NVhb3~h?$%CFDa@= zp5M{2m1wiUJCD`jT~JU55QJlOptN<7#(3V4TbOx?)*rPq-Wj&BR=PtGUapj1?1wv# zmFSNpCB!eyJaeY~fMnF!v;;hndSYfY{Z{nJ9)WTZ(|>j>zPCOCz=eQdl-31S_mOm| z@!@txoru0JCCCOTNhA^_rPA{9kYR$sZhLYm4lH29jS#L}5a|FQP@=k$K?*bv;90 zChnqsufKlNuz#YanO)6-fNMHLFNE~3lAsPCqUUU@#hYYGSZ8>gyUXct%T=#dCi(H? z#Ia~CxwTWL9kPR3*Zz+A{^34-?)~VauRm|_Zjj>jkSF>Bn#4GDhNAgpZdKld;2*v2 z=A!0nK_$p47O@lg`)v!m7Lak8br*&G$P`5D3kYyPJHB6Y*a8Gg49!mbj7 zf8HW67KuCS3`DzQ+rnAc2fKpILHKk7xfOVkoyXh`RpN=`$AL153!yV+rFgcUK5l?hKYL9i z1!|i;B;LbZ{pMb~J?)nyCW7vmsMbMuG&D(nwq=B#mBW=mhK8>ZLp7>tXf)@v(WD}r zg+}^CqvmFr|HJ!SMfHSt@&=$QA{vHA~BL~9hN&S#WSSg#pcwTf9kKb{H(8cV?qn1 z9P+qrW7i_grz{h#7Q~%jDJ~%KgX_LiI@vOH3?(urZ695DMq@E2CWVlV0G!S29mJ37 z=Hwm6pHOR25w1}B#m7A6q?~7R!~>j*g0TS(FQi?s(VLk9eV;k0!=&kMvj^{fa$p6K zPGO>#wz#O1quZ<3_%ogcc2S2ur*`>r?eO9EFtRbK-A?6RkQa>+_@Xm2>NWD?WMzZI zVN>GE6N*7=>zfQr7ka1G42aXI!h;JN6PnPGE$8&!m)4GyzgR(03bV2C`RQphyM*NA zui!w4+}WS)t(X-HS5uHpJnrV~y?fVzu}eOEd9flwHF|K(_wsQt#0yNWRXyt4VZFs8 zTBFp1Q7YemH4*r!0BJwoRn<77IT*1Fv8FDy^R9Z$7=sd#s%c|*O^2N0rQZLjW%9V- z96eS2o$1g5^kQk%7Zn!1|L`GJNAwI4m*KKH3>u2IHw6VJ7;Ts!dPyTLL07zV=~6*U zu8ar7vGrP-cDo3ytEg%akmF7xIU&H7r+@}?Y((hc9@-rRzmc%Oc&##L8GH`+bJ(zD|xEmXSYpFWw;C?Vz3Ly?gQ0=GZh&<89x ze7G<0K)W0wfYl0+EM#y$vG-!2df#7oFw115)#q07MQ_4$YPIb;Wm;=V`nzR~G&R*! zr&9h{RfP*Qn!luNccD>%CVFRg?%v(?q^!k>>BPqo(~m31TwuqPx`3Ljm3rqcU9g)w zZkZQ67mNTd`n$-`Cboayz83fTS9O@=EnHpq8b>WJIpnbdRrG_Tj$wXoN?_vF^friFn(g;G1$g!uA~0~57qm>TW#T?u2hWKwOiXq zgeI|=tHe*FS7H^WwA*BinVA_|Er?0y=iEWu=JejH2-%TT0H7)Ud!h-yn5PHb%Ci)^ z1#DNq7*$R#Vd){gGvd01S%e}#iXm<{24mHfS!6kfyIo-r9b^!eRa%N^Z89kjDr!95 za`N&P@64`f?zg(VMSFC+sVp(tLiY!T7UxeQ!25LVqo4i9lTB5TdgZ#3euuUhT^J3c1(!$nTlh~sRwZ^b5(S@?xZhzVGD>g@OPqT7& zFQHh(a3#3QmM2>M3*NqMVXPf7^S|aZoy`qs3kCE%uW#QDKbrX2*ZhYX`43JU|DTC7 zW{5il;V$C6cOJE6Sj9+ zh9Nk)(9gm-JBC7HK=~sCG)?`M}htY>h+R#(m#!iqG%c4YLhY`Gex1z?U!Z z(}lF6?1l|cFqeQ_DaEXw+c>fKh8)Agme16$s(I7q?~#eL*mN&s#d^=i&X78pXRfy- z!UpZwk?N5filT^|8`7@J0pnF5&VCr90oDXb=gz4BX}DZN111m^X?J$$@Rm_`G+I2q zm&_$;XebJ_g~XkhyyK8Z6T9r;T1QUm8#SeDFgnH*4)4HgO)LPxGIPWCJIrC|605*# zO0!D)Ji%RM%+2&|b!vh;=>w{W36Mi)uY0B-5xUmgxagZMRV$c+zak`ak7yT%`xlnI z=DfiswhG|LiA{mam&LaJ(;_6Rf0}+gvRT1yDL8F9$JEY{71CyN=kg(JaqLw*MtDhP@C4R6XlP8@|Y6_Y4*Zi)&7N3!m+>s9Sd@A zFV0jb^^HR#fc)bG0?bVpue-0^#8+T?<^?3$*DqfE&pOunJ)Ab`|Lq=8* z7nWxI!c1eP)AUvH%X+Hx!3O&dc5!S33edK3>W{TJyN4)QfY~vsb|<%XOH?EO{>|Wv zQH1VdpX0!+f!P`J_3r*{f~_Hrw_OqO=8L?$L1gPSxtV9iI3Q-!M+4Uslc?m_h+Z3BE?+j-IdZOxA#xr>qvA}s%W^=k`^yTMpY9~rO2?KOB(Z%;ZTr^J=6GRXJ1?b$bHK%m zD!q!WY>jMV|Nr&9md*WN>7V~EeJ^p9hA@()z%#z{3FBK{*!B>zA>QN1D#`{qJ{{PX z1M)~bAXlsujt<0wzL0^f;^!6{3cZIdzdPuUq;u4m^%JOg#*Oo@tyG%chg~Xj7zZ5C zzU{MZIqry-CSsWbl-AK{CBV)Vde_m|)-Uecv*$b78rlU<>3gK4rHOlpZDQ31&yf?1 zOia$XrhTRUd9sJslZ}A{LL$fo*UYt-pp19}pMC#)E*e6Gh#HDkF%eX@b?eRQCOeFA zK1st=vvv(&kk%#t$nTymDn}HK_7edIFIOetcJb72HWt@i=Q}lYE*+__oRk_qTxA41 zcyQn0hnAh;EWyNEb)MH#j3!uA#>O&A6}-AFSnXE}KUI7Fw`&*?bn)HC1nHX(!O|%q_LY6N~^$**4a- z>BK_DW)ynB09QKD=-@X(jrX;4u z+8*(i**|8P9bb@57T*|L5hZs9!-grc&8xO{B;&gb-mSw&c;rYyi}`eHTt|)iLv|Kn zo}QlBg9UpMM)uop7xm=zYXyPd!0yx0>sj+S6;OZKk**M9*Q^CZF?`-lu3)|Ud|8tr zciX1gd+#0i(LqmU7@$1*oWfBw*Mi3C+KfM@A%kg%&u~pVR3dVd7EU1|| zW;}1n+pGak4EpqGDr<#gS5J4$5O48Nq9`b5T-&h!QAL3vCgo!f;$VU8%A-=B{KRY5HZqh4QyP^g zuc&Cf7z9Y;YFhu!dmq$&|9CC~6%_9U%uaGKDEH}EjVCkKlhYG9b zN$z%jr{b!cYF|VL-M>f2P>bd-|C%qI4sBHnIP6n#y_Z<9GS(;^6AA=_h7tjmxkR~F zz2;Q2z!E3VM@f2?f5?1DNkWvB!EyNPO+1!A-Rh((BRkrB&%-8 zbL;xui*J49Oqy?9uhlXCyY6J|)If_0C&=dioA4w@ubtlT{q!lPcmF}nUpjd;eQ*LD z+l4!wyaMvC&ieh3hKi2P;meoV195RuI5C5j>U~3@UiH*&s=b@ke_e0c#0DpAa|!rrm9KBw#fw(G{<~ko^Z?O`$g4h#wgB?BnfO+P zEA^zP{kM1-Prb+Yf}rT>_Dr5K1+i;V>I{8f7(xgy>PzG$=FfxxojV|!D=0Ai{ShBH zzS^NPtcRQkK>%n#TDU5vFb5kLKn=yc>%r)uQ~*LvpHl3p=!GEdnARUZy?8axL3WCn zZ=}usj}}k<*AoyF|9URWPEo_a8^eHN3~AWA_pOHyC%C%$Q<4EMo=!eIEuA~n2Q~qZ z*YbFo0A3#-0GkS1XUjd`fWKqMjA3vYE6azN{4h_Nyzt&LJ?kWCyO^{(KS?x5oG8`i-0C#A8eWirtA2QzvmVz&At@WT!Bsom< zx-1JFZf`2C>Hgd9`m_1J+Fdp~uK(}tE=&If80)3{tQ%RlTk7+Dyf=LWI;5~ybP|I!C@+PQ7s7wslUJ%NRP(Tbw>;pe`xh3sF6?A`*$ z$+Dj}uV3fiZEtv;QA_P#4;>jrK!@R2X0mWWWb}2kLA1^L38+z(HRmBQ<1o&fchtv& z^TnE{#`d_-#C>~8xQWmokn7vf958jC9UB;z)8!(qF*HV%2=3jodGi>P*@H3_^$H>< zeZKELKh)yHlzpD}ZtN)P6Tiq!d(UDsH=qA>kc=0>Bz)<6y}fO$sQa2cNlTJk#6;51 zmUC(U7iI4q&*k37k6$7?NoG;95;7}fkIWKMQAT!2Rz^Z8A!KHjkrio3Dk4NGL`H+k zNJdD>%qV`(tIj$1ea?N~-|zS5kMp=6=kDb5xvuwky`Jl3cn^ujp>O(MSpA{7Zy{_=rl?ypk%OOtxoDxX)1{jZWdmp zW?O=1dYI5)J_kq(c@%qhb>thWAJgN#+~iPXIwTW091ix7j3#h;eF~Qzo0rC=HGEH_ zEuTQ$z{-PK9p4czEq08NEpi>*Sy-xhBoC?PorB5-MDZs#kD141nAuylG+s#asXLU6RZM^7ZJy6RvwyXPP=n@Rk;Nch;Xw33qy`T z4|Gyrwz3rY_g?5Gu4HE;gFR6BW%4arKm>r78EI(|H;W?($+_fE@Zs0YQj4S-h_KIs z7g1P{SY7~dPi6K4rJe>?FeMt}0T2R$B1CzqdMr&%->vDo40hX(gRGF=kaAO;z=kO`R*`X=A|W&9wHAb z1MEnEp&*U`CjtOz__8MrR>^yC4Bv(nF^-`56Eqh-FWIEJwDU%-&LLI?h!B=IsXDtk zH6)fM-GxjDRGmYJmVjXt$b8yf%FVlCjm% zo#z)gvM1G6GI^2O+hIF{x5G(lyusxM9b{X-NEx(#UcvgxvdHi_>%Y(|1G2i1b0Y}a zvnM9{bs-N&H3#Q7dWRN|W0!$A@{A8e05^j=Hi7I z$0tWZuM|=VRwJ6H3cWZWE!=PrgToAHWn@G_km|XW3gVs2L_XnJXQk-~aUe2r@D%|5 zk&QP3FW};ZZUSv4fXk*7nT>#cMMXcObtO>ok%l%%?~#uJOEORuw4Y>n{#Ckg+ZUfE zAqNPM1KdTx{lPar3grY^>K)Q@xF}(Y$5rExf^w*WVX)%NQx^bQP(&i!5K5rf^3;Tc z+`_`>#-Mo~_y}kS@uY~sk1@Mgs4}y%)NRXHkRdkL8@gWk=hPm&=>kJ?&y&;5K@VZi zc0YYO2($~JU*n4xm2SPu9)8Y2!v&!mMIf4WWBTX9+ypey=(9I%AYw3Z5M>82DVCo} zh;v6e6_7NXyyRd59Fou@Bb9Crj0%*!;c*9X=jbWyI8)<`Zmyi6N93wLpgpEg1b6s@ zxD(ttB8h}+08Jpe={Zo_Xzkcx0d?y&W2e zY$YAEAoxp>j9mph0U*+e6LJI+U^koHsYOKuvH$~?uyz2u&W&6z#C-Iw(u$z6 z0$?&Z=YJ_=D4L$Yzy1GN3>U4%R5C=$7ttj>&$Ss^n4LAuRIa7SJa!NGG?}UAg!IAq zw6x9UlfY1+=0&+j4mEX~!$Y5dc8W3(UK=|*fxX=-C9l0#<%U~oRES(WTmtkC_Y+=f z1zJdyOu#4r{eq7{L^vaX^9?sPBE{7TKOzkUhgtqK3veC;hiu){^)9!_y-$nv^aXYE zNPu7XQ0LbI{wRXtFLeDW)MKA9WN+`@9n4e%t4Ub0s3`WN*$~*Qcke{Xzv1;SdxZGn zR%@_bfCp<&XG4pJI7(T7Wl)BknrxBN40*pM{??%|DiOt4k4P^|z-x2SGYm61vnX)u1~D3x{XJPT#C4*hxRZs0re%J*3!%8zs{;BIL7Cj9CEzc zZj^46Wqf9H@$QyvDT4)i+|keiVJW|e_#N4AQ;5;tfq!fRFr`r1)?)A zC?4V4ffCPu{5MMQ^mp{}kT8=DAEMO!D|jY!+OYOQadz?YWqsyam^#pYBU59KkrBFJ zr0=WBOMU!HZjYMJq^55iMog5I@J*8eKuyxYYyaOF%UAo%kC_<6^+Uah*n5cEQ0#J! z8P=ug1wHE<7#wuzxUH7+FgN$f+qZB@Zxt73TDi)P1Gh!|YUh+O75bD|69cH?(Ag?r zLL&^62#E*ihzPUz`PGxkXZEMgctHn>Nr^Yzicp#G@nvOZs`bV#Q0bL_+#)IY4e5;7 zL6CQ#o|X_6=GAROpo8hb-pAa`Da?sq z6iFb^)oYfY#}G;4QFC+QXCL~u$vt}xA3Yl3zw2;EYJ9xrl^6VMqBLKho#X2UTf7Hjc$3EE>zZE_!JlE{LCh{E)GkZzSx@YPS zZi1eAJFSo)r4*CMUlt9dGoGIcJO%pGGD#)A$9fAEF}3T z92mBwd0;C5xhZQTrW4S?Jxt!SLylW=qm*N5D3EIXU_Kk6a9Pr6dk) z2vaZcrcFp$P5e?&O+;@v%zlvmFZ>Yw%R{!vcfWYZEP)JMV)PI9grW36))-(-gB*X< zrKsDGFadq5KM2&$35+SDeyQ!7k=_0asC)*d@JQZB@mpu2rM-RZw!=T!$HdLQ%0r|z zZ8_Y+TOgz-`cC)BQZxir1g6Gt%DG+XR3FMTQut3;QFzkPr}G*ul!fdPkk=~)%umG$ z>f@XGfNqDIMtH>I7iM|toB78jGAsVQ2tLxz)0vQn5cp#>V?rMdX{iX+Vz>Ts9?yT$ zl17;jVCd^JB-~m}DoFFsHZN{IK52n`KE4RmU{fdnPo{I!pbnRK00J=)2 z0O$j@g|D{=%?}J~*my{GLYxcc%OO$^(Kx_JaXO;i2~RIU=m<_ON>!K#;WXo7e2NtW zb&I8iMWnj=v5wnk!iz(zg=K0ic*Vq=an6Dx0^NhM%p?u}U3Qlhzzl`sgN)lI(y5fJ zSwk-h=rVd2nC5z`e(kEq6-vn|!t8x$4I-;n|$-PX>| zMw_C;Uo8%3xU**E`4dpFuJVG??kGX#jJWZf-`tWJEp zqpVqa6YzH85Q0Ye28IFz-=*%}P_FcSjx!GiJ>V7-X?tnu=Sn_PC>ZKQsQI^s(DLpoItEgVBo$agP*;?^K*4EInEd7w#dw$SS zGs{c#LG1H0EKw}wkgvQ)A#r5Uyob^tT@l1wi{n{d_^^?q8^Xl%ZbuQz5-ls_x8Xr) zDYvpyU+jeD*5>)>_AbX1o(4?@EzNfgi#h}MEpyLX{w`_!oa~MAUi?GYD#CHE#B(HfpO`wq*CL#6mfdt(4suGH06w5fC09VXMY=^-F{UlV)hfP>w>4^V^zK*`K(D7%= zPyIWPD%riEh)#_ese2RC(>#bGfjt&`9Gh%Fw~i_L8FELvFq%@w7vpa+wiGisVDVbq zyO$P60E~Qfw|>d}vu_{MSy0b4i>UQ*x4lSP@#H`8)yn@je6{BN#hfFUdr{E`p(c)P z_+t^M0!Ix}ufXf!y1mnLM-JSt3W9k+#X~40C?!LomGS^hsrLE+P}msAa2rO9`iX(`>y z4qm_x27u`|HIHT%@r@TAOl8_k4#Y(ox`6JwXhd--0cVaApgXI2 zD<>-I1@0kmGbF(zEG>NpI=YWI%#*m3d0>6G+E z=NA|8oQOlP{*s6QciejL#IeU8lnEt$Y!HaNI+Qfsb#`VBv8tu z9C~V?kpyuMI)pZ7Hk%3k+?($a8@G;Aut8>8emvuRYSVAs|APw^6-qyj9|z@(nFF!+ zi3`eiMK3p5-gDZkuD&dM=J-F=a=A~{TN;-|2=n(@v?$U{8Bt3v{=F5KF7E7 zS>@j?On)k17y8r&T8?52<^ndYEGCBHEApMafX!shgNGEV+S)X4H%AUmiL{oGe2h;| zKoSOV06O*@F*t&P6Hn=~oO>25CgE)GFwV<&q^w%Jkj6DYaq#N5Pm2yIP!1jL5M#_} z*51H&lG9f!Z1+-Fo!K9t>7r@xICJ3L> zl*Mfseg*)+zPIfgY8ISQP)K}ihKMst=)K+d%-Lh{|3X#&r3sure1y*t#Vq6iXs`WI z)xh)ay-#=QWFKyxv|}9`;?uV@pZQOYe{*}{LaXk%_`8P=99WL;1AHASH+YIb`0uO~ zwP6O04|g))yLmhSy0d_u)dse(i+{PECmVK)h7+!IydH3Ap*||{JeMLbT++WGrawjm zl7fK)cZVQ`fhc&Ba^iBNeTcEHsjGV|{4Kk<*SYRi#E^wY<+VO(VFn$MsKrXh` ziDBE61HM($plQbt9s_;-wWRRmiemDX74}sOmp+kPH%J1=S;wIfd|p?Fj%<^Y!AnxW zPpH~oNu>R8e3Oi^AMUTV5&ouYo*4qV$RT1Sl)*;f{?{+P8t3}f_qkvA>?sKp9cVE%HP0m_alDgmvXch4!y#rWiCl8Tw_z#}J%Mj` z7lW_**?_4cuLST4czUQ=iAPYs!CMB|41Aj~d=vrsbzf3Dvt6{lQu7z4&N=Wl>HOx& zfIE$3ruaIg19J2l$QnR(lb3O79yu{N#}PP!ZyPM@%QtUF8an^398g~;v(*&ithK@V zlA2|F3-n;XvEe4gI6OT1U!sK<9&)MBNe9uP!UDv=Q6!ps6V%mvrqTOyh~- zL1loZ@c|G-ofmqsA&8`v?hd~D3HLK_EKnw@wEx0m0T1kW4Rn+oZ*b(huyZBqLNM5* zLA3=-V}rTO#*Lw6Ab|_i$$3x4Ae@yHj(Z4V{xljr#O5N$j(la2S>vi0wCx{bKHt>^zgPfJ$j_tk%!2re+OJUOI_N`bhW|+yd7}BotwD8 zq6@Sv)}G9SXQbAUlNQm9JNja*H{jU-d_r zu7AjX>v7zNnY-GpSU+lr8XBw&OygP=|1!m>07{U+&3n$d+-Zch0X~TO*4s2%VQKFp z3rrvi*o*#v7R7>YqsM81wq{gZQ8n#j*Ue>#}HN>$&6Hj?#Z>0hBZx z%L$+y0umC+X@0XAO$b9n_IU6)V)amKp+-uzn;*a5)PdT=r`{2B>kb^&^EJE z{}q#B6e1pfQ@@}6u!}c7HG*3}hzZo=Vk7|2f1Xb}o5PmJM>`g(_BjcF4F%?lhYhy& zvgKJ|Sp`HalJ)oCg7)dor1RChr1Eu@Hl22u@KZQLpu|BwD z`pABAXKFdB1;cJ|p4HLZrP?CJo290z zy8qjm@GCm;rL9A+&HMJo&k7wfMUc7b3A#F(THa{um@`*TpH4m6)I1RcUL1=yB3aM8$Y;j`kmTliV zen2JvP5LQE&Fv0jhvQ62^?l=R)lMa4kv;3i75Z^9Q#=B95bRjMjOPh<6B4>QZc_P= zAS;w!4=}V77sz*0We~ZlHH)1ui21h&b@jBWLjh)nVh)1{KMSC5tA-)7%?{s0 zZJ^h4Jtut5L*rS?v9rAEl>L*DBnFWQ3Ut_eJ}4UAbJX)5@I7n!=<#D)L~i0p@o{%2 zQ(8~H;iK`Bn+(zTX_O$C8`Dfu!)BLmC=BnJu7SZ-W$A_ad1Id?R3f;oml2@p@bFpL z*`L!KQYt)p>M_8r2H1m{f8TiujRY6By=`6uPkW*$BljgamdZ^FG1c?UQ{GS z{P=!)={t%6qaw`n1s$?8t{;+77*Y2Sv!AV&*6{FH;JDyl0va$f_aVRV`cN9mDuABZf92E7sCfX7{x`f1W7squxp??!Xh!< z-&>Cuso^$32+ZaWt~{d}lb(L-HvNK3?vjOry*=Vk4&6j+itDgNAEmp|M&$1L{G9iS z6M$qda1RH;2|N$e^)&gaCpzKKZ)wUDZ@S#QbW>8FAaHw%g#FC4d}#|IY1#h%vO_cZcHrJWV(k=R)2IwK^X_^qUdyoN-Cfd!FAP8BY5zY(mZf6?6*8a zS`BvHIDm=3%NnCbS9x6)#PP6QPHxIFs!U2a!I3#Tq*#;5o*RP~@r+Kdro!|gU!c@d zwoj*woqK*-cof%G2KJtsUUbdXr<=GbI=b@Kl|)~!HlO(`%8Z?6Sq08437rG&M|q1( zCeZ&N`8K97-2)^xR5~11{u{D?I3L(x^*!jN?U#Xv?QWiH8G8pXf(B!<34}R>M@Q#* zfE2=Euz;B@Vq6>?`yu_dTAH@PwnlvoY&@RX-mH$CLMkS9PQ4eG!TfmtNivcMG!i6D zT|7p6$fIw!U21F_K6ta)>LHkr{_-VK&5uG=fCVFg4@oI!^dNE>j29+1L-Y#z z3p*D2Ae2N%S=6xB03LyTgX0Bs0(4aH($ECr1YWUTIrUlJliJ!3DsORuV6I2y!dC@* z9?%VBlZM*@3GxuOk9_@#0ZfO1@grL>Ei)5u7K%kk-Jv{y)?`<*Wc8aj_d$@LP(%!% zwzeZ`MZVbcPCLZdp`gVAC#S@wDI~ENe1Z@P+Z~0Zq9S)Rx11Z2viRlY&8%e+ZHJB$ zX9kjFev68ciUh1ckfk0-6!J6QfNMufg|mU*-EcYvF z;Pt0wH|+Ctb0bs6FD!#{iV~BrFG9Zpn&7yH2mC&yadC6JO&kUBcP*@PwCK{{_|U} z22tww{T6Ct_;-sP+Ir(dU#Oa_V@i1ZE!x_@aOtM@1{Wn4Up z>s(PupI)5HGZe-Vg%^~sPRC8ThN};1cdq!u%NGtx6kcq&fJ>%vaEtyfY9dYm`YkA^ zfByW5z=#IFuo{tu(MV`|u9?guBVwL#Q}t?362v7W@)G;& zefpqqMC#RvCkVB`jWzoD^8zN%;+Nskld*lZx;404>XWQW;DSXCk)$%`^l(NlIjNn5 zI|32+eu4~fxR#rnaPgvs3OUjlVwp=&AU8^X#+vKc^0Abfpgxesp>n`kX2)-CMq$6VpN8G#$tFLOtX}D-$1x!?XrHI==S@S~>zx}#uXp6i zHyK~_`SR|`r%89m2B=~Pq&(`FC4f&8xhvbhaLusZ9%xpkhf$IHJ2F6YTf3pP=qvXW z{*kJWCey{W09Fao9)68~C~L*(K5e>@iOkcPpjT1y;TCH+GjMH5$CDZa$oDFxt9y6r z>8Zbr;iZLj1hlWB<(@q<&K;5sJfss~Q+di6s1jNhj$q_1BsiThjl&pXAA5=d;3tMH zF2d=3^`W@P=w6uSQJUgNk>X-!*OK38_-4gJ8**(5LCZ=VkF2F5 z)R;*%UFgcOM7KCH{d{=$sx-*Da0+96ZfbloaC+?FB-3*-b6+{vZpGj>;a9$i=aui^ zK)PoB2w#Ad0_j_?5;*3jfVEzGp;Rrs11CDO+}h{LoN$?FlxSX zOsxA>*tA%fVRuX==7wWf;~>?^&uLP&$l5KCrnA^qZ617CaE3bJaOuPG#w%tmzNNySIYIe_iHC4MSY?9H8N`LS)7p-;b2pcmi9nH z4$$%EK$2C5xz<*Hx05$2l;%9roTP*A$KI}?CPk4qrbGe8w{lH%l(hua!sWYgcSO<`>DihG;vm`{5nl08JZuSEL? zQ`qi{#lP%Ir#+s1Xs8h8W}_?1uzquz%dJBM=Vuhc0Es{(GYFAsgUE0B1bG}3 zA^>WD@fcuiY;!ZF>?9N^n$~PunU&r^GQG-@D+jyg+a0capV27U{sx^eKsnfpp{DrW z<`WB?#2KLGRf<>Uys$)a;zL`t<*=q3Y;4z8{|tHK&R>z#B}K< z3Z4lwEG}G_07SnT>kDTil&$vp^UuIs@j(z4FgE5SrTBdn9C_Zva=IJ-F_UiZ+M?z|bi{K)r4h6uf)rD>Kcc zd&*iB^)_;`LykCH43>z&7`GI^TOo$75#!G{RIb#3g0OespcG+x_i!tQ`c+{jYGNWg zfW6bFmr%=qN(-Ui^TtAMK#ls=gj>rsP`aovu?Ty%ZYYc?T(viC3|$U(D-5vhU9!sD ztbaUnO7)*NC4HRniU7K}18B#Q*LpiK=~3MGZoe^*JUH#9fy(hvV>qa1?!$;nm7_c5+%q=1Gewq7-Ko-N}3ocpxH&=RDHH z*-S9F?+ZAMr-v%xvkBja(BE1AMU>QVj-$h*A%Gg1JT&l>hV!oW&L_?;mhZzroq8nQ z_ujn|(jwReK=3wwSzq4$Mrdb^HnzQa6VkJt1l`ip*Fj)4I(nnUZ-w!J0}Y=h@fn6` z61*taqSt?BgIc0A-xWG7tN_Fqdw6-VMLZGEg>8-k)#B3;vhEsbkMcp9@NZbs?<#;w0VjGM+dlS z8P5iU0obrgn%dD)h0Y444_w*^_*utLbnl);)v1btvRb!FU~7;eJ?a4)gKBEA|Ke`9 z>?lTbu$VUpeiBB6T+5$OiFZHM4OLt!j?lh<6+gyH3YFvg{K zqf4kM9%o;W7qIQFQ(Zb}J!I?{yk0n6M*tw+$?si0 za3@^&f@l_B%vzbMaH1`dh*c74b@S~@Q4=kDIL+#rX18sSOC;`s7K8q-sPpGBk3t30 zgQ3j9G*KX4x3_EHxYSt3CHTw*q5@ITXeA^PC>maSmZkG*|TSU+acU< zuj>3DsQ~oj+R4{v<#xsc+3#KfAgkI zh>OOLIO3zzRAI_CWO71X44WVXVq(m+IHSF>ic!iiP%T4_6mkm+Hex_5+)u)nmG=|^ zujPaBqQ#zxxM5XgiG#vSR4}UZv4x-!=E@F3RaIi3AP3{OfrKGn38e}o^N=o3si9Ax z)CY!vabbHTY+<NQw5VA0=+&_TLjb*w$WXiD{BxtT~(!$NVj6easqhYT`PM5;fFR8YZ5Xt z#*k=_;@QDr8%E8cfH!<9#p9A#w=UuERrNlzl-RceH??bRiY|oRYD`Y@-6;AC!eP{6 zSi1|YFHkzP5T4gnd6|*SFM5NIJ>uG7Q%^sQUa#zUMTpz24pOQj!U~qoAwghu`4{29 zDMv7yZX6oXD{p);L0JfDWWLxN2kwPG>#Hvv*i%BIUEvT`p}BWIUQH7w1>pSso+W2D z3mYR`eG6p?Ak_?`oVW~2%=l5pdWjyd{0+ZIO9b>sY`_WZ zubi=XNjDuVoro%vZd_^B1#w)~&FDBX*Uz0&z3C9<%39&u^6HzsvAok(eeS70kr!K? z+vv#1$kim9g`pmdMMm1!)RnCHW)_;`o^`0RP@6<@N}b(av&SIGMMrK3fDq~H+s&hc zrGSy;5Nv<9Sj}Cy+`t!8=+9PHgDveSRVqdC2r?6LdT6xk4RH&GFCLatP>AMHVCR3v zOIm=Dy+I#bF)aT~HRoBk%gNhcBKz)1@-}|JfiHgW{soT{FhOA!$6-W@SzEXI^;UQe z3q+fWEy7BKN81rpHnQ{~zfY`)Ok|Fqn;(mVB=YBg#OV}8Uk@Zuphs7VurBMg*P&$E zj?IGRg^8X%ZhT9w2n6*kxXaDnR=Yr3Ci%P-a1HuAq`q7b@ZI?nR6!Lln{Nd-paA z3)ek=e%#$15POPpz;>4e?T%ClBK0@8;mjvbK|HnDDlL5p&{@7wHWXZlITW&Ltgg1k znIYX?+ufahsusf=1qH#Po!eE;9TWmtc3@y2I%Y6R+Xl#j&(@aAUAWeuYOR`F4MuTU zAbPbBBBK|6~V?`mjsw;L8Y@}Oj&$T(3t-dej1{2O!sLKBvF3LCe@{1^g+>{PkWZu6 z?-m@dD93888PNe`hq4lvwp~=;vR6=nNxk~QUuwn|da?$eky>2oO}FKfOSlX0qiV-4 zRzK#^im|thjI8v*c;>*rQEkAZBJOb0`~ilnyW?O6!G&J2tLtX$<;zFy?QsA%n~x&f zAtcHD5wmhevzRr=Ga_)0GQkPO%>aA!fzbj<6tlZ_12uv+!&o=*(?JYMQy$DyiKolW9kkM`GYRwgs;i|w2%lgJw%_-UM(5mi zWS;Fp8I2AX*;a_7xl_I?;>kxGmMLksIIB|)%*?omL(D>nEN*7k8f;${D_Y?@%oQ6x zMLpRPttbSTn1Nud0Y9XG2nvK_&xxgu8)u=Ql9TIJwOkS_!b}KM?AW687rE(ITw3f$ zl|r&(2T>bX`fynGZgradt2WP@kS9W=QMcohy7$+`0;Ml2FW>VFsOAYpGU(;Qz^Fkl z>vBhlp*F&DyF9;c+b=dPfxeH90FWBKRqu?|Dyf+u#s&dwVnWr^bBnm}ax%LM^?lJo z-Xc9-I)tA;Da!#8L1x=YNe2aq7jX#lb8mkG`UNp0+UT7D%W?}JpmdkN|KiS(DCMl4 zkfUA;R(RKjH1lqW$;b?a>|v$y>?0^qG%HTJ{;36kikJR5nNEQ^2V*m!I}F?aZY~zkk!&W2jS`MjVKC;aiY;R0h}$>Oc%M2_|mfD{TQ4+cmv!CpiNBD zx>k_E9Z-!H66G?^MU#A^ikz`6+@$v@?$V`s%rkrU_HBIP+rKcR?M9;T_DP~X^y)b4@t6HwJ+XQ;Y5KqaxYYs06@ zJ2AJ_RO}S$)lfz*b)Fl@D~Fe=p#g(OnJeXH(TSiX8?P~bsSe2)fypO7081j^yvPo& zM!Y8gdnnt%<5b~mL!5lmqCD}y8a-0}gkeJXUgnkl50J2pDijR{VQ6fO3|94E77QB{ zW~Mbr+fJIS(K!c)hN3d-8)fQphZKx8is{X@qPrCp7RpKUl64dQfZ2cIgmpg8bZRn3 z=vE#6I%uB|x^gB!4$>@WcekGd>2w)%qsw!uK-8Rl{&LV4OG&w7$&ykhcz6Zr9g|^8 zuyI$Rk;mP`HK)v{INpIQuHO9U6y+i!TV@6h;Y6hazj3RHlgqwjYqt-+=FyeaA z>YlU-}x)$R(R3s|boGi>l6&+8v`ZT)K zGpdru-ra>ie-zG0US_EM;m=qA2J}=Evk74Lx7xRF8^%|x;@5%*1EWKmad4pdhx7~r z6?9bp&JUK(7^j220~{N*W@|%jtpFci4TwM3%E5G_$ZQb6RR^ceh4m_^9h%?2$9U94 z0o@`~ZnVe&`Z_vD4meQa!b3pqX;JA=ftnFACUGXE@&{sqHiHVFO}b-p`6~dex4+b0Pvn->&xvs#YPUr(3+s0);`2naA2weU2#YApXISj= z=>f;PJCivC>Cz~Aq3nfN%&+JDSLySPy0hzs?tV}|`YE%^kg@cv` zNls!C%mkF{6d&LuzQXiTPlv0y>!<(tYv07W>hzT=KQOZL_ID3WzF2@O&ubXi#lB5X ze+kG|QtfgGD_pgWKSXbl-=rg^VX634sG6dFPWC<@!<*Y>t_&B=%)AbN2@S&Wqt{-ER7{COW@) zKDS{G8cOLkFB*YNV!IBEjBG~`$6ROnwQ&gog?H{DkI({Y98s}ZJxy^h_cCMx%XDp0aD^{XcPVJPZh1#HI=j2|0;jwes??Z>}e2l(fBkiLD7` z)NUL#)-_b1?9eNMBf-oIlve7_x^GK#Nx<^6XG`@?}A$`OcSfs0HYY7pd`a_wrxwS?9BnX z@cOkEXk8e5Ps8PS=eUW`4;6#N_Sn zj(=)NtRi?>!Z4)tH)Wd9?D|XUi;GLK=+E-L!`@aYMGLuEnq!d*3beWNS~^T&%-UB9)LK$9YjL)vsPc&|5Y8A ziOr#~hqfBJ09=>fOCMCDU@487mL53vZQRn2KC6L!EU%Mq_ZDRF9f{O?mF*=z8{82ho z`s>0n0`p%JS2&wY>@Z$;!pj1Qgz^@5cCwT`_;?p(G3NgA_(Rul@8QEAWCFz@SJ&v} zxWjOn=*EE=LGm5=TR{|`=g}At$a?uFzeYX9@CotyTZ;g~LZN8g1|%2Jenmw_l`=R~ zDGtmIVuFv8Ql7so5Q*4?gy4e|un_+@j!ko60mXU$b1z?vslElVpsISjlk149SfNKM`a?YphdIL{i(UB0pc8>h!@Rz6|NV0LWDTqe! zjpStv_dGg&k_EkXjgF_sw)ZdYt(O-yDPm*W-@Zk{B52pI0a+>xSbg-<=hVdhYAK3P z-Ja;Hlht}WK(iV=1tEu!NZ?JtzEJ4R`7a{EG=Etmjiei66u`ipow*sk_pL(rWP9*jERF#112de)G*J;}LX;+i(aZ#z6>yfjWeZ1&9u zFwSd=90S6$os~ey-HwFXz1(mth!Rj>e29>!q@}0F2(aBdcg9&CtPE9jVlr|hh3s;}oi++FbrMsz^nv-aFo_C6d%rsn(h2^FtA zNAD&0IGjxs>lbxsbE^C#=_e{#JBx~Hr_=|dWMppH&{dDf-ivd+Ooz?*H1cih|#BO9~V@7K@xOhaH zv~_h=ly}`ODxwGka^8@kjmTMOPwP50WJLlI79Eu%ljTfx0w zee-@Q`!yor#=!(|ofw@>x#iA@aT@}W-nL-K0oaW5kAY;sP3M+~(Ny7r_Hm4d&O3F6 z=*J0E>Q#HyBPg%=Qx5`h1qJOfMUH@h2%#fqUYdx+@NaL;+;$Vh(l75?`m$5P2SIQE zm4_G){O;oy!-(g@lv*w>U6uSBYdZ{KI%h{X$J6Bd(Dobb+=*!#&>dYj6%*2JL%2RU z!|b-efmjZw1w4oB-rcSvw+> z7e-|s@-mvS9ww=OAKx*P2+&KlUmRaOLGl8I)t8`-gq;J81047FZm7U!3sT`kG0cZS zJ*Bi{jN859V&Do0j6+@kgnBt1cwGzsMVwlFj}KR2(80&6icA>^QtK6jMhSe)_o=Dc zOj|UVfXAnr=o+0E3%|b2zjZ5Q|Mw%jKw=Zx1zoIcs(_-B*#`anTTnT3-;1|CdKBpR z{Hf2J+DU=izvv4bA#cQPq?Zto1}8r`lp!2sT`H6i9Xj>v{by{`ln^>?HvfY9GtDa} zt1S8^#uUF=H=2~-;&$Jel??hfBmM_{gflu@tNSQKVNmf2kpX`wQNT`b)V*`#hBKf+ zM9~9at?(I3hgSz9n}ONv;A=&gW#r^*4CEol#!U&#ml)DcOGB5B#1;yluge}mHjB4_ zlWzI4YW&4;CX-YOBsm{HPT&HBVVNQjsxX9_;W?d`Si5Z5vUDXs67z^>hRhJ7FXHTA z$40-T3d%>44C2;~I0OGo%9ZYkuH>yp_ks7>#>&jB>PxX{^XBPq-$>;e7)-#c5bCHg zljL3;z&(_0uRg~B{{zW*T^rD)Z%++D`%2Z<__#75a#Fy+C>EVq68E%OJMAX%K9ZF}2E4s<{Qo(bh6 zNK?GOcKRagOXNDlCrF<_bGzc#MED~Z2~fdTyym527{K~Pkov?S%uqsL?-dRp>VPle zu03yJSqKFk2XLpPq(tTPVrN&^PiRgVga6PIpZdI3-_CR5p3Vu2{4bEKDz}9{`4|(! z%zX)E23V=5hcYDZK9MvRz3T+L(GJa6)JQ-(b47Ba{U!+R_N={8NFuXi|q3V!)SrKrQREMZe!rgLD8QFR9YS z9!MvWOar>Y6-VUtM=Q9QX7UXmr@ZFu@IADT7E0Rnr1mi22IqMI3C81xER)W)pFhyV z;$Cl`q$VcUP4&txv18!{BNLc+R6>DObZ7H#+#szl5TaYU7QPB!chpq{3Uw~Uf&9F7 z?t6kD&{5h4j09}K;KTfUT8*dfZc{P^ZEo9~E{Kl!1?t`Z?B-byC^DsD<{Fuvjqdu< zmpg^}%V-{UUI<;A?rxo<_uv|3yICCJ>)zKpOpLmovjx@S#32xZ2hN_>)6*+!Z?ntpwz$sc<9%=n>%%P0&MsW!ZP%W7^F9U1z5l zEB$!=#LNsp%#naXxsI1>SDgOhbi?D7(~~3Eop2WZLgoN!p;wwm+o|Hwj{)*ckRBrr zzcD;4?1BH>9gJ;+N@f1zvk}}NIPwwUhkR&X$Y$?|DK5LQe|fg8s@P6uDuV@p%Fr<& zU7L7>s4rlAI3xqsK!$AS1?f$z#$^)`xWQ7kdx@;;xp$N!qxl)nLlk!!*mCjcfV zY4gDuByNk5+6=(2hCLr%3->oTg+2KQ@&^|rDreXPVB9dwJV!y3kE8+M0ssl&Fy_)| zJnoiXAHL^BV@xK9X(WQ;gTikO@QIWn>FL?buufCgz)5rBxKRd})X@1W`-(NGx|)oZ zdWQ1RaYQ#{r8eONUuEgFZULYcMWCUuU0sd9f;>xzL+0-Kp?bhPZ4y`hpi&T+DR-Ke zwg!beL?O|?^>~Wf7gi|YZi^Eb&{#q72#>hmZ^szr@y(w5 zE{8B~Zlx>F-d%s&)>VF;@>W;}oDHPD z_!KTC$lOg5Uw1J*ceaYxMIPqMi#WBxz?hJyE0z_O5!gD?_qi<%!$Fb709v!VtBa?- z+D5YIuYSX2I_3cxS6nqWh*Ya&_?$fT#c0SM?Ibto^2r~z7K`s|IXNPfd$J$t+M-yr zTAbiev^jK$AW%Jlw{Ey|vU~a~!Uc8#`UFo5^5`d-v>6o|8oC=AaLmQSC*BMPP*_;? zfVE9gDU>C-xxvbFn1B#OAsqBz9~8IncSExYc4#qU>4ziyEd?sO$zLBO%XV+Za&pZP zXJ-=)BW`ZW`1VCsl$WJ6X50bvE)1R?9&ybz%|U1q@}eOBUKWT=rm=7R_wQ`rpMTRR z1kopmX*E@F$N$}AbOGA~?xX|=O-Me4URsE9+CJ{V8(br#>j7;ogflL0@9fB=ijw8_ zeEDwa;fz#f|1E1)!DIkPR+N=ciQzw7nc(Rhm>K7FoiZ2?1ZoP5 z5r#r19QvnaI6YzTf9es(wBYfUUU+q6x&r9Sa{jkP2~>*l z6O=6)>xYImFta(_D=A?JhCEqs5EaL>KDyuDG79z5RVl!;?A`&Hzz(U z{j6I0PUcYm%jcuvl93g%z#{b&NqsazFWcI>Pc0m~@W7c2sm8}me%;n)111lS-!Cc< zXpp4|N?Ov{!I?+(v%wR6{x4UPHH$^RQ4zb;qT2D}GD1b6CZCosqsVARpy)n5y#{16 z61;d2fRq5ABMcj86|QQ`RuvHzexwwR-%$G9f=UC@t&tIo$}L9BlvY{`!9WWq|D9MF9l#d7-9-!jYmIe_%mJ47{CqY_S|O+W z-RpL@?xST6uvlUh;OUUQG#{TTi7LH}K@ z-JFnIovdGC{^F&UuDrmMKRixvdH&{cib?s0$LU)2rGN7{G4lL&_vltgXmg0M0x=^H z1FWio2>ga77Sh)FmyqcyvITz|m?UgL%8*!@8_sS+VLS|MEq3}~$aztm9(sX7q7@1! zH=!!fGB7cRyg2`>R&tizupu))|1c)w63|?G;qBl={V4kgvH|?bFJ0vv`ba$isfA%& z|CM!tkr;%9u9a2B;u`f|(iyy&ziXagB`LUV`&Sv6Y-0Kuss|WRFz4@?H0&v48gr0~ zvz-8=SScJohF>}s%HHKh&*=mNR6Lws4@;br(*dYPmm}&AV)fVd?3bl7Y*d>`J<`4T zfd2M^@3LtX6pbvwpPn11>BPar{{Z=?0L0uL?WYdnY1{l|>+-m1DLY4Bd-INKsbE2%D#CkNF(f2MP33w?=+Fl_ zAokFW5UMByk)4z9d32PVKMb$^q5A&=Lt@yPZ|Z^2ZZu4%q3n!*v_T_;XiC}m?DN^Y z)UWgJtRJI#!OcDhlgPjOpcoQLo>WVf#&S(^%`PsCfmeeRS%YT|*M@kk4#H$=V}k3` zBj>m&nPx_)2n2k{dyT~Zw(4+&OLc6h<82Xt-OK_F(KdL&*F|%zULCY{2=d-rxL7va z{D%&wm1aBfpUg-G?|B>{plaVVHr^;Jdj~M1ww6j|g=jWlY+=?A2w!Mu%1cY}&usn) zTug6~Hp~}`OJNoy?ZN_-1LC^@v0!Qe`Ul*Fq#lU?NQtBZB!`j*OmJ63bTmhpW(U26 z3BYE20VwOqjt0>=bN7ehbmM-2&#wG2p%0b;{~qi=3c`19-mn+$J#gUC&eaX#n{?yg zU&m5I*9Iwnt}}n|-jinjEd42A;9#x~sB?B_8LMQ!8_ygFYxAFRGO zx>c?B%X2f{Q0n~FQ~4{e_p)6409Fo}fm%`AzBt^`xx&7)E?O2{KCbU&r)y+yhB$cxY)DJuFSK%iqEu#uYLwdZY>DQ7#a1NYQ+bZ`=1d#ajacJK^WCoi2G z{`v)y*2a-X=Jxw?85tX{?mVj=oVoT98O0r!p8j=s_=v0PX*>^Lg`knySy>Tzi>eoN z&&~{G3jr3RV@2{-#Gyh1BG4f&HntVn764l99UXB_ZJo1W^rn3h_)^@ABx*vyLsAnk zLZ7b(cL+!hVJ4{!D*sYC^=bJd;dV-vH$l(K(-RFN@=_vDt(y<7UA_AF+=M+)!jQ51 zAPP~cV=j8N-LYdgb8`oBbT2|}8KoUKo#*|-r4r%@G}<7jpypH8*S~)IHUM_dhVO3< zx^DWMzEf0KL`)178mZKwChYa8Hr0bPfCU=xAypvCibPDtlvRPBac;GBX$~}Ckp9jd z3xM>WU}j~_xzPZiv)t?6Yx;W!+~0L~e}!&pU;y#Z&^kK-aZNN+Y28gNNJTEqlx-q@ zECXtx;wE>$l6(=*^5>3|0PE>@r`q|`cc{!_{ZJ6sObTu<{U-y|WzR{Z{lNeQuc%+i z|0?paPdF&Si}D8JLWOe2TwJbp|9_Od30RNo`u6{osnQ~em57Q|BuSBwq$nvRq!5v$ zOhqB3B2hx3BGDjC2ubEb8px0#ROX~IN2sXY&r@sd-`@MT_xt|;9mhV_-fMq{=leYO zeP7pgUgvpU$qW=hI+mKa=-q$jV+}n0W#8uBR=b!xp`ZYZFE_x2d<+IL&7;r6B&x^Y zmP20`{W&X>F6M&=6T9_-3CDW8*)`q%j4O#MYYU#*Kp9`(Ccoxyv1*BzFQW>^AKRw1 z|74oDzxBZX^_JY}3w?1}3NvlD>TOZgi!1Qi@4im(t@qz}Qaa|;EtKm@+x4%FeQT3K z>w3jA1Mde9J#Ab(!$N1NAhp}8en4B|=CJ|c&Z5I_{!3TxAF4Nj#a4Q>2mH^*3e*g& z2x@9FR$N8c1`D#)B|l0>lF6hz&c04<#fq@D>6&@k!Ewhz7Cq zdSvo*1^THaQ9FbhNu+jSEu^|4QM^@sPIB38JLO7sN66n_?sr$tRz3WtaP72L8r}O# zY*M5^4(irF{iJp88@PbPe==FJB$*P5<>}KyDvA&kQBV;dFn!J(^FOvr&3rO{`57Y^ zs-Wr}qRVAcXK-nR^}EMnwZC=t4nHy|EM!G&hN|@;h)h_Vuuc|fYiNXeC|~YPr zzzUly%~z3}P-t2|Y4~UPmVd=x@-0p6R{55kb1-WfDuQSg^`x!8 z{pR3bSCclS^ocGW&Vn|Rd7Ai=yEqIPyDv7jo9N-l8WOyna>K<=Z-kG8_AX;c?j+}! zu^w;4Ma=7+pVT5bZMosD2JH&1Pg1RPb<=?|qIFOuUiz6YL)xL2b#f{wu#SAXZ<>x5}XUhhAsrHJRjAgiU_?S47%)}2X_&vF;~82AB% zQ)EgO6`Cfgt{-~{w)~wsrv>K{|D{h8u|sZJXuRaFqfLWykF^lH+m$s&k{E!b8V;-5 zb7YWzLV5BBv%QlUhpDAyr6$V&U8+y&5oE>}_vN)dOzmfT)(Zy!N-tV9l4Ek#e{&}I zxQ^H-gEi2Z07X!EH4EeiVyxr+3HrP^s;lY3$4p8Ap%h$gg~ zYzzJSeOtT^Y#rTDDQ=EKTP7Y@!njDT^JYfMyK()#{o1Lid%E-4T`E$8lodKHwizT4 z9pj80<)jvf1RqCL(2|-UouY4xi3?1h)X{BOxlspIfM`MXpqn40bswg$xVOhQxZ{b0 zlTz8$Q(%?fzI~8}qr-~78eCTWD!Sv0{GhB%-geeYnTww`N!p{~Rv50O1xlcBu%GGS z%^uZ@LK?5EGRw`)wV6I0;}3W9i4g3A<~gTayV}XQsbEWwudBneA53$WH3*gz25f`E zGP#>V)u7=`k9T@SOKeJmN8~nNg+geRa)f+eVdS#ei~*l$0-MC=_KiVZT4M2w+R#;p znC1zok479I?vFTq00h5l`TOEGJB#j=?%gqutp*|V0QM5l<3T&7+t@@XbnwUH=^bn( z4jD>fK|4l3<67Ywab(@o{`9DS=T0vj``_8^sq=RKKhf2JKxn_x$ilH}B77=iIxb9y=Bo z)xX%g{Y^n-Haqh3rvnH28R}kLW%eI*QAhK8r>&~%2Qn$zwsU9F$XZf(VK)4&#^4HZ z?@RtspT<2ID>iTNforPPO5~0h?CLQukks+nvuCLl8nG6ORqs3<_oJIIFK*(*&9?+b za88G9qSpW7zp3FG|Fm8wEutB$!=TNTIZateYpHCR)627hGkDeiWwAwW zr|}=+#%vrZb$$4JCAmyd&T?g;C#CIFIKHJ}T9C#&)3f|bTslV^?x&%cNPiDz^)qMhEA84luI#&Ciqo?+qvFoHxirs2UNXOY7?ZeJ zUK$#=N=tj&zCvwd5bH-&hx_ARk>Adc);3*}zZ_N8miFzMLA&UoWHS{ha$Uz~1P9R- zh4guwUNpM<3N|HaGmpj`Jm|i~_ivQKtl?AdzhQ-Ldw919&wS%bF@K{ITH4{mMWa@9 z8Du6i-|_V)ac{zu&VN8F-u)e|eyA|Re9^K}h+?PtK1P;7G ziYAy6ZkV6JyP;OX>B0-Z&2_g^xl2npC+Ui1h_RwBgdiVGsKDaI`Of0*VRHGcVix%n2`Nh(4&?)s8Q zd1ca20SQ{OC1KeI`4P@i8@DKm8$rXmb7$v3vn`!>L)`7R>n8+)Jp;)v8b~0A&OYgX zb8&M;SZ`IoUmr(7e&v}7^}q7&-}pwMI=wQe)K5~x&@%#%)? zx(9Sjf2~L(aQ&@htE0Zbre|05-}rQdu5RAKo5#AMjlb0vDnqAs+Om7Nzx$^tCVG?a z>hG-#O*0x7p>g-@oOgAol+bj+TF|-7!G4(OH+wdKbo+>S22Jq|28c zTcMqvVVN3vcj6g2wskGt(U?a-I(FCTjDPnbG4`R9%}s$#CL7 z$&KKsSY4c4|=QdYl%(s^@}9tU2@1QL`Z8EU}ozE$fh_MNE|CFIL?elGGNY@ zpI^(ZTT|c7mCI23!PNVzZxN+44G*Y*^~gubF?_fM9cSIb>8WezM{9eYw_A9YAz~)E zCMx%PY0fp>b!?us^@O_-W?po=C#+5$HE&_5+OMqHl6%z$tzPUfCM_d=rgG=l(tqk$ z%B?Y*GFV;?jPuKZ%^VNeVIFNAO0P-Hiqmavw|*+1cAv4|@VR6QRbaz^6$vhWzU5wT zwM7O%={@Jlj&LdrBW6Jm?zU@f8-`d^(i|JUTO|ZMo*!Dc|_?gWB(Jp7HiJ zp7DuW?_fM^(LOr8lMtXL#Cr~0B%-54CDm1Mx+etr)V?;E+xbsv=`TDAbfgOf%mty{ zkE-&ap2}t!|JEH<*Ih>GfrKo6K;KSd0>g8sUu=~3j8VxtbB6gDXEQTXrYKH6t4H!m zh21ATXSk5ASDTzHhjGOGM|{7pFvSJ)q(h zWpH7kpakA#tZf!qm$CAh>t2gF8XbRpaQf9lH%8@0_K3dYCMx9nNPdq|@i;ko=DUG1 z!H8&P_i|31=WDU+%-It^RKyHDJibCvz-~3(X#d#ClcnD29;020+5n+R>*r84Qjy?_ zRiLQVccrXv?ZE6_e{p7$v#&997devF*Ldw!4c^_Vw<;9%oi}~vOhF3!`|W^OpP|0h zqXJG;I*r!z4vh|N=)1XnM9Q9jNWuz}zO(^EC+1!J5tZ|MF_{}sCvEJso_DlfM7Z~T zI^N@9=^0U*BTSc}*M%^a4CIwQliFXhitZuWGHO& z;)mUK>BDjgelh2d2{tD53ScUcj9mRe_jAd9e z@w0C0wXE#g@r3gHE4jH9bjpzI0XFW0APCLm?3+HgNg#IkIN&dfc?P;$*0XOd?9PnL|&_5cb(#x0YE?){heVsDw@v|+q)d7|9 zgZn|4fwanLDzx)FzXvRe`zGIW-=YQG6%_a|STO5<*Sl|0#O3x?<2W5BK`o zI<08E16QA}IJ;oQro$KOa_kby+);!4r3xFo_eO1*^(eRfhg;Tfb`BPZK~UySpYDSa z=l!RmmJGTc!ZgY)O;AwKH8pR^iT!`;o-$z8z<&k1PkSd-5SQH2bKj9j9AXUqzbc`o zC8C5nb7_}F*O|Ma8aT=c#Ikww=CP?o%-t39hfl;E958%%_Z^QJNU}-s%cO^oAGe2v zPx}Ib(8i4|v6shmgX8f_WFWC^Tj#A}9y7X!C}y4ccr-=r>pK7M*=HQOO_x1mU)k%( zBgw&viYe>noayN!YqGq={`wsCkmF|+XN>M>-F@`6o~A#V7P&?bk{0VQ^2*O|e_kv- z*!0oa^lj9^gLcNx?@m^p5=I9@Txo2-U7Gi8P0nUjn?@wqVgW?Buc?lc*$(g_u+o11 zBwX(d326kC=ch1@n6hH#j2V|MU#3ZmM+b#Q3{%C#Q&&GNgCvW6I%OY(q6aqSQ>Wg# zb0>1!gqRPDZrChXzC_9QlZ@@8=A(lvq>tZXBbnqrQJY(FK;`=%CoWvDe-&HK1_hRw zzp+83ll4l4H>r^pjehN>7%NR1HIlMX!=xvpj|J^c6yGVeY=Ha2!V!ysvB4;gsK`GNi96V?lEuT(xZW5Ie?b*1ZMRhepyB|Z$B zczxhry6?1DgP!xvY@#L|4EC_PcF#>*iMp1&khG!!b<^t7~va)Z!~=jLvfPDn}uq4^0V@Se3H<}tft zW9fE!HF}|SxV-*bP(cMwZpFIf@kBzjvA7cb`2PKfwV4Jkh8EE}W@yFc&-decPt4Cr zdQV7pc5^Fd??cGLD&aXD@Y>gTfx;{F~iszkeJ8nv3HFLe~=5IP2l&JPBkHs131_n>Q2I;NkFobuU39PP>FHb+-p5h=%%mFNX_6B#bb+}q2@Jh+FG)vCxs zt*J&vn_y?)jsZuIzC39t$}Jv`54UnG1tILseEeq2q?&<1o%qmiB>TUY? z@gohGo46&$K>WlL01^>iKb!-x2EN`c&At>{aIU!6fhsCyTMM*q-5uZZfhn+zPJvyD z1`p>`z?cj9vv!zHpDxT`ff>XgYapzeDZP)Y_a0`J^6{5t<0c)AuOlrlZLumpn^rf+ zs_IDOJ^Nm&etCOV+;})^xLgn}@I{ms2;1*x`);_`ZMp6zFJi@wRLru&~$Mq;G}^T7rQ@lrJDL@Ybv-=coQN}e9uyo$^~Vrsje2^Vie>!$RfJ# zUCEHQFMQ=Vm_K~{D9p)_7MBZ(zx|X}P?6d!MQOCw_Zdmj3#reD#<-5TT6I56JUq0Y z6+M40m@VA-s^N%m)p%L<%h1qJu#^WmkXpe2dk5hWO&fsGbjU-&zLRaC4k~JD-T`mm zLUbqHxs;RqnOW|K4p}DOka>16>hu@*u1rM;m%di@?Z{@;=?4uVq>C%j4OA!nG&R*4 zIr6Q?dCGg)<*R-&lPvq`$}vTIj~Aq_u}D9jQ8Meue!m>|GbLZ3!pXniY&`b(F)^DA zx70d^gtcBpkwXKeyLIap{9U=)eefUSEaOY17r%3F{PA&4ZMQx0bJVp>{W@t&4LEE^ zDG$ggp@h_i3@B#%sW?4Oq$cPeiv4j9QS>r4_etZtU9Q9InBPuCZsnCr# z*KgP`AGdyRQ>fqMNNhH3dz*~%m7!0P(?jzk>N=*be4{ZvtzUA&(RmeTU13o1>}Stb ztC(Jt5_$j4iFL1A473MNV)!lNWOv2j6|-6T_1m|z$~D>Pk;#?qU+;Z?-Wae%`A6n7 zMw}}-aYlpqDFwcH`!*yi@hAGQOL9RqkB;|I8b~>#C=Ja%Y?eZ;d@p(VDc6IdUw&)o z+_f+Bu*0lB&>)des{1*(_K953dH0H-HFq>z&9*b$>NmspsP!lRiEatk8}k-+_D}xP zI|v_KY!z5DuP3i(UeSlb{XhFZ%B&qKEsi7}>9y5~YrTVNN>3-l>D%#n%bL}8jN}9j zOEA$_+t1Q;v0xUOk`up6w^vHw-l67u({G%aIkI=piNAO3Y1%blUjKoa8In_Pq#x1F zFpjq}U1B$(#s@>;gz3rL60 zt;-2w^6Sd#_wSFUrk2RbHSI-e7`GjvXrMHdwcamuGjf9DPEDKFd&kkTY}q%vVoYxC zSD7yJB2D}GrZUYlA6(TZG>52W?+;C>URR;xw(k48o=Q%|aT7Aurk>{Z%g@~~^T-Q- zeY3iR-5cvR6;;ZdO7975yQ5Np?roQRt3kf9(2>X)@0uCIQ z1NZ{D8a431kjp*|+Dd^NU1W@eqrc$U&Rx4s9G~4Nk~z3>%Gi-cf+G$6+(Tp6Pp@iV z0ssaL_Ypd?%W%;!H8X2K)=_N^ysSBRaM=Yi-eR-nXbX)TvN<1nNexNUyq*prEW`vP>P_ivvR(=kie^O0Xo^xQd% z1##AHyWwiRbz>gRXnQca=OUTXL0bko-xE5f`Rf_GvLoeN?%ajbWAx{!%eelQO7zuq zT^4EDAHD9**W1}l3EgJb?8y6bbl;t@+Lo_+95nKVxDp}T%hS`h{!2W#SZd|PppkuC zf62?J9(JEu9%bY^VCDCB$M(w%%$~XUQpYp%TuV!)Pg2spxmR((;68m0;1s~Vv)He_ z#cL6sn^Bj|4-V~bwJm6DRDIoxcTb<=pCEnz{OTo*!f54f4)-5FRt}4mE>n)OxN}WH2_o$o6V2l+zcqy(7fk*7_8b=5oV$Ib zNaFANx##r4!sdeAlikv;%vj_q_|L%qC9CDXl>0wx7w4I>a#7NqSs{V)ga z@_ERL9k(BqDWCWlQgU>a(YAnq3675OAMP7Nd!S!q7Q0F@u=rInS&K=Z;{G1*HmvMy zoapAUJi@{&PrAgXv_IYCZ3>2F%5`|AhWi{F1&l6@pP-9F^?-tG~Git9+w zK<+ke-i(wQsr1CykY!hb{Wqy6zDVm(Ryj}S@fYT|qou{Qaf-P)S%n+(xZjm!qrajD zS{xkC7M}9i9gqZ9Fb>TTQi~QWfC;&kpfEy87<0U&K`@6iHonfZp)pu%^xLI+_{sI_ z#$ePG=9sr)><^?K{38k;nBC&VDjy&HMiyi;X%Ye)h&n~SK@(>mlJD8f0VE_F2=eY3};&^k|QR;lHEW#&f@BaNav~IB|2tGv1nvuu{$e=i5 zE+TDa7_Lj;@ZNb27rk!d)yJ8|`Hj^1GOBmBAy~p}%G`(uU5tULnV_TK*mZB3UvCts z>cijv#7_a$=`&(K*U#)$n>lkf;IB&OF|Kz#T1n&FK-0;Z(&DV`nn=NZLS4OklY@{j zSX(Rky`qt?6v$oeTdTl7Z&Z4? z{dTdkycwzTX!ahrqsz|BTpAobf6*URwd<&v?|(;2mn1gAvt)X$#_&OdoSCW>7$$U$ zayHKM8G(7aQ=xRQ?`~ni*e%4Mr)lWs_%*pjLdRx=hO`kZK4xGrfM}HWEu5K1~MKlG9tpP1EugN8l_07@GwK9r=zDg`(at+ zz38-$M=06-m}g(N3i6e@ni{EjdVZI$1nweXMGZEWJtOVv*6Cq=uWg^Mm8=uiR9?4v zCqh*3g{K~0$P}Roq{7>n;;}O{^q6~8W0sG<^&O259m77(#`jbbe8=r*xBCcqe znsI&BTNGgg__~#h8#s8diD@66AIl=1rP%N3^!WMPb(-CyuW}ZFLO!k!FvH-J9JVIL zLv%aIq37&F_ME(>CrzEkerRWPn1UUF(tNEDtnx7wJ`RR`juHhP|upSc<^8*R1Qg&x&Zv+TN0C#Ps_mz12= z%U8Ku*D$#C9h?Hedm9!JPvPD`HNaVz)bPn29319qMr~Y=R|bARdZR}Z3jLT2!FPoF zdSR@NzKQTWC&NiX$yiYu?EtLT`|ZXPBnXzY3YkB$`t@}QE)}I7T+rt^elOr3+t{qW z;@^(1Nx1)JtbXj>`NR*ZIhQZ5Tl91f9WxF)qp&Sv_0scBzdH>ZzQ3L5VKsv2z%Voi zjaHhuT5LedXWfIh`^-)X8xyV!uZt_qbfQ!R?aJTV={r7)r_l}ueWX;!u;*ROpxF}f z1xt@wFX_1A{F%%K^_jaKP;NurNI816hY=te2u`n_Jxfw;R53?ES@^a|O(Ef9%|hTG zxWmte{BwZ@GYEW?K^s!JAD&%|lE(UJ>093u<}^Z?6`7V@ z8ZuzZton7Cq-klv@ z?JhY*e4OP0QwN9VC{>;vwH-z%VjJM};z{{+bEKEGdGg%5Dq=dSPQjx4?Bv917te$f ze)@N=(&Ca!EqHhR&l-mW_kBD)2_=5Pk?lSPEdYgBjVD}o`GLt(TS;R$3NEft{nU7a ztpeCyIAkf;r&llhKOug?tyxo%|3^)%4F&mtUk?Xr5Aj}UGghU?e4PAyjkhBvaIiO? zjhQ?iGa02fL<*V5vwqwNDNw91yzK)VDr2XtrsNb{?4f(^N7@v>ZS6&(hf&6Vj;X22 zsJ{M`B61C9S1_Fx&mTsK8>rxm^N^?ZFmmxQ5?}f_UE!da$-)hqQEZX}%p$ebgtr0v zANB>bFP=$4@-%p>LK6;ae=uPh(d-Y7{Q|l`a96(Tua(IBxor7%9KdAf0p7Jn%cZdV~J)UI!!0H)ghf0}ws)#ARrP z_s*`Ij|T|aLp)&2GB-jm;#u^;av3W2Xgp#m93bhiih`>a)kwX(ZFuJ}E+{>Ws-H^T zcO6sOsc*{gCzr!TqGHoRnRfR3sJjOZ&B;nyl6#3*1!RUd)QkoDi}j0r?m}p|lrzy} z@-W5EoyRYdo8)~0R#Fcm!JbS0)S$LpHe(lFf7f5F%HOGA{#4{)9S}3`mY~fJZi_* zOR-TVd(*g_V6h&TrwrR3GZ{f8qlqwb9I34>ufN8{$jFEx+DdOY?IDAoIe2Vcn0D8P zx2nWMqMH&wd8nNr(veP4P}ELnp=dX`>!ur?miA6CX=j{EM=y!@50bknxOFge0=gx} z^9SsI2|*GkvbYj7NQzhfV#(D=!fEtydj2Gv5+tuqPSS#$0{#7Dafo&G-|mj*Ts{;N z^BaZWy@seUxWmcF5Vs}@RGXo@3d%t#*-jElf|)%PkE!W423>GkGj8G0iTNjvA8+EF zp4G)r^3TGRBTkZM;mAXppAsEi25ZX=*tRWB73oxTZ0xYAdyGF_1v?zqL!Pvq zy**j!Hv<2>OY7X3H*Khx!nWMJ*FDbJ;7^gr%Cc{*<~W2OF(a4DZ@(wf31w(oli9`L z(8iYBj-I(lbyTGzt+Vj(`)ekQG->GDwcy${vDH&Yjp+EO3bggUSRXq}o5v0=9rgO& zp=x)Da0^ACpq@6ZgXlzlV0#pl&LzdWW~No43YXn0og zIab-!`1EBEBVwYW3cO^$M}~(BSring`=<`t$I`Z77}F>cul(_Q?@u>1KfLA9RF@k0 zSsPj+R0!}?W-}+$iZVw_~@00Cv#W2878VWw| zfblHo(li4d_n}qgLnv}Vt;4$hVdUCdAy7Je?_R()FX}=*Nl!=TVOd$U^z<1sG97a} zQDYgzwoo?jGH7(+Gyz}9m>;g_+tDSEx(0qK^HAtZ@%J40x@xSC@~_0Wy!dJ7Y8!m+ zImrzbUlEX{flhYW)?#UzVAU^jS|G8Oc1=tr8v4LgCY&W49&%>F&`f8eJ|*?%<=feB z&A+>U&b)cPjP77B8Eu0cgmV|W9TT|3w6y4$7?i~1wXrcVQ%8Z0Jbv(C2-LhgcaqwV zuWx8zk8r1UN^{KQYK0mq$}!u8ItB5p+*?kxHBY+Pizz7Ir<<%MdsB^@f{&PH)B&78 zwQeD07gr(_=^he081Lc0+Ha6yr-_g`eYXAPhZHLR=i&w08j#v&jO6xQ{r#y(_~%IC&7{$_0)7M^xUjJxf+)fwrWhoHM-zIvYU( zL~c|_P3tOk$T^rty=M5>u`$xGKYY04d|Tegl#JA+h+h0|X)aZzLgj**Ul`EFlmOJvVTmON2H!#vmglxFudZWXz`vRGB6C;(ZP>~qPVVd6|!*~HCimZ+iOOd zi+BH?$`Ea3r8n@?SlPsg@cM3j!Q>i#4w%OEd1x+bEYqS2GuTT+<@RG=X6#Yw`;U_V`WnxBOMyj8;P1AzW=%e$Ux#7XlVy zf_I-k&$qKv_dYj2->jV|Qy!q3?#%bv+U_l3DO4IkM1RR#*!2;l}vu~rcK7fh?6G9 z#)VI!S_AxYCn>Q2VICsCUb1Uf12k)}^+O-dn2R1yNthR1_e1e=ANA&N;cm8FP)tmW zUf-??cm2DbzO~?_w8*6oRLu=I?IEU))H`X@9!+A&ty`f6JKKq-bRp|Q z_L(UO3FMPKD4kmv1L(G30sT7;eXKngwt#E5+lFB2={PclET=nrW?sNQWfFpj#mKZZ znTQYF zG=`6t=f+g|`}K{FQAwdUz03Qn^5Wr&W2 z%|EN}n)hOHZ4tYqzD1nSPmXk?@oq?HD07Oqr7D5qThL%XCI2{g{ydWfPbyD2Nd2Jw$K1_vrB;DQQL5E?s`Xq9DPz==nb1OXIdH{73K<)brsRw<(DBw)2h= z%k&_fp;8#4rWW^)vOk}d`|L{G5pi*G7n|c=3wDQuOgRv}@-jIE5X%ZHi_48-Vk zEHfz(0HAYRRv;$QHH;LGTX6Hoho`p>jFM0Su%InvZEL$k?k!-K7GP~;v~Tx^uQ|ogt`t0oD@0oH}H02Xc3M@aN4dIeeI?@Hv2g_pP*EbST+ZjB6vt!F15r4Ob z@>|&s%SZFmnFTNyzclXovd&_naN-WH&^byD(ZsR#{Q2``ov&pub|@4nisgq6qA4Bi zltsU)$9}z;iWSD8+tCfOZ$c$ShNIb<;10_G^mvB>R+8+=f`Wq63gV*QT{4VCkC+2V zyP3J*$M|qbbzx*RNPwhzT0+7?9w!{dNQDE}rcFr9U%gv(a7Ty0q}*4l&dp5+v0Shq zE+@wim{PB=B@YQ`+qir#*<)*HOZIDNCY3X{uz-W}`|W|19A{f|9fz+Ixg-i>M_4$d zE(^yeek>8@lnY$3lv*$K#65G;uEvE@rLde&hN0QM@@6hI}vb{lKs1rKKYf`tI}u{??+|K-Ya zQQ}tykrY85GKu&>PPi?DAXEp5aGOK;?7fqzAo!o}o=!cLtf;(E^Lt2ImV%1T%S~#L zY465AD8bNZi}XTFuE@vfXaqm%)4O+fBT7$|Z0b7i)p_Egkf=k4Hqual60T$-D@-c@ zNd}85?b<=q9H7xXuBSK57!pFHJ~kD~YE>OaL+T|SLvkiIR!5UqcQ?{{p=0QVrP0D= z%f`Ar(#UFWtxQsW`}R<6ZIrYJT7L@_uq=4)zfm|?b(8Bf>@bjl7g~3CF*Pe(3dUl? zh>Hq*JrnZ z2f}w>#xb&P-MGc6sZ3sgDgq7NXdC4Qo&H&~dI^*+4UCRYp8Nvp5Gc(qwcgL7Q3)h% zFE**oQR%hC=oMy8w7hE=S$6r*AqVoHzmx3&e>9V6(=hxxcTbsRfTNuL*j(uQHIXzA z=!>SGHmqHpYC9uAfw7hNVM6Q>SK_1=d=>8;91sXPTPWN!iEiDhJkC=e1JwNsQGjl8 z@;D=qLGW^PH0D#LBv$NQx3*fou^@8j9C2ajh|GJHbgzStt*pQI7RkCGte$t#3!C+2 zofAUE+P9|&n%aL%XVIhhKiYj6WpXBsATmi@&~J-(APG#o5oBEC;Iq3~VEq4ySWQBS zlUphmZb|K$YmD{0B;AI4-AlNhubpun*v7PF!5Rz$I4ER{1WHg@5#6O`jZ^tHOvtdY zwN3ULAyvV!TwbH!MRt6Yw~2~-ckiE{hL%lNlf^TLZvGA7H^(0i1+meV`u2Bj?)2Fx zcLP*}&>nktt4MCSMl|SQL^@iaXx_oa1R$Bl47pZmS7S3Wcy2IqkdvIST2i@j1?q-P zvzrgpbpoITUOEh{eddhk(xvJO`w}gw_mlmm9vZt}8f&b~rSUH2nrHj<@2-1jQ+9SU6_Ouy98P*cj<5*7LTh?xx3~WM z!y$u%w<~5BzFNgTa{X|_uDqkt=g;Hr{|c*f#kjrb`IeyM!-s9EmOk>51mZrKnaPSL zZ(KfGNfa08_4)bjs&DL|n_h}jgq(>#A}7C4h{XiCINS&LVH8(6lwhD;Eo?vVQOsQ9 z94hJe^f02g+_T3Di*;iiFhaZjAn}-W(BE7DUyg=x;|>Z{?!&^bDwepcZ~wbGmXg;= z$(~jL>8!WwMa9;n{!{tcyOauy0Vya@#jg-2fQ+?EF9}cK`t`k0QFjlHu~q6QCVG!0 zKdS{!Q^J?h#dCUqNK&=}=LjK>=Vg`o9{{>HO%_A%eLh^bvzN_-xn64m&r%@j7|7@^ z2(3%NeiEV}eE({8Y2vnR;@&x$7+LKuh|t>tRq+-H4>1O1ErXn-^+i#g;%bhp3tzf;u_vLHb7AB$Ly^@;4mhSz+@|05 zq^ohN=jZ3Xpau-=tE+7%T42-TyHiIOciHcKZ8;d{=WM(!6xDbQ$dh{JY6+&yf#7X} zbag#J5HiQS1{njZU3zwAquhaiYHM;jIY+Rtl2jfHUuk?e9zP&yBao4rg+O{r*8y%d z<>lnSP+xK4+}{qeTUUCXmfm1(ZRib;QHzn(GUyIy($@ATs}3-W_3`fM`4LKQSehBr z?DEyE)@aP42{QUb{9g;OxV7D?@FU1k&U?ci11Gh+9jSj&(~}F+-5yV|>f`g9RaEmy z+bjMvBa|aTdkk#ZsJZ)x26V58C%4efU9fz8-q^p_WJPJRsj8zVvKNjC>-Cb2I9GEN z@Fdx7nSQ2{s5vQRmaY=>`keTD(!N-orPH0A2jyy^?PU$1SUs7QmCb6U*ny7*r=HX^ z1R^*TJe1q`urqo42#iLZtDG6%zI;&(aUFwc?-!m3 zw254Qz6iZr)vdR-@xuA^D~8^oA|2iL4#D6_*Tlv9#;*TL(0;O0Ebe%PmhSiU?Oisv zRSH{Lwk2JuVkqbDZ_o`;3T7=zjRob03F|#=nwl(17Cg)I$Sh#14bSt?>&v)Z`B=B( zdl0Z3Tx|TqnJoC`A_yJi2|#YkI^WkGIA{<DfW7G-dL2`>>q4FsiXGS*8~cJFhYLfEa#WEnJCC9ABSyISykdF&QEvU(-9}{P!72pOi{yTk z2V}~TjF4$OW2$0#fGBh6_dd&{MP*4r&%!T|X90%{89bP_8_cKs^R)#Wa)f2=1Tf~M_x%SEhMPtc>(5@j;C0zj-Lv>^+z?#; zKNi0WYLtqTB9wh8T@uSst&L>X%L?v?Ye@;d#e!$2z`id(CCaoWlWPu|rJFfOxYvT2 zE3_;!!o%N`0)F#-Z!jOFhjs5>TD!RY!mZ{n4~S@caInw^oPNF2zACY;Y)+1(h3ppV zgVf2y!eTkc6$MOjFD3I1yvmAXMvFrJ-VFU>5xcHD`M1&W>@nC$xR1DccP! z#@*BNBPn0`smx_6e%S!2daLmy|qAd2qcd`(n8$HXM>L{9TyY4ImrI~WeXfD@jZTM-rFa!XDz%7JEFhG<7* zD44s2;m-=>2%s$3Z;&aMNwZ=2lRUlQ=L(q2jQ*xB%v!Wm1+o_oq~BPwrxe5u`)tza z&dUskcMzLE&JLKBz2t5`UN?@@d=cae4i_i_Cl$IF&LNR+eJKHdBrzUz8&r{(&g5N! zEceC6UqaDdCfWAYA@_3p{Gf_bGn`T|bz9B1f~W`#GokM#tN{tu0G}v}h2{@Tx*;`> zZ~olUyzAp^`*;Y%m&niYi4q>iAmSjewDRX#n0D`QZdJfXL;Ga*G!9S%xqbWhpW+hV z7c3sb%S8UYJ`-jkdf1(D?a3XsN6>HtSH8d2ip7=7!W!_;j~=<)p6!f?h!;5%`R1K= zwxE>&y-2t|4=N{KS1i!OX_(~ixSo{da!(mCFl|B7CByeyTW?96HKT`~5*g_nvZsb5 z$H$+7@Jz166rZd`?OZDVDu?Gn7P#!*h_Dqn4e4Je-$!6qq1`UnjIoOYAdk%kO zE-L7%8TJ14>uX+$%S~B0^kL{MZwqK_7u~gWH~0rJCx%X90Y?qggwmZymb1yeQ$vv} zv-;vnuKBbsr$5LVUR72`V2yuy!&;vGYG~5EgU+w3j@_aNG?A#2N zhuh}1$F&g#zP{IodOSih6inxw<^DhelTz#uw`!jRon}#7KVbkc0hxl9T_TOyAM0t6 zNJ+FMw{7Wo?;}n-I6UVWbNYxYUB7#G7%etD3dwDRhvfazLHLpWmnho3(Ql)CC!hPh z9Lf=$LXO)d!Y}Ly4$7F|*GI!>As?hp-5(vz zRG00?uNYeX12bfZX6twTbz?AJ=_$G8{(EMnG3g4p;M>*?-zz_TYNl6Ao-Eww)VBNl zdjMw3X=ziu$8F6OpkYMA2gVOf83w#X)=Vl{vhPcK!-Idnc*F=BZxvn%Z4Meq^!R=* za`^BGcw`$MSgh@4x(&c<{*j&A z)pbT^VEXl5w;_+y;lUTPbrrUqB3E7*Gb}K- z0nqaCF}vIqd>S6S?@0DtT-1a`t<`qMHUIcpX>KIGHMjQr_a+@Bj{FKgU44|0dcT&M*4;ll& z@`EFmSTg8S*zue<+4_NU26 zsVf4vZnd2;LkRdq{;kKQ$bK}`gnyKz|Mah6X7a@G7X;!E!d$zy^ei2@#?zBV07lST7-sVl5+@!e|AP(h2t6JbcjfV8%wP-PneH~3 z;I^3h1thZ#xwZ0YeUyfp039@ue(}i3IF_RcK%#Wg1F8ajDO`L4jpF}@iV?yFFIhls{UdQQ=~iDzF}#6tX{OInJ&tR#_|PuVMPh zVLysp1mJ@Q8g9JzgV~J7e^tI>Q zoLV=#w0_USSKMga<LWDqPPEycg!Nh?Pt?as$TteCix$7`|vq@(u=yY|@&<6@~1p4_` zaTO%$w6vSgpKm02!gtJZABUHWk!7sG(7_>&`{-g2$A41#++MfEKAg<-Or_TXx>{r$ z-@bh#k{g<(X6%orcVSp0i=rGy5Q^~aCuqZ60O#ECo>vJ2EwH^TTS7PU*29N&-`kzGl#nn%ZGxaS<@MPr&XdPk%{=(l4KM?zH9n>Sb$ z4IS)C;KBz1fXeN{5YbS3?gqi<62Av8LpHY6q}GiR_xvwZ?DQ6~fViyhAQXXNJfQWG zKrO0)&~#*s{+;g@yy6RzGNWzRkDIy!4)MvbP10W2O_-BGhy!q^>?OnU*KRA{zN557 zUZHs?sI4LWFzZljLpmb)%ZCrkj0)*jiYt*s6A%G+1Euwom29YdUx0IB#BjWx^J26dH$m;9 zL1bxp2lE&BP!QSBtj4FOJ5+chumPIDnvEC;WJos+{)>!}F#g-wc~bvb8ft3A_wJEw zz$#(=(Gpyt~Z`MS;qrldvRv!rqdg2yo=EzOPp@CFusljz&b&~ zAG&+DHR0q}T@BzM^b}9BY5;5-8)#(J>^GI4K%3yqK+-YysiFXza3g-Xy(H73N}Bd^Ds(Oz%n% zrm}wX=f;U%>q+IFR3P{D&xkuK3&0~(`%6tGPre(i+vVdG5AwfFuKmT5b z;D_B6)FEP-e+CU*;nSCw(PY8Oy>GvMcR2KKinSibKsTKCFnPeH*L{2Y2SHq|V_Wi7 z{Aj3sCcRnC90lQEl>IAK&Y@VWWr@-!Iap9Z5i9!D_Wy{&dJdU|54=FSBD;N3;3yaL zQ8-Qa=CKlJ_}w8Y-0twvPy*C&JWrcs>Ii2apjD}A_FLX>_ z(W94bSd4!TVtHY12cRmy=gOeApDFS~lktLCQ&hx4?$vfFBGH#_m@#poq_GF2r_Y5ePRp+x1_qFU;)TJ1#i{b_f-byg5^0w~ZRwY& z8TCG+4+S0AFSw;7Mam;EnMTTc!3pdxH$%7Btdr+Z6XDw|laR z{Dw%EmXZQ_!tBy@t*H=ehFG^dDb||$Mh8NFL@9H93U6=Yg~nUWKB3@teZ^@AT5BkO znGFYgU&3K0p+w0{>yz_iJU%~|%R{eVdupC~%mU!n_3YhS{geP9^B#)K&t&i;4vMbi za8}%#O= z)2b8b$FKjvgO%ng2SjXFw?*c3N2AA#Y*TL{mR!z7nW<^Y@+j7K`I9F; z7!C$9n<|P9{YNH4Ij4OUz#jG*vS`7CopTEy`Y`8Pf-Ru)MV|(b#-EU_9{_hewd`=D zgct>$$3T+pGA5c)+cyLkd$q=olu<=~d!G9b3Vg^e`m3?|N7S76KTp)}l-b_5D_*eM z?^cltg~U)>meCr!_L?I{KbjTo^S9`EZL zTCsOu0KhGY^_{e5IwM#S^KcCu-wD~1+qv{3cD^Dr|>yG{Yqwa;% z${1V&#Bthjy|C`D9IayK8iRaLi2vM$l;o;@e)%GO*OX^PV#tzvZalez%UCKjPH84_ zThr8X+{XoG4G{b5(2eeOc{{1&(J$e=qo#kGMgX`yAxa=l)YbiO%3wF&!PE0K|G1(R z8fwdMF(NTxbXc}9Hl19foHiRwjohx1#Znlx?h$Udiwh6wm4kAoLYn7Fa$eDQ;}QM) z?V2n&I{aTK_1azo1IeK~NXnw^ zh3gF;ZEI^T^-O7^on3FGL^zoFwq!7*A_In-J8>RxGyU+-ri?ExKTs?Km-v{&j$U4- z1KW*WtzgYeE_A1`FwA9+`3E^^>9GC#&BPf6HEi%;$!^_}YmPA9hXKT0I8J@ z!OqxIs=|Qj7?Q{Ime`%(XIIgPba<{N> zHh#kK@vpE|OMlj@&)-~tJ}-I*<3YI-@YNunuJl<(qd=}TIk7)Z{q?J%0Uov_(pE?IyswXsxDwBI*0LKQvh&;CIuuN$wYdN|n5hNznf1Y+K%x@< z8Mm0tpK!NNdmZ)G%U^B+9}K}k<_xg)y02ds4)=$ChJ8M;BW^{#a{WiPW~PSktrtzq z`UgQy82^0PYsN@W;i5{6!t0rT^$e=>fIR%GP$} z1v!y%t8S|G%4PwyK6^G!M`wnb>?5}VVXS~3NBEAAjP6WAG1(l4AFUZ>HPtrhX2KuD zI7nQGTn0_uC*8aE{)mV(%gaZWo$RNzKoomd7yv?r1}G@5L~Cu$f)X@;PR`C5?ncE6 z1@~49TUu_7aHe1I6APb*=DwoGB4j5u%I)G>v+vA)XLOyA#n7)7!b9UHwF!5!mOZeZ z*_2#!|M;9yM%w^g>pRWWTuUN{8bLV_4nlymk&SVXBcYXo9 zK30^kU^*iqC1pW}+7h>>xp^TPXQ#x4Oj&CEZt~vg*DssSIJT*Ja<72FuQwIV9FnE{ zb5mB?(X5%PO!mG$9@0g8lcM5$!z{xmVtX2z+&5Z22n(ICDbCSrt*EX(=X@Jn+BG@}MM9N|d8CwrekQy}ND-(dZ{7@9YI%_Dy4cy7 zrjF*EtWcGW=jU!jSkk|Lf6!VQ z9`ubfX;lFXGYrqb;sMBpXu*O7C)d+2 zberh2y&3I=2iAlMS1DTBT`4PTj95CPdC<}A)ASQ=eRf^ExDtsSpH{eH@U+uONkQXO zB2>$LMNBp^vRzVp=-nj05^<$KtEdN4hV4#TisCbG&XPQfKRz!6bL4;BFfD{$&inxA zx=8&ZC-q$Eq#hD&;Clgn!&0?TqoT%6Tr}QFGwSpUo59bEGM@T;E<0mBc`_rkHYL@7 zGqv-dtubruwzo^tGNMwhU%BF+s`B%}n})m4W$ZK+e#l%LyE7}yy^zvN)&#o(rq`tO zRrJL*B4W(ZAp<|xh0o9N_eX@OuI?x=@QvGDdGR7PsfL3<>rZ3ND7?2dqki4Y?CE#5 zv|`VT5<)m6p|nevVoJAG>ex-6zCPnwXTHU1RO8V}{Ll+AS>C}@E_cnl?e#1VEK;;! zePD`d?Ia#rFVVg#si`Tp{Y+0kw;B8?KRgSVXlpKWVUm_!ICe}*WYAbW;KxLr(KZp6 zjgu13oY7TPty<~#tB;Mj<_3^PQfb~GMNNVo0 z6=Kkej@`pUGYi%76?0db))_>Ys?0Y$?65d7j6KRI7|yxJT8oXx6_QMl|Nf=tXyT<#Z$`#GOD+M z-~ERVV{T?kD=J=KvGm9BsSAO@-P+T)Jb&?`m%Kck&ifk)#s&5Ro6>C6)YXxcPm}E? zE9(U6egFQE`v-2I@DB=_dhS&EI1{H1L%+yuP@2>`EP0%x{gNd~mG4=z0p-U(Yrj-< zx4z^at>x%*^40fZpY8pn$F(_dW?R~X-CRP1@yn5i2u4VYBO8Qy=T#cHbev`%Hfw@26{IQ8schIb0?#I!a>s={I z;SfOr)vL&ac7IpO8-<)qP5_)RFIq*fL1rHRe&f78*-)eKCzC}K9Y=`v#m1B(UALu6 zI|xnVPQAIYYc>myFK?T zYp~tQ2j(%-?SqUV0h|J^iJaWr2Y`Y-O}{XRQE}7W4DzJA5RBe-nq$Vv8Z*Yh*4B$i zDefr-!LyW7m`xv7YE8f_5BKg$ZWN1k85IPtE!iVGUt z*=0XeR>mbyE=tYF7*(ZVKrYm8Q>s-xeH7dz0svetfmn_W{iL zAy%!@eK4DDbNqP8Nu@ZH*&IJ)NXXK}Ex5*d%E?V=fr^ZrflxcQub*nOz*@N*BjR)S zhTRxYux9Syl=dEnvRt|5j^w!{MY5RX=lkn@@N0=ybZ%>E4p<0dI^RkD@m2HKLJ%r< z!+i;Uh(3^O1^S*97IOe{HyJ(_2y;= zu++KoTJP0C4vJC-(oRI)->8?GXm?mV0H3<{JOajjzc}c889fLHb7NzpU^FN)a>ov; zgo&0Mtl}yatxcCf7M9m;-XD#$O6kRd(~9RFnp;?`^75+v+<(b{+oPtqY|}7d)~MOj z^39lw&sJav$T7@&)v@T!>y~0Kb2#D5^9e(7o&d>kw>eEiDT7oWG>2o;yYpk(ulGuB z+Fds(-8DPWZ|mbh_a_-s(jUt&zq+-O5n4drudk%EpV?p6@+X8q82~oz?kL6cO>7}C zAD@AN`-K;+G6u=!s%IjN5SM8`>N4)to^_eVd{k{*iufxS2S{MWw*xfAX43LkuX=Rv ze!aqdR1dp1d&;?dLQ1-WVA;b1QX%9&%J8829=60{4-D$CrxlN_B)+J)E!tcDj}2X3 zPiU@mV$btxO7qX1-YUqru(C>jwPfpv|Wt^#MwW>1zfkOhSY$`cCFhF;Kc{Uh2 z%iNZz+HfJ`|D^_`M%b66#a2S#^&(M zq}1HwJQX;TTtZ{>Dteolg->``yz`I62U24SBgW3UGWJNYm@3UF|z>u>zjz3JH;@S679Z|~6s@0z&7JjNmHa?bP_UzS*^r$ho zR`@A_TA?gy=ePt^HcgY|#{>IxS-ekN)xgHrV8s5DF(PoOYdT>N$XPRz@BXwFkJg24W)TAeB zbw~B_*iKjc=pCb*8r+qm6tA<7E&pX+g?n@yw-+dobM)|G!=^Xxv;D2;(IfEp=<%m*}0jPCOkt_UTdUl|QEGwmm|U zUs{s)!cvdvcG2ERswyhZ6$V!JzjbkKFK}Gb^*4W#-PZJmq$bm9B__HuM1g42Cewho z2%rVhczYOXcIVleT zN?R8@(6UA1sLf1D{c94u7lN?rW+g_CV1ldd(0ov8a4a4;`{ncd1+l$F;crFc9Ja1g zGa&&PYAr8?4N?|FFFHNVr`jh{LhRH8y140&=;y&_7Y!e_uN&0oL;5&cK5I?3%u5P| zLCU7KH4zL2nk+r6eM~MjAL=98@f+i5zpfYVRFf#)V`6V^JE1-)(Wh&hz74mtKZ#}I zfamH)TQ{#2D_3UlnW89X2Tx}xNgh_(fD1-SfDc6&9w0MB;$eS-^R=WV^VxS#2NP(#A`!<|ctU&Mi2~y_$dRQe?fzZgA+}FYg~r zs?hI|`N_OVIR-jW9R%m-WvlhKfto%pX`0ciF~9elELO&v&D*wt zu;=0}e^gdRK(qGzD{TZk9)4t-vpNcD!spqgR&^a}00CGYvq}l(f$C*h#9Y9xg6r3% z1lpdl2i6nV(n7FK*fH9%-)UKsP@g%x(53?)_+To8SB4J)-LNg)O_r^dMu)a-eUPSt z0W#RZj1jtkN<<{bh;3dAtj7gRSFYK=zeGS2UORN>G-9QhyLbV)r^@g0muUMU+^tfV zmFaWHo6g7HjKYXJjfp39*QGAOXHJy{nzq$i>goz|069AwdrA7lvznc&m*wofhTZf< z{TJ{eTum(Pn**=JXN>d4e%35bEme8Uyh|-(CHn`TOi4NCUeV4cGm2G1V*14x{~x>k z79XhlrnbRxPWRipDfVqVMwCVa9kEC99QhxJ7}@4A8vY=zic1-h?OCQ`;9Dr6)2L$K zy(5oyxvMJ*^JtI~o&;J8csK6Fk4^m`vTIIJdjSRk`4VY<{W8Zg$@7bV|A+m&1%?a( z+c&~|#M1jl=FMHx(xn7Ow(rnE#jd{_YA4_&#QvP>7cZu<-|NP&@N5*qUAuD5JC!WQd_9a-Prj$-j8)me;q1Hr%k0;MQu__&0rs#hv+c&S&TuOOf0ZPZkpM=VzqFaUUZD7W!Aj->!fZrc+n z*8!bIftMpD?UWWqmN+EFJ_%hLP+2utl+9qD=-Ui!R=2hWa()7HH|kr#`cM8zkJ`qhCiOI4iX!I!Zs7fL`?gvba#zx_wMlAZ|K_`_5{u<51s4b zaeDqdR1Sz>`AlLWB0i93?lIN%)tU{zI+G0xmxdJzQe|9B2r0Y zst&|nc-PQ2%s9J#cJUgXUn-$n@d2S%eXAi^94dFJ`^Vy^4c zr$(NVLUbIc%t#~vloj+|;a@b3EBeZsIAr=3=jSh{yXUaMc)dG6*z)lxSq>d>F){IvQ9wh>*OdnMjw)}ToALPJQ>TFSa|UeO@qaYYSi#3=T3 zr_Rxj&n)jF1YVu*u?aI4(Y#?k3kjnM4-4b%N1IhznQ&i!nzC#=a8&LVBt&i)v|qeO z&wHcV%F*#{O^v02RLhovxR&+d1hmNhw#%y|o=mFN|Ewh~ws37cv~>(SirZ<3X>>u2 zQr>ni7gIt))qu|BEA+VA`Xy~?-KrJqOXpKix!gbimOng1R2}!40S?)^-9y-deg1qS z@V)SkwyjVX^uu4^8juVC;7%A-&XR>QX=!9`=?P#+R}flrg~}iA-cCT*2vN->a~}?t zR5;Ntunj^pVGLL$;=i1iD3{XVxIgPjXKJ67Y2Dm|&Ng2V_V7ZEx&DThQ5QC9#hV@3 zeLA*gop}r|HD)B_>NQiLi_$2|!2Az2X{XSAz{Wjh3+8!M4Vj?(o%>dys_`&OJfypC zt8$b@8sY}>e)`LaKSHO@o%^tTZ*{M6M>79xclB*EDo5PN&zBNtc2U|-zA;fV0-Y$` z)F)ib{Z$4y@tkPc(28Lp9?GKK(WA*{+V?%=c8<8A7p;`)q_i$*NWh`)!#7|rTAUgf zqTJjtX7+~5*_H1U@nst-H!;0(MX~1ianC?O+Eg#&8Vjqa>L2XO z8t_@OO1ZXo#q9WhF$Z0l=TegOOl! zW9{YOf?h_x22RJmP{zg3cdB6@{Z&T*RH&KMYe9+K1^^coMgsXfEjW?61=Fktgy)`} z{g&(s{tG}oc4a{xFFjz@Lq?_!>j&XUpo6jT@tFMD2_*1%B989NssK(H$6u7q40Dx@ z%v(4Ttg@%T#*hyUx|ye!IXh#SxHci;G{dN$!4skeQcm#ufg>TNzfb>iX6b{2^TxNy zRP@^^0yZAOkP@t4N6ztc6bb@I$)tc`gO6ZUdY9y}bt!V^n z)){d~AIP%z(v4%rExtZ+`DT77re=w3F^jRLlnkA^QzXXqyl=~$vKAs}lQb4^< zj_eWLEOBh48%U?hqu;SgY2-F{I!EML8--QD`n{yG48)nVM-%u1jk0YMiAaYvG?Bc# zI3SR$u8iNkG5OeMyUp_=%3r=DphN=~`swwjCcdiv9OyvU2s@Cj`)^L|7XK}9^Jdb# z-__Sk3g>1!T3d_jzSyogb?Tka6A(NLk4JAScy8FHtmCH3aO6rqwGNHwFmbmyLhgAE ziS4wu@yyibu}i=1N?6k)fAxV~;maSKL{AjF^!2M(9eNGF30Y9XA^@Y-ajp*dL;D?; z`PJfAqd#C4#}}^AG$n}{{j4Ig(L|?-1S1+_-1_yBJx8_{Uf%#sw6ZFI+>?})M3smJ zHDVTtA*+zc>FOdeAdXjk!94h=0O*9KMvo@7W~g}KC^tif+&Qo^3IBgE{7 zmX8>3ZLHjB(@@S8H;BLgPgI;g5x5Bj!+$?`d)xMcH+{oLA|V@EI|AdX2P)bnBwHknhh@zPGQ>Kb|IpgoG3n z+ntYJaiu?Zz}>3m3+Q-*-gE99#!5}mfB0*-s&vt95^b!hD-U|U>|X~su^6}aqZ)Ps zl2kZJi9le?0}fAQn}IbRB~l98e&F$d0HLg79mwecKN-actPpoF?$cR#!|=!Z?Pzqi zv1EL6$wd42(uE;LO80@WZ5OYzD!Ke8zAYyg9Fx2|^!eZBtnKWMw0m1w*?O?;)_$f^ zQ>3euTV%)YTl*RtJ&P)YnIt9~E8}VKq7JWIIn-xf6{aU)$kO{mPWn@am{(=Tj~q2h zoGhv9HMjQR^eIFd(p6EBKalW{zVG4b$Kk2@y|xL5srmRh+3{S~ues|?2rNzoD2SjG zYN~5CL8*%nfc;b82_Q+GtRcIMQ^j22v`K7jtbAn?oIx09|Fl-VNy-cN!4ng*NTmFIaRG`-xK0HE}hLMAJW6jdh#nJ1u%^?BNrZk9DtxhlDKV4kb%- z;Ky4A-{w<$YY#I3=v=?~(DZWhTK&#;Q%+`1f3@ar@!$z!{U{4eTa?^)`}rJW9j9p; zvMsW0=d9_5%!=SynIL$%^ty(IRZzQp!esQN08CS(QR_+t&>eGCd6)1IqT?#uF-7H> zC`(605o<&G4|3!es8Y*SFGHk?IbhdcNMs`gQs~R_)I)hOk-xr2P@PmN`jg3Seq`C znW^XNKYjeTD486DW?{zs<-G+xg>3-F-rk>KqY-sqYUu;&_TVMg2DhPv0B8&5LQFkj zO3AZMo7Wq(d0IWp;bqOYUgIe=`rBvt1~Fjbr&jiw%J=B|$pV81*!5_|DN0>FfBsD0 z8osAE0)dH`qYWWRr%hK#wgU<)7e*s;YOpd;Ibdi=@L<0}9RglzOUNNonErjjdQKOB z^a6mXvi=Lh1FZtSWs-}^JqJ>wj~sc$8I=kEZYDnc7;NA0;XALtXoU~8+y`=MourgV z=~fegJV6jw5BfG}SQeQ}?^xyOcjb7mk21)U=bYOBvBQU33Xr_VXX=3<0RQ_dM8}XI*AqUT6; z+7!-qy&rZHmiM^b%+zm4xBdlzehKc<{^VJ<9F|~m7xte$>&>-eam-Xj9;GFnQ0`u? z8r}!P?+ryf80r>Mi72JliGd)~G5o~ezIm;fKd*44O||mDGp%kriogIsPhs;t$xM$6 zWP&-z^na=Q*J)C3$xg|u2);VMu8Xy11ebuXj}P0m?l53LJb|2(oPq76C+Vg>KIagG z@nJK z{z1;Li%F#McI^UiAEv8oY+}NvODVP7J9?zD^6yc4?F1kPuI7dh9|)N@mOJ5M`%WDB zF(F}!>MK=x|32RHneVlnbfO?$1y%-NS6S0=XCf#Ss4e|BJHgbxov_j7t90sPKrCm; z@jj})UaOut+fvYuWZbz|J*;{5G!_R>^?e+nT-QNPr;Z+F3nt@JVtRUXoC^bJ;rzdD z;+$pWeeDUrc_15P42FXrhW6FzzSBRZ(pd}ToyJA-(JL!Eg!|COu3h_s-&n}Lb}i1~ zBG)Xs_4YzYu9Idxa=K^k=lErVH}XROx0bJaEGl7+lCajvVacTCDTQH-KFamyc>SG*K2G?BuT$?kR+HXhKu9u!6zk)V{6)djdiSW#bT8Fz(n z3{YdI3)BA7A-MJmx%^CHE$&mT0RyHz(2dLkt~gDZMw>yNI_l{SZM&Klb5W_v@$vV1!x!K$0B)I4;W2`pak#7@=)*W5X@=?`gKxy>y_n*+B$d``aa zrRE@piN%q!E(EMwCoxT2s>n#g;1y^5=X^Zi58tglf0G;R$Xbji$O^ep2$qTL&#TaL z#4?4R?UI&NP7#hAJ=#);*rP2c-&#sQW*4FE0WixX21k8TA?Ia%?qxH}MAMS!d)^af zAoycLwarX~Ba0r4UEaGH89tJ+Wc8;ne}LCl4@4$aRZ`eLY>T3n_}j~+c(pRNa-s;zwote`*JBY?g@95+Pn znG!MP2m+%PMmQAe=i=LY%2Z#y)wPELyEJ>!kRG!64A4b)LBL^@k87!CqZ`^$} z4|i!M<*XX}dar%j2g9HBh9Fst?9;U@%*;|qdL7c@^^9hu;lSG^&5YSBKLy}Zq6s?T zsICA$W&{w83kW82-b}f<42-=(qYX}wWLGygQXAK>lPayw?@5QAI&^X>{rX9YM2sPB zClJsdSwBDhG_3(HWv5HJYq6$y7zL%sJYeIm+x;6~`2YOm&!E^|;PlJ2+sL^D`J$J& zpXw66Z(pCDH4pCIZQ1VoaV}G}ZRQkGx_WZ;f)A+zaC02RejLZlg*4MCQ-I4{mo00( zanLd&BVqe>IQt#!gy4HqREV*fknRue?b2I73Kcc3<9f*e)&n5p>e^C3q-**_j~0R5 z6tLgWRgECcaG0V`7eSH02r>JD-?@y84354)k#s!oEg`^_6~9yOp`dWH!ktV)M%>&A z_Eo8k@#)xaKk<)Gg2hobz5DLWxC|F(=cn(zOVbwsQQrV*0Kj&MW{08!HzBE=$;pGG z%*c(p>@*_EVmPZI-$k+}oto}wH+|~VXRlvJ1H?txjI#HN$yEEZtq8kSS~|<$-#^qe z70EfX9McNK0n8ZD9PUzxJ6Q8(yy$clDZxzlgagrGK%IGy-fN`v?*03|D<2No-j$DH z7PNiu-j>5lj@gr{vwzS&MEG#h8aw?ym3gtxk4gbB`y|WXaQpQbeUC(b+8|t#c{f+q zQDq$Wjg^)35#9g?drao(7G0Bs3?zJJdod80_-fR*?uKjSe^9q6beEatC`=rEUdZsHe zZE_1fIlaU-K22Qpxz1<>OMN3wgDZSl?K1e*&x0csCgb-1vbbmFJiX_#BJ5dfaB|mP z#*m)pXU(2{p1qfGGgM?cD(Hn+d>a>!b0e8F))P ze>D2sTHI$%M?SL~xw%std%0}h^2eHv!II8X&unZ0xlO;(+_L2Yu;taWXY-aXpCb3; z0uULUH6ampGiScSE$Hm*Ok~yfuO2%!cd9u@i@_sgZ0P`g;%a1j4=f(O9WJXQc@hF) zYEb#4<1Huigxqr2H2zP9)+ZTW`P2vIa@5qIw-y7e6QKC+vOArKM^tz#XLC8Vrj==}T6^}1pjv%%mEKCOU61D?}80{2(`@w?;)A&H!Pc$~x zn0$1OjSWj(4ksrYlK#qjku++e$3+h6YWhADwi^_jiAMK+H}~o($_sJ_!OGtLO?f%p zO&|CS3b~leqNlg3`m*)a74Gi(2$)$^^=5FJamkDeji86~Q)ar1vvq^+@&#Tb(sD7oN;w{%JGp2TGDeC^ zaqOMuj_EdF@Mtn2A3n@riLHtZvAH3`uA6nP$0yg2cV-vLbj0P{ic$iXPfa^V%tRb3 zA;3aynvXUHZ?-eBXJ-C95CNoTgumdu%O94|)HKa1>wG-s)v{XEgCB2Tq}#IPuJ6n! z3pqu_<8}Fswt|Ldx%Q1_F{J)_2KjC0Wm{bT#~ z&a1m^78Qw~Ub&x}t<+`3x!E5TawPQ^Rkl6ZIHIeSQWqGqx^p#7UxOXSn*3K+< z@*r8)ODC9^NMu7_=Ehm5iA%M>i@%?YH1*vsYXW*I3i1c&Sr#z{iT-S#j_`+EO1{IArAGmt*D<@Z>3Gs({f0+Q4XzU zX%qGK+_@;9&YQHl->z`atz@FSGGDvrwOs3~zuPHp^=s~?7UYk8-$i)!@J#aaI2J45 zX#3*#?V}qm1{|F!FO3T6H~zcR(mf|jNX1f;IC=`Ek=+Ckh_UyE%nkWYyCo`1oQ}*} zA%H7RKU4P*`$!FGRV+Wg_x1BOyy5YpX0c9cK0>O?&q88T z&0ZQWJhN`qR^o9UPLC~9n2B|PmrtjChJ+D$DyP2gmcir$t#|#}H7;ZZB);XJ4bad9 zmb~H=h6h9MHY#lTm;UEMsb|PP`ycK-vz66Rht7<9_@w8C$6cF?ap465ii3>9An+q1 zR6|V#F~D%NVoM?TV9phFBw|pr=zZpOa!Rm<&Tb3Aul9X6eTdk?(A_(9VxHMw5Cg0s zAppAUy&C$U#!UXNJ_hH*J~EVG3=!0`VB@api#KmFK>XTvRRc?U5fS;jxEUZZK3+;- zXr|}UYi?LrvhFx*mM;%ojEXOxFnQ}}JLGB$W$q2p6Yf8FfMZ!_tf0g!u7mD1-%$O# z62M(R5C}c%%bN(1sIC2eJ>&^mZ<_GgsI-1@@{Pq$ht_-9MP^C|iCl?{~M(u?c@4Y#_S*S2TP<%%3J#^>=JVTDL78en*db|;XVIi41a{6jbe?4UThqR{7 zWxU#Xa+=@VV=g{&^BHh9AZsgu4Fo%OG|(4^PENoBL|_;Nz>EPQhX+)k>%c`w>eGw& zq`hq4KE=mpY&@VhWsMBc&d6csVG2W|-gfO$S@<-COX z)lahKOq{4LsyN4ajNG01$1Vm4+O*Q0BXo7CcRQy|^O|cfE`ixM=Xx`aV|L+Ek1@Wf z&kbZvz+uCm%w!%BdsCI^?&lhLxM1(gP!U^~lLdBu-M&#GX0~bk3ooGwod1(jnH;=) z-n`H~dydr^&e^GXxZA&WGNF^*VqX9SVLlo4<6GP@y|R!QKUBCZx(}Rn*ggH}h=9w_ zcE!XDxG<_Lt))O4P9bBftL&7hIG2=}TA`-f#I}5rb;F_`ng7G$Tb;Q!bxOsRk*1>A zVUMBzg=vfXWU{aYQ6WI(sN}W{a%_g!J8X%Z&+jE}kjTq{&swZAN;!eBk2k?Op@H{h z$wL$4TFK@SYyG>3M^5->uHuTJBoBOIfq~%gYDCfQY~(d7&~j^G!Gy?@S_VqzAlNagJ^{1B^ZQ& zh)|P-1>cUWlu8xvr{&$;G9&Nf80tOrRuJ0&TwN2@rklPYz=hWGwBii4@Qs@`O;V9L zHu0?r5&~ny6p~~YQ zSC#!GHgH}W7kq4}syQ&nu32@Sx>duO3Zg3tL&w;87PR|2D&yfeSo*-S*o+S{5x%{H zV<1yo2C$qv_~6?oS%X7OZ7x)L;eY@~uLGcsh}JdV>CpH45wzqY_&C?qOf)GJH4vGx z3l{2}k&@ETz(W6;_>{vnP5YmKHC&4xJBSxLm98+N}u1}Dqo{%?;#|H!2u7ptC&v8fIMn z&?KqdVD<8Kj>RNUYf9^!3w~dqj^Offu&YFvbDB93Tr_#r*NzVR>MOb^jFdy6pI)9` ze`0~zC=HDqvZD!wpud27!71$|>$`Y&0y#Q6QwOg4?YE%q+xM|n33oQ?k&hRp`Zy_VY6JP}B6`>v*3tClrrbKYANgTsfxPPQkKB=K+;N|NVg{!%5L9Y27C>*@qZy7 zy=F8aAJb=-sHmuH$V{XT;hZJhvh>C&dz?ru1DL(J5n-%DCV%4+&i z3JD=>Nm{FPzWR}l;q0qlg2l$i+Bz&WR6@{lfo!N@0-zy4CHj!*_tdFVxz9@Vr~R(- zsGuPGf@G2H9Ly-7K`t%{U>+!hD?1+W8XJGUM${r??n!@GJq#a}McExB>Ur|=@&E*B zgX&mD$<#L`t}ph+zrhrt{~s_#$@|e6d_mA@`j_80!AFlCCBl}q056_BlkU@Jim#Tq z-HC`-ls4>W-~df1T7{%^^XB&3uM-$f76TQBS(1tE(GwI7pxB!-O%iOr)XZ7`?mmVi&@4$h+e*q^8 z9>!VTAcn=6$VudIME1T4#A> z+abrr4SO7?tXT05r72flZrrwQw}E^q?ze8<)K}cy!J;_bbL%RT<^6uiW{ry)P~@b24hH zue9%gto0x80~6<5!$=ZUeQ-Q+R}LIJn5`&qXlG8IoWIRF$_SW)%qiw&+G|nUO4V7o z@B@K#V4P?w<$Lt7h}JcG-&@_%TL0g98|$dnCR351eyFc!Yau?fAV6)faz@W@CWACJ z`RxeXCb+tg@v3;wq4cL>38d2V9*pahnv&9UwD*bUb~#QukSW5EW5+z)-M_v%v>jE# zxbfpffqe%hr-b=ze!<8!v*Me(Y1_Pj^pAsb?)c<1*Z{uV{I(Tv`!THW-+US)fDTFN zcyWhs&%Xl4(pA@9n5=k5w7h0o>i=+LX+;x2F-ctf_z!@BEHjg$R4Wbg6k=UwsfG9K z(L-EJ5?2M?9co(s?Ah3)~m8&U*HJA7~@5sK&9cWyAS%zEt6zJ4DIsckzW4 z7L?>RyF{yX&M=kNwcPwmYk2VO>~eRXsZ*tUJ^(wNZDnP2Z)LwF83~gD4r;g?l5^nA zIt5-7QJDJQ$cOW;Lh8lS3g73tmh=@j_ZiM{kO!QgKPivzqNvEaFR~DUb#JW=MMy(| z%}w|D{{d~ZiMmxsjJW&^a>O?29(yYlAN2O4BynBI?5 zgJvvmC!!e_Z%#1PYm$@fz3Fwss%JZ~Lj?0$0SE^diqS%#{}fjI1(ckA5 zyvpaK%6{)Qg2tdWokV*F+XTu_Rrdew+#`B)ygoQJqId0bTD*A5t|9BjbgY~9wEl}z zeMI-DYTEmu&IL+p<6!`d*a(93&PFYZ*h3)5%Bw?FxqScwE5cT3{>ets%3N<&A6sRN z`H^J%cKI*7L@MC#K+2FJ9`lSALSA8Uvs2S*_H26x4WUiDb^wleUCuPK$;_~*0YwqM zE*K|Ug|*w5OpV@mR>q^P^Kpm%1KG!lC=`xg9J?9lRu_3!n9MM%UrFlX^r$b;e8}{= z$;;nxSK61a-dTNEg7Tgf^6}kF+IYw}m)t$u8m54*NMtubF2r7Ub@bfi9KDz2Z~|++ zw8Im)e0Goa2@85!U9HeX{O29JZ-#8t93PsGRnUTY9@+%8gQ!|}Tz=}8{?ip388(LV zAAJ%g8Lg}_&jtEr8pGFzm>Nd5#_~l(cRSTv`M<&O!Lpif6fItm_Oy>tFO%G)9>8Ps z4?WSDtqCWCNB5cW=@H5U{Z79`cN-6C6pkUinAn_|rxtY-V7tLL$!Uj!2{LXW;8sFV zJDPwg02S^i1E_XNB~2inkL`j|c&=!x0Q)Tu14^ubKiQQ2F(YZEZ~f&IEMk_ z7U(l%ZZTUb$>iHx6(nS3H1r<7MV9aQn2f@yUOx( zYN`-mqp6zzJm+p>`Sa8fs!0AB-pOh`z!+?CAYIpVJa>w(udl$f-Tzj3QmnPRYq<{y z`oQoLe_WKvzI~g$6Fb|s5a8($D~Pe*RiDso1I`0}fg}Lc%%|`$A7eFb=AK5`JyK>N zrbD+EKq0~qq6$9tQJ|g=@6^dw6yx`B`cg^%+38x}Nt62!MgTS>6+o*+0P&;jY(XGo zBpn+DI6KT7As)b>D?si+p4j?q5AK5cfH{+=A%1MkmrR_vn_3t#2(vS?A}^Br9`rp6 z?2!NQv!Q5;=6Jl!C6_NB!Y`h?>G}&Yw?%;pur`rvAFv*soBb9<^wOnEB4X*T%&cKg z?znLCW^Z_fOb9s(a$cLjyUWdH9;ZY}6YM5}Oc@N@Tb4DRt7H^@{vsKFDav;FyI z&d;wzSxYE^eX%$K!N=Uy)99laxs_RvM-3Q4U zUt6W@1QmicqfClFW0qf#5K?B6RJRrZYsd9-m~nBp5F_wA&M@K8d7q+-7Z0SQOzr;@ z5eb#8h9#zkhEHzYI>tN1Pz9x|ei~WW4BM+>$QKPQSFZlay^eorXUukQ`C>kq9&n06Ezx()yZncp zz5S`tUe?iJHdCoppX~h3f1_6ILNraki;nk^nI(<+0DSpOD0^hHGMBo!|7b zaq?K+vDEE8KDD<-tYL+<5J-IPV|6hL6PYm=+wr$ucB-B_vHAWW^!jMhWH(4X051fBy@py{=w}($?j!?hHf5^cI4iiOcd>?MTd+Ymc2vKfEy1#~}J)UHIB&JAo zOdRl%F=(>7iv4ecdUKT&=k%{#n`9n+4=#Hd=VJvixfGFdC{T^qo9QbAvyHP>6+aa`a=M_cs(9?5}^7*u~0VWlkwY9k2_RR8$o z@}Kcn{g4d<_^tZ%)?rYjy_MBFP*`mH))hE=%FD&{{K83%C0RenQDT)uaN5|JiAdzd zpgVp7#uh3-@{6|pHWVzmEFCXC@NvmZD`bn)Q{-GYpPaENpt`GA0> zI*#RUD^_rCiaW07ck!5hg`S-0BK_qGUd7eTA4sM}W%1qRp3} zH2+;+eVH<*5zMxbM7EDW9WyI&IWjiFHuCEc^U$oghF0PC)(FOO&CWMAHunqt-|!ZB zo%A{ppvQ|xyNE8HR+HD7yd4n2cQ?uz2#M>ZSl}j8o3d9yTDmXmA%_`v3jNCnn{cnB zWaypJy-QaQwq@5hX~%rzV@8kWhvJvN>oCY8sw)MQMv&3>SB^^myQ4qzQu46GWg#Jw z+zFi4quz4pqDxEVMM(oT7V8T1qN>4KzAmhXQpwFK>0l6kN6X(qjI= zfyhcxsQvcQw(ES^&cbfi!Y*YK3jOrHIF~w8w4a-IH1+F;>gwvafZ?YkjJ&S1%oF=VARp zefvt(N=lD={WPl+Z*IZ9)Z$6V!vEG?L*SF|0PNzHm7<#2?|@YsfzH7q1f6FKBW$xK zQ*pnle;-iix1~V_DXp;o@Zk+3%SDZ-fNCQ;jwHX!t0YO`GE%~EQSHJm@c$+-CynQh z6hcBnFWQ85oo8s*ad7wM$>KjMGFkh1gCf&!Rz+qS{~!JRW`MZABxC>9-)|N*K=ap~ zDiQ7Wf4BGMDD3+2;|I_mL>w(P8>fW;x-Fn3rRB69B9 zm4L#|0&~m*+Q4oC*$(+TLPC##6xx)AUw%d$GCfwxz7o0!i2#dpZ3U9F-VxEYJn;PN z5iqy0;;*XudQf-azlc`sNYjCaBF|aRAgW+}>y9@-?rfWA(B3*ufj7$e7E4Lmsj5mi zdGGplCKBR-jW25gI27)_;Y{&g^t7p@i=F&{h;OK_{?Tj?>QBN2?LzSKw-SQMRLy?> zmevyCskA5RZ|bv)Z}xQWdAl@i(&a*{wQCKI*ri=?P*19JmoUP5!DOD}ZM!qduz2C^ z$(NrB`_Y5y)H7Cz%c6C4cT^Y|?3qp%|R_nUFwr;*1^*6G?hjNSDLX4Sn za4 z5NAu-r2RH;jEdV0)0{S~U2JgY`9?>^Pfu(uW!?7mXXDL-K`v2TBNzVG@q-NwR&XS(Wrj<{V%FT*k(6eRsRKx;|eyku~2}nGmqs##;CHe@6Ip zXdqGZ8*Zb(8jhRiJ7LMSnfcaTAk2;JqZ?~UEsd}AYH2*va2gR^zzx4ysF zbAjLY#4a1OJwZdTGW!3V1}w$JR+j2A&BEd)_$j5i#{&KYjfzgxQJi|(i}ss|@=j&e z7!G~6;B{AT7D05#e$q)0gqp8u=1yB6fW%mU7g1<#XgNboRYFavevq}PvyQzp*?@3t z-U~Tp_f@OPV>j;GHwSUwj3jJ&{c3S7fl;BUrC!-+qW0L4S3&+$e3ACG4gE8aSF(*I z);hN{BO|~3T%~6;{i07rewewI97#m)w@&Z5U`$z{_D5R7o8*tE>6>ZLx9_@7Xn{+dbuV36WNf@) za{N$(&rC%@8vXjM??do|y`7!M_xw*xE9$DM)Gr!elKjL?E9@EZmXoyn9j`K<2FLia zpp!_SB@|rRuGguU@-_IwgQx$RV5kZpqVO%D!{VMkF7n8I>g{P;Vm6t=v7r&f`KxX3_zEf+3%BC+tIAZTaWAQ%{ z1m{hfVqBtQ8n*IvlMi^c9{mjL`=v8WAPt(nq2o}78ZLIpJy+||-y-`Fd1=~9DgEvX zKKNXsI*Mb|!L+pDvDQ_UmBXEKLaIKko}KIduG&|yGce!4gvh@#-n(gX{K^$8jvPO})?uwAk`ysQ1T*43 z6+a?-kd~qgM9c>PN#DlF#rwxcTeT{k3HRvib7A5Er|I;Ay_*H zPUpP$5x#JCb~dRch{Jfs(`U@!BX}1F&x72CuN`h1B4d?@PkFYnOtXR=upHLBeOY4{o&nY7*ZFv`lUmPZNnZTjzc95mFFO<;tQ4`?RGD z9MwGz$Ez*lIMB=`SN-lJczf(O=aqZ407PVd%6(QO0)sc+M9|LS1=Os8rb)#_d(s1f zYkTl@0EzFt`GBc&R@T}~mXyyr#sULTcS*%zUk*JTvNb%k8FlTxYcs{t71Iw-qw`|*XVeNPQ1=Wa$9e!TlSuz}#o-v4qYD<*B zEv5TMm}ffr!(kwutXgBDHB-T4wR!QP zWluF7wc3surRaGxSo_+DY0|#9eL_v|R*l;;`FiJ1)epuSBLaR^{Iti&r(06!8tV1U zlS{yo=Mlr!=oh~WKt}l>$F}Y=FA&svy@RLsrSrag0362~Hxg~RzWL)bDJfOMzcAu- z?!2Anu*{wr-q^VI!nfn0mVOUjGoJg(59l;qS?Ceajfx~>el+}9d|L9!MRCF7Kn)G& zWy{2LdQ41#uTbzZ2JQKw{R4t1Fb9EW;Fb9)UCNn5xf`(kuJH0A!jJ@k9@6hk_RdCR zDBZEQzKz~x+k@f~o;|)G;sl!#$6#20$FWgUS$?$litFQPgpOxqo&RvpzOgfmt+q7S zT$1hjBC{!yla0B+K7RUy(l>eB0VJ-_?-egcU2P=HFetF(jIk2~k%EGjOwb1VcR%Rf z=xV_3GTTiy21c&#aqJMxAMPk>iN`~E)|b1ue5uX)DgS^sG;evyWh)6`bT9E{3UJK$ zHmB^P9pd<>7uS=^_zoCOG|TO~>v|d*+Ck5dSW35K5a;?tRDW723BS&(eo!3h@l8*d7j0{ z^Ak_lt|(?BRh9Es=@sCAK)TF0%(y?=P zegzH_WP0dMm(UqWss#3m>v=@eYU|c@iBI|9@MCcl55-p~EZAbJ)QC7}5sF#Kk-g5| zC@%g0>(p-Jb5LnQzo0r*4{q7g0jk?}R=ZZ#lP0yF5;i6I{lua-ANl#`CUOk z*&M+2LT1G0>jN=0Jf0T5AE9v|n(7W62ss$q@M-gQPHl4Nh0@Fku#Z1@KHC;!O9A~c zR>tj)dl@dY!1eT{PaWuoH@MjblGLh=&4fb+cMk(U>zF5P@cDS_1lX@*NlD>(qV$14 z>_L$OWZPXBuw_Uq25QEeLpGXUo`L3}&+xr*@7;8c#jc@?w_$0`$oK|y&k8I+24o>L z|2UW@DU0HRn>Sf*J36^F0IZ_n+VlQD*D9(Q7+m1D(B6sWjbuvz4-D`}9WsXZ?aPE2 zduIKD#(+MfJS|jYa2Ja9W5^vMq%L3Xr=@jt$<6<2>$$s3lS|ma_HHloAo;BmcD>#b z7+6rX>Y7v*t1qQVe1CTtRs+8Oc-Af=S`4ZGG^nMe`p^#~ztA6EI@QBjh7cj7xL{B{ z6g8?HC56_zhG^MK(VpEP%p}l8v)D+z#m~;S_8C4Qma?|0)E~PD!waN_>)9Cd*sxru z&ttj>=^LB2ggn+#8}r6RUGY^{guL`f-_yUn0zriirka-dpAiB^NHo=*5u4YkPAFSj zEculr9571@V`Jm^HM75&L(8H~!MXU;xt)-`a(sli+vZ&uf`I<4WaV97qj(Og0dWxk z5VuuL{Zrvx+(kvPiVK6cGuhD@?c2oqukN11X|BiaYFea7sq<{^>;I)G=WmwI`y|hQ z^AlY+w_xvATEbUA!rMlC+2lb@(?bm%b2u9M+FHYcAfX`xSiHlkcEY=0oq1b2t`a9b z{&|;}-ISltzZQtsOVdB8&A$ZyUc Date: Thu, 31 Aug 2017 13:18:04 +0100 Subject: [PATCH 15/83] Update user fixtures with new entity code --- t/etc/fixtures/config/users.pl | 82 +++++++++++++------ t/etc/fixtures/data/users/_config_set | 42 ++++------ t/etc/fixtures/data/users/customers/1.fix | 1 + t/etc/fixtures/data/users/customers/2.fix | 1 + t/etc/fixtures/data/users/customers/3.fix | 1 + t/etc/fixtures/data/users/customers/4.fix | 1 + t/etc/fixtures/data/users/customers/5.fix | 8 ++ t/etc/fixtures/data/users/entities/1.fix | 4 + t/etc/fixtures/data/users/entities/2.fix | 4 + t/etc/fixtures/data/users/entities/3.fix | 4 + t/etc/fixtures/data/users/entities/4.fix | 4 + t/etc/fixtures/data/users/entities/5.fix | 4 + t/etc/fixtures/data/users/entities/6.fix | 4 + t/etc/fixtures/data/users/organisations/1.fix | 18 ++-- t/etc/fixtures/data/users/users/1.fix | 15 ++-- t/etc/fixtures/data/users/users/2.fix | 15 ++-- t/etc/fixtures/data/users/users/3.fix | 15 ++-- t/etc/fixtures/data/users/users/4.fix | 15 ++-- t/etc/fixtures/data/users/users/5.fix | 15 ++-- t/etc/fixtures/data/users/users/6.fix | 8 ++ 20 files changed, 159 insertions(+), 102 deletions(-) create mode 100644 t/etc/fixtures/data/users/customers/5.fix create mode 100644 t/etc/fixtures/data/users/entities/1.fix create mode 100644 t/etc/fixtures/data/users/entities/2.fix create mode 100644 t/etc/fixtures/data/users/entities/3.fix create mode 100644 t/etc/fixtures/data/users/entities/4.fix create mode 100644 t/etc/fixtures/data/users/entities/5.fix create mode 100644 t/etc/fixtures/data/users/entities/6.fix create mode 100644 t/etc/fixtures/data/users/users/6.fix diff --git a/t/etc/fixtures/config/users.pl b/t/etc/fixtures/config/users.pl index 676592b..88d3066 100644 --- a/t/etc/fixtures/config/users.pl +++ b/t/etc/fixtures/config/users.pl @@ -29,63 +29,93 @@ $schema->resultset('Leaderboard')->populate([ [ 'All Time Count', 'all_time_count' ], ]); -my $user1 = { +my $entity1 = { customer => { full_name => 'Test User1', display_name => 'Test User1', postcode => 'LA1 1AA', year_of_birth => 2006, }, - email => 'test1@example.com', - password => 'abc123', + user => { + email => 'test1@example.com', + password => 'abc123', + }, + type => "customer", }; -my $user2 = { +my $entity2 = { customer => { - full_name => 'Test User2', - display_name => 'Test User2', - postcode => 'LA1 1AA', + full_name => 'Test User2', + display_name => 'Test User2', + postcode => 'LA1 1AA', year_of_birth => 2006, }, - email => 'test2@example.com', - password => 'abc123', + user => { + email => 'test2@example.com', + password => 'abc123', + }, + type => "customer", }; -my $user3 = { +my $entity3 = { customer => { - full_name => 'Test User3', - display_name => 'Test User3', - postcode => 'LA1 1AA', + full_name => 'Test User3', + display_name => 'Test User3', + postcode => 'LA1 1AA', year_of_birth => 2006, }, - email => 'test3@example.com', - password => 'abc123', + user => { + email => 'test3@example.com', + password => 'abc123', + }, + type => "customer", }; -my $user4 = { +my $entity4 = { customer => { - full_name => 'Test User4', - display_name => 'Test User4', - postcode => 'LA1 1AA', + full_name => 'Test User4', + display_name => 'Test User4', + postcode => 'LA1 1AA', year_of_birth => 2006, }, - email => 'test4@example.com', - password => 'abc123', + user => { + email => 'test4@example.com', + password => 'abc123', + }, + type => "customer", }; -my $org = { +my $entity5 = { organisation => { name => 'Test Org', street_name => 'Test Street', town => 'Lancaster', postcode => 'LA1 1AA', }, - email => 'org@example.com', - password => 'abc123', + user => { + email => 'org@example.com', + password => 'abc123', + }, + type => "customer", }; -$schema->resultset('User')->create( $_ ) - for ( $user1, $user2, $user3, $user4, $org ); +my $entity6 = { + customer => { + full_name => 'Test Admin', + display_name => 'Test Admin', + postcode => 'LA1 1AA', + year_of_birth => 2006, + }, + user => { + email => 'admin@example.com', + password => 'abc123', + is_admin => \"1", + }, + type => "customer", +}; + +$schema->resultset('Entity')->create( $_ ) + for ( $entity1, $entity2, $entity3, $entity4, $entity5, $entity6 ); my $data_set = 'users'; diff --git a/t/etc/fixtures/data/users/_config_set b/t/etc/fixtures/data/users/_config_set index f2242db..debc83c 100644 --- a/t/etc/fixtures/data/users/_config_set +++ b/t/etc/fixtures/data/users/_config_set @@ -5,20 +5,21 @@ $VAR1 = { 'belongs_to' => { 'fetch' => 0 }, - 'might_have' => { - 'fetch' => 0 - }, 'sets' => [ { - 'class' => 'Leaderboard', + 'class' => 'Feedback', 'quantity' => 'all' }, { - 'class' => 'AccountToken', + 'quantity' => 'all', + 'class' => 'LeaderboardSet' + }, + { + 'class' => 'Transaction', 'quantity' => 'all' }, { - 'class' => 'LeaderboardValue', + 'class' => 'Entity', 'quantity' => 'all' }, { @@ -26,40 +27,31 @@ $VAR1 = { 'class' => 'SessionToken' }, { - 'class' => 'Administrator', + 'class' => 'AccountToken', 'quantity' => 'all' }, { - 'class' => 'LeaderboardSet', + 'class' => 'Organisation', 'quantity' => 'all' }, { 'quantity' => 'all', - 'class' => 'Feedback' + 'class' => 'User' }, { - 'class' => 'PendingOrganisation', + 'class' => 'Customer', 'quantity' => 'all' }, { - 'class' => 'User', - 'quantity' => 'all' - }, - { - 'class' => 'Transaction', + 'class' => 'Leaderboard', 'quantity' => 'all' }, { 'quantity' => 'all', - 'class' => 'Organisation' - }, - { - 'quantity' => 'all', - 'class' => 'Customer' - }, - { - 'quantity' => 'all', - 'class' => 'PendingTransaction' + 'class' => 'LeaderboardValue' } - ] + ], + 'might_have' => { + 'fetch' => 0 + } }; diff --git a/t/etc/fixtures/data/users/customers/1.fix b/t/etc/fixtures/data/users/customers/1.fix index 16d0da6..705c22d 100644 --- a/t/etc/fixtures/data/users/customers/1.fix +++ b/t/etc/fixtures/data/users/customers/1.fix @@ -1,5 +1,6 @@ $HASH1 = { display_name => 'Test User1', + entity_id => 1, full_name => 'Test User1', id => 1, postcode => 'LA1 1AA', diff --git a/t/etc/fixtures/data/users/customers/2.fix b/t/etc/fixtures/data/users/customers/2.fix index 3c31701..0fee4aa 100644 --- a/t/etc/fixtures/data/users/customers/2.fix +++ b/t/etc/fixtures/data/users/customers/2.fix @@ -1,5 +1,6 @@ $HASH1 = { display_name => 'Test User2', + entity_id => 2, full_name => 'Test User2', id => 2, postcode => 'LA1 1AA', diff --git a/t/etc/fixtures/data/users/customers/3.fix b/t/etc/fixtures/data/users/customers/3.fix index b434d5c..a64e402 100644 --- a/t/etc/fixtures/data/users/customers/3.fix +++ b/t/etc/fixtures/data/users/customers/3.fix @@ -1,5 +1,6 @@ $HASH1 = { display_name => 'Test User3', + entity_id => 3, full_name => 'Test User3', id => 3, postcode => 'LA1 1AA', diff --git a/t/etc/fixtures/data/users/customers/4.fix b/t/etc/fixtures/data/users/customers/4.fix index d5f881b..49d0530 100644 --- a/t/etc/fixtures/data/users/customers/4.fix +++ b/t/etc/fixtures/data/users/customers/4.fix @@ -1,5 +1,6 @@ $HASH1 = { display_name => 'Test User4', + entity_id => 4, full_name => 'Test User4', id => 4, postcode => 'LA1 1AA', diff --git a/t/etc/fixtures/data/users/customers/5.fix b/t/etc/fixtures/data/users/customers/5.fix new file mode 100644 index 0000000..50ef1c2 --- /dev/null +++ b/t/etc/fixtures/data/users/customers/5.fix @@ -0,0 +1,8 @@ +$HASH1 = { + display_name => 'Test Admin', + entity_id => 6, + full_name => 'Test Admin', + id => 5, + postcode => 'LA1 1AA', + year_of_birth => 2006 + }; diff --git a/t/etc/fixtures/data/users/entities/1.fix b/t/etc/fixtures/data/users/entities/1.fix new file mode 100644 index 0000000..a45e065 --- /dev/null +++ b/t/etc/fixtures/data/users/entities/1.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 1, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/users/entities/2.fix b/t/etc/fixtures/data/users/entities/2.fix new file mode 100644 index 0000000..20849e3 --- /dev/null +++ b/t/etc/fixtures/data/users/entities/2.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 2, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/users/entities/3.fix b/t/etc/fixtures/data/users/entities/3.fix new file mode 100644 index 0000000..3fd6a9a --- /dev/null +++ b/t/etc/fixtures/data/users/entities/3.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 3, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/users/entities/4.fix b/t/etc/fixtures/data/users/entities/4.fix new file mode 100644 index 0000000..bacb15b --- /dev/null +++ b/t/etc/fixtures/data/users/entities/4.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 4, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/users/entities/5.fix b/t/etc/fixtures/data/users/entities/5.fix new file mode 100644 index 0000000..e22cd57 --- /dev/null +++ b/t/etc/fixtures/data/users/entities/5.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 5, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/users/entities/6.fix b/t/etc/fixtures/data/users/entities/6.fix new file mode 100644 index 0000000..ee9f57f --- /dev/null +++ b/t/etc/fixtures/data/users/entities/6.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 6, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/users/organisations/1.fix b/t/etc/fixtures/data/users/organisations/1.fix index 035a3ce..b761450 100644 --- a/t/etc/fixtures/data/users/organisations/1.fix +++ b/t/etc/fixtures/data/users/organisations/1.fix @@ -1,10 +1,16 @@ $HASH1 = { - id => 1, - name => 'Test Org', + country => undef, + entity_id + => 5, + id => 1, + name => 'Test Org', + pending => 0, postcode - => 'LA1 1AA', - sector => undef, + => 'LA1 1AA', + sector => undef, street_name - => 'Test Street', - town => 'Lancaster' + => 'Test Street', + submitted_by_id + => undef, + town => 'Lancaster' }; diff --git a/t/etc/fixtures/data/users/users/1.fix b/t/etc/fixtures/data/users/users/1.fix index 0946374..9507717 100644 --- a/t/etc/fixtures/data/users/users/1.fix +++ b/t/etc/fixtures/data/users/users/1.fix @@ -1,11 +1,8 @@ $HASH1 = { - customer_id - => 1, - email => 'test1@example.com', - id => 1, - join_date - => '2017-08-25 15:36:11', - organisation_id - => undef, - password => '$2a$08$yCau6xDkRFZINg80iVvMh.M3JnLq2g.LJ7GMJL5KQvO45pDL.D/Rq' + email => 'test1@example.com', + entity_id => 1, + id => 1, + is_admin => 0, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$HSuznDeSU1fuONwKhp2/S.TX/X4p4g0dHtz20kVXxprm8hIg5QQma' }; diff --git a/t/etc/fixtures/data/users/users/2.fix b/t/etc/fixtures/data/users/users/2.fix index 9b35ef4..9654ada 100644 --- a/t/etc/fixtures/data/users/users/2.fix +++ b/t/etc/fixtures/data/users/users/2.fix @@ -1,11 +1,8 @@ $HASH1 = { - customer_id - => 2, - email => 'test2@example.com', - id => 2, - join_date - => '2017-08-25 15:36:11', - organisation_id - => undef, - password => '$2a$08$BZAsbHSW8TN/jlL2DFGDoeKFRKzj2dQTBwatxb0p/maefEcWcziom' + email => 'test2@example.com', + entity_id => 2, + id => 2, + is_admin => 0, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$5XYLWPJvVGuWUvfSWj9cIOg4/tyB4fZ3knzwgw5UnBSKFBFIKOiFC' }; diff --git a/t/etc/fixtures/data/users/users/3.fix b/t/etc/fixtures/data/users/users/3.fix index f3ad391..fd07648 100644 --- a/t/etc/fixtures/data/users/users/3.fix +++ b/t/etc/fixtures/data/users/users/3.fix @@ -1,11 +1,8 @@ $HASH1 = { - customer_id - => 3, - email => 'test3@example.com', - id => 3, - join_date - => '2017-08-25 15:36:11', - organisation_id - => undef, - password => '$2a$08$xdkD/OA5izrOX9cvJDa4i..8T3YGmfVSo/G87wDRoGWnQmlC0gxOW' + email => 'test3@example.com', + entity_id => 3, + id => 3, + is_admin => 0, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$p5VU4leHetEvuz2P3uMfYuuPTkDGg8wsM7QuSVKkXR.s3B9T2JaVW' }; diff --git a/t/etc/fixtures/data/users/users/4.fix b/t/etc/fixtures/data/users/users/4.fix index 47d83c3..e8300ec 100644 --- a/t/etc/fixtures/data/users/users/4.fix +++ b/t/etc/fixtures/data/users/users/4.fix @@ -1,11 +1,8 @@ $HASH1 = { - customer_id - => 4, - email => 'test4@example.com', - id => 4, - join_date - => '2017-08-25 15:36:11', - organisation_id - => undef, - password => '$2a$08$svjdm.Syn3f062pDIN3/ROTUU7W16n0zJFm9/sm3x7pfbMLZFV.5G' + email => 'test4@example.com', + entity_id => 4, + id => 4, + is_admin => 0, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$fkkTUYA9zvt32iBbN7aOcOnmiHzOkbMEjN31PB9CsitGrE.KF7cAG' }; diff --git a/t/etc/fixtures/data/users/users/5.fix b/t/etc/fixtures/data/users/users/5.fix index 50e56c9..97859de 100644 --- a/t/etc/fixtures/data/users/users/5.fix +++ b/t/etc/fixtures/data/users/users/5.fix @@ -1,11 +1,8 @@ $HASH1 = { - customer_id - => undef, - email => 'org@example.com', - id => 5, - join_date - => '2017-08-25 15:36:11', - organisation_id - => 1, - password => '$2a$08$3PkZF7D9FiOq8hgU7cJ6puY86Fkl34bQj6dZeJRXPU8hhJIMZtge2' + email => 'org@example.com', + entity_id => 5, + id => 5, + is_admin => 0, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$MWC45.w1AnLPNNHiS96ICOHfNlTrIqB0.7OPzy5qB.z0pZB0theo.' }; diff --git a/t/etc/fixtures/data/users/users/6.fix b/t/etc/fixtures/data/users/users/6.fix new file mode 100644 index 0000000..d1b52dc --- /dev/null +++ b/t/etc/fixtures/data/users/users/6.fix @@ -0,0 +1,8 @@ +$HASH1 = { + email => 'admin@example.com', + entity_id => 6, + id => 6, + is_admin => 1, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$1sPcPB9GnoNgzhBAk2bHSOqEmv6.Y6YLrAa2DJ6TR2WefzTYZQ92G' + }; From 0b6bb47072cfa966499470a97baf0eb847fa2e3a Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 13:20:06 +0100 Subject: [PATCH 16/83] Added documentation for user fixtures --- doc/Fixtures/Users.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 doc/Fixtures/Users.md diff --git a/doc/Fixtures/Users.md b/doc/Fixtures/Users.md new file mode 100644 index 0000000..15db8d3 --- /dev/null +++ b/doc/Fixtures/Users.md @@ -0,0 +1,24 @@ +# Users Fixtures + +* Fixture Name: `users` + +## Users: + +* Test User1 + * email: test1@example.com + * password: abc123 +* Test User2 + * email: test2@example.com + * password: abc123 +* Test User3 + * email: test3@example.com + * password: abc123 +* Test User4 + * email: test4@example.com + * password: abc123 +* Test Org + * email: test5@example.com + * password: abc123 +* Test Admin + * email: admin@example.com + * password: abc123 From 6cd3113912bd1d9dbeea7fa377397c5ee62b5b9f Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 13:20:24 +0100 Subject: [PATCH 17/83] Updated basic test to use fixtures --- t/basic.t | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/t/basic.t b/t/basic.t index 9a14ba3..666e762 100644 --- a/t/basic.t +++ b/t/basic.t @@ -1,15 +1,16 @@ use Mojo::Base -strict; + +use FindBin qw/ $Bin /; + use Test::More; -use Test::Mojo; +use Test::Pear::LocalLoop; -use FindBin; +my $framework = Test::Pear::LocalLoop->new( + etc_dir => "$Bin/etc", +); +$framework->install_fixtures('users'); -BEGIN { - $ENV{MOJO_MODE} = 'testing'; - $ENV{MOJO_LOG_LEVEL} = 'debug'; -} - -my $t = Test::Mojo->new("Pear::LocalLoop"); +my $t = $framework->framework; $t->get_ok('/')->status_is(200); done_testing(); From 18992784b29923dee74708078391688f08522cd1 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 13:35:49 +0100 Subject: [PATCH 18/83] Fix admin login test and required changes for entity upgrade --- lib/Pear/LocalLoop/Controller/Admin.pm | 8 +++--- lib/Pear/LocalLoop/Schema/ResultSet/Entity.pm | 10 ++++++++ .../Schema/ResultSet/Organisation.pm | 10 ++++++++ t/admin/login.t | 25 ++++++------------- 4 files changed, 32 insertions(+), 21 deletions(-) create mode 100644 lib/Pear/LocalLoop/Schema/ResultSet/Entity.pm create mode 100644 lib/Pear/LocalLoop/Schema/ResultSet/Organisation.pm diff --git a/lib/Pear/LocalLoop/Controller/Admin.pm b/lib/Pear/LocalLoop/Controller/Admin.pm index 005707b..8030d59 100644 --- a/lib/Pear/LocalLoop/Controller/Admin.pm +++ b/lib/Pear/LocalLoop/Controller/Admin.pm @@ -5,10 +5,10 @@ sub under { my $c = shift; if ( $c->is_user_authenticated ) { - return 1 if defined $c->current_user->administrator; + return 1 if $c->current_user->is_admin; } $c->redirect_to('/'); - return undef; + return 0; } sub home { @@ -16,8 +16,8 @@ sub home { my $user_rs = $c->schema->resultset('User'); my $token_rs = $c->schema->resultset('AccountToken'); - my $pending_orgs_rs = $c->schema->resultset('PendingOrganisation'); - my $pending_transaction_rs = $c->schema->resultset('PendingTransaction'); + my $pending_orgs_rs = $c->schema->resultset('Organisation')->search({ pending => 1 }); + my $pending_transaction_rs = $pending_orgs_rs->entity->sales; $c->stash( user_count => $user_rs->count, tokens => { diff --git a/lib/Pear/LocalLoop/Schema/ResultSet/Entity.pm b/lib/Pear/LocalLoop/Schema/ResultSet/Entity.pm new file mode 100644 index 0000000..735b0db --- /dev/null +++ b/lib/Pear/LocalLoop/Schema/ResultSet/Entity.pm @@ -0,0 +1,10 @@ +package Pear::LocalLoop::Schema::ResultSet::Entity; + +use strict; +use warnings; + +use base 'DBIx::Class::ResultSet'; + +sub sales { shift->search_related('sales', @_) } + +1; diff --git a/lib/Pear/LocalLoop/Schema/ResultSet/Organisation.pm b/lib/Pear/LocalLoop/Schema/ResultSet/Organisation.pm new file mode 100644 index 0000000..10560b2 --- /dev/null +++ b/lib/Pear/LocalLoop/Schema/ResultSet/Organisation.pm @@ -0,0 +1,10 @@ +package Pear::LocalLoop::Schema::ResultSet::Organisation; + +use strict; +use warnings; + +use base 'DBIx::Class::ResultSet'; + +sub entity { shift->search_related('entity', @_) } + +1; diff --git a/t/admin/login.t b/t/admin/login.t index 1f7211a..abaea7e 100644 --- a/t/admin/login.t +++ b/t/admin/login.t @@ -1,27 +1,18 @@ use Mojo::Base -strict; +use FindBin qw/ $Bin /; + use Test::More; -use Mojo::JSON; use Test::Pear::LocalLoop; -my $framework = Test::Pear::LocalLoop->new; +my $framework = Test::Pear::LocalLoop->new( + etc_dir => "$Bin/../etc", +); +$framework->install_fixtures('users'); + my $t = $framework->framework; my $schema = $t->app->schema; -$schema->resultset('User')->create({ - email => 'admin@example.com', - password => 'abc123', - administrator => {}, -}); - -$schema->resultset('User')->create({ - email => 'user@example.com', - password => 'abc123', -}); - -is $schema->resultset('User')->count, 2, 'Users Created'; -is $schema->resultset('Administrator')->count, 1, 'Admin Created'; - my $location_is = sub { my ($t, $value, $desc) = @_; $desc ||= "Location: $value"; @@ -34,7 +25,7 @@ $t->get_ok('/admin') $t->ua->max_redirects(10); $t->post_ok('/admin', form => { - email => 'user@example.com', + email => 'test1@example.com', password => 'abc123', })->status_is(200); From 0159ef91dcad0e1b6b3bbafa618ca2ef6ddc0841 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 14:52:25 +0100 Subject: [PATCH 19/83] Fix undef issue on valid_read template --- templates/admin/organisations/valid_read.html.ep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/admin/organisations/valid_read.html.ep b/templates/admin/organisations/valid_read.html.ep index ffb7bb2..2055496 100644 --- a/templates/admin/organisations/valid_read.html.ep +++ b/templates/admin/organisations/valid_read.html.ep @@ -22,7 +22,7 @@
  • From 92afe1c531eb26123b23ae1431007be55869cdd1 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 14:52:45 +0100 Subject: [PATCH 20/83] Fix admin organisation management tests --- lib/Pear/LocalLoop.pm | 9 +- .../Controller/Admin/Organisations.pm | 110 +++--------------- t/admin/organisation.t | 70 +++++------ 3 files changed, 56 insertions(+), 133 deletions(-) diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index f06b1c0..c2e5972 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -179,12 +179,9 @@ sub startup { $admin_routes->get('/organisations')->to('admin-organisations#list'); $admin_routes->get('/organisations/add')->to('admin-organisations#add_org'); - $admin_routes->post('/organisations/add/submit')->to('admin-organisations#add_org_submit'); - $admin_routes->get('/organisations/valid/:id')->to('admin-organisations#valid_read'); - $admin_routes->post('/organisations/valid/:id/edit')->to('admin-organisations#valid_edit'); - $admin_routes->get('/organisations/pending/:id')->to('admin-organisations#pending_read'); - $admin_routes->post('/organisations/pending/:id/edit')->to('admin-organisations#pending_edit'); - $admin_routes->get('/organisations/pending/:id/approve')->to('admin-organisations#pending_approve'); + $admin_routes->post('/organisations/add')->to('admin-organisations#add_org_submit'); + $admin_routes->get('/organisations/:id')->to('admin-organisations#valid_read'); + $admin_routes->post('/organisations/:id')->to('admin-organisations#valid_edit'); $admin_routes->get('/feedback')->to('admin-feedback#index'); $admin_routes->get('/feedback/:id')->to('admin-feedback#read'); diff --git a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm index f382a5e..dba62e0 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm @@ -2,7 +2,6 @@ package Pear::LocalLoop::Controller::Admin::Organisations; use Mojo::Base 'Mojolicious::Controller'; use Try::Tiny; -use Data::Dumper; sub list { my $c = shift; @@ -33,35 +32,38 @@ sub add_org_submit { if ( $validation->has_error ) { $c->flash( error => 'The validation has failed' ); - $c->app->log->warn(Dumper $validation); - return $c->redirect_to( '/admin/organisations/add/' ); + return $c->redirect_to( '/admin/organisations/add' ); } my $organisation; try { - $organisation = $c->schema->resultset('Organisation')->create({ - name => $validation->param('name'), - street_name => $validation->param('street_name'), - town => $validation->param('town'), - sector => $validation->param('sector'), - postcode => $validation->param('postcode'), + my $entity = $c->schema->resultset('Entity')->create({ + organisation => { + name => $validation->param('name'), + street_name => $validation->param('street_name'), + town => $validation->param('town'), + sector => $validation->param('sector'), + postcode => $validation->param('postcode'), + }, + type => 'organisation', }); + $organisation = $entity->organisation; } finally { if ( @_ ) { $c->flash( error => 'Something went wrong Adding the Organisation' ); - $c->app->log->warn(Dumper @_); + $c->redirect_to( '/admin/organisations/add' ); } else { $c->flash( success => 'Added Organisation' ); + $c->redirect_to( '/admin/organisations/' . $organisation->id); } }; - $c->redirect_to( '/admin/organisations/add/' ); } sub valid_read { my $c = shift; my $valid_org = $c->schema->resultset('Organisation')->find( $c->param('id') ); - my $transactions = $valid_org->transactions->search( + my $transactions = $valid_org->entity->sales->search( undef, { page => $c->param('page') || 1, rows => 10, @@ -86,8 +88,7 @@ sub valid_edit { if ( $validation->has_error ) { $c->flash( error => 'The validation has failed' ); - $c->app->log->warn(Dumper $validation); - return $c->redirect_to( '/admin/organisations/valid/' . $c->param('id') ); + return $c->redirect_to( '/admin/organisations/' . $c->param('id') ); } my $valid_org = $c->schema->resultset('Organisation')->find( $c->param('id') ); @@ -105,90 +106,11 @@ sub valid_edit { } finally { if ( @_ ) { $c->flash( error => 'Something went wrong Updating the Organisation' ); - $c->app->log->warn(Dumper @_); } else { $c->flash( success => 'Updated Organisation' ); } }; - $c->redirect_to( '/admin/organisations/valid/' . $valid_org->id ); -} - -sub pending_read { - my $c = shift; - my $pending_org = $c->schema->resultset('PendingOrganisation')->find( $c->param('id') ); - my $transactions = $pending_org->transactions->search( - undef, { - page => $c->param('page') || 1, - rows => 10, - }, - ); - $c->stash( - pending_org => $pending_org, - transactions => $transactions, - ); -} - -sub pending_edit { - my $c = shift; - - my $validation = $c->validation; - $validation->required('name'); - $validation->required('street_name'); - $validation->required('town'); - $validation->required('postcode')->postcode; - - if ( $validation->has_error ) { - $c->flash( error => 'The validation has failed' ); - $c->app->log->warn(Dumper $validation); - return $c->redirect_to( '/admin/organisations/pending/' . $c->param('id') ); - } - - my $pending_org = $c->schema->resultset('PendingOrganisation')->find( $c->param('id') ); - - try { - $c->schema->storage->txn_do( sub { - $pending_org->update({ - name => $validation->param('name'), - street_name => $validation->param('street_name'), - town => $validation->param('town'), - postcode => $validation->param('postcode'), - }); - } ); - } finally { - if ( @_ ) { - $c->flash( error => 'Something went wrong Updating the Organisation' ); - $c->app->log->warn(Dumper @_); - } else { - $c->flash( success => 'Updated Organisation' ); - } - }; - $c->redirect_to( '/admin/organisations/pending/' . $pending_org->id ); -} - -sub pending_approve { - my $c = shift; - my $pending_org = $c->schema->resultset('PendingOrganisation')->find( $c->param('id') ); - - my $valid_org; - try { - $c->schema->storage->txn_do( sub { - $valid_org = $c->schema->resultset('Organisation')->create({ - name => $pending_org->name, - street_name => $pending_org->street_name, - town => $pending_org->town, - postcode => $pending_org->postcode, - }); - $c->copy_transactions_and_delete( $pending_org, $valid_org ); - } ); - } finally { - if ( @_ ) { - $c->flash( error => 'Something went wrong Validating the Organisation' ); - $c->redirect_to( '/admin/organisations/pending/' . $pending_org->id ); - } else { - $c->flash( success => 'Validated Organisation' ); - $c->redirect_to( '/admin/organisations/valid/' . $valid_org->id ); - } - } + $c->redirect_to( '/admin/organisations/' . $valid_org->id ); } 1; diff --git a/t/admin/organisation.t b/t/admin/organisation.t index 8ac7af6..cfc906b 100644 --- a/t/admin/organisation.t +++ b/t/admin/organisation.t @@ -1,38 +1,42 @@ use Mojo::Base -strict; +use FindBin qw/ $Bin /; + use Test::More; -use Mojo::JSON; use Test::Pear::LocalLoop; -my $framework = Test::Pear::LocalLoop->new; +my $framework = Test::Pear::LocalLoop->new( + etc_dir => "$Bin/../etc", +); +$framework->install_fixtures('users'); my $t = $framework->framework; my $schema = $t->app->schema; -my $user = $schema->resultset('User')->create({ - email => 'admin@example.com', - password => 'abc123', - administrator => {}, +my $valid_entity = $schema->resultset('Entity')->create({ + organisation => { + name => 'Shinra Electric Power Company', + street_name => 'Sector 0, Midgar, Eastern Continent', + town => 'Gaia', + sector => 'A', + postcode => 'WC1E 6AD', + }, + type => "organisation", }); -is $schema->resultset('Administrator')->count, 1, 'Admin Created'; - -$schema->resultset('Organisation')->create({ - id => 1, - name => 'Shinra Electric Power Company', - street_name => 'Sector 0, Midgar, Eastern Continent', - town => 'Gaia', - sector => 'A', - postcode => 'WC1E 6AD', +my $pending_entity = $schema->resultset('Entity')->create({ + organisation => { + name => '7th Heaven', + street_name => 'Slums, Sector 7', + town => 'Midgar', + sector => 'A', + postcode => 'WC1E 6AD', + pending => \"1", + }, + type => "organisation", }); -$schema->resultset('PendingOrganisation')->create({ - id => 2, - name => '7th Heaven', - street_name => 'Slums, Sector 7', - town => 'Midgar', - postcode => 'WC1E 6AD', - submitted_by_id => $user->id, -}); +my $valid_id = $valid_entity->organisation->id; +my $pending_id = $pending_entity->organisation->id; #login to admin $t->ua->max_redirects(10); @@ -42,15 +46,15 @@ $t->post_ok('/admin', form => { })->status_is(200); #Read approved organisation -$t->get_ok('/admin/organisations/valid/1/') - ->status_is(200); +$t->get_ok("/admin/organisations/$valid_id") + ->status_is(200)->or($framework->dump_error); #Read pending organisation -$t->get_ok('/admin/organisations/pending/2/') - ->status_is(200); +$t->get_ok("/admin/organisations/$pending_id") + ->status_is(200)->or($framework->dump_error); #Valid approved organisation update -$t->post_ok('/admin/organisations/valid/1/edit', form => { +$t->post_ok("/admin/organisations/$valid_id", form => { name => 'Shinra Electric Power Company', street_name => 'Sector 0, Midgar, Eastern Continent', town => 'Gaia', @@ -59,7 +63,7 @@ $t->post_ok('/admin/organisations/valid/1/edit', form => { })->status_is(200)->content_like(qr/Updated Organisation/); #Failed validation on approved organisation -$t->post_ok('/admin/organisations/valid/1/edit', form => { +$t->post_ok("/admin/organisations/$valid_id", form => { name => 'Shinra Electric Power Company', street_name => 'Sector 0, Midgar, Eastern Continent', sector => 'A', @@ -67,7 +71,7 @@ $t->post_ok('/admin/organisations/valid/1/edit', form => { })->content_like(qr/The validation has failed/); #Valid pending organisation update -$t->post_ok('/admin/organisations/pending/2/edit', form => { +$t->post_ok("/admin/organisations/$pending_id", form => { name => '7th Heaven', street_name => 'Slums, Sector 7', town => 'Midgar', @@ -75,14 +79,14 @@ $t->post_ok('/admin/organisations/pending/2/edit', form => { })->status_is(200)->content_like(qr/Updated Organisation/); #Failed validation on pending organisation -$t->post_ok('/admin/organisations/pending/2/edit', form => { +$t->post_ok("/admin/organisations/$pending_id", form => { name => '7th Heaven', street_name => 'Slums, Sector 7', postcode => 'WC1E 6AD', })->content_like(qr/The validation has failed/); #Valid adding organisation -$t->post_ok('/admin/organisations/add/submit', form => { +$t->post_ok('/admin/organisations/add', form => { name => 'Wall Market', street_name => 'Slums, Sector 6', town => 'Midgar', @@ -91,7 +95,7 @@ $t->post_ok('/admin/organisations/add/submit', form => { })->status_is(200)->content_like(qr/Added Organisation/); #Failed validation on adding organisation -$t->post_ok('/admin/organisations/add/submit', form => { +$t->post_ok('/admin/organisations/add', form => { name => 'Wall Market', street_name => 'Slums, Sector 6', postcode => 'TN35 5AQ', From 758089bad168f9470e05a3e31dc8b94d0a3aec89 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 14:53:04 +0100 Subject: [PATCH 21/83] Removed pending_read template as uneccessary now --- .../admin/organisations/pending_read.html.ep | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 templates/admin/organisations/pending_read.html.ep diff --git a/templates/admin/organisations/pending_read.html.ep b/templates/admin/organisations/pending_read.html.ep deleted file mode 100644 index bbf99e0..0000000 --- a/templates/admin/organisations/pending_read.html.ep +++ /dev/null @@ -1,49 +0,0 @@ -% layout 'admin'; -% title 'Organisations'; -% content_for javascript => begin -% end -% if ( my $error = flash 'error' ) { - -% } elsif ( my $success = flash 'success' ) { - -% } -
    -
    -

    - Transactions -

    -
      - % for my $transaction ( $transactions->all ) { -
    • -
      -
      -
      From: <%= $transaction->buyer->name %>
      -
      To: <%= $transaction->seller->name %>
      -
      Value: <%= $transaction->value %>
      -
      -
      -
    • - % } -
    • -
      - %= bootstrap_pagination( $c->param('page') || 1, $transactions->pager->last_page, { class => 'justify-content-center' } ); -
      -
    • -
    -
    From 815056b771734be85d90f173a872909f5eb558e7 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 16:35:51 +0100 Subject: [PATCH 22/83] Fix error in fixtures data --- t/etc/fixtures/config/users.pl | 2 +- t/etc/fixtures/data/users/entities/5.fix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/t/etc/fixtures/config/users.pl b/t/etc/fixtures/config/users.pl index 88d3066..9a7f5ca 100644 --- a/t/etc/fixtures/config/users.pl +++ b/t/etc/fixtures/config/users.pl @@ -96,7 +96,7 @@ my $entity5 = { email => 'org@example.com', password => 'abc123', }, - type => "customer", + type => "organisation", }; my $entity6 = { diff --git a/t/etc/fixtures/data/users/entities/5.fix b/t/etc/fixtures/data/users/entities/5.fix index e22cd57..9aa374d 100644 --- a/t/etc/fixtures/data/users/entities/5.fix +++ b/t/etc/fixtures/data/users/entities/5.fix @@ -1,4 +1,4 @@ $HASH1 = { id => 5, - type => 'customer' + type => 'organisation' }; From 4cc8aad36766bc0d1bfc0085a84f7a55994fa365 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 16:38:03 +0100 Subject: [PATCH 23/83] Fix admin user test for entity upgrade --- lib/Pear/LocalLoop.pm | 1 - lib/Pear/LocalLoop/Controller/Admin/Users.pm | 20 ++--- t/admin/user.t | 85 +++++++------------- templates/admin/users/read.html.ep | 6 +- 4 files changed, 41 insertions(+), 71 deletions(-) diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index c2e5972..df43bf5 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -175,7 +175,6 @@ sub startup { $admin_routes->get('/users/:id')->to('admin-users#read'); $admin_routes->post('/users/:id')->to('admin-users#update'); $admin_routes->post('/users/:id/delete')->to('admin-users#delete'); - $admin_routes->post('/users/:id/edit')->to('admin-users#edit'); $admin_routes->get('/organisations')->to('admin-organisations#list'); $admin_routes->get('/organisations/add')->to('admin-organisations#add_org'); diff --git a/lib/Pear/LocalLoop/Controller/Admin/Users.pm b/lib/Pear/LocalLoop/Controller/Admin/Users.pm index 064ffba..803b68e 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Users.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Users.pm @@ -40,7 +40,7 @@ sub read { } } -sub edit { +sub update { my $c = shift; my $id = $c->param('id'); @@ -61,10 +61,10 @@ sub edit { $validation->required('postcode')->postcode; $validation->optional('new_password'); - if ( defined $user->customer_id ) { + if ( $user->type eq 'customer' ) { $validation->required('display_name'); $validation->required('full_name'); - } elsif ( defined $user->organisation_id ) { + } elsif ( $user->type eq 'organisation' ) { $validation->required('name'); $validation->required('street_name'); $validation->required('town'); @@ -73,15 +73,14 @@ sub edit { if ( $validation->has_error ) { $c->flash( error => 'The validation has failed' ); - $c->app->log->warn(Dumper $validation); return $c->redirect_to( '/admin/users/' . $id ); } - if ( defined $user->customer_id ){ + if ( $user->type eq 'customer' ){ try { $c->schema->txn_do( sub { - $user->customer->update({ + $user->entity->customer->update({ full_name => $validation->param('full_name'), display_name => $validation->param('display_name'), postcode => $validation->param('postcode'), @@ -100,11 +99,11 @@ sub edit { }; } } - elsif ( defined $user->organisation_id ) { + elsif ( $user->type eq 'organisation' ) { try { $c->schema->txn_do( sub { - $user->organisation->update({ + $user->entity->organisation->update({ name => $validation->param('name'), street_name => $validation->param('street_name'), town => $validation->param('town'), @@ -129,9 +128,4 @@ sub edit { $c->redirect_to( '/admin/users/' . $id ); } -sub update { - my $c = shift; - $c->redirect_to( '/admin/users' ); -} - 1; diff --git a/t/admin/user.t b/t/admin/user.t index 129dce6..58ba1e0 100644 --- a/t/admin/user.t +++ b/t/admin/user.t @@ -1,49 +1,17 @@ use Mojo::Base -strict; +use FindBin qw/ $Bin /; + use Test::More; -use Mojo::JSON; use Test::Pear::LocalLoop; -my $framework = Test::Pear::LocalLoop->new; +my $framework = Test::Pear::LocalLoop->new( + etc_dir => "$Bin/../etc", +); +$framework->install_fixtures('users'); my $t = $framework->framework; my $schema = $t->app->schema; -my $user = $schema->resultset('User')->create({ - email => 'admin@example.com', - password => 'abc123', - administrator => {}, -}); - -is $schema->resultset('Administrator')->count, 1, 'Admin Created'; - -my $user1 = { - token => 'a', - full_name => 'Test User1', - display_name => 'Test User1', - email => 'test1@example.com', - postcode => 'LA1 1AA', - password => 'abc123', - year_of_birth => 2006, -}; - -my $org = { - token => 'e', - email => 'test50@example.com', - name => '7th Heaven', - street_name => 'Slums, Sector 7', - town => 'Midgar', - sector => 'A', - postcode => 'WC1E 6AD', - password => 'abc123', -}; - -$schema->resultset('AccountToken')->create({ name => $_->{token} }) - for ( $user1, $org ); - -$framework->register_customer($user1); - -$framework->register_organisation($org); - #login to admin $t->ua->max_redirects(10); $t->post_ok('/admin', form => { @@ -51,27 +19,36 @@ $t->post_ok('/admin', form => { password => 'abc123', })->status_is(200); +$t->get_ok('/admin/users') + ->status_is(200) + ->or($framework->dump_error); + #Read customer user -$t->get_ok('/admin/users/2/') +$t->get_ok('/admin/users/1') ->status_is(200); #Read organisation user -$t->get_ok('/admin/users/3/') +$t->get_ok('/admin/users/5') ->status_is(200); #Valid customer user update -$t->post_ok('/admin/users/2/edit', form => { - email => 'test12@example.com', - new_password => 'abc123', - full_name => 'Test User1', - display_name => 'Test User1', - town => 'Midgar', - sector => 'A', - postcode => 'WC1E 6AD', -})->status_is(200)->content_like(qr/Updated User/); +$t->post_ok( + '/admin/users/1', + form => { + email => 'test12@example.com', + new_password => 'abc123', + full_name => 'Test User1', + display_name => 'Test User1', + town => 'Midgar', + sector => 'A', + postcode => 'WC1E 6AD', + }) + ->status_is(200) + ->or($framework->dump_error) + ->content_like(qr/Updated User/); #Failed validation on customer user from no postcode -$t->post_ok('/admin/users/2/edit', form => { +$t->post_ok('/admin/users/2', form => { email => 'test12@example.com', new_password => 'abc123', full_name => 'Test User1', @@ -81,7 +58,7 @@ $t->post_ok('/admin/users/2/edit', form => { })->content_like(qr/The validation has failed/); #Failed validation on customer user from no display name -$t->post_ok('/admin/users/2/edit', form => { +$t->post_ok('/admin/users/2', form => { email => 'test12@example.com', new_password => 'abc123', full_name => 'Test User1', @@ -91,7 +68,7 @@ $t->post_ok('/admin/users/2/edit', form => { })->content_like(qr/The validation has failed/); #Valid organisation user update -$t->post_ok('/admin/users/3/edit', form => { +$t->post_ok('/admin/users/5', form => { email => 'test51@example.com', new_password => 'abc123', name => '7th Heaven', @@ -102,7 +79,7 @@ $t->post_ok('/admin/users/3/edit', form => { })->status_is(200)->content_like(qr/Updated User/); #Failed validation on organisation user from no postcode -$t->post_ok('/admin/users/3/edit', form => { +$t->post_ok('/admin/users/5', form => { email => 'test50@example.com', new_password => 'abc123', name => '7th Heaven', @@ -112,7 +89,7 @@ $t->post_ok('/admin/users/3/edit', form => { })->content_like(qr/The validation has failed/); #Failed validation on organisation user from no street name -$t->post_ok('/admin/users/3/edit', form => { +$t->post_ok('/admin/users/5', form => { email => 'test50@example.com', new_password => 'abc123', name => '7th Heaven', diff --git a/templates/admin/users/read.html.ep b/templates/admin/users/read.html.ep index 8e6cb47..eafe62e 100644 --- a/templates/admin/users/read.html.ep +++ b/templates/admin/users/read.html.ep @@ -25,14 +25,14 @@
    - +

    Leave blank unless you want to change their password

    - % if ( my $customer_rs = $user->customer ) { + % if ( my $customer_rs = $user->entity->customer ) {

    Customer Details

    @@ -52,7 +52,7 @@ - % } elsif ( my $org_rs = $user->organisation ) { + % } elsif ( my $org_rs = $user->entity->organisation ) {

    Organisation Details

    From bf16077b10d37a317a931c810686db43d8b859eb Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 16:46:25 +0100 Subject: [PATCH 24/83] Fix register for new entity work --- lib/Pear/LocalLoop/Controller/Api/Register.pm | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/Register.pm b/lib/Pear/LocalLoop/Controller/Api/Register.pm index dfff302..8ba4f11 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Register.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Register.pm @@ -48,7 +48,7 @@ has error_messages => sub { }; }; -sub post_register{ +sub post_register { my $c = shift; my $validation = $c->validation; @@ -87,17 +87,19 @@ sub post_register{ name => $validation->param('token'), used => 0, })->update({ used => 1 }); - # Create customer as a seperate step, so we dont leak data - my $customer = $c->schema->resultset('Customer')->create({ - full_name => $validation->param('full_name'), - display_name => $validation->param('display_name'), - year_of_birth => $validation->param('year_of_birth'), - postcode => $validation->param('postcode'), - }); - $c->schema->resultset('User')->create({ - customer => $customer, - email => $validation->param('email'), - password => $validation->param('password'), + + $c->schema->resultset('Entity')->create({ + customer => { + full_name => $validation->param('full_name'), + display_name => $validation->param('display_name'), + year_of_birth => $validation->param('year_of_birth'), + postcode => $validation->param('postcode'), + }, + user => { + email => $validation->param('email'), + password => $validation->param('password'), + }, + type => 'customer', }); }); @@ -109,17 +111,19 @@ sub post_register{ name => $validation->param('token'), used => 0, })->update({ used => 1 }); - my $organisation = $c->schema->resultset('Organisation')->create({ - name => $validation->param('name'), - street_name => $validation->param('street_name'), - town => $validation->param('town'), - sector => $validation->param('sector'), - postcode => $validation->param('postcode'), - }); - $c->schema->resultset('User')->create({ - organisation => $organisation, - email => $validation->param('email'), - password => $validation->param('password'), + $c->schema->resultset('Entity')->create({ + organisation => { + name => $validation->param('name'), + street_name => $validation->param('street_name'), + town => $validation->param('town'), + sector => $validation->param('sector'), + postcode => $validation->param('postcode'), + }, + user => { + email => $validation->param('email'), + password => $validation->param('password'), + }, + type => 'organisation', }); }); } From 1ab2d0b71c1bbd1d8a5364c5116ad62b61f5aa85 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 16:58:23 +0100 Subject: [PATCH 25/83] Added new search data fixtures --- t/etc/fixtures/config/search.pl | 88 +++++++++++++++++++ t/etc/fixtures/data/search/_config_set | 57 ++++++++++++ t/etc/fixtures/data/search/_dumper_version | 1 + t/etc/fixtures/data/search/customers/1.fix | 8 ++ t/etc/fixtures/data/search/customers/2.fix | 8 ++ t/etc/fixtures/data/search/customers/3.fix | 8 ++ t/etc/fixtures/data/search/customers/4.fix | 8 ++ t/etc/fixtures/data/search/customers/5.fix | 8 ++ t/etc/fixtures/data/search/entities/1.fix | 4 + t/etc/fixtures/data/search/entities/10.fix | 4 + t/etc/fixtures/data/search/entities/11.fix | 4 + t/etc/fixtures/data/search/entities/2.fix | 4 + t/etc/fixtures/data/search/entities/3.fix | 4 + t/etc/fixtures/data/search/entities/4.fix | 4 + t/etc/fixtures/data/search/entities/5.fix | 4 + t/etc/fixtures/data/search/entities/6.fix | 4 + t/etc/fixtures/data/search/entities/7.fix | 4 + t/etc/fixtures/data/search/entities/8.fix | 4 + t/etc/fixtures/data/search/entities/9.fix | 4 + t/etc/fixtures/data/search/leaderboards/1.fix | 5 ++ t/etc/fixtures/data/search/leaderboards/2.fix | 5 ++ t/etc/fixtures/data/search/leaderboards/3.fix | 5 ++ t/etc/fixtures/data/search/leaderboards/4.fix | 5 ++ t/etc/fixtures/data/search/leaderboards/5.fix | 5 ++ t/etc/fixtures/data/search/leaderboards/6.fix | 5 ++ t/etc/fixtures/data/search/leaderboards/7.fix | 5 ++ t/etc/fixtures/data/search/leaderboards/8.fix | 5 ++ .../fixtures/data/search/organisations/1.fix | 16 ++++ .../fixtures/data/search/organisations/2.fix | 16 ++++ .../fixtures/data/search/organisations/3.fix | 16 ++++ .../fixtures/data/search/organisations/4.fix | 16 ++++ .../fixtures/data/search/organisations/5.fix | 16 ++++ .../fixtures/data/search/organisations/6.fix | 16 ++++ t/etc/fixtures/data/search/users/1.fix | 8 ++ t/etc/fixtures/data/search/users/2.fix | 8 ++ t/etc/fixtures/data/search/users/3.fix | 8 ++ t/etc/fixtures/data/search/users/4.fix | 8 ++ t/etc/fixtures/data/search/users/5.fix | 8 ++ t/etc/fixtures/data/search/users/6.fix | 8 ++ 39 files changed, 414 insertions(+) create mode 100644 t/etc/fixtures/config/search.pl create mode 100644 t/etc/fixtures/data/search/_config_set create mode 100644 t/etc/fixtures/data/search/_dumper_version create mode 100644 t/etc/fixtures/data/search/customers/1.fix create mode 100644 t/etc/fixtures/data/search/customers/2.fix create mode 100644 t/etc/fixtures/data/search/customers/3.fix create mode 100644 t/etc/fixtures/data/search/customers/4.fix create mode 100644 t/etc/fixtures/data/search/customers/5.fix create mode 100644 t/etc/fixtures/data/search/entities/1.fix create mode 100644 t/etc/fixtures/data/search/entities/10.fix create mode 100644 t/etc/fixtures/data/search/entities/11.fix create mode 100644 t/etc/fixtures/data/search/entities/2.fix create mode 100644 t/etc/fixtures/data/search/entities/3.fix create mode 100644 t/etc/fixtures/data/search/entities/4.fix create mode 100644 t/etc/fixtures/data/search/entities/5.fix create mode 100644 t/etc/fixtures/data/search/entities/6.fix create mode 100644 t/etc/fixtures/data/search/entities/7.fix create mode 100644 t/etc/fixtures/data/search/entities/8.fix create mode 100644 t/etc/fixtures/data/search/entities/9.fix create mode 100644 t/etc/fixtures/data/search/leaderboards/1.fix create mode 100644 t/etc/fixtures/data/search/leaderboards/2.fix create mode 100644 t/etc/fixtures/data/search/leaderboards/3.fix create mode 100644 t/etc/fixtures/data/search/leaderboards/4.fix create mode 100644 t/etc/fixtures/data/search/leaderboards/5.fix create mode 100644 t/etc/fixtures/data/search/leaderboards/6.fix create mode 100644 t/etc/fixtures/data/search/leaderboards/7.fix create mode 100644 t/etc/fixtures/data/search/leaderboards/8.fix create mode 100644 t/etc/fixtures/data/search/organisations/1.fix create mode 100644 t/etc/fixtures/data/search/organisations/2.fix create mode 100644 t/etc/fixtures/data/search/organisations/3.fix create mode 100644 t/etc/fixtures/data/search/organisations/4.fix create mode 100644 t/etc/fixtures/data/search/organisations/5.fix create mode 100644 t/etc/fixtures/data/search/organisations/6.fix create mode 100644 t/etc/fixtures/data/search/users/1.fix create mode 100644 t/etc/fixtures/data/search/users/2.fix create mode 100644 t/etc/fixtures/data/search/users/3.fix create mode 100644 t/etc/fixtures/data/search/users/4.fix create mode 100644 t/etc/fixtures/data/search/users/5.fix create mode 100644 t/etc/fixtures/data/search/users/6.fix diff --git a/t/etc/fixtures/config/search.pl b/t/etc/fixtures/config/search.pl new file mode 100644 index 0000000..78c869c --- /dev/null +++ b/t/etc/fixtures/config/search.pl @@ -0,0 +1,88 @@ +#! /usr/bin/env perl + +use strict; +use warnings; + +use DBIx::Class::Fixtures; +use FindBin qw/ $Bin /; +use lib "$Bin/../../../../lib"; +use Pear::LocalLoop::Schema; +use DateTime; + +my $fixtures = DBIx::Class::Fixtures->new({ + config_dir => "$Bin", +}); + +my $schema = Pear::LocalLoop::Schema->connect('dbi:SQLite::memory:'); + +$schema->deploy; + +$fixtures->populate({ + directory => "$Bin/../data/users", + no_deploy => 1, + schema => $schema, +}); + +my @orgs = ( + { + organisation => { + name => 'Avanti Bar & Restaurant', + street_name => '57 Main St', + town => 'Kirkby Lonsdale', + postcode => 'LA6 2AH', + sector => 'I', + }, + type => "organisation", + }, + { + organisation => { + name => 'Full House Noodle Bar', + street_name => '21 Common Garden St', + town => 'Lancaster', + postcode => 'LA1 1XD', + sector => 'I', + }, + type => "organisation", + }, + { + organisation => { + name => 'The Quay\'s Fishbar', + street_name => '1 Adcliffe Rd', + town => 'Lancaster', + postcode => 'LA1 1SS', + sector => 'I', + }, + type => "organisation", + }, + { + organisation => { + name => 'Dan\'s Fishop', + street_name => '56 North Rd', + town => 'Lancaster', + postcode => 'LA1 1LT', + sector => 'I', + }, + type => "organisation", + }, + { + organisation => { + name => 'Hodgeson\'s Chippy', + street_name => '96 Prospect St', + town => 'Lancaster', + postcode => 'LA1 3BH', + sector => 'I', + }, + type => "organisation", + }, +); + +$schema->resultset('Entity')->create( $_ ) for @orgs; + +my $data_set = 'search'; + +$fixtures->dump({ + all => 1, + schema => $schema, + directory => "$Bin/../data/" . $data_set, +}); + diff --git a/t/etc/fixtures/data/search/_config_set b/t/etc/fixtures/data/search/_config_set new file mode 100644 index 0000000..5f8269a --- /dev/null +++ b/t/etc/fixtures/data/search/_config_set @@ -0,0 +1,57 @@ +$VAR1 = { + 'has_many' => { + 'fetch' => 0 + }, + 'sets' => [ + { + 'class' => 'Feedback', + 'quantity' => 'all' + }, + { + 'quantity' => 'all', + 'class' => 'Transaction' + }, + { + 'quantity' => 'all', + 'class' => 'User' + }, + { + 'quantity' => 'all', + 'class' => 'LeaderboardSet' + }, + { + 'class' => 'Customer', + 'quantity' => 'all' + }, + { + 'quantity' => 'all', + 'class' => 'Organisation' + }, + { + 'quantity' => 'all', + 'class' => 'LeaderboardValue' + }, + { + 'class' => 'Entity', + 'quantity' => 'all' + }, + { + 'quantity' => 'all', + 'class' => 'Leaderboard' + }, + { + 'class' => 'SessionToken', + 'quantity' => 'all' + }, + { + 'quantity' => 'all', + 'class' => 'AccountToken' + } + ], + 'might_have' => { + 'fetch' => 0 + }, + 'belongs_to' => { + 'fetch' => 0 + } + }; diff --git a/t/etc/fixtures/data/search/_dumper_version b/t/etc/fixtures/data/search/_dumper_version new file mode 100644 index 0000000..8d0321e --- /dev/null +++ b/t/etc/fixtures/data/search/_dumper_version @@ -0,0 +1 @@ +1.001036 \ No newline at end of file diff --git a/t/etc/fixtures/data/search/customers/1.fix b/t/etc/fixtures/data/search/customers/1.fix new file mode 100644 index 0000000..705c22d --- /dev/null +++ b/t/etc/fixtures/data/search/customers/1.fix @@ -0,0 +1,8 @@ +$HASH1 = { + display_name => 'Test User1', + entity_id => 1, + full_name => 'Test User1', + id => 1, + postcode => 'LA1 1AA', + year_of_birth => 2006 + }; diff --git a/t/etc/fixtures/data/search/customers/2.fix b/t/etc/fixtures/data/search/customers/2.fix new file mode 100644 index 0000000..0fee4aa --- /dev/null +++ b/t/etc/fixtures/data/search/customers/2.fix @@ -0,0 +1,8 @@ +$HASH1 = { + display_name => 'Test User2', + entity_id => 2, + full_name => 'Test User2', + id => 2, + postcode => 'LA1 1AA', + year_of_birth => 2006 + }; diff --git a/t/etc/fixtures/data/search/customers/3.fix b/t/etc/fixtures/data/search/customers/3.fix new file mode 100644 index 0000000..a64e402 --- /dev/null +++ b/t/etc/fixtures/data/search/customers/3.fix @@ -0,0 +1,8 @@ +$HASH1 = { + display_name => 'Test User3', + entity_id => 3, + full_name => 'Test User3', + id => 3, + postcode => 'LA1 1AA', + year_of_birth => 2006 + }; diff --git a/t/etc/fixtures/data/search/customers/4.fix b/t/etc/fixtures/data/search/customers/4.fix new file mode 100644 index 0000000..49d0530 --- /dev/null +++ b/t/etc/fixtures/data/search/customers/4.fix @@ -0,0 +1,8 @@ +$HASH1 = { + display_name => 'Test User4', + entity_id => 4, + full_name => 'Test User4', + id => 4, + postcode => 'LA1 1AA', + year_of_birth => 2006 + }; diff --git a/t/etc/fixtures/data/search/customers/5.fix b/t/etc/fixtures/data/search/customers/5.fix new file mode 100644 index 0000000..50ef1c2 --- /dev/null +++ b/t/etc/fixtures/data/search/customers/5.fix @@ -0,0 +1,8 @@ +$HASH1 = { + display_name => 'Test Admin', + entity_id => 6, + full_name => 'Test Admin', + id => 5, + postcode => 'LA1 1AA', + year_of_birth => 2006 + }; diff --git a/t/etc/fixtures/data/search/entities/1.fix b/t/etc/fixtures/data/search/entities/1.fix new file mode 100644 index 0000000..a45e065 --- /dev/null +++ b/t/etc/fixtures/data/search/entities/1.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 1, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/search/entities/10.fix b/t/etc/fixtures/data/search/entities/10.fix new file mode 100644 index 0000000..918c437 --- /dev/null +++ b/t/etc/fixtures/data/search/entities/10.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 10, + type => 'organisation' + }; diff --git a/t/etc/fixtures/data/search/entities/11.fix b/t/etc/fixtures/data/search/entities/11.fix new file mode 100644 index 0000000..4f5536d --- /dev/null +++ b/t/etc/fixtures/data/search/entities/11.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 11, + type => 'organisation' + }; diff --git a/t/etc/fixtures/data/search/entities/2.fix b/t/etc/fixtures/data/search/entities/2.fix new file mode 100644 index 0000000..20849e3 --- /dev/null +++ b/t/etc/fixtures/data/search/entities/2.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 2, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/search/entities/3.fix b/t/etc/fixtures/data/search/entities/3.fix new file mode 100644 index 0000000..3fd6a9a --- /dev/null +++ b/t/etc/fixtures/data/search/entities/3.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 3, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/search/entities/4.fix b/t/etc/fixtures/data/search/entities/4.fix new file mode 100644 index 0000000..bacb15b --- /dev/null +++ b/t/etc/fixtures/data/search/entities/4.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 4, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/search/entities/5.fix b/t/etc/fixtures/data/search/entities/5.fix new file mode 100644 index 0000000..9aa374d --- /dev/null +++ b/t/etc/fixtures/data/search/entities/5.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 5, + type => 'organisation' + }; diff --git a/t/etc/fixtures/data/search/entities/6.fix b/t/etc/fixtures/data/search/entities/6.fix new file mode 100644 index 0000000..ee9f57f --- /dev/null +++ b/t/etc/fixtures/data/search/entities/6.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 6, + type => 'customer' + }; diff --git a/t/etc/fixtures/data/search/entities/7.fix b/t/etc/fixtures/data/search/entities/7.fix new file mode 100644 index 0000000..beeb562 --- /dev/null +++ b/t/etc/fixtures/data/search/entities/7.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 7, + type => 'organisation' + }; diff --git a/t/etc/fixtures/data/search/entities/8.fix b/t/etc/fixtures/data/search/entities/8.fix new file mode 100644 index 0000000..da99144 --- /dev/null +++ b/t/etc/fixtures/data/search/entities/8.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 8, + type => 'organisation' + }; diff --git a/t/etc/fixtures/data/search/entities/9.fix b/t/etc/fixtures/data/search/entities/9.fix new file mode 100644 index 0000000..fe3b1f4 --- /dev/null +++ b/t/etc/fixtures/data/search/entities/9.fix @@ -0,0 +1,4 @@ +$HASH1 = { + id => 9, + type => 'organisation' + }; diff --git a/t/etc/fixtures/data/search/leaderboards/1.fix b/t/etc/fixtures/data/search/leaderboards/1.fix new file mode 100644 index 0000000..597a843 --- /dev/null +++ b/t/etc/fixtures/data/search/leaderboards/1.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 1, + name => 'Daily Total', + type => 'daily_total' + }; diff --git a/t/etc/fixtures/data/search/leaderboards/2.fix b/t/etc/fixtures/data/search/leaderboards/2.fix new file mode 100644 index 0000000..08fef2c --- /dev/null +++ b/t/etc/fixtures/data/search/leaderboards/2.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 2, + name => 'Daily Count', + type => 'daily_count' + }; diff --git a/t/etc/fixtures/data/search/leaderboards/3.fix b/t/etc/fixtures/data/search/leaderboards/3.fix new file mode 100644 index 0000000..4166f18 --- /dev/null +++ b/t/etc/fixtures/data/search/leaderboards/3.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 3, + name => 'Weekly Total', + type => 'weekly_total' + }; diff --git a/t/etc/fixtures/data/search/leaderboards/4.fix b/t/etc/fixtures/data/search/leaderboards/4.fix new file mode 100644 index 0000000..feb773c --- /dev/null +++ b/t/etc/fixtures/data/search/leaderboards/4.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 4, + name => 'Weekly Count', + type => 'weekly_count' + }; diff --git a/t/etc/fixtures/data/search/leaderboards/5.fix b/t/etc/fixtures/data/search/leaderboards/5.fix new file mode 100644 index 0000000..d0522b2 --- /dev/null +++ b/t/etc/fixtures/data/search/leaderboards/5.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 5, + name => 'Monthly Total', + type => 'monthly_total' + }; diff --git a/t/etc/fixtures/data/search/leaderboards/6.fix b/t/etc/fixtures/data/search/leaderboards/6.fix new file mode 100644 index 0000000..f7bb145 --- /dev/null +++ b/t/etc/fixtures/data/search/leaderboards/6.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 6, + name => 'Monthly Count', + type => 'monthly_count' + }; diff --git a/t/etc/fixtures/data/search/leaderboards/7.fix b/t/etc/fixtures/data/search/leaderboards/7.fix new file mode 100644 index 0000000..b2aadcd --- /dev/null +++ b/t/etc/fixtures/data/search/leaderboards/7.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 7, + name => 'All Time Total', + type => 'all_time_total' + }; diff --git a/t/etc/fixtures/data/search/leaderboards/8.fix b/t/etc/fixtures/data/search/leaderboards/8.fix new file mode 100644 index 0000000..df58698 --- /dev/null +++ b/t/etc/fixtures/data/search/leaderboards/8.fix @@ -0,0 +1,5 @@ +$HASH1 = { + id => 8, + name => 'All Time Count', + type => 'all_time_count' + }; diff --git a/t/etc/fixtures/data/search/organisations/1.fix b/t/etc/fixtures/data/search/organisations/1.fix new file mode 100644 index 0000000..b761450 --- /dev/null +++ b/t/etc/fixtures/data/search/organisations/1.fix @@ -0,0 +1,16 @@ +$HASH1 = { + country => undef, + entity_id + => 5, + id => 1, + name => 'Test Org', + pending => 0, + postcode + => 'LA1 1AA', + sector => undef, + street_name + => 'Test Street', + submitted_by_id + => undef, + town => 'Lancaster' + }; diff --git a/t/etc/fixtures/data/search/organisations/2.fix b/t/etc/fixtures/data/search/organisations/2.fix new file mode 100644 index 0000000..5da65f1 --- /dev/null +++ b/t/etc/fixtures/data/search/organisations/2.fix @@ -0,0 +1,16 @@ +$HASH1 = { + country => undef, + entity_id + => 7, + id => 2, + name => 'Avanti Bar & Restaurant', + pending => 0, + postcode + => 'LA6 2AH', + sector => 'I', + street_name + => '57 Main St', + submitted_by_id + => undef, + town => 'Kirkby Lonsdale' + }; diff --git a/t/etc/fixtures/data/search/organisations/3.fix b/t/etc/fixtures/data/search/organisations/3.fix new file mode 100644 index 0000000..000cb56 --- /dev/null +++ b/t/etc/fixtures/data/search/organisations/3.fix @@ -0,0 +1,16 @@ +$HASH1 = { + country => undef, + entity_id + => 8, + id => 3, + name => 'Full House Noodle Bar', + pending => 0, + postcode + => 'LA1 1XD', + sector => 'I', + street_name + => '21 Common Garden St', + submitted_by_id + => undef, + town => 'Lancaster' + }; diff --git a/t/etc/fixtures/data/search/organisations/4.fix b/t/etc/fixtures/data/search/organisations/4.fix new file mode 100644 index 0000000..565b447 --- /dev/null +++ b/t/etc/fixtures/data/search/organisations/4.fix @@ -0,0 +1,16 @@ +$HASH1 = { + country => undef, + entity_id + => 9, + id => 4, + name => 'The Quay\'s Fishbar', + pending => 0, + postcode + => 'LA1 1SS', + sector => 'I', + street_name + => '1 Adcliffe Rd', + submitted_by_id + => undef, + town => 'Lancaster' + }; diff --git a/t/etc/fixtures/data/search/organisations/5.fix b/t/etc/fixtures/data/search/organisations/5.fix new file mode 100644 index 0000000..9e9cbe5 --- /dev/null +++ b/t/etc/fixtures/data/search/organisations/5.fix @@ -0,0 +1,16 @@ +$HASH1 = { + country => undef, + entity_id + => 10, + id => 5, + name => 'Dan\'s Fishop', + pending => 0, + postcode + => 'LA1 1LT', + sector => 'I', + street_name + => '56 North Rd', + submitted_by_id + => undef, + town => 'Lancaster' + }; diff --git a/t/etc/fixtures/data/search/organisations/6.fix b/t/etc/fixtures/data/search/organisations/6.fix new file mode 100644 index 0000000..2d941bf --- /dev/null +++ b/t/etc/fixtures/data/search/organisations/6.fix @@ -0,0 +1,16 @@ +$HASH1 = { + country => undef, + entity_id + => 11, + id => 6, + name => 'Hodgeson\'s Chippy', + pending => 0, + postcode + => 'LA1 3BH', + sector => 'I', + street_name + => '96 Prospect St', + submitted_by_id + => undef, + town => 'Lancaster' + }; diff --git a/t/etc/fixtures/data/search/users/1.fix b/t/etc/fixtures/data/search/users/1.fix new file mode 100644 index 0000000..9507717 --- /dev/null +++ b/t/etc/fixtures/data/search/users/1.fix @@ -0,0 +1,8 @@ +$HASH1 = { + email => 'test1@example.com', + entity_id => 1, + id => 1, + is_admin => 0, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$HSuznDeSU1fuONwKhp2/S.TX/X4p4g0dHtz20kVXxprm8hIg5QQma' + }; diff --git a/t/etc/fixtures/data/search/users/2.fix b/t/etc/fixtures/data/search/users/2.fix new file mode 100644 index 0000000..9654ada --- /dev/null +++ b/t/etc/fixtures/data/search/users/2.fix @@ -0,0 +1,8 @@ +$HASH1 = { + email => 'test2@example.com', + entity_id => 2, + id => 2, + is_admin => 0, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$5XYLWPJvVGuWUvfSWj9cIOg4/tyB4fZ3knzwgw5UnBSKFBFIKOiFC' + }; diff --git a/t/etc/fixtures/data/search/users/3.fix b/t/etc/fixtures/data/search/users/3.fix new file mode 100644 index 0000000..fd07648 --- /dev/null +++ b/t/etc/fixtures/data/search/users/3.fix @@ -0,0 +1,8 @@ +$HASH1 = { + email => 'test3@example.com', + entity_id => 3, + id => 3, + is_admin => 0, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$p5VU4leHetEvuz2P3uMfYuuPTkDGg8wsM7QuSVKkXR.s3B9T2JaVW' + }; diff --git a/t/etc/fixtures/data/search/users/4.fix b/t/etc/fixtures/data/search/users/4.fix new file mode 100644 index 0000000..e8300ec --- /dev/null +++ b/t/etc/fixtures/data/search/users/4.fix @@ -0,0 +1,8 @@ +$HASH1 = { + email => 'test4@example.com', + entity_id => 4, + id => 4, + is_admin => 0, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$fkkTUYA9zvt32iBbN7aOcOnmiHzOkbMEjN31PB9CsitGrE.KF7cAG' + }; diff --git a/t/etc/fixtures/data/search/users/5.fix b/t/etc/fixtures/data/search/users/5.fix new file mode 100644 index 0000000..97859de --- /dev/null +++ b/t/etc/fixtures/data/search/users/5.fix @@ -0,0 +1,8 @@ +$HASH1 = { + email => 'org@example.com', + entity_id => 5, + id => 5, + is_admin => 0, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$MWC45.w1AnLPNNHiS96ICOHfNlTrIqB0.7OPzy5qB.z0pZB0theo.' + }; diff --git a/t/etc/fixtures/data/search/users/6.fix b/t/etc/fixtures/data/search/users/6.fix new file mode 100644 index 0000000..d1b52dc --- /dev/null +++ b/t/etc/fixtures/data/search/users/6.fix @@ -0,0 +1,8 @@ +$HASH1 = { + email => 'admin@example.com', + entity_id => 6, + id => 6, + is_admin => 1, + join_date => '2017-08-31 12:17:25', + password => '$2a$08$1sPcPB9GnoNgzhBAk2bHSOqEmv6.Y6YLrAa2DJ6TR2WefzTYZQ92G' + }; From 2980fd129ff5ef283ecf4877f68d968c4818366e Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 17:53:54 +0100 Subject: [PATCH 26/83] Fix search test for entity upgrade --- lib/Pear/LocalLoop/Controller/Api/Upload.pm | 31 ++-- lib/Pear/LocalLoop/Schema/ResultSet/Entity.pm | 9 + t/api/search.t | 166 +++++------------- 3 files changed, 73 insertions(+), 133 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/Upload.pm b/lib/Pear/LocalLoop/Controller/Api/Upload.pm index 08db6cc..1f8c2e5 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Upload.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Upload.pm @@ -137,14 +137,15 @@ sub post_upload { return $c->api_validation_error if $validation->has_error; - $organisation = $c->schema->resultset('PendingOrganisation')->create({ - submitted_by => $user, - submitted_at => DateTime->now, - name => $validation->param('organisation_name'), - street_name => $validation->param('street_name'), - town => $validation->param('town'), - postcode => $validation->param('postcode'), + my $entity = $c->schema->resultset('Entity')->create_org({ + submitted_by_id => $user->id, + name => $validation->param('organisation_name'), + street_name => $validation->param('street_name'), + town => $validation->param('town'), + postcode => $validation->param('postcode'), + pending => \"1" }); + $organisation = $entity->organisation; } unless ( defined $organisation ) { @@ -164,12 +165,12 @@ sub post_upload { $purchase_time ||= DateTime->now(); my $file = defined $upload ? $c->store_file_from_upload( $upload ) : undef; - my $new_transaction = $organisation->create_related( - 'transactions', + my $new_transaction = $organisation->entity->create_related( + 'sales', { - buyer => $user, + buyer => $user->entity, value => $transaction_value, - ( defined $file ? ( proof_image => $file ) : ( proof_image => 'a' ) ), + ( defined $file ? ( proof_image => $file ) : () ), purchase_time => $c->format_db_datetime($purchase_time), } ); @@ -209,11 +210,15 @@ sub post_search { my $search_stmt = [ 'LOWER("name") LIKE ?', '%' . lc $search_name . '%' ]; - my $valid_orgs_rs = $c->schema->resultset('Organisation')->search( + my $org_rs = $c->schema->resultset('Organisation'); + my $valid_orgs_rs = $org_rs->search({ pending => 0 })->search( \$search_stmt, ); - my $pending_orgs_rs = $c->stash->{api_user}->pending_organisations->search( + my $pending_orgs_rs = $org_rs->search({ + pending => 1, + submitted_by_id => $c->stash->{api_user}->id, + })->search( \$search_stmt, ); diff --git a/lib/Pear/LocalLoop/Schema/ResultSet/Entity.pm b/lib/Pear/LocalLoop/Schema/ResultSet/Entity.pm index 735b0db..59fd059 100644 --- a/lib/Pear/LocalLoop/Schema/ResultSet/Entity.pm +++ b/lib/Pear/LocalLoop/Schema/ResultSet/Entity.pm @@ -7,4 +7,13 @@ use base 'DBIx::Class::ResultSet'; sub sales { shift->search_related('sales', @_) } +sub create_org { + my ( $self, $org ) = @_; + + return $self->create({ + organisation => $org, + type => 'organisation', + }); +} + 1; diff --git a/t/api/search.t b/t/api/search.t index dbc688d..561e437 100644 --- a/t/api/search.t +++ b/t/api/search.t @@ -1,122 +1,46 @@ use Mojo::Base -strict; +use FindBin qw/ $Bin /; + use Test::More; use Mojo::JSON; use Test::Pear::LocalLoop; -my $framework = Test::Pear::LocalLoop->new; +my $framework = Test::Pear::LocalLoop->new( + etc_dir => "$Bin/../etc", +); +$framework->install_fixtures('search'); + my $t = $framework->framework; my $schema = $t->app->schema; -my $dump_error = sub { diag $t->tx->res->to_string }; -my @account_tokens = ('a', 'b'); -$schema->resultset('AccountToken')->populate([ - [ qw/ name / ], - map { [ $_ ] } @account_tokens, -]); +#Login as customer +my $session_key = $framework->login({ + 'email' => 'test1@example.com', + 'password' => 'abc123', +}); -$schema->resultset('Organisation')->populate([ - [ qw/ name street_name town postcode / ], - [ "Avanti Bar & Restaurant", "57 Main St", "Kirkby Lonsdale", "LA6 2AH" ], - [ "Full House Noodle Bar", "21 Common Garden St", "Lancaster", "LA1 1XD" ], - [ "The Quay's Fishbar", "1 Adcliffe Rd", "Lancaster", "LA1 1SS" ], - [ "Dan's Fishop", "56 North Rd", "Lancaster", "LA1 1LT" ], - [ "Hodgeson's Chippy", "96 Prospect St", "Lancaster", "LA1 3BH" ], -]); +my $json; +my $upload; -#test with a customer. -print "test 1 - Create customer user account (Rufus)\n"; -my $emailRufus = 'rufus@shinra.energy'; -my $passwordRufus = 'MakoGold'; -my $testJson = { - 'usertype' => 'customer', - 'token' => shift(@account_tokens), - 'full_name' => 'RufusShinra', - 'display_name' => 'RufusShinra', - 'email' => $emailRufus, - 'postcode' => 'RG26 5NU', - 'password' => $passwordRufus, - 'year_of_birth' => 2006 -}; -$t->post_ok('/api/register' => json => $testJson) - ->status_is(200)->or($framework->dump_error) - ->json_is('/success', Mojo::JSON->true); - -#test with an organisation. -print "test 2 - Create organisation user account (Choco Billy)\n"; -my $emailBilly = 'choco.billy@chocofarm.org'; -my $passwordBilly = 'Choco'; -$testJson = { - 'usertype' => 'organisation', - 'token' => shift(@account_tokens), - 'name' => 'ChocoBillysGreens', - 'email' => $emailBilly, - 'postcode' => 'LA1 1HT', - 'password' => $passwordBilly, - 'street_name' => 'Market St', - 'town' => 'Lancaster', - 'sector' => 'A', -}; -$t->post_ok('/api/register' => json => $testJson) +$t->post_ok( '/api/upload', form => { + json => Mojo::JSON::encode_json({ + transaction_value => 10, + transaction_type => 3, + organisation_name => 'Shoreway Fisheries', + street_name => "2 James St", + town => "Lancaster", + postcode => "LA1 1UP", + purchase_time => "2017-08-14T11:29:07.965+01:00", + session_key => $session_key, + }), + file => { file => './t/test.jpg' }, + }) ->status_is(200) + ->or($framework->dump_error) ->json_is('/success', Mojo::JSON->true); -my $session_key; - -sub login_rufus { - $testJson = { - 'email' => $emailRufus, - 'password' => $passwordRufus, - }; - $t->post_ok('/api/login' => json => $testJson) - ->status_is(200) - ->json_is('/success', Mojo::JSON->true); - $session_key = $t->tx->res->json('/session_key'); -}; - -sub login_billy { - $testJson = { - 'email' => $emailBilly, - 'password' => $passwordBilly, - }; - $t->post_ok('/api/login' => json => $testJson) - ->status_is(200) - ->json_is('/success', Mojo::JSON->true); - $session_key = $t->tx->res->json('/session_key'); -}; - -sub log_out{ - $t->post_ok('/api/logout', json => { session_key => $session_key }) - ->status_is(200) - ->json_is('/success', Mojo::JSON->true); -} - - -###################################################### - -#Login as Rufus (customer) - -print "test 3 - Login - Rufus (cookies, customer)\n"; -login_rufus(); - -print "test 4 - Added something containing 'fish'\n"; -my $json = { - transaction_value => 10, - transaction_type => 3, - organisation_name => 'Shoreway Fisheries', - street_name => "2 James St", - town => "Lancaster", - postcode => "LA1 1UP", - purchase_time => "2017-08-14T11:29:07.965+01:00", - session_key => $session_key, -}; -my $upload = {json => Mojo::JSON::encode_json($json), file => {file => './t/test.jpg'}}; -$t->post_ok('/api/upload' => form => $upload ) - ->status_is(200) - ->json_is('/success', Mojo::JSON->true); - -print "test 5 - Logout Rufus \n"; -log_out(); +$framework->logout( $session_key ); #End of Rufus (customer) @@ -125,7 +49,10 @@ log_out(); #Login as Choco billy (organisation) print "test 6 - Login - Choco billy (cookies, organisation)\n"; -login_billy(); +$session_key = $framework->login({ + 'email' => 'org@example.com', + 'password' => 'abc123', +}); print "test 7 - Added something containing 'bar'\n"; $json = { @@ -160,16 +87,12 @@ $t->post_ok('/api/upload' => form => $upload ) ->json_is('/success', Mojo::JSON->true); print "test 9 - Logout Choco billy \n"; -log_out(); +$framework->logout( $session_key ); -#End of Choco billy (organisation) - -###################################################### - -#Login as Rufus (customer) - -print "test 10 - Login - Rufus (cookies, customer)\n"; -login_rufus(); +$session_key = $framework->login({ + 'email' => 'test1@example.com', + 'password' => 'abc123', +}); sub check_vars{ my ($searchTerm, $numValidated, $numUnvalidated) = @_; @@ -179,6 +102,7 @@ sub check_vars{ session_key => $session_key, }) ->status_is(200) + ->or($framework->dump_error) ->json_is('/success', Mojo::JSON->true) ->json_has("unvalidated") ->json_has("validated"); @@ -196,7 +120,7 @@ sub check_vars{ }; print "test 11 - search blank\n"; -check_vars(" ", 5, 1); +check_vars(" ", 6, 1); print "test 12 - Testing expected values with 'booths'\n"; #Expect 0 validated and 0 unvalidated with "booths". @@ -215,7 +139,7 @@ print "test 15 - Testing expected values with 'bar'\n"; check_vars("bar", 3, 0); print "test 16 - Logout Rufus \n"; -log_out(); +$framework->logout( $session_key ); #End of Rufus (customer) @@ -224,7 +148,10 @@ log_out(); #Login as Choco billy (organisation) print "test 17 - Login - Choco billy (cookies, organisation)\n"; -login_billy(); +$session_key = $framework->login({ + 'email' => 'org@example.com', + 'password' => 'abc123', +}); print "test 18 - Testing expected values with 'booths'\n"; #Expect 0 validated and 0 unvalidated with "booths". @@ -243,7 +170,6 @@ print "test 21 - Testing expected values with 'bar', with two unvalidated organi check_vars("bar", 3, 2); print "test 22 - Logout Choco billy \n"; -log_out(); - +$framework->logout( $session_key ); done_testing(); From 8d205d62c26c73eb5ea430dc9492cb2b8cd11015 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 18:04:51 +0100 Subject: [PATCH 27/83] Refactor stats test to use fixtures and fix stats endpoints for entity update --- lib/Pear/LocalLoop/Controller/Api/Stats.pm | 12 ++--- t/api/stats.t | 59 +++++++--------------- 2 files changed, 25 insertions(+), 46 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/Stats.pm b/lib/Pear/LocalLoop/Controller/Api/Stats.pm index c6b1c7d..bf15384 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Stats.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Stats.pm @@ -15,21 +15,21 @@ has error_messages => sub { sub post_index { my $c = shift; - my $user = $c->stash->{api_user}; + my $user = $c->stash->{api_user}->entity; - my $today_rs = $user->transactions->today_rs; + my $today_rs = $user->purchases->today_rs; my $today_sum = $today_rs->get_column('value')->sum; my $today_count = $today_rs->count; - my $week_rs = $user->transactions->week_rs; + my $week_rs = $user->purchases->week_rs; my $week_sum = $week_rs->get_column('value')->sum; my $week_count = $week_rs->count; - my $month_rs = $user->transactions->month_rs; + my $month_rs = $user->purchases->month_rs; my $month_sum = $month_rs->get_column('value')->sum; my $month_count = $month_rs->count; - my $user_rs = $user->transactions; + my $user_rs = $user->purchases; my $user_sum = $user_rs->get_column('value')->sum; my $user_count = $user_rs->count; @@ -40,7 +40,7 @@ sub post_index { my $leaderboard_rs = $c->schema->resultset('Leaderboard'); my $monthly_board = $leaderboard_rs->get_latest( 'monthly_total' ); my $monthly_values = $monthly_board->values; - my $current_user_position = $monthly_values ? $monthly_values->find({ user_id => $c->stash->{api_user}->id }) : undef; + my $current_user_position = $monthly_values ? $monthly_values->find({ entity_id => $user->id }) : undef; return $c->render( json => { success => Mojo::JSON->true, diff --git a/t/api/stats.t b/t/api/stats.t index 2a6d889..8e8fbc1 100644 --- a/t/api/stats.t +++ b/t/api/stats.t @@ -1,54 +1,33 @@ use Mojo::Base -strict; +use FindBin qw/ $Bin /; + use Test::More; use Mojo::JSON; use Test::Pear::LocalLoop; use DateTime; -my $framework = Test::Pear::LocalLoop->new; +my $framework = Test::Pear::LocalLoop->new( + etc_dir => "$Bin/../etc", +); +$framework->install_fixtures('users'); + my $t = $framework->framework; my $schema = $t->app->schema; my $dtf = $schema->storage->datetime_parser; -my $user = { - token => 'a', - full_name => 'Test User', - display_name => 'Test User', - email => 'test@example.com', - postcode => 'LA1 1AA', - password => 'abc123', - year_of_birth => 2006, -}; - -my $org = { - token => 'b', - email => 'test2@example.com', - name => 'Test Org', - street_name => 'Test Street', - town => 'Lancaster', - postcode => 'LA1 1AA', - password => 'abc123', - sector => 'A', -}; - -$schema->resultset('AccountToken')->create({ name => $user->{token} }); -$schema->resultset('AccountToken')->create({ name => $org->{token} }); - -$framework->register_customer($user); -$framework->register_organisation($org); - -my $org_result = $schema->resultset('Organisation')->find({ name => $org->{name} }); -my $user_result = $schema->resultset('User')->find({ email => $user->{email} }); +my $org_result = $schema->resultset('Organisation')->find({ name => 'Test Org' })->entity; +my $user_result = $schema->resultset('User')->find({ email => 'test1@example.com' })->entity; my $session_key = $framework->login({ - email => $user->{email}, - password => $user->{password}, + email => 'test1@example.com', + password => 'abc123', }); $t->app->schema->resultset('Leaderboard')->create_new( 'monthly_total', DateTime->now->truncate(to => 'month' )->subtract( months => 1) ); $t->post_ok('/api/stats' => json => { session_key => $session_key } ) - ->status_is(200) + ->status_is(200)->or($framework->dump_error) ->json_is('/success', Mojo::JSON->true) ->json_is('/today_sum', 0) ->json_is('/today_count', 0) @@ -62,7 +41,7 @@ $t->post_ok('/api/stats' => json => { session_key => $session_key } ) ->json_is('/global_count', 0); for ( 1 .. 10 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_, proof_image => 'a', @@ -70,7 +49,7 @@ for ( 1 .. 10 ) { } for ( 11 .. 20 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_, proof_image => 'a', @@ -79,7 +58,7 @@ for ( 11 .. 20 ) { } for ( 21 .. 30 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_, proof_image => 'a', @@ -88,7 +67,7 @@ for ( 21 .. 30 ) { } for ( 31 .. 40 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_, proof_image => 'a', @@ -97,7 +76,7 @@ for ( 31 .. 40 ) { } for ( 41 .. 50 ) { - $org_result->user->create_related( 'transactions', { + $org_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_, proof_image => 'a', @@ -105,7 +84,7 @@ for ( 41 .. 50 ) { }); } -is $user_result->transactions->search({ +is $user_result->purchases->search({ purchase_time => { -between => [ $dtf->format_datetime(DateTime->today()), @@ -113,7 +92,7 @@ is $user_result->transactions->search({ ], }, })->get_column('value')->sum, 55, 'Got correct sum'; -is $user_result->transactions->today_rs->get_column('value')->sum, 55, 'Got correct sum through rs'; +is $user_result->purchases->today_rs->get_column('value')->sum, 55, 'Got correct sum through rs'; $t->post_ok('/api/stats' => json => { session_key => $session_key } ) ->status_is(200) From aa6cc63506cd80a2ac0ff5478d955c68c680a8e5 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 18:25:27 +0100 Subject: [PATCH 28/83] Refactor leaderboard creation functions for entity upgrade --- .../LocalLoop/Schema/Result/Leaderboard.pm | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/Pear/LocalLoop/Schema/Result/Leaderboard.pm b/lib/Pear/LocalLoop/Schema/Result/Leaderboard.pm index 480aaa4..7c122d7 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Leaderboard.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Leaderboard.pm @@ -67,8 +67,8 @@ sub create_new { sub _get_customer_rs { my $self = shift; - return $self->result_source->schema->resultset('User')->search({ - organisation_id => undef, + return $self->result_source->schema->resultset('Entity')->search({ + type => 'customer', }); } @@ -93,7 +93,7 @@ sub _set_position_and_trend { my $previous_value; if ( defined $previous_board ) { - $previous_value = $previous_board->find({ user_id => $lb_val->{user_id} }); + $previous_value = $previous_board->find({ entity_id => $lb_val->{entity_id} }); } my $trend; @@ -122,12 +122,12 @@ sub _create_total_set { my @leaderboard; while ( my $user_result = $user_rs->next ) { - my $transaction_rs = $user_result->transactions->search_between( $start, $end ); + my $transaction_rs = $user_result->purchases->search_between( $start, $end ); my $transaction_sum = $transaction_rs->get_column('value')->sum; push @leaderboard, { - user_id => $user_result->id, + entity_id => $user_result->id, value => $transaction_sum || 0, }; } @@ -153,12 +153,12 @@ sub _create_count_set { my @leaderboard; while ( my $user_result = $user_rs->next ) { - my $transaction_rs = $user_result->transactions->search_between( $start, $end ); + my $transaction_rs = $user_result->purchases->search_between( $start, $end ); my $transaction_count = $transaction_rs->count; push @leaderboard, { - user_id => $user_result->id, + entity_id => $user_result->id, value => $transaction_count || 0, }; } @@ -184,12 +184,12 @@ sub _create_total_all_time { my @leaderboard; while ( my $user_result = $user_rs->next ) { - my $transaction_rs = $user_result->transactions->search_before( $end ); + my $transaction_rs = $user_result->purchases->search_before( $end ); my $transaction_sum = $transaction_rs->get_column('value')->sum; push @leaderboard, { - user_id => $user_result->id, + entity_id => $user_result->id, value => $transaction_sum || 0, }; } @@ -215,12 +215,12 @@ sub _create_count_all_time { my @leaderboard; while ( my $user_result = $user_rs->next ) { - my $transaction_rs = $user_result->transactions->search_before( $end ); + my $transaction_rs = $user_result->purchases->search_before( $end ); my $transaction_count = $transaction_rs->count; push @leaderboard, { - user_id => $user_result->id, + entity_id => $user_result->id, value => $transaction_count || 0, }; } From 66cc99b975e3d48b6018b75405122f31184b2a1a Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 18:26:19 +0100 Subject: [PATCH 29/83] Fixed schema leaderboard test for entity changes --- t/schema/leaderboard.t | 88 +++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/t/schema/leaderboard.t b/t/schema/leaderboard.t index 5915760..5d2cbcb 100644 --- a/t/schema/leaderboard.t +++ b/t/schema/leaderboard.t @@ -77,9 +77,9 @@ my $now = DateTime->today(); for my $user ( $user1, $user2, $user3, $user4 ) { $tweak ++; - my $user_result = $schema->resultset('User')->find({ email => $user->{email} }); + my $user_result = $schema->resultset('User')->find({ email => $user->{email} })->entity; for ( 1 .. 10 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_ + $tweak, proof_image => 'a', @@ -87,7 +87,7 @@ for my $user ( $user1, $user2, $user3, $user4 ) { } for ( 11 .. 20 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_ + $tweak, proof_image => 'a', @@ -96,7 +96,7 @@ for my $user ( $user1, $user2, $user3, $user4 ) { } for ( 21 .. 30 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_ + $tweak, proof_image => 'a', @@ -105,7 +105,7 @@ for my $user ( $user1, $user2, $user3, $user4 ) { } for ( 31 .. 40 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_ + $tweak, proof_image => 'a', @@ -113,7 +113,7 @@ for my $user ( $user1, $user2, $user3, $user4 ) { }); } - is $user_result->transactions->count, 40, 'correct count for user' . $tweak; + is $user_result->purchases->count, 40, 'correct count for user' . $tweak; } sub test_leaderboard { @@ -130,7 +130,7 @@ sub test_leaderboard { {}, { order_by => { -desc => 'value' }, - columns => [ qw/ user_id value / ], + columns => [ qw/ entity_id value / ], }, ); $today_values->result_class( 'DBIx::Class::ResultClass::HashRefInflator' ); @@ -144,10 +144,10 @@ test_leaderboard( 'daily_total', $now, [ - { user_id => 4, value => 95 }, - { user_id => 3, value => 85 }, - { user_id => 2, value => 75 }, - { user_id => 1, value => 65 }, + { entity_id => 4, value => 95 }, + { entity_id => 3, value => 85 }, + { entity_id => 2, value => 75 }, + { entity_id => 1, value => 65 }, ] ); @@ -156,10 +156,10 @@ test_leaderboard( 'daily_count', $now, [ - { user_id => 1, value => 10 }, - { user_id => 2, value => 10 }, - { user_id => 3, value => 10 }, - { user_id => 4, value => 10 }, + { entity_id => 1, value => 10 }, + { entity_id => 2, value => 10 }, + { entity_id => 3, value => 10 }, + { entity_id => 4, value => 10 }, ] ); @@ -168,10 +168,10 @@ test_leaderboard( 'weekly_total', $now->clone->subtract( days => 7 ), [ - { user_id => 4, value => 195 }, - { user_id => 3, value => 185 }, - { user_id => 2, value => 175 }, - { user_id => 1, value => 165 }, + { entity_id => 4, value => 195 }, + { entity_id => 3, value => 185 }, + { entity_id => 2, value => 175 }, + { entity_id => 1, value => 165 }, ] ); @@ -180,10 +180,10 @@ test_leaderboard( 'weekly_count', $now->clone->subtract( days => 7 ), [ - { user_id => 1, value => 10 }, - { user_id => 2, value => 10 }, - { user_id => 3, value => 10 }, - { user_id => 4, value => 10 }, + { entity_id => 1, value => 10 }, + { entity_id => 2, value => 10 }, + { entity_id => 3, value => 10 }, + { entity_id => 4, value => 10 }, ] ); @@ -192,10 +192,10 @@ test_leaderboard( 'monthly_total', $now->clone->subtract( months => 1 ), [ - { user_id => 4, value => 490 }, - { user_id => 3, value => 470 }, - { user_id => 2, value => 450 }, - { user_id => 1, value => 430 }, + { entity_id => 4, value => 490 }, + { entity_id => 3, value => 470 }, + { entity_id => 2, value => 450 }, + { entity_id => 1, value => 430 }, ] ); @@ -204,10 +204,10 @@ test_leaderboard( 'monthly_count', $now->clone->subtract( months => 1 ), [ - { user_id => 1, value => 20 }, - { user_id => 2, value => 20 }, - { user_id => 3, value => 20 }, - { user_id => 4, value => 20 }, + { entity_id => 1, value => 20 }, + { entity_id => 2, value => 20 }, + { entity_id => 3, value => 20 }, + { entity_id => 4, value => 20 }, ] ); @@ -216,10 +216,10 @@ test_leaderboard( 'all_time_total', $now, [ - { user_id => 4, value => 885 }, - { user_id => 3, value => 855 }, - { user_id => 2, value => 825 }, - { user_id => 1, value => 795 }, + { entity_id => 4, value => 885 }, + { entity_id => 3, value => 855 }, + { entity_id => 2, value => 825 }, + { entity_id => 1, value => 795 }, ] ); @@ -228,10 +228,10 @@ test_leaderboard( 'all_time_count', $now, [ - { user_id => 1, value => 30 }, - { user_id => 2, value => 30 }, - { user_id => 3, value => 30 }, - { user_id => 4, value => 30 }, + { entity_id => 1, value => 30 }, + { entity_id => 2, value => 30 }, + { entity_id => 3, value => 30 }, + { entity_id => 4, value => 30 }, ] ); @@ -249,16 +249,16 @@ subtest 'get_latest' => sub { {}, { order_by => { -desc => 'value' }, - columns => [ qw/ user_id value / ], + columns => [ qw/ entity_id value / ], }, ); $today_values->result_class( 'DBIx::Class::ResultClass::HashRefInflator' ); my $expected = [ - { user_id => 4, value => 95 }, - { user_id => 3, value => 85 }, - { user_id => 2, value => 75 }, - { user_id => 1, value => 65 }, + { entity_id => 4, value => 95 }, + { entity_id => 3, value => 85 }, + { entity_id => 2, value => 75 }, + { entity_id => 1, value => 65 }, ]; is_deeply [ $today_values->all ], $expected, 'array as expected'; From 44c29ad7a27ebc90800a41799424b4f59560f43f Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 18:27:44 +0100 Subject: [PATCH 30/83] Fixed leaderboard_trend test for entity upgrade --- t/schema/leaderboard_trend.t | 42 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/t/schema/leaderboard_trend.t b/t/schema/leaderboard_trend.t index 33e7ea9..030d238 100644 --- a/t/schema/leaderboard_trend.t +++ b/t/schema/leaderboard_trend.t @@ -76,15 +76,15 @@ my $tweak = 0; my $now = DateTime->today(); { - my $user_result = $schema->resultset('User')->find({ email => $user1->{email} }); + my $user_result = $schema->resultset('User')->find({ email => $user1->{email} })->entity; - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 1, proof_image => 'a', }); - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 9, proof_image => 'a', @@ -93,15 +93,15 @@ my $now = DateTime->today(); } { - my $user_result = $schema->resultset('User')->find({ email => $user2->{email} }); + my $user_result = $schema->resultset('User')->find({ email => $user2->{email} })->entity; - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 3, proof_image => 'a', }); - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 1, proof_image => 'a', @@ -110,15 +110,15 @@ my $now = DateTime->today(); } { - my $user_result = $schema->resultset('User')->find({ email => $user3->{email} }); + my $user_result = $schema->resultset('User')->find({ email => $user3->{email} })->entity; - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 5, proof_image => 'a', }); - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 5, proof_image => 'a', @@ -127,15 +127,15 @@ my $now = DateTime->today(); } { - my $user_result = $schema->resultset('User')->find({ email => $user4->{email} }); + my $user_result = $schema->resultset('User')->find({ email => $user4->{email} })->entity; - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 9, proof_image => 'a', }); - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 3, proof_image => 'a', @@ -162,7 +162,7 @@ sub test_leaderboard { {}, { order_by => { -desc => 'value' }, - columns => [ qw/ user_id value trend position / ], + columns => [ qw/ entity_id value trend position / ], }, ); $today_values->result_class( 'DBIx::Class::ResultClass::HashRefInflator' ); @@ -178,10 +178,10 @@ test_leaderboard( 'daily_total', $now, [ - { user_id => 4, value => 9, trend => -1, position => 1}, - { user_id => 3, value => 5, trend => 0, position => 2}, - { user_id => 2, value => 3, trend => -1, position => 3}, - { user_id => 1, value => 1, trend => 1, position => 4}, + { entity_id => 4, value => 9, trend => -1, position => 1}, + { entity_id => 3, value => 5, trend => 0, position => 2}, + { entity_id => 2, value => 3, trend => -1, position => 3}, + { entity_id => 1, value => 1, trend => 1, position => 4}, ] ); @@ -190,10 +190,10 @@ test_leaderboard( 'daily_count', $now, [ - { user_id => 1, value => 1, trend => 0, position => 1 }, - { user_id => 2, value => 1, trend => 0, position => 2 }, - { user_id => 3, value => 1, trend => 0, position => 3 }, - { user_id => 4, value => 1, trend => 0, position => 4 }, + { entity_id => 1, value => 1, trend => 0, position => 1 }, + { entity_id => 2, value => 1, trend => 0, position => 2 }, + { entity_id => 3, value => 1, trend => 0, position => 3 }, + { entity_id => 4, value => 1, trend => 0, position => 4 }, ] ); From 96c8fa16c8230c99fd9f16caf2ae3d258df970e4 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 18:28:36 +0100 Subject: [PATCH 31/83] fixed resultset_leaderboard schema test for entity changes --- t/schema/resultset_leaderboard.t | 90 ++++++++++++++++---------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/t/schema/resultset_leaderboard.t b/t/schema/resultset_leaderboard.t index 92b1120..4a27bbd 100644 --- a/t/schema/resultset_leaderboard.t +++ b/t/schema/resultset_leaderboard.t @@ -69,7 +69,7 @@ $framework->register_customer($_) $framework->register_organisation($org); -my $org_result = $schema->resultset('Organisation')->find({ name => $org->{name} }); +my $org_result = $schema->resultset('Organisation')->find({ name => $org->{name} })->entity; my $tweak = 0; @@ -77,9 +77,9 @@ my $now = DateTime->today(); for my $user ( $user1, $user2, $user3, $user4 ) { $tweak ++; - my $user_result = $schema->resultset('User')->find({ email => $user->{email} }); + my $user_result = $schema->resultset('User')->find({ email => $user->{email} })->entity; for ( 1 .. 10 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_ + $tweak, proof_image => 'a', @@ -87,7 +87,7 @@ for my $user ( $user1, $user2, $user3, $user4 ) { } for ( 11 .. 20 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_ + $tweak, proof_image => 'a', @@ -96,7 +96,7 @@ for my $user ( $user1, $user2, $user3, $user4 ) { } for ( 21 .. 30 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_ + $tweak, proof_image => 'a', @@ -105,7 +105,7 @@ for my $user ( $user1, $user2, $user3, $user4 ) { } for ( 31 .. 40 ) { - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => $_ + $tweak, proof_image => 'a', @@ -113,7 +113,7 @@ for my $user ( $user1, $user2, $user3, $user4 ) { }); } - is $user_result->transactions->count, 40, 'correct count for user' . $tweak; + is $user_result->purchases->count, 40, 'correct count for user' . $tweak; } sub test_leaderboard { @@ -132,7 +132,7 @@ sub test_leaderboard { {}, { order_by => { -desc => 'value' }, - columns => [ qw/ user_id value position / ], + columns => [ qw/ entity_id value position / ], }, ); $today_values->result_class( 'DBIx::Class::ResultClass::HashRefInflator' ); @@ -146,10 +146,10 @@ test_leaderboard( 'daily_total', $now, [ - { user_id => 4, value => 95, position => 1 }, - { user_id => 3, value => 85, position => 2 }, - { user_id => 2, value => 75, position => 3 }, - { user_id => 1, value => 65, position => 4 }, + { entity_id => 4, value => 95, position => 1 }, + { entity_id => 3, value => 85, position => 2 }, + { entity_id => 2, value => 75, position => 3 }, + { entity_id => 1, value => 65, position => 4 }, ] ); @@ -158,10 +158,10 @@ test_leaderboard( 'daily_count', $now, [ - { user_id => 1, value => 10, position => 1 }, - { user_id => 2, value => 10, position => 2 }, - { user_id => 3, value => 10, position => 3 }, - { user_id => 4, value => 10, position => 4 }, + { entity_id => 1, value => 10, position => 1 }, + { entity_id => 2, value => 10, position => 2 }, + { entity_id => 3, value => 10, position => 3 }, + { entity_id => 4, value => 10, position => 4 }, ] ); @@ -170,10 +170,10 @@ test_leaderboard( 'weekly_total', $now->clone->subtract( days => 7 ), [ - { user_id => 4, value => 195, position => 1 }, - { user_id => 3, value => 185, position => 2 }, - { user_id => 2, value => 175, position => 3 }, - { user_id => 1, value => 165, position => 4 }, + { entity_id => 4, value => 195, position => 1 }, + { entity_id => 3, value => 185, position => 2 }, + { entity_id => 2, value => 175, position => 3 }, + { entity_id => 1, value => 165, position => 4 }, ] ); @@ -182,10 +182,10 @@ test_leaderboard( 'weekly_count', $now->clone->subtract( days => 7 ), [ - { user_id => 1, value => 10, position => 1 }, - { user_id => 2, value => 10, position => 2 }, - { user_id => 3, value => 10, position => 3 }, - { user_id => 4, value => 10, position => 4 }, + { entity_id => 1, value => 10, position => 1 }, + { entity_id => 2, value => 10, position => 2 }, + { entity_id => 3, value => 10, position => 3 }, + { entity_id => 4, value => 10, position => 4 }, ] ); @@ -194,10 +194,10 @@ test_leaderboard( 'monthly_total', $now->clone->subtract( months => 1 ), [ - { user_id => 4, value => 490, position => 1 }, - { user_id => 3, value => 470, position => 2 }, - { user_id => 2, value => 450, position => 3 }, - { user_id => 1, value => 430, position => 4 }, + { entity_id => 4, value => 490, position => 1 }, + { entity_id => 3, value => 470, position => 2 }, + { entity_id => 2, value => 450, position => 3 }, + { entity_id => 1, value => 430, position => 4 }, ] ); @@ -206,10 +206,10 @@ test_leaderboard( 'monthly_count', $now->clone->subtract( months => 1 ), [ - { user_id => 1, value => 20, position => 1 }, - { user_id => 2, value => 20, position => 2 }, - { user_id => 3, value => 20, position => 3 }, - { user_id => 4, value => 20, position => 4 }, + { entity_id => 1, value => 20, position => 1 }, + { entity_id => 2, value => 20, position => 2 }, + { entity_id => 3, value => 20, position => 3 }, + { entity_id => 4, value => 20, position => 4 }, ] ); @@ -218,10 +218,10 @@ test_leaderboard( 'all_time_total', $now, [ - { user_id => 4, value => 885, position => 1 }, - { user_id => 3, value => 855, position => 2 }, - { user_id => 2, value => 825, position => 3 }, - { user_id => 1, value => 795, position => 4 }, + { entity_id => 4, value => 885, position => 1 }, + { entity_id => 3, value => 855, position => 2 }, + { entity_id => 2, value => 825, position => 3 }, + { entity_id => 1, value => 795, position => 4 }, ] ); @@ -230,10 +230,10 @@ test_leaderboard( 'all_time_count', $now, [ - { user_id => 1, value => 30, position => 1 }, - { user_id => 2, value => 30, position => 2 }, - { user_id => 3, value => 30, position => 3 }, - { user_id => 4, value => 30, position => 4 }, + { entity_id => 1, value => 30, position => 1 }, + { entity_id => 2, value => 30, position => 2 }, + { entity_id => 3, value => 30, position => 3 }, + { entity_id => 4, value => 30, position => 4 }, ] ); @@ -251,16 +251,16 @@ subtest 'get_latest' => sub { {}, { order_by => { -desc => 'value' }, - columns => [ qw/ user_id value position / ], + columns => [ qw/ entity_id value position / ], }, ); $today_values->result_class( 'DBIx::Class::ResultClass::HashRefInflator' ); my $expected = [ - { user_id => 4, value => 95, position => 1 }, - { user_id => 3, value => 85, position => 2 }, - { user_id => 2, value => 75, position => 3 }, - { user_id => 1, value => 65, position => 4 }, + { entity_id => 4, value => 95, position => 1 }, + { entity_id => 3, value => 85, position => 2 }, + { entity_id => 2, value => 75, position => 3 }, + { entity_id => 1, value => 65, position => 4 }, ]; is_deeply [ $today_values->all ], $expected, 'array as expected'; From e91863a3d796c7fab5bbb650a38a71661e181378 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 18:31:42 +0100 Subject: [PATCH 32/83] Fixed stats endpoint for leaderboards on entity upgrade --- lib/Pear/LocalLoop/Controller/Api/Stats.pm | 4 ++-- t/api/stats_leaderboards.t | 26 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/Stats.pm b/lib/Pear/LocalLoop/Controller/Api/Stats.pm index bf15384..3ee2617 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Stats.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Stats.pm @@ -84,14 +84,14 @@ sub post_leaderboards { /, { display_name => 'customer.display_name' }, ], - join => { user => 'customer' }, + join => { entity => 'customer' }, }, ); $today_values->result_class( 'DBIx::Class::ResultClass::HashRefInflator' ); my @leaderboard_array = $today_values->all; - my $current_user_position = $today_values->find({ user_id => $c->stash->{api_user}->id }); + my $current_user_position = $today_values->find({ entity_id => $c->stash->{api_user}->entity->id }); return $c->render( json => { success => Mojo::JSON->true, diff --git a/t/api/stats_leaderboards.t b/t/api/stats_leaderboards.t index 11003c2..84eb331 100644 --- a/t/api/stats_leaderboards.t +++ b/t/api/stats_leaderboards.t @@ -69,22 +69,22 @@ $framework->register_customer($_) $framework->register_organisation($org); -my $org_result = $schema->resultset('Organisation')->find({ name => $org->{name} }); +my $org_result = $schema->resultset('Organisation')->find({ name => $org->{name} })->entity; my $tweak = 0; my $now = DateTime->today(); { - my $user_result = $schema->resultset('User')->find({ email => $user1->{email} }); + my $user_result = $schema->resultset('User')->find({ email => $user1->{email} })->entity; - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 1, proof_image => 'a', }); - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 9, proof_image => 'a', @@ -93,15 +93,15 @@ my $now = DateTime->today(); } { - my $user_result = $schema->resultset('User')->find({ email => $user2->{email} }); + my $user_result = $schema->resultset('User')->find({ email => $user2->{email} })->entity; - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 3, proof_image => 'a', }); - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 1, proof_image => 'a', @@ -110,15 +110,15 @@ my $now = DateTime->today(); } { - my $user_result = $schema->resultset('User')->find({ email => $user3->{email} }); + my $user_result = $schema->resultset('User')->find({ email => $user3->{email} })->entity; - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 5, proof_image => 'a', }); - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 5, proof_image => 'a', @@ -127,15 +127,15 @@ my $now = DateTime->today(); } { - my $user_result = $schema->resultset('User')->find({ email => $user4->{email} }); + my $user_result = $schema->resultset('User')->find({ email => $user4->{email} })->entity; - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 9, proof_image => 'a', }); - $user_result->create_related( 'transactions', { + $user_result->create_related( 'purchases', { seller_id => $org_result->id, value => 3, proof_image => 'a', From dd6e21eb50481d0be627d3dd558caf8dc9ca554c Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 19:03:49 +0100 Subject: [PATCH 33/83] Fix upload tests for entity upgrade --- lib/Pear/LocalLoop/Controller/Api/Upload.pm | 4 +- .../LocalLoop/Schema/Result/Transaction.pm | 2 +- t/api/upload.t | 39 ++++++++++--------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/Upload.pm b/lib/Pear/LocalLoop/Controller/Api/Upload.pm index 1f8c2e5..8ebe48b 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Upload.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Upload.pm @@ -112,7 +112,7 @@ sub post_upload { if ( $type == 1 ) { # Validated Organisation - my $valid_org_rs = $c->schema->resultset('Organisation'); + my $valid_org_rs = $c->schema->resultset('Organisation')->search({ pending => 0 }); $validation->required('organisation_id')->number->in_resultset( 'id', $valid_org_rs ); return $c->api_validation_error if $validation->has_error; @@ -121,7 +121,7 @@ sub post_upload { } elsif ( $type == 2 ) { # Unvalidated Organisation - my $valid_org_rs = $c->schema->resultset('PendingOrganisation')->search({ submitted_by_id => $user->id }); + my $valid_org_rs = $c->schema->resultset('Organisation')->search({ submitted_by_id => $user->id, pending => 1 }); $validation->required('organisation_id')->number->in_resultset( 'id', $valid_org_rs ); return $c->api_validation_error if $validation->has_error; diff --git a/lib/Pear/LocalLoop/Schema/Result/Transaction.pm b/lib/Pear/LocalLoop/Schema/Result/Transaction.pm index 7b87a16..13654be 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Transaction.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Transaction.pm @@ -35,7 +35,7 @@ __PACKAGE__->add_columns( }, "proof_image" => { data_type => "text", - is_nullable => 0, + is_nullable => 1, }, "submitted_at" => { data_type => "datetime", diff --git a/t/api/upload.t b/t/api/upload.t index ca925cf..18f5b52 100644 --- a/t/api/upload.t +++ b/t/api/upload.t @@ -20,24 +20,25 @@ $schema->resultset('AccountToken')->populate([ my $test_purchase_time = "2017-08-14T11:29:07.965+01:00"; #Add one company that we've apparently authenticated but does not have an account. -my $org_id_shinra = 1; my $org_rs = $schema->resultset('Organisation'); is $org_rs->count, 0, "No organisations"; -$org_rs->create({ - id => $org_id_shinra, +my $shinra_entity = $schema->resultset('Entity')->create_org({ name => 'Shinra Electric Power Company', street_name => 'Sector 0, Midgar, Eastern Continent', town => 'Gaia', postcode => 'E1 M00', + submitted_by_id => 1, }); is $org_rs->count, 1, "1 testing organisation"; +my $org_id_shinra = $shinra_entity->organisation->id; + #Valid customer, this also tests that redirects are disabled for register. print "test 1 - Create customer user account (Rufus)\n"; -my $emailRufus = 'rufus@shinra.energy'; -my $passwordRufus = 'MakoGold'; +my $emailRufus = 'test1@example.com'; +my $passwordRufus = 'abc123'; my $testJson = { 'usertype' => 'customer', 'token' => shift(@account_tokens), @@ -203,6 +204,7 @@ $json = { $upload = {json => Mojo::JSON::encode_json($json)}; $t->post_ok('/api/upload' => form => $upload ) ->status_is(200) +->or($framework->dump_error) ->json_is('/success', Mojo::JSON->true) ->json_like('/message', qr/Upload Successful/); is $schema->resultset('Transaction')->count, 1, "1 transaction"; @@ -270,8 +272,8 @@ $t->post_ok('/api/upload' => form => $upload ) ->content_like(qr/organisation_name is missing/i); print "test 17 - add valid transaction (type 3: new organisation)\n"; -is $schema->resultset('PendingOrganisation')->count, 0, "No pending organisations"; -is $schema->resultset('PendingTransaction')->count, 0, "No pending transactions"; +is $schema->resultset('Organisation')->search({ pending => 1 })->count, 0, "No pending organisations"; +is $schema->resultset('Organisation')->search({ pending => 1 })->entity->sales->count, 0, "No pending transactions"; $json = { transaction_value => 10, @@ -288,8 +290,8 @@ $t->post_ok('/api/upload' => form => $upload ) ->status_is(200) ->json_is('/success', Mojo::JSON->true) ->json_like('/message', qr/Upload Successful/); -is $schema->resultset('PendingOrganisation')->count, 1, "1 pending organisations"; -is $schema->resultset('PendingTransaction')->count, 1, "1 pending transactions"; +is $schema->resultset('Organisation')->search({ pending => 1 })->count, 1, "1 pending organisations"; +is $schema->resultset('Organisation')->search({ pending => 1 })->entity->sales->count, 1, "1 pending transactions"; # Add type 2 (unverified organisation) checking. @@ -303,6 +305,7 @@ $json = { $upload = {json => Mojo::JSON::encode_json($json), file => {file => './t/test.jpg'}}; $t->post_ok('/api/upload' => form => $upload ) ->status_is(400) + ->or($framework->dump_error) ->json_is('/success', Mojo::JSON->false) ->content_like(qr/organisation_id is missing/i); @@ -335,10 +338,10 @@ $t->post_ok('/api/upload' => form => $upload ) ->content_like(qr/organisation_id does not exist in the database/i); print "test 21 - purchase_time is missing\n"; -is $schema->resultset('PendingTransaction')->count, 1, "1 pending transactions"; +is $schema->resultset('Organisation')->search({ pending => 1 })->entity->sales->count, 1, "1 pending transactions"; $json = { transaction_value => 10, - transaction_type => 2, + transaction_type => 1, organisation_id => $org_id_shinra, session_key => $session_key, }; @@ -369,9 +372,9 @@ $t->post_ok('/api/login' => json => $testJson) $session_key = $t->tx->res->json('/session_key'); print "test 24 - add valid transaction but for with account (type 2: existing organisation)\n"; -my $org_result = $schema->resultset('PendingOrganisation')->find({ name => '7th Heaven' }); +my $org_result = $schema->resultset('Organisation')->find({ name => '7th Heaven' }); my $unvalidatedOrganisationId = $org_result->id; -is $schema->resultset('PendingTransaction')->count, 2, "2 pending transactions"; +is $schema->resultset('Organisation')->search({ pending => 1 })->entity->sales->count, 1, "1 pending transactions"; $json = { transaction_value => 10, transaction_type => 2, @@ -384,7 +387,7 @@ $t->post_ok('/api/upload' => form => $upload ) ->status_is(400) ->json_is('/success', Mojo::JSON->false) ->content_like(qr/organisation_id does not exist in the database/i); -is $schema->resultset('PendingTransaction')->count, 2, "2 pending transactions"; +is $schema->resultset('Organisation')->search({ pending => 1 })->entity->sales->count, 1, "1 pending transactions"; print "test 25 - Logout Hojo\n"; $t->post_ok('/api/logout', json => { session_key => $session_key } ) @@ -408,7 +411,7 @@ $t->post_ok('/api/login' => json => $testJson) $session_key = $t->tx->res->json('/session_key'); print "test 27 - add valid transaction (type 2: existing organisation)\n"; -is $schema->resultset('PendingTransaction')->count, 2, "2 pending transactions"; +is $schema->resultset('Organisation')->search({ pending => 1 })->entity->sales->count, 1, "1 pending transactions"; $json = { transaction_value => 10, transaction_type => 2, @@ -421,7 +424,7 @@ $t->post_ok('/api/upload' => form => $upload ) ->status_is(200) ->json_is('/success', Mojo::JSON->true) ->json_like('/message', qr/Upload Successful/); -is $schema->resultset('PendingTransaction')->count, 3, "3 pending transactions"; +is $schema->resultset('Organisation')->search({ pending => 1 })->entity->sales->count, 2, "2 pending transactions"; print "test 28 - Logout Rufus\n"; @@ -446,7 +449,7 @@ $t->post_ok('/api/login' => json => $testJson) $session_key = $t->tx->res->json('/session_key'); print "test 30 - organisation buy from another organisation\n"; -is $schema->resultset('Transaction')->count, 2, "2 transaction"; +is $schema->resultset('Transaction')->count, 5, "5 transaction"; $json = { transaction_value => 100000, transaction_type => 1, @@ -459,6 +462,6 @@ $t->post_ok('/api/upload' => form => $upload ) ->status_is(200) ->json_is('/success', Mojo::JSON->true) ->json_like('/message', qr/Upload Successful/); -is $schema->resultset('Transaction')->count, 3, "3 transaction"; +is $schema->resultset('Transaction')->count, 6, "6 transaction"; done_testing(); From adfcd8880f1c07d9e129110a634d012ce082cfcc Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 19:08:22 +0100 Subject: [PATCH 34/83] Fix User endpoints for entity upgrade --- lib/Pear/LocalLoop/Controller/Api/User.pm | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/User.pm b/lib/Pear/LocalLoop/Controller/Api/User.pm index f9b46cb..555c902 100644 --- a/lib/Pear/LocalLoop/Controller/Api/User.pm +++ b/lib/Pear/LocalLoop/Controller/Api/User.pm @@ -65,12 +65,12 @@ sub post_account { my $postcode; #Needs elsif added for trader page for this similar relevant entry - if ( defined $user_result->customer_id ) { - $full_name = $user_result->customer->full_name; - $display_name = $user_result->customer->display_name; - $postcode = $user_result->customer->postcode; - } elsif ( defined $user_result->organisation_id ) { - $display_name = $user_result->organisation->name; + if ( $user_result->type eq 'customer' ) { + $full_name = $user_result->entity->customer->full_name; + $display_name = $user_result->entity->customer->display_name; + $postcode = $user_result->entity->customer->postcode; + } elsif ( $user_result->type eq 'organisation' ) { + $display_name = $user_result->entity->organisation->name; } else { return; } @@ -121,10 +121,10 @@ sub post_account_update { $validation->required('postcode')->postcode; $validation->optional('new_password'); - if ( defined $user->customer_id ) { + if ( $user->type eq 'customer' ) { $validation->required('display_name'); $validation->required('full_name'); - } elsif ( defined $user->organisation_id ) { + } elsif ( $user->type eq 'organisation' ) { $validation->required('name'); $validation->required('street_name'); $validation->required('town'); @@ -133,10 +133,10 @@ sub post_account_update { return $c->api_validation_error if $validation->has_error; - if ( defined $user->customer_id ){ + if ( $user->type eq 'customer' ){ $c->schema->txn_do( sub { - $user->customer->update({ + $user->entity->customer->update({ full_name => $validation->param('full_name'), display_name => $validation->param('display_name'), postcode => $validation->param('postcode'), @@ -148,10 +148,10 @@ sub post_account_update { }); } - elsif ( defined $user->organisation_id ) { + elsif ( $user->type eq 'organisation' ) { $c->schema->txn_do( sub { - $user->organisation->update({ + $user->entity->organisation->update({ name => $validation->param('name'), street_name => $validation->param('street_name'), town => $validation->param('town'), From cca27b547531100f006ac2bd29b8b163cea4f5a3 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 31 Aug 2017 19:12:13 +0100 Subject: [PATCH 35/83] Fix org graphs test for entity upgrade --- lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm | 2 +- t/api/v1/organisation/graphs.t | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm index bb807ba..bc78d91 100644 --- a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm @@ -56,7 +56,7 @@ sub graph_customers_last_30_days { sub _customers_last_duration { my ( $c, $duration ) = @_; - my $org = $c->stash->{api_user}->organisation; + my $org = $c->stash->{api_user}->entity; my $data = { day => [], count => [] }; diff --git a/t/api/v1/organisation/graphs.t b/t/api/v1/organisation/graphs.t index d6885f3..c5ee6f9 100644 --- a/t/api/v1/organisation/graphs.t +++ b/t/api/v1/organisation/graphs.t @@ -77,9 +77,11 @@ sub create_random_transaction { my $buyer = shift; my $time = shift; + my $buyer_result = $schema->resultset('User')->find({ email => $buyer })->entity; + my $seller_result = $schema->resultset('Organisation')->find({ name => 'Test Org' })->entity; $schema->resultset('Transaction')->create({ - buyer => { email => $buyer }, - seller => { name => 'Test Org' }, + buyer => $buyer_result, + seller => $seller_result, value => ( int( rand( 10000 ) ) / 100 ), proof_image => 'a', purchase_time => $time, From 2594d826ef087af6f6016a42bdb67cd7cc07d73e Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 4 Sep 2017 14:03:39 +0100 Subject: [PATCH 36/83] Finished upgrade to schema for entity upgrade --- .../deploy/6/001-auto-__VERSION.sql | 18 + share/ddl/PostgreSQL/deploy/6/001-auto.sql | 210 ++++ share/ddl/PostgreSQL/upgrade/5-6/001-auto.sql | 85 ++ .../upgrade/5-6/003-remove-temps.sql | 13 + .../SQLite/deploy/6/001-auto-__VERSION.sql | 18 + share/ddl/SQLite/deploy/6/001-auto.sql | 145 +++ share/ddl/SQLite/upgrade/5-6/001-auto.sql | 90 ++ .../SQLite/upgrade/5-6/003-remove-temps.sql | 13 + .../_common/upgrade/5-6/002-entity-upgrade.pl | 111 ++ .../_source/deploy/6/001-auto-__VERSION.yml | 91 ++ share/ddl/_source/deploy/6/001-auto.yml | 1064 +++++++++++++++++ 11 files changed, 1858 insertions(+) create mode 100644 share/ddl/PostgreSQL/deploy/6/001-auto-__VERSION.sql create mode 100644 share/ddl/PostgreSQL/deploy/6/001-auto.sql create mode 100644 share/ddl/PostgreSQL/upgrade/5-6/001-auto.sql create mode 100644 share/ddl/PostgreSQL/upgrade/5-6/003-remove-temps.sql create mode 100644 share/ddl/SQLite/deploy/6/001-auto-__VERSION.sql create mode 100644 share/ddl/SQLite/deploy/6/001-auto.sql create mode 100644 share/ddl/SQLite/upgrade/5-6/001-auto.sql create mode 100644 share/ddl/SQLite/upgrade/5-6/003-remove-temps.sql create mode 100644 share/ddl/_common/upgrade/5-6/002-entity-upgrade.pl create mode 100644 share/ddl/_source/deploy/6/001-auto-__VERSION.yml create mode 100644 share/ddl/_source/deploy/6/001-auto.yml diff --git a/share/ddl/PostgreSQL/deploy/6/001-auto-__VERSION.sql b/share/ddl/PostgreSQL/deploy/6/001-auto-__VERSION.sql new file mode 100644 index 0000000..ab9b4e1 --- /dev/null +++ b/share/ddl/PostgreSQL/deploy/6/001-auto-__VERSION.sql @@ -0,0 +1,18 @@ +-- +-- Created by SQL::Translator::Producer::PostgreSQL +-- Created on Fri Sep 1 15:14:28 2017 +-- +; +-- +-- Table: dbix_class_deploymenthandler_versions +-- +CREATE TABLE "dbix_class_deploymenthandler_versions" ( + "id" serial NOT NULL, + "version" character varying(50) NOT NULL, + "ddl" text, + "upgrade_sql" text, + PRIMARY KEY ("id"), + CONSTRAINT "dbix_class_deploymenthandler_versions_version" UNIQUE ("version") +); + +; diff --git a/share/ddl/PostgreSQL/deploy/6/001-auto.sql b/share/ddl/PostgreSQL/deploy/6/001-auto.sql new file mode 100644 index 0000000..0d97746 --- /dev/null +++ b/share/ddl/PostgreSQL/deploy/6/001-auto.sql @@ -0,0 +1,210 @@ +-- +-- Created by SQL::Translator::Producer::PostgreSQL +-- Created on Fri Sep 1 15:14:28 2017 +-- +; +-- +-- Table: account_tokens +-- +CREATE TABLE "account_tokens" ( + "id" serial NOT NULL, + "name" text NOT NULL, + "used" integer DEFAULT 0 NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "account_tokens_name" UNIQUE ("name") +); + +; +-- +-- Table: entities +-- +CREATE TABLE "entities" ( + "id" serial NOT NULL, + "type" character varying(255) NOT NULL, + PRIMARY KEY ("id") +); + +; +-- +-- Table: leaderboards +-- +CREATE TABLE "leaderboards" ( + "id" serial NOT NULL, + "name" character varying(255) NOT NULL, + "type" character varying(255) NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "leaderboards_type" UNIQUE ("type") +); + +; +-- +-- Table: customers +-- +CREATE TABLE "customers" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "display_name" character varying(255) NOT NULL, + "full_name" character varying(255) NOT NULL, + "year_of_birth" integer NOT NULL, + "postcode" character varying(16) NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "customers_idx_entity_id" on "customers" ("entity_id"); + +; +-- +-- Table: leaderboard_sets +-- +CREATE TABLE "leaderboard_sets" ( + "id" serial NOT NULL, + "leaderboard_id" integer NOT NULL, + "date" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "leaderboard_sets_idx_leaderboard_id" on "leaderboard_sets" ("leaderboard_id"); + +; +-- +-- Table: organisations +-- +CREATE TABLE "organisations" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "name" character varying(255) NOT NULL, + "street_name" text, + "town" character varying(255) NOT NULL, + "postcode" character varying(16), + "country" character varying(255), + "sector" character varying(1), + "pending" boolean DEFAULT 0 NOT NULL, + "submitted_by_id" integer, + PRIMARY KEY ("id") +); +CREATE INDEX "organisations_idx_entity_id" on "organisations" ("entity_id"); + +; +-- +-- Table: transactions +-- +CREATE TABLE "transactions" ( + "id" serial NOT NULL, + "buyer_id" integer NOT NULL, + "seller_id" integer NOT NULL, + "value" numeric(16,2) NOT NULL, + "proof_image" text, + "submitted_at" timestamp NOT NULL, + "purchase_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "transactions_idx_buyer_id" on "transactions" ("buyer_id"); +CREATE INDEX "transactions_idx_seller_id" on "transactions" ("seller_id"); + +; +-- +-- Table: users +-- +CREATE TABLE "users" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "email" text NOT NULL, + "join_date" timestamp NOT NULL, + "password" character varying(100) NOT NULL, + "is_admin" boolean DEFAULT 0 NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "users_email" UNIQUE ("email") +); +CREATE INDEX "users_idx_entity_id" on "users" ("entity_id"); + +; +-- +-- Table: feedback +-- +CREATE TABLE "feedback" ( + "id" serial NOT NULL, + "user_id" integer NOT NULL, + "submitted_at" timestamp NOT NULL, + "feedbacktext" text NOT NULL, + "app_name" character varying(255) NOT NULL, + "package_name" character varying(255) NOT NULL, + "version_code" character varying(255) NOT NULL, + "version_number" character varying(255) NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "feedback_idx_user_id" on "feedback" ("user_id"); + +; +-- +-- Table: session_tokens +-- +CREATE TABLE "session_tokens" ( + "id" serial NOT NULL, + "token" character varying(255) NOT NULL, + "user_id" integer NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "session_tokens_token" UNIQUE ("token") +); +CREATE INDEX "session_tokens_idx_user_id" on "session_tokens" ("user_id"); + +; +-- +-- Table: leaderboard_values +-- +CREATE TABLE "leaderboard_values" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "set_id" integer NOT NULL, + "position" integer NOT NULL, + "value" numeric(16,2) NOT NULL, + "trend" integer DEFAULT 0 NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "leaderboard_values_entity_id_set_id" UNIQUE ("entity_id", "set_id") +); +CREATE INDEX "leaderboard_values_idx_entity_id" on "leaderboard_values" ("entity_id"); +CREATE INDEX "leaderboard_values_idx_set_id" on "leaderboard_values" ("set_id"); + +; +-- +-- Foreign Key Definitions +-- + +; +ALTER TABLE "customers" ADD CONSTRAINT "customers_fk_entity_id" FOREIGN KEY ("entity_id") + REFERENCES "entities" ("id") ON DELETE CASCADE DEFERRABLE; + +; +ALTER TABLE "leaderboard_sets" ADD CONSTRAINT "leaderboard_sets_fk_leaderboard_id" FOREIGN KEY ("leaderboard_id") + REFERENCES "leaderboards" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "organisations" ADD CONSTRAINT "organisations_fk_entity_id" FOREIGN KEY ("entity_id") + REFERENCES "entities" ("id") ON DELETE CASCADE DEFERRABLE; + +; +ALTER TABLE "transactions" ADD CONSTRAINT "transactions_fk_buyer_id" FOREIGN KEY ("buyer_id") + REFERENCES "entities" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "transactions" ADD CONSTRAINT "transactions_fk_seller_id" FOREIGN KEY ("seller_id") + REFERENCES "entities" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "users" ADD CONSTRAINT "users_fk_entity_id" FOREIGN KEY ("entity_id") + REFERENCES "entities" ("id") ON DELETE CASCADE DEFERRABLE; + +; +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_fk_user_id" FOREIGN KEY ("user_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "session_tokens" ADD CONSTRAINT "session_tokens_fk_user_id" FOREIGN KEY ("user_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "leaderboard_values" ADD CONSTRAINT "leaderboard_values_fk_entity_id" FOREIGN KEY ("entity_id") + REFERENCES "entities" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "leaderboard_values" ADD CONSTRAINT "leaderboard_values_fk_set_id" FOREIGN KEY ("set_id") + REFERENCES "leaderboard_sets" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; diff --git a/share/ddl/PostgreSQL/upgrade/5-6/001-auto.sql b/share/ddl/PostgreSQL/upgrade/5-6/001-auto.sql new file mode 100644 index 0000000..c970017 --- /dev/null +++ b/share/ddl/PostgreSQL/upgrade/5-6/001-auto.sql @@ -0,0 +1,85 @@ +-- Convert schema 'share/ddl/_source/deploy/5/001-auto.yml' to 'share/ddl/_source/deploy/6/001-auto.yml':; +-- Customised for proper migration + +BEGIN; + +CREATE TABLE "entities" ( + "id" serial NOT NULL, + "type" character varying(255) NOT NULL, + PRIMARY KEY ("id") +); + +ALTER TABLE customers RENAME TO customers_temp; +ALTER TABLE organisations RENAME TO organisations_temp; +ALTER TABLE transactions RENAME TO transactions_temp; +ALTER TABLE users RENAME TO users_temp; + +CREATE TABLE "customers" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "display_name" character varying(255) NOT NULL, + "full_name" character varying(255) NOT NULL, + "year_of_birth" integer NOT NULL, + "postcode" character varying(16) NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "customers_idx_entity_id" on "customers" ("entity_id"); + +CREATE TABLE "organisations" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "name" character varying(255) NOT NULL, + "street_name" text, + "town" character varying(255) NOT NULL, + "postcode" character varying(16), + "country" character varying(255), + "sector" character varying(1), + "pending" boolean DEFAULT 0 NOT NULL, + "submitted_by_id" integer, + PRIMARY KEY ("id") +); +CREATE INDEX "organisations_idx_entity_id" on "organisations" ("entity_id"); + +CREATE TABLE "transactions" ( + "id" serial NOT NULL, + "buyer_id" integer NOT NULL, + "seller_id" integer NOT NULL, + "value" numeric(16,2) NOT NULL, + "proof_image" text, + "submitted_at" timestamp NOT NULL, + "purchase_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "transactions_idx_buyer_id" on "transactions" ("buyer_id"); +CREATE INDEX "transactions_idx_seller_id" on "transactions" ("seller_id"); + +CREATE TABLE "users" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "email" text NOT NULL, + "join_date" timestamp NOT NULL, + "password" character varying(100) NOT NULL, + "is_admin" boolean DEFAULT 0 NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "users_email" UNIQUE ("email") +); +CREATE INDEX "users_idx_entity_id" on "users" ("entity_id"); + +DROP TABLE leaderboard_values; +TRUNCATE TABLE leaderboard_sets; + +CREATE TABLE "leaderboard_values" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "set_id" integer NOT NULL, + "position" integer NOT NULL, + "value" numeric(16,2) NOT NULL, + "trend" integer DEFAULT 0 NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "leaderboard_values_entity_id_set_id" UNIQUE ("entity_id", "set_id") +); +CREATE INDEX "leaderboard_values_idx_entity_id" on "leaderboard_values" ("entity_id"); +CREATE INDEX "leaderboard_values_idx_set_id" on "leaderboard_values" ("set_id"); + +COMMIT; + diff --git a/share/ddl/PostgreSQL/upgrade/5-6/003-remove-temps.sql b/share/ddl/PostgreSQL/upgrade/5-6/003-remove-temps.sql new file mode 100644 index 0000000..8784b75 --- /dev/null +++ b/share/ddl/PostgreSQL/upgrade/5-6/003-remove-temps.sql @@ -0,0 +1,13 @@ +-- Remove temporary tables created during migration + +BEGIN; + +DROP TABLE customers_temp; +DROP TABLE organisations_temp; +DROP TABLE transactions_temp; +DROP TABLE users_temp; +DROP TABLE pending_organisations; +DROP TABLE pending_transactions; +DROP TABLE administrators; + +COMMIT; diff --git a/share/ddl/SQLite/deploy/6/001-auto-__VERSION.sql b/share/ddl/SQLite/deploy/6/001-auto-__VERSION.sql new file mode 100644 index 0000000..d3f4167 --- /dev/null +++ b/share/ddl/SQLite/deploy/6/001-auto-__VERSION.sql @@ -0,0 +1,18 @@ +-- +-- Created by SQL::Translator::Producer::SQLite +-- Created on Fri Sep 1 15:14:28 2017 +-- + +; +BEGIN TRANSACTION; +-- +-- Table: dbix_class_deploymenthandler_versions +-- +CREATE TABLE dbix_class_deploymenthandler_versions ( + id INTEGER PRIMARY KEY NOT NULL, + version varchar(50) NOT NULL, + ddl text, + upgrade_sql text +); +CREATE UNIQUE INDEX dbix_class_deploymenthandler_versions_version ON dbix_class_deploymenthandler_versions (version); +COMMIT; diff --git a/share/ddl/SQLite/deploy/6/001-auto.sql b/share/ddl/SQLite/deploy/6/001-auto.sql new file mode 100644 index 0000000..e178109 --- /dev/null +++ b/share/ddl/SQLite/deploy/6/001-auto.sql @@ -0,0 +1,145 @@ +-- +-- Created by SQL::Translator::Producer::SQLite +-- Created on Fri Sep 1 15:14:28 2017 +-- + +; +BEGIN TRANSACTION; +-- +-- Table: account_tokens +-- +CREATE TABLE account_tokens ( + id INTEGER PRIMARY KEY NOT NULL, + name text NOT NULL, + used integer NOT NULL DEFAULT 0 +); +CREATE UNIQUE INDEX account_tokens_name ON account_tokens (name); +-- +-- Table: entities +-- +CREATE TABLE entities ( + id INTEGER PRIMARY KEY NOT NULL, + type varchar(255) NOT NULL +); +-- +-- Table: leaderboards +-- +CREATE TABLE leaderboards ( + id INTEGER PRIMARY KEY NOT NULL, + name varchar(255) NOT NULL, + type varchar(255) NOT NULL +); +CREATE UNIQUE INDEX leaderboards_type ON leaderboards (type); +-- +-- Table: customers +-- +CREATE TABLE customers ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + display_name varchar(255) NOT NULL, + full_name varchar(255) NOT NULL, + year_of_birth integer NOT NULL, + postcode varchar(16) NOT NULL, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE +); +CREATE INDEX customers_idx_entity_id ON customers (entity_id); +-- +-- Table: leaderboard_sets +-- +CREATE TABLE leaderboard_sets ( + id INTEGER PRIMARY KEY NOT NULL, + leaderboard_id integer NOT NULL, + date datetime NOT NULL, + FOREIGN KEY (leaderboard_id) REFERENCES leaderboards(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX leaderboard_sets_idx_leaderboard_id ON leaderboard_sets (leaderboard_id); +-- +-- Table: organisations +-- +CREATE TABLE organisations ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + name varchar(255) NOT NULL, + street_name text, + town varchar(255) NOT NULL, + postcode varchar(16), + country varchar(255), + sector varchar(1), + pending boolean NOT NULL DEFAULT 0, + submitted_by_id integer, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE +); +CREATE INDEX organisations_idx_entity_id ON organisations (entity_id); +-- +-- Table: transactions +-- +CREATE TABLE transactions ( + id INTEGER PRIMARY KEY NOT NULL, + buyer_id integer NOT NULL, + seller_id integer NOT NULL, + value decimal(16,2) NOT NULL, + proof_image text, + submitted_at datetime NOT NULL, + purchase_time datetime NOT NULL, + FOREIGN KEY (buyer_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (seller_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX transactions_idx_buyer_id ON transactions (buyer_id); +CREATE INDEX transactions_idx_seller_id ON transactions (seller_id); +-- +-- Table: users +-- +CREATE TABLE users ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + email text NOT NULL, + join_date datetime NOT NULL, + password varchar(100) NOT NULL, + is_admin boolean NOT NULL DEFAULT 0, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE +); +CREATE INDEX users_idx_entity_id ON users (entity_id); +CREATE UNIQUE INDEX users_email ON users (email); +-- +-- Table: feedback +-- +CREATE TABLE feedback ( + id INTEGER PRIMARY KEY NOT NULL, + user_id integer NOT NULL, + submitted_at datetime NOT NULL, + feedbacktext text NOT NULL, + app_name varchar(255) NOT NULL, + package_name varchar(255) NOT NULL, + version_code varchar(255) NOT NULL, + version_number varchar(255) NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX feedback_idx_user_id ON feedback (user_id); +-- +-- Table: session_tokens +-- +CREATE TABLE session_tokens ( + id INTEGER PRIMARY KEY NOT NULL, + token varchar(255) NOT NULL, + user_id integer NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX session_tokens_idx_user_id ON session_tokens (user_id); +CREATE UNIQUE INDEX session_tokens_token ON session_tokens (token); +-- +-- Table: leaderboard_values +-- +CREATE TABLE leaderboard_values ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + set_id integer NOT NULL, + position integer NOT NULL, + value decimal(16,2) NOT NULL, + trend integer NOT NULL DEFAULT 0, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (set_id) REFERENCES leaderboard_sets(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX leaderboard_values_idx_entity_id ON leaderboard_values (entity_id); +CREATE INDEX leaderboard_values_idx_set_id ON leaderboard_values (set_id); +CREATE UNIQUE INDEX leaderboard_values_entity_id_set_id ON leaderboard_values (entity_id, set_id); +COMMIT; diff --git a/share/ddl/SQLite/upgrade/5-6/001-auto.sql b/share/ddl/SQLite/upgrade/5-6/001-auto.sql new file mode 100644 index 0000000..ada6df7 --- /dev/null +++ b/share/ddl/SQLite/upgrade/5-6/001-auto.sql @@ -0,0 +1,90 @@ +-- Convert schema 'share/ddl/_source/deploy/5/001-auto.yml' to 'share/ddl/_source/deploy/6/001-auto.yml':; +-- Customised for proper migration + +BEGIN; + +CREATE TABLE entities ( + id INTEGER PRIMARY KEY NOT NULL, + type varchar(255) NOT NULL +); + +ALTER TABLE customers RENAME TO customers_temp; + +CREATE TABLE customers ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + display_name varchar(255) NOT NULL, + full_name varchar(255) NOT NULL, + year_of_birth integer NOT NULL, + postcode varchar(16) NOT NULL, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE +); +CREATE INDEX customers_idx_entity_id ON customers (entity_id); + +-- Leaderboards must be regenerated, this saves trying to do this the hard way +DROP TABLE leaderboard_values; +DELETE FROM leaderboard_sets; + +CREATE TABLE leaderboard_values ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + set_id integer NOT NULL, + position integer NOT NULL, + value decimal(16,2) NOT NULL, + trend integer NOT NULL DEFAULT 0, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (set_id) REFERENCES leaderboard_sets(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX leaderboard_values_idx_entity_id ON leaderboard_values (entity_id); +CREATE INDEX leaderboard_values_idx_set_id ON leaderboard_values (set_id); +CREATE UNIQUE INDEX leaderboard_values_entity_id_set_id ON leaderboard_values (entity_id, set_id); + +ALTER TABLE organisations RENAME TO organisations_temp; + +CREATE TABLE organisations ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + name varchar(255) NOT NULL, + street_name text, + town varchar(255) NOT NULL, + postcode varchar(16), + country varchar(255), + sector varchar(1), + pending boolean NOT NULL DEFAULT 0, + submitted_by_id integer, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE +); +CREATE INDEX organisations_idx_entity_id ON organisations (entity_id); + +ALTER TABLE transactions RENAME TO transactions_temp; + +CREATE TABLE transactions ( + id INTEGER PRIMARY KEY NOT NULL, + buyer_id integer NOT NULL, + seller_id integer NOT NULL, + value decimal(16,2) NOT NULL, + proof_image text, + submitted_at datetime NOT NULL, + purchase_time datetime NOT NULL, + FOREIGN KEY (buyer_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (seller_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX transactions_idx_buyer_id02 ON transactions (buyer_id); +CREATE INDEX transactions_idx_seller_id02 ON transactions (seller_id); + +ALTER TABLE users RENAME TO users_temp; + +CREATE TABLE users ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + email text NOT NULL, + join_date datetime NOT NULL, + password varchar(100) NOT NULL, + is_admin boolean NOT NULL DEFAULT 0, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE +); +CREATE INDEX users_idx_entity_id02 ON users (entity_id); +CREATE UNIQUE INDEX users_email02 ON users (email); + +COMMIT; + diff --git a/share/ddl/SQLite/upgrade/5-6/003-remove-temps.sql b/share/ddl/SQLite/upgrade/5-6/003-remove-temps.sql new file mode 100644 index 0000000..8784b75 --- /dev/null +++ b/share/ddl/SQLite/upgrade/5-6/003-remove-temps.sql @@ -0,0 +1,13 @@ +-- Remove temporary tables created during migration + +BEGIN; + +DROP TABLE customers_temp; +DROP TABLE organisations_temp; +DROP TABLE transactions_temp; +DROP TABLE users_temp; +DROP TABLE pending_organisations; +DROP TABLE pending_transactions; +DROP TABLE administrators; + +COMMIT; diff --git a/share/ddl/_common/upgrade/5-6/002-entity-upgrade.pl b/share/ddl/_common/upgrade/5-6/002-entity-upgrade.pl new file mode 100644 index 0000000..6a892c8 --- /dev/null +++ b/share/ddl/_common/upgrade/5-6/002-entity-upgrade.pl @@ -0,0 +1,111 @@ +#! perl + +use strict; +use warnings; + +use DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator::ScriptHelpers + 'schema_from_schema_loader'; + +schema_from_schema_loader({ naming => 'v7' }, sub { + my $schema = shift; + + my $user_rs = $schema->resultset('UsersTemp'); + my $customer_rs = $schema->resultset('CustomersTemp'); + my $organisation_rs = $schema->resultset('OrganisationsTemp'); + my $transaction_rs = $schema->resultset('TransactionsTemp'); + my $pending_org_rs = $schema->resultset('PendingOrganisation'); + my $pending_trans_rs = $schema->resultset('PendingTransaction'); + + # Lookups used for converting transactions + my $org_lookup = {}; + my $user_lookup = {}; + + # First migrate all customers, organisations, and pending organisations across to the entity table. + for my $customer_result ( $customer_rs->all ) { + my $user_result = $user_rs->find({ customer_id => $customer_result->id }); + my $administrator = $schema->resultset('Administrator')->find({ user_id => $user_result->id }); + my $new_entity = $schema->resultset('Entity')->create({ type => 'customer' }); + + my $new_customer = $schema->resultset('Customer')->create({ + entity_id => $new_entity->id, + display_name => $customer_result->display_name, + full_name => $customer_result->full_name, + year_of_birth => $customer_result->year_of_birth, + postcode => $customer_result->postcode, + }); + + # In the old system, all customers were users + my $new_user = $schema->resultset('User')->create({ + entity_id => $new_entity->id, + email => $user_result->email, + join_date => $user_result->join_date, + password => $user_result->password, + is_admin => defined $administrator ? 1 : 0, + }); + $user_lookup->{$user_result->id} = $new_entity->id; + } + + for my $organisation_result ( $organisation_rs->all ) { + my $user_result = $user_rs->find({ organisation_id => $organisation_result->id }); + my $new_entity = $schema->resultset('Entity')->create({ type => 'organisation' }); + + my $org = $schema->resultset('Organisation')->create({ + entity_id => $new_entity->id, + name => $organisation_result->name, + street_name => $organisation_result->street_name, + town => $organisation_result->town, + postcode => $organisation_result->postcode, + }); + + # In the old system, not all organisations were users - but could have still been an admin? + if ( defined $user_result ) { + my $administrator = $schema->resultset('Administrator')->find({ user_id => $user_result->id }); + my $new_user = $schema->resultset('User')->create({ + entity_id => $new_entity->id, + email => $user_result->email, + join_date => $user_result->join_date, + password => $user_result->password, + is_admin => defined $administrator ? 1 : 0, + }); + $user_lookup->{$user_result->id} = $new_entity->id; + } + $org_lookup->{$organisation_result->id} = $new_entity->id; + } + + for my $transaction_result ( $transaction_rs->all ) { + my $new_transaction = $schema->resultset('Transaction')->create({ + buyer_id => $user_lookup->{ $transaction_result->buyer_id }, + seller_id => $org_lookup->{ $transaction_result->seller_id }, + value => $transaction_result->value, + proof_image => $transaction_result->proof_image, + submitted_at => $transaction_result->submitted_at, + purchase_time => $transaction_result->purchase_time, + }); + } + + for my $pending_result ( $pending_org_rs->all ) { + my $entity = $schema->resultset('Entity')->create({ type => 'organisation' }); + my $org = $schema->resultset('Organisation')->create({ + entity_id => $entity->id, + name => $pending_result->name, + street_name => $pending_result->street_name, + town => $pending_result->town, + postcode => $pending_result->postcode, + submitted_by_id => $user_lookup->{ $pending_result->submitted_by_id }, + pending => 1, + }); + my $pending_trans_set_rs = $pending_trans_rs->search({ + seller_id => $pending_result->id, + }); + for my $trans_result ( $pending_trans_set_rs->all ) { + $schema->resultset('Transaction')->create({ + buyer_id => $user_lookup->{ $trans_result->buyer_id }, + seller_id => $entity->id, + value => $trans_result->value, + proof_image => $trans_result->proof_image, + submitted_at => $trans_result->submitted_at, + purchase_time => $trans_result->purchase_time, + }); + } + } +}); diff --git a/share/ddl/_source/deploy/6/001-auto-__VERSION.yml b/share/ddl/_source/deploy/6/001-auto-__VERSION.yml new file mode 100644 index 0000000..907f443 --- /dev/null +++ b/share/ddl/_source/deploy/6/001-auto-__VERSION.yml @@ -0,0 +1,91 @@ +--- +schema: + procedures: {} + tables: + dbix_class_deploymenthandler_versions: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - version + match_type: '' + name: dbix_class_deploymenthandler_versions_version + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + fields: + ddl: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: ddl + order: 3 + size: + - 0 + id: + data_type: int + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + upgrade_sql: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: upgrade_sql + order: 4 + size: + - 0 + version: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: version + order: 2 + size: + - 50 + indices: [] + name: dbix_class_deploymenthandler_versions + options: [] + order: 1 + triggers: {} + views: {} +translator: + add_drop_table: 0 + filename: ~ + no_comments: 0 + parser_args: + sources: + - __VERSION + parser_type: SQL::Translator::Parser::DBIx::Class + producer_args: {} + producer_type: SQL::Translator::Producer::YAML + show_warnings: 0 + trace: 0 + version: 0.11021 diff --git a/share/ddl/_source/deploy/6/001-auto.yml b/share/ddl/_source/deploy/6/001-auto.yml new file mode 100644 index 0000000..2448ed4 --- /dev/null +++ b/share/ddl/_source/deploy/6/001-auto.yml @@ -0,0 +1,1064 @@ +--- +schema: + procedures: {} + tables: + account_tokens: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - name + match_type: '' + name: account_tokens_name + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + name: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: name + order: 2 + size: + - 0 + used: + data_type: integer + default_value: 0 + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: used + order: 3 + size: + - 0 + indices: [] + name: account_tokens + options: [] + order: 1 + customers: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - entity_id + match_type: '' + name: customers_fk_entity_id + on_delete: CASCADE + on_update: '' + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + fields: + display_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: display_name + order: 3 + size: + - 255 + entity_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: entity_id + order: 2 + size: + - 0 + full_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: full_name + order: 4 + size: + - 255 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + postcode: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: postcode + order: 6 + size: + - 16 + year_of_birth: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: year_of_birth + order: 5 + size: + - 0 + indices: + - fields: + - entity_id + name: customers_idx_entity_id + options: [] + type: NORMAL + name: customers + options: [] + order: 4 + entities: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + type: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: type + order: 2 + size: + - 255 + indices: [] + name: entities + options: [] + order: 2 + feedback: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - user_id + match_type: '' + name: feedback_fk_user_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + fields: + app_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: app_name + order: 5 + size: + - 255 + feedbacktext: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: feedbacktext + order: 4 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + package_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: package_name + order: 6 + size: + - 255 + submitted_at: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: submitted_at + order: 3 + size: + - 0 + user_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: user_id + order: 2 + size: + - 0 + version_code: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: version_code + order: 7 + size: + - 255 + version_number: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: version_number + order: 8 + size: + - 255 + indices: + - fields: + - user_id + name: feedback_idx_user_id + options: [] + type: NORMAL + name: feedback + options: [] + order: 9 + leaderboard_sets: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - leaderboard_id + match_type: '' + name: leaderboard_sets_fk_leaderboard_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: leaderboards + type: FOREIGN KEY + fields: + date: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: date + order: 3 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + leaderboard_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: leaderboard_id + order: 2 + size: + - 0 + indices: + - fields: + - leaderboard_id + name: leaderboard_sets_idx_leaderboard_id + options: [] + type: NORMAL + name: leaderboard_sets + options: [] + order: 5 + leaderboard_values: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - entity_id + - set_id + match_type: '' + name: leaderboard_values_entity_id_set_id + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 0 + expression: '' + fields: + - entity_id + match_type: '' + name: leaderboard_values_fk_entity_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + - deferrable: 0 + expression: '' + fields: + - set_id + match_type: '' + name: leaderboard_values_fk_set_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: leaderboard_sets + type: FOREIGN KEY + fields: + entity_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: entity_id + order: 2 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + position: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: position + order: 4 + size: + - 0 + set_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: set_id + order: 3 + size: + - 0 + trend: + data_type: integer + default_value: 0 + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: trend + order: 6 + size: + - 0 + value: + data_type: decimal + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: value + order: 5 + size: + - 16 + - 2 + indices: + - fields: + - entity_id + name: leaderboard_values_idx_entity_id + options: [] + type: NORMAL + - fields: + - set_id + name: leaderboard_values_idx_set_id + options: [] + type: NORMAL + name: leaderboard_values + options: [] + order: 11 + leaderboards: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - type + match_type: '' + name: leaderboards_type + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: name + order: 2 + size: + - 255 + type: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: type + order: 3 + size: + - 255 + indices: [] + name: leaderboards + options: [] + order: 3 + organisations: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - entity_id + match_type: '' + name: organisations_fk_entity_id + on_delete: CASCADE + on_update: '' + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + fields: + country: + data_type: varchar + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: country + order: 7 + size: + - 255 + entity_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: entity_id + order: 2 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: name + order: 3 + size: + - 255 + pending: + data_type: boolean + default_value: !!perl/ref + =: 0 + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: pending + order: 9 + size: + - 0 + postcode: + data_type: varchar + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: postcode + order: 6 + size: + - 16 + sector: + data_type: varchar + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: sector + order: 8 + size: + - 1 + street_name: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: street_name + order: 4 + size: + - 0 + submitted_by_id: + data_type: integer + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: submitted_by_id + order: 10 + size: + - 0 + town: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: town + order: 5 + size: + - 255 + indices: + - fields: + - entity_id + name: organisations_idx_entity_id + options: [] + type: NORMAL + name: organisations + options: [] + order: 6 + session_tokens: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - token + match_type: '' + name: session_tokens_token + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 0 + expression: '' + fields: + - user_id + match_type: '' + name: session_tokens_fk_user_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + token: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: token + order: 2 + size: + - 255 + user_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: user_id + order: 3 + size: + - 0 + indices: + - fields: + - user_id + name: session_tokens_idx_user_id + options: [] + type: NORMAL + name: session_tokens + options: [] + order: 10 + transactions: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - buyer_id + match_type: '' + name: transactions_fk_buyer_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + - deferrable: 0 + expression: '' + fields: + - seller_id + match_type: '' + name: transactions_fk_seller_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + fields: + buyer_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: buyer_id + order: 2 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + proof_image: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: proof_image + order: 5 + size: + - 0 + purchase_time: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: purchase_time + order: 7 + size: + - 0 + seller_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: seller_id + order: 3 + size: + - 0 + submitted_at: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: submitted_at + order: 6 + size: + - 0 + value: + data_type: decimal + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: value + order: 4 + size: + - 16 + - 2 + indices: + - fields: + - buyer_id + name: transactions_idx_buyer_id + options: [] + type: NORMAL + - fields: + - seller_id + name: transactions_idx_seller_id + options: [] + type: NORMAL + name: transactions + options: [] + order: 7 + users: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - email + match_type: '' + name: users_email + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 1 + expression: '' + fields: + - entity_id + match_type: '' + name: users_fk_entity_id + on_delete: CASCADE + on_update: '' + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + fields: + email: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: email + order: 3 + size: + - 0 + entity_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: entity_id + order: 2 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + is_admin: + data_type: boolean + default_value: !!perl/ref + =: 0 + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: is_admin + order: 6 + size: + - 0 + join_date: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: join_date + order: 4 + size: + - 0 + password: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: password + order: 5 + size: + - 100 + indices: + - fields: + - entity_id + name: users_idx_entity_id + options: [] + type: NORMAL + name: users + options: [] + order: 8 + triggers: {} + views: {} +translator: + add_drop_table: 0 + filename: ~ + no_comments: 0 + parser_args: + sources: + - AccountToken + - Customer + - Entity + - Feedback + - Leaderboard + - LeaderboardSet + - LeaderboardValue + - Organisation + - SessionToken + - Transaction + - User + parser_type: SQL::Translator::Parser::DBIx::Class + producer_args: {} + producer_type: SQL::Translator::Producer::YAML + show_warnings: 0 + trace: 0 + version: 0.11021 From 2ef5193e415c56919b412cc1a033d78168bb0d46 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 4 Sep 2017 14:03:58 +0100 Subject: [PATCH 37/83] Fixed organisaiton listing and editing in backend --- .../Controller/Admin/Organisations.pm | 6 +- lib/Pear/LocalLoop/Schema/Result/Entity.pm | 12 ++++ templates/admin/organisations/list.html.ep | 4 +- .../admin/organisations/valid_read.html.ep | 59 +++++++++++++++---- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm index dba62e0..9a9a919 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm @@ -6,8 +6,8 @@ use Try::Tiny; sub list { my $c = shift; - my $valid_orgs_rs = $c->schema->resultset('Organisation'); - my $pending_orgs_rs = $c->schema->resultset('PendingOrganisation'); + my $valid_orgs_rs = $c->schema->resultset('Organisation')->search({ pending => 0 }); + my $pending_orgs_rs = $c->schema->resultset('Organisation')->search({ pending => 1 }); $c->stash( valid_orgs_rs => $valid_orgs_rs, @@ -85,6 +85,7 @@ sub valid_edit { $validation->required('town'); $validation->optional('sector'); $validation->required('postcode')->postcode; + $validation->optional('pending'); if ( $validation->has_error ) { $c->flash( error => 'The validation has failed' ); @@ -101,6 +102,7 @@ sub valid_edit { town => $validation->param('town'), sector => $validation->param('sector'), postcode => $validation->param('postcode'), + pending => defined $validation->param('pending') ? 0 : 1, }); } ); } finally { diff --git a/lib/Pear/LocalLoop/Schema/Result/Entity.pm b/lib/Pear/LocalLoop/Schema/Result/Entity.pm index 121d560..0321049 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Entity.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Entity.pm @@ -51,4 +51,16 @@ __PACKAGE__->has_many( { cascade_copy => 0, cascade_delete => 0 }, ); +sub name { + my $self = shift; + + if ( $self->type eq 'customer' ) { + return $self->customer->display_name; + } elsif ( $self->type eq 'organisation' ) { + return $self->organisation->name; + } else { + return "Unknown Name"; + } +} + 1; diff --git a/templates/admin/organisations/list.html.ep b/templates/admin/organisations/list.html.ep index dad10cf..4db8974 100644 --- a/templates/admin/organisations/list.html.ep +++ b/templates/admin/organisations/list.html.ep @@ -20,7 +20,7 @@
    % for my $valid_org ($valid_orgs_rs->all) { - +
    %= $valid_org->name
    @@ -41,7 +41,7 @@
    % } else { % for my $pending_org ($pending_orgs_rs->all) { - +
    %= $pending_org->name
    diff --git a/templates/admin/organisations/valid_read.html.ep b/templates/admin/organisations/valid_read.html.ep index 2055496..5bbe739 100644 --- a/templates/admin/organisations/valid_read.html.ep +++ b/templates/admin/organisations/valid_read.html.ep @@ -15,20 +15,53 @@

    %= $valid_org->name

    -
      - -
    • -
    • -
    • -
    • - -
    • -
    • -
    • +
      + +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + pending ? '' : ' checked' %>> +
      +
      +
      +
      + +
      +
      -
    +

    From 4fa52901cbbe057f8da78b95542a0571627b1d79 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 4 Sep 2017 14:09:36 +0100 Subject: [PATCH 38/83] Update add org to work with new form --- .../Controller/Admin/Organisations.pm | 3 + templates/admin/organisations/add_org.html.ep | 60 ++++++++++++++----- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm index 9a9a919..6921c6a 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Organisations.pm @@ -29,6 +29,7 @@ sub add_org_submit { $validation->required('town'); $validation->optional('sector'); $validation->optional('postcode')->postcode; + $validation->optional('pending'); if ( $validation->has_error ) { $c->flash( error => 'The validation has failed' ); @@ -45,6 +46,8 @@ sub add_org_submit { town => $validation->param('town'), sector => $validation->param('sector'), postcode => $validation->param('postcode'), + submitted_by_id => $c->current_user->id, + pending => defined $validation->param('pending') ? 0 : 1, }, type => 'organisation', }); diff --git a/templates/admin/organisations/add_org.html.ep b/templates/admin/organisations/add_org.html.ep index 6e7702e..be22d3c 100644 --- a/templates/admin/organisations/add_org.html.ep +++ b/templates/admin/organisations/add_org.html.ep @@ -15,19 +15,51 @@

    Add an Organisation

    -
      -
      -
    • Only 'Organisation Name' and 'Town' are required entries.
    • -
    • -
    • -
    • -
    • - -
    • -
    • -
    • +
      + +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      +
      + +
      +
      -
    +

    From 161d50e09589a265c7594e5b5155eb6d20c23190 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 4 Sep 2017 14:10:38 +0100 Subject: [PATCH 39/83] Fix editing user in Admin --- templates/admin/users/read.html.ep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/admin/users/read.html.ep b/templates/admin/users/read.html.ep index eafe62e..149be12 100644 --- a/templates/admin/users/read.html.ep +++ b/templates/admin/users/read.html.ep @@ -11,7 +11,7 @@ Success! <%= $success %> % } -
    +

    User Details

    From 2137b5607f2456725b25340f4099387586b0297f Mon Sep 17 00:00:00 2001 From: Finn Date: Mon, 4 Sep 2017 16:12:03 +0100 Subject: [PATCH 40/83] Fixed Org account editing for portal --- lib/Pear/LocalLoop/Controller/Api/User.pm | 44 +++++++++++++++-------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/User.pm b/lib/Pear/LocalLoop/Controller/Api/User.pm index 555c902..e017e19 100644 --- a/lib/Pear/LocalLoop/Controller/Api/User.pm +++ b/lib/Pear/LocalLoop/Controller/Api/User.pm @@ -60,28 +60,42 @@ sub post_account { if ( defined $user_result ) { my $email = $user_result->email; - my $full_name; - my $display_name; - my $postcode; #Needs elsif added for trader page for this similar relevant entry if ( $user_result->type eq 'customer' ) { - $full_name = $user_result->entity->customer->full_name; - $display_name = $user_result->entity->customer->display_name; - $postcode = $user_result->entity->customer->postcode; + my $full_name = $user_result->entity->customer->full_name; + my $display_name = $user_result->entity->customer->display_name; + my $postcode = $user_result->entity->customer->postcode; + return $c->render( json => { + success => Mojo::JSON->true, + full_name => $full_name, + display_name => $display_name, + email => $email, + postcode => $postcode, + }); } elsif ( $user_result->type eq 'organisation' ) { - $display_name = $user_result->entity->organisation->name; + my $name = $user_result->entity->organisation->name; + my $postcode = $user_result->entity->organisation->postcode; + my $street_name = $user_result->entity->organisation->street_name; + my $town = $user_result->entity->organisation->town; + return $c->render( json => { + success => Mojo::JSON->true, + town => $town, + name => $name, + street_name => $street_name, + email => $email, + postcode => $postcode, + }); } else { - return; + return $c->render( + json => { + success => Mojo::JSON->false, + message => 'Invalid Server Error.', + }, + status => 500 + ); } - return $c->render( json => { - success => Mojo::JSON->true, - full_name => $full_name, - display_name => $display_name, - email => $email, - postcode => $postcode, - }); } return $c->render( json => { From fabd01f98869a68af6049410deaec315f89786f6 Mon Sep 17 00:00:00 2001 From: Finn Date: Mon, 4 Sep 2017 17:05:10 +0100 Subject: [PATCH 41/83] Fixed for sector in account editing --- lib/Pear/LocalLoop/Controller/Api/User.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Pear/LocalLoop/Controller/Api/User.pm b/lib/Pear/LocalLoop/Controller/Api/User.pm index e017e19..b83f04b 100644 --- a/lib/Pear/LocalLoop/Controller/Api/User.pm +++ b/lib/Pear/LocalLoop/Controller/Api/User.pm @@ -33,6 +33,9 @@ has error_messages => sub { town => { required => { message => 'No town sent.', status => 400 }, }, + sector => { + required => { message => 'No sector sent.', status => 400 }, + }, }; }; @@ -78,10 +81,12 @@ sub post_account { my $postcode = $user_result->entity->organisation->postcode; my $street_name = $user_result->entity->organisation->street_name; my $town = $user_result->entity->organisation->town; + my $sector = $user_result->entity->organisation->sector; return $c->render( json => { success => Mojo::JSON->true, town => $town, name => $name, + sector => $sector, street_name => $street_name, email => $email, postcode => $postcode, From 4d21de7bae2edc887dcc47a080b377c8d6fb683d Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 4 Sep 2017 17:29:28 +0100 Subject: [PATCH 42/83] Improved Graphs and added sales and purchase data graphs. Includes an API change for development. --- .../Controller/Api/V1/Organisation/Graphs.pm | 101 +++++++++++++++--- t/api/v1/organisation/graphs.t | 31 +++++- 2 files changed, 112 insertions(+), 20 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm index bc78d91..07b952b 100644 --- a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm @@ -18,6 +18,10 @@ sub index { $validation->required('graph')->in( qw/ customers_last_7_days customers_last_30_days + sales_last_7_days + sales_last_30_days + purchases_last_7_days + purchases_last_30_days / ); return $c->api_validation_error if $validation->has_error; @@ -56,24 +60,20 @@ sub graph_customers_last_30_days { sub _customers_last_duration { my ( $c, $duration ) = @_; - my $org = $c->stash->{api_user}->entity; + my $entity = $c->stash->{api_user}->entity; - my $data = { day => [], count => [] }; + my $data = { labels => [], data => [] }; - my $start = DateTime->today; - my $end = $start->clone->subtract_duration( $duration ); + my ( $start, $end ) = $c->_get_start_end_duration( $duration ); - my $dtf = $c->schema->storage->datetime_parser; - - while ( $end < $start ) { - my $moving_end = $end->clone->add( days => 1 ); - my $transactions = $c->schema->resultset('Transaction')->search({ - seller_id => $org->id, - purchase_time => { '-between' => [ $dtf->format_datetime($end), $dtf->format_datetime($moving_end) ] }, - })->count; - push @{$data->{day}}, $end->day_name; - push @{$data->{count}}, $transactions; - $end->add( days => 1 ); + while ( $start < $end ) { + my $next_end = $start->clone->add( days => 1 ); + my $transactions = $entity->sales + ->search_between( $start, $next_end ) + ->count; + push @{ $data->{ labels } }, $start->day_name; + push @{ $data->{ data } }, $transactions; + $start->add( days => 1 ); } return $c->render( @@ -84,4 +84,75 @@ sub _customers_last_duration { ); } +sub graph_sales_last_7_days { return shift->_sales_last_duration( 7 ) } +sub graph_sales_last_30_days { return shift->_sales_last_duration( 30 ) } + +sub _sales_last_duration { + my ( $c, $day_duration ) = @_; + + my $duration = DateTime::Duration->new( days => $day_duration ); + my $entity = $c->stash->{api_user}->entity; + + my $data = { labels => [], data => [] }; + + my ( $start, $end ) = $c->_get_start_end_duration( $duration ); + + while ( $start < $end ) { + my $next_end = $start->clone->add( days => 1 ); + my $transactions = $entity->sales + ->search_between( $start, $next_end ) + ->get_column('value') + ->sum; + push @{ $data->{ labels } }, $start->day_name; + push @{ $data->{ data } }, $transactions; + $start->add( days => 1 ); + } + + return $c->render( + json => { + success => Mojo::JSON->true, + graph => $data, + } + ); +} + +sub graph_purchases_last_7_days { return shift->_purchases_last_duration( 7 ) } +sub graph_purchases_last_30_days { return shift->_purchases_last_duration( 30 ) } + +sub _purchases_last_duration { + my ( $c, $day_duration ) = @_; + + my $duration = DateTime::Duration->new( days => $day_duration ); + my $entity = $c->stash->{api_user}->entity; + + my $data = { labels => [], data => [] }; + + my ( $start, $end ) = $c->_get_start_end_duration( $duration ); + + while ( $start < $end ) { + my $next_end = $start->clone->add( days => 1 ); + my $transactions = $entity->purchases + ->search_between( $start, $next_end ) + ->get_column('value') + ->sum; + push @{ $data->{ labels } }, $start->day_name; + push @{ $data->{ data } }, $transactions; + $start->add( days => 1 ); + } + + return $c->render( + json => { + success => Mojo::JSON->true, + graph => $data, + } + ); +} + +sub _get_start_end_duration { + my ( $c, $duration ) = @_; + my $end = DateTime->today; + my $start = $end->clone->subtract_duration( $duration ); + return ( $start, $end ); +} + 1; diff --git a/t/api/v1/organisation/graphs.t b/t/api/v1/organisation/graphs.t index c5ee6f9..45531a6 100644 --- a/t/api/v1/organisation/graphs.t +++ b/t/api/v1/organisation/graphs.t @@ -44,8 +44,8 @@ $t->post_ok('/api/v1/organisation/graphs' => json => { }) ->status_is(200)->or($framework->dump_error) ->json_is('/graph', { - day => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 6 ) ], - count => [ 2, 4, 2, 3, 3, 4, 1 ], + labels => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 6 ) ], + data => [ 2, 4, 2, 3, 3, 4, 1 ], }); $t->post_ok('/api/v1/organisation/graphs' => json => { @@ -54,8 +54,28 @@ $t->post_ok('/api/v1/organisation/graphs' => json => { }) ->status_is(200)->or($framework->dump_error) ->json_is('/graph', { - day => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 29 ) ], - count => [ 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1 ], + labels => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 29 ) ], + data => [ 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1 ], + }); + +$t->post_ok('/api/v1/organisation/graphs' => json => { + session_key => $session_key, + graph => 'sales_last_7_days', + }) + ->status_is(200)->or($framework->dump_error) + ->json_is('/graph', { + labels => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 6 ) ], + data => [ 20, 40, 20, 30, 30, 40, 10 ], + }); + +$t->post_ok('/api/v1/organisation/graphs' => json => { + session_key => $session_key, + graph => 'sales_last_30_days', + }) + ->status_is(200)->or($framework->dump_error) + ->json_is('/graph', { + labels => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 29 ) ], + data => [ 40, 20, 30, 30, 40, 10, 40, 30, 30, 20, 40, 20, 40, 20, 30, 30, 40, 10, 40, 30, 30, 20, 40, 20, 40, 20, 30, 30, 40, 10 ], }); $framework->logout( $session_key ); @@ -73,6 +93,7 @@ $t->post_ok('/api/v1/organisation/graphs' => json => { ->json_is('/success', Mojo::JSON->false) ->json_is('/error', 'user_not_org'); + sub create_random_transaction { my $buyer = shift; my $time = shift; @@ -82,7 +103,7 @@ sub create_random_transaction { $schema->resultset('Transaction')->create({ buyer => $buyer_result, seller => $seller_result, - value => ( int( rand( 10000 ) ) / 100 ), + value => 10, proof_image => 'a', purchase_time => $time, }); From 4ba6a443fca484dfffa8392ebbb8487a1562e37c Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Mon, 4 Sep 2017 17:39:48 +0100 Subject: [PATCH 43/83] Stop null values on org graphs --- lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm index 07b952b..5b4eab5 100644 --- a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm @@ -102,7 +102,7 @@ sub _sales_last_duration { my $transactions = $entity->sales ->search_between( $start, $next_end ) ->get_column('value') - ->sum; + ->sum || 0; push @{ $data->{ labels } }, $start->day_name; push @{ $data->{ data } }, $transactions; $start->add( days => 1 ); @@ -134,7 +134,7 @@ sub _purchases_last_duration { my $transactions = $entity->purchases ->search_between( $start, $next_end ) ->get_column('value') - ->sum; + ->sum || 0; push @{ $data->{ labels } }, $start->day_name; push @{ $data->{ data } }, $transactions; $start->add( days => 1 ); From a463b99fa9fdeebe4dd4e53a06febdf280db6234 Mon Sep 17 00:00:00 2001 From: Finn Date: Tue, 5 Sep 2017 12:03:49 +0100 Subject: [PATCH 44/83] Stopped Orgs from buying for themselves & added to test --- lib/Pear/LocalLoop/Controller/Api/Upload.pm | 19 ++++++++++++++++--- t/api/upload.t | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/Upload.pm b/lib/Pear/LocalLoop/Controller/Api/Upload.pm index 8ebe48b..87a3fba 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Upload.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Upload.pm @@ -112,7 +112,10 @@ sub post_upload { if ( $type == 1 ) { # Validated Organisation - my $valid_org_rs = $c->schema->resultset('Organisation')->search({ pending => 0 }); + my $valid_org_rs = $c->schema->resultset('Organisation')->search({ + pending => 0, + entity_id => { "!=" => $user->entity_id }, + }); $validation->required('organisation_id')->number->in_resultset( 'id', $valid_org_rs ); return $c->api_validation_error if $validation->has_error; @@ -121,7 +124,11 @@ sub post_upload { } elsif ( $type == 2 ) { # Unvalidated Organisation - my $valid_org_rs = $c->schema->resultset('Organisation')->search({ submitted_by_id => $user->id, pending => 1 }); + my $valid_org_rs = $c->schema->resultset('Organisation')->search({ + submitted_by_id => $user->id, + pending => 1, + entity_id => { "!=" => $user->entity_id }, + }); $validation->required('organisation_id')->number->in_resultset( 'id', $valid_org_rs ); return $c->api_validation_error if $validation->has_error; @@ -198,6 +205,8 @@ sub post_search { my $c = shift; my $self = $c; + my $user = $c->stash->{api_user}; + my $validation = $c->validation; $validation->input( $c->stash->{api_json} ); @@ -211,13 +220,17 @@ sub post_search { my $search_stmt = [ 'LOWER("name") LIKE ?', '%' . lc $search_name . '%' ]; my $org_rs = $c->schema->resultset('Organisation'); - my $valid_orgs_rs = $org_rs->search({ pending => 0 })->search( + my $valid_orgs_rs = $org_rs->search({ + pending => 0, + entity_id => { "!=" => $user->entity_id }, + })->search( \$search_stmt, ); my $pending_orgs_rs = $org_rs->search({ pending => 1, submitted_by_id => $c->stash->{api_user}->id, + entity_id => { "!=" => $user->entity_id }, })->search( \$search_stmt, ); diff --git a/t/api/upload.t b/t/api/upload.t index 18f5b52..fb64136 100644 --- a/t/api/upload.t +++ b/t/api/upload.t @@ -464,4 +464,19 @@ $t->post_ok('/api/upload' => form => $upload ) ->json_like('/message', qr/Upload Successful/); is $schema->resultset('Transaction')->count, 6, "6 transaction"; +print "test 31 - organisation buy from same organisation\n"; +$json = { + transaction_value => 100000, + transaction_type => 1, + purchase_time => $test_purchase_time, + organisation_id => 2, + session_key => $session_key, +}; +$upload = {json => Mojo::JSON::encode_json($json), file => {file => './t/test.jpg'}}; +$t->post_ok('/api/upload' => form => $upload ) + ->status_is(400) + ->json_is('/success', Mojo::JSON->false) + ->json_like('/message', qr/organisation_id does not exist in the database/); +is $schema->resultset('Transaction')->count, 6, "6 transaction"; + done_testing(); From cd6c0755887acc3927057efab9d5e39c4df3b46d Mon Sep 17 00:00:00 2001 From: Finn Date: Tue, 5 Sep 2017 12:27:37 +0100 Subject: [PATCH 45/83] View Transactions from a User on their page --- lib/Pear/LocalLoop/Controller/Admin/Users.pm | 12 +++++++++- templates/admin/users/read.html.ep | 25 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/Pear/LocalLoop/Controller/Admin/Users.pm b/lib/Pear/LocalLoop/Controller/Admin/Users.pm index 803b68e..13905c6 100644 --- a/lib/Pear/LocalLoop/Controller/Admin/Users.pm +++ b/lib/Pear/LocalLoop/Controller/Admin/Users.pm @@ -33,7 +33,17 @@ sub read { my $id = $c->param('id'); if ( my $user = $c->user_result_set->find($id) ) { - $c->stash( user => $user ); + my $transactions = $user->entity->purchases->search( + undef, { + page => $c->param('page') || 1, + rows => 10, + order_by => { -desc => 'submitted_at' }, + }, + ); + $c->stash( + user => $user, + transactions => $transactions, + ); } else { $c->flash( error => 'No User found' ); $c->redirect_to( '/admin/users' ); diff --git a/templates/admin/users/read.html.ep b/templates/admin/users/read.html.ep index 149be12..37d7763 100644 --- a/templates/admin/users/read.html.ep +++ b/templates/admin/users/read.html.ep @@ -86,3 +86,28 @@ +
    +

    + Transactions +

    +
      + % for my $transaction ( $transactions->all ) { +
    • +
      +
      +
      From: <%= $transaction->buyer->name %>
      +
      To: <%= $transaction->seller->name %>
      +
      Value: <%= $transaction->value %>
      +
      Submitted At: <%= $transaction->submitted_at %>
      +
      Purchase Time: <%= $transaction->purchase_time %>
      +
      +
      +
    • + % } +
    • +
      + %= bootstrap_pagination( $c->param('page') || 1, $transactions->pager->last_page, { class => 'justify-content-center' } ); +
      +
    • +
    +
    From e7631dd90e38d401c8e621575a25b6a85496c39b Mon Sep 17 00:00:00 2001 From: Finn Date: Tue, 5 Sep 2017 13:44:58 +0100 Subject: [PATCH 46/83] Code for viewing single transaction added --- lib/Pear/LocalLoop.pm | 4 ++ .../Controller/Admin/Transactions.pm | 49 +++++++++++++++++++ templates/admin/transactions/index.html.ep | 35 +++++++++++++ templates/admin/transactions/read.html.ep | 38 ++++++++++++++ templates/layouts/admin.html.ep | 1 + 5 files changed, 127 insertions(+) create mode 100644 lib/Pear/LocalLoop/Controller/Admin/Transactions.pm create mode 100644 templates/admin/transactions/index.html.ep create mode 100644 templates/admin/transactions/read.html.ep diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index df43bf5..c4ee95e 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -185,6 +185,10 @@ sub startup { $admin_routes->get('/feedback')->to('admin-feedback#index'); $admin_routes->get('/feedback/:id')->to('admin-feedback#read'); + $admin_routes->get('/transactions')->to('admin-transactions#index'); + $admin_routes->get('/transactions/:id')->to('admin-transactions#read'); + $admin_routes->get('/transactions/:id/image')->to('admin-transactions#image'); + # my $user_routes = $r->under('/')->to('root#under'); # $user_routes->get('/home')->to('root#home'); diff --git a/lib/Pear/LocalLoop/Controller/Admin/Transactions.pm b/lib/Pear/LocalLoop/Controller/Admin/Transactions.pm new file mode 100644 index 0000000..86a168c --- /dev/null +++ b/lib/Pear/LocalLoop/Controller/Admin/Transactions.pm @@ -0,0 +1,49 @@ +package Pear::LocalLoop::Controller::Admin::Transactions; +use Mojo::Base 'Mojolicious::Controller'; + +has result_set => sub { + my $c = shift; + return $c->schema->resultset('Transaction'); +}; + +sub index { + my $c = shift; + + my $transactions = $c->result_set->search( + undef, { + page => $c->param('page') || 1, + rows => 10, + order_by => { -desc => 'submitted_at' }, + }, + ); + $c->stash( + transactions => $transactions, + ); +} + +sub read { + my $c = shift; + + my $id = $c->param('id'); + + if ( my $transaction = $c->result_set->find($id) ) { + $c->stash( transaction => $transaction ); + } else { + $c->flash( error => 'No transaction found' ); + $c->redirect_to( '/admin/transactions' ); + } +} + +sub image { + my $c = shift; + + my $id = $c->param('id'); + + my $transaction = $c->result_set->find($id); + + if ( $transaction->proof_image ) { + $c->reply->asset($c->get_file_from_uuid($transaction->proof_image)); + } +} + +1; diff --git a/templates/admin/transactions/index.html.ep b/templates/admin/transactions/index.html.ep new file mode 100644 index 0000000..191f845 --- /dev/null +++ b/templates/admin/transactions/index.html.ep @@ -0,0 +1,35 @@ +% layout 'admin'; +% title 'Transactions'; +% content_for javascript => begin +% end +% if ( my $error = flash 'error' ) { + +% } elsif ( my $success = flash 'success' ) { + +% } +
    diff --git a/templates/admin/transactions/read.html.ep b/templates/admin/transactions/read.html.ep new file mode 100644 index 0000000..a40f384 --- /dev/null +++ b/templates/admin/transactions/read.html.ep @@ -0,0 +1,38 @@ +% layout 'admin'; +% title 'Transactions'; +% content_for javascript => begin +% end +% if ( my $error = flash 'error' ) { + +% } elsif ( my $success = flash 'success' ) { + +% } +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    diff --git a/templates/layouts/admin.html.ep b/templates/layouts/admin.html.ep index e13f18f..417937d 100644 --- a/templates/layouts/admin.html.ep +++ b/templates/layouts/admin.html.ep @@ -30,6 +30,7 @@ % } -
    -
    - - +
    +

    + Transaction Details + Delete Transaction +

    +
    + +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    - +
    From edac93bef57d7f698f90b2709e3a0a9770ba9d3c Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 7 Sep 2017 13:20:53 +0100 Subject: [PATCH 66/83] Changed deletion method to POST and working test --- lib/Pear/LocalLoop.pm | 2 +- t/admin/transaction.t | 74 +++++++++++++++++++++++ templates/admin/transactions/read.html.ep | 8 ++- 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 t/admin/transaction.t diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index 7a5697c..ef7106d 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -189,7 +189,7 @@ sub startup { $admin_routes->get('/transactions')->to('admin-transactions#index'); $admin_routes->get('/transactions/:id')->to('admin-transactions#read'); $admin_routes->get('/transactions/:id/image')->to('admin-transactions#image'); - $admin_routes->get('/transactions/:id/delete')->to('admin-transactions#delete'); + $admin_routes->post('/transactions/:id/delete')->to('admin-transactions#delete'); # my $user_routes = $r->under('/')->to('root#under'); diff --git a/t/admin/transaction.t b/t/admin/transaction.t new file mode 100644 index 0000000..4419213 --- /dev/null +++ b/t/admin/transaction.t @@ -0,0 +1,74 @@ +use Mojo::Base -strict; + +use FindBin qw/ $Bin /; + +use Test::More; +use Mojo::JSON; +use Test::Pear::LocalLoop; +use DateTime; + +my $framework = Test::Pear::LocalLoop->new( + etc_dir => "$Bin/../etc", +); +$framework->install_fixtures('users'); + +my $t = $framework->framework; +my $schema = $t->app->schema; + +my $start = DateTime->today->subtract( hours => 12 ); + +# create 30 days worth of data +for my $count ( 0 .. 29 ) { + my $trans_day = $start->clone->subtract( days => $count ); + + create_random_transaction( 'test1@example.com', $trans_day ); + if ( $count % 2 ) { + create_random_transaction( 'test2@example.com', $trans_day ); + } + if ( $count % 3 ) { + create_random_transaction( 'test3@example.com', $trans_day ); + } + if ( $count % 4 ) { + create_random_transaction( 'test4@example.com', $trans_day ); + } +} + +#login to admin +$t->ua->max_redirects(10); +$t->post_ok('/admin', form => { + email => 'admin@example.com', + password => 'abc123', +})->status_is(200); + +#Read valid transaction +$t->get_ok("/admin/transactions/1") + ->status_is(200)->or($framework->dump_error); + +#get stock image for valid transaction +$t->get_ok("/admin/transactions/1/image") + ->status_is(200)->or($framework->dump_error); + +#Delete valid transaction +$t->post_ok("/admin/transactions/1/delete") + ->status_is(200)->or($framework->dump_error) + ->content_like(qr/Successfully deleted transaction/); + +#Read deleted transaction +$t->get_ok("/admin/transactions/1") + ->content_like(qr/No transaction found/); + +sub create_random_transaction { + my $buyer = shift; + my $time = shift; + + my $buyer_result = $schema->resultset('User')->find({ email => $buyer })->entity; + my $seller_result = $schema->resultset('Organisation')->find({ name => 'Test Org' })->entity; + $schema->resultset('Transaction')->create({ + buyer => $buyer_result, + seller => $seller_result, + value => 10, + purchase_time => $time, + }); +} + +done_testing; diff --git a/templates/admin/transactions/read.html.ep b/templates/admin/transactions/read.html.ep index 5cbf658..9eb9596 100644 --- a/templates/admin/transactions/read.html.ep +++ b/templates/admin/transactions/read.html.ep @@ -14,10 +14,14 @@

    Transaction Details - Delete Transaction +
    +
    + +
    +

    -
    +
    From 54c2b7f64d448e7dead97ea43e27730d5d81dfbb Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 7 Sep 2017 13:26:03 +0100 Subject: [PATCH 67/83] Added count to test --- t/admin/transaction.t | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/admin/transaction.t b/t/admin/transaction.t index 4419213..d3a3980 100644 --- a/t/admin/transaction.t +++ b/t/admin/transaction.t @@ -33,6 +33,8 @@ for my $count ( 0 .. 29 ) { } } +is $schema->resultset('Transaction')->count, 87; + #login to admin $t->ua->max_redirects(10); $t->post_ok('/admin', form => { @@ -53,6 +55,8 @@ $t->post_ok("/admin/transactions/1/delete") ->status_is(200)->or($framework->dump_error) ->content_like(qr/Successfully deleted transaction/); +is $schema->resultset('Transaction')->count, 86; + #Read deleted transaction $t->get_ok("/admin/transactions/1") ->content_like(qr/No transaction found/); From 05c20b9e8549b2bcf2d45040b28dbf7ef1e6ea07 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 7 Sep 2017 16:03:01 +0100 Subject: [PATCH 68/83] Added new snippets endpoints --- lib/Pear/LocalLoop.pm | 1 + .../Api/V1/Organisation/Snippets.pm | 61 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Snippets.pm diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index ef7106d..18e997b 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -161,6 +161,7 @@ sub startup { my $api_v1_org = $api_v1->under('/organisation')->to('api-v1-organisation#auth'); $api_v1_org->post('/graphs')->to('api-v1-organisation-graphs#index'); + $api_v1_org->post('/snippets')->to('api-v1-organisation-snippets#index'); my $admin_routes = $r->under('/admin')->to('admin#under'); diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Snippets.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Snippets.pm new file mode 100644 index 0000000..6bcd55c --- /dev/null +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Snippets.pm @@ -0,0 +1,61 @@ +package Pear::LocalLoop::Controller::Api::V1::Organisation::Snippets; +use Mojo::Base 'Mojolicious::Controller'; + +sub index { + my $c = shift; + + my $entity = $c->stash->{api_user}->entity; + my $data = { + this_month_sales_count => 0, + this_month_sales_total => 0, + this_month_purchases_count => 0, + this_month_purchases_total => 0, + this_week_sales_count => 0, + this_week_sales_total => 0, + this_week_purchases_count => 0, + this_week_purchases_total => 0, + today_sales_count => 0, + today_sales_total => 0, + today_purchases_count => 0, + today_purchases_total => 0, + }; + + my $now = DateTime->now; + my $today = DateTime->today; + my $week_ago = $today->clone->subtract( days => 7 ); + my $month_ago = $today->clone->subtract( days => 30 ); + + my $today_sales = $entity->sales->search_between( $today, $now ); + $data->{ today_sales_count } = $today_sales->count; + $data->{ today_sales_total } = $today_sales->get_column('value')->sum || 0; + + my $week_sales = $entity->sales->search_between( $week_ago, $today ); + $data->{ this_week_sales_count } = $week_sales->count; + $data->{ this_week_sales_total } = $week_sales->get_column('value')->sum || 0; + + my $month_sales = $entity->sales->search_between( $month_ago, $today ); + $data->{ this_month_sales_count } = $month_sales->count; + $data->{ this_month_sales_total } = $month_sales->get_column('value')->sum || 0; + + my $today_purchases = $entity->purchases->search_between( $today, $now ); + $data->{ today_purchases_count } = $today_purchases->count; + $data->{ today_purchases_total } = $today_purchases->get_column('value')->sum || 0; + + my $week_purchases = $entity->purchases->search_between( $week_ago, $today ); + $data->{ this_week_purchases_count } = $week_purchases->count; + $data->{ this_week_purchases_total } = $week_purchases->get_column('value')->sum || 0; + + my $month_purchases = $entity->purchases->search_between( $month_ago, $today ); + $data->{ this_month_purchases_count } = $month_purchases->count; + $data->{ this_month_purchases_total } = $month_purchases->get_column('value')->sum || 0; + + return $c->render( + json => { + success => Mojo::JSON->true, + snippets => $data, + } + ); + +} + +1; From ff466b4ed8ed04bf80287e76cff6593356fc4aff Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 7 Sep 2017 16:06:51 +0100 Subject: [PATCH 69/83] Added basic test for snippets data --- t/api/v1/organisation/snippets.t | 82 ++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 t/api/v1/organisation/snippets.t diff --git a/t/api/v1/organisation/snippets.t b/t/api/v1/organisation/snippets.t new file mode 100644 index 0000000..b7e127a --- /dev/null +++ b/t/api/v1/organisation/snippets.t @@ -0,0 +1,82 @@ +use Mojo::Base -strict; + +use FindBin qw/ $Bin /; + +use Test::More; +use Mojo::JSON; +use Test::Pear::LocalLoop; +use DateTime; + +my $framework = Test::Pear::LocalLoop->new( + etc_dir => "$Bin/../../../etc", +); +$framework->install_fixtures('users'); + +my $t = $framework->framework; +my $schema = $t->app->schema; + +my $start = DateTime->today->subtract( hours => 12 ); + +# create 30 days worth of data +for my $count ( 0 .. 29 ) { + my $trans_day = $start->clone->subtract( days => $count ); + + create_random_transaction( 'test1@example.com', $trans_day ); + if ( $count % 2 ) { + create_random_transaction( 'test2@example.com', $trans_day ); + } + if ( $count % 3 ) { + create_random_transaction( 'test3@example.com', $trans_day ); + } + if ( $count % 4 ) { + create_random_transaction( 'test4@example.com', $trans_day ); + } +} + +my $session_key = $framework->login({ + email => 'org@example.com', + password => 'abc123', +}); + +$t->post_ok('/api/v1/organisation/snippets' => json => { + session_key => $session_key, + }) + ->status_is(200)->or($framework->dump_error) + ->json_is('/snippets', { + this_month_sales_count => 87, + this_month_sales_total => 870, + this_week_sales_count => 19, + this_week_sales_total => 190, + today_sales_count => 0, + today_sales_total => 0, + this_month_purchases_count => 0, + this_month_purchases_total => 0, + this_week_purchases_count => 0, + this_week_purchases_total => 0, + today_purchases_count => 0, + today_purchases_total => 0, + }); + +$framework->logout( $session_key ); + +$session_key = $framework->login({ + email => 'test1@example.com', + password => 'abc123', +}); + +sub create_random_transaction { + my $buyer = shift; + my $time = shift; + + my $buyer_result = $schema->resultset('User')->find({ email => $buyer })->entity; + my $seller_result = $schema->resultset('Organisation')->find({ name => 'Test Org' })->entity; + $schema->resultset('Transaction')->create({ + buyer => $buyer_result, + seller => $seller_result, + value => 10, + proof_image => 'a', + purchase_time => $time, + }); +} + +done_testing; From d5dd71661d47f8aa6c9864762a46171eb110057e Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 7 Sep 2017 16:18:26 +0100 Subject: [PATCH 70/83] Fix inflation of booleans from database --- lib/Pear/LocalLoop/Schema/Result/Organisation.pm | 11 ----------- lib/Pear/LocalLoop/Schema/Result/User.pm | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/lib/Pear/LocalLoop/Schema/Result/Organisation.pm b/lib/Pear/LocalLoop/Schema/Result/Organisation.pm index aa75de7..7f6c4c8 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Organisation.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Organisation.pm @@ -70,7 +70,6 @@ __PACKAGE__->belongs_to( __PACKAGE__->filter_column( pending => { filter_to_storage => 'to_bool', - filter_from_storage => 'from_bool', }); # Only works when calling ->deploy, but atleast helps for tests @@ -94,14 +93,4 @@ sub to_bool { } } -sub from_bool { - my ( $self, $val ) = @_; - my $driver_name = $self->result_source->schema->storage->dbh->{Driver}->{Name}; - if ( $driver_name eq 'SQLite' ) { - return $val; - } else { - return lc $val eq 'true' ? 1 : 0; - } -} - 1; diff --git a/lib/Pear/LocalLoop/Schema/Result/User.pm b/lib/Pear/LocalLoop/Schema/Result/User.pm index b5457c5..162fa12 100644 --- a/lib/Pear/LocalLoop/Schema/Result/User.pm +++ b/lib/Pear/LocalLoop/Schema/Result/User.pm @@ -90,7 +90,6 @@ sub sqlt_deploy_hook { __PACKAGE__->filter_column( is_admin => { filter_to_storage => 'to_bool', - filter_from_storage => 'from_bool', }); sub to_bool { @@ -103,16 +102,6 @@ sub to_bool { } } -sub from_bool { - my ( $self, $val ) = @_; - my $driver_name = $self->result_source->schema->storage->dbh->{Driver}->{Name}; - if ( $driver_name eq 'SQLite' ) { - return $val; - } else { - return lc $val eq 'true' ? 1 : 0; - } -} - sub generate_session { my $self = shift; From e13a12817963b617ea4566635a298d79471cf648 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 8 Sep 2017 12:00:23 +0100 Subject: [PATCH 71/83] Add zero to sums to force number on output --- lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm index 5b4eab5..24e4263 100644 --- a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm @@ -102,7 +102,7 @@ sub _sales_last_duration { my $transactions = $entity->sales ->search_between( $start, $next_end ) ->get_column('value') - ->sum || 0; + ->sum + 0; push @{ $data->{ labels } }, $start->day_name; push @{ $data->{ data } }, $transactions; $start->add( days => 1 ); @@ -134,7 +134,7 @@ sub _purchases_last_duration { my $transactions = $entity->purchases ->search_between( $start, $next_end ) ->get_column('value') - ->sum || 0; + ->sum + 0; push @{ $data->{ labels } }, $start->day_name; push @{ $data->{ data } }, $transactions; $start->add( days => 1 ); From 83094f380db9a3ab4589796ea577e46b3080270d Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 8 Sep 2017 12:03:48 +0100 Subject: [PATCH 72/83] Continue defaulting to 0 as well --- lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm index 24e4263..b414152 100644 --- a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm @@ -102,7 +102,7 @@ sub _sales_last_duration { my $transactions = $entity->sales ->search_between( $start, $next_end ) ->get_column('value') - ->sum + 0; + ->sum || 0 + 0; push @{ $data->{ labels } }, $start->day_name; push @{ $data->{ data } }, $transactions; $start->add( days => 1 ); @@ -134,7 +134,7 @@ sub _purchases_last_duration { my $transactions = $entity->purchases ->search_between( $start, $next_end ) ->get_column('value') - ->sum + 0; + ->sum || 0 + 0; push @{ $data->{ labels } }, $start->day_name; push @{ $data->{ data } }, $transactions; $start->add( days => 1 ); From 4176c61c0066f453e381723bd8e6e20bb85efd24 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 8 Sep 2017 12:46:54 +0100 Subject: [PATCH 73/83] Remove post user/day, and reformat validators --- lib/Pear/LocalLoop.pm | 11 ---- lib/Pear/LocalLoop/Controller/Api/User.pm | 16 ------ lib/Pear/LocalLoop/Plugin/Datetime.pm | 66 ++++++++++++++--------- lib/Pear/LocalLoop/Plugin/Validators.pm | 4 +- 4 files changed, 44 insertions(+), 53 deletions(-) diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index 18e997b..a1763b4 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -74,16 +74,6 @@ sub startup { } }); - $self->helper( datetime_formatter => sub { - my $c = shift; - - return DateTime::Format::Strptime->new( - pattern => '%FT%T%z', - strict => 1, - on_error => 'undef', - ); - }); - $self->helper( get_path_from_uuid => sub { my $c = shift; my $uuid = shift; @@ -150,7 +140,6 @@ sub startup { $api->post('/search')->to('api-upload#post_search'); $api->post('/user')->to('api-user#post_account'); $api->post('/user/account')->to('api-user#post_account_update'); - $api->post('/user/day')->to('api-user#post_day'); $api->post('/user-history')->to('api-user#post_user_history'); $api->post('/stats')->to('api-stats#post_index'); $api->post('/stats/leaderboard')->to('api-stats#post_leaderboards'); diff --git a/lib/Pear/LocalLoop/Controller/Api/User.pm b/lib/Pear/LocalLoop/Controller/Api/User.pm index dfe9bb3..0c0a2f3 100644 --- a/lib/Pear/LocalLoop/Controller/Api/User.pm +++ b/lib/Pear/LocalLoop/Controller/Api/User.pm @@ -39,22 +39,6 @@ has error_messages => sub { }; }; -sub post_day { - my $c = shift; - - my $validation = $c->validation; - - $validation->input( $c->stash->{api_json} ); - - $validation->optional('day')->is_iso_datetime; - - return $c->api_validation_error if $validation->has_error; - - $c->render( json => { - success => Mojo::JSON->true, - }); -} - sub post_account { my $c = shift; diff --git a/lib/Pear/LocalLoop/Plugin/Datetime.pm b/lib/Pear/LocalLoop/Plugin/Datetime.pm index a94c292..8b7495a 100644 --- a/lib/Pear/LocalLoop/Plugin/Datetime.pm +++ b/lib/Pear/LocalLoop/Plugin/Datetime.pm @@ -7,34 +7,52 @@ sub register { my ( $plugin, $app, $conf ) = @_; $app->helper( iso_datetime_parser => sub { - return DateTime::Format::Strptime->new( pattern => '%Y-%m-%dT%H:%M:%S.%3N%z' ); -}); + return DateTime::Format::Strptime->new( pattern => '%Y-%m-%dT%H:%M:%S.%3N%z' ); + }); -$app->helper( parse_iso_datetime => sub { - my ( $c, $date_string ) = @_; - return $c->iso_datetime_parser->parse_datetime( - $date_string, - ); -}); + $app->helper( iso_date_parser => sub { + return DateTime::Format::Strptime->new( pattern => '%Y-%m-%d' ); + }); -$app->helper( format_iso_datetime => sub { - my ( $c, $datetime_obj ) = @_; - return $c->iso_datetime_parser->parse_datetime( - $datetime_obj, - ); -}); + $app->helper( parse_iso_date => sub { + my ( $c, $date_string ) = @_; + return $c->iso_date_parser->parse_datetime( + $date_string, + ); + }); -$app->helper( db_datetime_parser => sub { - return shift->schema->storage->datetime_parser; -}); + $app->helper( format_iso_date => sub { + my ( $c, $datetime_obj ) = @_; + return $c->iso_date_parser->format_datetime( + $datetime_obj, + ); + }); -$app->helper( format_db_datetime => sub { - my ( $c, $datetime_obj ) = @_; - $datetime_obj->set_time_zone('UTC'); - return $c->db_datetime_parser->format_datetime( - $datetime_obj, - ); -}); + $app->helper( parse_iso_datetime => sub { + my ( $c, $date_string ) = @_; + return $c->iso_datetime_parser->parse_datetime( + $date_string, + ); + }); + + $app->helper( format_iso_datetime => sub { + my ( $c, $datetime_obj ) = @_; + return $c->iso_datetime_parser->format_datetime( + $datetime_obj, + ); + }); + + $app->helper( db_datetime_parser => sub { + return shift->schema->storage->datetime_parser; + }); + + $app->helper( format_db_datetime => sub { + my ( $c, $datetime_obj ) = @_; + $datetime_obj->set_time_zone('UTC'); + return $c->db_datetime_parser->format_datetime( + $datetime_obj, + ); + }); } diff --git a/lib/Pear/LocalLoop/Plugin/Validators.pm b/lib/Pear/LocalLoop/Plugin/Validators.pm index 33aeca2..bab1863 100644 --- a/lib/Pear/LocalLoop/Plugin/Validators.pm +++ b/lib/Pear/LocalLoop/Plugin/Validators.pm @@ -53,9 +53,9 @@ sub register { return $app->types->type($extension) eq $filetype ? undef : 1; }); - $app->validator->add_check( is_iso_datetime => sub { + $app->validator->add_check( is_iso_date => sub { my ( $validation, $name, $value ) = @_; - $value = $app->datetime_formatter->parse_datetime( $value ); + $value = $app->iso_date_parser->parse_datetime( $value ); return defined $value ? undef : 1; }); From 6de616584a2b41b33a52745c5f76daa07d8609f1 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 8 Sep 2017 12:47:20 +0100 Subject: [PATCH 74/83] Add customer_range graph --- .../Controller/Api/V1/Organisation/Graphs.pm | 35 +++++++++++++++++++ t/api/v1/organisation/graphs.t | 12 +++++++ 2 files changed, 47 insertions(+) diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm index 5b4eab5..2fb474a 100644 --- a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm @@ -22,6 +22,7 @@ sub index { sales_last_30_days purchases_last_7_days purchases_last_30_days + customers_range / ); return $c->api_validation_error if $validation->has_error; @@ -43,6 +44,40 @@ sub index { return $c->$graph_sub; } +sub graph_customers_range { + my $c = shift; + + my $validation = $c->validation; + $validation->input( $c->stash->{api_json} ); + $validation->required('start')->is_iso_date; + $validation->required('end')->is_iso_date; + + return $c->api_validation_error if $validation->has_error; + + my $entity = $c->stash->{api_user}->entity; + + my $data = { labels => [], data => [] }; + my $start = $c->parse_iso_date( $validation->param('start') ); + my $end = $c->parse_iso_date( $validation->param('end') ); + + while ( $start <= $end ) { + my $next_end = $start->clone->add( days => 1 ); + my $transactions = $entity->sales + ->search_between( $start, $next_end ) + ->count; + push @{ $data->{ labels } }, $c->format_iso_date( $start ); + push @{ $data->{ data } }, $transactions; + $start->add( days => 1 ); + } + + return $c->render( + json => { + success => Mojo::JSON->true, + graph => $data, + } + ); +} + sub graph_customers_last_7_days { my $c = shift; diff --git a/t/api/v1/organisation/graphs.t b/t/api/v1/organisation/graphs.t index 45531a6..d373aa1 100644 --- a/t/api/v1/organisation/graphs.t +++ b/t/api/v1/organisation/graphs.t @@ -78,6 +78,18 @@ $t->post_ok('/api/v1/organisation/graphs' => json => { data => [ 40, 20, 30, 30, 40, 10, 40, 30, 30, 20, 40, 20, 40, 20, 30, 30, 40, 10, 40, 30, 30, 20, 40, 20, 40, 20, 30, 30, 40, 10 ], }); +$t->post_ok('/api/v1/organisation/graphs' => json => { + session_key => $session_key, + graph => 'customers_range', + start => $start->clone->subtract( days => 8 )->ymd, + end => $start->clone->ymd, + }) + ->status_is(200)->or($framework->dump_error) + ->json_is('/graph', { + labels => [ map { $start->clone->subtract( days => $_ )->ymd } reverse ( 0 .. 8 ) ], + data => [ 2, 4, 2, 4, 2, 3, 3, 4, 1 ], + }); + $framework->logout( $session_key ); $session_key = $framework->login({ From ebfd162b0791d869175e41a118f49cc017d337d8 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 8 Sep 2017 13:12:56 +0100 Subject: [PATCH 75/83] Make leaderboard calc script executable --- script/recalc_leaderboards | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 script/recalc_leaderboards diff --git a/script/recalc_leaderboards b/script/recalc_leaderboards old mode 100644 new mode 100755 From ffd9b43231b0cfaf3f9f4eadf26700ae392ee34f Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Fri, 8 Sep 2017 13:22:49 +0100 Subject: [PATCH 76/83] Remove tests against removed endpoint --- t/api/user.t | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/t/api/user.t b/t/api/user.t index a848e95..643165a 100644 --- a/t/api/user.t +++ b/t/api/user.t @@ -32,28 +32,6 @@ my $session_key = $framework->login({ password => $password, }); -my $json_no_date = { session_key => $session_key }; -$t->post_ok('/api/user/day', json => $json_no_date) - ->status_is(200)->or($framework->dump_error) - ->json_is('/success', Mojo::JSON->true); - -my $json_invalid_date = { - session_key => $session_key, - day => 'invalid', -}; -$t->post_ok('/api/user/day', json => $json_invalid_date) - ->status_is(400)->or($framework->dump_error) - ->json_is('/success', Mojo::JSON->false) - ->json_like('/message', qr/Invalid ISO8601 Datetime/); - -my $json_valid_date = { - session_key => $session_key, - day => $t->app->datetime_formatter->format_datetime(DateTime->now), -}; -$t->post_ok('/api/user/day', json => $json_valid_date) - ->status_is(200)->or($framework->dump_error) - ->json_is('/success', Mojo::JSON->true); - $t->post_ok('/api/user', json => { session_key => $session_key }) ->status_is(200)->or($framework->dump_error) ->json_is({ From 1c7c59687a24240d0efe1a8b4fc086d9d4c6d3e4 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Wed, 13 Sep 2017 15:23:01 +0100 Subject: [PATCH 77/83] Fix organisation boolean in test --- t/admin/organisation.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/admin/organisation.t b/t/admin/organisation.t index cfc906b..32f066d 100644 --- a/t/admin/organisation.t +++ b/t/admin/organisation.t @@ -30,7 +30,7 @@ my $pending_entity = $schema->resultset('Entity')->create({ town => 'Midgar', sector => 'A', postcode => 'WC1E 6AD', - pending => \"1", + pending => 1, }, type => "organisation", }); From 51e1c4d7d4dde82471651ffa088d0be56c43e8aa Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Wed, 13 Sep 2017 15:23:23 +0100 Subject: [PATCH 78/83] Change to using Moo for Test framework, and allow for PG testing --- lib/Test/Pear/LocalLoop.pm | 99 +++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 12 deletions(-) diff --git a/lib/Test/Pear/LocalLoop.pm b/lib/Test/Pear/LocalLoop.pm index 5fb6fae..961fd55 100644 --- a/lib/Test/Pear/LocalLoop.pm +++ b/lib/Test/Pear/LocalLoop.pm @@ -1,5 +1,5 @@ package Test::Pear::LocalLoop; -use Mojo::Base -base; +use Moo; use Test::More; use File::Temp; @@ -7,12 +7,48 @@ use Test::Mojo; use DateTime::Format::Strptime; use DBIx::Class::Fixtures; -has config => sub { +# Conditionally require Test::PostgreSQL +sub BUILD { + if ( $ENV{PEAR_TEST_PG} ) { + require Test::PostgreSQL + or die "you need Test::PostgreSQL to run PG testing"; + Test::PostgreSQL->import; + } +} + +sub DEMOLISH { + my ( $self, $in_global_destruction ) = @_; + + if ( $ENV{PEAR_TEST_PG} && !$in_global_destruction ) { + $self->mojo->app->schema->storage->dbh->disconnect; + $self->pg->stop; + } +} + +has pg => ( + is => 'lazy', + builder => sub { + return Test::PostgreSQL->new(); + }, +); + +has config => ( + is => 'lazy', + builder => sub { + my $self = shift; my $file = File::Temp->new; - print $file <<'END'; + my $dsn; + + if ( $ENV{PEAR_TEST_PG} ) { + $dsn = $self->pg->dsn; + } else { + $dsn = "dbi:SQLite::memory:"; + } + + print $file <<"END"; { - dsn => "dbi:SQLite::memory:", + dsn => "$dsn", user => undef, pass => undef, } @@ -20,9 +56,12 @@ END $file->seek( 0, SEEK_END ); return $file; -}; + }, +); -has mojo => sub { +has mojo => ( + is => 'lazy', + builder => sub { my $self = shift; $ENV{MOJO_CONFIG} = $self->config->filename; @@ -31,9 +70,18 @@ has mojo => sub { $t->app->schema->deploy; return $t; -}; + }, +); -has _deployed => sub { 0 }; +has etc_dir => ( + is => 'lazy', + builder => sub { die "etc dir not set" }, +); + +has _deployed => ( + is => 'rwp', + default => 0, +); sub framework { my $self = shift; @@ -56,13 +104,11 @@ sub framework { ]); } - $self->_deployed(1); + $self->_set__deployed(1); return $t; }; -has etc_dir => sub { die "etc dir not set" }; - sub dump_error { return sub { my $self = shift; @@ -142,11 +188,40 @@ sub install_fixtures { }); my $t = $self->framework(1); + my $schema = $t->app->schema; + $fixtures->populate({ directory => File::Spec->catdir( $self->etc_dir, 'fixtures', 'data', $fixture_name ), no_deploy => 1, - schema => $t->app->schema, + schema => $schema, }); + + # Reset table id sequences + if ( $ENV{PEAR_TEST_PG} ) { + $schema->storage->dbh_do( + sub { + my ( $storage, $dbh, $sets ) = @_; + for my $table ( keys %$sets ) { + my $seq = $sets->{$table}; + $dbh->do( + qq/ + SELECT setval( + '$seq', + COALESCE( + (SELECT MAX(id)+1 FROM $table), + 1 + ), + false + ); + /); + } + }, + { + entities => 'entities_id_seq', + organisations => 'organisations_id_seq', + } + ); + } } 1; From b34e47215ab376ca3e74865312a2c6eefec3a4c6 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Wed, 13 Sep 2017 15:23:48 +0100 Subject: [PATCH 79/83] Change to using straight numeric columns with no decimal points --- lib/Pear/LocalLoop/Schema.pm | 2 +- lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm | 4 ++-- lib/Pear/LocalLoop/Schema/Result/Transaction.pm | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Pear/LocalLoop/Schema.pm b/lib/Pear/LocalLoop/Schema.pm index b4bf402..a7fc365 100644 --- a/lib/Pear/LocalLoop/Schema.pm +++ b/lib/Pear/LocalLoop/Schema.pm @@ -6,7 +6,7 @@ use warnings; use base 'DBIx::Class::Schema'; -our $VERSION = 6; +our $VERSION = 7; __PACKAGE__->load_namespaces; diff --git a/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm b/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm index 1125c35..8c08f1c 100644 --- a/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm +++ b/lib/Pear/LocalLoop/Schema/Result/LeaderboardValue.pm @@ -28,8 +28,8 @@ __PACKAGE__->add_columns( is_nullable => 0, }, "value" => { - data_type => "decimal", - size => [ 16, 2 ], + data_type => "numeric", + size => [ 100, 0 ], is_nullable => 0, }, "trend" => { diff --git a/lib/Pear/LocalLoop/Schema/Result/Transaction.pm b/lib/Pear/LocalLoop/Schema/Result/Transaction.pm index 13654be..efdac85 100644 --- a/lib/Pear/LocalLoop/Schema/Result/Transaction.pm +++ b/lib/Pear/LocalLoop/Schema/Result/Transaction.pm @@ -29,8 +29,8 @@ __PACKAGE__->add_columns( is_nullable => 0, }, "value" => { - data_type => "decimal", - size => [ 16, 2 ], + data_type => "numeric", + size => [ 100, 0 ], is_nullable => 0, }, "proof_image" => { From c2cc0006bc32a0c319adb200b8ffb83fed5d1f24 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Wed, 13 Sep 2017 15:32:56 +0100 Subject: [PATCH 80/83] Finished upgrade script for value * 1000 --- .../deploy/7/001-auto-__VERSION.sql | 18 + share/ddl/PostgreSQL/deploy/7/001-auto.sql | 210 ++++ share/ddl/PostgreSQL/upgrade/6-7/001-auto.sql | 15 + .../SQLite/deploy/7/001-auto-__VERSION.sql | 18 + share/ddl/SQLite/deploy/7/001-auto.sql | 145 +++ share/ddl/SQLite/upgrade/6-7/001-auto.sql | 98 ++ .../_source/deploy/7/001-auto-__VERSION.yml | 91 ++ share/ddl/_source/deploy/7/001-auto.yml | 1064 +++++++++++++++++ 8 files changed, 1659 insertions(+) create mode 100644 share/ddl/PostgreSQL/deploy/7/001-auto-__VERSION.sql create mode 100644 share/ddl/PostgreSQL/deploy/7/001-auto.sql create mode 100644 share/ddl/PostgreSQL/upgrade/6-7/001-auto.sql create mode 100644 share/ddl/SQLite/deploy/7/001-auto-__VERSION.sql create mode 100644 share/ddl/SQLite/deploy/7/001-auto.sql create mode 100644 share/ddl/SQLite/upgrade/6-7/001-auto.sql create mode 100644 share/ddl/_source/deploy/7/001-auto-__VERSION.yml create mode 100644 share/ddl/_source/deploy/7/001-auto.yml diff --git a/share/ddl/PostgreSQL/deploy/7/001-auto-__VERSION.sql b/share/ddl/PostgreSQL/deploy/7/001-auto-__VERSION.sql new file mode 100644 index 0000000..3a15182 --- /dev/null +++ b/share/ddl/PostgreSQL/deploy/7/001-auto-__VERSION.sql @@ -0,0 +1,18 @@ +-- +-- Created by SQL::Translator::Producer::PostgreSQL +-- Created on Wed Sep 13 15:24:20 2017 +-- +; +-- +-- Table: dbix_class_deploymenthandler_versions +-- +CREATE TABLE "dbix_class_deploymenthandler_versions" ( + "id" serial NOT NULL, + "version" character varying(50) NOT NULL, + "ddl" text, + "upgrade_sql" text, + PRIMARY KEY ("id"), + CONSTRAINT "dbix_class_deploymenthandler_versions_version" UNIQUE ("version") +); + +; diff --git a/share/ddl/PostgreSQL/deploy/7/001-auto.sql b/share/ddl/PostgreSQL/deploy/7/001-auto.sql new file mode 100644 index 0000000..46d7045 --- /dev/null +++ b/share/ddl/PostgreSQL/deploy/7/001-auto.sql @@ -0,0 +1,210 @@ +-- +-- Created by SQL::Translator::Producer::PostgreSQL +-- Created on Wed Sep 13 15:24:20 2017 +-- +; +-- +-- Table: account_tokens +-- +CREATE TABLE "account_tokens" ( + "id" serial NOT NULL, + "name" text NOT NULL, + "used" integer DEFAULT 0 NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "account_tokens_name" UNIQUE ("name") +); + +; +-- +-- Table: entities +-- +CREATE TABLE "entities" ( + "id" serial NOT NULL, + "type" character varying(255) NOT NULL, + PRIMARY KEY ("id") +); + +; +-- +-- Table: leaderboards +-- +CREATE TABLE "leaderboards" ( + "id" serial NOT NULL, + "name" character varying(255) NOT NULL, + "type" character varying(255) NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "leaderboards_type" UNIQUE ("type") +); + +; +-- +-- Table: customers +-- +CREATE TABLE "customers" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "display_name" character varying(255) NOT NULL, + "full_name" character varying(255) NOT NULL, + "year_of_birth" integer NOT NULL, + "postcode" character varying(16) NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "customers_idx_entity_id" on "customers" ("entity_id"); + +; +-- +-- Table: leaderboard_sets +-- +CREATE TABLE "leaderboard_sets" ( + "id" serial NOT NULL, + "leaderboard_id" integer NOT NULL, + "date" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "leaderboard_sets_idx_leaderboard_id" on "leaderboard_sets" ("leaderboard_id"); + +; +-- +-- Table: organisations +-- +CREATE TABLE "organisations" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "name" character varying(255) NOT NULL, + "street_name" text, + "town" character varying(255) NOT NULL, + "postcode" character varying(16), + "country" character varying(255), + "sector" character varying(1), + "pending" boolean DEFAULT false NOT NULL, + "submitted_by_id" integer, + PRIMARY KEY ("id") +); +CREATE INDEX "organisations_idx_entity_id" on "organisations" ("entity_id"); + +; +-- +-- Table: transactions +-- +CREATE TABLE "transactions" ( + "id" serial NOT NULL, + "buyer_id" integer NOT NULL, + "seller_id" integer NOT NULL, + "value" numeric(100,0) NOT NULL, + "proof_image" text, + "submitted_at" timestamp NOT NULL, + "purchase_time" timestamp NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "transactions_idx_buyer_id" on "transactions" ("buyer_id"); +CREATE INDEX "transactions_idx_seller_id" on "transactions" ("seller_id"); + +; +-- +-- Table: users +-- +CREATE TABLE "users" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "email" text NOT NULL, + "join_date" timestamp NOT NULL, + "password" character varying(100) NOT NULL, + "is_admin" boolean DEFAULT false NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "users_email" UNIQUE ("email") +); +CREATE INDEX "users_idx_entity_id" on "users" ("entity_id"); + +; +-- +-- Table: feedback +-- +CREATE TABLE "feedback" ( + "id" serial NOT NULL, + "user_id" integer NOT NULL, + "submitted_at" timestamp NOT NULL, + "feedbacktext" text NOT NULL, + "app_name" character varying(255) NOT NULL, + "package_name" character varying(255) NOT NULL, + "version_code" character varying(255) NOT NULL, + "version_number" character varying(255) NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "feedback_idx_user_id" on "feedback" ("user_id"); + +; +-- +-- Table: session_tokens +-- +CREATE TABLE "session_tokens" ( + "id" serial NOT NULL, + "token" character varying(255) NOT NULL, + "user_id" integer NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "session_tokens_token" UNIQUE ("token") +); +CREATE INDEX "session_tokens_idx_user_id" on "session_tokens" ("user_id"); + +; +-- +-- Table: leaderboard_values +-- +CREATE TABLE "leaderboard_values" ( + "id" serial NOT NULL, + "entity_id" integer NOT NULL, + "set_id" integer NOT NULL, + "position" integer NOT NULL, + "value" numeric(100,0) NOT NULL, + "trend" integer DEFAULT 0 NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "leaderboard_values_entity_id_set_id" UNIQUE ("entity_id", "set_id") +); +CREATE INDEX "leaderboard_values_idx_entity_id" on "leaderboard_values" ("entity_id"); +CREATE INDEX "leaderboard_values_idx_set_id" on "leaderboard_values" ("set_id"); + +; +-- +-- Foreign Key Definitions +-- + +; +ALTER TABLE "customers" ADD CONSTRAINT "customers_fk_entity_id" FOREIGN KEY ("entity_id") + REFERENCES "entities" ("id") ON DELETE CASCADE DEFERRABLE; + +; +ALTER TABLE "leaderboard_sets" ADD CONSTRAINT "leaderboard_sets_fk_leaderboard_id" FOREIGN KEY ("leaderboard_id") + REFERENCES "leaderboards" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "organisations" ADD CONSTRAINT "organisations_fk_entity_id" FOREIGN KEY ("entity_id") + REFERENCES "entities" ("id") ON DELETE CASCADE DEFERRABLE; + +; +ALTER TABLE "transactions" ADD CONSTRAINT "transactions_fk_buyer_id" FOREIGN KEY ("buyer_id") + REFERENCES "entities" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "transactions" ADD CONSTRAINT "transactions_fk_seller_id" FOREIGN KEY ("seller_id") + REFERENCES "entities" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "users" ADD CONSTRAINT "users_fk_entity_id" FOREIGN KEY ("entity_id") + REFERENCES "entities" ("id") ON DELETE CASCADE DEFERRABLE; + +; +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_fk_user_id" FOREIGN KEY ("user_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "session_tokens" ADD CONSTRAINT "session_tokens_fk_user_id" FOREIGN KEY ("user_id") + REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "leaderboard_values" ADD CONSTRAINT "leaderboard_values_fk_entity_id" FOREIGN KEY ("entity_id") + REFERENCES "entities" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; +ALTER TABLE "leaderboard_values" ADD CONSTRAINT "leaderboard_values_fk_set_id" FOREIGN KEY ("set_id") + REFERENCES "leaderboard_sets" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +; diff --git a/share/ddl/PostgreSQL/upgrade/6-7/001-auto.sql b/share/ddl/PostgreSQL/upgrade/6-7/001-auto.sql new file mode 100644 index 0000000..4b30c31 --- /dev/null +++ b/share/ddl/PostgreSQL/upgrade/6-7/001-auto.sql @@ -0,0 +1,15 @@ +-- Convert schema 'share/ddl/_source/deploy/6/001-auto.yml' to 'share/ddl/_source/deploy/7/001-auto.yml':; + +; +BEGIN; + +; +ALTER TABLE leaderboard_values ALTER COLUMN value TYPE numeric(100,0) USING value * 100000; + +; +ALTER TABLE transactions ALTER COLUMN value TYPE numeric(100,0) USING value * 100000; + +; + +COMMIT; + diff --git a/share/ddl/SQLite/deploy/7/001-auto-__VERSION.sql b/share/ddl/SQLite/deploy/7/001-auto-__VERSION.sql new file mode 100644 index 0000000..4602862 --- /dev/null +++ b/share/ddl/SQLite/deploy/7/001-auto-__VERSION.sql @@ -0,0 +1,18 @@ +-- +-- Created by SQL::Translator::Producer::SQLite +-- Created on Wed Sep 13 15:24:20 2017 +-- + +; +BEGIN TRANSACTION; +-- +-- Table: dbix_class_deploymenthandler_versions +-- +CREATE TABLE dbix_class_deploymenthandler_versions ( + id INTEGER PRIMARY KEY NOT NULL, + version varchar(50) NOT NULL, + ddl text, + upgrade_sql text +); +CREATE UNIQUE INDEX dbix_class_deploymenthandler_versions_version ON dbix_class_deploymenthandler_versions (version); +COMMIT; diff --git a/share/ddl/SQLite/deploy/7/001-auto.sql b/share/ddl/SQLite/deploy/7/001-auto.sql new file mode 100644 index 0000000..4c8b658 --- /dev/null +++ b/share/ddl/SQLite/deploy/7/001-auto.sql @@ -0,0 +1,145 @@ +-- +-- Created by SQL::Translator::Producer::SQLite +-- Created on Wed Sep 13 15:24:20 2017 +-- + +; +BEGIN TRANSACTION; +-- +-- Table: account_tokens +-- +CREATE TABLE account_tokens ( + id INTEGER PRIMARY KEY NOT NULL, + name text NOT NULL, + used integer NOT NULL DEFAULT 0 +); +CREATE UNIQUE INDEX account_tokens_name ON account_tokens (name); +-- +-- Table: entities +-- +CREATE TABLE entities ( + id INTEGER PRIMARY KEY NOT NULL, + type varchar(255) NOT NULL +); +-- +-- Table: leaderboards +-- +CREATE TABLE leaderboards ( + id INTEGER PRIMARY KEY NOT NULL, + name varchar(255) NOT NULL, + type varchar(255) NOT NULL +); +CREATE UNIQUE INDEX leaderboards_type ON leaderboards (type); +-- +-- Table: customers +-- +CREATE TABLE customers ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + display_name varchar(255) NOT NULL, + full_name varchar(255) NOT NULL, + year_of_birth integer NOT NULL, + postcode varchar(16) NOT NULL, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE +); +CREATE INDEX customers_idx_entity_id ON customers (entity_id); +-- +-- Table: leaderboard_sets +-- +CREATE TABLE leaderboard_sets ( + id INTEGER PRIMARY KEY NOT NULL, + leaderboard_id integer NOT NULL, + date datetime NOT NULL, + FOREIGN KEY (leaderboard_id) REFERENCES leaderboards(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX leaderboard_sets_idx_leaderboard_id ON leaderboard_sets (leaderboard_id); +-- +-- Table: organisations +-- +CREATE TABLE organisations ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + name varchar(255) NOT NULL, + street_name text, + town varchar(255) NOT NULL, + postcode varchar(16), + country varchar(255), + sector varchar(1), + pending boolean NOT NULL DEFAULT false, + submitted_by_id integer, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE +); +CREATE INDEX organisations_idx_entity_id ON organisations (entity_id); +-- +-- Table: transactions +-- +CREATE TABLE transactions ( + id INTEGER PRIMARY KEY NOT NULL, + buyer_id integer NOT NULL, + seller_id integer NOT NULL, + value numeric(100,0) NOT NULL, + proof_image text, + submitted_at datetime NOT NULL, + purchase_time datetime NOT NULL, + FOREIGN KEY (buyer_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (seller_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX transactions_idx_buyer_id ON transactions (buyer_id); +CREATE INDEX transactions_idx_seller_id ON transactions (seller_id); +-- +-- Table: users +-- +CREATE TABLE users ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + email text NOT NULL, + join_date datetime NOT NULL, + password varchar(100) NOT NULL, + is_admin boolean NOT NULL DEFAULT false, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE +); +CREATE INDEX users_idx_entity_id ON users (entity_id); +CREATE UNIQUE INDEX users_email ON users (email); +-- +-- Table: feedback +-- +CREATE TABLE feedback ( + id INTEGER PRIMARY KEY NOT NULL, + user_id integer NOT NULL, + submitted_at datetime NOT NULL, + feedbacktext text NOT NULL, + app_name varchar(255) NOT NULL, + package_name varchar(255) NOT NULL, + version_code varchar(255) NOT NULL, + version_number varchar(255) NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX feedback_idx_user_id ON feedback (user_id); +-- +-- Table: session_tokens +-- +CREATE TABLE session_tokens ( + id INTEGER PRIMARY KEY NOT NULL, + token varchar(255) NOT NULL, + user_id integer NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX session_tokens_idx_user_id ON session_tokens (user_id); +CREATE UNIQUE INDEX session_tokens_token ON session_tokens (token); +-- +-- Table: leaderboard_values +-- +CREATE TABLE leaderboard_values ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + set_id integer NOT NULL, + position integer NOT NULL, + value numeric(100,0) NOT NULL, + trend integer NOT NULL DEFAULT 0, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (set_id) REFERENCES leaderboard_sets(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); +CREATE INDEX leaderboard_values_idx_entity_id ON leaderboard_values (entity_id); +CREATE INDEX leaderboard_values_idx_set_id ON leaderboard_values (set_id); +CREATE UNIQUE INDEX leaderboard_values_entity_id_set_id ON leaderboard_values (entity_id, set_id); +COMMIT; diff --git a/share/ddl/SQLite/upgrade/6-7/001-auto.sql b/share/ddl/SQLite/upgrade/6-7/001-auto.sql new file mode 100644 index 0000000..4b6298a --- /dev/null +++ b/share/ddl/SQLite/upgrade/6-7/001-auto.sql @@ -0,0 +1,98 @@ +-- Convert schema 'share/ddl/_source/deploy/6/001-auto.yml' to 'share/ddl/_source/deploy/7/001-auto.yml':; + +; +BEGIN; + +; +CREATE TEMPORARY TABLE leaderboard_values_temp_alter ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + set_id integer NOT NULL, + position integer NOT NULL, + value decimal(16,2) NOT NULL, + trend integer NOT NULL DEFAULT 0, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (set_id) REFERENCES leaderboard_sets(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); + +; +INSERT INTO leaderboard_values_temp_alter( id, entity_id, set_id, position, value, trend) SELECT id, entity_id, set_id, position, value, trend FROM leaderboard_values; + +; +DROP TABLE leaderboard_values; + +; +CREATE TABLE leaderboard_values ( + id INTEGER PRIMARY KEY NOT NULL, + entity_id integer NOT NULL, + set_id integer NOT NULL, + position integer NOT NULL, + value numeric(100,0) NOT NULL, + trend integer NOT NULL DEFAULT 0, + FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (set_id) REFERENCES leaderboard_sets(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); + +; +CREATE INDEX leaderboard_values_idx_enti00 ON leaderboard_values (entity_id); + +; +CREATE INDEX leaderboard_values_idx_set_00 ON leaderboard_values (set_id); + +; +CREATE UNIQUE INDEX leaderboard_values_entity_i00 ON leaderboard_values (entity_id, set_id); + +; +INSERT INTO leaderboard_values SELECT id, entity_id, set_id, position, value, trend FROM leaderboard_values_temp_alter; + +; +DROP TABLE leaderboard_values_temp_alter; + +; +CREATE TEMPORARY TABLE transactions_temp_alter ( + id INTEGER PRIMARY KEY NOT NULL, + buyer_id integer NOT NULL, + seller_id integer NOT NULL, + value decimal(16,2) NOT NULL, + proof_image text, + submitted_at datetime NOT NULL, + purchase_time datetime NOT NULL, + FOREIGN KEY (buyer_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (seller_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); + +; +INSERT INTO transactions_temp_alter( id, buyer_id, seller_id, value, proof_image, submitted_at, purchase_time) SELECT id, buyer_id, seller_id, value, proof_image, submitted_at, purchase_time FROM transactions; + +; +DROP TABLE transactions; + +; +CREATE TABLE transactions ( + id INTEGER PRIMARY KEY NOT NULL, + buyer_id integer NOT NULL, + seller_id integer NOT NULL, + value numeric(100,0) NOT NULL, + proof_image text, + submitted_at datetime NOT NULL, + purchase_time datetime NOT NULL, + FOREIGN KEY (buyer_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION, + FOREIGN KEY (seller_id) REFERENCES entities(id) ON DELETE NO ACTION ON UPDATE NO ACTION +); + +; +CREATE INDEX transactions_idx_buyer_id02 ON transactions (buyer_id); + +; +CREATE INDEX transactions_idx_seller_id02 ON transactions (seller_id); + +; +INSERT INTO transactions SELECT id, buyer_id, seller_id, value * 100000, proof_image, submitted_at, purchase_time FROM transactions_temp_alter; + +; +DROP TABLE transactions_temp_alter; + +; + +COMMIT; + diff --git a/share/ddl/_source/deploy/7/001-auto-__VERSION.yml b/share/ddl/_source/deploy/7/001-auto-__VERSION.yml new file mode 100644 index 0000000..907f443 --- /dev/null +++ b/share/ddl/_source/deploy/7/001-auto-__VERSION.yml @@ -0,0 +1,91 @@ +--- +schema: + procedures: {} + tables: + dbix_class_deploymenthandler_versions: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - version + match_type: '' + name: dbix_class_deploymenthandler_versions_version + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + fields: + ddl: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: ddl + order: 3 + size: + - 0 + id: + data_type: int + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + upgrade_sql: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: upgrade_sql + order: 4 + size: + - 0 + version: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: version + order: 2 + size: + - 50 + indices: [] + name: dbix_class_deploymenthandler_versions + options: [] + order: 1 + triggers: {} + views: {} +translator: + add_drop_table: 0 + filename: ~ + no_comments: 0 + parser_args: + sources: + - __VERSION + parser_type: SQL::Translator::Parser::DBIx::Class + producer_args: {} + producer_type: SQL::Translator::Producer::YAML + show_warnings: 0 + trace: 0 + version: 0.11021 diff --git a/share/ddl/_source/deploy/7/001-auto.yml b/share/ddl/_source/deploy/7/001-auto.yml new file mode 100644 index 0000000..e5c5ed6 --- /dev/null +++ b/share/ddl/_source/deploy/7/001-auto.yml @@ -0,0 +1,1064 @@ +--- +schema: + procedures: {} + tables: + account_tokens: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - name + match_type: '' + name: account_tokens_name + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + name: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: name + order: 2 + size: + - 0 + used: + data_type: integer + default_value: 0 + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: used + order: 3 + size: + - 0 + indices: [] + name: account_tokens + options: [] + order: 1 + customers: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - entity_id + match_type: '' + name: customers_fk_entity_id + on_delete: CASCADE + on_update: '' + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + fields: + display_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: display_name + order: 3 + size: + - 255 + entity_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: entity_id + order: 2 + size: + - 0 + full_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: full_name + order: 4 + size: + - 255 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + postcode: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: postcode + order: 6 + size: + - 16 + year_of_birth: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: year_of_birth + order: 5 + size: + - 0 + indices: + - fields: + - entity_id + name: customers_idx_entity_id + options: [] + type: NORMAL + name: customers + options: [] + order: 4 + entities: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + type: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: type + order: 2 + size: + - 255 + indices: [] + name: entities + options: [] + order: 2 + feedback: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - user_id + match_type: '' + name: feedback_fk_user_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + fields: + app_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: app_name + order: 5 + size: + - 255 + feedbacktext: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: feedbacktext + order: 4 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + package_name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: package_name + order: 6 + size: + - 255 + submitted_at: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: submitted_at + order: 3 + size: + - 0 + user_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: user_id + order: 2 + size: + - 0 + version_code: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: version_code + order: 7 + size: + - 255 + version_number: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: version_number + order: 8 + size: + - 255 + indices: + - fields: + - user_id + name: feedback_idx_user_id + options: [] + type: NORMAL + name: feedback + options: [] + order: 9 + leaderboard_sets: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - leaderboard_id + match_type: '' + name: leaderboard_sets_fk_leaderboard_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: leaderboards + type: FOREIGN KEY + fields: + date: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: date + order: 3 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + leaderboard_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: leaderboard_id + order: 2 + size: + - 0 + indices: + - fields: + - leaderboard_id + name: leaderboard_sets_idx_leaderboard_id + options: [] + type: NORMAL + name: leaderboard_sets + options: [] + order: 5 + leaderboard_values: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - entity_id + - set_id + match_type: '' + name: leaderboard_values_entity_id_set_id + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 0 + expression: '' + fields: + - entity_id + match_type: '' + name: leaderboard_values_fk_entity_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + - deferrable: 0 + expression: '' + fields: + - set_id + match_type: '' + name: leaderboard_values_fk_set_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: leaderboard_sets + type: FOREIGN KEY + fields: + entity_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: entity_id + order: 2 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + position: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: position + order: 4 + size: + - 0 + set_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: set_id + order: 3 + size: + - 0 + trend: + data_type: integer + default_value: 0 + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: trend + order: 6 + size: + - 0 + value: + data_type: numeric + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: value + order: 5 + size: + - 100 + - 0 + indices: + - fields: + - entity_id + name: leaderboard_values_idx_entity_id + options: [] + type: NORMAL + - fields: + - set_id + name: leaderboard_values_idx_set_id + options: [] + type: NORMAL + name: leaderboard_values + options: [] + order: 11 + leaderboards: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - type + match_type: '' + name: leaderboards_type + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: name + order: 2 + size: + - 255 + type: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: type + order: 3 + size: + - 255 + indices: [] + name: leaderboards + options: [] + order: 3 + organisations: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - entity_id + match_type: '' + name: organisations_fk_entity_id + on_delete: CASCADE + on_update: '' + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + fields: + country: + data_type: varchar + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: country + order: 7 + size: + - 255 + entity_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: entity_id + order: 2 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + name: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: name + order: 3 + size: + - 255 + pending: + data_type: boolean + default_value: !!perl/ref + =: false + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: pending + order: 9 + size: + - 0 + postcode: + data_type: varchar + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: postcode + order: 6 + size: + - 16 + sector: + data_type: varchar + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: sector + order: 8 + size: + - 1 + street_name: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: street_name + order: 4 + size: + - 0 + submitted_by_id: + data_type: integer + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: submitted_by_id + order: 10 + size: + - 0 + town: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: town + order: 5 + size: + - 255 + indices: + - fields: + - entity_id + name: organisations_idx_entity_id + options: [] + type: NORMAL + name: organisations + options: [] + order: 6 + session_tokens: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - token + match_type: '' + name: session_tokens_token + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 0 + expression: '' + fields: + - user_id + match_type: '' + name: session_tokens_fk_user_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: users + type: FOREIGN KEY + fields: + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + token: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: token + order: 2 + size: + - 255 + user_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: user_id + order: 3 + size: + - 0 + indices: + - fields: + - user_id + name: session_tokens_idx_user_id + options: [] + type: NORMAL + name: session_tokens + options: [] + order: 10 + transactions: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 0 + expression: '' + fields: + - buyer_id + match_type: '' + name: transactions_fk_buyer_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + - deferrable: 0 + expression: '' + fields: + - seller_id + match_type: '' + name: transactions_fk_seller_id + on_delete: NO ACTION + on_update: NO ACTION + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + fields: + buyer_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: buyer_id + order: 2 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + proof_image: + data_type: text + default_value: ~ + is_nullable: 1 + is_primary_key: 0 + is_unique: 0 + name: proof_image + order: 5 + size: + - 0 + purchase_time: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: purchase_time + order: 7 + size: + - 0 + seller_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: seller_id + order: 3 + size: + - 0 + submitted_at: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: submitted_at + order: 6 + size: + - 0 + value: + data_type: numeric + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: value + order: 4 + size: + - 100 + - 0 + indices: + - fields: + - buyer_id + name: transactions_idx_buyer_id + options: [] + type: NORMAL + - fields: + - seller_id + name: transactions_idx_seller_id + options: [] + type: NORMAL + name: transactions + options: [] + order: 7 + users: + constraints: + - deferrable: 1 + expression: '' + fields: + - id + match_type: '' + name: '' + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: PRIMARY KEY + - deferrable: 1 + expression: '' + fields: + - email + match_type: '' + name: users_email + on_delete: '' + on_update: '' + options: [] + reference_fields: [] + reference_table: '' + type: UNIQUE + - deferrable: 1 + expression: '' + fields: + - entity_id + match_type: '' + name: users_fk_entity_id + on_delete: CASCADE + on_update: '' + options: [] + reference_fields: + - id + reference_table: entities + type: FOREIGN KEY + fields: + email: + data_type: text + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 1 + name: email + order: 3 + size: + - 0 + entity_id: + data_type: integer + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: entity_id + order: 2 + size: + - 0 + id: + data_type: integer + default_value: ~ + is_auto_increment: 1 + is_nullable: 0 + is_primary_key: 1 + is_unique: 0 + name: id + order: 1 + size: + - 0 + is_admin: + data_type: boolean + default_value: !!perl/ref + =: false + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: is_admin + order: 6 + size: + - 0 + join_date: + data_type: datetime + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: join_date + order: 4 + size: + - 0 + password: + data_type: varchar + default_value: ~ + is_nullable: 0 + is_primary_key: 0 + is_unique: 0 + name: password + order: 5 + size: + - 100 + indices: + - fields: + - entity_id + name: users_idx_entity_id + options: [] + type: NORMAL + name: users + options: [] + order: 8 + triggers: {} + views: {} +translator: + add_drop_table: 0 + filename: ~ + no_comments: 0 + parser_args: + sources: + - AccountToken + - Customer + - Entity + - Feedback + - Leaderboard + - LeaderboardSet + - LeaderboardValue + - Organisation + - SessionToken + - Transaction + - User + parser_type: SQL::Translator::Parser::DBIx::Class + producer_args: {} + producer_type: SQL::Translator::Producer::YAML + show_warnings: 0 + trace: 0 + version: 0.11021 From 1ffabb3e944d12ffe489c3bc6ad5b76a4558a8f1 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Wed, 13 Sep 2017 16:07:23 +0100 Subject: [PATCH 81/83] Updated all endpoints to use new transaction value multiple --- lib/Pear/LocalLoop/Controller/Api/Stats.pm | 24 +++++++++++-------- .../LocalLoop/Controller/Api/Transactions.pm | 2 +- lib/Pear/LocalLoop/Controller/Api/Upload.pm | 2 +- .../Controller/Api/V1/Organisation/Graphs.pm | 4 ++-- .../Api/V1/Organisation/Snippets.pm | 6 +++++ t/api/stats.t | 14 +++++------ t/api/v1/organisation/graphs.t | 2 +- t/api/v1/organisation/snippets.t | 2 +- 8 files changed, 33 insertions(+), 23 deletions(-) diff --git a/lib/Pear/LocalLoop/Controller/Api/Stats.pm b/lib/Pear/LocalLoop/Controller/Api/Stats.pm index 3ee2617..ddb5152 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Stats.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Stats.pm @@ -18,23 +18,23 @@ sub post_index { my $user = $c->stash->{api_user}->entity; my $today_rs = $user->purchases->today_rs; - my $today_sum = $today_rs->get_column('value')->sum; + my $today_sum = $today_rs->get_column('value')->sum || 0; my $today_count = $today_rs->count; my $week_rs = $user->purchases->week_rs; - my $week_sum = $week_rs->get_column('value')->sum; + my $week_sum = $week_rs->get_column('value')->sum || 0; my $week_count = $week_rs->count; my $month_rs = $user->purchases->month_rs; - my $month_sum = $month_rs->get_column('value')->sum; + my $month_sum = $month_rs->get_column('value')->sum || 0; my $month_count = $month_rs->count; my $user_rs = $user->purchases; - my $user_sum = $user_rs->get_column('value')->sum; + my $user_sum = $user_rs->get_column('value')->sum || 0; my $user_count = $user_rs->count; my $global_rs = $c->schema->resultset('Transaction'); - my $global_sum = $global_rs->get_column('value')->sum; + my $global_sum = $global_rs->get_column('value')->sum || 0; my $global_count = $global_rs->count; my $leaderboard_rs = $c->schema->resultset('Leaderboard'); @@ -44,15 +44,15 @@ sub post_index { return $c->render( json => { success => Mojo::JSON->true, - today_sum => $today_sum || 0, + today_sum => $today_sum / 100000, today_count => $today_count, - week_sum => $week_sum || 0, + week_sum => $week_sum / 100000, week_count => $week_count, - month_sum => $month_sum || 0, + month_sum => $month_sum / 100000, month_count => $month_count, - user_sum => $user_sum || 0, + user_sum => $user_sum / 100000, user_count => $user_count, - global_sum => $global_sum || 0, + global_sum => $global_sum / 100000, global_count => $global_count, user_position => defined $current_user_position ? $current_user_position->position : 0, }); @@ -91,6 +91,10 @@ sub post_leaderboards { my @leaderboard_array = $today_values->all; + if ( $validation->param('type') =~ /total$/ ) { + map { $_->{value} / 100000 } @leaderboard_array; + } + my $current_user_position = $today_values->find({ entity_id => $c->stash->{api_user}->entity->id }); return $c->render( json => { diff --git a/lib/Pear/LocalLoop/Controller/Api/Transactions.pm b/lib/Pear/LocalLoop/Controller/Api/Transactions.pm index d205b69..79e61ad 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Transactions.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Transactions.pm @@ -34,7 +34,7 @@ sub post_transaction_list_purchases { my @transaction_list = ( map {{ seller => $_->seller->name, - value => $_->value, + value => $_->value / 100000, purchase_time => $_->purchase_time, }} $transactions->all ); diff --git a/lib/Pear/LocalLoop/Controller/Api/Upload.pm b/lib/Pear/LocalLoop/Controller/Api/Upload.pm index 7b037d5..e7ae0d5 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Upload.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Upload.pm @@ -176,7 +176,7 @@ sub post_upload { 'sales', { buyer => $user->entity, - value => $transaction_value, + value => $transaction_value * 100000, ( defined $file ? ( proof_image => $file ) : () ), purchase_time => $c->format_db_datetime($purchase_time), } diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm index c0cdd51..d1eddaf 100644 --- a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Graphs.pm @@ -139,7 +139,7 @@ sub _sales_last_duration { ->get_column('value') ->sum || 0 + 0; push @{ $data->{ labels } }, $start->day_name; - push @{ $data->{ data } }, $transactions; + push @{ $data->{ data } }, $transactions / 100000; $start->add( days => 1 ); } @@ -171,7 +171,7 @@ sub _purchases_last_duration { ->get_column('value') ->sum || 0 + 0; push @{ $data->{ labels } }, $start->day_name; - push @{ $data->{ data } }, $transactions; + push @{ $data->{ data } }, $transactions / 100000; $start->add( days => 1 ); } diff --git a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Snippets.pm b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Snippets.pm index 6bcd55c..07cd90a 100644 --- a/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Snippets.pm +++ b/lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Snippets.pm @@ -28,26 +28,32 @@ sub index { my $today_sales = $entity->sales->search_between( $today, $now ); $data->{ today_sales_count } = $today_sales->count; $data->{ today_sales_total } = $today_sales->get_column('value')->sum || 0; + $data->{ today_sales_total } /= 100000; my $week_sales = $entity->sales->search_between( $week_ago, $today ); $data->{ this_week_sales_count } = $week_sales->count; $data->{ this_week_sales_total } = $week_sales->get_column('value')->sum || 0; + $data->{ this_week_sales_total } /= 100000; my $month_sales = $entity->sales->search_between( $month_ago, $today ); $data->{ this_month_sales_count } = $month_sales->count; $data->{ this_month_sales_total } = $month_sales->get_column('value')->sum || 0; + $data->{ this_month_sales_total } /= 100000; my $today_purchases = $entity->purchases->search_between( $today, $now ); $data->{ today_purchases_count } = $today_purchases->count; $data->{ today_purchases_total } = $today_purchases->get_column('value')->sum || 0; + $data->{ today_purchases_total } /= 100000; my $week_purchases = $entity->purchases->search_between( $week_ago, $today ); $data->{ this_week_purchases_count } = $week_purchases->count; $data->{ this_week_purchases_total } = $week_purchases->get_column('value')->sum || 0; + $data->{ this_week_purchases_total } /= 100000; my $month_purchases = $entity->purchases->search_between( $month_ago, $today ); $data->{ this_month_purchases_count } = $month_purchases->count; $data->{ this_month_purchases_total } = $month_purchases->get_column('value')->sum || 0; + $data->{ this_month_purchases_total } /= 100000; return $c->render( json => { diff --git a/t/api/stats.t b/t/api/stats.t index 8e8fbc1..0484664 100644 --- a/t/api/stats.t +++ b/t/api/stats.t @@ -43,7 +43,7 @@ $t->post_ok('/api/stats' => json => { session_key => $session_key } ) for ( 1 .. 10 ) { $user_result->create_related( 'purchases', { seller_id => $org_result->id, - value => $_, + value => $_ * 100000, proof_image => 'a', }); } @@ -51,7 +51,7 @@ for ( 1 .. 10 ) { for ( 11 .. 20 ) { $user_result->create_related( 'purchases', { seller_id => $org_result->id, - value => $_, + value => $_ * 100000, proof_image => 'a', purchase_time => $dtf->format_datetime(DateTime->today()->subtract( days => 5 )), }); @@ -60,7 +60,7 @@ for ( 11 .. 20 ) { for ( 21 .. 30 ) { $user_result->create_related( 'purchases', { seller_id => $org_result->id, - value => $_, + value => $_ * 100000, proof_image => 'a', purchase_time => $dtf->format_datetime(DateTime->today()->subtract( days => 25 )), }); @@ -69,7 +69,7 @@ for ( 21 .. 30 ) { for ( 31 .. 40 ) { $user_result->create_related( 'purchases', { seller_id => $org_result->id, - value => $_, + value => $_ * 100000, proof_image => 'a', purchase_time => $dtf->format_datetime(DateTime->today()->subtract( days => 50 )), }); @@ -78,7 +78,7 @@ for ( 31 .. 40 ) { for ( 41 .. 50 ) { $org_result->create_related( 'purchases', { seller_id => $org_result->id, - value => $_, + value => $_ * 100000, proof_image => 'a', purchase_time => $dtf->format_datetime(DateTime->today()->subtract( days => 50 )), }); @@ -91,8 +91,8 @@ is $user_result->purchases->search({ $dtf->format_datetime(DateTime->today()->add( days => 1 )), ], }, -})->get_column('value')->sum, 55, 'Got correct sum'; -is $user_result->purchases->today_rs->get_column('value')->sum, 55, 'Got correct sum through rs'; +})->get_column('value')->sum, 5500000, 'Got correct sum'; +is $user_result->purchases->today_rs->get_column('value')->sum, 5500000, 'Got correct sum through rs'; $t->post_ok('/api/stats' => json => { session_key => $session_key } ) ->status_is(200) diff --git a/t/api/v1/organisation/graphs.t b/t/api/v1/organisation/graphs.t index d373aa1..57b6cbb 100644 --- a/t/api/v1/organisation/graphs.t +++ b/t/api/v1/organisation/graphs.t @@ -115,7 +115,7 @@ sub create_random_transaction { $schema->resultset('Transaction')->create({ buyer => $buyer_result, seller => $seller_result, - value => 10, + value => 10 * 100000, proof_image => 'a', purchase_time => $time, }); diff --git a/t/api/v1/organisation/snippets.t b/t/api/v1/organisation/snippets.t index b7e127a..f4ee79c 100644 --- a/t/api/v1/organisation/snippets.t +++ b/t/api/v1/organisation/snippets.t @@ -73,7 +73,7 @@ sub create_random_transaction { $schema->resultset('Transaction')->create({ buyer => $buyer_result, seller => $seller_result, - value => 10, + value => 10 * 100000, proof_image => 'a', purchase_time => $time, }); From d9127b135df8564055fc30e41b72f5a44b179707 Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 14 Sep 2017 15:20:41 +0100 Subject: [PATCH 82/83] Added changelog file --- CHANGELOG.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5c00e79 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,55 @@ +# Changelog + +# v0.9.1 + +* Change to semantic versioning +* Change database schema to use entity style model +* Added schema graphs for showing the schema layout +* **Fix:** null values on Org Graphs +* **Feature:** Org Graphs for sales and purchase data +* **Fix:** Deny organisations buying from themselves +* **Feature:** API endpoint for viewing purchases +* **Feature:** Transaction viewing in Admin interface +* **Fix:** Booleans under postgres and sqlite +* **Feature:** Organisation snippets API + +# v0.009 + +*No changes recorded* + +# v0.008.1 + +*No changes recorded* + +# v0.008 + +*No changes recorded* + +# v0.007 + +*No changes recorded* + +# v0.006 + +*No changes recorded* + +# v0.005 + +*No changes recorded* + +# v0.004 + +*No changes recorded* + +# v0.003 + +Made leaderboard cronjob scripts work correctly by using production config +instead of defaulting to development + +# v0.002 + +Release with leaderboard scripts for automatic generation of leaderboards + +# v0.001 + +First release with basic functionality. From 88bcb9b6b0678d3dd455243332b8287cfbc3460f Mon Sep 17 00:00:00 2001 From: Tom Bloor Date: Thu, 14 Sep 2017 15:21:05 +0100 Subject: [PATCH 83/83] Added section for next release to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c00e79..a90e471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +# Next Release + # v0.9.1 * Change to semantic versioning