feat: implement multi-tenancy support across knowledge services and normalize TenantId to string type.

This commit is contained in:
2026-05-03 17:52:12 +02:00
parent eac0e9057e
commit e21c24b66d
16 changed files with 334 additions and 94 deletions
+35 -6
View File
@@ -59,6 +59,11 @@ builder.Services.AddCascadingAuthenticationState();
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(
NexusReader.Application.DependencyInjection.Assembly,
NexusReader.Infrastructure.DependencyInjection.Assembly
));
// Authorization Policies
builder.Services.AddScoped<IAuthorizationHandler, TokenLimitHandler>();
builder.Services.AddAuthorizationBuilder()
@@ -114,6 +119,16 @@ builder.Services.Configure<IdentityOptions>(options =>
var app = builder.Build();
// Startup Validation
using (var scope = app.Services.CreateScope())
{
var marker = scope.ServiceProvider.GetService<IInfrastructureMarker>();
if (marker == null)
{
throw new InvalidOperationException("CRITICAL: Infrastructure layer was not registered. Ensure AddInfrastructure() is called in Program.cs.");
}
}
// Ensure Database is initialized and seeded
using (var scope = app.Services.CreateScope())
{
@@ -198,27 +213,40 @@ app.MapGet("/api/epub/{index}", async (int index, IEpubService epubService) =>
var knowledgeApi = app.MapGroup("/api/knowledge").RequireAuthorization("HasAvailableTokens");
knowledgeApi.MapPost("/", async (KnowledgeRequest request, IKnowledgeService knowledgeService) =>
knowledgeApi.MapPost("/", async (KnowledgeRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
var result = await knowledgeService.GetKnowledgeAsync(request.Text);
var tenantId = user.FindFirstValue("TenantId") ?? "global";
var result = await knowledgeService.GetKnowledgeAsync(request.Text, tenantId);
if (result.IsSuccess) return Results.Ok(result.Value);
return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});
knowledgeApi.MapPost("/graph", async (KnowledgeRequest request, IKnowledgeService knowledgeService) =>
knowledgeApi.MapPost("/graph", async (KnowledgeRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
var result = await knowledgeService.GetGraphDataAsync(request.Text);
var tenantId = user.FindFirstValue("TenantId") ?? "global";
var result = await knowledgeService.GetGraphDataAsync(request.Text, tenantId);
if (result.IsSuccess) return Results.Ok(result.Value);
return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});
knowledgeApi.MapPost("/summary", async (KnowledgeRequest request, IKnowledgeService knowledgeService) =>
knowledgeApi.MapPost("/summary", async (KnowledgeRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
var result = await knowledgeService.GetSummaryAndQuizAsync(request.Text);
var tenantId = user.FindFirstValue("TenantId") ?? "global";
var result = await knowledgeService.GetSummaryAndQuizAsync(request.Text, tenantId);
if (result.IsSuccess) return Results.Ok(result.Value);
return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});
knowledgeApi.MapPost("/verify-groundedness", async (GroundednessRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
{
var tenantId = user.FindFirstValue("TenantId") ?? "global";
var result = await knowledgeService.VerifyGroundednessAsync(request.Answer, request.Context, tenantId);
if (result.IsSuccess) return Results.Ok(result.Value);
return Results.BadRequest(result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error");
});
knowledgeApi.MapDelete("/", async (IKnowledgeService knowledgeService) =>
{
var result = await knowledgeService.ClearCacheAsync();
@@ -377,3 +405,4 @@ app.MapRazorComponents<App>()
app.Run();
public record KnowledgeRequest(string Text);
public record GroundednessRequest(string Answer, string Context);