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 0000000..deeedd2
Binary files /dev/null and b/Services.txt differ
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);
+ }
+
}