using System.Text.Json.Serialization; using ERPCore.Infra.Auth; using ERPCore.Infra.Auth.AuthHex; using ERPCore.Infra.Persistence; using ERPCore.Infra.UoW; using ERPCore.Repositories; using ERPCore.Repositories.Interfaces; using ERPCore.Services; using ERPCore.Services.Auth; using ERPCore.Services.Interfaces; using ERPCore.Services.Stock; using ERPCore.System.Errors; using Microsoft.AspNetCore.Authentication; using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi; using Serilog; var builder = WebApplication.CreateBuilder(args); // Serilog (file sink) — reads levels from configuration, plus a rolling daily file. builder.Host.UseSerilog((ctx, cfg) => cfg .ReadFrom.Configuration(ctx.Configuration) .WriteTo.File("logs/erpcore-.log", rollingInterval: RollingInterval.Day)); // Controllers + JSON: serialize enums as their string names (docs/11 §8, camelCase). builder.Services.AddControllers() .AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); // EF Core + PostgreSQL builder.Services.AddDbContext(o => o.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); // ProblemDetails (RFC 7807) + domain-exception mapping builder.Services.AddProblemDetails(); builder.Services.AddExceptionHandler(); // Auth: validate external AuthHex RS256 tokens + ERP door policy (docs/10 A.4) builder.Services.AddErpJwtAuth(builder.Configuration); // AuthController proxy → AuthHex (docs/11 §2.0) builder.Services.AddHttpClient(c => { var baseUrl = builder.Configuration["AuthHex:BaseUrl"] ?? throw new InvalidOperationException("AuthHex:BaseUrl is not configured."); c.BaseAddress = new Uri(baseUrl); }); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Current-user (audit actor). AuthHex has no sub/nameid → a claims transformation // JIT-provisions a local shadow user and injects the local `int` id as `nameid`. builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Unit of work + generic repository base builder.Services.AddScoped(); builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); // Master-data services (docs/11 §2) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Cross-cutting + procurement services (docs/11 §3) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Stock core + goods receipt (docs/11 §4–5) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Stock transactions + reference data (docs/11 §5–6) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Cross-cutting: audit trail + GL-ready journal read access (docs/11 §6 / FR-X-02, FR-STK-13) builder.Services.AddScoped(); // Health checks (EF Core DB) builder.Services.AddHealthChecks().AddDbContextCheck(); // Swagger / OpenAPI (Swashbuckle v10 → OpenAPI 3.1) builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(o => o.SwaggerDoc("v1", new OpenApiInfo { Title = "ERPCore API", Version = "v1" })); var app = builder.Build(); // Seed configurable reference data (reason codes) idempotently at startup. using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); await DataSeeder.SeedAsync(db); } app.UseSerilogRequestLogging(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseExceptionHandler(); // -> DomainExceptionHandler / ProblemDetails app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapHealthChecks("/health"); app.Run();