Löschen implementiert
All checks were successful
Build & Deploy PLDpro.Web Test to 192.168.1.100 / build-and-deploy (push) Successful in 1m16s
All checks were successful
Build & Deploy PLDpro.Web Test to 192.168.1.100 / build-and-deploy (push) Successful in 1m16s
This commit is contained in:
@@ -57,6 +57,7 @@
|
|||||||
<MudTh>Geändert</MudTh>
|
<MudTh>Geändert</MudTh>
|
||||||
<MudTh></MudTh>
|
<MudTh></MudTh>
|
||||||
<MudTh></MudTh>
|
<MudTh></MudTh>
|
||||||
|
<MudTh></MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd DataLabel="Key">@context.Key</MudTd>
|
<MudTd DataLabel="Key">@context.Key</MudTd>
|
||||||
@@ -73,10 +74,17 @@
|
|||||||
}
|
}
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Href="@($"/api/storage/buckets/{selectedBucket}/files/{encodedName}/download")" Target="_blank" StartIcon="@Icons.Material.Filled.Download">Download by Name</MudButton>
|
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Href="@($"/api/storage/buckets/{selectedBucket}/files/{encodedName}/download")" Target="_blank" StartIcon="@Icons.Material.Filled.Download">Download by Name</MudButton>
|
||||||
</MudTd>
|
</MudTd>
|
||||||
|
|
||||||
|
<MudTd>
|
||||||
|
<MudButton Variant="Variant.Text" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete"
|
||||||
|
OnClick="@(async () => await ConfirmAndDelete(context.Key))">
|
||||||
|
Löschen
|
||||||
|
</MudButton>
|
||||||
|
</MudTd>
|
||||||
</RowTemplate>
|
</RowTemplate>
|
||||||
</MudTable>
|
</MudTable>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private record BucketVm(string Name, DateTime? CreationDate);
|
private record BucketVm(string Name, DateTime? CreationDate);
|
||||||
@@ -148,5 +156,42 @@
|
|||||||
return $"/api/storage/buckets/{selectedBucket}/download/{encodedKey}";
|
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<List<ObjectVm>>($"/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<bool> JSConfirm(string message)
|
||||||
|
=> await JS.InvokeAsync<bool>("confirm", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
Program.cs
14
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();
|
app.Run();
|
||||||
|
|||||||
BIN
Services.txt
Normal file
BIN
Services.txt
Normal file
Binary file not shown.
@@ -9,5 +9,6 @@ public interface IStorageService
|
|||||||
Task CreateBucketAsync(string bucketName, CancellationToken ct = default);
|
Task CreateBucketAsync(string bucketName, CancellationToken ct = default);
|
||||||
Task<IEnumerable<ObjectItem>> ListObjectsAsync(string bucket, CancellationToken ct = default);
|
Task<IEnumerable<ObjectItem>> ListObjectsAsync(string bucket, CancellationToken ct = default);
|
||||||
Task UploadObjectAsync(string bucket, string key, Stream content, string contentType, 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);
|
Task<(Stream Stream, string ContentType, long? ContentLength)> GetObjectAsync(string bucket, string key, CancellationToken ct = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,4 +74,15 @@ public sealed class S3StorageService(IAmazonS3 s3) : IStorageService
|
|||||||
return (resp.ResponseStream, contentType, len);
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ public interface IStorageMetadataRepository
|
|||||||
Task EnsureSchemaAsync(CancellationToken ct = default);
|
Task EnsureSchemaAsync(CancellationToken ct = default);
|
||||||
Task UpsertAsync(string bucket, string fileName, string? path, string key, long? size, string? contentType, CancellationToken ct = default);
|
Task UpsertAsync(string bucket, string fileName, string? path, string key, long? size, string? contentType, CancellationToken ct = default);
|
||||||
Task<StorageObject?> TryGetAsync(string bucket, string fileName, CancellationToken ct = default);
|
Task<StorageObject?> TryGetAsync(string bucket, string fileName, CancellationToken ct = default);
|
||||||
|
Task DeleteByKeyAsync(string bucket, string key, CancellationToken ct = default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record StorageObject(
|
public sealed record StorageObject(
|
||||||
|
|||||||
@@ -90,4 +90,19 @@ public sealed class StorageMetadataRepository : IStorageMetadataRepository
|
|||||||
}
|
}
|
||||||
return null;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user