From 8020bbd394f22a93eab2a9b269cc1a4ef79a013e Mon Sep 17 00:00:00 2001 From: Kincses Date: Fri, 6 Feb 2026 23:43:01 +0000 Subject: [PATCH] feat: complete Tier 2 onboarding - KYC, Private Fleet, and Wallet creation fully functional --- .../app/api/__pycache__/deps.cpython-312.pyc | Bin 2362 -> 1968 bytes backend/app/api/deps.py | 54 ++++++++------ .../__pycache__/auth.cpython-312.pyc | Bin 3922 -> 4952 bytes backend/app/api/v1/endpoints/auth.py | 35 ++++++--- .../core/__pycache__/security.cpython-312.pyc | Bin 1913 -> 2414 bytes backend/app/core/security.py | 13 +++- .../models/__pycache__/user.cpython-312.pyc | Bin 1907 -> 184 bytes .../__pycache__/auth_service.cpython-312.pyc | Bin 9844 -> 10649 bytes backend/app/services/auth_service.py | 70 +++++++++++------- 9 files changed, 114 insertions(+), 58 deletions(-) mode change 100755 => 100644 backend/app/api/__pycache__/deps.cpython-312.pyc mode change 100755 => 100644 backend/app/models/__pycache__/user.cpython-312.pyc diff --git a/backend/app/api/__pycache__/deps.cpython-312.pyc b/backend/app/api/__pycache__/deps.cpython-312.pyc old mode 100755 new mode 100644 index 9be3ecb95a5f4ca31cfb3c3d0e1113e6bf5e0dfb..d194b41fd09dc02d4f540ae6219b4311269d82db GIT binary patch literal 1968 zcmb7FO=ufO6rSDHuH@CfsJM+Ca z@4fkX-@N@X8dU*n58SI(MFHSfxXrtH-stheLLvp45^85CIID1Gm*% z!q)m)ifJSm^<_^=`O>UthT(u2foU@ele3B$`$*OXTk%5TFYaCtXlx9(oazL0ATo%H z3K9*LVFNlQrqPpAQ)k96RAGZyu0s_}4AR8RXK-??N#;k+7#N>-k$DUn2qLDBc(Kkw zjF~-?6%1`yB^=E`qMI|^#)MVZggS8-Ah!ujV~CbgC8J@L7Kcj)3tY0@Ig8JQJfmr3 zQ_-BEvL{gmLDED{Ropti*N0etg?&!~OnVv#EFYwIKLE4^|28g)Z4qaQ(W(O1{VYEKAX+~R+@Qc*13ijJ2#ycVMKIti^g0l zXTw_O1|m6+O~ryp!&|)8IW4JTLSk4pm2h*W`6`Pr#d(@DmR!fhojas+16#RyYsM%x z-(a&RpgnKkC8u-4?%d5eu%07^&4hV_bnX_L{B+|>IU_e~b?z?YNar5v+_M)U+XNxS zLLBk5Bbj%cfOHpsar)@NVSTzXHa&H6^6V*Xyi6tbdT0<{fYl~}h|>^7xVe0O9wJCZ z%cPR!5SqeO!#0raI#73!?y!-jdLc*k%H))OVsg4trmBT?lidh`=JWYOZ0_u_Q|0n_ zrJx{AI27k=1RF-nb`6sTo9v>U+M(?vqCT!qhqeT<^-B+6k zL5Jwv1r0G59v%P2xO(bdf8M*8qmS&FM& zAPuNp6~uRcx^Qh_W&c_%_Y&LUHrohDrmw4?sb3xbdg#j|>)8XV*#o!DZ{}{!{gD3d zz3=y|7RT4JC)N@Z>*~bv@r`h7J)B<+=hwpfmdn3}S3=V|2mb}%JOBUy literal 2362 zcma)7U2IfE6rS0;_uk#T-QDi?2Wo5CgugC7TUv>TAj!hE6fiA98^RL0x!pT0ce(rH z+`BEswl+WvD6ybXF!X?NUhVP5m3rMP5aK`Tpp<6aAknlJ5-`z`F!%F}^(z`|}V zn68LdVB|nVR6OI-4P4kuyizG6xYwb0&pG2&pyf!Ft+Pgz_6TfzL{j{u8fH}V3QGAo zKE7fw5DxyyT!pde-b*rs%0`xkIa7?09LXrU>22%mJlJw7Npgmo&6t92$VOfV^U98B z-Z;^GP}cQ`EC z`mvDy`bU2OY|r?kJid%K3GZ+K-V?TP*CYU+;E3~60l;S%nlFT?L;MPF zFZ)^q@Ph~<)1ma3ycOnTF$&-fAs5uMP#0bVX(6`mC!#xqZMF?Nh8!t~?((9PEVv;~ zuQlL}6^bF)D8*J;RusoLXWz#e?1sBad6${M(D2(bqufUJj#3r2q|}$JQY^&Fim}2L z3~{!XlMTS3BAe{~EO2EBlbl1&RE<5i)Z5%k)s|#7vUS7!5dV~@)Y-PBUg4jN$`GHb zFX?Rb6mKD0U+NWMskg&uH8QMh$ZXaiT8Gj9zAxvKz6OeK0A9=`>m7*gCa33nPD`Ea zxrd0AE*z#*NG=JM2~0sDhOBC*z56o#vZg8_i;ky#EHQ*KStHb&&1aN+0QT0Z-v07r zC_~aA`J{3CW`86cpo|_S2M>rcic!Wcvx?=i#MUiMiLUl&S7%$tp?&d|7-f7jMRJNH z^9G^p#FV`@lOH%iC@~$XVsdK6fHw=dN~XHW_o`Gk%wW;o)WQ+u}pc_COgsT4zjg=JUT>{^Y?*;3x=BI*d&x^CcnkySGZ@fyTf@{u;cm37( zf~&?`XEwD>Zfcu&rF%Md&9$8~wVNkvH;*fy_g(M% zuHu^`-_}h=TBd4Sr^@%vc=nF&nU#EFJ1*>a>!lf~ep0HRl0u`g2a^AuuXbFRX^2iX zL?`ygr+vpJT*vN9{;^$e?pmTcKJDwCaCL)f^uXey8dO&K7s71kh550`hS;6NwD0(Y z>-ap!6p!ZxB(>qkdKiv+2FGggx$S$J(DzXZ;4K%9)i`gtqYW^)-Ly7V&HY&I1l%58 zX$r<*PR;aDmWoWVR|bORR0_!&Jwc1|qCBDl1E^{YQuc?K&QuoSY>}f{e^ryU Generator: - async with SessionLocal() as session: - yield session +# Javítva v1-re +reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login") async def get_current_user( db: AsyncSession = Depends(get_db), token: str = Depends(reusable_oauth2), ) -> User: - try: - payload = decode_token(token) - user_id = payload.get("sub") - if not user_id: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token error") - except JWTError: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") + payload = decode_token(token) + if not payload: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Érvénytelen vagy lejárt token." + ) + + user_id = payload.get("sub") + if not user_id: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Token azonosítási hiba." + ) + # Felhasználó keresése az adatbázisban res = await db.execute(select(User).where(User.id == int(user_id))) - user = res.scalars().first() + user = res.scalar_one_or_none() if not user: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Felhasználó nem található." + ) - if not user.is_active: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Fiók nem aktív.") + if user.is_deleted: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Ez a fiók törölve lett." + ) - return user + # FONTOS: Itt NEM dobunk hibát, ha user.is_active == False, + # mert a Step 2 (KYC) kitöltéséhez be kell tudnia lépni inaktívként is! + + return user \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc b/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc index 3c2ff8bc5f8dfc8e5146f5de5b87b8fd15c9f3cd..28947ec6e5e602abb535b76b1fbfb8e7ccc80dc8 100644 GIT binary patch delta 2238 zcmZuyU2IfE6rQ<1d$--)ZvVI2ZRsuYw?MbHRw`(X6(|OiQUX;eCe3y4w7uQCKg_+` z+7+zeABjHLj8Psi+6N;|A!3>kVu&%h;YnjwYXYn`V2noJ7OIKy$uoDmSQBsBGiTlS?w0)woN^lY#3?4NJn~w*_aHC zgj~JSY)Xbl!WapN;Kj8KBh93FvX^y9b&8MF>Hdp>xv#^KDpIQliFZuWt6!7vyS9{F ze#9mjwQG;sZ)zDUXQb@wpQ)6L8&=My&V1Z(mQ-iK zQniF_OgZx=Oi4#lGfXw%Tr+hrkr=xub>czxgR}!TFh%~jYZJsRRP28-PBONUPDMSm z5mso3FU#=>5>FH)4nRS+(+QHIVYVW7S8n4zTm+ZGo>q+OS+DXsJLaord8Lv~`-1Fu z#fN3~r?MmHxu3mwQUpXgy`g>H^WqWIi{9SnW>=AY5JEjkCW#4nCrHlCBByHcII*lz z_h2La)zR&Lx}Ut!iq*D4~DFw$)N58kBF+nNP(AX*2uPw>`*{C@4wDRU0SS zhgrnGvHqZQWrEnS-z|E#x(x`*PWaol^Lv-dqgYuNAPd;Xvv8B3k8nE|TiNISJtKp8 z-=Vy8sHmXeBY#`aG|-vmUHClikv?eJg>!+<_!{dw5;O6F2gIn6D)`rPKhEQqKN*x) z;eYikdtTWt3X>8$q11_JQDj5DkOC#P0=B|ZzLr^CIWM?XZgQ*a%?`l6QaXFoLy06n zFq2NG)VVUATf=LoIz%~lZS@Egdd#KsvziI7@?TcZTUu<;co(`wme+iMm=l_nOO*(p zB1zTOOqfi}YPPc!qb@^sgJbpy5C^DcXSq3>O0=u}|8tzC4mk^{obAlhxOV^4DNTh- z*s-WUJrIWWf&fnWU;wbzARGnsN|fU$&4PUo_vLSiKFS3r-hVkq`}jH+oPiccsKmG` zVBC$G=(jb|3mY$(pQabmSErV125$HUUfFlc7yPj*azV`Z9R0p(IPV?4+1Qrfnflh3 z&P!>iwEqD9+X~h|tTUVY1^fx_N799GzrfzAn1M$cn1vq$K^G0|R>$jBN6Kym|AWM| za?Vo6lwnyp4PY{PVU|?WNF~;FkwG{AEYImFz(|cdmy)XKEZ8JxBzCIAbSZB;*Aify zAm$7uYwo}sne;-TrR4gF5aq%GU+ac)B>i13p@-KOHcFt={}ItyqOBr0_f?_&@% zYm&$5VOYamAS@G@S`;zHw^83R>RUm(R#4{(iu{7wS5O!H-A2ck(eV`&T|t}Jb5&!r z3VsgfBRxgLl`qYE?zk!s=3OYCAW@W^aYne0$e&xcjbBfiw7~KeDwba_x?{ zz9@60=s|U%d#;s-+fWqT)kb!wDzsU|&)|I9Lq)`ut1b83k-N3u75EYz0u25EBG+xn delta 1490 zcmZ`(O-vg{6rS0g^*=Fx27~!oNSj~?wIBpa1vE{Q7NrVQLaCY@S}p9@EU~?4c5Mf0 zN)ok6)mCbvk!YkQQaH3t1gMEPG>1y9WT~o@%cdNXSVBpa9&+fxz@a~<&MbD|l9Bbj zH}Ab~=9`(fez@=VrnY-!WgfuBSK}A8AvXX|`J^-)MQ7_Lc%>~p4}kzMTq*`n2XJ7j zvsAicRX46uJ_>xqL}rY}yay zNCkEj$(6gv!55ILs0C}f*`C$7x=0J`qSZ0lp&jKVPp#p!CPu5pp&~7~i`Fnx7p|ut z2v?y<{}v*2OuP*NMW|mARkSS8BdDLvv#1U_=mP5SJ4=V3Ls_J{sD)~wn?6I~A&=@c zD@xmDrBdBD;JUCZ*@tGy3s41f(#KK;X24nT6v)68F$2TC5xO8n=&w=-tfZULx4{-x zn|B{OJAtQ7O;3g$q>4pIHT}U6gf;XxN4Mu?9^l|xMb1VDD`>0hQ>OK%t1$#sVOiYS z)AX`3d#H#$adjMeu5M1uimD?kOqG`^cC6~WCXRtV@a0ikql>28`vXCiC-odc92z+^ z(L3(G(S6+A&w+~%Bf|TMRC8$O(80h!3}iOQ0p{eTxE|Azq?`GCNn?`1Gw{zj@MlE> z?f0bV(>sO)SSv~{^`UE)aInZSmj#Bhph7{>7#f^&=Ucy7ha zEgG5})#EctLNR3=*ODrWrWN{^zjb~us}2j~ATzd3gLh_0n?I_Pm~MVJtr$i|Co1V< zNzO337;LNb1Ri)g?|V9Lj^Ez1k&JthL5o> zywnB1gAw$-y9>^i`6FpewCGGHPAFQObkLtYmruOK%8t#N@?>r$J*CJA9Fr9}O7x_O zlj;FkNoZ$u!a^wna}&qu+^zUHPBK5HC&q~)pHWOxOPa5YkXrh=x7};M82>U-Cuz>x z+H*oTOgWKTBxv_+D#CT3+2kdcg_&uNIGA-wUt79&|m}3rE6@^m4g1 zpLY;FWnz*?(@dA$&vrl9*PRz**d%si@pW&3ErwvbcU%JrT}{RD5E*7M{*Pn?85Tqc z;bSnk1_swb?>dOAgU~u?SqEM0_ZS?%503xguKXf&&hZ2~FNJ7B<>`3`d>>k&o&w;` zFY!&=ZiztXEC@VQC)rS6`8R+7-+)&0!Gi7FZP>IeC$ wo-4RGvHpDhXQOMd;Zg74-RP>m0VFsLuTB(fdn3CUupLfo+z@z2tnZ$l8LDiH;88rO~`w?p&HxpQ4~IkFK}|a@n$mWakz5ba(tI( zXQn7MsO`mHg%AW%(f)}?NeZafKI8f2ivp7gjoGKU)I{vXrpy;k)<>I)yBET1@Ffl*4EaEqLna4mXGDSmx%3zyTL1x$Q}}OG~D?sN}V> z^>)+LBmha%b*=ioK^K{lma!91^nyW4Rth+1AwT z$BWlK#phPNpCetX;orN##CL>A@=GMq=D_|jBambH+!%W4zd*RxJO-5Uw4AVO!oX6D zwZxYVQ#%-j;O!2h-dle*t~L&cXly delta 499 zcmX9)OKTKC5bmDonas{>H!o4}v0%tDC^;mE2Sb9$qT&nU9&{rN<8g%sQy6V@=-!S|f1TLe~x$$=UgE(U-?+NKqU6*y~g9Sd=J>wD?$^ZoL z3OV3YS9oM~8~bL|n0#Bed13C1bA5}S*&d0}*Vm2P)*L}c$Ht${I-$tI23^HeABHL! z6>e*lgl#$xQblk4K48;|yh7w&G>tef`l~<@29@ zn(sJw^|7_IQ2Dvf+xIrwo%m7vWl_8P`2Mqvhn)vcB7s+^39srit9h;RolOyv9O5KR zL8&;;c43G&Nz~9Sdsv$%6!E9gw7%oC4yysgPNo2B^ib53(P$~n5GW0M+kGsTUzjS# zLcGqh0hTMM{#jYRXIT3EAIMqV%jBB@tl bool: @@ -17,4 +16,12 @@ def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] to_encode = data.copy() expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)) to_encode.update({"exp": expire}) - return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) \ No newline at end of file + return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + +def decode_token(token: str) -> Optional[Dict[str, Any]]: + """JWT token visszafejtése és ellenőrzése.""" + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + return payload + except JWTError: + return None \ No newline at end of file diff --git a/backend/app/models/__pycache__/user.cpython-312.pyc b/backend/app/models/__pycache__/user.cpython-312.pyc old mode 100755 new mode 100644 index c7156409eb549153eec1aaf1c4f0e9a39c714cc0..8d3f67fd245ed80ce131f884317d49f6ffd6dcb6 GIT binary patch literal 184 zcmX@j%ge<81m|})XD(%AV0aATzyK4J@mU1On9h*GkiwY5kjogwn9CH!#K=&|q{;je zsE0w5=@v_9aca>m4iFiXpOfmR$$X0=GbJ^zB(tQlh#9D+hy_TjWcUmu8GecDCl(Zd zac+J}YEH3!DNvtYLFFwD8=ydGPO4oICr};8>|$OZ@qw9Uc_RV|Wym@ct z{ryrb8VJ5GE_Lj61)�IQZ0*+5ZffCx{>-Imi@i#*y5NnQ^mb)|E}!RZPWIP1V&* zO%nTAM|X2(F6L#&aPww9<`t*l7R{oBGH3=7^&uh}Em5>Ka^UiC-If#HJQnNfpqrz` z?QDvcJ5tT~O9=Bdm8(-tr|o)NoAm;^LK#fLnJdxGNw$jrb1*>?I>^) zebJ}v7Wm`}Caw*R+W>G4$hzI2ytwR88dxs&Fx+##rg7b}JnT}-;)Z3pP11G*U$CsZ zZS15w%H`>~*?BH6-mc%`8jw4)Q*~avdb3_%u;wn$U!JMo`Wo$vd;U8#iCeAs2V>On zC&dLOS{+t|-I!>=_*Ebe(QctXtncQ_;q-24D4f|Hc=y2^J9(gZBnrK1_y*$fdK5_3 zmP|Q=s6tgD6M0AJWlD=tU7$qW(Rz8!rMSAIZ)d8reK3Dz5Ds?fJ2@dc=EyMFIX1kbm ztdD7jE4D}I{j0SsSKFR_w@ukQa4W8RZO6e&4)hKi1prz_BXk+Joq%VqErO^A&{>}} zJ<(kyQSg*msfO5XdhHfCr1P~ARsjR7UyuP|yJ%T~FrAirDHScuR(#81qUo8K2RvgF z24f0w1@3Pt}odvSS2`Z1HqYn^OcT%~l<8oQ ziOI^dUh^I|8jQlriG>4hBvBANJ0aBKb~`1w(+5Pfs{aBAbm+jTaBjCcvZekycs88> zGuOAFZ7qB|5alk0SM~~(jf-2OUGj7>Dolm-z3TAhoi2^47s8MB%0rv#8RN#BDQ=>KC%i7( zoHNJGIZND-cjsE-Eh=xww&px>PtF_ns=P7l z%lYGel{RJDa)Ee2rJJ+CTqqu*q=}3X-ux!vZI;VTxGF;8;n}9Bb>a5bpjP2TAuFUL zMJov>08>d(Qn-T>OXnx$4fG>xlq#l7A)OcaY$3^CU#1$4hUHuOw`fpK7$$f#*keJ2 z5P+gd^1PxulARWhvS#d!Ah=Lum`aM`Oo8!AQ<`TM?9xt)0%M&>>L&#$DM^gA)&|9~QQP&WO?h4UF;)NY;qhHek3uW*x|yQ_6IeIW z`m~sUd3e@`VhzIM2s(uA0E%8XA*7}yL2-(yWH!kXg}jg`utXmI>?u@x0^v!7T>w!% zQ`fN%XnD}S(K!Kxh_grPjqQ~)_HH_U{WtcPXlD-y6z5`3HBvu_|0K1a26 z5;k12!I&}C#FbWuR~l~!d8-s_&_$d42d=eTw7pJwTC$zA&CnTgLh~Y-p&w~xXw){n z6I2gM!c=5%TO{`4Xe7xerNXfYyZQ0t+|7@qo0mi(vghEwXM0~s&q)O_!r#0oP3MjY zb3ztm5KsG_RkRtgkcZf*%@ue-v7~t+FQui~1YbyrN^?3@780pKUP`8X13C8Y~_k$I5grUV9_u>-&`yv6Jg!eNw|(AR8&pUzFmfA$ zM!>Z)I|7ejL=X@#PpX-to&m+8m zFbq&uEEsd)@RA8U4TVnCw@Z|A61W8$#{^L^3n$W|1nCYU{-mPM3o|OwJd@031u3B_ zo9;8HZ$gba&n93R5#72U_t$@HFGQ5Td82!isBsE5{omx4a?P^0@EM;xBBl5Adc zco#hH`ajt9VeDdT#n->$7+7<(F0`DL-kw>Qd4E&I6J2p^uIY)zQ8ST1NZ#QKx1W

wCw)wsPw7X^A7olIvKl2Th!##`pDxvN3Bd7OO z1JOlL-3s*Hsy_t3*gXUBhThGtc)C|T0~OD}ZF^wV-c_-8ExNCIE_o{U{@Zr<*`KVo zY_7CyzVgh9{gG?MihcC1PUE%C@5brF7xtXr^ZtpIK>uoM|NL0Btz)%qOQmhgO55=K z#J^nS@NNIbY9M$ac0P7)$0A*9zuI%DXEC%qynERetJ)lf$@7_CM+tDO(s zv*_ELXY@5Yak$>*7PvDrXKk=sZS5blzSFnr@2mLxt{lI5^3us0?!S0G@l+n(z2YCA zk5$9%7v|2-Eq1SjhgN+<^LwhU;Hs;?(&_5Ie!{R8yl7n9K01gbjDP* zJ5}>@x9lDADSv-CM@ehXceSm=Gw@w4Lj1$u!Wt~0Z>#2x@4mKqzabiN*60Soo& zYzw)5w(SM_5YYeyx_z)`OL{7{!|LBQvyXyA>g&rihk zRDTdq4vhu7>4(?F;2$*{Bu&CL%f0P^^20zzwX7d|FyXR61bonkqMipa=)t1cDO7l{ z8qAhz#mKM>WZMw10IY{5%soG-^_Uj>)G)Jyz-Wf$1H*`~2Ainydrqn0>m>agOfBn9ZI=z{ten0E#A^ml)PG>?FeL2=f4!$X!5!A%ZY0 zX;i>fi#Ozv&gV9dQl0x;u}1KJEmi;Dc;XwCzpEjfv(|*7+MXu)aoR*F< z)id-$aiAW0ibm||A3e%AK$9B(i zOsZ;EdcSwhJ?EZ#?tSOuEl)f-UiG2VX+vx{a^FwC8(6CHzD0~AB$=|-oGot4 z+2i({BkssK*Cx!QQG&Res-oImc@ zX3i1quybwBmNzZ#*Y&5S zMu&+Rgbv_M#WOOINfc;vT1l{dB+?3E4&VTQ3E&_D%`BgjQ*)}Uxs_Bhn9d#;yobBqZze&&2C zknVEL&Ol6uX^v*jIsW?|ZjINlkSW;XX zr3~MwMd)P~X$$5B%e;Bsl4+@^byCA+zY(Rl1I~am+S1%+^h)@@%;KP-I60$&D0_EBNwz;f`9@vcpk_2c!xXLuo1(sZOA} z03fW($^B)Z^bFUN+fhz#$XfDsfJuNUfW3%)4Gqs~ct*o#HFI7rvN|!X*?=hOH$_I7 zq5?%|yB6p#RuDRat%nmJ`-BD7$LQ&|(H7rf_54Gqd$XDLZwiqOA@XzS=Jd++s&8ZW zfrrAuO<`a|82GLFy9>X*@K6}PG+YXX=v%=COUW19;!%y4ejE(rzfh~VfAPS;X7Bh$ z@Azi#$&KEVAN5XrR6n`VJ8?^W>y_2UYp>jByt#X2_s^os7asIZ{HcEO($M7>N@CNd z*uEjQZ;Cw|V$VZyADjJZDNxT|<4<_x+g0+{UAHgWuP)pex{LO39CSO<8rDIzTozYQ+UgdoS~;%0pzTIT5>ee=N*y7YwXTm!=oX5 zaVR{}h3+)tktXAvNG;HPFn_22aELJ<)CET({0Dmv^NhK72#>TH@AVCt8MAJ{BYTYN zoFC{Q9_cWyizc8OVflI!20Ef^?$PNE9lNZf0dBqdkTB}y?mO|Qi@#rG2HI<4g8KoE zEiSjW?zPwJPvMt#f*6_}OAjR>vCL9g>vkkFK&2z?0384jxsx&97c)!W=qxDgc(V^! z4*;}L0(DJqpn4kqzg|h8QW8Cj4k6Y?i2!^b05r;&^>c@U^N=C{oQFX5Ax8j?($AXf z@fZz6hVYwfFGsH7k!qM92N(c&k%7jg^D2Q>LsWos0IxE*g&s3RWDJ1ylrlidDZiop zEhi#fCeO9`EyP}{DgE{P!B2sE%pu;rWnk3SX-t3BGA4envN|S~PpcnK|1;Q6#pwQ4 zO-Rnpw&x1$Ln`f+!c+dU(f@qlH>P}JPWpprC