From 3ca683d06f6e30b82f0173a9cdd235cd8d1861ac Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 9 Feb 2026 20:29:36 +0100 Subject: [PATCH] =?UTF-8?q?L=C3=B6schen=20implementiert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Components/Pages/Storage.razor | 49 +++++++++++++++++- Program.cs | 14 +++++ Services.txt | Bin 0 -> 28296 bytes Services/IStorageService.cs | 1 + Services/S3StorageService.cs | 11 ++++ .../Storage/IStorageMetadataRepository.cs | 1 + Services/Storage/StorageMetaDataRepository.cs | 15 ++++++ 7 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 Services.txt diff --git a/Components/Pages/Storage.razor b/Components/Pages/Storage.razor index 2858694..13f1728 100644 --- a/Components/Pages/Storage.razor +++ b/Components/Pages/Storage.razor @@ -57,6 +57,7 @@ Geändert + @context.Key @@ -73,10 +74,17 @@ } Download by Name + + + + Löschen + + -} + } @code { private record BucketVm(string Name, DateTime? CreationDate); @@ -148,5 +156,42 @@ return $"/api/storage/buckets/{selectedBucket}/download/{encodedKey}"; } - + + private async Task ConfirmAndDelete(string key) + { + // Simple Bestätigung; alternativ MudDialog verwenden + var really = await JSConfirm($"Objekt löschen?\n\nBucket: {selectedBucket}\nKey: {key}"); + if (really) + { + await DeleteObject(key); + } + } + + + private async Task DeleteObject(string key) + { + if (string.IsNullOrEmpty(selectedBucket)) return; + + var encodedKey = Uri.EscapeDataString(key); + var url = $"/api/storage/buckets/{selectedBucket}/objects/{encodedKey}"; + + var resp = await Http!.DeleteAsync(url); + if (resp.IsSuccessStatusCode) + { + // Liste aktualisieren + objects = await Http!.GetFromJsonAsync>($"/api/storage/buckets/{selectedBucket}/objects") ?? new(); + StateHasChanged(); + } + else + { + var msg = await resp.Content.ReadAsStringAsync(); + throw new InvalidOperationException($"Delete fehlgeschlagen: {(int)resp.StatusCode} {resp.ReasonPhrase}\n{msg}"); + } + } + + // Sehr einfache JS-Confirm-Hilfe (füge IJSRuntime-Injection hinzu) + [Inject] private IJSRuntime JS { get; set; } = default!; + private async Task JSConfirm(string message) + => await JS.InvokeAsync("confirm", message); } + diff --git a/Program.cs b/Program.cs index 819fa0b..bf86c26 100644 --- a/Program.cs +++ b/Program.cs @@ -178,5 +178,19 @@ storage.MapGet("/buckets/{bucket}/files/{fileName}/download", async ( }); +storage.MapDelete("/buckets/{bucket}/objects/{*key}", async( +IStorageService svc, +IStorageMetadataRepository meta, +string bucket, +string key, +CancellationToken ct) => + { + await svc.DeleteObjectAsync(bucket, key); + await meta.DeleteByKeyAsync(bucket, key, ct); + return Results.NoContent(); + }); + + + app.Run(); diff --git a/Services.txt b/Services.txt new file mode 100644 index 0000000000000000000000000000000000000000..deeedd235488ab95af65d57e0ae4ae70d6e09683 GIT binary patch literal 28296 zcma%D1yGz@vc}!r-QC@TySoH;2A5y~0>RzgA-KD{YjAf6?hcRK-F>@v-`!U`RWtuo zO;yi-`s>sEb)W8EQ3ezY4e0kvHCIaak1v1!fCeH0QZaROwKO($Vsvqb1p)^9{mUO8 zYO3%+kjSi|mWdzlV-goj#XrPA0YUvAF}VLKMoPsQ;Am)W`ggeu#!ml9u=w8vCn%0u z^)ex~;ocy6$`U<5#yf_-AhbQziDZWRSUbB3d)>yqz0hd%Bt4a=p zustxiokyuf2Vby=O$_RjrQpfXSoUvHbP|>C z3Q%O5G=6umf;p681TE5Zh%IFsiIMsaVc4YjVEV@NzDs4(h`&{(yy<%E_@Nw^kM}># z^uJl0f{~S}@qfEG_%Ys(#i4DJguCN*zJrKF-Do&{RoF}$!)e1Ts^u|AS>!}9Dg64j8J@py7t`nuoJ1zq#*1MGawnO_#NjwGtysn z_+|+z{fvgMB~d$>r{-OCxeW6I5<0$l8GpNVd;|Wy%I=J2D9?`-e*J$}N%j{Is<3`? zG&OWK{Tq~`&Vc^{NzYDCK}HRIBReYxVI#*95g~bLVWkdzcdOV;fRD75d^I~GCZmS1 zrLw$PjfjJlk-Sx?h@-rbL$yd%wUd&SkyMF_rK77=h>Vl~qXs);5(rY4Y7(01egUn} z?(0_$DplJ2qC`#52w;?YmM!Bhnd#ZQs?ikr(Au-ZiNfU8!_59`A!N%*Ho#qpgy3i5i`$HTlDPCL0Aek z%{o<8ie@Q)Xye47PKfYI_%#qgY`JA|Jv;2GyQa7|h1;BA=ALl_0gM17bH zUtncfYP|%c-|W4eO1eBYDw&-67j0(VCDf^?MibJ1kV_BIYcXEL zs`U)43kMIE(-u-wsGtPtvD)de4JXd64os%9;@B3LdecEODa(Jh%Yzd)j&_Hai&w%B zI>>(jsxcA4X}WkL;@_@cYi-}n8OUqg>Sb1Mg)^tG6&!s&gwkL4or|BMl&PmV!n1^B zG*O~qC?|I0Gtksreb>>u;vSNYqlcf+GH6rS9aQo$0H0>KPzfC9F}DatEriBA)3)NM z0l>K@ns@+3Ts@=ziXUt}0Z5CM^1TEt<*ZJY?n61T*n*gvcb+m^C57C(fHZK(Ledax zN5Rg$0Gb~rbK!K~ynZc}eOk1E+-sfxGO)HBlhn;&m7Bj;WA%ge%A{qg)irTG%s@ny zQ<4X6x|@_y_~o;6eb2C^k}ebXU}lO-(GQm*_A3b*j{r_kMX3?MwBBTLHQV(A`5;N; zGjmJ5ynH-0`S$YM&oO6mr(ew=@?J?{~f& zyi;rN-~|k?{4=YuIgRgdTKT~uxqa;|Eb1bPce&ZYoRhEvwIvgjyFf-;X18(z9=mjI zIa#|68Jh0SOpk5BdFTEpx0xu9qXEVZ1kI4;Savf=oA<9$2YzZ~3)*d0*YB zke}YK4@Xz29ny+v`3WuS$vl38WpqD)tX`v3?WP4TnfcC0q71W!BVjt2MESj3@h|E@ zc935hGDTXEbyTRrQ76kvZ?ab&ciUACZ?6A{aSLbh^FG)zf?WqajfmcgHk= zM9zrRSAQ|}O1X-ivC^Rq5ATWwp_nG*KN6!z9r1s342r;Gm<-_2P zQ(kvpErVaJR)XJ+*ks#?%JBtyYf;M~D!l>^brA!6SJ>-SrW$$!zTnD2<7QwnXk&Ko zpl6)v2_`a4@P?|HWrfKY-Ikwf8wJ|+)$r)Tn8_4O~X1HIIB z`OPK&9c%i(0_X33{WmtE{|z?3?cg^y2%N5#a~f-QX_9CiQSokESLl*MRz5oH#{+$t zb;TU6z?7#6>_HAa5CNc{PyjzESHisWy#x7sFU;{I)X^=~(5T18+&ZpV5AY)2U!SvH z+u1IAaj_~+p0hCniJd&qvd~xVXtr#o&s5|c0KSa*u!(d(MnB`Zk8)WV)M~AmT?U-j zkbRVM`y(lRKSd4b~MI6_ksIg+{4aDtMWF4G^8XS?h*<&uvm zxPdi6n%4Y$Qky0{3$!yLPXcO*6l)6<-V0D?5}V1_Y(uRduO3JKs9Dq1qad2CV};R{ zip!=I$iT(c42nWwIc9U8NMFp*kU#*hhXIyCLfK+`2q#g>&rxLAr6zTw>k$-5-Ug-P zNJ!K$XQg()A9jcUEYXRD#Kh2z$1T)>+hClhX!eUO^)*C*EL&B1`;xzjNdUJnZa6uv z8|Ws4H5G@#5h3Kr;`l-9*E%zTM$6oDuFl9&j5Z8Mw3Faxl^6i|!^N%%?H8#>TWBxe zCt+{scs#lD#gcFMZM=Nz(#a3L)id?u#^F@v#93E!HI|#OpT~43+AK%(4vn+%jV0q} zrP*(W3k0yarqVPq25e}68uAbagRSf%=#7hY1UG3PfkV&PqbLh6ZAEHkZ+vL-_|bE~ z#2D&ms^qkKPvXuu1<{SPWM!DENBC4}snLT&kV)`K;qu+EI*N5$})sC6tU>Qmfh*I|eqxuX{g#_f#=?cuIATD29$mvu<#a9u#QC}c5H zlNHH)s!)jT^;4sqcg&43&Q8BQ2Bq!Vb%nQ&r&vHdb@|J_wsz)7D{N?*ou%o>IRn)h zGB|olz=4(P=mYqfL9!)M9qZzmjr*D;1%tOaatx3QXwA)oDX6agL5bRxbv$4y@@+^W zD(2@4bh>+Bz=YlDQMqTh97O)XCR|Q8qcd9hn_jsjWBGK|QgzAfF=HP|KiZ5RUyLy4 zbG=_4DX%g)J5x{)DCD~g^Sj+%+nPReCo8iaV7EFUW4E^4dB=Zk22wC|g(DPlERcay*Bn}mL<-}1gZ|Ka6mKB-(=e-e0=;>zWrMe8*!-R@9Ve@tbP0(dI#J5;uDnj zgB33Y{qizTdGQ>La^9Vv(uXCoF@g{;RSloL`F)Fye&0w70rjTtKLRBzh_Zxj2(`_S zFH+&H_*4|CZCegSUl~Xw9p(y53`@t&f3WWB_J04?y7bL#e{A^u{eJy=&1tgze${D` zg~KnQYB&!23Z~(RX28xUTut?G5*2$T;G9A}e$w`UsR0L8H^Q_E#@gtr{h>8B;+fFi zhv8ejr|>kvx8MeFOUfC$d|M=?ERmV)xDB?qp|~*WZ|2$T_3sEwjyb)f3k>1$b2@=eG zEwEUutwR-gF*c6WTlT81Hh)GsYIT;ql*m0EKoXn|n;O{=q*!za>%QB-Gk%erCX9f7 zllR$MG**byKE~CMTQMp|unBtfI~P$?`(e#3R_YXU-=Z1E^CTGQ=$rMaQP5BFy@;sH zb?X{Css{Q{_f|<^^%kGia6gxnsT_la&29L&VlTti7fa+dj>UA!)q($|2)3{n2_^w& z`MKHLpm$nAxB91Rv7(xwQBP)JYJu&U`<4*@!Rq-1N&nc8Xy4F>zxIsJ{jBvirq-+fvAYn-!NwBw| zt9q3?0Pl6WFyD!peI8u>fDD|0&&@d#LVi!a(Aq7K-U;Ql3YEgGay30(q5Twq+Rc|h z&HJQ^m#;_O(FQdk z*TA=nYJ=XL*2gGj-UVvk>KRQtzSjrut*C!`-C4{EDLg-ICq%ioP6c2bAABBF`uYC<>B|Ul*nDr!oqW{j3 zS!?9-Gz$7ITy>st_Olx1)I7xFjpOqAVx_l_^_Ul(*3I$0s#n%_>oZ-`YxzXlAmP{HNIxuhVWC@&(u<9yl+`8Y5B3p{e&E0Vn>~(5S>o9Gbq|?RvQlb^Wu~+WH z_~lw1P7b;q-wd}@RPZS9+BYQH4O(tIwF!0@Z=)yQ^w^X;Hw`@z!v!<>sLEdF;aj^H z@82XQdF0J`przl7+Aqup{U-aad|w5-Y_bot2~P@r6i*Z$NT=C_%&Ak0LNUHyHa=s9 zdT>(AXUgB=)C~p_c^7Z5wJshp^+SgSu2l~~MXwWgI~4#m&P23!h(I5Vja%xD z+he)rVjLG@5hl1B)`~6J zm6wXRj*Gm`V;Y%hqez#QmWjdh%H5FXI?V$(H(;@D1>++I);$V^2X^FBErUh$TSVvY zTC%GUFLw_eO6CMlC_a{&u)zIK`JM3<_Z-2sAbWt~g>djQI0)<=?(Ja0Nr8@)99^_4 zr$KH;p`k*q=I|q%*;C8L3zvWw|H}cR9&jX3r1S*XOrrvSN(W@(Oe9zsq4etF3EGfB zEZPN+6hDmtqof@6f)U7V&LpNpIzmZVu}mslS=o#(%{Ch*DZa%00&C?yZU>5o;HG#g zJx`s^T@A^O9KLZCe?e`E$5|eQ1nEq)bX3e!3K=p5D1uL!bi@Q%LVWrt#?U>Kzsx*h zx$ewG4}S758C2X0mPh6PRM1_}pzD_oc9Q!QBT=C5q56v(1H5~Oh)Zu#=$ zawcXv2rG_4suQiYKFcw|{jLqJM8vgDpUt?>cOxuT%Gb8Uj#>Yp2AR(TJ#h`Zr`WIx zS_3`-Id2f7i(W|KZURBAyk0(|9$qGGV9$>*W0t_0b#Q$dM`inTK|%AfvVlfuOD%WT zpdIfmIj@Q7SG~(tnn+C;zj(%&N1|c4-jv^73fduF1>>gMxAe)b_104e7j8Sr8oh3# zs;XwW1rx=0H(!#ve zy)sMg=B30ik6!TwnTo)8=+%YL*P&tKoTjq^bg$92VQ3?*Ce2EdQ9DA?)pz^_@WxjuCBR%Kz%ilH0x@+t)J6tqmt(&&Ob0@>Z&FfPB+r`DQKun(a?uTKLBYxC@=r!-{paPrm- zxoRn+!Mb>jt^KGZ2s3joJ{>hKkMd8iq2+kxfcO=>d7mJTCtU0>C0xr>YEe6NpN#^d z*R=qn_U!s@;tjhxU!VXWQW8EDXeo6BX!Rh~P*q2=%ahM9Ly3Ni9XY@PY<-VY%oC|G zAdQam+QV8{QnUwUgw$#rT_Ng;@O|+!v0TRfv2ZJ)#%VwARMoj1!NUqiu=7W<0%9D2 zLRAzsbN#Wm(L6He0}V(PAfDvr^mR8n4(=O?pHfCF-}%kPvz*G#Gbo*5PPZdvmkJB7 zr?T{;d5QxIss%4<-7b@rt? z8MwC9EbuDe89_ud*A)x%Gc~CFS=6>aC}%voPPxz0Zwj~5$+;7UbxY|eHiz4F0(vv# zqvL}r#C^Bj{*(_4h*&Q)qdAUp#oo~%F~gr%eQbr{`{cR6Z(P?|W@lk5Oc;Apbg|OS ztr2@y3W1sCtHyZ!)oXv&Z$fve#ghth#CW0%68qqM;!b8!wti^EWr!3sG)4IZQ=?0d zYvf+AZ!wUUETP=wBsY!R`7D^a)v)D zBmc(s5kA;HgSn-%p|i82rICxX>0chK{U-P$EPo^V^=jjGD@<@5n1{Sbho-cld|h7& zf-qWLaOXcSCRG1OPN8d##ar-vim8d?uyrnt01FbkB7MA+(9j@!($&}PCM7Q({tQ61 zDemTf1{wyv8*WT)60w%B7pSv%B=j?t5UMb1jGzU6I1H{Jo_1#7Q8!amB5-a_x6O4YLl*V5m_3jjH$sb%2URcIZ zMiFv-RuzcYM?wu`ob}5JCB`xfuM;*wp9l_OxGL4BHtMCWd60P*c@S+lUuw})v$=7h zE^$GBM<^_`ZtPLD(2FCm*L&lN#(1QNnCwCtD*62Qy5|CqGD5qr8C10CFba6I9i-!t zdig|HSsM25+IeC>=%gcxpxV(z;f1%@^rN+iFay7*2MISkH4q#RpTl(U2&GYRVXGh2 zci*dv6r{Tl$vT-_3IR!Po-l={?Ek!RC>%%N8XzUq!a4k5E+qm27uqY2cRIY+1DB37 zbu#cHmR8f#((A`6{K->twwpG=^u+KmFVlD#{cQI%PjV*S)M@;|>guwtx!ALumMZ^b z4Qp<$njQq(9+Qn0n>};*{DMjCMFy3?QtTYlrq(pcj!%tob3c8?*iC61A5*iX>9YQl z>b~82s*Em|$Y8|^40`3m(P~;id09kKfSAQd+RmCcnUnf+$)t+O4T}7T&N`yo`tU^o zs0?843MBfLIN6cKmO_u7CG6Cy#W4}e5p05N4gabd7W?=FNh@8;yMqQw$eh#CFNJBE1n+X0+zJlz>B(mRxss$&u39JInS-rG|BWKous~-f& zzE6(v)pGL~onvIM_? zc=uS6#?N06$tsX{ltp=ZB0S4Dnikt)u+O@rwAY@xXJAy|rrK;{X2q;(@rmc@^}C?= zRd1B>%S&I|mqBS>Xrd}*93?JOj_;65`1FC1ylr1-c?M1TIZDjgJ5k3RTIy!+t-T;ZywD1c4kecP{;?$6S)pzB_uQw5c?r}VCy$r>a6_FN?}p z)2}(s4#1a`Jtyws94(}7-qAuf4XG&h`?+_A@M(c|uj1S2M0gxJqi&14>+MrwhUZSG z_9TuPT`i-!%Sy$_1$g_~8I~{6kv|k$)ZY5<2b3{=Vi}w^ zaAn%5M3f*Da@(_p+Mn&^2w<>bI@jxYFr1W;Z98YsQ?(3@Ayw95b` zdFknUflu_1i=MHaAFo^7IxY2C5yyhpJ$~V@1-w4qZ$N^|UXz`1Q#j~8d$;re^6Cyp za?D0Y&aq%RGZQ_rvdj$$XdaU0y=^U<=*llsXY6G-^tmytJAoIT6W4L{gD(cyWYWdo zeLR`TK?O{cO1e%IM(D~1e)a}*HqjWJ$}!5U8$M!(x9#mZFP1BhvuJLKc*4Rt0s89p z#bJB%P5FR26u(IlHj-uY5$=b#&9^0ZXD(|U<})Nus&ai-;}FO2RqtMLI^qy^hRO?= zV#C^iL7AA-;~8kC0l&8GKzci=lfga^4bv`tuO-=W7{N;OYP4N){`t9tK!I(Fh{4k^ z0ePEQmjQ%yHuz@(%jr6P=pvq&(nfjZFcXAn85yX=)CA9mmfUJk1L2~T?BdN0o> zr^QPcFSMI^Ltf|`tFJ(dRHvj3v2wkR-A@Qy%((DZn`?S}rIpii{yT_3WTVOn#^%nn zzW1!55<(o#V7W1sdFKEl4KX{ z-ZBe86vGy^Oh2H`n@-+D1&==9H8RqJI`xMn5Hu^*?#VUui3X0c2~m|R^t0l&!ypp5 zNJ0b)>^E>wBLW|gTT(7p_g{6W+A8#0aSNCUUvwN-qvt6@Ezr2>^@g)RSQp2prsFfv z7{>2k;|D*Ms|(Y$+wC}{@x=R9A3y2Ae%Xe+@=|nsq@G;pmbz8pDo7K+6Dx<|dTN}t z0Ae9aUGh4r-#7&1@YD;cq{zo17CMq;qEV6%cu>UZ*kk6^x{GgbJi;*h19;;bQn34y zn<=R)v=!c1TNkUf8Zm!lz+^J0JlL5Kb;vPThp_3HX@NSTZ`Wx1v}F=|K{%$#_eJ0n z(yCm@&6~I5w&cKXEKP|8NU7XRspP`0_brHlz;5r06;Y|4h@aTFfbnP)?`MikI^V&E z2wu-Dh1V1P2zSBYqx-b$UgSg3U3`9Mp9hxQMzTt;Z(axy|GJKhR!0%NtfMYLmph+? zAS_mNZi^_nz${D_r|8;}30+4TWu9TMJh~(tK5*G!+nF^N6y%SczuVru8N{5_=ksuV zf@8i5UhQVt2aer_qy|dvCfwZ1hp3}w(|`xM#Uh}_-kq1~x&EG^iO|aCG&Tj3Il3h1 z)<@{W=ZzeRbW+`LzK60 zQaSd3M_IDui|YsZ746{BVsH~ajt*n-0eLNcXPavf^u{QI+(~jfKvmhONY`12_({yI z1%>!D2xf6Z!M^4jhv za(NlKtI!^I6?8RvK9r+XYK=6{JaD|XgZLnU@}U+9SrQa>X&s!Mq`S2(XX&d~1m3>M(zNd72nEsKj?*qvAj@%^pB@6YB$7YWVM5f zY|;JKNPL40pY+&+GBA9JB99U6&-p>qdDAahG zG3H@>jVnk&+j&@DHU>n$N7hw!6cP>1aNkCR+AR%ISn=mCj{$nksf&r_21 z9P_Hpk@fZT-@6_O7}2h-5YIJPI&95&;fl_UGTG%aklRen+17@8Sh9Lpb}M?ZCkoBq z;s)h5;0?$@B|wRZ-lWE$9iMt``(hXafDdY}3s3k5B($U_D$1gy02P8j3Lc%BNZsqD zX=irN%*KwuZy@vjCRE<%2xX|2U}C;s@+8)a+gCy#0rTpHT6|6qR@dq%S{o-W+Hd2^u})NnS@x(n?mlNC?~|L{%_?@kk)d zyin4&xrI+Ve|J~eS=rBke`K7^*#BR~`MbNy(B9t3)Y;k6&fJMX)YR3~24HV%YUj*g z}(k;Z6iy0Xhqp! zRz~_xD$Z&$RzU?R$cipb)@G_sF2Qoq$?B#q#`-?)PAc?#9=>WSL=yo~L;7G46d|xb z@@BcrH>cmnYQBH!5ljBJc8}j3lK;Aj|8(b7AB`OUxO2T2J-{*(TF^P=t)OUgI)@~T zaXb|!@Xu}-DqA7jN{XFyueyp1f-E4Vxpx6vZ49{%uDhLkuZP=}e#}7!OqxlVlvdJ% z@k|91U|yv6Xg`%9P5L1wHwM-~#&Bu!HGp|{&2DksNHOY>6*!v`l)Cq%;_?&aRxAAZ zARQ-)$SH9KwcIjIXJ$#WR3*DCNE0o$%^8NeGLl-J-9l%}$wPqQ;Q7P)-r>k~+obSn zu+!8N1P<+aZ?{629%_5y)gY7pdwMuvas-SE#G4ATk@LjmP1W`cvvV*C(XnANt5R&Y zGKgGR#r$<|EoZZ{;ou}9C|Yg?#3` zV)m4$q$ZJNy2c_mAI#1{#6tI{t+^!es9{UJSee{c!xb#SV07ihV~>E(Bf+*QK~rqc zc?uzZ9w0o|H$zsnh(?9C`i5a;$XMkvOQK)O;(;i7!=d4YbNiJGkSgN8eO6|Y{o0dS zvo+e_`(kxH?V56d`P*9Whl^`Nfq{U`|I|4$`|ZzdJ-@qz6m3lG9RUm)rbZuKJ0IVy z{>nt(xLhqo|DF++$92p12%(AHlf8VJEeKmsmbC|#Lz-rTAj2@^mn9q@Vp1upRC;0m zVC0=b-`Sw_<~FTjdG9k;J({ntZ`ZQ0UqjQ=xt8EoLpH?^zQ{4*Pv*pb4x#0=XX0;fuw3H~UL)bi)HZ`u^gsFPIBy9r*b|#0@%VZ1Fm2GA^g* zc+P9^HBf`D&0A(0*>zkg;I|fq{QGXUKb%*}hbI52#qQicYZ39!T6D6pbN?&#Ezbcg z3vvJEd2693CaLL%$0locK3s4WG}XwYv>byHlqB77l^V>D6gAbz0<41Ej0^)rO-(%7 zjo&Yuo0tzgf8P{X)58nb$4v?U$*W@i+vvdW!i%CKz}(UBKedHteN-WTH(~uzc+s?V zTH!!>%S?HJ6rD{`%br8;nOy3wbdbRk9bc>#132n_$lOH`Lkqj^$hU$F+Ncw8Yg zOanj%a59~!-Z^8MJ?DDwJJeirvP^;!-*aJn6#8}%S2o)5g^+1NJVzC+-KMiBypCjj zX2o^TY*fDq4SL0nZ=-6AR`={T>MOslABb0cd)2p;e9A};u7`=BB^WJ$#t89UA}Ol` zQO|t+j*AwW6A>h^<|}3kYN|$L(hw~c6d6M*%&K;sX|RYnX80#Y1Ehyu@-$;Mj+ewO zgn5)&j#f0D>0rt+{QMu6!KF=`14X7pYm|>w3NH&6SSaa5blgfXK;RGIG@33#W8d@Q z10Z`HBMjPddnwm*hQY$aEVeOB{;FRpNRGDDLjNK)#|lVu!fm63R~n6?`EeBdCD4 zv9Cz(9E+bfbKWalq1Vx*+3Z2<@aW6$fVZ_r7M2|7SgFiEMa?G;4YHU^i79u2jjdXD zQbUJO=5mv0Sk>M(=h7xeY?F^t`Nc8h{KQHultdm76IbaFf?_2SF!Ag>LH3hlxBo--}Pze zbIpAanREH4D}YvXc*QV2It1?LwtOQ$31DI@Xee1?DZ^u8qA@TvU6D-7fgzN|+`&9# zy=kUd(h{d`Svb-K`yYlgmiW7}&kz=dvgb$1c?Qcs5IL)N^0Kq|CAXUkmh8|Td07uU}%&1gvp@N z{X|I~hDRdmBybhh9vbjntPKPJO}|f#hGbKxKVWAH!7-5z2bN@%8S^#BvmoF2a?GwX&jD_se7A z$*BU|=^QpEqU8g$eEX>)Znj0VGlPU0TpYW-@`XrQIRW)^WK5R((RSZeku=)##1c9 z+auug@ZiUBk5#?mbE{{yR5@vQTa=Ls++m#qABh@zE>U8)Eq}EXiYyK$KDRw+G$gG) z8e7u%%*vOCGYQfTBw~K?tCsassTAPbLIv^Dr^QCR3+sFuamb|7sq7r+JZNnz)|F64 z-m}ff3$%`71J8$&dmU1%c|%&LJw2d7*DsGa3a%-0N^9 z#DGs9bl#AD9NQrpGMtgK%;z-%*5C3De!8QtNJW8oUrm3J9#DT%<5J@bh?((eBWP!U zZM614pyHHoqJgKA0K25JZ#D-34nNU3FYuH+&3-+A3)&s9msjs-b}E#eQ@74-qWzjD z9Y+mnj@bLPz-06Fq7qk2M8JoPU)#V{0!6tCF2fzvp$ev!C}r9|!~vX+%agST`Y~-M z8A!$Dt2)s;p1n>c$cW(0nZuEx^M z5Qry#IrWAH*k^ZyaRg~XFx+E+EfOr4<~N<|Gb&QMb>Pc@X|#1O3vJL3UD*xG#R6V(BM0*JtUeV%$L3BhGxmBnVs^b6rDj6`&U4 z`Z09-oVM3s8DtdG<=XI+i(w|{nsxPJ1Tl2G@{O=QF!4thjuLa#du z=4HU%#lX=F*XjEC9O+GxeZTVyPalW@!G0cEpMKLSXhD}NthEA88mA>oDg2h!IFCBu zIe+0tMk3k-L@!U4Q)0NS3Rd*ILEb?)Gzl|Z|JFe41d+$)$VK}uM{OsD!cor=NNP0x zFL|M=`ZcFB%TOYWWi=HYN!p2@CJs2QRI~a^9Bo`bING((Ikqp!HkdGh3F|9Z*5|u^ zX!{!Bvrolcvzy@7N^nZNU( zjL1vCypr0oJ9~%xP228BVjv8lfq=IDH>LmJZ~tdp5cY$%eFE6p1MEJM`2T-W@HcP! zCn^2+-cz(c3lg@sXK*z90{BZ}|69^Om|%qRsNLMhs08UH;sC6Ay8{xDB6`wQ>|rhq zjSW%>&0xxE@)NShJ_StYfWN{vX7Vn?7h$Nm*uCM8IV=6g%Tv$W&GG)@sI2u)bbN4M z$9H}9&YrZUIh0%zlYuTa8)yMFAeJ6i3exCAUMtSY0!3f_T z3yMX!Sm}Y|R}N&l&b#3o+3W;2dAWRwrSSfTEOpu?*ce7G?w9~|35lOO)ynjg-9~9M z^8rWr7#!fH$Od&3T!KfVs+7`$QKMIf&>KZwSqXc+1BU10`3`LDXxMFul5vnHK!@4! zE8#_qF*<{jS6@zLGR{*q*1hB&=V|=Iz|E61-lzFsue~PZ-ZfGKw>0%&Qoj%sz?#8^ zA~$oar5fI0!rr94L@qd2lOmGIv-UwaO2W@hHjUZPH3JI>5fGhoCurY~m}R6MC5@Rb zBX4GqCA!xw-I6is&{wXEjS{MKDBb!Xk^bz*jHgUk9()aOxeuwCn#)lxnD0rlyYszJ z_-!zOJG^Q^z(7Fr|C?3+mBFC=8E08T4}i;G_bmTo!u0>Q8E6fhyUi*pq; ztUL|kks6H_QeGKcxKH==&hYhpaq8I%x3qI~7Q-lEGXF)b5-Y_@!TtQOwevmi*nfR; z5w&&BYJz%ps9yYGw|kKgOK<-`R8XH5CIj)M-fw!MI?Jb&h28vVPH4$@B1Cak{62&1 z$XF!R{hXK$Nw;B#ZFE3154L;1&z@YODJ-d%e3wPdQ5P$ZtVXcg_IV`F+UP3>@^qyx zlz5&s1#I|4yyBN{YqTvlBcGjxi-g7n@_0Y7-K-9=cF#&w&0AG-;Na&u$z`T=Xw3fj zT<*FOhlu~t9}L%GU}E*{lr{C%kX!i5>syv+(f24nxJ75aNY_MED~>akScY5oG~-c- ztxyH?0r8%wrJOs8P+Aew2fhS~C^y&=w&wt?Y9E?kf%&B_cy5G?L^JyOeMe9-Ea!>~ zeTQUQv95M35I%el@+(y%S7YY+Sy~NwDwnbt+z3t-6}w~SCU#CazoIx%9-QQw_*7=P z#HsU=Q{>&gvdk>Oo@Ruj>wzWuhj5g-_0+kpL|irJr9fS&9U;u(WP%R{qkQ5~-itNI zre)nx%@vsl&L`q_)MO*&R<_i(fLGG2ke!mP9fp#?Dp=PU|3-pF=<9l(hp!wv%1R=G zwD*WQ>E!jYao_nSuw4CV!t!Fe*)!HZ|1d2p#S$TuC7z005ebe>L>%+P9^Xohem-wx z`5N}BA40JPX?u#{%weSJCy<-YXEs$>HjQ)<9s_ARBHTv}aUEO4w}wz@kbe3SKmCYh z>v}v-hJyv0#p6UMGdL$+_%6hrH%(W&%0TT745_Vlg2P$hPy>k8@3pJE(}xJ{U$jQ< zYG(S65BiD|3xhD`6un_m0XpSBLtI>GyI;}RIG5(a70LT^X{^P5T8aA44S*oPWA*E- zoiT(_Bd7*ifeePwc41UuuCzj91qA+356s6XEP}S|e|>X(?8Jp%*&<`KcA1&)(<46N zl*2If*`(*FyI*#o0!;MywafI4ZcU0hLZcVXiig;-`;vUwQeQF;NX>E!%LL_jB%)^Eq36{ad=6B=O7kv*+^~ zu};7ix0DUL3qgx@lwHuAI>E^uC8&1B=f|LBN{5;>*`(faH#Al%g1k+n{X8$Xr|Wix#jVvQUtK!y~bG#?>S2` z9G&0xPQ$u2Jyx&h8&rGtlaKap-hF9r(tti|N1xc@0GeoiUv#cR{OW3&wBB_|B4WyS zzIB!wp-px{9q5=xp8`*4bQi}=7yZb}c~n+ydKzPqQ?F1q$+5BNEJz<-GYcv~DY|=H zxQZqYHon0Js>S4=EZVdgU%PKJZqB>kLHCc6U%Coklph+Gt*c; zGS=VR(I>M!7BrY<9yGKF!<1tGu-SBfH^1=EWA~ttX30+y*ZW%zSGm*#<2WeJ*R=+q z?AS3F5DcEee5xhxPvPB0DyT9nR5go~wN0YK40uKespCyH^itrza(<2=WfO~n z+c`-2C>edB0mtAu*i8Zn;KVXQN-bbwV8i$OsQ7%ujiEjAji!%KVeqH8f%b3pq2JZm ze<2!*A5Vn%Yr2v0Q6~ERV1oZYY!LjFxK98VJ7-hJ|E%(VB_1x3lhS=mNCW4A9lcDH zrq!i~!)uaD{@cQfpDr`yAm9sAV&yQy9!vM-@|)#o!HS#=?9;lu;s1PM5YiVFU5 zV9*um;_IiV)w}^~<7%!^-F%&?`;~2-J>JTTa^GN^VxTr4l-|?th^k?oZRQ>-Y6kbn zzw1bO6~|hNH2x%>{g>EgyGmv%p^q$~i3JsT#6`Rs%{&;u1EN|RfWU&3Y&f>$PPFO8 z-gHn1iVNRqEH5uH`VH4AQj1P~XV5dV{}%o?WOe#6pC3LTs|Eg7yZ(1d_xrf#|3D_@ z=m_{PH5{QbZZ|80)FCm#3)eg)y;-~`u1l$g^9@n1gkswtX}OH@PWo7{mRH`2CNmr~ zs*jebL1j=3y@&GrtZ`%VWb8bv(=1RDS$rqZX+9z-K|VK+RMJ5}`BU@JC^*jjFj7r1|j}!P!b27dIgS!FSM2M!MX)A}x2v`TS{Q z$jk9(d#}|1#bvNOtwH3-vep=a3k9DUnUAh#{pEK%S=o1 zb#4b_p=Om2mBc($<-|%IQ@Q@DJm0{i?7>kK&CK-}B(*?|tYtt(E;Q*!z+A{-7e$Th zYp=*Uw_v?jyiK!~lqvVwyA!n{7UxsTh9F>UY4z>#%1w;BATwUoi7BFcE@~f?QFXjW z6c6q#+DPgL&5>Ov_P-*cgauVqprnFHM@>M|A3>@wsKZo1*^RQ9)^JK#%iXP&OVdJw&7l(e!8(5h%kF4gemkCE}tT`p;( z57*QA;ivzZo|M@9xn}uo_0_?_^It|I8kvQoBjIru+`4>H zhc2i5E2ul|)Po_7?35BkW%1aG#$_}eJhH8vQD21hX|`6msB1FE&#t zoId52K&Yk!(PPHbLo;SlT~2GFv=DWE#>s3?oncLN)S}*cMDWm;8oyaMZ~3fq%soer zUUsxg`2*%jGtT@#3sI2pU~Ct5n-RvfZ1GF#1!Thvd#GASIdhfsk?|thHf6SV-SigPFKIiOjpB=wXPxBoW{lb>1iVEBPe!6%@ zIlaI;t+E-k8t!+RaRgdD3_dLMNe@=QR1Qv~z<$!sn;Xqr`oyc{qF=LO{&ZY|kdHE5 z2vy!cnG5F3hw|WO<~_f@TfMU|dL#l0UnaI^dXy)Z(2V4dn&o6H7xsd?j8 z%V<2W{L;&k^;lcH`h%9Ajc_JG%^S+*d(xQ0QqHVzDnT=N4Ml}wE;Yg-)JmMOIV2Ut zj`8*C^yo#}PmaeI%aGxp`eWm^qR07~<=#h3pkWc?H6wqH722uBT`|Eq80K`U9p;p8 z9FOAuOwvy|=AO@hu)gtqiaNtb-HFBHI=mnptvwMM!$eDp7Me(-x#-q?1*tRjRJJC< zY_Bt;x{>*|5x7YtI@73Ncf-)HaX<$_jJRk$_~{;*N7V>;fq8?U_!#KN7+w~yr(@P( zj>0eLry8wfa}uZT70by`_ML^uqc^<~eS5MpAz#roXZoco=d!HPXSbubkdt}h0h(ok?NfjuA-Rr~q$TNpasc49n*eKXD)mwHgi z>nzpQ0F87u8k!(2rp=#Bf_3JUpw&+@c}1MKFIgE_5O>iG#NPPn7r262C*o$kbudeA zg>%X=jFB1ZYN`-q4cMcc=WL)s|+e><&B5Vi^-`ubjXU8W?* zU(c`{FC}7xdJ7F1xC+RbY}>3ISBPGK44@bQ? zUT4|ZFgL6>d55O4z>GYg%CAu_dnEbf2)i--fhJKnvp|LocFM+g%221r7abyBlY!Iu zM9MX+fgyFDdg2(HK8+n$A1+(_eqIpB7Oky!vTo3d7X~&KI$4wV4~>~ktYBMQMDHaJ zmp9m?Y(69pJG{_;|EIg#1afow0yd)#m)zZ}p4_#r*I$ebH(OJPTL+2xK)l{cEp3}= zUP4c>HEl*gS7m!v^Lp(t4M@H+RsEXuOT`|$E{hqkiZ7c1kQrSIxLAYxIWjkt<2os( z{GQ>H%*?{+%xztf-89*9<0XaJyVaK3%WE>U#_eXT1ixm9A3Xp zhs>?TiKSXu_s4fNhyC;ouxR`m;Ul=DPY^Iyl%sg7>6nJP;1HJDJMRc;lXIjeM(0}gD+50p%i!9@GsL#* zJ^_ZhG%S>uG2WT0im?k*DLo7_WUSK*o-R=mwD3*Bm)r~$)7^?iF2Hr)|B=?ZfH6yV zRAYNz&JM-Xu_5`HR+(c*{R6!%c63v5=?N_75JIG7$8jnCEI*0dN9lqVUhOQjx)HD4 zn78mc4Z68~`$zpG#8*v8P~Jz>xSryo2iL5>I+3uAWt8Lt))ROH;N!Q2 z@BwM|O?2^Pg_<*JkVWDR82D0Y;ucJQOGe@K&}<1CRd>W4QovPmpz#0Uz(j4UJ1BEZ z?6w+jD;*rP5=c2!s;nsaoM8yXvNEm=n~NHMSOLF>db(b1A$Cx)pJX^a3s;7Qneb&@myG1k zF4DU?m0PIEav!H3l<2vxvhLQ$xi!E1icD>w>IE=6qxD7iCO3#)io`%V#fnFdx5yF4 zgdJ0b(Ff*Kn#z~n#**5k787CrMEonaED7MscxhWta0Y1*D=n? zqIE&AzfU-SKRT)yFd}B!om@4Ae|<7#E&$H=gdDPN+z1dLGABZ2E{Irk<3IA zM^A0#LfL4ldmIEiun~pTGH4HYBz`!Y%Lk~mlDeO#DQ>L_rVQey8d8rm6}h>ssINO5@fXB7#jX+u#SL3Vxo1}&F_Fy6X= z1k;$f&f6aCVDHrzv~kaLO6>XZ#(w!#?nH*d5>$fM=wX_eF zxJ}2|L}O90j+^&x`^B=3u&p~P4&%W-UZ=>becYz}mfcK}miJ)}he3YG`AP2p-z%V} zYvmsQ1xM>VPc5(bd8HIRT&D;FF7Bv}ia(q8yct@M4JmHXtI3`Z@IRNQfR^c@dr}|7 zec({6%aUMMk&~FZ2zirNh1)(4A`=AGSJIV^yAI<_0!Q+9b@lFV;bWE2AuG`Z#gt+{ z%Xj_Q)b6~Ds`DZA-B+-ajVvs;h4RDJfo)jmV~LOs$d`IZT31Aso|ivxXa#>-xdz}Y zM8OjkmJ=()%(gCr1N+sthG!v+QnBiZ1m3p^T6!KgHO<4*%8iYh9fsw(Vgl?e)K(=R zF?&3Xspb?RZ$AInyqqyHOKJi0Sg_Fn>L80l#R_9NaIRPU8+Y6tYqTfBULV#sA1+7# z41d-SyT(|=lX>!}uRFY{`5c{>=Zq#m*KWbf=&BKzn%?f^cp`=)=lSu?Sw-uY9 zL)&bD;sx!S7$k1E8fvM^oi6=wMMA;5uWBFS;LnGI(Nuf(v1Vvk7uSyu^#`Pg0+E8V z=p;i8BO08_Y={m#&82W?(h(6g7#AL}dum$jlLr(YX=nKjVKMR5uxRbv$1U5O-sJ<8 zpm%*hY?g22)5|E(wCcC8qJ1)rh{C>15T>s-Qu_u8i=uaZD8J#zoE?7gmPw4;hYHrb zlvWMQAte}3`2wJ2S&>qM`B$?_KzWVQL8Li(3+kJ=K{D>i^u)Pm z0ilK~w>YT6-%0Si)EFNb2nJO8=qL8=)^V+_tK*@}_|SZ;5~^aDv9 zgU7Ic{P(1zM>|hu_>T!c3Z1vNh*b-Xde#C)Q7zQ&b47oAFD?_vgS9kIKQMrQ~7UR+Xmy zXyGu%FZ$_6cj%XOoAp_;jn%;nFOpRkkp27p7ICI~NC37_v`-BQ{5|(@ns07l{vnk=RNcYBf)?G*I(l^QkT>Pdek0 z{(CY^ugU3)rzYAp@XtlDc>pJvPqmMqt`1B!o_w0Jk{NDLiY`rQ<8X}w!APL^`L--I zUwGzT6iS&84y$`_`8z#|>3#Oi`k`jq#GiekN7Abq_rWQ>YI5kSZf49# zoH?%idDZg8fZoqf)`X;Y>K6nM;&7|lK^Z${PapO1Y_j+3;NTS~2+I0XhH>gN)0-}l+Lw*5 zZ_|($>pU&D8Gh9l3UIbka%d2XD%ZSa-&Y1GUrVR?t~)|6uhYO)VMYNkJDa*#*) zC+^OC6*^LXyRML`^0~%AF%#!(j8el`!@9j56hH9o*@g&B`yL{n*-YE_mz=JlSz!kj zC=F3?Y*i_zd*w7oba-Tec2yvn5(}wU*6>J~jjiV`ZW&Lj%$qaOsqrY2wf7E%{IsgH z9u=j93<50#!=&iRbR2}HP@L5^fBw;|$k*iQ8=k~m(EzX-@3p%%2;0&|%V1v>50h#0#i08H39ZkijgHg%sRz8%1dxPxhDX%Cnk z$9X_H(yxwg`PN%*ou2fcnwXvq>U-%IAL9WI#=ES10xGqaY>t7S2mNA+JsIBwG<+=( z{USy&XlrCxrMu=`Q}?d^**3V>DS_FlNCDnNK77Lfj&RDLol9Utt!Keiz=7Q69&zZM z!S~)-dqhp|ZR3J4Xp)O7M2)FIss(+wtvP%VrxI+|mR>9LzG_R_Kr_%NE5A zW>Q~`dgD>&&)L>vf=m5Q*3X$x1uge*U|tfx)puQ$^k>ST5>lW1f<^CqQ9H7=zdGzU zE@J7tiKc>3bq;#5r25iQ1U>I25sC~FOJhdqFn_V<<#dG$W19>(hKO$_d~9PIXP%y| zM!ZK+lS5_W(6fTiw#9s{BheN-Elr?8w10}Nj4qH!HFG=>LlWU{&0x-JM;5mgeHY#% z`-mi)ITNoNGaS|5Z9k3c(32&m8@FfivGChv^Pc|0yFNIn)D_0m0*7T*NTm98J7EhF8z@{FsB&o~W-J`;gC=w)Tlu)nyFVn0G;Mt}n>`Tj z7c5UoNaP~q<=j#4E_#$E)+W>^9-Q3Pevvz&a3Nl2pgEK$b{DyLYts<<#WS3y+Ve?7 zCu0{?`A%S@y>?$kob@Ty^9|6wnM{B7H|Er~8AYVomNAZ*sy{}h62BN2mD)6$CFyOe zFEPfl4zGDFTL6lEK@!JY86^{PIaw z;?IZ*6!Ah8uLN7)vWuug@s$qQ{DGGFvS*cQNjYLNTLWejA?I$YvWYH7kGb3i->pS) zwC@A5&&qguMZ;$vx1UUs+Li3q+)~oTGRAT$75|y=kao4|B7)_6{pfttJ%s~V@y5J1 z&$u1n*X@t8$0R`r-(D)59;R+#Bl+szW=y^Kqw#{`md|PfxMq45Y}WsYUS5sF6J3s8 zDuYw*#tt`|bLcb&IJ49wujpwmZ4u>TBmyw9v$L>PR>&$+mNpi#@Jx^tR`PtM^sG3W zLQb+f{EaLu%8Qv@**)c3Nqc)T%FHq^VuOWEs_EkBT5ow10OYZHx%Q~!E%X-k41{bc_JN`x! z&^fl)ND;JFb{vT#ALng^@*QwTj#(NF=4vfZ210^dyc~iXHxWfQbG%x?q{Qs`g~@rJ zmrzOpacCXem=Ma-$&6<8_CeFH{I>u)HgVa8XpXpDK2M|+LL*-vEDlL~W0qmQUJFjA zv*z?_br?j->$tBXnJStSB|CEb1dj|;worRCD|HwRt|51lkQaes*Mrgd zp@FoIDQn;IM1JsQJ98xx@+tN{fFx<|N^DdXf>qM-w@Sc2{#OAHGFT=5Z<=pN@y=f$ zVAAAY?LF5swf|BnhpDt! zdVVv>z7|coKyGBq+`hc*n)by=XCWgFuPpeY)R#L;?jvVqNrXhiZkJFhbb2$7&HY3bGFS^tEeA{t(Hni*y0EnDIfsL& zIgGrSv>B|}_)BWOdIBMJQsDA-TVs1i^BWSEkW=^H5|>awFAQH=8=3vB@|${*AlSkb zFaJo2T17m10w$O3aHeY%d(M$A&ibqy^bP~#Wr1QX%~uK_^R(#+n{n>(<|L3trsLVW zl)1B%xwRkp4DFa}6iIk5sb4J%zf>bf@!;!jCr)dhR8z;)J3{CKIjfpPRQLO}qu@RY zLzIaer6a)qt|JvKxe_xcyF|5OMMELH7fQnF_8fZ#%M(#NpY$ymGVhU`zH8{Q1kQ!4 z2DN4e0qMTY@Ib#ZN66Dp6u@skJ7#yEupMUrSIJ#I=b4RlN+b{OZ`VfkADYkdY=zA< zR9MznEb<@=w&Ah_)U$bNG_LH}6PGxi{$UZ?@Zr+Fg4f0Ok9C2Cy9IyKI}3=x|MlnR zLIm=C^BW8hVPGL|Fi@2J6At2&L0*9X&8$xf^AB7V;4w7ZFG}Fwyvg8-0LW;r+Atg_ z0T&`|;Mx5>-kZ)b7!`8bpoa}Xg=V=YheE9a;DX)GtEj)H?Y}_;G{yX75u4YD_&s_4 z4I-e4-RUmPVhsFTygG~j#9V)kBnVMFG`Td{B}pydp!eUB{tIn9SOSDL{@02?*7+)N zF)oyZ3y~ZYXbCq_R)b+7RK>qwA^Q_TRt?RR%mxKp1vtD8_SZiBZ`9RbnGot~Xqk|y zK=YQeUXuA5Ou+Jwvi_RN|A~?L$~6Db-W75q6ey7wBCgmlH`Akn(IDpW(geV0&?HLu zP-sXb|{ zm%3m7wTJf_36PFj=pIaHlU4;-x+3A)XBrq4(q9X`XpsI*=r%&=15^d*X8}8g|Juz7 zhJ|!CO5TX0(2rsGEfbAOC>pK#`*pQAyXlzJl9&~#g_9g7>dzY}U?b8E;Li+Te zK_Q)W(8b%A_k#dC81&{oJ1`!k&+Zo<n4+gq0;(s*~f&s5Sdmz3M zbg}R)D8MR!0GPB4s?y@-LU1rL^trumsCZDw7b3KRmymBNJ-%VY(6xLtP`Fh9hih^F zwFLRf_#ws`qH5@JILb?Mc);EXlxF@K|L^MruS^D_q^tQ@=yP%*qAhw!$PIONU|2{( z_SZJ~6`X>u3!}I+_WbK$e_wBRjea0I2fAM8vT4E+zh>yat2etw0;KK-I*onVotu)p zM#68ZfWQJERY1^cf@mK)4trTmcjT@S@cZ2OHA?}B=t4&jFHfRF?HU=siSL31K;pa5 zlYp!PbmZakBy2RU5%Bx4 wZ|nc@q`2|QdW~8kfYA4@%lpFUGT?u?y8bN#5eYI#dhlN}CJfAb1IVxc0Z-9mrT_o{ literal 0 HcmV?d00001 diff --git a/Services/IStorageService.cs b/Services/IStorageService.cs index 5655bbb..40f9108 100644 --- a/Services/IStorageService.cs +++ b/Services/IStorageService.cs @@ -9,5 +9,6 @@ public interface IStorageService Task CreateBucketAsync(string bucketName, CancellationToken ct = default); Task> ListObjectsAsync(string bucket, CancellationToken ct = default); Task UploadObjectAsync(string bucket, string key, Stream content, string contentType, CancellationToken ct = default); + Task DeleteObjectAsync(string bucket, string key); Task<(Stream Stream, string ContentType, long? ContentLength)> GetObjectAsync(string bucket, string key, CancellationToken ct = default); } diff --git a/Services/S3StorageService.cs b/Services/S3StorageService.cs index 7a58261..055f10b 100644 --- a/Services/S3StorageService.cs +++ b/Services/S3StorageService.cs @@ -74,4 +74,15 @@ public sealed class S3StorageService(IAmazonS3 s3) : IStorageService return (resp.ResponseStream, contentType, len); } + + public async Task DeleteObjectAsync(string bucket, string key) + { + // S3-Delete ist idempotent: 204 auch wenn das Objekt nicht existiert. + await _s3.DeleteObjectAsync(new Amazon.S3.Model.DeleteObjectRequest + { + BucketName = bucket, + Key = key + }); + } + } diff --git a/Services/Storage/IStorageMetadataRepository.cs b/Services/Storage/IStorageMetadataRepository.cs index 7d590b5..065ad92 100644 --- a/Services/Storage/IStorageMetadataRepository.cs +++ b/Services/Storage/IStorageMetadataRepository.cs @@ -6,6 +6,7 @@ public interface IStorageMetadataRepository Task EnsureSchemaAsync(CancellationToken ct = default); Task UpsertAsync(string bucket, string fileName, string? path, string key, long? size, string? contentType, CancellationToken ct = default); Task TryGetAsync(string bucket, string fileName, CancellationToken ct = default); + Task DeleteByKeyAsync(string bucket, string key, CancellationToken ct = default); } public sealed record StorageObject( diff --git a/Services/Storage/StorageMetaDataRepository.cs b/Services/Storage/StorageMetaDataRepository.cs index 3027396..5f3ddb2 100644 --- a/Services/Storage/StorageMetaDataRepository.cs +++ b/Services/Storage/StorageMetaDataRepository.cs @@ -90,4 +90,19 @@ public sealed class StorageMetadataRepository : IStorageMetadataRepository } return null; } + + public async Task DeleteByKeyAsync(string bucket, string key, CancellationToken ct = default) + { + const string sql = """ + DELETE FROM storage_objects + WHERE bucket = @bucket AND s3_key = @key; + """; + await using var conn = new MySqlConnector.MySqlConnection(_connStr); + await conn.OpenAsync(ct); + await using var cmd = new MySqlConnector.MySqlCommand(sql, conn); + cmd.Parameters.AddWithValue("@bucket", bucket); + cmd.Parameters.AddWithValue("@key", key); + await cmd.ExecuteNonQueryAsync(ct); + } + }