From 23d671846375d1380d0b3b82107a7e5051022336 Mon Sep 17 00:00:00 2001 From: listlessbird <124798751+listlessbird@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:17:16 +0530 Subject: [PATCH] migrate to postgres from sqlite (#9) * feat: migrate to pgsql use googleId as primaryKey in the user table * fix: migrate futher --- web/.env.example | 2 +- web/.gitignore | 7 +- web/bun.lockb | Bin 322652 -> 323036 bytes web/compose.dev.yml | 21 + web/drizzle.config.ts | 7 +- web/migrations/0000_cheerful_blob.sql | 54 --- web/migrations/meta/0000_snapshot.json | 390 ------------------ web/migrations/meta/_journal.json | 13 - web/package.json | 14 +- .../app/(auth)/login/google/callback/route.ts | 4 +- web/src/db/db-fns.ts | 49 +-- web/src/db/db.ts | 55 +-- web/src/db/schema.ts | 109 +++-- web/src/lib/generation-service.ts | 8 +- web/src/lib/session.ts | 4 +- 15 files changed, 119 insertions(+), 618 deletions(-) create mode 100644 web/compose.dev.yml delete mode 100644 web/migrations/0000_cheerful_blob.sql delete mode 100644 web/migrations/meta/0000_snapshot.json delete mode 100644 web/migrations/meta/_journal.json diff --git a/web/.env.example b/web/.env.example index 1d6eb65..d3cde35 100644 --- a/web/.env.example +++ b/web/.env.example @@ -8,7 +8,7 @@ CF_ACCOUNT_ID= BUCKET_NAME= ASSEMBLY_API_KEY= REPLICATE_API_KEY= -D1_ID= +DB_URL= CF_ID= D1_KEY= GOOGLE_CLIENT_ID= diff --git a/web/.gitignore b/web/.gitignore index d3c6266..fdd0221 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -26,8 +26,8 @@ yarn-debug.log* yarn-error.log* # local env files -.env*.local -.env +.env* +!.env.example # vercel .vercel @@ -42,5 +42,4 @@ next-env.d.ts scratch.ts secrets.ps1 -fly.toml -.env.prod \ No newline at end of file +fly.toml \ No newline at end of file diff --git a/web/bun.lockb b/web/bun.lockb index e8b1091ab34d950302986be64d8d0ab7d15e890e..49f40646618d44221322457374c5b6ad8d4d44b0 100644 GIT binary patch delta 58839 zcmeFadz?+>-~Ye&p4l~vhJ>6FIw3h9W*9TunZblghERjaOwNXyQPT{iOq!&XE|SoZ zO413TLIfdomsqX=*T(*%s?P`DTG;QxXTuxd8gNcl#?;A_S60)v3w9CUukcMis=@n6 zP1?d~IpecOk#IG-3T^~9gcrbS$e)yU9y}9U1@GtM9C#eIDtsOLTzIgxAGdZ_n|`jJ zzMya-f%+sI05^a;!K5iXA65@?GACT0Ih96WSH>PaY5XM2z=LEu1N&zfXBGCMHpPE~ zT?w8K%Y&=wjnds`c`mGUGvP{s^86V0C1i5}PLozxiC-4Wl3i#2@h^ZAk>E^`@ zC^&VYpSF515WwSwVOW*Fd6CZ>U{#ipO$#~dGzkRG!j6OG-H90!CuNTb1Wq>g%Q*t8 zjtx!y@oR!`$#HTYCpe_s6T7KK~ZSwXdmU2NIb z9$4(|4A(07vyC})i%Bu{&18dPe3!ei&0G}Nw2o&Dt1p7suA{5ls~9hg8zm2NOx+s!<^ zdcl%3-{=3QrS>C}M!x(ARu}RI_~V=~(C6`!CXOX4aKRwIrW%%GEgvPF@_zxVl*MPA0#vFhsBtNpH|!^+(sR_R?VKX{El{ajf2i(u{5von0o z%o&|AK4VPg=<(Upvnj9rD8E&?u+rzXPMnBb5I8x~_uW<+Am3j<+Mg`ziLH;l9zRNd z1lAaiVn@-~wYk=BU=Y@reh7OD6V`(L-lqEymfbnapO&j&rJLuK$NIPcK?Q6ep;mlf zSoSm$XdYJ@=Vv&6o$sONu$Ar;Y)({#FT3%oPLsv6KO}ZgRC*v@-{QTAR{&M~ZR&zJP>gFS`YI9-D{TZ3HSkJU z<-4{cPrwbar@;-h(WMYjOKZW6;gi$+9()ALUJJ`3^Wlr&GiLjKortXl_p^L4tOm5Z z-EUAWY|XeoSr;n*8@MLC1D1#C7vX{a1WwKIE2u$6wQR&4euloV3OI6`KY%-6#jl1{ z(L8IfvlWbTkDifGFm#?jV4QwgOo2czY?boKUH(eS%b3873j{i#%dbsf4P0HzT#8N_ ze|=`))O^2HKf{%X{~A`htc;vVH%tr!5?y~9-EZS3XJ4B~u?2zK5NeR{3|n9j*3!++ zX+C!Rq)~z2@AV5jMuSvv{sO0XD`fLg|n-*3}LJGK+s z35?-Pk~wv3W}84DT5;WleowE>$eA{UCQi+qo}J9SmZCDLvU5>N;duItV!dt z$7D>sHYf9Xu9kA1zu#{_%3}A9D(4QJ`GDWg$=OpgrqR#|GvwAl;F=|Vf2U<-PE<90 ztbIAGbvK!O)nLyVq;Ij*Z{B=Ze!9&KR;^V~dzs&uZ(%Q2#8>50F>PArn84KR2~#t!4Y*gI=@hhH?Weg2_9ocGYsY7g&6^U*<&Y8n#%630m~Sl z6KM9Fr(ayim$s>yL_F*Zg&Eg*^#-~>@2~KdusZXnTdi8Hf+{pa)9dI8-?0Z^RlD2r zJ1^RK4m;@YpsQQ2!J7XsS#Gq>@Aml2>nEz4wXhW*Yq<)n{9`gRCpXW|2|VNH3l%=@ zuB_I;S^bh<>?7_@8uiEq-^$G4+vn80K=kX!;EjGGS?$g7{n)k?qCMyCO}=k#h1K}> zFZ+#|f-T=hFKsfgHGb1FCTENqpBYHSu8m!rax^dgeB2xNg2GLHpm6+Vf0JttHy}Yx zSQ*M-Ra8EK7=%%5G+nm(4Ih&|)!SA!W2@IW<1=!y@JfI3HN?J^@>R|RSQBgrd^TMB z4K;x6e+vP*c}t1EEYIEU4^y9QzCL!+WX`~Wz;AE*J&&NPq9?G$*G`(+JTrs6!loZ= z_1t(Qd_%*uLQK@avR#+4DKAZlwZTMe3 znt`A(z6MrBm&3}KyUW+>V=Lq7uqxV5%hj_FV5L8@+wbufYf_)4{G!_CLw4 z#Qq&V(vlMPkLdgIXsz z>#;gvo#qZ|o8bI})eS4GxL`NeNs5Hu#V5nv_@tz8Gp4&Ax}DH4Puo`>N}^@yp5~T^ zz9n>}r*&n7uJA$&2n|#yc)%@6j)a3ulKyUSa#A>r(4}5zF`-@x1&_EzDUoms7E&9x zv{h308bViiG0(fHkw~}}b2i<}Gl|exH!YGB{)muY;(5&ct2`~A&_FL;DWR)9?GpA= zS`z6TOTd@*5c0By8rO5TcTEY;V&@q_>dvtQGQ4bE&WSejE<%@ijVW`T?h&UE3o!-v zh1~hL?`pTGdnEXTThcue3Z3g#>X8y0m)#^Wg@!s8=NHG@>jwzHL&lpX>CF z1Q)oee3rOHy@@-I)2eBwU*%j%vGuEtK2=*IU*3Z*u7@938j>e(oj0{3AH3gj60}jf>Z4hW?i|p3`;A+kNf05#5HVQG3B*b zeWEFYPrIoDBjF=xiEe3|q)>7Tcl*GU@az^EveJP`P8DkGPjY5S+l25itZSu*UnP{T zP^fk*x6)-P!2xdSWfA8ooPQa~gYM2Y3C=039?`m-QLTMX_-%Xy>#z2IgQay&9OKpy z$EZ>-WpKRfToDPba#Q*I%`Lhj5=v_8ZoeYM$!!}QYz8s-ikmt(5}e=`4URbPw_`ZG z0(Pb)IQ856Rndopgy0M}^~y+iw=_+{P=gNc9c@#bu~e_N#(Fd2Ni2;TyAX?`%q2@gt?a~Z(kJ#OmIi1RwO z20m6E4IXn#hDJhdI=YpHrO^1)VG(CXNA@8S1l?*q5<=ZOx!Z@OgkNNIQ{B>GN#W{A zfk1DCg2UXRt0K-yY&jB__elsJpiS3l0fi?e2LdA%a!Lu=0@D*?S@Z02Bv?)8GA}`p z#Wj#3{6QXsg=^WcetQ-t#$jkA zy~a5oU};!Fu7;&@cRwMu_D%>d!%A^W(~`mt#rN=vN+YDX8gvJxC&poDYK7ct`0a%2 zWJH`uPk%^gOos$#4we>y=UHcqwaCn({sT)sU<2!);9Q2owD*xOBO!b@7N?4=&anip zbc;uKj_vI&XBu;-7b;Fk3P)(krCwi)%R_qzv9;mjnyHM6SH8M79!ryh^4lc@-*QXF zL_(GNx|K3h!o6`oAGdg9=U4)qWXQXpkUu_*pwqD*p7io+JU3vCa^uH$j>V1aD7gA6 zLY!4-(rQ9plbn-;z5`t%?9`b5th@#w_|m7OIs&7U712E?{sf4+70p-C;MCXgwU8lZl&HS zp=BO9pAzTSymLchEQ9LJq0q3)+`c6d*?vbqBGgM$!a3*i=ro$2lNg7gwaHLM62ebl zb@37%BgBSN=y(I(_zFMO>E3F+)mo0Xjr}Z3yKuO}V4bUr+a@_#gfut3s|jZ#mRiV_ z25bGeQenlqgYfRfjFo&%&+yGHSgbEvBkNTx_8#qA0~3NjyG4^C&fp>b)Zi@CH6i$b z>r9R~pQ351GF}}M!e^&zMAO(-uOpPA_5@eB&XkCA_E2u#kj|gdJ*?%`6};0enGy-_ zL1Xeh**R{QUp4M$Hyq~{O^rA!(EMsN#9v~mi>Iq0&LvkxhfDJ;3#*HpwxV+!0e@+z zKPM%V#qm~b^6=;a8${gQSc+qgut)B2OQuDf3r0kD2XBwW(k${uD!9xonjUe!MAInw zOZKdhexq5W__-Gr8>xC*n&=i?A93QZ4g}hwRCDLINeJg+ahC0qV!p(eBOrzAKrqx^yA z@^f`!9EO~Ex=~_-!UqVo(;^Sm$#J*eniA&M z_CQa!_@2&g>g-7Py&H6HOT&%l=IVkv{+1+X2_dG8b>m4l^|na3U!EFHZ15Sk1p5H? zFwgyw8QPlDZc7R;C)C+<(q2NIlftcUlsD35CxvGcVmNW(RzjC~+GRHd0%N>VUL@qt z(KGT{$zJV)-cO9hxYjGY(#xdBU)*td>hb;Dw7U^;dzpmNWu_CaDC z#-)A~9{(6?syB?Oh3*}5Q z(Js6}$S*p6F29X>m0VBAPxmn)zcHQew3@S+5a(Q5*Fmhl8cwHmvA?0w26o|0*SR+m zdZ^gDT>X|Pf53K5OK=+B<&O%x=k1BEvmoM3p64&Y)3n!ycVM;iij2M6Ps!PbJ@PWF zPE^d+!G*@1Sgo+U^N6z%t827T&PgntwK<19oftQt$&XRhJ3Fj)Q%%IFe-CpE%|FTC zf~DqRQ14K`fuKAhkdrzbi62>B;m zPC`x*mP+6Z$CcFkSaN`WN^fW?Zr{VO{9bU8?c8VM{L7~u*5V{i+{Fw18n}dAn;3`T zFF|?#0jvlW5H%)&UC?wT=@aA14NlLYRu8z9mg_5J z>hg&5^#kQDACeG0XGtJ%iFb!$93ieZxCniLPzS%}^n~!YzMhceG=0z?2CiOk={T&u zUfXq|dEHHYIO3!%^>=jI%oKP4OCIvSM}1-~&Y28#*JZleVmdiF3VD6fRmP)O@`!g` z6F!dRo&KCo%cH%c?%-6n=+Q{tp6i^$~fsE%MRpGamD6@I1@imXehbXBJuuzqO=z##&6^feGQy zu=u{ly{M^f$rBOh%qQ5%{j~IR7S`Z$>tn3Tuowlt81;D4-$B?j9!=nDsPk0BIfXWe z6!@In1D^8R;_WKIBDaJTpP;GF+{7Wp*{k>h>XkVE(FEseEH3;t^VxSX?5d~Hr?7N_ zXRWtRjC4xf^Gqb%=^0JoG`@i364F4>C7k^+)}>f_Zh0JE{QY?uagZ8?_mvEI`X z(3CsA+xdky;p_2f~5}l-}!%XOP-B{+CAr1dM+h6$xVHZn<3Bn$-M-j z!1M0*=Te*z&--V2e>Jbi8b}$;KpNt_;Lltd!p)WeSX{ugP72-ff_q1LinGB+`jKgRZZEbiZR%hMg3Lo%8Z1j!R`-S7LVF}JvSRMS*xsx$}y}SL1 z6z7fdB$}Fmm!eC2eqLf6hSK|Ua-y|pHY4#8mcOsaI&Q7gytob<{6hSc6R}i^e`jVj zmfu6hEL^5IZ@)W#WArNd`itvq zia0%Aj!spzs~Ah`f*#CB;9g(}DXP5UuN=N#Jec4N!Sef9joTQ-ZqdsTrF%Kzbb8gl zO!0^C9jxBvExLO%S7qha39QS@t^Qm5^NiQ_@O&)JGwj?u33VbB-+7rJmACRQov+bd~ zDbA2L{0`80x>o9@ZjA)rcZ;@0oaWo3r#!9o94uANdZuA(ur#UsCAJT%n`d!?an64$ zI-N)v9wE!?njxeCV%aQBaNfo8%iFmr!HIvnJWqatGYm_+0BeSoun{ZWv*v%47*|?; zQ-RfX4HjRudL%i|5W2!oL;>f%kuR{c%hGiA=gfE2Jl&~Xs!(|e zyRoz(QbImot#;^AQTMsV5%SYNO33ek%Gr;_ZT%rhPRpJC$Ws3D1ZNu7mC@Rq?SEM~ zvEh5szNm!pSi{}o_DRk*LVkN0l5myxRX20jNhf4o#+U#5u=wsp`tJ$(J(_=Wg45-L z^4z%pHY_&Y9!cSs33+{R;y#SdW}SIcuzb($WNAN))tB}%9Uf1N`^ewua02`LAS{hC zm$Wx01YI}vortpwt+VeY#^&Q_Ntz;ASn{C1FFb>#tY2i=ws{KzeOKjO69>uYrC%7pMe zvb;;j4+stP3uQJZd={PC8uZy%+&1Owc!}$L7zthcxw}0rC4A@S^w;yjkA#?4%<#Ei zl(&?O2eG&SU*9=ypFaXzitb62(T=M8e!Ks{dK2q%uNm{_COCD!EcYQEos4Biof-EM z7Pl>!S#kRVfsW*1ciX}V9;*iy=N()y8_SLY-P(htiNI+SA2<3c+9*w&nOOeH8Z?`) zz*zoF&?)|REW0+zmwq5xrZ%Ie-J(w-x^Ck0ZP(co37>XQt517d7ea|%Z@7wgakrne+tbGprt7oMz>Dhx~m0nS2=5C_mq@gz!eJ_PSdeJnE+IjW{O{S7?oM z`8RyoCM~|0qP97oMVxQZlF|5rwkaXh zvijd~(4Fvot)@Xzq}Wtq<>~^YcLh4cG487GYq@WK->^VRZ+&oxm6Guagh86MFNJmd z9jgX5Oz)`3Drb<@#Y%U%wJUIe7ctmIRODJ{sh$gybQ&M3IL^k4m9H|ahE{{MV9tj53)JF6`Rl>T zS5Tio6#~ty&>YqwR)UtW3T$KbzhgzUweeyF+wq}%304=YypGlu)3Aa-iWMrdGIsS7 z1bV@;Q(-0TYwa{x1rCt-FMI~}RW{v7o9-Go9$gOtDc>YGPUC+A0lDZVSQTkWD8Wq2 zx4=5Yioeb3b6~mnE^FTn*TG&1YwvspmaCtKb%>RJEv))B!aoM=384F<8J!3 zmN%peu$28i8(Wc;>wa_%^>Q2kcdTYVV&f~a%6-)86O2S7r!N)Lvf!&t(z)JXq zwZDYriNn_Z7FPc6t$oDukFY%VtF@0?{>|#AtQ{yIw|a0Ite#bYwLH&()$;mqCAhJ* zo4{IStzrHG?X2G3atBzes;jlT!8*h$zc1Vgz7diE5l}Mzh?POSOu3@ehXFs zZ(I8vn7_bIYrhZ6qq{AC4y)Y#mJeFJ435#3e#A!n>Srh@%vOfy%H7 ztjdQxb~dbi=sZ}SYYaDm2UxxVR=ydq($9p|fmvd0>9^R3J7Bf^ZulbjMOX!HfK|bp zu*QA|%wOOGK9v3wSRVS+@@KFr+z0a)IG_*9s`zVc?YMuysmgfDCJ-waQmy|9D}5~S zN*4!v9#MKX{`XprijViKKZmytk{~C&$e94#{V5F z>Kq#{R{r|1TG+ta4Q;$w`gvAAKdh1GAW+U)AI+>SR)LpTTP(Xdtb$s?8q5whz9P#d z9c}tfmXmC{zu2!oAVG0m_)x*!Y(lXL>ItjjURD>&?rrUgto*4~ugFT@*XHYIFurdy@@nY%2to{GO+L))uQl^I@fP zt^V&=>F*_-$}u**7`-5{kbrvpppB@=s$iL)FtFUli?#lqu(ntgJZWvQ@~yJASn*F= zyCMfQ|5n=wv0C`7&F~zo^|jW<{~fE~bvFLru^RM}O(#~q4YD==yntnSVzW&sR>rM1 z!)sRmudph3-KH0-oHwm4R{S>DyS6W}!giZLtORet%2;accWk`4vb*ZfTHfl}V`D$H zycbrk&tV-ES^oXfrrQszyN6*d`lGP&{RHa}s}(<6{smV0-)%fx;3fFO3Kdxeow6DK zwDIC<=x0)j;?IIxz+GS^=?SaCUa$_a?A|uMzt#U@`mY3OHbSg|E`ued^P%{mmWRRe zzzA!Pgms8D(?-F{Ki0-)ShFR=O|SVw&YlyHgVd#sIMXSGL?M<-qzijQzmS3~{rseIhrqp|%9My&DhhtVwMn1~>R7I8 zGyEN^;B#%d2Jl(ftzlKv)^dATyJ8BgawBjJ`0@e*Dqsk#3N&!6IT^zgQ8~*obyQ?s9<8d@<50vh|dI!4ZKewRqmX7g0{g?QkThRa9g06V$SyM;0_FC=STCxA!f=;!b_~#b% zKewR&{Vi#&n}2RW`};=4ThH1~Wa}dGpIgvh;0ayZ{BsL>84E_|n160T|8ooapIgxX z+=BkUx+VS3EogR5@0RpGx1j&I1s(Y37PNQkS!?2-ThQDb)wT9Nx1j&I1#NG%{(reO ztuHJ8|Gx#@%J%fXd<%MQUj1OZDqR|FEdKqyr|#?fz=+j>q>qn2HsHq2RhpK5*YdIE zGe7Oz=GX7rK04ypZjI|!+jUW`8}8}ae{Zi#%bst&V@BI+0)3Bgg3#Qoyd*dz*un&xLoLlvQ7f}X)Y>>Lpf)B$)YhyQwKLUP zLhVhqsDs%oN-%X=L5XIHsG}(nbux`wLrEr2)YSp$dx|@#epdMzvsHZtB>SemNhkBbuqCV!RDAlBPfcl!{qJHLtsJ|J+TPK2PW+i0O z6X|e#A{`!Jh9)A!bwt=CVUTe;BCL}zp(DcOX1#>0P6+ioAq+Ozoe*j#A(ToOV(KLE zu}uQ+d*~M&YDy&Jc1CF48R06E*BPNnGQwU7BTUm|gk2JflM${qyCuv?K}b$P$S_4I z2#FDdGK93zI#bvq_E5Fz*BtAcdhVxZOnyG|w?%H|xF$;*pRBca?~2z`Uagn2`kJ}# z9NhfiirDyP29A!sWOBO~f{F9H74JxDx8sF6-5;Crv)LPA4lLoZfMBLM9HF9xU8pFn z3&L2ls0%`$t_UY3WSi8k2*)L?>WVPVoRF}h8^VZg2ouc8ZV2h!5#qZeOfo~eBgFMU z*d$?!ae5%ElQ5wNLXKH4A*&}s{hkQZO?FR&+Px4;CEQ@@^g`GsA-@+wo+*)#+Z&;I zZ-g67UT=gZeGv9a$Tv;_SYWaTBGev)P%6QgI)f0lNys0B zu*j51$h{1q`DF-;P2Ob)O)f{+D`AOgdO5-_3B{KqEH%3&%((&~`3i*PrsxWU#K8z< z5*{`k2O}Jkuw*d8qvo)Lg;ye^U5W6xS#%{rpCJe*C9E{5LlBNjSjCD9K50%!Sdoq} zA{}9sS(%QIJ`^E-D8gzpbSOgHFoaDK));3P!a4~Ph9Nv>)=S8`3Zed02rroIs}O1r zM<|uB*3=n}uuVe#aD?@yL_+QegytgJ{Nmz0X!t3U+goPOhX&DG_nnf81eMTXilu%+)M-Tjqp>6{8VGj7BImD@P-wk3onZgYd2yItC#w6Je8toyN&TSSMjZCc^t>y@af5 z5$a!y@S(}R7NPc7gi;9~n>u3=wn@kzi?G|2NXX4XXr6_z$K+)pG|5KTD`Br`nvJka zLUA_2=VrHrIoBa1Ux%>I6kUgqI1Zsq!hX|n9Ks<9OU5A_Foz{99FLGT9^s%_G#;VP z1cZ|k4x7{o2*)L?nt<@FIU!-iM1&C&5z5TUi3sVF5aK5x{9uMoLWr9jtYTiC6zm&3 zYMjXk>n4+8!emnXWY$Z_nu1V&3c@cYdkRABsR*SKj+r`B5w=OlpNeq8lt{?UL1>HBI(S2(|MON+r}Xb@CCmNyyJf zsAEbb~2qz^pHL15C9G9@_7KCQzgoG8hB8<2d zp}ARkD?<8gg!tJAEzQu`2ywR|Y?9F0IJY6JlQ7{ngtlhAgsj^U>fesg-elj7PkgdYeVX2z~BCI4L33q~3*aT*9im z5c-)D5?0JZ7%>ka&8(bmFc3 znfL%3N{(4CA!`XK>MtS1bd$XVq4t9ar4nv1bsj|6CL#YpggjFsA$KW4^Q8zkn!Ke5 zO_m|-m5^_mE<@NQp?DcWf!Qr#&T@q0P2Lj-O`b&9D`AOg`Xs_G3B^w$EH%3&%y|kS`6-0ursyez#8n7o5*{`k zS0Nmduw)g&qvo)Lg-;`-J&o|VS@bkQpVbH_C9E{5s}YV%ShX7ANpnKNif0f;JcF>x ztb7I`eGNkV8idtm=o*B$XAw3@SYw=L5!OkV@GQb}X1#>0=Md^Yhwy^Qeh#7b^9ZF9 z)|xucBW#n9|2)EaQz9Yv1%&1=AZ##sFCa8|5n-={O{VFK2)iT{zliXP*^Ll<)wEs< zZ8k-aNnA&C*;=BvnvUxb4oO(D4&ilkSi-{f2x;pP-ZYEWBlLL*;iQBTlll_EaS5wl zLU_xZkg#F{!iWtBrDo*@g!GLF@f#7|HA6Qd#BD;@Bw?p&CZgxwUvjrh}3&K8Av;`q?D?*us{ifqqghLXRY(+R=4og`08baD@2nWrg*AV)= zj&M@KVUzkg!f^?!UPt)WoRF~M4TKSIAe5PvZy=<m*FrhVYYFFCnW0p?(R%FDAPLq4su!QVGXQo$UzQB;;>LIAKa8E8(PR`ZmHY3B_+C{AqSem{W?7T#67hMWqOd?;w;(h%p`CK{zB~$vX&Pb6CQ{ zcM;OwML69odKaP34uq2u9Fw{O;kblVI}j?H6B1VJL>RFXA>ORqiIDytq4@U@s+yti zA;i6put`ES%O_TirLhTO`N+r}Xbv{JcCL#YrggT}~ zLheTh%|Ak@XYxKmX!0?_UJ3P0(~l8$Nhtmpp@G>gVa_guW1EEhZxM!?5(&BAAvFIE;VP5&9YT{bguN0*n5Jb2yCf8s zAzW>COPKRLLh|6@gu?nv+_rT^q&yoe?pjKhW>;Q_cOvK2~&*oGr~Fv z6Mja>G3zB{{en>c7li31`xk`Tzao@MxWUx<6=9o%{9h6BOo@ctV+hTUA>3&4jv+KT zj<8okzG-?KVV8vB;|K+2w}d$-5Ry+I%rr$O5E6ewD3frD>G&JMAqh)Lx0^-3BlP(L;iQBjlllk3aS5ybK$vSzNLXMPDeN?VWmku9pSiyRi`67X--I35r;4$4q=s98HbSW zAjCTetIbdcA+8d_CJAedQwd?6gb9@po-^wuWK~9}Um4*AlU*61_8ADJ64shJXCQ2o zkbefkdQ&1HHy)vRJi-Q(7mv`S3c_9qn@rOx2)iT{R|)k?dqwxiqjwX(sNQ8~^5T0x zfB32g&e~FM`0>jwjC*TM#`$Mgy1w?bFGhX6VZxTxbq7zoqG)gZ9Xp*lb8h1C$! zsv*2-7F7!k32rk-MI|QnENHt~E_%zH5WQ^%RfkH=O3^zeSOa?33>ED#YeYMZQxkg6 zWQg83>qQ@!YG*?qnrzWWX0zyHQ>PZR%S;jNHYK7@OrzS+9+L-|CUrvb{ol>sX4dCT zTk%0*X5~wljqg&U)zq&0zB+B&hT0>ZxwCtfi@LmB{mC)kKk@6FbJAO^pP%-}uG4>9 zlJG@`!wV+AcmBG$_kZjCE6h_>Pr-Xl(>j#?nVBj2-0X(ToVs`*xh@{qXNu~ge`)rK z_M49Nps&n)(E)Q<^tI`F4s_5g5*;!}MTbpledrsrT=cCuA^Oe?Iu|N4D@EU%U<2p} zGgNfMtPveGPDAKNlOg)atQY-ksx^XsG1-u*eIAV~ZA9aanL6j8A74A=ywEE#?wPG> z@LyZRR*L?I%U7KjLW(Q$ul~%oXZw z8$Z=GeM1{V7p_@*Zokm5_?mO>^2^e~ivC~CUzp5{(K6J!(k?yC&C&lC^Ux2Wd;h>ep+o{wXHUt9!t5vYOh&MkEmQ?H9gR-YV;#j^nuGa(D?HnGrCFz zaBTCn0`Ezj3@b*Tde_6t_W$}0=m~fgsDB3Yy;{Waw$=17&2LsKwVK}C@TJxC)V%Um z0eX&H$GcXmO1NK4^m!61o{8AoCfsQ?JqCA=O5u3VYI>~beyhE2H9hI0|9)4;2UgQ# zMele@An>8pGyu^zzuUWY zPgUFy>_U^v_gk$I;Q}$(#^7~^Qw{yoYI@LJPv|#<0~&uNWRvkkwKPbiw2MLXy&NH{ zHA5RoK{{frrkQ_^GH}FNtvTU-w$WiUWzi6+Vm0b?o35o<{u7I^719!0tYf2Eqg`#a zN>*!wb`6@EQQ2y32_FDD&ahfL!k60W3!0HXhdw%21$O~@^yxidz(TMH+<&^c@|Vy(;Udbo z!<79JYEYmxKMLqMj8ouGpy&J#fpTIBRS8xm*2Pc5`g5QDO zBcMHCKhV1G2l|6Fa48rN6EJgs4V4tk!ng(KWfMI?FQE1R3|;FAl0gbM2h<1Wf(D== zXavp!u^Zfnee0D5Uw@xWu(1x)S zya!$ddUj{9i9a4nx;T>{r!~Eb(tF8QZ@4AU1k!}j1kr?O7ckc!4_#UC4!(aEEC$*R z^qlq!;6><=l;8c z9zf4^KL%C;-fU11IG2D6<^eqpIU0-snP4o)2G@b{U;>y3CV?qnD#!tPKC>(62DE>r zf;2D?3<7#qImG4{17blKoCYeL#_EnIPz7j@`wAQYUjyxLdNz9p(7xt?N}wfZ1=@hN zpdDxr^xB0B!9}16xERy|bwPD-CWr-Lptn-!RTU?->FJ#p+9W>)9hq85AOiFfj#{8L zr~~SPdZ0dN0L}z8fL`YDCkPTg0PF1`+R^ozk~LrzxEQnodb!IMp!aI*1rzk5jcy3t zK@Y%=8|V%Cf_@+k{K53oJC=5W_rM3>L$DG&0iFV@KzC3Fe8&9R4fX)NGR*~g7;l1U z{d=fi!AyevK!1<|l0g#a2=pEo?L(j7qz4%}y(ZvEnxKc~^`yOClyDE|1$qL#pWrFb z2s8lq0-X!)1A5%}Yj6-80*Apjr*Yo7mxAvDKOh`2@qdJB6&xdY9P9yl==OFn2jqi7 zFcZuImxC+7U~nbSlf-%q`W}!(ot;4nh=6av_uw<2w}`a@t%2UFkqBylW*|PyOGC9; zcLjQJ%B?`JkXc2?^{|Kmi-6vEbT^m|t_L@OT#yHJ9vT5gnzBDa=NDW_a0t+=uhxO} zK=0$~4fN_WosPc1&&6OCNCRC#H_#pEX;W?KOX=S-FrTpAaIgxT2bKXnpg&0d0&{@g z08ky&&~xHv6Q~7hgZkiH&=52MO~J*W8E6h#f>xk4NB|u{Cy)d>gJh5bA|MO=0Tu$i zH*6`~hz6byE(A?Mi_=-wEeYrZ+aL4+UBNHlIQR|deQ$cBNll<9aTbFIfX=cHf~8;? zxEI_4ZUu9}3@{N)0y_JS0(!C7A#g5e02=8PROf;7!3E$#&=@oU`*7u#U_bZ@8~}Ww z2=oL!KnV?61RelOzzBM{5!?u_2C;OkBj^N@f!=d48jJzev1@<=o!bWj&fS5_fL`-( zHh7rU>eX0!%hsLXCXfxr0KK73r**yYCIn)@LF~K1=@jaKN?<7E{DO7@{04ppe*nD= zZ3q0aUc~VVftSFQK-V?bl5sNV0eXVZDWox|1O8;bSEn*v6>3A=114j?4@QAc$iD?_ z1)IS{kP3PMT@3D_3|;Jf3v_|EJA9^pRi}$7VP0rK-I&V?ULfs@z>p>t{0tPW*Dwj= z>3}Yt8d7H(aRY#Kz2oa4a2>c79HL&?R}z0YxB?6YzoJV!t_wtt9*umi)OmW_HYp9w zxSvCH3aSxtjLg4+SHO2vrf>8`=nLR`feUnjFdyjre+6g`z9!89@Fmy>J_p)t_kvG> zoW2%316F~jz$B0j#)3@xo)e6z7H*Dlu_+41ti4bdr@C6a8e9d2fpjngXz&Ju1d|bp zX>9Hc#nk4W?D9}dgKF}956}&C0V!rvD5hRPCxY6?wRdPw?+moZcL7SOEXvvkXbAfP zU8)QOx@;K)F0*0f9SSZ2Bf$tT99#pWDc*j4jgpxh6O*Nn@R)0f&8m<>C!lfQI?I|w z6D)Lsx(BQTE5N-#JKhYC2XetQkOQWIDPS@%88I<6%)r=~*n%4{uLskCic%j)F3A_lN18acFF&pDz&Imqh_MH|}y}lAD(>kC`O8f$N z9z1K-#Kkm-*D6)CqMkR6oS0hX-qT{v3_hIFA?1%;JiqnVE~Xb(Y^87(K;pxtP@ zz{f!Kz5_~u3RWf00yT0YcpJP0wu4tyZ1wCCg0F$jU@O=R)F1g{3y>bQ|24dgxYsMh zzd`s-5G}7_z86(p1Nx;}FWgD0kHC9CUf2QN1)euxHQGXmFTC%dp z|6hO?HO94i{@)B&v?k4|=&=1YOa)&d@3*)RJ_vLHqf5EEpbGX`u-=LrLpW9pej)rb zI0TM>pTLjcDEI+<56ZxI;9Kwws0I!LrB~da+Fec&_!ayCP5@PM45;wq;CJww<#_mH z1zXn=D%5*_0^!<(&jvMs8g>DQL$3^~f=b{ta5`u}oTE1iDuE0^36(&NiightYLFUJ zg|HeUDBYPr?*Og^^bX(}K-U@ypG%sWaBZOPEf)jzT#eENa#L^-xKNjKx+u`Kov!ip z&R|{I=@PFI&?TNO@1!Z6F7w2)ub?mu(_;7Pqyma8!?`=|r3g)}>ywQgI@Prmv_=BYi=6230IEny3eM zH_#PGi+~hRF-;d6S5b@R?@nAa@88ACTL~KfXoe{3RTPR;Z~B0!uHHvoC-0~+>Rr?W zg9t0%K+q4U@zFA(L!sAOD4$&K<)ePDM;aU5i&3wYDVm`_VP%L`qMk$x9#A2j(p(A@ zugR*oXv3qd#zh+@@2H*v^)OnQ3b_oZkjr6>sT!vEyFnxLd9dPj@pu=w72E=5ftf&F z(4@W@LTCum%l~+R&tz350 zJBp8%Rne0L(Tvede^+QUF4{QdshCb)xCuzt3RgwZc+HQfhyT?(q$wyLFJ&J1FPYU+ zRbC9=3Fd-3!0n(AD9=!!`A~76k}fUUU@fYOi);|K7T64pe{@C3 zGeiadVccesxS|(kVE@k!599w|^}yftB04euE0a<8J8Jx+JDIl7=u)dV zd;c>xpH2E2Kv!tz0~g!_kjle3)?R@44LA%Aft5gCXbT=B@H%(}ya1jD&w*871PPvm zSAbaXI9LK60E@vwa33(BCb%Cg0@|o_C{C3;20sE+m+a;6GO$$Z{~-bogGYf{^#phd zDB)`OY48kK1D*vhgLPmncoC?f%C`Zm2QLAo*$6fPm7)08z*eAqn}Is>s@DG&0;)ui zcs~^%gx>+BU>kT7yaC<m3cMKd@4)wm^@D(Z7|;)1`r)fHXbUtH-;wrA z!VN(ypbPLSa6x50T7o8^DbTN9a#t8`3@!pcVP6RA*E2Otzoe;$niuB~t^;a=TA(^O z8^iz&(+O|{90$k1uiz;70er9VFC(A?YN1-C%6toBgG)ej(4v4371D%?TElHX5>Tre;U7T|g>`0O=x!qu9DR{|L}+?T5e;>R1kI^6EA>OMjI2lgV8e z8V~)1G8Lo&wN7{PF9mmkxj;Xmq=P#^5tsw60)xQ>a66a{ZUvWvTfi(csBTR40&O#e zm~yy&dbt_ogB+mS?4v*yxDn__1pR=ZUv91k(}0>a1xyCx!8mXo$OdD<)nEXKHt|Zr zmw|y`kp7*7+M{+pOhc~lMQ`s@W1@XfoYD;i!$CB?G!-@sD6Q;h9_b^2{BkXzGVeBd zCOig=2BS33GYBYBEfSPLtV{|=Tdc;(6DsuYdK8WSyFAj=XysQ}1E_%+EVrvi>VaHa zF)|v~NGox)kQ^J1Hdeh>2c}zHg{!g~K-62ggjGkhOr=+S1rmg4%T$;$YhKK>ak8ar z{8hMIt2|LXTBsVD3p8}M0eM!9R9>;ljONokBV6DWVvX{!=5@tFqZP?pnv{xDxMCW4 zXdbv1+ygX8=YwdPs0Y;nHPi(U={y%rAW@0bTFv^3x*DKG6rG5(2+N((39A)!AD9MI zfy$7J4bU+BYgvj{84J}5txCCP8IaqSf(Jnk(6XBjt^tdHCf*Y809Xv}2P#|zM(0D6 zEB5p;;-g+&Y16NO3)BErs>!i1tPttxOG2k5y>2f%2ltdjiNq zF8~eC^FTwy9$4V*(9dCX1_?luL_4%L-RR!;EV{;W4VVm`0@??kfmeg4!73Y$HcrFY z1XQ#KV{3?ApoYAx^-n9j|F%YrSODq*HR5hq4cG`512y0!upX!(>%dx2aYO#Q%H$cX zm_a~Z(9WqjA+Ijib>lUrnK5<@5hhR*H7Ol+w^JB7WO`eo9ZS2&{ob%coeR5aV^ZJK^ zZCkW!(atpLO3vXI#ALQTq)ABDD#KKHMf1Lh=UcKCw? z-1RNI`^bv7dtLwb`F|y-ai$-$`}bpSR4Us)`>&Ww&NTfmj;UR*Cn@TXcWm+W_wSsa zRrptmk*45c+MR0_T^!Rkc#kQ?s`n7d&m#F%&%~u?X5Tu#hV`s|W(hWn85^oy!(7)a zrcLPl8s@=fl;4Ry)g;4~;s8LPxYIA-y?P``Q+hDT+syB`b z<*?u8c0K>*_ZLn7tInC`2NJYjK!Um?_#u7of+~GqsP$KZ)x^k^=hQnf_dwgKAN>{c zrb%v2*}F)5+`7R!#ky#| znTI#pKUm+NIxmHnKN4H=_!_2S>lPhbwBm~)F`9fk)1TOp`t#BQUQ8>+@WK!|vKRN_ zHI;syc;BoVl^#2v1Pp^_@i)8&hUQjoXnM7bxj63ZhTcC=EHvMDjHwmdfh&2la~Zw* z!>novoiuN@jA<60dmdN9W@i7G+9uL6CLH{2ZOv9O^@H^W@GaJ%nho)bn-1CbW%nAv ziy12&=_ai;`7_Me)=Z}HW-(U1Jd(4#3eOu@>*td%hep;2rX;hpX!z~sT@tikU=tku z<&r0Ei(P&~2@<^W9wvraU%2V*7kb4l>UGs$H9c$Uw;}7_PSkel@fNaHC+kJkV|P9H zo;b54;~qkU%D)#m7B4<`LpP4x@RvNkcb+c)9MyCzI{ zY~OL8S8ldXrD@d%0$prOYSq9!120-L#nwYix+$Y@o-R7QZA@CdC;7!mgO`=_^q;>~ zeKMOR)z05xUNDnL5IkizwZ&;0%+a>wdec;H_ZLk!5m~cM>!mn!F+bO7vwAUc$B;$2 zbqhl*wpJ}Vu#v1V#iXnE0tw{tmqXXIO}+7v@B9Sq@yb@SSp~gs_9)%IdN0Jn^JZG$ zGgGZI38GI2%$a=ZoA)06x~`CyTzj@N4vzFW8Ufz z)7_cf%wGhrnpZlJ;w{rU0s7SRgU&fZBpXy=+|!qAy{FoJFKIt#8>V}KOH5%xOq+T` zF7Z87wR^8URg=pSSqKR&+O%h*9%D*X(iC$rfd>3x>Lp_3o1G%RmU{EZT#e$7thwRJ ztIsM~^;hx#u{GARnXNU%gga8Ot;Uw`$JNVg;dkon>RXJQ`Wf27ee>y)R+!vKx35{^3-h$<}IEG*017f)VtQ8NO)a0}0US#hJ z+75aOpK|rh$*m1NK3vLa;}R19a$Ic}lX6x8QY<|v#uucN4>6EdV=_%;5BkX$Y>K<8LAIXe9FWkDxDnZOyqxx0c!`72Ji zsIl5#u{Ced5~U`nj$)* zb!$y;+M#XVnzrD*X+O&C4vNOm<+gkw8NHQo{G!e^025`R>IxG|2%@Gv;Co_wDNWFo z+fH$>qIOAi_(7FATXF}(Y!bVst`8-&$LlsepwwDR3qaefEkEo`SeG&|_HcH5O%?<2AAD&L<@dpozNGIvLvSi* zXV6tVny#dmtP$d(W?d9JBQDxXLbW@9p5=DVvpSq=9x`{DKo8wEIYk**F@6YloHfs> zC{!=$x~e!s+o4*C(Fa$!9D4p$<@!)THzYJjSYj!$tKzPm4ghAx&G%Ny%+>`R;Z_DG zfvpqecZD`KXi2BLDK>f~EhPIurGw^PJN}w=PYFaGE5YP*9sH25C=gUVpi12gNZP2H z?%qK-Ce8V{1oC&oE&wSScPmrT!&0Na(q>gZcdlYA_6N{^Mj3ZAasKQmJWkargM4(+4$lm%enCk9P<2>Wy^~00_B=~{G5%sG z0vz8qK_;JcqANk~Xt^rc_mE)d66#xuHgXA8n&{v4qd~#Qh|)TXXtgQG{+E>t&t!8h z^6VmuP6mVfvN7UL2_(!>lrmvD{i#yQF7yMWJ*YM>*(yzc8J+E^mw-r!IaPiJtbxHz<5b|8dOtVMU#7jjdG<%-c67A|H4a@W`=O? zlpJbs8fAF_J&oBk;&bTt5!_T`!ThV_nQP8Y?e`f|TW)kFM`-=$a0ZiuM5r-aKYwEX z!7rXbp;)v>o{rQK81P%<5Bj3HM)mswya?dTR;_lWF1no+m@2{HJD$=g0BFp6P@T1k zjn1-%*l3Qv|K#>BsXJ}~5Sxc)$hRB!pg;P;eKhYu7X4s1lPNMxv8s)pg&4zj(DfU2 zx)wkDYz3~|u53&Sh4sT~G6BGL6w3#X|8-gQ{hK5JX3wW20GM8(bk;niiDA%lvtYV{ z8FZGx!kq*zpViVY;wcIXqUM2fjbO42QvwAI>NtEK;kz)!-E&+oktwYUn_G8b_=b-$ z42eGTlZVk`0Xp2Qw=|gGI_Vqqu*wH|&e3ZOg)4OJkC3*5Ov4p-I?-RT(~kU1SZ+ki zmlap68jair>*D`7)DWFJ&NtY;Ew3Ole|lknR-@k`w(U*R!j-{{V_OGcV`&xvC3r>- z5#V=~Q0f(-Bxpy5ihcW7--Q!=Ui2^%f@UG<8QqUiR^XT7vm?Qp)sz~EjnfW#90>x` zsBaYb`mnF?&Eq3}>e9()AhbkX={TAn1@LK9fJS2#Mi161wa9;f(i{ou_yHh55S5_u z3n!O>*!2a53tVl6oE|von=k4Cmz#)=ebB=exNT_q*%Ke_vtm8q8gjlNGprE3b8AK6J-3DQ8 zev%%1wr_jv$L%j-F!s7kR(upo{8$M=)$_5G&qg&FhyyOrI^_7OgB1&R6ti7{mlrI< zq&ux02Jn9PVj-H*n%2?Oe<-LF3qWJK2&q-y z5umYfsN!LG&(Ym#_2kn5LN+CHigi{>H~Ek~0@8F4_aU4->j`I1hL zgkoQr%m*c{Qanf-!-4ordV2WNS^9G6~SB7{_ew30AiQ=8!!F9j(jvWjN2mtrRE zSumRAP}e6UtQ+s#d!&KUMgn|~K;)9}wG75SDFMuxIL9z`UwnZNEZCNWSCTo#)adnz zb9O43&M+`bDv!V%sOp(hECn(>lPs6$jPPjM#Zr$&$mAu7Dh4A8w3c!F@)~B5|-edV_)2G0PQgV4yPnwf_q74O(vx;Ky!{Rv!~aT&yZM; zp(_g@EuoQGpE1HdyB{>a+xwP%Zv(E+C~_QzjVAlK|BiE7M3K_~zL_GKY8}HTIx*@N zuv>yszd?uc`$nF#7T8%u?0Kdq>}QbglK_d58=cn&`E}H5fy=4ctz9kX!g)+P@_GE!7L-G-}AXe+@`l< z42tz9IWpHLJ*KfH>m8`nQt2j3>$l#e;3Fa2b4p#;RTwAs+bzzt-t}Gmi)>RQJ?F>c zXx@CbnI1>UP+)vdDVMq2QhIGgOwO~hPW?)_H`8l3 z_f*DxC0r%-XD(8YwhX@@oT&7XdaMkPrc8%lTh&OBjMDxSUk;5t+w3;^HZk zend%H5fc$FRSnmJl;8>Vp9Mb7r&cTAO}5Pt&Z__KrxI$LpARs&%+vJ!EFgM7BLJtf z{00}7;B^{kQxlpE=sLgv=kX1_ngzR;+>ypvW{x32WS>;4(viY~c@~~3mK9hqP08?~ zhJ{k`z{uUJl$GVw@x{dwu3OC^rQa0}7av{v|Lay&pJU9b|L}-q!y1`VNglnEk{A@t z*csx}S&O0gZz!8JCHri3_TS=eAIu>2^F*C1UN`jg)tUVt!BB7x#RB4WT7^@8Z4m(25zXCU2UqmD z6?_~3tZs(Aw7eGZjP3$XXF6XT2qn+RZkP7D+6KU(T+3=&vQp+%6%Z^To>VbxMVmRP z!woiRCwp8*z5RPZ9^;$*6!1#3VNjQBdBPj;#6f5 z0N5#Ur&aWEn$o!5vt&^{tg%{9yWg&U#q98iE1X(>CCP3z#il{y>H>lta89@A7ZWqh zbS0k@_flLO*sP(dn^1GTOQD-^)~QaN zL31}jr-rN(MZ(aW;tDGZiyCqYxZdueTmWb~t*1XWfw3X$#dK-o-;8iyqxCmqN6oHm zKGXm$HiNOxCB*Z;Cr0P>S&UlsRlKnViQm%Y&5CW1eTuNWfuRTTlB=&ly}2^p)B=EK zl$6@9_v%d;VG#-{ydh?2m@gn0Yh9~6yxTay5_MK5ykYz5cuEJ-vg(cUR-BksTShgL zc?8&j0Km4nX*#srCsj}vdf4mJJ6&GjLJAhhb8?rB!nUtHXi(6&`G`yCY0hqrxtX-v zNR!gB^xlB50!`1hFYc)H9m~dg;d%+?7)INGeCjj+un_#&vOg25>ypz%HJS-_FM4dz z4!+>KBc!P}!u?9Tal{+;eFfI@+34c&p>Jy)(3qaXn?`tJlP1!NL32K8wea}PC{8Lr za&$)z8~fnrtxYXIt|-b#p3SJ^7RA}LSLV1aN~kF=c>VG{BnuVmz7xc6#~9{TtSW@s zcenrgXN{?MGfz(ZPvo!_iX_UtwXvFm5;Ww(Ri~N`UBz)b=g|xr2GBC>Rdv6oXf4Ba zfwCFg4Ip9$G5ke~=iR>e6vcN~j2E-(hjbqRUT*+8%&l+BI+Oj*!=r^Fugc+{gz~&Ng^~i2z_yxjOUG=#1{iDsuoY zChEoLVaZ;b?y=+Tr{}ds57dku^lqaMw`0*K0l?}wEnU0zvP-cbbPBGcP%79fj*e2XdgxMH|sCstORA;fv_c~n98N0mK*hB-i>nMC;j&^Ya+ zJ3A2u)|34%u~6?_N@E1J0lO4OT{*(W!ku@~nq3GZ?RL>myDT*ND9jY zPvZ6pukpdIAbahy7*=?L4l-%;w8s{QJn4WlT|kF2QQK+<+h&Sa@y4T%LKMrfhWmZ1 z=%d|;nV$P7YB&5izr<@rhj!yk=qA11t#qioct3urgRxv+96aDtbA`J}y42Zp$1f2$ zFvHa{Mz~4STuR7-o(BSgZP16;ew=gm_j=fws1Px)1l{8&7*i1dnWdhp)yY5Q z$rpb~4Z$-0aS~k{C_GDwZfzZ()caZ-PWz#@{N`h=Tsrd;cr}D7WGjgx5wxu1#v;Gp z0m1cCf9{Q4I`3a1r{G-ArR;2^c<-I}K{l?|J%Rc&kJc%5_}1SYDprD^ zUq~MmpxH!md%$`}aExVyMmMndjG2_P2XG=nps@RxffpPSA*t7;2@Rce-h=t7c+kE> z{(GU_s*OCOpsRQ^KTHeuLJe&W)8@TOV{_NT{DUS58RUOVv8>3QS{}Lns#rR@Hiu1D zJtBnH{?MxyzQ^Nc88}|=2sPOUOuk2GUmj{GA++`=TvC4wumJtX(!Qzwdxee#h5^IT z!{X~{uY0E5kJl2Okb9i3>HB?H*;0x;gl02cWluXP{01~P52I>gR5iaPceE3G_5nWW zQ+^}o9KcTE`ZQ~Gj|LMuOHz@-Mo_o?z&U~(^T8Bc&cttLD3QIIK`Zvd)hwZW_Oyjee*=v_ z01=bf=Cge^AD+E^21kV4(H-aL_PKyNN%p7ET%ut1bd$bh&13wL116E5X-zJOl9JKc z{Vdp&_9*_FKFb_4IWn${&OcKzAhjIMLP{h#<^l64YK2BS z3vevsNlZCV^P<^Y76TX@;@Vmo!2oswfW@^3Rjt2>KJ#}s13(@XoCj@2vhAj+eT-uB z*-c>lT#m8O23%izR#h7zH#P5<%&x@_W;w7kJD24gwK9WA7ztY)B&DeBctY6fgUp!= zmQ}vtEqMS`X_RyjD>R-<4x{3024Sn(B=kak660v`Apn}4qV0#c;+h_TCGS5aOx>i# zld=0l}~|(nTG=e zvkE0-+^m~RbZjTUL;+jpDrDt==-E$VS+lH)mqXT^`w|Ac;skKE3kldrNid^hw5yNW za@GJsxOH|@EkIYk#;RQ58enbaVNyy5qz%1C=Z&ZOCqPw}{|v`nlW+Mq{`a|SbLw;YaeWWh z%!C8OL`lEs(Fr$Zqk0uVlceRn++->vESE7q@g)3NJjGoHNzg~h4oRxC9_haXvql>b}Zm>5M2VA3w3+6PFN^ zsdU%ItI@v%2i^tNmLKbk_-@Jp315Q{Y8h+3ikaq$PD%Dqjg{XzCS`%LT;7 z&)4I(ED)0MU~U0=ilhRFK&L9YG$3g!k`K1J@@D#(>)Hp?s)yWbPUjTZi*~vE@`H6U9QsP zOHr-8B8fQKJ|{nOzE5{a1k6;VB5Igqx7vKJh>KqnpFL`t8@_?1XDoXG)49gmYJ&j4 zVsx{mj*ag4f80!xhM|MW|2!;HWDqp(ykh5Begv8bG!{Vf>xQ+xdbksI6KbgEe!Lrg zz~b~fG^hY@EE#OD=TK_4~k3RF$5na^= zb`m{;TRFDHwU0RxZ5W2KT%^W>C3BNMa5nl52WS*PBitJQ@?E&u|4OPp7g00H>}4%$`qvT)PP2h z@71y7lOs!K7s;4o^oFYZx0#R8d1McoOQ~1cJQbN#C7yr_wG}JwCX-C+^#^hm{~Hwh z2P$#pmc_Q+rG!q!#_YR z-;Z?DmQ&ZkrYMd^6+km@(nd6;Hy90Wl0_l1s_E3I5WO;I)p*>b_(G+zrtM8iDO6hG zT<3Bj=Gsp$09A@X-2TdZsU{0@b0GDu77L|fbsE3h6nF*G%S}}untBDhq+{46eFXW| z`t0QOCUp<8Yb`AOa&3XlGdAy*boGkjpnG>O6?GjId5$t8e&Z9&g!nK@*nSZCWZ>ufyIzO*n^rXkv-;t+29F^f@)!fY36`JPXK=A*LzGpkd)O#X*3h%V$#5C&z4iFAR%v?hjT?GKb>Ps{l+aj{MsknKL zFA~9IiA93Hd)U?v93Ceqt|q^uhm}L8z5OBirA;k%D#O$THgugP-vp-3>_*sVF|j9R_Z|ke+|nsKzvGxw?M>TT7gD8LBd`7dT@a2 zl8d;6o*<yS~PQYL*xa6S-lXvwWOfiN(JpH$=Tc+NogNhcYJ05zCt4a?DYr$%*b8ZE)UPF zSY2ZPD3j-W9-I5L?>79Y6PC@!R<7aWwA*y?3B%YjdCtdjo?DW`9S}EIvMtY~xMPFc z8b8DM3FLbR9G@ov8rN*-ZaVw=S_43tvfvZ^NOLh%drS^J`ZU2|QcB1o!%!{@R!D#t zEa^T7(V0sbd}!VNcJkJ-M-0FY56I#!l*m&;#6+xndoC#pXD7@NGNyN>c6V{YG>?+* z;~?3f#*Xl!h8CTZF#;-wchJ^ zUHck)+xKzxeS53l+oaKxX-~ahu;RpYXQtzh`xi!#=Jbf97IiSHX;zpMSyiJ@IP zk4F3SIyrw}-N3?cS5N8ZFG?6cDQ8lSZ%P%PFXZzTkAh?2F*#Ev%C+6OK3{F@%Pq&k zao8uSdb|&=h5f4KRd7vg1785ofX{~uah9oKojtzXUT_#Z_oc`D?Jxf-_-_ z;ADCuzw0eu1IsTRmfvm~J=@p9SKJOkLqBe8!Nj2j1-@75t}1u|mgAW0F=O+F`Fukr z6$}|$=<~hiOpog0U+cUVRWt9F%e^4R!D=i!j|h3a-N5HN4|@}=(T&ux23tWagVn~k zMm`@iT0As2d-N#EWnydk99w=QnW|@&(=WPa(YnT7ZIfWt(&aZEFBk^D@Cy2Wr! zEF72Pn~ww2S3HHVaV%~FYcX75?U7FXV9m@>OK;7Nhtg70&$NRYh5}rQE)j3nou+r=oANSNq3)Z(+U;tCi>4crjlC zYoV_}S0B2x_4%01;^@e!|FQStf|uc5bLWjqE~hrfh1 z+GE=JeD&c|*lIB1KZ^=9SgSgC{@puzv+Ka>ej8ZBaT%<@mv!>i>tR^K(6Y1aB46>t zHlqrT8sDWx!do1E00`dfK~(x4w3_cLr6y+<&z*y>iXanJwcisa&(Dc^_{MzqUMj?3fXn zwz9rnn>#FTwEQ@%L0teVhTAPShZW0cSc97h%dab}m?urr7WVn}(~efjmHk~VDt_2Y z6cDVoMZz3sS>JgqH_we(C_6J3*7HI84xny=?! zP5fQM?JmUDE_~Gb-R0Ru#mAY=I%EvV^&C6d3d_({0mqrUzyCm&-3XTdmXV(SDps1=Iw!HH$~>>1 zTx)X*SUSJnd`Gc0uH|qQ_#Mj=atg+dzA4AIcD@(Y3ivGYAA;qV zn_Vz=@))1**nQqI>SOcAS+9F>H!udFEwG}=OYhULTB#an6c8KrU`@IUELRZDj z7kCvdwe}`#1v+Fx_Rt)<*75<*?`F6P`c7E+3$1^oV|QS?*R}kd2_tfv`+Sjx-!WcK zhi4Z|98ZW7awZih*n1axBh#cYKP9^Yg~!jms`5c!vxP*o8~Ig5zNgrt=a)94B;a^$An=nZ_EY>y)c z_79JF<-3uu1|vuFg>*>mjN~TL=W|}KdU4U>6+Yi(NVmZk!NV+nzuX)8bZi}#W~FzQ z{cET*pYob+ik}XJI9QuF2y2afNF0lH<>~YycM(6l`r#_vIyQoJi;W=0{J;8GQ zt={4H>C5hMR8;&X2^CmMh8l`28O`*NLZ9!)SG;bpGu`v%rfpuQ3r1%b zG7YGni{YB^Iq(JWuGhQ(Ccp00GkJ%%O?Sgpb-g(KhLV(Y?X) z@UasTah`7eUiUco+3zwb51UqYL5k1vPSqcT{lJedOO zQ4uVA*C$>@jrV&2E`U{W(*s_^_rdDGEX#~!LC!>-^$Gde;|K~}0Y}5?*bhZipdlV@ z8_1rLlQ4Abgq-Vb!(CxHu4v&^yz?`U6F&FWM15F8dLFDH{Rx);$;Z8p96985VA2iyWrfSkFC$Ju|p?L$QzTh89N@I=U{c_5Ot~Z1xLKP2f^-^ws#gC zvE|40bLd3Qp;p)$v{+c9|Knkg8~D8SJLYSzp0_r6hL=a>+wH!ge6$~f2Jq@dmU-@pL){hXRoKMDYqv~i@V-B zu5Dk7hhPiiUac2wO$TC}>@G<$Rd`5*QOU_}kr;Xrs{>Zh$!?bDKg-EX2?q_Mah;Qu zk`nykOsWs-e~_!KIFpJZrVLjbd?vM))U~b_Vs84osVq``mGZB0%2LC@9ZY{ur#v+! zScCJUx0@PDs*6(o4Nh5FIQSEGbEmv%O0XflqMr6`qe%>PvO2hl47XGwD|dub8cqp5 zLdvW2D^i19tuyPtuj{vf)D5n7;*8dbQ`gfTCFPb4d|TV8-61X5ontA>b$^0XwwsE% zFcRvuq^@>DUFT$W42O=^XZFIAxu~Aye1qYwJx2({pw`uWp~*gXvgp+<~0o zlywOQ-^1?U#J5QCS9dbIhW%MiN!M^_etmA=slo3YT$~h*(ay>0n&PkKWOfVtZ*@w# zg@b!9_W8n2d}4|}?8K#qLt`&xCMn``4mM2;y@A!#jd2Mf{EVf5qLt@w;lyaW z-NT{Hm-&2JRs`2IF?0f}omWS*#8Au2y;9Uj-d%rM9TS7EVy%Kb8vN% zzmfA%uQY#mC$4wc|A3R(I~>@3h4WzVwBT8d>5yhI)P>Yl6o_(`bWRMtj@9LiHQ-8b zV|aPQ>Z;Ge8I2?P)+=O5~n^bH4>p(Q!x%~Jy3CpfkHr3KqG(PYKzVpjDeu00qavp4v7P^q;6>GG+ zAa2CcQYQlT#ZsqiU^rBcrbU9*H!&2S=q-{c=iqfoG1g!%Z%lH^GQxpfiB9dzv|yzq zjV!b0@9LCfhC?@FYto}Noc^brxIy8-mr2f`L1{!@GAJCHnaml4gWt*Slo&Xf?9?8d z797Qym+r(5P6=)&)m15fRi|ulICOoA*EPRWKRq${AZvZ3Hcar+RG;s9H+35+4mGqY zY;-R*jMUZALeG=xOLN}zRtjrS@Xo>orC7lvO z-LbT!D9w;P?PO+$LnqKQ71ZCF);fEO-yPJ@WNR^k?B&&18c#adGci<|Wk8QLy4i`r z&RAS4=-Lue-q8F^s=E_UJng%>J31{Tc!E@KCw^$#Xrk)v7Pyxbrx_i8lN9;3bM@1` zRY71a6aB?bTuwN!CEXd6lNS7qtZr`MP7DTD1qv@D<;}K6?HjBcy7ub=M3ta78u}CaDG3P(eUmf)xjNvs#NJs3i~S9OP1T&Ii%R4_`XM~r&Bt-ZOk>^ zwq!QLiNPDN+Q}jG6e%?sa_4-X^{VV{)a3qNnUH&et&pYjH+Ym(bEmXLN+^DSw?5tb zh|qW}b*7SAdKFf4EY=pIyB|wq9drjLiK%0EX>Bo7ldw2wyyL;Y!YLaY4xKg7TO83& z{SJx#eop2%E^%n=V$D!;V(=}j{!VG%lwe(^Elu%oDa;%n4!wol1V3+8pR$(QmcP9d zHz6FlH{|^f9ovM(iBnv|JrjS&V$pX@365lMUF%Nx%cRu57Zp%_cM zfm1#$F|+|oi-CJlJb%E_NU+QpiKHQ3oy41%7`h3|+XG8lC59fy(z0>$0^baAKDs$g zcP~@J{yUt^so~JnVO~`{Zt7c7 zSG%e9BeY?(qVFWdp3%0=PV}GSluZi<3v+FW&>N(*4!A4ol*qkR$*p03wo`U%IJhp) zz3ZHo5( zaX9!RHn*_2cFyGc3AF| z-4+hk8mCK*Y;NFYgoA%zdx3Nu@AKuV9sf!v?)Gr-H|!L*js6ofAe>Oa2T8Tmwhz2L z!KpnnE!eQY=j$vd_EO2raPZE_?gfvL-A(ESCu>GZ=;|rndF+ki8mHuraL`xihCL(2 zpX0>c84f*&%|_C$&rgcR7~+)PkrKN0W^WZmIrSGLMPs-l75st}^EWdk*m|nwRD&~x z6!V8x`4*pVm^+iBNV!WjxRum(sxsJan(n=n+Cs`L5xV$RZ_#novOLFOX(4;oQ&`$+ zY&;g^H&}gLOP*;(%%_+4bW$|N1a~5TFLFMblg3(zyE`0sve+3kH7yvNuJ;p~yCJ0d zIOWsZMw7^PrSooc$BNAHq`WD9h16I#x7iGLF3G)vl&AejiqS>uf4k3@uT*F^DIFqi z+`;NI^}a|RH+v}>B*D8Ur1S3d`3l_BOj2Fl)J{@v)1mlT-Z|h6z)&o8*{k*#)<7rz z_LNYk+1`|S`Xg9gXRf#_(uJv{yt-c@mFKq7c#i8gvu!j9FPLvV>F%~McYC)xUX2f9 zv3=QwUy#y?hR%^(+sV8?92hv)y?0$aH!@fCCnbjVVQD_NZJw6MtzzbaaOkpm-VUs! zV>dVxtEF4%MpE*155`dCdm|@@?ycHmH6_ozwuHuGwfCB5&evdd!m8x1m7lOWV^wvp z7Q>tp6AryUpB0AYUHX&m^CI+On&@OM3q|lTxm(<2Jk?vYz!I0K?1T zd=0*i)y65$ND28K@J8IdZx5wm$%{+k4M{N=>c4lee}L7-%~I4=jpxW^i9607SjzM6 zsAgJ=D#_cA)sj3$g_x=@jO;&+em|_Rn^!+4kwY-^k#KMiT5I?1+c_oPh_fGWO$=sX zaal^^-i1^buf~pvq3^L&hj%G!_TZVz4-06r6Sp`VdInAX@S=-;$UDoqn|?ki1|x|q z4sC8$?l@y{r4Af=$QiUm?=VZ2ghLNK92xP00~3SqVO{Ayuc%i_f1GmeOh=Mx?NybL z7+mb?rHLt_Pe^q@=Z=QEhI)^9tut<1XKr>%9u0?nK&>$?0hie5_VY%17(6^+#&e5`;M-|J${;^KnediT*SvZdEum4^16qL<*B) zFw)%$4=zg#p1;by7O;GWlJd5=Hqu%wO}=+86@1#OfJ#kL491xi6`G8tedN8FUWuhs zqq5#Ignq(OCCn1-KI4s@dng5O!Qx59z!d*;PUf@W;J3nCm_M-b=0prmq5}w=cA0Y&^QETd$-EZV|8+!HFv*YsShk6t|e`r_bd)Q7Ta7b z%^`8nza3cKVv`lM-Wys1z_UG;H`^>(|1_s$eK_<5nr4F2Wnf~c$p&whe9n^6q-YF| z>DJs^c`2@Udq}Bz=4w!4=-iFou)Eaf6uS^R~Mf6;;pE6M8`)+H}`f=5EHz-up!_qRv~n z^&$`J-lQ0eYdnKs{jYcz6W1}=1*@H-OHx8uQ$SRBjD=221uA}t2nz3SfGbM7p3%3cWvK7ZAzy)7+t&g)+M?rH76 z%_-Ry_Rn(4wuM8V?ue{dy$x*mrq?)2gTRJkHKl6rta<>egJ)$YhW2799M>~=-dk91 z*LsrD{BRmAP7K|Sr3q#~Y)%ZlA}`nC{wY*-XXI{5J77FkhF2eP?7-^jSv=3Iv5PI} zly^!A<&f%sriR^E*G0TSSG`@aCkwE2vJxN1aqV~9_;@_qUnx(YkradBEf4|pVCr9F5K6`++H;&q;OmiZ4kFHG0px`Ewfy6`irN8N3g$Y!?Wi{`>A5 zgjOk`X{5X(g543^hDDpqW9;rTL&amI9$37u!v9fHUPlhzni%>K%a-QiobnH?4hAMz zK+26hw273CQSa8|2dw^Xya&0!WPIS=2(S!pN{Yeg>t%78SK8yvF3aoIM1MD@?N~#SiCzN`rImX>)OOn*YZey{W(c77~WWNp$UDB zrR$ov_S$@+ptE?xeXEoCK{&Ag6Q?$RF=)S^9&1enpCHBRVtKba5Q$Zr@nI|tKL^(P zNik=PC9fyN9Q3YhUhlj8lXVN$HE!bv=Ol*S!MZjQO6amr&&)q-Z!8uMCs<>fNG0RN zrD+=%b}U`CxPUMYZ9Y3w16{ipOEGa^GSYjjMT9J&)}No*S~Gc@i{&i|UEH6=^46`& z#U46SCkO3Nr|hG!?vp+a`=>jZABTf4e&OyvcEyjRlHB;YSq~+A>0KANt>2RrgVB;K z-YI1yh8(O;uA|oDhgd4+UBas#<_*7>Hz+YU9;=leUHTt)O3K5bHAgB06N>qYzwP16 zP)ty0nV*D1i_ucico()QF;M=M^U)`1p_)e{4Q3Z71_xvD2Ses?tx}Q3V$c5Cd!yjv z73WlI+Ss{wO~{Em5Dpz8N0%N}6Zazppk?P1mXo$|@ z64==3+cYM$7**TWeUlV8{*&|ImubP2pEUxdU#0}_B1LaWeRw8y=}BqjN83ib31x03 z)x#-2tPzlX^(pmHsfW&_K0lL6_$88i)0xzkGpU$gBf0%Zb#-f5cqX;~OzQIABDwjb zI=g;r&ZK@mlWKc9k~=+;3cXIM1HC>^_ksQxr|ekR-`|P*CM7R{(Gm0#R|c$pUjVeW zoCNDtk(J-ZRsWj(=G996hDAXv zPER+hm$G>M6|3q#Ztg#`n&Jp`Ut;-j-E&`J+5J89pSUL4DE<3Xy-2vP|0jF_HB7hl zRAdEs8xYU1`C{3(`^+zu{e%2IXWDl)lcmhD9u-+#m2oAb}C88BA=EA~67$7>cv<53FgfDP7LtQI#~TP*tppw?ajs(lNP{xZ<(WuW|5 zfL>zdZv)lA9&3LH>jit0Q9*Dk{>aPs{Wq)*aNTqp+7DL(Tr~7=mVDm;y~GOaJIkD8 zKCQx^ZTlp=msk;>1nMDM)qVXHEB|*O|36$Wj}=f9qV#Bfso_d8EUR1yTY*+BBB4mm zhvid)U#cJuR>3-O6}XYL8^d~um7f5s!lqXLD^^xBn=e+fIlok{sFlqStHMNUixpT~ zYgc3y4BPxpu#LBUY2z;=8SV4;+X61gs&d_baQjbv491yBQ4`P%T=@MW-$t`ykae@gJccIMfK_g!GXI4&D`Tu)k>x+$ zmY-nr;UagGrXWbWnO}081}l(TC0bUpm|qHXrq#tt-pMcd&$9Mi)}EtGUSb6{&)QRW2_E3&G4)an&k{*R-JSHSXn()v~4 zBDaH2*$lBZ=5w~dI#|o>MXPUv`SHDG`3+bVZ@0VyR)afX4di>){)f%~0OrT{q2;mw zi&T!ESm6Myk@(WuM_^U>wY9&o{2i)T^)`qPOEATp6 zpl6Y|%nFyo+IUyN{P>z%y@lnLur^xQ+U;Py#A=`$tUImSU}ep;JPTIG=E3Ul{g#VL zY~m4EFR>z91WQ_Kc^OP+d{0>WNmwKBwBA2tN$mK|9#prs%U{NAXdf$uzLKk)hn_Zc*OcGviV}|w&m6qtDY6s7OUJ!Ym1ft zl=~)A37a8S#wu%z72va0Ukz(Ft+V-m#VY^2&HqoVfQmL)N3jZSw01?-NNlmXSOvFQ z|5vR3->~Z6X8py~Gu-!@6~uCQ-SQijx7&QN@^`=r;4N$KwE1GKp%1OT*K(QionwF0 zEK;d*S^vTs-vieBAgu0w3F{L0Ev$0KVZFqP;(Nl#o86vyF2;KI-Ir+ z;%XGALM>|OT(}9G2Fs-*tO`59dWmIsw)x$yUXhjG!|Gzy)3*{!UD~z$lEXmD8L&oR zu(hv)^%85P-3Y6K9GgGf@(9bhumT@x?NP8^VkO60eL|5HrogIj8mxwkEzf`z*zMN7 z6V^*Czq?=sbdR;?!)n;Ex`Fi)%kLq}i>w~6gfbqrf>;ecCN}W_PD9nV98>MBgysGW zEPb`rpR@V~YrhDqnwPA-#qukbU$eX&){=S`R(g404sn=uts1yta7(oz7y6@{& z|A5{0|Bp7P0a{N7YQk^Zj{JM}+W#*cH9r4Mh5tjp$ddT02(=X6p$F>Gd$uR4KEWcA zR@VRSljV550n$sX22(8m{b+d2`N6UZpNLg1)>AH{pPmh%4r+i&t|9-UW-CZ28- z^N>+DoPR%B{;NmIIy|KR{b-qi`1{fFUp?{F68S%-H?)t8|EfY==*~P^USAgPZ&}6U zT7ry)CTe4vHHT8nY*AZtP?TztTR>@Mz9?*th}xO< zEur?NMAX3?7j-o0t)Nb3iKw$VCF)}OwT8Ny6{2p&p9rO!Oi_2UTGYdYlAxX@Thz;J zgv^E{Ivk%&hx?ekWQ5#ggk2K)nYcCxwb~#|YlCo&*&$)OgoG4?0j4koVM+=@xrBkH zQCogBY>P0rEkdR#ldxApYAV8DGdmSwRw}|V30WpN4IwEFp)?KQ26IHhVF|s$2-&8D z?+!By!~WQw>%V?w+hq@|-+9fGdukl~DEf~JF7LA;Z_CxcEvxdvt`9rCbL@k`4N9Zy zT=#qn-xK+d`<(qdMy>1|{6~Y#qrE?xv+}@KE#0SwzgPVo9%|CtQEj(&w6m%m?c|tK z5>86UYL76&tZ0w0ygfo}2ZTJ6*#RM=1HvW=qfDqHLQF@5{Ei6uW}}1+65=}{j5T?k z5OO;q?2<6v#C1lf)fr)0XM_T?L&A0m30)8-nZhmzQ@S9OOPFjLbwy~{6=7~yghEp$ zVXuVLZU|G&>~09Nx*;5sFwG>VBP69Gl%^vTnIjSoOX$@dVY(^lj*J&2VtJc>w}Qn2Vs|l`6jL}Lan|C)A}MfW`~6B5)%3$EHH)r5cpq67!L`?G`bq0 z;nfIpuSO^_WfJyENWBK(Av60Lgjv@h9FtILlKUeh^+zb}kFdxbk#JZ-uK@^4OvwO* zg#!>yOL)|zUyIP~T7*^CA}lkfB%G9x#dh*PVO9)8SUwOTHUnXW$;?2=$UxX6VWkOW zBE)1OlfNb*At- zgelh{luKA|8f76g%tDx(g|N|-N!TkP^?HOC&Ft$DW?hePOu{CUd;>z#4G5(-AZ#&5 zBpjB|>qdmFrsPJ1g*PIcmaxsFXCrjWMp%`N@R~U#;iQDDAqa1n6+;k~4?&0>im=0E z4n@cqim*w-TP8FNA!Zmt{xF1HW}}1+65?|Z-Z6PO2)Q{3yCl44;)WyC8jdh+IKpnT zL&A0m2_q0bFoh!!ri?%+m++x!l#9?X7h!HLLYXO(uvbEA9>T|Fb{@j4JcMHs%1!b} zgrt!Or6UpcnJ-9{s<8jWzsoRV--LRLP)mu5vi z!t#8C*f9u4Oy(Gbj4=qCBpfxNu?R6^5%R|(95WjwY>*H?4&hsqHx40poWF{Bdz`GU zgkR0)&+9XdxNScIDI?3N7@Q2TQHOYUiIV`2uO}O|?$xXN{yb0m7gea3f z8KK)`gjJIfg65QjlM=F~AXGLhrXVbzf)HDX5HgvC2pNS4nDTaGeWG{ zC}D$y_^Ak0P2N<5+^Gn=BvdnTw;wojz~Bxq1SYT`le($ z!oukYrzKos(r-iPb{oQ~+Yl}@rzD(|kTnCLfmtyFVfhS%*xL~rnatY}GHyrMB%!eh z%|wWqiI6`NA;D~vut7rn9SB#MygLwb??Bikp_z%h6QS0f2-EIFXkm6p*e)Sq7D6ji zI16FQEQE3iiKfwPgod*b=FUb)Hf0j_N=Us6A;rwT3t`q>2*)I(n&deMNplcN=OBd5 z5ebJS^tv0Ny(zgHVd32frzLbW>2ndf%|%!>7ooE`CE=umta}i;nicmTEWZaKb{;~y z$()CfF%MyrgdQezFG9?{2>JIS^fDVIY>*H?AEA%Qn~#t?A7Ph-ekSfdgj)9@OuG-^ z8nZ*fb_od%!T?j~AWU%($|Vdmjqc}H!}}5D-j9%J$|UTSkh%b2u$jF8Vb%hKV-m7V z@&gD-4^Um{Sr?O2{ff7-3eF zAS^FIhllL$}?!ySXB#bw4 zr3kf3IZ17DvT3vkq2VHgxr-1AO__wf5>gi< zOf|C?Bg|Tia7@B9le`2WX$eB<5`-diM8aVSy_OoJ5o&5FkmmOq9Ny9{Br$y|nzu?%68ggGYkI6}

FjA%rzS&Y>*KD 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 diff --git a/web/compose.dev.yml b/web/compose.dev.yml new file mode 100644 index 0000000..bf79ffc --- /dev/null +++ b/web/compose.dev.yml @@ -0,0 +1,21 @@ +name: sparkles-web +services: + postgres: + image: postgres + restart: always + environment: + - POSTGRES_DB=sparkles-db + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + ports: + - "5432:5432" + volumes: + - pg_data:/var/lib/postgresql/data + +volumes: + pg_data: \ No newline at end of file diff --git a/web/drizzle.config.ts b/web/drizzle.config.ts index 9abd61a..898210f 100644 --- a/web/drizzle.config.ts +++ b/web/drizzle.config.ts @@ -3,11 +3,8 @@ import { defineConfig } from "drizzle-kit"; export default defineConfig({ schema: "./src/db/schema.ts", out: "./migrations", - dialect: "sqlite", - driver: "d1-http", + dialect: "postgresql", dbCredentials: { - accountId: process.env.CF_ID!, - databaseId: process.env.D1_ID!, - token: process.env.D1_KEY!, + url: process.env.DB_URL!, }, }); diff --git a/web/migrations/0000_cheerful_blob.sql b/web/migrations/0000_cheerful_blob.sql deleted file mode 100644 index e803df3..0000000 --- a/web/migrations/0000_cheerful_blob.sql +++ /dev/null @@ -1,54 +0,0 @@ -CREATE TABLE `config` ( - `id` text PRIMARY KEY NOT NULL, - `created_at` text DEFAULT (current_timestamp) NOT NULL, - `topic` text NOT NULL, - `duration` integer DEFAULT 30 NOT NULL, - `style` text NOT NULL, - `user_google_id` text NOT NULL, - FOREIGN KEY (`user_google_id`) REFERENCES `user`(`google_id`) ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -CREATE TABLE `generated_script` ( - `id` text PRIMARY KEY NOT NULL, - `created_at` text DEFAULT (current_timestamp) NOT NULL, - `script` text DEFAULT '[]' NOT NULL, - `user_google_id` text NOT NULL, - `config_id` text, - FOREIGN KEY (`user_google_id`) REFERENCES `user`(`google_id`) ON UPDATE no action ON DELETE cascade, - FOREIGN KEY (`config_id`) REFERENCES `config`(`id`) ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -CREATE TABLE `generations` ( - `id` text PRIMARY KEY NOT NULL, - `created_at` text DEFAULT (current_timestamp) NOT NULL, - `speech_url` text, - `captions_url` text, - `video_url` text, - `images` text DEFAULT '[]' NOT NULL, - `config_id` text NOT NULL, - `script_id` text, - `user_google_id` text NOT NULL, - `status` text DEFAULT 'pending' NOT NULL, - `error` text, - `updated_at` text DEFAULT (current_timestamp) NOT NULL, - FOREIGN KEY (`config_id`) REFERENCES `config`(`id`) ON UPDATE no action ON DELETE cascade, - FOREIGN KEY (`script_id`) REFERENCES `generated_script`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`user_google_id`) REFERENCES `user`(`google_id`) ON UPDATE no action ON DELETE cascade -); ---> statement-breakpoint -CREATE TABLE `session` ( - `id` text PRIMARY KEY NOT NULL, - `user_id` integer NOT NULL, - `expires_at` integer NOT NULL, - FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `user` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `email` text NOT NULL, - `username` text NOT NULL, - `google_id` text NOT NULL, - `picture` text NOT NULL -); ---> statement-breakpoint -CREATE UNIQUE INDEX `user_google_id_unique` ON `user` (`google_id`); \ No newline at end of file diff --git a/web/migrations/meta/0000_snapshot.json b/web/migrations/meta/0000_snapshot.json deleted file mode 100644 index d515bf8..0000000 --- a/web/migrations/meta/0000_snapshot.json +++ /dev/null @@ -1,390 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "f41be609-4fa8-4c14-9201-000c1f81aa8d", - "prevId": "00000000-0000-0000-0000-000000000000", - "tables": { - "config": { - "name": "config", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(current_timestamp)" - }, - "topic": { - "name": "topic", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "duration": { - "name": "duration", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 30 - }, - "style": { - "name": "style", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "user_google_id": { - "name": "user_google_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "config_user_google_id_user_google_id_fk": { - "name": "config_user_google_id_user_google_id_fk", - "tableFrom": "config", - "tableTo": "user", - "columnsFrom": [ - "user_google_id" - ], - "columnsTo": [ - "google_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "generated_script": { - "name": "generated_script", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(current_timestamp)" - }, - "script": { - "name": "script", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'[]'" - }, - "user_google_id": { - "name": "user_google_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "config_id": { - "name": "config_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "generated_script_user_google_id_user_google_id_fk": { - "name": "generated_script_user_google_id_user_google_id_fk", - "tableFrom": "generated_script", - "tableTo": "user", - "columnsFrom": [ - "user_google_id" - ], - "columnsTo": [ - "google_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "generated_script_config_id_config_id_fk": { - "name": "generated_script_config_id_config_id_fk", - "tableFrom": "generated_script", - "tableTo": "config", - "columnsFrom": [ - "config_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "generations": { - "name": "generations", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(current_timestamp)" - }, - "speech_url": { - "name": "speech_url", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "captions_url": { - "name": "captions_url", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "video_url": { - "name": "video_url", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "images": { - "name": "images", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'[]'" - }, - "config_id": { - "name": "config_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "script_id": { - "name": "script_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_google_id": { - "name": "user_google_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'pending'" - }, - "error": { - "name": "error", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(current_timestamp)" - } - }, - "indexes": {}, - "foreignKeys": { - "generations_config_id_config_id_fk": { - "name": "generations_config_id_config_id_fk", - "tableFrom": "generations", - "tableTo": "config", - "columnsFrom": [ - "config_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "generations_script_id_generated_script_id_fk": { - "name": "generations_script_id_generated_script_id_fk", - "tableFrom": "generations", - "tableTo": "generated_script", - "columnsFrom": [ - "script_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "generations_user_google_id_user_google_id_fk": { - "name": "generations_user_google_id_user_google_id_fk", - "tableFrom": "generations", - "tableTo": "user", - "columnsFrom": [ - "user_google_id" - ], - "columnsTo": [ - "google_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "username": { - "name": "username", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "google_id": { - "name": "google_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "picture": { - "name": "picture", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_google_id_unique": { - "name": "user_google_id_unique", - "columns": [ - "google_id" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/web/migrations/meta/_journal.json b/web/migrations/meta/_journal.json deleted file mode 100644 index 1e427a0..0000000 --- a/web/migrations/meta/_journal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "7", - "dialect": "sqlite", - "entries": [ - { - "idx": 0, - "version": "6", - "when": 1730546008204, - "tag": "0000_cheerful_blob", - "breakpoints": true - } - ] -} \ No newline at end of file diff --git a/web/package.json b/web/package.json index d3775c4..dd69835 100644 --- a/web/package.json +++ b/web/package.json @@ -3,14 +3,15 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --turbo", + "dev": "docker compose -f compose.dev.yml up -d && NODE_OPTIONS='--inspect' next dev", "build": "next build", "start": "next start", "lint": "next lint", "db:gen": "drizzle-kit generate", - "db:mig": "drizzle-kit migrate", - "db:push": "drizzle-kit push", - "debug": "NODE_OPTIONS='--inspect' next dev" + "db:mig": "bun --env-file=.env.development drizzle-kit migrate", + "db:push": "bun --env-file=.env.development drizzle-kit push", + "turbo": "docker compose -f compose.dev.yml up -d && NODE_OPTIONS='--inspect' next dev --turbo", + "db:stu": "bun --env-file=.env.development drizzle-kit studio" }, "dependencies": { "@ai-sdk/google": "^0.0.51", @@ -36,11 +37,12 @@ "assemblyai": "^4.7.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "drizzle-orm": "^0.34.1", + "drizzle-orm": "^0.36.0", "framer-motion": "^11.11.7", "lucide-react": "^0.451.0", "next": "15.0.0-rc.0", "p-queue": "^8.0.1", + "postgres": "3.4.4", "react": "19.0.0-rc-f994737d14-20240522", "react-dom": "19.0.0-rc-f994737d14-20240522", "react-hook-form": "^7.53.0", @@ -53,7 +55,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", - "drizzle-kit": "^0.25.0", + "drizzle-kit": "^0.27.1", "eslint": "^8", "eslint-config-next": "15.0.0-rc.0", "postcss": "^8", diff --git a/web/src/app/(auth)/login/google/callback/route.ts b/web/src/app/(auth)/login/google/callback/route.ts index 276a60f..7b67809 100644 --- a/web/src/app/(auth)/login/google/callback/route.ts +++ b/web/src/app/(auth)/login/google/callback/route.ts @@ -52,7 +52,7 @@ export async function GET(request: NextRequest) { if (existingUser !== null) { const sessionToken = generateSessionToken(); - const session = await createSession(sessionToken, existingUser.id); + const session = await createSession(sessionToken, existingUser.googleId); setSessionTokenCookie(sessionToken, session.expiresAt); return new Response(null, { status: 302, @@ -70,7 +70,7 @@ export async function GET(request: NextRequest) { }); const sessionToken = generateSessionToken(); - const session = await createSession(sessionToken, user.id); + const session = await createSession(sessionToken, user.googleId); setSessionTokenCookie(sessionToken, session.expiresAt); return new Response(null, { diff --git a/web/src/db/db-fns.ts b/web/src/db/db-fns.ts index dba274d..e5aee1f 100644 --- a/web/src/db/db-fns.ts +++ b/web/src/db/db-fns.ts @@ -34,37 +34,16 @@ export async function storeScript( configId: string, userGoogleId: string ): Promise { - const scriptId = randomUUID(); - await db.insert(generatedScriptsTable).values({ - id: scriptId, - script: script, - userGoogleId, - configId, - }); - - return scriptId; -} + const storedScript = await db + .insert(generatedScriptsTable) + .values({ + script: script, + userGoogleId, + configId, + }) + .returning({ id: generatedScriptsTable.id }); -export async function storeGeneration(data: { - speechUrl: string; - captionsUrl: string | null; - images: string[]; - configId: string; - scriptId: string; - userGoogleId: string; -}): Promise { - const generationId = randomUUID(); - await db.insert(generationsTable).values({ - id: generationId, - speechUrl: data.speechUrl, - captions_url: data.captionsUrl ?? "", - images: data.images, - configId: data.configId, - scriptId: data.scriptId, - userGoogleId: data.userGoogleId, - video_url: null, - }); - return generationId; + return storedScript[0].id; } export async function storeGeneratedVideo({ @@ -78,14 +57,14 @@ export async function storeGeneratedVideo({ }) { return db .update(generationsTable) - .set({ video_url: r2Url }) + .set({ videoUrl: r2Url }) .where( and( eq(generationsTable.configId, configId), eq(generationsTable.userGoogleId, userGoogleId) ) ) - .returning({ url: generationsTable.video_url }); + .returning({ url: generationsTable.videoUrl }); } export async function getGenerationById(id: string, userGoogleId: string) { @@ -142,8 +121,8 @@ export async function getAllGenerationsByConfigId( id: generationsTable.id, createdAt: generationsTable.createdAt, speechUrl: generationsTable.speechUrl, - captionsUrl: generationsTable.captions_url, - videoUrl: generationsTable.video_url, + captionsUrl: generationsTable.captionsUrl, + videoUrl: generationsTable.videoUrl, images: generationsTable.images, configId: generationsTable.configId, scriptId: generationsTable.scriptId, @@ -237,6 +216,6 @@ export async function updateGenerationStatus( ) { await db .update(generationsTable) - .set({ status, error, updatedAt: new Date().toISOString() }) + .set({ status, error }) .where(eq(generationsTable.id, id)); } diff --git a/web/src/db/db.ts b/web/src/db/db.ts index 69f564b..33be931 100644 --- a/web/src/db/db.ts +++ b/web/src/db/db.ts @@ -1,48 +1,9 @@ -import { drizzle } from "drizzle-orm/sqlite-proxy"; import * as schema from "@/db/schema"; - -const { CF_ID, D1_ID, D1_KEY } = process.env; - -export const db = drizzle( - async (sql, params, method) => { - const url = `https://api.cloudflare.com/client/v4/accounts/${CF_ID!}/d1/database/${D1_ID!}/query`; - - const res = await fetch(url, { - method: "POST", - headers: { - Authorization: `Bearer ${D1_KEY!}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ sql, params, method }), - }); - - const data = await res.json(); - - // console.log("Response from cloudflare d1:", res); - // console.log("Response from sqlite proxy server:", data); - - if (res.status !== 200) - throw new Error( - `Error from sqlite proxy server: ${res.status} ${ - res.statusText - }\n${JSON.stringify(data)}` - ); - if (data.errors.length > 0 || !data.success) - throw new Error( - `Error from sqlite proxy server: \n${JSON.stringify(data)}}` - ); - - const qResult = data.result[0]; - - console.log("DB Result: ", { result: data.result[0].results }); - - if (!qResult.success) - throw new Error( - `Error from sqlite proxy server: \n${JSON.stringify(data)}` - ); - - // https://orm.drizzle.team/docs/get-started-sqlite#http-proxy - return { rows: qResult.results.map((r: any) => Object.values(r)) }; - }, - { schema } -); +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; + +const client = postgres(process.env.DB_URL!); +export const db = drizzle(client, { + schema, + // logger: process.env.NODE_ENV === "development" ? true : false, +}); diff --git a/web/src/db/schema.ts b/web/src/db/schema.ts index ab14af1..0317944 100644 --- a/web/src/db/schema.ts +++ b/web/src/db/schema.ts @@ -1,55 +1,56 @@ -import { InferSelectModel, relations, sql } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { - integer, - sqliteTable, + pgTable, text, - SQLiteText, - SQLiteInteger, -} from "drizzle-orm/sqlite-core"; + timestamp, + integer, + varchar, + uuid, + pgEnum, + serial, + jsonb, +} from "drizzle-orm/pg-core"; +import { type InferSelectModel } from "drizzle-orm"; -export const generationsTable = sqliteTable("generations", { +export const generationStatusEnum = pgEnum("generation_status", [ + "pending", + "script_ready", + "speech_ready", + "images_ready", + "captions_ready", + "complete", + "failed", +]); + +export const generationsTable = pgTable("generations", { id: text("id").notNull().primaryKey(), - createdAt: text("created_at") + createdAt: timestamp("created_at", { withTimezone: true }) .notNull() - .default(sql`(current_timestamp)`), + .defaultNow(), speechUrl: text("speech_url"), - captions_url: text("captions_url"), - video_url: text("video_url"), - images: text("images", { mode: "json" }) + captionsUrl: text("captions_url"), + videoUrl: text("video_url"), + images: text("images") + .array() .notNull() - .$type() - .default(sql`'[]'`), + .default(sql`ARRAY[]::text[]`), configId: text("config_id") .notNull() .references(() => configTable.configId, { onDelete: "cascade" }), - scriptId: text("script_id").references(() => generatedScriptsTable.id), + scriptId: uuid("script_id").references(() => generatedScriptsTable.id), userGoogleId: text("user_google_id") .notNull() .references(() => userTable.googleId, { onDelete: "cascade" }), - status: text("status", { - enum: [ - "pending", - "script_ready", - "speech_ready", - "images_ready", - "captions_ready", - "complete", - "failed", - ], - }) - .notNull() - .default("pending"), + status: generationStatusEnum("status").notNull().default("pending"), error: text("error"), - updatedAt: text("updated_at") + updatedAt: timestamp("updated_at", { withTimezone: true }) .notNull() - .default(sql`(current_timestamp)`), + .defaultNow(), }); -export const configTable = sqliteTable("config", { +export const configTable = pgTable("config", { configId: text("id").notNull().primaryKey(), - createdAt: text("created_at") - .notNull() - .default(sql`(current_timestamp)`), + createdAt: timestamp("created_at").notNull().defaultNow(), topic: text("topic").notNull(), duration: integer("duration").notNull().default(30), style: text("style").notNull(), @@ -58,15 +59,13 @@ export const configTable = sqliteTable("config", { .references(() => userTable.googleId, { onDelete: "cascade" }), }); -export const generatedScriptsTable = sqliteTable("generated_script", { - id: text("id").notNull().primaryKey(), - createdAt: text("created_at") - .notNull() - .default(sql`(current_timestamp)`), - script: text("script", { mode: "json" }) - .notNull() +export const generatedScriptsTable = pgTable("generated_script", { + id: uuid("id").primaryKey().defaultRandom(), + createdAt: timestamp("created_at").notNull().defaultNow(), + script: jsonb("script") .$type>() - .default(sql`'[]'`), + .notNull() + .default([]), userGoogleId: text("user_google_id") .notNull() .references(() => userTable.googleId, { onDelete: "cascade" }), @@ -75,23 +74,21 @@ export const generatedScriptsTable = sqliteTable("generated_script", { }), }); -export const userTable = sqliteTable("user", { - id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), +export const userTable = pgTable("user", { + googleId: text("google_id").primaryKey(), email: text("email").notNull(), username: text("username").notNull(), - googleId: text("google_id").notNull().unique(), picture: text("picture").notNull(), }); -export const sessionTable = sqliteTable("session", { - id: text("id").notNull().primaryKey(), - userId: integer("user_id", { mode: "number" }) +export const sessionTable = pgTable("session", { + id: text("id").primaryKey(), + userId: text("user_id") .notNull() - .references(() => userTable.id), - expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), + .references(() => userTable.googleId, { onDelete: "cascade" }), + expiresAt: timestamp("expires_at").notNull(), }); -// Relations remain the same export const userRelations = relations(userTable, ({ many }) => ({ sessions: many(sessionTable), generations: many(generationsTable), @@ -102,7 +99,7 @@ export const userRelations = relations(userTable, ({ many }) => ({ export const sessionRelations = relations(sessionTable, ({ one }) => ({ user: one(userTable, { fields: [sessionTable.userId], - references: [userTable.id], + references: [userTable.googleId], }), })); @@ -113,8 +110,10 @@ export const generatedScriptsRelations = relations( fields: [generatedScriptsTable.userGoogleId], references: [userTable.googleId], }), - generations: many(generationsTable), - configs: one(configTable), + config: one(configTable, { + fields: [generatedScriptsTable.configId], + references: [configTable.configId], + }), }) ); @@ -123,8 +122,8 @@ export const configRelations = relations(configTable, ({ one, many }) => ({ fields: [configTable.userGoogleId], references: [userTable.googleId], }), - script: one(generatedScriptsTable), generations: many(generationsTable), + generatedScriptsRelations: many(generatedScriptsTable), })); export const generationsRelations = relations(generationsTable, ({ one }) => ({ diff --git a/web/src/lib/generation-service.ts b/web/src/lib/generation-service.ts index cd529b9..80c92b5 100644 --- a/web/src/lib/generation-service.ts +++ b/web/src/lib/generation-service.ts @@ -52,12 +52,12 @@ export class GenerationService { } : undefined, images: generation.images, - captions: generation.captions_url + captions: generation.captionsUrl ? { - url: generation.captions_url, + url: generation.captionsUrl, signedUrl: await makeSignedUrl( this.r2, - generation.captions_url.split(".com/")[1], + generation.captionsUrl.split(".com/")[1], this.bucketName ), } @@ -72,7 +72,7 @@ export class GenerationService { .update(generationsTable) .set({ speechUrl: state.speech?.url, - captions_url: state.captions?.url, + captionsUrl: state.captions?.url, images: state.images, status: state.status, error: state.error, diff --git a/web/src/lib/session.ts b/web/src/lib/session.ts index 45e40bb..3f91415 100644 --- a/web/src/lib/session.ts +++ b/web/src/lib/session.ts @@ -16,7 +16,7 @@ export function generateSessionToken(): string { export async function createSession( token: string, - userId: number + userId: string ): Promise { const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); const session: Session = { @@ -37,7 +37,7 @@ export async function validateSessionToken( const result = await db .select({ user: userTable, session: sessionTable }) .from(sessionTable) - .innerJoin(userTable, eq(sessionTable.userId, userTable.id)) + .innerJoin(userTable, eq(sessionTable.userId, userTable.googleId)) .where(eq(sessionTable.id, sessionId)); if (result.length < 1) {