using System.Reflection; using System.Linq; using System.Text; using FastEndpoints; using FastEndpoints.Swagger; using FLSSL.Hubs; using GB5Shared.Connection; using GB5Shared.DaprCache; using GB5Shared.DateConverter; using static GB5Shared.DateConverter.GB5JsonOptions; using GB5Shared.DTO.Framework.CommonConfig; using GB5Shared.EventLogPublish; using GB5Shared.GB5CommonFunction; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using GB5Shared.Telemetry; using GB5Shared.Validation; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Caching.Hybrid; using OpenTelemetry; using OpenTelemetry.Resources; using OpenTelemetry.Trace; var builder = WebApplication.CreateBuilder(args); // ── Port from config — change "AppPort" in appsettings.json to use any port ── var appPort = builder.Configuration.GetValue("AppPort"); builder.WebHost.UseUrls($"http://0.0.0.0:{appPort}"); Console.OutputEncoding = Encoding.UTF8; // ── Dapr ────────────────────────────────────────────────────────── builder.Services.AddDaprClient(); builder.Services.AddControllers() .AddDapr() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; options.JsonSerializerOptions.DictionaryKeyPolicy = null; options.JsonSerializerOptions.AddGB5Converters(); }); // ── Caching ─────────────────────────────────────────────────────── #pragma warning disable EXTEXP0018 builder.Services.AddHybridCache(options => { options.DefaultEntryOptions = new HybridCacheEntryOptions(); options.DisableCompression = false; }); #pragma warning restore EXTEXP0018 builder.Services.AddMemoryCache(); builder.Services.AddDistributedMemoryCache(); // ── CORS ────────────────────────────────────────────────────────── // ── Application Services ────────────────────────────────────────── builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.Configure( builder.Configuration.GetSection("Gb5SystemDTO")); // ── Named HTTP Clients ──────────────────────────────────────────── // FlsBridge — used by FlsEventBLL for HTTP bridge deliveries builder.Services.AddHttpClient("FlsBridge") .SetHandlerLifetime(TimeSpan.FromMinutes(5)); // Dapr sidecar client — used by FlsEventBLL for Dapr pub/sub deliveries builder.Services.AddHttpClient("Dapr") .SetHandlerLifetime(TimeSpan.FromMinutes(5)); // ── Kestrel ─────────────────────────────────────────────────────── builder.Services.Configure(options => { options.AllowSynchronousIO = true; }); // ── SignalR ─────────────────────────────────────────────────────── builder.Services.AddSignalR(options => { options.EnableDetailedErrors = builder.Environment.IsDevelopment(); options.KeepAliveInterval = TimeSpan.FromSeconds(15); options.ClientTimeoutInterval = TimeSpan.FromSeconds(60); options.MaximumReceiveMessageSize = 32 * 1024; }); // ── Telemetry ───────────────────────────────────────────────────── builder.Services.AddGB5Telemetry(builder.Configuration, "GB5-FLS"); // ── Assembly Scan for DI ────────────────────────────────────────── var flsBllAssembly = Assembly.Load("FLSBLL"); var flsDalAssembly = Assembly.Load("FLSDAL"); builder.Services.Scan(scan => scan .FromAssemblies(flsBllAssembly, flsDalAssembly) // Exclude plain DTOs/records (any namespace containing ".DTO", not just an exact "DTOs" // last-segment match — FLSDAL.DTO.Survey.SaveSurveyDraftRequest slipped past the old check) // and Exception types. A `record` implicitly implements IEquatable and every Exception // implements ISerializable, so without this filter AsImplementedInterfaces() registers them // as services and the container later fails trying to activate them via their data/message // constructor. .AddClasses(c => c.Where(t => !t.IsAbstract && !t.IsInterface && (t.Namespace == null || !t.Namespace.Contains(".DTO")) && !typeof(Exception).IsAssignableFrom(t))) .AsImplementedInterfaces() .WithScopedLifetime()); // ── FastEndpoints ───────────────────────────────────────────────── var endpointAssemblies = new[] { Assembly.Load("FLSSL"), Assembly.Load("FLSBLL"), Assembly.Load("FLSDAL"), Assembly.Load("GB5Shared") }; builder.Services.AddFastEndpoints(o => { o.Assemblies = endpointAssemblies; }); // ── Swagger ─────────────────────────────────────────────────────── builder.Services.SwaggerDocument(o => { o.DocumentSettings = s => { s.Title = "GB5 FLS API"; s.Version = "v1"; s.Description = "Form Lifecycle Service — surveys, quick ratings, token-based response collection"; }; o.EnableJWTBearerAuth = false; o.ShortSchemaNames = true; }); // ── Build ───────────────────────────────────────────────────────── var app = builder.Build(); app.UseCloudEvents(); app.UseMiddleware(); app.UseMiddleware(); app.UseFastEndpoints(c => { c.Serializer.Options.PropertyNamingPolicy = null; c.Serializer.Options.DictionaryKeyPolicy = null; c.Serializer.Options.AddGB5Converters(); }); app.MapControllers(); app.MapSubscribeHandler(); // ── SignalR Hub ─────────────────────────────────────────────────── app.MapHub("/hubs/fls-monitor"); // ── Swagger UI ──────────────────────────────────────────────────── app.UseOpenApi(); app.UseSwaggerUi(o => { o.Path = "/GB5Documentation"; o.DocumentPath = "/swagger/{documentName}/swagger.json"; o.TransformToExternalPath = (internalUiRoute, _) => "/fls" + internalUiRoute; }); app.MapGet("/", () => "Hello from GB5 FLS API (.NET 9)"); app.UseAuthorization(); app.Run();