From bf97b6c542aa7cc4f8e8422572b821b5794715a9 Mon Sep 17 00:00:00 2001 From: pandeptwidyaop Date: Thu, 15 Aug 2024 19:28:53 +0800 Subject: [PATCH] Done all --- .gitignore | 4 ++- Dockerfile | 49 ++++++++++++++++++++++++++++++++++ dev.Dockerfile | 22 +++++++++++++-- docker-compose.yml | 2 ++ gosrc/.air.toml | 41 ++++++++++++++++++++++++++++ gosrc/.gitignore | 1 + gosrc/go.mod | 18 +++++++++++++ gosrc/go.sum | 27 +++++++++++++++++++ gosrc/main.go | 56 +++++++++++++++++++++++++++++++++++++++ src/Command/Generate.php | 6 ++--- src/Register.php | 2 +- test.docx | Bin 7037 -> 0 bytes 12 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 Dockerfile create mode 100644 gosrc/.air.toml create mode 100644 gosrc/.gitignore create mode 100644 gosrc/go.mod create mode 100644 gosrc/go.sum create mode 100644 gosrc/main.go delete mode 100644 test.docx diff --git a/.gitignore b/.gitignore index 7399652..74e3563 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /vendor **/.DS_Store -**/.~lock* \ No newline at end of file +**/.~lock* +app +build \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..27a13c1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +FROM golang:1.23-alpine AS build + +COPY ./gosrc/ /build + +WORKDIR /build + +RUN go mod download + +RUN go build -ldflags="-s -w" -o ./dist/app + + +FROM php:8.2-cli + +# Install dependencies needed for Composer and PHP zip extension +RUN echo "deb http://deb.debian.org/debian bookworm contrib non-free" > /etc/apt/sources.list.d/contrib.list +RUN apt-get update && apt-get install -y \ + curl \ + unzip \ + libzip-dev \ + libreoffice \ + fontconfig \ + gcc \ + g++ \ + build-essential \ + ttf-mscorefonts-installer \ + && docker-php-ext-install zip \ + && rm -rf /var/lib/apt/lists/* + +# Install fonts +RUN mkdir -p /usr/share/fonts/truetype/custom +COPY ./fonts/gilroy/* /usr/share/fonts/truetype/custom +COPY ./fonts/lexend/* /usr/share/fonts/truetype/custom +RUN fc-cache -f -v + +# Download and install Composer +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +# Verify Composer installation +RUN composer --version + +WORKDIR /app + +COPY . . + +COPY --from=build /build/dist/app . + +RUN rm -rf gosrc + +CMD [ "/app/app" ] \ No newline at end of file diff --git a/dev.Dockerfile b/dev.Dockerfile index 06a8eea..af1bfae 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -8,6 +8,9 @@ RUN apt-get update && apt-get install -y \ libzip-dev \ libreoffice \ fontconfig \ + gcc \ + g++ \ + build-essential \ ttf-mscorefonts-installer \ && docker-php-ext-install zip \ && rm -rf /var/lib/apt/lists/* @@ -24,6 +27,21 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local # Verify Composer installation RUN composer --version -WORKDIR /app +# Install Golang +RUN curl -OL https://go.dev/dl/go1.23.0.linux-arm64.tar.gz \ + && tar -C /usr/local -xzf go1.23.0.linux-arm64.tar.gz \ + && rm go1.23.0.linux-arm64.tar.gz -CMD ["tail","-f", "/dev/null"] +# Set Go environment variables +ENV PATH="/usr/local/go/bin:${PATH}" + +# Verify Go installation +RUN go version + +RUN curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b /usr/local/bin + +WORKDIR /app/gosrc + +CMD ["air", "-c", ".air.toml", "--", "run"] + +EXPOSE 80 diff --git a/docker-compose.yml b/docker-compose.yml index fe79d9b..571f7fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,3 +5,5 @@ services: dockerfile: dev.Dockerfile volumes: - ./:/app + ports: + - 8080:80 diff --git a/gosrc/.air.toml b/gosrc/.air.toml new file mode 100644 index 0000000..0e22436 --- /dev/null +++ b/gosrc/.air.toml @@ -0,0 +1,41 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "build" + +[build] +args_bin = [] +bin = "./../app" +cmd = "go build -gcflags='all=-N -l' -o ./../app ./main.go" +delay = 0 +exclude_dir = ["assets", "build", "vendor", "testdata", "storage"] +exclude_file = [] +exclude_regex = ["_test.go"] +exclude_unchanged = false +follow_symlink = false +include_dir = [] +include_ext = ["go", "tpl", "tmpl", "html", "yaml"] +include_file = [] +kill_delay = "0s" +log = "build-errors.log" +rerun = false +rerun_delay = 500 +send_interrupt = false +stop_on_error = true + +[color] +app = "" +build = "yellow" +main = "magenta" +runner = "green" +watcher = "cyan" + +[log] +main_only = true +time = true + +[misc] +clean_on_exit = false + +[screen] +clear_on_rebuild = false +keep_scroll = true diff --git a/gosrc/.gitignore b/gosrc/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/gosrc/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/gosrc/go.mod b/gosrc/go.mod new file mode 100644 index 0000000..c502e66 --- /dev/null +++ b/gosrc/go.mod @@ -0,0 +1,18 @@ +module menulis.ai/genpdf + +go 1.22.0 + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/gofiber/fiber/v2 v2.52.5 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.15.0 // indirect +) diff --git a/gosrc/go.sum b/gosrc/go.sum new file mode 100644 index 0000000..fab6978 --- /dev/null +++ b/gosrc/go.sum @@ -0,0 +1,27 @@ +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= +github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/gosrc/main.go b/gosrc/main.go new file mode 100644 index 0000000..b80f21d --- /dev/null +++ b/gosrc/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "path" + "strings" + + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) + +var rootPath string + +func main() { + rootPath = os.Getenv("APP_ROOT_PATH") + if rootPath == "" { + rootPath = "/app" + } + + app := fiber.New() + + app.Post("ebook", handleEbook) + + log.Fatal(app.Listen(":80")) +} + +func handleEbook(c *fiber.Ctx) error { + + f, err := c.FormFile("file") + if err != nil { + return c.Status(fiber.StatusBadRequest).SendString(fmt.Sprintf("error receiving file: %s", err)) + } + + sp := path.Join(rootPath, fmt.Sprintf("storage/%s.json", uuid.NewString())) + + err = c.SaveFile(f, sp) + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed to save file: %s", err)) + } + + cmd := exec.Command(path.Join(rootPath, "genpdf"), "generate:ebook", sp) + + o, err := cmd.Output() + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed to convert ebook: %s", err)) + } + err = c.Download(strings.TrimSpace(string(o))) + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed to sending ebook: %s", err)) + } + + return nil +} diff --git a/src/Command/Generate.php b/src/Command/Generate.php index 63a15c9..e9b32d6 100644 --- a/src/Command/Generate.php +++ b/src/Command/Generate.php @@ -12,7 +12,7 @@ use Symfony\Component\Process\Exception\ProcessFailedException; class Generate extends Command { - protected static $defaultName = "generate"; + protected static $defaultName = "generate:ebook"; protected function configure() { @@ -35,7 +35,7 @@ class Generate extends Command "--convert-to", "pdf", "--outdir", - "storage/", + "/app/storage/", $path ]); @@ -45,7 +45,7 @@ class Generate extends Command throw new ProcessFailedException($cmd); } - $output->writeln($cmd->getOutput()); + $output->writeln(rootPath("storage/" . pathinfo($path, PATHINFO_FILENAME) . ".pdf")); return Command::SUCCESS; } diff --git a/src/Register.php b/src/Register.php index ab1c022..b08ba7e 100644 --- a/src/Register.php +++ b/src/Register.php @@ -13,7 +13,7 @@ class Register public function init(): array { return [ - new Generate("generate") + new Generate("generate:ebook") ]; } } diff --git a/test.docx b/test.docx deleted file mode 100644 index 24c14e9ee3138184790b2b65f7b8e7eb6877e290..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7037 zcmaJ`1z1#DyB;K^rMqijD5a%KdH^Zu8cJYjBpehe32B9q?(UG5#sMjjZbV8N8L7h^ zJ?H$M!+-Cc^*pnmy`Op4n*Duizu)`4TU#9+gA4!w-~jH*6Fy2Q@5Nm}ohjY|0Ps*p z3lE61CqLiM@A8BNHFP=r(ESTCg}s&;1LFAf-58uU(qbZFyP6h-`!cr5<dCAgI;yzho#4dM>s~?4#!5-xeUaU89G+8*_UuhlL(qD&$?~e_iscL+tB#tw zqT#iwkYQkVt8(=Z?^Q5dKfD&YxrCWi&1W_v}dgwlazpGR#SxUdKnkfzdl4__`?YQ;0rRhDLs; z7@Ej|GJ>SwUY)xZX*BQLvpkU_-1QIM5H@cH_^ro?b_cX4#Qip)P8}`!-g>K|fCpYR zU&I9D%V=K>=6488>2Zr8vddIyxVE(i!hjI^j;UMFj*M5!B z!ONKWR!1RwTVGJt&)+FT0DS_Ds1?m|(0gd-0M>4t6-Z2^Y$ZvMIL1vK|K)Yh8j|eV z--5Qm9dZPhLTB`>8p7GVBrrL8(6yFOV!cC|@dB6eYfK*ab0MhMTRrHc9su(_VKDSC zdvKI=v7us}<)o-SGwt&ygx?X_$)zTUO8)V@vyVC65-g9E=1Fb==6w zlMqJ(O;k>lP&py~XHKl#+`RxK0;g++#yiaRf$$mXkJIMO5{)sw@-9xm*D$h>_VD1rR*LoUYn4o8&!u}5ii9y_ z?@ss~F3DSRp~l=34a#dx7*?-gkaA;U>nEYSDepgY0YfKvjUITb+UiWuF|uE~>KKuP zuS74;w{gAccZe)^de4&kg{WX$K5>$_h6{c_yU?p# z@1bYa9(Y~>kzH^piLO2Ue)G+%K0yQB}P9FLRleg>iG=YSo zD7nM(gWsgu;u@g8BkA#w+qD8>t_c3WQm7=rUA)Q)<(*S$sMwKqN~46 z9!lhv{!yWaXvReGnjwk|uBPA9*-sr{Z#T8$agqPv^rbZZBo0>%9Tg4aZKv0*#ht5e zJm*sVv#{K}C+sIL@IaF(Oj(>Bm1)?Yb6+#p5^731mQN+HoY4E~J{=%DlNb@59}&)K z97i)`~U}4(@kBG_y&h{6pseSNJPYa#z zaiUI=h}EotB*5`Lkd{XyzA5F{meoXambGJe6Sd(tOi~;===nD5VGf#^71ZTTE^L1+ z+V(Js6{ZPaan4K4nml{l&?*p9Z#MPgJ`nD6;ix zdd@L`Y$H0&sX0jSiv@jCIX9T%qbP@oC|^3Q_vtHxv^K6l$iAaGo=;7K-`o1kI_{`G zxW@2H1Kq29P+%V}QgO=EEkFzl=D`hb|5!RDe&Ks+K9sq+ok>mAh-FK%1og<@GJco* zMUq)sautLn>MB>R?|(40`okta_6=tMarCz}dh)^Lu|h43T${%kM+K5)kuWg)R-<)& zIc~kEkc8Re?(?_JPo1GKJ9)0vOLAbfiEG4#h#tjos83VN#skp3N%my2e7+Xe9jqpZ|=dDTlq^dT+I~Q%Q2?v#R*6B(P8n=&rk(*Q5F}k%V)-MFMz1efWKuB?E~|VCJ2Z z&6CCauaUhU8Fo&6X#E6-?Hl=*bZ_}ZY9{{w8v8+begQN`2ik@cl^uEQdu$?8Od|jL zK)>zxoUR7cgv`&&D7C*=R<_}(2NDfxx``_3nHigcFVTP&E0xDjN;Kqg(;yk{zC z@i#6AYQ*OhG7}L6C|#Xz7I1xoi7Md;;#!mSa7E}4=Q1hFMF&%1{`ZCL+(;cM0>3Al zk1vs%gH=5S^mp@3yDc8|2@R4mHc62=Q|Ezl^Q+Lt1i^2fw}bnW-?TR$GTB?~M!Fj5 zKXlIc!%aBgO|Zj?g;!f_;~sJ*@Gg`_QhUG6GWB~5$HOJ%Jl;G(o5-u%H(>r=8InEvFKAH#0Fwc!2upSLL}Q(FGYfIC#QLRHb0`Y zwV3kU&#bUnb+O2=`upYy;>TN=8x`=h>M)oARsF_r008p8Y1qpF;sW9S^)2|5gcp)E zp>qQGVfz>4!F#2v?{1GXX$gYxe7LF<8WyNi8$PSlFc+@ZWBRv$Xfv81Wh1Gf+@h3)j52zZj#nRsto|xXir+18N0QrI$ya74-81gBuZSi1;I*I&P+>9QpCZ#^*Cv3N9Rqg5+=ft7br$kr@0**l5#iZmF(<^$` z9ZI6u$}%@Cy%N&{j5Bp2@g&mtua|LI`~2W29C0<9j8%Czc$R3%=-OU)xA<4`xrevi z36qw&r8zf?ys29%SRNp%j3W~~4j}S(%U1Lm=4;~|ftcB9avVJK?~mo*tfb{OM5}1C z`^`@pRi&w9D;7{HRvFno(sF3=qV~{O<5;g{BF{Aybz$eN!gBQQ}SW+K%>8&#k7AR-X+!kGQRXuf>bltcSF~+q z?~75i8X8;YbQlb6wMiAawR7Z&kuM{xRO9SHZe6-Bc0-~h1{Q=jE_(R-k`mHZa@^&< z;yrEqA$>j=b%nQ8R$ynt-|@kJtm>`LZgAG^;0@g(3>WlDLf^{g;^1^2&luXpS1F7` z{Y|(n2Mw)dl*r~T*TqlgA`a+0b?uY{t2Eboi|?`a5rq5xQbQ&eg+~?62Nk;q%8LTf z-AqJFLuX5M7wwM~rpiO``oi=w!R~=;SS8O|CW3Z$d0}=XCT#!bNlSev?V}Yl%x+{d z>US~D2e6uMTEZp6zH4NwEBM8wP8P;BNLy^+Q(r|1sKxmE9iNNA&Y6_i<$?3#ZOzAU z$yD?mBgebglhrl0gk?7DALxww5%J`25Xje@n~s5bWqf)YgNdl>N_6>z7#agm`(OHlXLv9SDh& za_ttt4@G8ljV`L(9+hGBX~{m7rjH8LZgv4YnvyUe2cN-H(q2Uv&E(d?xpF zC!?)jpwhRhn`nCCI5D+#b*Qf&%IvcYw9d5yZI4QSD{N+}EmC8rwJt7HCKxK7Y@S9# zPaV8D*U1a3ahQw$)_R1m^qEev@dXdgW!8%($QRIEN@$2gUoio}$yA3iwNR zo(&`^dSEUUQ64u~Ql(0N6|%&5P3Jr8*P~7iR~t=P z=+Nk#(FpZl9Ed1r_ocYjEpFL5-Lfk-1h>KTFYx~Th}H`M9t)ymcRfA;K=hyZc=zoGW*=5mWoqn*QotE@MaX z+tv1gU(-?rH{O7$i5TQ)Ig}J79=MP^i-2_Xu&^L~a}8oK`gG&9`7jl2F~dyF1Mgd$ z#+bPapq=A=vQTELH~8K?+5@zmpi{-Xh5V{(Ke4LI-<3b{a{MlqjX+>u_nOZI+YlFN z9{#5-gIf^L7g*sHRik%1C>H2~KzIcT_OCP*vEVuk^LSy+vIm}O^1e&x4$rX}fUyIIv$h$c zVXltUa0S{zYn@ryx<12;DVf6Zm7Y{PkX#w?aSZ3MAm}au(XnAT5AsosB47AG#OuHq zHbb@CqZjjton$pR=Vy=772IENyH3YY8~i}#hcEi%YqKc{n>m!|V$VWj5+gLy7UxSj z9kABTLT41t?;Q!>X~&q0*FJCXm4l`3Re#Za6US9^w!XeBUb#(D$fe;dx4(77x?)mji)4_=))_8nZ}3*V<~5M;b?@$Zl6E< z$ZpXAyM5PxT(v2LIy$6V+w<)6E=qYoE{*rUc_~&Fu=+zwL;ZmwC;Hs9qQSh?{6#u( zwUESnXdRwNk~3v~P{hee$hWw*2i}dZwqrTGyETNFp=E=Rj$j`i7t38|)A^Ggs|ku)f8ZYS-G z5Gl*ba&WyH8EY3c;c7ap)sEaY?IIcD)O?PmX0d#z9ph`LM(%VrXnKkH?^dnj67LQZ zssJuy|LH0GVjnvw)C=PAOH6%ER)@~Y68G<4k(IW+*expO=HV#8RTLMJ3oUw3H~pe5 z2AMr090EJvJ6B@B)Jah1FO#Z0etSJ6;O6(8ORWY?$n-;qa(5g_TruL^5UGDh3Sqmp zH7JpqCpjc!@BL`$SookzYlDhul~A>sL+D)*N3`J7=gUCu+)1A}?mMO5;f13NbusTl zC?>bXaAvcz$C=Wh<=O%}=7L1Xv~es-s~_UBT&do=nw|Aq4sUJFM039Fxqv@3 z>!&#C0jbvfnGhV6mZqmET$0(ABU`K|z|?!TbDY3TJZjNlsj*i1)4O3>%5!b-)h9YX zf<^gL!lZbEXm@G!^Y6n|z{Lgvfkk!uDL$lThY53LW%{?nhqE`KivP(ycRc9f#y$%cFa=FC!TycdvY9 zD$jx>s$A_k?(T>dxbOJR8m~1TZZ7`O)~D1-&?K0rdQ!HmY^HrJ7yHLwHp$=KX;p-p zq7kZ&>7ttd|HvsjsH>Nrm9_IP+ps)w!ReMD@lOkt8A(HKj9BFk9WR}fH)OON8g71m z2}&(1hAzG1CR>ny+@^jv@!nFJYQZr$cZNa=XzwY_@JdX9$E^4p$42S;;Bx#uTub%Q zVe4UEl|bp1w|!+EBE;%byb!?+ZyZeyj1M#fqocwO56e6jWmQZ%?u?z)9BQ%X=!zO^ zDCc5Z$v%Q59d^XK>*upc-=CqWTVYz&QBa}RE~iWN{^(wJUT1?;jp;d+_#658C)s9(dS?Jg-n=loqVXa#qX{3iHv+KnW@q#rk$3IXSB4$<`z8#(0^P!GkJ53!NQ|4 zTMhcXZ@8^+1L>k`UzA?y4ADR5A6SfpO`20?6I6s3_uv}S!*-aB&f|Bk`>q_v`!PSZ z->|C~YSeJNQF)le0sv6T7GR=)$^puwve5H$gLs<#R26N>gRWfy#7f9Or7teYRLw;t z+|QL52VY~F8TO@OG#)B6WFH{uIe{hGYDG8;ijnqvQobBD3BwIFks_Aqqui{Cwi+zj zrW^9f)P81?y^Xda^!ZWgXp~l!9@LNgpnF{2ftU$RinQ8Bz`Eq2wNl<YN3&B7x&ef+>Mh zfv)xLKOTYD`YD4#C?~4rSgXw6J1W-wabKGASrI)I<5(M`vH8fT)YX6^nB-b0b^%}RcA>6={r t8!d^lR{lc&gSl_6ev@N<<9jInSH{s+$3)Q)0DzAgSEy~~MD_FRe*g^`ETI4Z