Update
All checks were successful
Build & Deploy PLDpro.Web Test to 192.168.1.100 / build-and-deploy (push) Successful in 1m13s
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:
76
Program.cs
76
Program.cs
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user