Update
All checks were successful
Build & Deploy PLDpro.Web Test to 192.168.1.100 / build-and-deploy (push) Successful in 1m13s

This commit is contained in:
2026-02-09 18:38:12 +01:00
parent 6008c43fec
commit d333409c19
9 changed files with 242 additions and 10 deletions

View File

@@ -1,17 +1,20 @@
using Amazon.Runtime;
using Amazon.S3;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.Extensions.Options;
using MudBlazor.Services;
using Pldpro.Web.Components;
using Pldpro.Web.Components.Pages;
using Pldpro.Web.Models;
using Pldpro.Web.Services;
using System.IO;
using System.Net.Http;
using System.Net.Mime;
using System.Net.NetworkInformation;
using System.Runtime.Intrinsics.Arm;
using static MudBlazor.CategoryTypes;
using static MudBlazor.Colors;
using System.Net.Http;
var builder = WebApplication.CreateBuilder(args);
@@ -27,6 +30,10 @@ builder.Services.AddRazorComponents()
// HttpClient-Fabrik für serverseitige Komponenten
builder.Services.AddHttpClient();
// MySQL Repository
builder.Services.AddSingleton<IStorageMetadataRepository, StorageMetadataRepository>();
// --- S3 / RustFS Settings binding ---
builder.Services.Configure<S3Settings>(builder.Configuration.GetSection("S3"));
@@ -55,6 +62,15 @@ builder.Services.AddScoped<IStorageService, S3StorageService>();
var app = builder.Build();
// Schema sicherstellen (einmalig beim Start)
using (var scope = app.Services.CreateScope())
{
var repo = scope.ServiceProvider.GetRequiredService<IStorageMetadataRepository>();
await repo.EnsureSchemaAsync();
}
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
@@ -99,17 +115,65 @@ storage.MapGet("/buckets/{bucket}/objects", async (IStorageService svc, string b
});
// Datei in Bucket hochladen (Form-Data: file)
storage.MapPost("/buckets/{bucket}/upload", async (HttpRequest req, IStorageService svc, string bucket) =>
storage.MapPost("/buckets/{bucket}/upload", async (HttpRequest req, IStorageService svc, IStorageMetadataRepository meta, string bucket, CancellationToken ct) =>
{
if (!req.HasFormContentType) return Results.BadRequest("Multipart/form-data expected");
var form = await req.ReadFormAsync();
var file = form.Files["file"];
if (file is null) return Results.BadRequest("'file' missing");
var path = form["path"].ToString(); // optional, z.B. "docs/2026"
path = string.IsNullOrWhiteSpace(path) ? null : path!.Trim().Trim('/');
//await using var stream = file.OpenReadStream(); // Streamlimit über FormOptions konfiguriert
// await svc.UploadObjectAsync(bucket, file.FileName, stream, file.ContentType ?? "application/octet-stream");
// Key bauen: optionaler Pfad + Dateiname
var key = string.IsNullOrWhiteSpace(path) ? file.FileName : $"{path}/{file.FileName}";
await using var stream = file.OpenReadStream();
var contentType = file.ContentType ?? "application/octet-stream";
await svc.UploadObjectAsync(bucket, key, stream, contentType, ct);
await using var stream = file.OpenReadStream(); // Streamlimit über FormOptions konfiguriert
await svc.UploadObjectAsync(bucket, file.FileName, stream, file.ContentType ?? "application/octet-stream");
return Results.Ok();
}).DisableAntiforgery(); // für Blazor XHR Upload
// Metadaten persistieren
await meta.UpsertAsync(bucket, file.FileName, path, key, file.Length, contentType, ct);
return Results.Ok(new { bucket, file = file.FileName, path, key });
}).DisableAntiforgery(); // für Blazor XHR Upload
// Objekt herunterladen
storage.MapGet("/buckets/{bucket}/download/{*key}", async (IStorageService svc,string bucket,string key,CancellationToken ct) =>
{
// key ist als Catch-all {*key} definiert, damit auch Keys mit "/" (Prefix/Ordner) funktionieren.
var(stream, contentType, length) = await svc.GetObjectAsync(bucket, key, ct);
// Dateiname aus Key ableiten:
var fileName = key.Split('/', StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? key;
return Results.File(
fileStream: stream,
contentType: contentType,
fileDownloadName: fileName,
enableRangeProcessing: true, // erlaubt Resume/Teildownloads
lastModified: null, // optional: Last-Modified selbst setzen
entityTag: null); // optional: ETag setzen
});
storage.MapGet("/buckets/{bucket}/files/{fileName}/download", async (
IStorageService svc,
IStorageMetadataRepository meta,
string bucket,
string fileName,
CancellationToken ct) =>
{
var entry = await meta.TryGetAsync(bucket, fileName, ct);
if (entry is null) return Results.NotFound($"No metadata for {bucket}/{fileName}");
var (stream, contentType, _) = await svc.GetObjectAsync(bucket, entry.Key, ct);
return Results.File(stream, contentType, fileName, enableRangeProcessing: true);
});