using FastEndpoints; using GB5Shared.GOP.Qualifier; using FastEndpoints.Swagger; using GB5Shared.Addon; using GB5Shared.Connection; using GB5Shared.DateConverter; using GB5Shared.DTO.Framework.CommonConfig; using GB5Shared.EntityHandler; using GB5Shared.EventLogPublish; using GB5Shared.GB5CommonFunction; using GB5Shared.GB5Library.Qualifier; using GB5Shared.GenerateAutoNumber; using GB5Shared.Hosting; using GB5Shared.PubSub.OutBox; using GB5Shared.QueryExecutor; using GB5Shared.Storage; using GB5Shared.Telemetry; using GB5Shared.Validation; using GB5Shared.WorkFlow.WorkFlowEngine; using GB5Shared.WorkFlow.WorkFlowRunTime; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Caching.Hybrid; using Microsoft.Extensions.Logging; using Npgsql; using System.Data; using System.Text; using static GB5Shared.DateConverter.GB5JsonOptions; using static GB5Shared.DTO.Framework.Enum.FrameworkEnumDTO; // ------------------------- // Bootstrap // ------------------------- var builder = WebApplication.CreateBuilder(args); // ------------------------- // DI validation // This service loads its module(s) via GB5Shared.Hosting.ModuleLoader, which deliberately keeps // the service running when a module has unresolvable dependencies — it disables only the affected // endpoints (o.Filter) and logs them. In the Development environment .NET turns on ValidateOnBuild, // which eagerly constructs every registered descriptor at builder.Build() and would crash on those // same gaps, defeating that design. Disable eager validation so the service builds lazily and // ModuleLoader's graceful degradation works in every environment (matches non-Development default). // ------------------------- builder.Host.UseDefaultServiceProvider(o => { o.ValidateOnBuild = false; o.ValidateScopes = false; }); // ------------------------- // Console Encoding // ------------------------- Console.OutputEncoding = Encoding.UTF8; // ── 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}"); // ------------------------- // Dapr & Controllers Setup // ------------------------- builder.Services.AddHttpContextAccessor(); builder.Services.AddDaprClient(); builder.Services.AddControllers() .AddDapr() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; options.JsonSerializerOptions.DictionaryKeyPolicy = null; options.JsonSerializerOptions.AddGB5Converters(); }); // ------------------------- // JSON Global (Minimal API / HttpContext) // ------------------------- builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.PropertyNamingPolicy = null; options.SerializerOptions.DictionaryKeyPolicy = null; options.SerializerOptions.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(); // ------------------------- // Application Services (shared/cross-cutting — modules do NOT register these) // ------------------------- 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.AddScoped(); builder.Services.AddScoped(typeof(BaseEntityAppService<>), typeof(BaseEntityAppService<>)); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddGB5QualifierEngine(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.Configure( builder.Configuration.GetSection("Gb5SystemDTO")); builder.Services.Configure( builder.Configuration.GetSection("DefaultLanguage")); // IAttachmentUploadService, ITemplateResolutionEngine, IAttachmentPathResolutionService now live // in GB5Shared.Attachment — not covered by the FrameworkBLL/FrameworkDAL scan, since GB5Shared // isn't in its FromAssemblies list. builder.Services.Configure( builder.Configuration.GetSection(GB5Shared.Attachment.AttachmentPathSettings.Section)); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddGB5Storage(builder.Configuration.GetSection("StorageConfiguration")); // ✅ Read Gb5SystemDTO config var gb5Config = builder.Configuration .GetSection("Gb5SystemDTO") .Get(); // ✅ Resolve active DB type from appsettings DataBaseType value var activeDbType = (DatabaseType)gb5Config.DataBaseType; // ✅ Register DatabaseTypeConfig as singleton — injected into DAL builder.Services.AddSingleton(new DatabaseTypeConfig(activeDbType)); // ✅ IDbConnection — switches SQL Server or PostgreSQL based on DataBaseType builder.Services.AddScoped(sp => { // ✅ Use ActiveConnectionString — handles both old and new format var connStr = gb5Config.ActiveConnectionString; return activeDbType switch { DatabaseType.SQL => (IDbConnection)new SqlConnection(connStr), DatabaseType.POSTGRE => (IDbConnection)new NpgsqlConnection(connStr), _ => throw new NotSupportedException($"DataBaseType '{gb5Config.DataBaseType}' not supported.") }; }); // Npgsql 6+ strict mode rejects DateTime with Kind != Utc for timestamptz columns. // Must be set before any NpgsqlConnection is created. AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); // ------------------------- // Kestrel // ------------------------- builder.Services.Configure(options => { options.AllowSynchronousIO = true; }); // -- OpenTelemetry (Tracing + Metrics) ---------------------------------------- builder.Services.AddGB5Telemetry(builder.Configuration, "GB5-MAINTENANCE"); // ------------------------- // Module discovery + registration (graceful degradation) // ModuleLoader scans the output folder for GB5 modules (IModule), runs each module's Register(), // and probes every endpoint's DI in a throwaway container — disabling only the endpoints whose // dependencies can't resolve (e.g. Claim, which needs cross-module PayRoll/Admin/Accounts/Framework // services not present in this standalone service) while the rest of the module keeps running. // Running standalone, only the Maintenance module is discovered; hosted by BusinessHost, this // Program.cs is not executed (only MaintenanceModule is used). // ------------------------- using var startupLoggerFactory = LoggerFactory.Create(b => b.AddConsole()); var startupLogger = startupLoggerFactory.CreateLogger("MaintenanceSL.ModuleLoader"); var moduleLoad = ModuleLoader.LoadAll(builder.Services, builder.Configuration, startupLogger, "GB5Shared"); // ------------------------- // FastEndpoints // ------------------------- builder.Services.AddFastEndpoints(o => { o.Assemblies = moduleLoad.FastEndpointsAssemblies; o.Filter = moduleLoad.EndpointFilter; }); // ------------------------- // Swagger // ------------------------- builder.Services.SwaggerDocument(o => { o.DocumentSettings = s => { s.Title = "GB5 Maintenance Service"; s.Version = "v1"; s.Description = "GB5 Maintenance Microservice"; }; }); // ------------------------- // Build App // ------------------------- var app = builder.Build(); // ------------------------- // Middleware // ------------------------- app.UseCloudEvents(); app.UseMiddleware(); app.UseMiddleware(); app.UseFastEndpoints(c => { c.Serializer.Options.PropertyNamingPolicy = null; c.Serializer.Options.DictionaryKeyPolicy = null; c.Serializer.Options.AddGB5Converters(); }); moduleLoad.MapEndpoints(app, app.Logger); app.MapControllers(); app.MapSubscribeHandler(); // ------------------------- // Swagger UI (All Environments) // ------------------------- app.UseOpenApi(); app.UseSwaggerUi(o => { o.Path = "/GB5Documentation"; o.DocumentPath = "/swagger/{documentName}/swagger.json"; o.TransformToExternalPath = (internalUiRoute, _) => "/ms" + internalUiRoute; }); // ------------------------- // Module health // ------------------------- moduleLoad.MapModuleHealth(app, "Maintenance"); // ------------------------- // Test Endpoint // ------------------------- app.MapGet("/", () => $"Hello from GB5 Maintenance Service - {moduleLoad.BannerText()}"); // ------------------------- // HTTPS & Authorization // ------------------------- app.UseAuthorization(); // ------------------------- // Run the Application // ------------------------- app.Run();