Refactor: Web Consolidation and Identity Stabilization #40
@@ -1,21 +1,16 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Pgvector.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.AI;
|
||||
using GeminiDotnet;
|
||||
using GeminiDotnet.Extensions.AI;
|
||||
using NexusReader.Data.Persistence;
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NexusReader.Application.Abstractions.Services;
|
||||
using NexusReader.Data.Persistence;
|
||||
using NexusReader.Infrastructure.Services;
|
||||
using NexusReader.Infrastructure.Configuration;
|
||||
using Polly;
|
||||
using Polly.Retry;
|
||||
using NexusReader.Domain.Entities;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using NexusReader.Domain.Entities;
|
||||
using NexusReader.Infrastructure.Identity;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NexusReader.Application.Security.Authorization;
|
||||
using NexusReader.Application.Commands.Sync;
|
||||
using NexusReader.Infrastructure.Handlers;
|
||||
using MediatR;
|
||||
|
||||
namespace NexusReader.Infrastructure;
|
||||
|
||||
@@ -23,72 +18,54 @@ public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var pgConnectionString = configuration.GetConnectionString("PostgresConnection");
|
||||
if (!string.IsNullOrEmpty(pgConnectionString))
|
||||
{
|
||||
var connectionString = configuration.GetConnectionString("DefaultConnection")
|
||||
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
|
||||
|
||||
// Register DB Context Factory for multi-threaded/asynchronous safety in Blazor
|
||||
services.AddDbContextFactory<AppDbContext>(options =>
|
||||
options.UseNpgsql(pgConnectionString, x => x.UseVector()));
|
||||
{
|
||||
if (connectionString.Contains("Host="))
|
||||
{
|
||||
options.UseNpgsql(connectionString, o => o.UseVector());
|
||||
}
|
||||
else
|
||||
{
|
||||
var sqliteConnectionString = configuration.GetConnectionString("SqliteConnection") ?? "Data Source=nexus.db";
|
||||
services.AddDbContextFactory<AppDbContext>(options =>
|
||||
options.UseSqlite(sqliteConnectionString));
|
||||
options.UseSqlite(connectionString);
|
||||
}
|
||||
|
||||
services.Configure<AiSettings>(configuration.GetSection(AiSettings.SectionName));
|
||||
services.Configure<StripeSettings>(configuration.GetSection(StripeSettings.SectionName));
|
||||
var aiSettings = configuration.GetSection(AiSettings.SectionName).Get<AiSettings>() ?? new AiSettings();
|
||||
|
||||
Console.WriteLine($"[Infrastructure] AI Configured: Model={aiSettings.Model}, KeyPresent={!string.IsNullOrWhiteSpace(aiSettings.ApiKey) && aiSettings.ApiKey != "PLACEHOLDER"}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(aiSettings.ApiKey) || aiSettings.ApiKey == "PLACEHOLDER")
|
||||
{
|
||||
Console.WriteLine("[Infrastructure] WARNING: AI API Key is missing or placeholder!");
|
||||
}
|
||||
|
||||
services.AddResiliencePipeline("ai-retry", builder =>
|
||||
{
|
||||
builder.AddRetry(new RetryStrategyOptions
|
||||
{
|
||||
ShouldHandle = new PredicateBuilder().Handle<Exception>(ex =>
|
||||
ex.Message.Contains("429") || ex.Message.Contains("Too Many Requests") || ex.Message.Contains("quota")),
|
||||
BackoffType = DelayBackoffType.Exponential,
|
||||
UseJitter = true,
|
||||
MaxRetryAttempts = aiSettings.RetryAttempts,
|
||||
Delay = TimeSpan.FromSeconds(2)
|
||||
});
|
||||
});
|
||||
|
||||
services.AddChatClient(new GeminiChatClient(new GeminiClientOptions
|
||||
// Register Scoped Context for traditional usage
|
||||
services.AddDbContext<AppDbContext>(options =>
|
||||
{
|
||||
ApiKey = aiSettings.ApiKey,
|
||||
ModelId = aiSettings.Model
|
||||
}));
|
||||
|
||||
services.AddEmbeddingGenerator(new GeminiEmbeddingGenerator(new GeminiClientOptions
|
||||
if (connectionString.Contains("Host="))
|
||||
{
|
||||
ApiKey = aiSettings.ApiKey,
|
||||
ModelId = aiSettings.EmbeddingModel ?? "text-embedding-004"
|
||||
}));
|
||||
options.UseNpgsql(connectionString, o => o.UseVector());
|
||||
}
|
||||
else
|
||||
{
|
||||
options.UseSqlite(connectionString);
|
||||
}
|
||||
});
|
||||
|
||||
// Identity Configuration
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("TokenLimitPolicy", policy =>
|
||||
policy.Requirements.Add(new TokenLimitRequirement()));
|
||||
});
|
||||
|
||||
services.AddScoped<IAuthorizationHandler, TokenLimitHandler>();
|
||||
|
||||
// Services
|
||||
services.AddScoped<IEpubReader, EpubReaderService>();
|
||||
services.AddScoped<IEpubMetadataExtractor, EpubMetadataExtractor>();
|
||||
services.AddScoped<IKnowledgeService, KnowledgeService>();
|
||||
services.AddTransient<IEpubService, EpubService>();
|
||||
services.AddScoped<IBillingService, BillingService>();
|
||||
services.AddSingleton<PromptRegistry>();
|
||||
|
||||
services.AddAuthorizationCore(options =>
|
||||
{
|
||||
options.AddPolicy("ProUser", policy => policy.Requirements.Add(new ProUserRequirement()));
|
||||
});
|
||||
|
||||
services.AddScoped<IAuthorizationHandler, ProUserHandler>();
|
||||
|
||||
services.AddScoped<IInfrastructureMarker, InfrastructureMarker>();
|
||||
// Handlers (MediatR)
|
||||
services.AddScoped<IRequestHandler<UpdateReadingProgressCommand, FluentResults.Result>, UpdateReadingProgressCommandHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static System.Reflection.Assembly Assembly => typeof(DependencyInjection).Assembly;
|
||||
}
|
||||
|
||||
public interface IInfrastructureMarker { }
|
||||
internal class InfrastructureMarker : IInfrastructureMarker { }
|
||||
|
||||
Reference in New Issue
Block a user