From 47181fa5cab725c7ab259d05491f7490ce6deea3 Mon Sep 17 00:00:00 2001 From: linkitapps Date: Sun, 27 Sep 2026 01:23:47 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Gitea=20=EC=97=B0=EB=8F=99=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=B4=EC=99=84,=20Apache=20=EC=97=AD=EB=B0=A9?= =?UTF-8?q?=ED=96=A5=20=ED=94=84=EB=A1=9D=EC=8B=9C=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20=EC=9A=94=EC=B2=AD=EC=84=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix(config): 로컬 실행 시 루트의 .env 파일을 자동 로드하도록 Settings.from_env 개선 (테스트 실행 시 격리) - fix(ops): 기존 8000번 포트 충돌 방지를 위해 docker-compose.ops.yml 호스트 포트를 127.0.0.1:18000:8000으로 조정 - fix(git-binding): Git 저장소 바인딩 시 projects 테이블의 branch도 함께 동기화하도록 app.py 보완 - ui(projects): 프로젝트 카드의 BRANCH 표시에 바인딩된 브랜치를 우선 표시하고, OWNER 라벨을 '담당'으로 명확화 - feat(deploy): Ubuntu Apache2 웹서버용 Gitea 및 SourceHub 역방향 프록시 설정 템플릿 추가 (deploy/apache-*.conf) - docs: 프로젝트 CRUD 및 사내 개발자 관리 기능 갭 분석 및 개발 요청서 Word 문서 추가 (docs/) --- .env.example | 4 ++-- .env.production.example | 2 +- app.py | 3 +++ deploy/apache-gitea.conf | 22 ++++++++++++++++++ deploy/apache-sourcehub.conf | 21 +++++++++++++++++ docker-compose.ops.yml | 2 +- docs/NEXPA_SourceHub_기능개선_개발요청서.docx | Bin 0 -> 40176 bytes sourcehub/config.py | 16 +++++++++++++ static/index.html | 2 +- 9 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 deploy/apache-gitea.conf create mode 100644 deploy/apache-sourcehub.conf create mode 100644 docs/NEXPA_SourceHub_기능개선_개발요청서.docx diff --git a/.env.example b/.env.example index f665ee9..8dd8dbb 100644 --- a/.env.example +++ b/.env.example @@ -42,8 +42,8 @@ SOURCEHUB_MALWARE_SCAN=disabled # SOURCEHUB_CLAMD_PORT=3310 # ---------- Gitea ---------- -# SOURCEHUB_GITEA_BASE_URL=https://git.example.com -# SOURCEHUB_GITEA_TOKEN= +SOURCEHUB_GITEA_BASE_URL=https://git.nexpa-ai.com +SOURCEHUB_GITEA_TOKEN=CHANGE_ME # ---------- Deployment executor ---------- SOURCEHUB_DEPLOY_MODE=disabled diff --git a/.env.production.example b/.env.production.example index c4f15a1..12a55e0 100644 --- a/.env.production.example +++ b/.env.production.example @@ -38,7 +38,7 @@ SOURCEHUB_ALLOWED_HOSTS=sourcehub.nexpa-ai.com,localhost,127.0.0.1 SOURCEHUB_ENABLE_HSTS=true # Gitea (deploy/gitea — host 3200 / SSH 2222) -SOURCEHUB_GITEA_BASE_URL=http://127.0.0.1:3200 +SOURCEHUB_GITEA_BASE_URL=https://git.nexpa-ai.com SOURCEHUB_GITEA_TOKEN=CHANGE_ME # Deployment executor: keep disabled until CI/CD target is explicitly registered diff --git a/app.py b/app.py index 6ac9610..59ee275 100644 --- a/app.py +++ b/app.py @@ -497,6 +497,9 @@ def bind_git(project_code: str, body: RepoBindIn, request: Request, x_role: Opti conn.execute(update(repo_bindings).where(repo_bindings.c.project_code == project_code).values( owner=body.owner.strip(), repo=body.repo.strip(), branch=body.branch.strip(), updated_at=now_iso(), updated_by=a.role, )) + conn.execute(update(projects).where(projects.c.code == project_code).values( + branch=body.branch.strip(), + )) log(conn, a.role, "Git", "저장소 바인딩 변경", p["name"], {"owner": body.owner, "repo": body.repo, "branch": body.branch}) return git_status(project_code, request, x_role) diff --git a/deploy/apache-gitea.conf b/deploy/apache-gitea.conf new file mode 100644 index 0000000..116c7a2 --- /dev/null +++ b/deploy/apache-gitea.conf @@ -0,0 +1,22 @@ +# Apache Reverse Proxy Configuration for Gitea +# Copy to /etc/apache2/sites-available/gitea.conf +# Required modules: sudo a2enmod proxy proxy_http ssl headers rewrite + + + ServerName git.nexpa-ai.com + + ProxyPreserveHost On + ProxyRequests Off + + ProxyPass / http://127.0.0.1:3200/ + ProxyPassReverse / http://127.0.0.1:3200/ + + RequestHeader set X-Forwarded-Proto "http" + + # Allow large git push uploads (500MB) + LimitRequestBody 524288000 + + +# Note: Once SSL certificate is issued via Certbot: +# Run: sudo certbot --apache -d git.nexpa-ai.com +# Certbot will automatically add the SSL (*:443) VirtualHost and HTTPS redirect. diff --git a/deploy/apache-sourcehub.conf b/deploy/apache-sourcehub.conf new file mode 100644 index 0000000..f8214a5 --- /dev/null +++ b/deploy/apache-sourcehub.conf @@ -0,0 +1,21 @@ +# Apache Reverse Proxy Configuration for NEXPA SourceHub 3.8 +# Copy to /etc/apache2/sites-available/sourcehub.conf +# Required modules: sudo a2enmod proxy proxy_http ssl headers rewrite + + + ServerName sourcehub.nexpa-ai.com + + ProxyPreserveHost On + ProxyRequests Off + + ProxyPass / http://127.0.0.1:18000/ + ProxyPassReverse / http://127.0.0.1:18000/ + + RequestHeader set X-Forwarded-Proto "http" + + LimitRequestBody 524288000 + + +# Note: Once SSL certificate is issued via Certbot: +# Run: sudo certbot --apache -d sourcehub.nexpa-ai.com +# Certbot will automatically add the SSL (*:443) VirtualHost and HTTPS redirect. diff --git a/docker-compose.ops.yml b/docker-compose.ops.yml index 06a806f..a6cfa82 100644 --- a/docker-compose.ops.yml +++ b/docker-compose.ops.yml @@ -26,7 +26,7 @@ services: sourcehub: build: . ports: - - "8000:8000" + - "127.0.0.1:18000:8000" depends_on: - postgres - minio diff --git a/docs/NEXPA_SourceHub_기능개선_개발요청서.docx b/docs/NEXPA_SourceHub_기능개선_개발요청서.docx new file mode 100644 index 0000000000000000000000000000000000000000..d77b2cc481327baad797942d26507d710a0a4941 GIT binary patch literal 40176 zcmagDV|ZrUwk{gmwr!(gCl%YaRk3Z`tk||Iwr$%^>ZImeYcHI$@BK6KJ$;P!stxp~ zMNSeJ1O)&900Q7U6qk0T!q2!QKmdSIFaQALuT@PUTbm!oHa~Qg-0X}Uwdq`~ts0Z0 zW!Lx-g0DVMlj(U0JcQs;%XaN3>}cY!gsU?iIMkQvFQkB8p2ok0DbL7-fYHTeragJ# zt<~{#c{Y)03JG6mYiFt!5CIo^7Hs6xU7D~Th{O>}EQ#;o&i?|dY031&oMHoH*sJOP zri&DGC77$>pQQEz5KUegqk@D#7zClfGYT47T`N#G4A6+ z^djUdlKrtvN2ZcxnE}=2$B`PQcdaZMtk)1f;8^QCL0#`)s%&4k*83CAbr9MXVRqlT zht6e8mH8B`t~ByqQDYOyB541@)-sT+X6_g`TlKdMSMJ%G3bk2>hI+J;m-%UcIF|d9 z5M1V2k3S`OJ)w3YSUOWjOr>YwQ~%Qw<lPHX=8 zhEg=il$sw&xP^$_ar$o_uh7Nin8Lih?@PrS2YjPXMEP$h(mDWg|ItM^GD-gDukLLE z0RTXM{nd3awsNGW`*W;}pZE^Uh~Rt0D>6)4Y}2GHT)3hmd@Nna+mkweS!ClaUbNcP zDKD(8hSrOBbhT$_vY1hTy-ZzX6`*RMzTls?)T_2ddDGCYyAF{I+2Kd%s zab@eil}q?DU(*k9JzxIQSUW_;G#MjCbdEwe347#wA&H}@=(5<>OtiMwb~!W6@*BG2 z03lgL9aoZju!#<&0S?m!?&6u2J+VVt65a2HyD7YLhT4=XjmDT|FNl#D zO}4B53@9`hTG9OLHlO(l2=OZ*BU?i`2U|NwdP7?W<3BffR{WSuKLdiulc&hMv`W|= z0)&VJ72s+3cX1TI<<>_QR%xrTY_^H5y+Uh^Txw^$Ev}(EJKhcU#%5r{p!gvCkhm*9 zqi|?-)Xy~owDq@a5h~8gq`R4Ik zgC_~IS`>=%js+DFrB{Mg_~R+sh+zp1P$*CQ{P4mOh~w)KOPh=E05TCv@&Ln<2lS;T zbwK8p%OL|CcC0|0v8ps=caJwU8L`%yVAp=sw7nYMGWvMqYQP4bi~Z75ILtJrr~zZb zxo~~~g@FfT=9f=XaA{Ft+77>Pbgo_a zJ2XGPyH4v!>dW`TsD-%(`J-u!gFHpwJ98rsZpeklGz2m$@#+4?oG~L&0;O0W6GWm- ze3qtbBLBGF=eO7=<}wcTGy4q)#;GZs4&|JS(C2&KE%tx*<$i4q{p_nRRv-WX2>;#} zeLK6q+M+aOv&MkX@kR}z{Uh8QTtFTp-hWYGMrZ@8CO)%yew65Y97rqUuD91qC@N<# z{X-a6J=a^q=-ZNj33!8ynKJ6wOa8E&{RUKHb@ zFj~kTX{vHnqaH-{2IDt);C7C()KfoJ;rKTjJc`<7>^!I6jrm2+ook;5u9M^%mp4=8 z8DzuDfj_Fv%H2w)cv!{u9!}MhlVIUQf%HF^LB`z5%_&wwMbV`Ruo?9_ugA?mETBDp zJk%MTAm*A{Yoc?{5%Ztn(b(P2!8{K@?V}qOZu5IjKZx3uXI)aP0icIBoYfO3)|IM; z-bP)-cB=^t<#MtDe+W@Tj?5A#S?JsL-XrFy`K{Z>wmjCCa>c?)KEi>#atSWKqX|>3 z3W00j#wIkSA^X&h+TNYBbHF52-U6!uhTrbL9cI-aC-R%)th6DLcumgmiqwwPlz-o) zt03g58YCx4#(guu$EG%Yxu7RR_5;q*2UJri2m!T zb+Z2YIQ_XZF0?civBl85w{#hQK`YRTq8kyErJ@9H%NbWgF0WKdAL!G-=0;7I6e2FW zAvFn`$FXf=VETa~Vq5h#Tiog+>^aaUVUvLA-6|T|Au4 zXYrv}mb}Az1syvf-Nxn}fg!mLYX>h>oOsxk!uHd$2_B3G#=l0sJ zhn$maKXhxyxh?aa3MbA>fdy*gAvDjPadluMYJ|LijqEj2vUzR=^2sk&qQG9*K@F;R zOrMj9jLHrh(+@4q)^Nv?pP7cr58O|W-z~6VRhns!J>4KKHf65GBpP_lRUcsUrZ)Gc z)^onuD))WJ43Tq$Ks`Zc(%~Dom@rhI?}6M87KEzn3+>Y0G5LVuIOMAIbK&I!&Wvdf z;$4t;C5UXJXYWNbnqg6I7Lk-Y5F`O4lZXVEye3&gl7&(B8=Hz!lgr^1R}X5Up*~a# zzk2yIcrVLYAOa3pTDpSMQjsaP)I51c7%-N@yT_Qo{m^Fu2x6dU=#n^TZC@F9qu<2u zuycICi0@&+BKRz5+m#@Ffn<|pEr=ngkde~|&TO1*yiTb(^?dJbvT&60?;NMl0OVJp zy>V*dEu_>1fcQw57jPoj*<8os$GSNu#FFhN*9?vTJTQzZbe7`17Ioo);*Ko3Mt9WHSK5%&O&N$&lYC~mT zi5~eWu1Z($CG#o{CGrW(P9t;ajYgQf@=M+PXeQ%!(Iob1-SW60Zt-P)3tU%EH{Vjg zR3I3JN8Y9K_s;o!^qazrgY#2}EGwG*(DWo3La9Hn+s@ms?npea-C3|BKvH(!*R!Gl zA+l!5h8abEXQNQrffT^s+2Jr=VZxc}`D$Ud}N3Th6MC92@sx4iijY&kNUU!;2>qzl)2Am>@(QyT~5( zyOfZTNZe5LGXVuG^*Jk+!QPv=p3!|MJkFswP7)`sHD5AAI(ecwmf&*GcpEmw?MKU1 z*f3*kn~SUF%{5qJBB)g;ySFF?j_~}_(>k`Xefuw#z-}F-Jamw+T7sG*34QUro#cs< z_7@99x%5>D?`>TS#hu&;;=FmLcghq}jLos+EAu;LdklmJP-=AAF-pogD=6Z0B2eme zXna1(eLb|{ao9o0Gs^*svG)u#I>^8nZQ6k2_)3h!9c2?V5CBZ8-awg?pnBuCG?YMt z`;XJb#_@fXzC>aE)+D*}C81ftPQVUIm|@~Vx|_UcYB(l(rW3kn$B#xg_8IMDkY?Tc z0Id`1Q_8B;u)*j@6ieg2fXkWF=xiVt(*7jLGI1Cnz^Pr0t?Sb7CJbmS{(FO^?1P91 zBC$VP^f(gF=_G6ypHb@)k%yJQf+f7u=Cb`{JsQKPRK@LL=7?gg;z^ob&Kwxw2CR4l z>?_`=JtA^{sy5!cyO9&do_N(YkjJ89dO0ZOjOn3Alv&H&O8D82(7&@i1Y8@lf1Ci$ zY#X5HJbmBEeTSf|G;YXYa;J-688Ludox3?t?8)j~lOPyVh%D8&Gez20s$YwT~q69RL&p@#Y9Q!?E3ODSaAX%I{r9y`;=NnpZDs}>N z5_d46!7IQoerP(wUkffYJuG85Py!H14htL@1E+wXJlg~fS@|5%m5DUko-v)kmO(rv z38XF)_wM(|0S-j*v*#BuMg>sLm<5i%FU&aP$PWO-ybeWxCwXEOP55QW2s_j3=vaDk9V zGPne>J`!z;?ujJ})B~6Sa!>%KSi#m*0VpGTU|TynZihT7FgX$c&HKPIpB!LOKx%8A zfG3X&4xwbZKE>%x5RByPjS|!)^V>5%ZtoUNSYp#&Nk+l1JZ*#t~Mqywy>2 zhF^NY?SuVJI* z!jjB9H!1g^7{RGivU-XtaOS?AY2La*M`rgQeRz*5$AK&Ryp&9x_onxkgS--OiV ztM~w--=8Nhg(oI;^a+g?dZR#7>}<8``02~TMu6@wB(}B)HY$9L9XJpHI@n$S_Hik0H;kZQxh87M$W z5hf2=bs6dx3-nvkyM0F3Lw#FG)}jWTPqpkFM&9|mFIm%Ns4a>CMl6S-_Pyx-mIQ?A zG{BMKdI^EeEcz`1wZ1!VhHYfM!r8$GhmgY}IOQQg8J+Q{7U{!y+ovmP;-oXOQDBMb z(MU*&82yhrgo5|PS|@lIn9YyVI~^|AE}f)l7ujkRGXmaR8(@`-#2y5@*$Hmfva4!L zc^DMDxy-sYSTcx?!kg)P@75N3_h)aP_4GG}W|go;0tG<^e&ro1a2JFRzbp>5Yp}T0 zvTU!{>}PgElUx~kJ&o!FgdsWGcn5*?Aq-5EIMy)RcsB1(!_J zg}EreN$+D~rSv#po74`7sAp&bG;yL;T*C;U?SU8Wjz}wO-gaKt(K=M`!_ARm5R68>B9J+vv*a_p&HIGT zfdsrRNQ${#Pr=Hdzhe>TXp;po8v{AW-)X+a&+yq9-0i>bF{k&Hod z9TozFu8yKf+AwMXLylsvhUnFZCx#5oYe_1VTGQSs0<2U@l>xH)Q{+fJ3h$}`FQWP#dX3L~>usqy= zc}+9|Ka1PI+7gfz&)fJ0s~blg#6i}hh}lmC_~wgEQ1}G$_QG>$wMRG>*IkKaFHPDtR~FTd6p=C7SvsqKyzeK!wg_m6pwjLl`SGfSojL&X zd=8D|CmH57C&z5tuIfLT{3@kB)qA61L?JWzsOap93h6PbQqAs&u;7%R6h&PI^hVhAw9&k)0-Z|SJpIBWE=OR0D)~QRNM)&DGFh&{s_nf0Yn@C!ASIG$XnrMoB;#jO8*#z3xo$) zN1EFPaqazEqVhqo11w{bRz8lwgu*rjq3)PXw!;P#W>*n}74K!TG<9m%22>`f7iEpO zUI45&yE(O89>C>y7IT*)Ex zfNbWArfH)gb1FNnEZj@#rX2r^VCG4I*A^$0%jQO|>cmf$b#gvjJ^M(%qZmme`@IT5 zI}h$GRY8s@$n#P)wtRXZ09>3&uc3ibXpzdS9?&IV4X82=1T_520s;%+?Mv&kYaHF@ zC02%TuKcQ15`b!J8YFJVljcTU*J=`b6Ee+tSxtsa6)f#Y;^&rS=`$7hDI9eXp#lK2 zB3pS&{-skYE*2I6ZVAh_`b#~or;sb6oPdy*mO@Ju0DqJmR4nL_WK%-Z1(hZSeO8c3 z3)Y8oF#47KG0N_cq0-wxAJ>`pY`h_BK;H_2$9(R|Hi6 z6JozCg}rICdM=Q4rg~IfV_cA03XQZO1SOkC3;U2*?5S zY_iTgVj3f-9x#VpU8Pc2>dicXy#8*yFPPut{1OY)I4BfOmtSSb8u-Z)gFQ9z-a<_6 zRHKd$+>GtR6yl5bdnP~iU@b`M{Hlc;pHAadbU!_M4hs%OrSe(H zz9QIkFwu;!4?@N8%F^7Ygr;u7jTwPIeAdO}V-w!q4gyQQ>>Bq)mGf&1QIJ@0*Ph(* zVWU`qomb~vC{I5zKoHO`BfNMh90BTPm^kVuh@PZ&d5L@Cq8|)_n90UJPn}7MrZqQl z`GJX`tLIPb?uNWqLNb1>tAPWB+a?Y|t*c?OACtbRX5%FJL{^>s{?GA%jW0G5)7RW+ z>}zg>^EEg6YdrAx)brnm1bSe4A~4VM$=azztABb1iLS;! z3LR`|ZWz%B?fUIKv3cc?ThC2ADlTqvVNf@G_veTNf=8JPk~2o)Hib_)>!}x47O<=& zfc#d|%htKfEhs&6QJZ8|ar8Y=`W>pam2{vEili~mhQAfHDyr~UR53odj$H#tU3exw zRX-#r`-giw(al6v%WY?c0&x?}IQOudSKA0kNY@`9c=F)OLw?$y!s!?O{N&j+{; zlu5$^j!@Bo-@CL{6p}88%2OtAKl8Jm`RJa>q9K5=OSe#p5Yx|^OvN4G+hXPj0S?l- zbj3i+Aj}C%4vYJl!~u61$R8fyKzw_=k;OQ>oVaS1@?)xEXFPq^sYreVj7W}Z8ro<=u|xzP0?CFI9fR!~;?UKU@+)t8MX zM|q%{m7rw&@sZ=xVRhxLiyxiwak;_X-woTDB!R}KPv-N(5|NHK#dJ9+9$7X4${rawb&xO9+bpSu+AZ7f|S4=tG7F(zWcUDrVlx$~{zvOn!mpB>z=aM>iEpnvRZ z+`l^Biyo4z4k@1Adfqj(`h*7J{f5$g_iFWWu~ko@jxhXOb#8t0Y;D@~ddq=7$&k=? zhZ*v6>E`x)(?|2VCUk2Gc}MO({2dP3O{bcnHhDW|j{7QNmTWeCL3ZEgVR^3e`TRhu zaB+LlARE|0COF&Ai;{@fCV9+_Kgo;J)%JP6=lx*(aleDob$!X3jH>fK1>cRs_9k>( ze05nox4#?mFn0KHcw*k^;lkI^VgJrQ#Qu~9-!0lZa#3%;VmZcMkejf-PfYyy_fU;s-*l2g1QqA*uP0)V>go3suu2NuTv>70>)_k)#|74(S7O zC|-q^Fn>uU@BD4RuO*C*TV&8T74Lk#$ywQo7 z?N{&_#oy(LC%OCa-%~Ce8of7da~4C|W6tT-U&`8@4JGL>DF6$Rt7=j6z`;Aig@ zs#A_1P;^?nzHIvxB5SL;bI*_*yMr{AZi5%Hq;=(r~5*7p=Vyf=!WlH=4`{>i=1piB`_-OMW%1i0y!s; zpb=9}Fm_dmf)Ik2JRDj+2Zz+CbRr(ff4)c(Qh?*G2;fsNl^wK@U_1jzVfs)SP~Gg# zlaGk zJ}m(#?bk^`$xWcTf1V_OAa{d?_{a4wl?xR1gsvl+{Ae<_X#d2vEspzBO6UeHF7T&x z`U{Qb3->=t|E3}k=W$bo?)`6q7_2Wn3J8BxGGTsA?x~fo^ZAd z?B=1S?95{d{`u^6?zLvK5=2FT0;+>MFHrbbebPfyq(59#Vhw{P_9vS_eFw(mP9 zZI$G&c0Z6^M!?GU9XzYU=AXAh3^6Q5Jir35$@eW?neJoimCMT#Ul_cE7P>lzq&wV&tsBi7%&e zjKU7epA8+5lk}zTim^;aADfB$AB4{w=Qi{!o%8E_HXDIE+!MKg zI~*o7;||XptikOM-T9yo?Ft*W7wBQNo4n6pBnE%t70j$1K)8ra6HttsEpgH{7P#eq z8w+}EYRNXzcaJCCmWZrS&T%Bgt}D;9Gz)a(>^0(ea-B?aAx+u&PUla(qNQ5Ls6E0q zpHyMf5}%*Ip+#=bkTrm47mwXOw`gjm?PcQlX^V}=vg#BwEKScM+N*lD&7OYabMO=ODjaoBl`a=Lpc zgwHO2R=^nyE!{%L()%M%f?dn3A_kh$t_m=~5f;JIeDvWbWf8q}`;H{CmnS46u0E$m zQQ_zdUZ^_qL_0yTG2)O{u)$@2_m$#&QSpBE9vkmZUA>K8wxwk|DqPPe_8BO1_|1Ih zM87}N=i8@TUhju)KW_(rxwn6qyLmr-CjDN#c%3_WKec1f@Az!t%X*}LpBe&n{{(rlGPR%n$Bu$``2 zc>DT>c6-YE-mq%CD+|9g5WFbcy`k2iQ9s0kHGPM*BE(!iP4~ktPnJxdqhl?OGLmU` zISdp$zhlbn;F(lqta0L2z^uW818|%=dq0Wo1-;QMxSSqIFX|@3@pQ>9%(qoHY|Kd_ z%O4$k4&t1|3mtcetI!TK-b!9GI$QutFkQFcfFt+!r_1MRZb|4-CT&L27|@1OMX!aD zOcIS$Ss;dH?_S4=IvFnuBSl5@#9w5tOf|80&TnILoyd541rlv*gHcJZB{1XMM?Uhg zYWhXLnSlNvjzG2Lj+$xAL8S*9;>U`!t`%ELG^MM=EVy{}Y?E)QFugo_{v1Q`P-(2Y zS<)Kqtz4RGM!NRmf)2#q~5;v(FkM*K1Wk)Vq zb<#x0b~h;nBv{2{C*9r1G~2fvWnsFjtW!{oJBWWmORLz~qwqAzqJo@4H$K?_N=_Xw|k*k(4VAa!h5rd?I;~x!zutAnj%<@96F$BTFJTX>gv%^ci6CZ6yG;!+F_4dc)o(k#4VGF$futNv_Rc$dxfca=}U4c!V;NJx8~!*bl2~<=>z-cmW2j zqD{J#M-oZx`b2{0psV-(WG_k-~$AE(8Cp3r`OK1c?Y`wC1Qe$eR`<-q~1S3FO z9pHPq3fg^bRi)$|yt9|U%S_?oG_yDfOJw}!&vOT%M}CdSIw1`$^@rXnOIg2wwqoDT z+NE@c(|ScNn?vj?NABOsxU23Aw*9dP&p|6f^-?9E&vz%%k}>HgmuJsh3}DnEKXC$Zu$7 z!iWKj6Kz6w#_X7;xl#H2O4W3!qLNA_F0YL5Q4gQ5P;j_+E*(Mem`{hw=kWsDOSBQl8tBo zyZeICD`f@^tYN3Y)GK(|v;LR%;}&L7&{dqm=|k%b^I3M_dv*EDvCsRTdtk@?8QNxH z4<3y%zro&~g#d=`qRr6{M zNr;(_D|v7DWYN3G;wYlIG1S`w~VqP-DZ~^b5i9bn=PCY)wg4U%Y^v0`8 zzP{kx$-TcL{d`nB!A9mjbG2|P;K|4(DY`V-h|bkA8m}<*_OVmFSq=_0(UnDdkf{*r z%sCR}wHST4DAZMdQL|WA&0G=9Gk&7Jy74-^W->9 z!e{!3*`7xwj@Vh-)WbN9?LnS-cx-*TTy=5XMSY6kj{K|u6W>D}LV91cheN*&Yv$PD zOCxA&q%LSX=!?t;u`^=rEESM&zR;D=t^`V%&%P+*1?FyOyS#+c8MRSrOuq~{e2L_q zF?n=1llYXo4Wh$IlgEyGE@hUCr+p4rlM6_vU@FsXGx>)x*vOXN}L$$ic~8vPoqfm4ScEswqK z3)`?K4BZvkrw@c&=FmcS2D{83WoAtrrZY-P*!IzGSL)~s2Y3pX-*(coNv)*=9F+We zR3EbQgk$I{qs`M23-!Rw;kv-haX|89KIl5=mT*Ut>KW{!FPIGK;7u*$nGD#)l@5=_c?-TG zk|T4I)g5M^Q)W|<2BO#?{fOT%S-;J zeCAI%7*C+P@|)H1Mj`nA!~AP;L|D7y_8&nr@Fp0emYhPL`>$TJ9wx0Ejck#0y=OC_+D?9Z=wM> zsu>eQ;y zX(HZ>(tJXd3(EJJQJeXjwn^bG^R}=|R3)M0+*L-A3O;I7`l&b@>jv3$JC!P9CG`(v zN%tiy3_(K9-qVd7qQ7{cy2t|jG%PHQxx$DdO+Easbj2p|U}L+=%v)nT3zW->Yo``e zUx{<(298cCfNoc(iX(ilAMcbvXqGcqr=TLCsDPkis4;)e)8&NYk%_T4LzGCmmMim| zo(s!}@PG60>m%giJJt{`AR++OP3Yw(k2L$D@{h!KZ5q1FPU|c)M~wX+9uSg$^2qE- zpabp{-^d}>=sBJa(lbz%Ku~;HN}N3rP`YQ}Y-<(a*Y4A-Je5D{rBMP=q5(R^fJcE- z0-3-5d}G8kNAp}o0~~RVZ%UvX{`0b_A~uDOmeO5lfDrGRp<0+*K02iaik~vyb#y`j zbPGW)`f}9|Sqh<6!-*?H@-H_1@c&}tbo&2fqZoGeW4|F^imN{|E3UTC03q2jOFA__ zcYHtv964gSY5f-N+fkoBC&K@%KJgWz4PbFSR=HTv0|*MPs`DIg7Ok`hd_Y0Q7P4Hz zwThL$%3f{f7vTkDi9WX+ha93@|J&7&QK9;^xh4q7n#8J15rN@LJ;7So1)JS#S&<0; zZInuB_8_mSp)CI`Cy!@UG*K?K97qR_x0DEfAQ1tsHtqkIgTif;7U_vb=NXT(u|bqe zxmGE6hX^7^1ze;2U(=)*2Fbp14LBP<@sG@C{eLu%A>5a*GKbBU9zQ!pb_W+N28_4c zGo_=-j_%s{iZ$uC4e6G^eMYnMquI41R4VxPYFh)0T&cDORN*ZhEl88MoC1vU`#u$p zc~=6(he0mE=Scadee3J(ON|e-{=8QwG${ohc9fWoJ#j1CplZM~c}Vr-6Df3II^`>C z14PWartXtURVQI`Z?2HCvOplPD`Tj48enGEA`Gc5g*ofZcwb@-oNRDHypfwAD`zW> z0ztC>u)^Toie4{I1(M~iuehHSE&`|T2rv=YfXY57qV5TlZr-pJIn$XxK&zo&J-Fyo zT5D_p_V)3B9v0u5HalFwO4yS|UFvS{88;d*pLdp;6d^X9|BdIkd4px1G$8_IZo(vh z1A$l$;6P8bT?(2oO%XLsFj0Nha7A@&8s`#&$F6aXIt*tUV6+U4=oWAS_IJH>+?8_D z2+C}nF+;}dJHxPFD0MsQIy7XNGJb5UC_r&QX@Ud}0E!U8FrmMOp^4DuP|<|qR%DD8 zL4h2=nT=!4PnjbK#n^jh!hRil>>4W4h*mR$HySbiD!6i`0rLjHt4$ST%LGeU$yA)P zpDscOMVa<)P>wm5TTcUn={-|EL53tW7l-YCZ8}I5ZMMw}$c)2XuM^9`m=*~M@+Dd^%Lz9gNzDSWsrQ%%lEA%-!i%|lw5}^N=v$^ zDjdm#q8FR2elF5ttY6Yy-wR9%(*z*8q6f>rK2hvf6NaLUL&e%o>NAt2zqnY)$6abPR6ac%K zf34G!N=Pk&H5rmmh8cVAW)7yo@aTj&F6L|eIo}S;6zX_d>|s2q?q(P8)7uW%Ugod$ zJ@Y(n<9Qi@%hx4HHb3=3FLmnjSD>C%-ac|4U&W?jf!d zeAJ zRz(-jAG{oz7ety^*3mN*7q95By5oRN{y49_?C+DfY!iv%1h0G0`FgQW(1Oj_!Ubnv zF?dxr)gP|5$P*lvSS0!k;+<;WLI!U8$7&UC?B^*|I_vjpbJ74J_WOm#2DQVAtVU+> z_9Fs6M1x6iuR(j}s4vXJCgyIIVbSb7Dopkvxe^=uleW~5EJZRRN1$;Jd~L+G8KB{FK~Ow|l>H)r(1@B{04JQU3zE#7KC9#j_eAe%Q#(2{#RtdVGXgGgJwwbRrZ zb_llCI6yE`l1Gx)Y^Iy9dF%SgiY4_i`v;}?2DRLg`(Mas^dz=lqEoik z9sWVyrzi2Ep!y7@+ANPU+xKG@{8+Iyj=ob@XRFh%mFKyExeY1{_jlXz==c1G=cV1$ z&LWWF!Z#WQ&aBGQ8<(cTSQ^u^%{c%K`HMy=SzITV z{x#LcPI%qA^l8?I!Zw~#J7TM1-sBCHppgMr+E_ox2f>jV8Zl8z;v?dbph{7%W^{yi2M6?muLYN>BaX!q2h1m=|=0w(sQ9jkjs-w z6YMf`g~esjZ5=x-l^?v)>2nRQsKP9;!gHe@#_Fe?b)iL0zD1BM?QuD8tEaiZKs+Cp zq$UW`39Q{W)1@-$WSCFFb1eDhKuyfbcTsaJC#7Wtbhh85Ejw+qE?Fv_K@p{aXBuud z{gU0U`18$ibIpM!y7!N|n!bhm@ym95P-6+t8BN%7|0qi+k{INfVTm$J_q~u>@Z4jO zb}u_E8N-&DTfN#V<&-Yh6%d}|XQ}K+Df_u8nPjE`ZkEo^EZpP&JNX+;pR@aY+fv0p zJ!(7^jr{sIuse@AqrXg^uv9*D4YtiStp3xaq~>Yo&#=Euh9vzl$pQVxWT*B2pGgjB z%kHW6LzYVKFO$ec=3Dq|vg>d$GZ9cxxI+^)ObHy(%%R>Z0~ZcA=Z|*)T9*mQfUo)A zzqTn*G_)is{impRnwU;gy1d*U|`3w240fpfJ2>Azm8Gy86 zNxsLT&m(8%F1pwCivu1q2BN0wuwhRD%dU^D3PYDPmj$D@Y>3sn0&aRGUJKTys54~8 zaAxe&nk)x=D*`O^ZSw-!=TAQY9PqvVgQoKbts{34xU%2%4;ng$M}Wy*8uhuc^)BeV zrgA^@I-9F{2Y_vWm}?%wqoYS{s-3gt>Ud4aHfSgcgpThp;MWff1RY=K_yOo!|5tzX z#PpjV81y~d!Ph`jFR}F+@dy7BH%RbD z9E_lA-ZSg|UptKDm{fgujc$Ngpg#v-^=<$JV9jm-G?I320S!6bYya8DeYic))FA{N zJ$fh%H{zU+O7yWY4ef4Uw{i2=r~Pc}RMcr*`S107mk2?XU2h7rw%C4g}uk zXVU`u&+wXLtNsz*vJRW>xXHOh>OT|)?F8LAMPDC8C75gjveuL$Pb+bwfOIOsLHbc8 z1pwJe9HbH;5OI`|?3Orp@%BUvHfvM;1XzyTX`iXyW`--CRn!Cp>+>)W7%{EGtSXbepq>C0sRvXzC z9cay?H9ZdQzC)l~;vX99 zSue_-dBvM8fvLLSsapmNSR9!Ujk)8pdUA5aNvHaRUW#R6L5&iKxT;Om-xpeV^u1|W z1kC+jK#{lZcy@|O4ov}CNDr4ph!O7kYaeY~x-Uw+JM%UP|*(u3N5W`7i$P zBsJ&O&1=db-kky5{TV1Bn$ow!8hZl;_-kk>!9$;z$@5TE_yrxe)O}Eu4PHRsS$1V9 zV~^XSIPf_KB|Ib!yh{Nz54?Xf12OQXa1L{aa6MN*XRd4>eqXh9DxQ2-l818DdM4Ul zZM6AzM{P@HR?3b{a<~L|$d1_}W6^9*CWuRM_Z42>J4i4n1D~I~z__fdmc*edr;Ti? zE_4*+nx1zuKFB!~`59%{3nqX~*o)H)#K5~<>eeu{CtHna(F0nV?whpaLRt>hcfc5E z=h)Z3sh}-On}fbW0Q{N~nMMSSMNcCpzCgl2tsbfJ&=}?DK9tmWdFStqUfz{sHPeq* zzo6A$oZmSHlwK<$-7y2KBi)_lBZj`@E&s}R@z#qdeqeigY25V2($C2u`a`sJq}skH zZR&jh+x4~I-1^F(=iUEi-iZV8R4^^4CpR~EZ=qlCH9|E8%95P!Tb0$*b(y_K-@Aqx z+UB5(xA(T)_w9=RZq<=2k8^26+M=eB_XR#l?mzNt37D^PQcTl*`Q&|W>M~~*o}ySy zWVPAdg^!{@18^Soh*I$;ib%;e-f?L-G(+(cC~bvPwHKWr2@-nyZ>HX4X?By zW>;Bk7za8tue^|W=}$LDj;BmMdn%57Td|1AJ-V#XOC$^ru5uqJb5^?`dzOcUPYOYG z*I4#b$aq_TT^-}1;zR_Rzh0wa`mC#!0x&TE0v^yI8U4s)!8ok5Tds?v< zUSDAfmm@I>km~v5?4wA^t<&C{>N!p0T4>7e)g2J^T`o!*luxl3{{T+P9oImjYvEhQ zl}<1G!q5VgH>!ur!qj1*KmJb$s%NLp?WIA;)VA$Ef}y-265KB{TfYSN{!4K0)V%*c z1aJQl{QM=DVkzq3TYIW!xf9z~r$2sKOgZZNXpB+QLX1(-KY%;7mqvSs{s~N>{f%iT z!o<)Q%KO#5n^IB7HZupxy9n)l+A+6FhaZ$r$x!i&=tOKGiiN8#`#*q14M4lcMvlLd z@XraR^oR0}Tx$2;?4)zude1HP#}~z}KQEnK!N~Q`>Dsg%OhW~8l|1+ER8BfMN8WdxD@UMKQo7;4* zzdD-cpVQ3k`C{+Ljaue81MPA{-P5<}IxVG7pB9y5dJAS5fJ<`?vzwC%oCBYxQnjBe zP<8gJE|FtkJWN`MPJnq>1tV86zl;tvvKV}xcE;m%HRR}E4huw{DX}@5LC+FtDOV%i zkj^%mR!dWFEy#bSkMs09J% zD*V3zT8jSwJP7{<(DKIBYwy3O9k}PL2QPMR%Z;ldL(ZX7FCgm2j5{>|D`>aw>TwSq z&Hi&-r^jB)n5ySyS04tH1CBS;Z`(CzyT#5{cnu5(Ehd0fY5O-d-Dx;ZE0k_Npgo<+!8)2~FkrNU#ooNd;>Wu}X6DH$|v zT<)pk^Q;9eRHF-Sr(PT;MUVlAmUuqP0e8+7DVl57%eb0}c0K^oJ&bHSFcvH&WA6b* zj&U^i{#a|0^NA0hC5R{2sSWc1DHcF?{S<=qi&t~Q`GDG_$gOJ76l?K6TlWXh5uaVc zBB4Z}vk@EPzEQBXl7JZp0ZTp;Ps3F53xI@y*3$Iqom9mHi2Bd>a@0tkVzp#ol*Pojci_oW2YubZgj_|mq5`x#u z-iN4`DeOL7(T1*uF6GSF7#1T{Elp;T-_ZEej5n>G9jlIPfK5FOeSDDlk;rPX5{Wv& z)cDtD6SoSOt)pZGOj^=`yQ(o60r=>s!-qL@z-q;O1RITS5@h7}3Mo?+HYhjD_RVB# zie*j}GKBVl(Ql)pqMxot^;6(RO*14aWN|uMmBHL*Nu z3k68jbE!!$I@@695)^CU^^O%%1kPbePKinChAwO-szzzBW?@MZg-e+Zj*5K;4ig?V zr|b;1Oka&`1|NIi&grB~Vwa#K;ULYOo(zUJK-7>!j=10atx zH3qUn{P&Lf;dAhM^o@H5EPf@c`IA6p5m$xHMeNcSwlmc$^xqjfD=Gkik*ViKu*@oG zQavU!H!uUf7)M6^UknC0$+Miw2S$a})PE2m=Y;>N7Cc!pHf5iwmcgH%cb&V^AkUuN z%cxM9UYl<=W%Kx^Z*!teV=*||iaq*&2zv+UO1Ax5IJP^sZQJfR>9}LtwrzB5+qP|W z?2bF^IB(~ibML$N|GoFVu}5l*>{>N{bIw(@_F^rX>| zHIho4zz!Q!AIJ+0v}(1*7izE+jz33xEt(W6l;-y-AuzxBZNbo>Rk8El#(_RAXU_`|fDOUV@Mp4Zk}Fl1SEhnT+ksDp zqP_#AX5A(&u~pDNm&b?^P3-uYCwv*gtdKVw8lMXy1TLE!02ub1mg54DK%s9qE2!^aFZ)(+98L%Mx-zR*$)u}e>wRR5 z5!gB&xH9s{If%H~j{clNKcLJSYpum-4*#NH`wf{Y={#751pHA)QWz!(^;8tbq62^d z^I7`D248i2k<5EHU528P!CP-ys|{;&UNS43?H$;|!@>J3l0Ym|06?gJ16lnAr2225 zj8C9@0>>HxrP7ZyHer~*ek~E#!T&~>Id$KX*??Uag#k3GD z2Zson3Q4K-3stE#ng!rG-@!7Ue-x|&ek+ZA>4rC=3|N%_PFycjE&3SLWQW4i5CrCulr#(FKS5z#1Pp?imm8Nb1%;*=)@k1Gnl^hMa@cBU{ zh!xky;y8j3_W17m?&jY8;@!4c#eVi@8}5Vs@yMplXO*{lIcMAV_DXl(mSTJ3;vw>W zf6u)^r~Bh*=I}D(+dA3sQRMwq^XNZ;~T>#7~E`#Y;V0NG=6(0pQT$J`>39+_tl+F}gPIky&mw zdQ`5MsX%3&UKRMbu4!?aM7MpBeW%xHaE<3k!>58f-EI3bM*Ohbia~}!EAZCn{#ftz zS}QNt*(0DXkMRC7qT5_C#jdfjy)9o-5=fB3P(h#?8#{6T0$5Yqf4^{Q|MZcaa^`!L z61%ehfkSZgQ8pAhweI2TmHQQv?+U*_06(SXBtKKTUZ-&2)$zIF_>Q6@s!jyQw&IKq z4-r*{0O9?kVRN^wU|fce4&$WSo1?OcoviVQ5eLI|gv?@u#KKf`F-CrI`bTLNv?7o4 zkjq0xD2Y;ob*plA&Zc0@l_>|*{>vJC?xT-OAVGJ|W^XXyt6w{SuP^~$u{_)hc&g*R zcR%e)!m;&KKg>-0ytkID%glaA>(E7CA3DC+yFZ~n1m`F|O@I3RU_ax!AQF)AUb}6h z^q^%pVGm^`#!w?4xoM$h8*RYpBdf8sx!qDRqUl+X@K!b4&oC1idbn`(WZ%~H;`}tC zQy!ng;YtvLTk6vb?QvMLbX@ZGy+4$|o%Wdww9ojUWXVR?Ht!&>mDUcvS{Gy!8}>t; zzj{k&ns2Bt-Yc~~M%i{mxh@d_W@aENDcX)VyvBeO&nue(iw0-cEg`%fxn(PCgBp## zS@-d1Pu-?@hGQn=<)vgmf{E777o>LcvAc69=MKJ0lt6Jcd$X4|r%c!neK3_%GtNnxm-qd=r z`^0JWqU|J(cDz-1m_w<`@zMrRFJdP3<6IPOqb?))AvYX(A4Rso!#`UyBecmX&O2RKnH-U@E_ZwTCF zs1~5NbetZtz0XAdy81Jb+mtmhZgJ@+O5-Q|Unu{X;6G9R&nz(6TEOjoL2_!G}bLTI8=aQi+{FK3nDyDXeN(F4;qAa)dK8{~6Lk|zL#{2(# ziNcW*aGUVia^ZA&zT|H6`Z%_nKec>ds~XwofGu&IK*V@|bbErjaPT~N+sNuhW zdO4x5hHUOIipZ|UhudotaNa+I$B$avUOUv0<6|`1p1Q|ru6T=$G^%`Wqwm_izlrre z89QEjo_IP~yL&aSo6>f`3%dv0V>6sTIB`2MhSKQI4H@)DRl*)E#cYkD^<0JaCa5a<4)3zaeNix2%Qnj7xz25#PKh#onGB$6k z<=E5dv!0GW?Yo{HV|W?setAtW|F-i=TP+Y#0trU&ljj`m>Ix%NcF%)z6LWzJ>p04Y zKPL-S&WJxd%gFO=Q3OWMrM&Ol46K0!?CAB`6lhdF2huUXYn3R@^V$eA$e579zfl*# zJO_Q=hOO{>bezizn0bk9T(N(N8oazb2`?X!n~s#ZDdE{q__c4Bh%7t2U|fo_$^gZtfqt5-;2 zT|&<_PPYTQJn3iI1A|Ta6BFqSA{%OiFWfLV2p>WERPYMaq0evdt~eNQ>1+^f9~N2DV!mB>%a6G2wE1{GwQgh^#248Qh}fhg z)bWzrNm=BDRfN`v~Ve~oYnU3-TZF;YUdn= zj3L2@abx5%jd&b(_FbQY_PlwrsNBI5&%w5eEz5G+#>g2;8Hq)X&jii8aSRhsm2Wg;FpvM1nO zB+(|sM-Hk37G5@hF`MGsA1Jns3Q`95lWtdMtI27RQ{S)6bXoL(T{)@!OZJod7p@zt z?73D|0#a;>3}phbe`q$WIztGYAJQ$wRAY&!*-;r09qOLg5bbyv(JY-3QWU4_K3`Ad z)(S1%FsF6Hu*dr1qs+5(Q1N+As?BVRtZ_Nnbn#>M-~Kf2W-x8g=Y)Q8uFo6rh|Mzb z9_KT4mY=6nNX^^7T)jb1@s}IJIbESZcbCYz_rtx);{eTx#BqN1$tTKV+eUQGl0~e= z-jxyPm)61T;cpzJ*{`HHaHH$20*?^FtL`|d)y~H`P5gD!rOLs2=T;ei^7wmIuP3D; z$-yCCRO(k>x-t3X^2b3P#Lm|oXb`m*mK6E()f1VNtum4pZ?Op1??{J`(W9Vk+Wu`!b9a3emJvV)P#i$9!a zB5p0-<X*eYrUd<{_Dh$x%dut}yIdU=sS=$}L5cr-7pdJ5%M-6qB zeAd`DPqoBR>-lbR;qiou$2g9KD)vogQzSS>#3f;0cf2BPH(?B3^0ZER#NQz?2qz2m zEQ?7b@ul_LU85@wy`pors$>A&=y^=f!K0%-=Co`aOsrAu&yB1GPQUfnLr2$PZ%*Eq^Y>5mYgRd5PIb5PZu`u3lAX-n6HiZeP@eReL@%VikQ8TTQ1tE0qD^l(p)XnU zIrQK-Nx1&~v*aYc(d@faYHV`#|10u@BY#lC}<8^&pz zT;1>#meEG0*IovR5I?z=ie=EsVY&HHX0c*b@)9@J&qGAfi`U}$X)13c{qycCW1q?O zGT+h`PwEkkhiud0#oA|(9oR?bbacPh;o-M{iKiV|knG7A)#hGY+-qO!ZU{h2GjvHe zbs7&n^~XSP_}WicXIhQK1n8mbpNJ#Gg^M0NCiV#MJyr}=UYgdfFJOSlgb1TD3H_{G zk@`>PYhI4UjFu6%#e&ebQo9{{tUSD02$_h%M-PY`dP^`X8C9-b9^G6u@U!5ZNn3*Y z6m&fYEeT%{dZ}G5u6G#R&$DJ#!F!Lve?z`Dh4u`9{NSDC1Ew8R*lwMRg$BiZZ{E1P zl2XuyY)tX9(`L@5r~SR_-nNR~=5=#ivUDr7BlYn;4vzrgXidZ8oRX-+UVkLHcU>RJ zXVvPXpYRs}7~1yX`kGW<^&kDqm{ir|{mT#*lC+wx+e080R|}ue8|hY$Bqj^Z(CIjy3W&$92Pjt(5~E5!Byv zAGVca4Ms7`$(DE}2T?koV?{RK_fnQO`&nmcnMdg+8@Ju^W`Y1N1K+^zMR-L z-iFS;wrtxHRuri}OKk1Q_km*^&Qs9r(B*V}(b4##ljfCThR2nQGtDg9^{bz!VV-9I zm(dBK@uCPAlMYm5NvlGpH|u_xvfeh;)ACji(B4Hw{=NLm(J)e8K!n+LLXMC0A*5xw zutl|3iyw=svx&Tu?Wu5uTS=26lQqL*F1W>{s3n9;?-#Dwx)vy-CPYYii)xxY?^%^1 z>6g%!l&;EamF>NSnK;qCdSyiS@!cx6lPa{|DKk|twpGx!vizYfF>ssk$3P`IPPS6fXUtlZVj za%}=zevtl}xE{-V%eVx;esn;~@~)$ack3q27C>>gYRpu9C0?Lw7w%#)lD?CTri*P7 zSmao6eGA)fj+s%Vt7-h;Uc)w;*paWZ>D)N_I2j|yC&Z{KHOoUX0@1cg%C^d{#fw-M zK+(_)B6h zmk9TJ%EC1PHiOFX?82)pc6#Vn^X#u}(Hv)Q^wyq#YBk@|5w>1lQipum*9lCZk>-a} zp}%Kds79WT{c@t^`$*CmeLWno$>GAS#8(?4GT+cYRr2|jw+5ZB5s4;2mG_XSqLs&X zXT6s{4t`W6o;(zDB3gdEdOf=Oe4!=u>z)WT_5`2sGN@njR_OW4fJ#e{)^l<-KeP9H zhf1+fwgGT09uQ71`F4L%vRC%0+=+A2QWoKbDT9bJg-AJ1U8n7L2(VOy#gcJ4uec}c zKlc|@j7rq^8Or*i6;`Wqz_?KFbaMKr3IlKr8X z+Toz+@hKzHwH}d(YrlV~Eh+zQ9@d*#@1hAl>{3ooEIPY~D!!yMsTTx=L=1#iYfpEg z5(d;e3kSz^&l(}ZVT9o{MsOh)_MahZ z0$7AAp~wkPTV7&wY33>nCYXz!zCamT2XbH?0tMvoB2>*Ix^o=@RsYN<3^p7D^%&VT zF=-dSY<$!r`GV4*Rp?%Z12?(GHk2f?FnGK!hy>2kjzYBwI**P(&bKM8I?yGiJV8r~;-I1c*{I`7altghMwn zB#O)Ay0>Y9^+Sj@Bwb?(h5-?Q@UQ}3VikA}m49y-w?u|Yn{Bf)N5gztVGn}hW)9}E zL{?`BW(w5$(FcXB{yLfq`cO@l1%nKPneI}rH-LWM9~-ZLS^=kyqig%GOJaW-Fv$93I3(O$z~rO2cbYwm zo>vlOpSRW&aQi;}xG;!uoJ1J-@r=aqFXLK?FAsv_`29df!NAnKR|Eb7emQ-}1n)Em zFC#+(sWaoa}!+Yrjg*Fp)h_+ z8i_i8jasA{Z6OX{z)4o2YfEi=B77UsKB^yyLhF-RrPe$V5sog(8ml@Q%+Yz+cRHG> zdF`vmFsM+gr#Rf4&pXxGOTBZJj>p!D*&^+49~{W%cxVcmzN#}zXA96pzKt=8lf~@I;CM|L8CK*oJ>QVZku+v8)^7r%ZJ;&btgnalgMjCob z+7Sk};jeWsMr=2$nr%f>i{H;&oj#LOTa&VGZ&jXeikWVea$^Chd@wcJojO4)IS;io zNB5?DE^cqbZg2iPIr7q{Ut5!Lk`{|=jO0uv)R-yjc4^iUZ=Q|m`p-gFw!>B!1jw*; zjWpFw(sj+E@@y9fp~TgWDZg%OI~POLuZ>Zz2>q{c{o{4BKzs)j(vmqe%MHOb5? z-dsP;+$WDIq#KPI`}nK*z?p6TzoWFT5Df2;!y$9e8o~7oXL^Ckc(Xvc2 zuny0*2*>w(Im@`c0bKi$(Uq3*`+5D(b5)JIb$_$IucC2e*L%Wzm$8n37_hF}+uDQ~ z%%fajU#n5z4GjlnnXK+uriwQLS|IZwx61h#8gNSE|3%L!!Xd0q-+$||^~L&1(hPrg zMymPi{?_=qKyoYfesA{xmg{Mt9-c~G&B9e_+GR(mKaljEuMP9;oV0);ce&c{;FOEMc)(_PD}tbv=O^~4fo`NVi`>yKpW#Yd|THZx(6 zAxalwZE$=#11jVu+Wm!R9%gbB_7!RB6c~aTeh!Y3QJQt1M*dPEg(B1pAR_-SjDJL& z3W~6PiU6?se~XA76m9t0rSrD{rd;SbIGRp;ll21TQ>3Z$V5(1AF7&>5ekv5CxLqXh zbCbCCfI0?WuV67zIxx6+xJma9GZ!jX0H4qJ5UfP$`oSxO;!!2tBM)S+Ad^Jt;d`k0 zSXh6HarnQAF;ioGF>k7%N=wrbQX*%0(bVKQRmZRA`i)VpnbVs?W0?ioHKnPA^^@z_ z&ybm_3E_LSOezii=DJ(M*3~>$ou_)0k8H=?+uOvRK*j}PgOR_NYvtI?X6L(XoYc z?iH|YM^p@AXle>u{lUeGTf%5n8})lg3OdsdLheHDIKbGHHN*?Mm!=cqC`lNCW#zSPeotYPbHvu>7Agl^H zV#NcSGbz!elnj2eaEJE=l(9ozT59y3*r|D?$ zfe#O@2$C?8K2_XMGvocO!W}rsM8_&@KuYEH{pMA{Md=?M%DKk?9q*3+((yjFQO`{L z{~+O2qQh*g-{vTEh>mLLU9Y7q1)+gRrc#*n~ zPh78I53N{q>%+D1`2e|8#l6vHSj?tRT5w56HCkJ@fpzpuZ(gD5POiPNZP&@MOCEho zFyqd~=CuX3u#nS#bKk+LmJYO1H0m8hGPK4%aZ&ofVV<#+h{jia!*mRhSR0Kyle-Ev z*Wo@Go%3BkYd#PqRg`3cDq1_C`i5}v#ht?bWe+GpX#&*=^L;W6!T3l9iUr9;vfd@4 z0awH!U_8O15I0zGAbeKIU!1lr4=t(w3?+IsPN>jdvH)ED4b!>77(02f@yBUVAVOZq zb$cb-nimh?G2pqv}qU{lBML0Y@{QeNWI2Hf+guhi25gLoj5IB z4O(OUnZYM&rN8<=s6*Jvg{Oa^)`i6P&8PF;WuaSYVnT$m;UZZ`RqmFm?kK3z_^E@| zg%m0UaIP>FA_UU5iKjt^v1LNhp;->1jB5$ebkHw{!D9_#hp}n;EyXHwXjLjwK?aoR zaQ{NG#H99TLsMy|TV@sbH&;c|k-W+V&^ibMc&TUt`)Wf;a3T$dXb>@g7-UWA<*;Po zx&T#|@0G|n%%6%Hfhq_JY?{>zlty8cyPRf;mKnQy>WUxKN`}(WLx2t7A%iqgEIFwM zxsuTK2$uybiOS?CuE$!iny!q-*rd;dDtATA+^4bgh;>wF$EzdR3`8IR!K2aj?hO~G z&@EM@84b!)5x57jL!|X&m&R=(*`^Rd2eA>Lnn;)J7TKLCEi;4xrim!2eak$w)Ze;0n<*XrA`*KbbOKu$=;GQF5irm|G!gQ>AW z^usDN)tiv>x8eO(~62}1!DC&p(@t>^q3Lx2*vw#uYba-lL4 zrLwZ*(0`*ALjN}^Awmcl!D21>4|tV-EYPk6SP&13{%OHfM?~Ti;oWNFPN@fSl82h& zQ08Js1T}>5);Er!-3R|f-t36DafDNulPu@N2`&hG3xTu@SQA8J z<4@rJRyi%2S`vYp_Dc}0A$9J+HUlEievJ5+8h|pOFL+YQ*bP8AF<;_;P$mZXZ_1oM zW=yPfhSK%e6~v-rN#)VF_)YU3BaG*{Swv$D47unf2r|>rDH>m`Cu-H+m&r=0efO0* zRT|xeP{x;dF-;*IE+pwJ9HJBtz~>GKOl{#yP7j2l`8ZaC_X^h)wFGU@ZAcYWd(-kG z_0easB{w#73xBr;V<4e&3|m&e?@{@cc>kV6{J3@iR>S)`>Z}SsAq{f*{|DoGDp*#ARDO+B zKeKKleo!Q9?)bN9$u;t*tRHW65J5Ol9Dqr1P#nU7XLVQbyAV6^HK=%^1qmFJ*2y;K zV*=9DTXu=|?oS~(R~4WQ!HVb6K?AoD@W#XHlALjNVxK+TRg!D_MeEVYJMThbVCVN9NpB7vm@Nq4%xCB!9_uc1A#8N z+Oh8^gnNAQ14oJBbMKQP!?j_iXW0FH>kVRE_w-lH__58r$pZvvNEA8u`B6YagN*8K z1H>?Hl!kh?T?Je5Q)vhcKq;vTK&hMpKWLmCcRbyeB(y22y!4i)!94~kt|Vt_9>)e# zXb@;2fD)J6JZ&ryk7(ZkD7qx)!{QlXvR&&~YiH2F%pd6PGfN*-Pm*0b6?J<`no7Zb z{~+bleR+uPR4MReEtO6XL!?wYQO(qNJJFx1adx8VsR?$2@KDm6nGl4#PWcg$@hCB0 zhM0IT2TvJ3ouU`9Z8ZeYm-G)>aQrP=z!Fze~PzIbPa}N$Ri*)kvQVE0OD+rp*a5HF4OF%ubjN z8#)6-nklEE#-i9C-oF}KvhfBEFl*ZQ7rg5v(Jl~GoSg?kwjDZPv!oeF+Bm!OGFzz( z*wECdiLacmUO9vqa<_knB`y=uh)4Y^EO**|*;O(vwU0lOWRen^+4pjXa={noU?uSM z)QU)IE5SxM>KtH6&a6@oi~OK35_M`QMMnQ$pLyK+&9Li5P-Xb_{DTbTH_erxLo@z~ z3-n_u*5H^7HwKpzwam6SZQ+8FeRF~9<`>r#FS(ad6M_(|l1FGRJl2%RY|w@hjj^ax z-603aTLBGQtD3R4`;b%txMIQPAw^n=EUkG{Wcq%5+FVz)lm`a4s=70=x*c2t%Q9G5 zl_N_E(y_I?1~<)*x{yhAn7R37s`Wvqav<*Ae!YO3B2V2DDoJpoZ`Qw0!`LgnVjVl9h+hNKr3PVKbU$J_Yq?RUw4%Tv92Kp36OkqSpyyefv-a; z5jNxAi0~>(dG`=^Ov3~kWvY(?=Xwoawh;tk%jd%wO_6yK z;?q1)n1Ejn(Yqd4TRMagqk(BKnIur;Bj_IDj|P`1jZbd1z$IYf;0MY9CL zumg6(^xA_C9f1FNzm*g5?^ul!6sTl6S`8JcL;uU*PFEFAp^`iQlM zNS9av7zUA!@O6LXEte+g&jiqST+ZhDD<1m}>ZcbnBb0lHxu`#ha0mMsaqR$}{f871 z|HlESza0~N&8xVEq(~onN^(06=KlBw_P1l0Le-sm?1254=<%*^OR1xw9MP{KCpA2e zXg`Uq`YC@(V#F%|$X5DQ^T7xsfy@&${NGx<{2v2j|E*;1Q3TNGC6&uL^BW*oK;*R# z$^k9*vkBMZ?SucV2wDF#H@GZkKIL3P;@AOlV-K;|5FkeZ<_!tqs3Ae#P2GlL53!eR z57A_j0RY&#>homYwB8ViaI)4hV1CFs;1C+)9=(&8^#%2FG?mUYu~}VO81|58i7=$Q(pM4uNZl*AT%pga7io(M@|k zPBZ1V@#tL*o|v^+10oDCFe>IxC0GmiT}HrL?{WnL_`ys3zX1=F>elu$T40K6^gP}v zZPgA7@b|>II@9e?4k$JcDn9IuG3u#RhE%|*VO7`D+Es3b;>|>RKX|6vrC{x^nwzfO zOI#;qfq`8*%xoO8*8+vMeFcS1UCZ`YaZ1OND_Qej06bFcCGPx~@Vw0n=SHDl z6}&L1tbp+kowe}IRlJzhD=+uTEKE7a(5xfHbJ-CVAI4tu%j$&<2=pCRS98K$)^(P+ z2suZoyjz@-wA@jyPbu)e3zLfA2>%Ak0<1QQ{i+JaIrZj{zPUS?0r9E_5c$2N4W1T& zHZRz3-fT*UmUWbHn#m!@B4jMbIc1lg;hgR+o!Sc^23OL?z;o?%9t~+s1Cn{gmpY_C7oH3mEY-Rxzpj&OxAdmrbn!O( zv?2ZP;{_di<+xE5-868y>x9MNMvW3V%7ZBbnGLU7<0?ldCdYZ@7M(umC7hbdLJ6gq zdzUt14hKy`nA`yB0nOF7JA#F)X?&h#7+g7`#aRaNhg!dWRNjChFNk?bw)}7Phe1Zx zZW9GV6dFQJ@J#?_Yp=bQ4^0IzaMHP!b9XAlO z5329QR7vO$=2Mn}x$kzUQvO9seDRf%jCe|f<|IuOWms-fjsl`7$W~Q^&R^3YM{{qo z>*+_<9EL}@IRfoAf(GV%Kff>kM!vy3#8GbAs8Ds5{In)en9UODkI7@?8J4wzj7dNt zExr*8b~p%kSXkJpo#r5{XpAr}wcDRfML}FOK}qo$k^&Ht>m=0yMLwUAy28}016r8N z-vQ)M6wcO!nqt3>#dY-xp2d<#h^sA={Tmk*oVk7%7lDl;mpQ1Tg4_Y&%J{=PgrE}* z#T?}iuyZg`+7RPF2bAH4>!>$(%Cq>~%pZA~NsE}Y%5s#_@WNbF+Gd#!N|Y>dB=9;m z?x~7$Fv%YdRUN_0#z|k|AIJcQqrA*J87{1$#4tSm(Ul>82ja?1I|k_o1uaU7?}~Eb zb8ZLd+bz|UVc>@c4nI{63mFT8z2CiB#SRPMCxy7;1GRa;AwR7+B)ucy*eT;Ri*;Z| z1eEa~lEmZC(c%h9N_8?Mxuum*(Ybbc_SpVmIJDKsVU$SB*#3Y)7{K9n7RsX-nNtzB zA^7ICBWV^Yhz;aYYz!X-GCG$A+n>V~w5QUohMXHAdgpaklQp0}4*lWjb{Cy3fSnOC zS`fjZ%8J1nNVG+qF=qx+pD9CvYoK0;1ZqzYv%Uy-nPm3@hGnaovaCX*I8z~MCCEXk zb(Zt6P(hPK0{eC2hCX?YME8Pu<+&74T@*(IR2P{&pjZ(P3w6h(P=|dcw^nYOa&t4G zXec%!6ZjU<$EDPgzL=iR44*?`H9+AeB~F+tCnx^HiJirx(T|Ig!XeI*qCf%`XfAth z0C6BJizRxf)=ELnq`(ff4xE0OM2L$?go}!*S`$ENQ9bx?N}{5;NNRGTPFPrnqJ`ej zfP*_F#ha4c9_0fuyV_V!IN&Z(=i1dtI?roXkZaG+1bZJPsDjmrb^-hFb{kVYgJinitSTx5BsrC^(5}*<;UFDvigD0rM>J|qaGM>F8PSGi=*nmqLwY-5 z`WLD4TSULifJ|yEaIPgGxhxg4gDnLs!jfIu2!qtW4OxTx-V+xWSx=sRxiL)vM||P%)|omvHwVO$lE?E(z<>uRYpo(=!WLs)42M}5_vy0V6(K7Kap$a#wz^aG=&!PQQ8z$Tn8A~t>$;K<5}?j+-Q)V0)JVgQENL$H{%X?ty!n0SF6N>m#GF714rTw{uu9N&uT zT2w|_RmBW#eKY`vc)*Q4yRHY{cnS?iy>M89STEk$JfWU8fmjwr-Dp?1cC@=z@+1kh zxnXoRcEj(@faa{Pj~M<$)KcSMHHVvCUe{l&d0wquan>&v4a#tgi46~FV$E{^ zBqJoqfrJ@Tb;ltH^s?)CD0QRndwH~xg_68r=o+m^wDkls+-<_sKW~QR40P;ztvg60n~9qb@{!WqWv4p z=-}Bg-l^noARBZn$lJ9j*|vX?Vy3;h@lDKn{rDd{au*1Q>-K;G0UbmC$NsgB#!gP= zHl}~AvuaY^boj}M+Pz((*Sc#0-E1$jPjy0CyCq9*1z2zoRPPOBeaDdimnXkXvK}#( zV)EnS9PQ?5x~JQ5a73OV6W%2kKG}dDh9O`MNR*0(n*DZerNru0pOrBsEP|0rkDuEQ z%e(#kVxON)kIo_u*aM0D@X)KZNwp|3oT{Zaye$p}EWomitL_o-at*mV$Ex9?g9vp^ z1@Q}g6mTKt^NrD`+GXMk?I^$-m02CEv!tk3EH#Dy?9<(G*=r>h(OM$(vm;YrV;&I( zuPGUKa=hzy5w+gSxm=;u?N6Rv4D3WX{83V?K|d}$hD%Bz!wke2Op}j`;J14XELd5^ z!0e!@vkyMfpUr%pgy*4%NB77LCV#fe1ZP<2h>lA~g*y_NG;xcLdL}0$IA6dMItItg zP~^$v1d5D5+dzSZ!U2!s@<8|s0hV>K5KWGoc1Q+zYlEc{%Zq5lhVfmyJ93HR5s^Y4^{QNSurUZQQW*K|j+qED`YH=o;=F>gB)SFa+Nu_eDzoxALD54aHdZnj^gIZu+rTs zTrY#sZygXq?SmP~$dbSSxUjRdr>yx=zpaHYe$S=JGD&L+XB+VIF_is$9nmi=}EZe`fP z96Rd|+_#6tl*62`)8<2JyotbW3UYQdp~>gy+LjZ^N!qh$^}YW_EHC&`W?ZG|R1P)hG+Ax%yx#qNwUWTjzDr?QygRP(eg43a&Q$# zWX$Uk5o=q(#mSW0wK5n@vE10J4*FO`oI?wTn>F$-9WK} z=dONqBId4GhyB91E_JBue13x7Q)ACKJki?zm96^RG*iUa>88m@<}&ZOV#F88d16EM zkF0f(?${;-`-RCC3iyP}HVJ_-%`bc)mer!)26a8;o#Op{BYDTofC1Yv=EQXqT{==fdj@fZX zPIN+4w&jaGMZY7JXI;tnh82~Ii?^zdJBNd8a7tklB#^Z!mv2v8s$-5;{aQ_lKOcSFLdh8; z-On=K0F1Njekmk@OgA+?7u&p%PxlM*SP?|AbL5xp43x2N+RZzGQPQ0xX9zETgLR{l zeJAbm=&vL!jV?L#u3v1d=0Wr9`m{a@X3-Pt*TJ?=eiCq5t-W8I?cw1ei-^!I-;dX= zZ@~ZCu@|mfM?(-Gpcfb*Ae4VOW@2mOq@r(NW&F9X)MiSb%}++$;VbV5%G6A0tHdT$ zWa{CN#t8%*PoOWjwdm{d7lLi`FVj3$v5XhBQXO~JaUK^mlcc}_P*_ePlAuIfA2Uw| zA9rtfnk}xh_2i<|)U8*&*mku2piG5c2RsDNT@xXORMAgJ5&>Vf(~u80ni(_~OekAb zQ`)jB$YH%aE1fr-zN1A!50sM|^haaDjMcKIIvg<53XoqL6RJnC;mwAfrbPm^);r?3 zhebk(V8^KuOJeSFcWoz=GRFi|FhwtoTYH49tBmT?iU}q$Yndn>jW8Zs;{3Y6WWtur z6D_qgVR?#BqH*K_I|&nO9tt?hK*m5pB;H#e9<|X`SDl_tXXi30e)k!MAeJstiDJTt z5G;_lJ6O*>Fzh&6>%;sicIUxPZAN6mnqEbkX{Lx|SRQySouMa{T0#3OA|@)m6nM3= zZ<>0xW!DA#VzSIuEnu;v?z?|5*GZt3&2TZCat?ETWs8wT*B7|kx6ZgH9e-{&VBT91 zlY?)+K}vS+f1X1*dvw9qRF`RU@U{+|6-TIfK(pi+x;r!B{18*M*Ss0y1Z;qPYX?#~ zI;N%C>3rC$ZJcS7J#=!iyS^}hcy@hTGQyzyp@_l$u#jNx#zBqI&hTYEXUGU~AZ{yO z-`TJqUT_v@#W))J0}P3;g{gxxpWOVL&{p14i(n-qa&ogEcDyMw-zTmBT~k;>XVTW}DD zsk*B}@9=5o*VJzEvl0*4YVmUKYn=bSC6k1RTZbWlfI6swfYAPROFEeuTN^X{b!7T$ zw=+#y+jVx#?mucs?FWm`>&_AFV&|(D%f;|0Eh4dvqFPpzii9Vm-S8Loy?*+ewF5Bc z%>%Vng0ScFZ|OF8%?Q7AF{#!fC>0%!hGc(|L5>hV@p|>>D*awM3&jV13A2}Zd(b>o za)&3}E=G75xoGkXd>>lI-T`?=AYGKWXrkJ)T&elq3R44x;SMF)I#bizdap zq?sh7Na9gNY4c@f?*|03=!KA^)t63I-7s&zJ>TC?WSE?VSzlqfX)@%;VYRj2UQ!wKBPvQZ=2`hf9v6gtQ)d zU>ig#&F59F0<|PzO0CPjUW2r)l0vWg+w~|^+E531Bnlp3({B`>5z8i96Boe5#K(du zI65Kb5x;^)v3*$SXDG@^fbjO_g4)*+LAp^+{>oTQjLAkHrx%fK__gdkn^$NzAlq0Y zV$?3I)Cx_Uj$zXq?x$0yI!huoYE8KzEap-&xr{p!3|GuVjCzMV=uY%VpJ740nb2e} zWMmy*RwDAF*E2Y7J#L=VDDl$y`!Dbz5B+V~0RBBsW@Wly|+$al^ei%nI#jX&UF{u1OmvSN#kBn2-RRPtWTceJz>sHBca?SB8+x&06 z+?yed`ug=HFYCgB?G?^cj=w|a7TI?Px5b(LXFPwp%kx~Bo0gW)RZ3S}8F{QkT>9R2 zY|c5)XgRIp=nK0HCFQPb+xlsz51&k|70j9O9z-(a`_uGT{7i~c565M}L;w*VqEK(` zpoc4zL8ECJ_+eQ;SU{Moy_G}fO|4sc*Z%7bH6;ZBgYE8*1UCVy)?)bb()dwuUjD7O z@?J_(XB+k6;xa{%_{EKRnPNDmykxBO{tu6X#|zyPOq^)Y(6c`Bj~iKmS)-R~Z#m*M)~p zVMswjBnIiOQ2`MFB^Bu|hZq_pg^?U$=u}z|0U5-XkS<5*4gncLN;(u!6uzr(_%X}- zWA2)}?(>|p&t2!7{p`K&-R&W+&u5~?Wf;8{LPKk+vCpLiEj#j@QHyM~?|l(BWHO7Yr|nwV=b5thnU33Z35+ci*_!VfbWM>6iE=!0Hm@DYHNe(WgR zI4*fBm(IH|09Rks)7Y33vow^pnimhN!SrHJT_$di`R0*QyoV=ig!fJ<8C@35fFTcP zn4eo?@437}TyfQ!tS_7Q#Fv(OF)=>>`n08qc6@fJL`OG2C@FHze@Ktt9c-c? zM&+Ye$C$%yPDh=$s|A03P2;3-k~^G*zsL^b6hKx-TpFeP`mj+=*XCIejk#9Sn;tc( zgs*lPP`q;&Z+_oXHhudzqV( zJ^QA$NJNcAopyTF5Poi&!SV=?%}!V#KUMmU!v<#Odv2F`(Z_ttmsd{bGX=>!;$?qS zm>rRv-S~14(1~JT!y^W~S32NqTKu?7jf{ibN;pMmB&_+w~Nw)4ufuXC3QW6 zBI!r&f_Z83$5g$CQ}!c>jCU;7eW(k3|1-_=F`(Nv6alu$nmALdqaZnYdq_M_8{sVao7 z6cZ8yH%JSBR7+k^60WJY-|$8F8S`I zu_nMCUcfH)Gj{!5DfU0>ewB$u4O9aDIhc5^N&8ZjeW@Mna=zgKE&imE+oiIEff{5H zUA?**wz)u;fLR>Nc6#XP986vI`Z^?j==rnKRK%*=XLso}*!y6YRE`?=DghPIHN3)a zTTidGIo8?Q??x$K=VY!EEp{#M5&{eAWpJeRgvl8S+vIq^+OmcXKB+$${@QY>fmfGR zg=^mmUdY5whOKme+!)GM?}X?S;je0-wULedl2Iuc-L}m9UTpw;qOYl^QPQBV@MGc! z{@Djw-Jqd44;X9>7|wLY@Si5@c(}TG{(7p}36(CPqVzX?n%nmly2SDb?dg$fAU>zD z+h%OO2p%}sv@)vnBtR1?W%JGl?X@*_g;ilG%*FD>AV2RAgq!qU*_Hf=ti~28Nv=*P z8Y=HVBv%*8^Nv&2>%px1yYdlYVJhxD_NC0e-PI3`u-+OjH5VCw;-wCAmac4tzE-c< z7)JgXVcHZMcP7XSvqtrxPBN}-M&PYdkMDXi?vL&6D6M-A4~kE2t*U3}OFA!@8Rcr< zsTOK+r{Sp5`nDMujzupw=3b?(oWM^~|80g|tZyGf4r(>RZCR9eDdWRw#S zlZgVnyZ(86Dc;@pQ)-#9@GB@8 zWc%l|1;d?`ibdz%l0E|gR<3{n%RH=iEdfm)9>QaN2u&YvU=JL^3?LHY2p98?+I#{hu=t z0aC1FB7DJ_p_jV^Dqw{DL~-`fRHZ+}ZALZe zo0igjr!try`ZPZL0CUyVvVaPVQq^{POJoYxOT61yoU68OUd{Vjtq4*1y}qbP#z(uS za>e8Nvnoj1^^N`C=`bW4m)tIyVTQjvE%ETvR zQ&~!-#FRkzZ8ItN1jm%Pj5USt#PV@7v{8J!gOPtU?RZiFyn$=av;S%FNhkT;W5KuL zs^wNC>tO#MTRS^4|c&v*uvZyL@xbC|53xcXhs%-Nlu+?(_*SP{VxLn>h?OwO^E zte5HOjhM~jYU_2kL+b`e|aMbmP*JMr=z$@Nvm$AQ;onHzSi^1B=|Z6j_lR4`nskpLRz4$XTG@%; zKwl!S2A)>Z-}B0H*BJ_d-o2@)N&=>!XL`%&xGQy)-hyEF!%QKWPFSKv|HjPZkLIlX z8P5T^fEAa5cLf$5gL||AWTKP=cD~m?S2g#zRHNNw%WiKlNPB@+YwOzQK~DJgl-&h? zjanPm!|QI%QV7O|NOnd@rkjwTmt$o>ggVaGyMH1TrKi@u_~EpIs}EQOK@cLpPOdtZ z#9Gh}o{wP)o)>J;d{8hlaeZ4k=aO|uXJ5)hS!2c?G*v`TQc6p}<8e&erl(&*?WJ7( zblVE;1xKpx&dUrTA*()5>_u3uJJ+d6Z+*hPc^LCl3>;MPb^euDL3g8{UkZlSB~5{j9eu#Q+(0h1ejy=9WpLE626GpwV1$nyyGQG6(_%&L zWUHqI{NOprXcl7CIpADYsfnh6Ics~f2|0RHH(j*9-2;VeR2e1uj)5%8 zNSGRlfI(>qYXyVsJdGUDqr_~9)%o*!y#<^(k2ORzUNXvVrCX{JkCKI#X z_-`(dw;Z=7ub>~Z)eFNeuQix=v5)7ruZG)wY$t2_-QS1n z(gc+fCSP045$jtdC)9{m7mo_zMrww@hzGax{FN=d_-ADx`-<%l5yFgPiX0;nx|$^# z!~_rGMJAaERZbL_IpON#r8uN!r9`e(Gbo|)tKV9M7e$U{alufGulXk#d{ZnKbuz<@ zGRKDOYU6KEDIY7=R39~gD9#V-Uc8GCoopSM^c}EKq28f|kWjmI8zHKLIK%?%;G3`B zyE05tNuw=uR_7M7^a_XQEQ?OrU7Q9vYFR>~wuo)WWB5E`X$-kKm@`+|nPTepN;yuf z>WHV8 zAKdO&x)OH`a6c&dFB*ZjqIw35^La^J9^5?XZyv*k|ML7_5(O8In|S#RS9$ag948Hf z%Yd7R_|4!O@(;tAv;;05H){MFPZ9nP9yf-JOM$Due^U^~{!xwN65tBm-vl4d5d2B@ z;&S0?$KPCIDBRBcR+6|ZxKisk%SPNkqjE-g;o@=q!Qc4Er2m^@XT3umbs}H}{hU=a NAS+;c_+|Y3_dikYIPL%d literal 0 HcmV?d00001 diff --git a/sourcehub/config.py b/sourcehub/config.py index bfb8e41..2b73b1a 100644 --- a/sourcehub/config.py +++ b/sourcehub/config.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import sys from dataclasses import dataclass from pathlib import Path @@ -55,6 +56,21 @@ class Settings: @classmethod def from_env(cls, base_dir: Path | None = None) -> "Settings": base = (base_dir or Path(__file__).resolve().parents[1]).resolve() + env_file = base / ".env" + if env_file.is_file() and "pytest" not in sys.modules: + try: + with open(env_file, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + k, v = line.split("=", 1) + k, v = k.strip(), v.strip().strip("'\"") + if k not in os.environ: + os.environ[k] = v + except Exception: + pass + data_dir = Path(os.getenv("SOURCEHUB_DATA_DIR", str(base / "data"))).expanduser() if not data_dir.is_absolute(): data_dir = (base / data_dir).resolve() diff --git a/static/index.html b/static/index.html index 6e4eb1c..ee695d8 100644 --- a/static/index.html +++ b/static/index.html @@ -75,7 +75,7 @@ function renderSystem(){const cards=[["DATABASE",system.database.kind,system.dat function populateProjects(){const opts=projects.map(p=>``).join("");$("#uploadProject").innerHTML=opts;$("#projectFilter").innerHTML=''+opts;$("#receiver").innerHTML=Object.entries(roles).map(([k,v])=>``).join("");renderProjects()} async function loadAll(){try{system=await api("/api/system/status");$("#userPill").textContent=roles[currentRole];await loadDashboard();await loadTransfers();await loadAudit()}catch(e){toast(parseErr(e))}} async function loadDashboard(){const [b,t]=await Promise.all([api("/api/baselines",{headers:{"X-Role":currentRole}}),api("/api/transfers",{headers:{"X-Role":currentRole}})]);$("#summary").innerHTML=`
기준 프로젝트${projects.length}현재 운영 기준
진행 전달${t.filter(x=>x.state!=="APPROVED").length}승인 흐름 포함
승인 완료${t.filter(x=>x.state==="APPROVED").length}immutable records
수정 요청${t.filter(x=>x.state==="CHANGES_REQUESTED").length}새 Revision 필요
`;$("#baselines").innerHTML=b.map(x=>`
${x.kind}
${x.project_name} ${x.version}${x.package_name}
${x.state}
`).join("")} -function renderProjects(){$("#projectGrid").innerHTML=projects.map(p=>{const b=p.repo_binding||{};return `

${p.name}

현재 기준 ${p.current_version}

v${p.current_version}
BRANCH${p.branch||"-"}
OWNER${p.owner}
GIT${b.repo?b.owner+"/"+b.repo:"미연결"}
`}).join("")} +function renderProjects(){$("#projectGrid").innerHTML=projects.map(p=>{const b=p.repo_binding||{};return `

${p.name}

현재 기준 ${p.current_version}

v${p.current_version}
BRANCH${b.branch||p.branch||"-"}
담당${p.owner}
GIT${b.repo?b.owner+"/"+b.repo:"미연결"}
`}).join("")} async function checkGit(code){try{const r=await api(`/api/projects/${code}/git/status`,{headers:{"X-Role":currentRole}});toast(r.status.message+(r.status.commit?` · ${r.status.commit.slice(0,8)}`:""))}catch(e){toast(parseErr(e))}} function openGit(code){const p=projects.find(x=>x.code===code),b=p.repo_binding||{};const f=$("#gitForm");f.project_code.value=code;f.owner.value=b.owner||"";f.repo.value=b.repo||"";f.branch.value=b.branch||p.branch||"";openModal("gitModal")} $("#gitForm").onsubmit=async e=>{e.preventDefault();const fd=new FormData(e.target),code=fd.get("project_code");try{await api(`/api/projects/${code}/git/bind`,{method:"POST",headers:headers(),body:JSON.stringify({owner:fd.get("owner"),repo:fd.get("repo"),branch:fd.get("branch")})});closeModal("gitModal");projects=await api("/api/projects",{headers:{"X-Role":currentRole}});renderProjects();toast("Git 바인딩이 저장되었습니다.")}catch(err){toast(parseErr(err))}};