using System.Diagnostics; using Baya.Application.Features.Users.Commands.Create; using Baya.Application.Models.ApiResult; using Baya.Application.Models.Identity; using Baya.Application.ServiceConfiguration; using Baya.Domain.Entities.User; using Baya.Infrastructure.CrossCutting.Logging; using Baya.Infrastructure.CrossCutting.ServiceConfiguration; using Baya.Infrastructure.Identity.Identity.Dtos; using Baya.Infrastructure.Identity.Jwt; using Baya.Infrastructure.Identity.ServiceConfiguration; using Baya.Infrastructure.Monitoring.Configurations; using Baya.Infrastructure.Persistence.ServiceConfiguration; using Baya.SharedKernel.Extensions; using Baya.Web.Api.Configuration; using Baya.Web.Plugins.Grpc; using Baya.WebFramework.Filters; using Baya.WebFramework.Middlewares; using Baya.WebFramework.Routing; using Baya.WebFramework.ServiceConfiguration; using Baya.WebFramework.Swagger; using Mapster; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Serilog; var builder = WebApplication.CreateBuilder(args); builder.Host.UseSerilog(LoggingConfiguration.ConfigureLogger); var configuration = builder.Configuration; // Fail fast if a load-bearing secret (DB connection, JWE/field-encryption keys) is missing or still a // committed placeholder — before any service reaches for it. Skipped in the "Testing" environment. builder.ValidateRequiredSecrets(); Activity.DefaultIdFormat = ActivityIdFormat.W3C; // HTTPS metadata is required for the token exchange in deployed environments; relaxed for local // Development and the Testing host, which run over plain HTTP. var requireHttpsMetadata = !builder.Environment.IsDevelopment() && !builder.Environment.IsEnvironment("Testing"); builder .ConfigureHealthChecks() .SetupOpenTelemetry(); builder.Services.Configure(configuration.GetSection(nameof(IdentitySettings))); var identitySettings = configuration.GetSection(nameof(IdentitySettings)).Get(); builder.Services.AddControllers(options => { options.Conventions.Add(new RouteTokenTransformerConvention(new SnakeCaseParameterTransformer())); options.Filters.Add(typeof(OkResultAttribute)); options.Filters.Add(typeof(NotFoundResultAttribute)); options.Filters.Add(typeof(ContentResultFilterAttribute)); options.Filters.Add(typeof(ModelStateValidationAttribute)); options.Filters.Add(typeof(BadRequestResultFilterAttribute)); options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult>>), StatusCodes.Status400BadRequest)); options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult), StatusCodes.Status401Unauthorized)); options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult), StatusCodes.Status403Forbidden)); options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult), StatusCodes.Status500InternalServerError)); }).ConfigureApiBehaviorOptions(options => { options.SuppressModelStateInvalidFilter = true; options.SuppressMapClientErrors = true; }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwagger("v1","v1.1"); builder.Services.AddApplicationServices() .RegisterIdentityServices(identitySettings, requireHttpsMetadata) .AddPersistenceServices(configuration) .AddCrossCuttingSeams(configuration) .AddWebFrameworkServices() .AddCorsPolicies(configuration) .AddForwardedHeadersConfiguration(configuration) .AddRateLimitingPolicies(); // Development-only: capture each OTP in-memory so GET /api/v1/dev/last_otp/{phone} can complete a login // without an SMS gateway. Nothing here is wired in any other environment. if (builder.Environment.IsDevelopment()) builder.Services.AddDevelopmentOtpCapture(); builder.Services.RegisterValidatorsAsServices(); builder.Services.AddExceptionHandler(); builder.Services.AddMapster(); TypeAdapterConfig.GlobalSettings.Scan(typeof(UserCreateCommand).Assembly, typeof(GetRolesDto).Assembly); #region Plugin Services Configuration builder.Services.ConfigureGrpcPluginServices(); #endregion var app = builder.Build(); // Integration tests (WebApplicationFactory, env "Testing") run on in-memory SQLite — the SQL Server // migrations can't apply there; the test factory does EnsureCreated + seeding itself. if (!app.Environment.IsEnvironment("Testing")) { await app.ApplyMigrationsAsync(); await app.SeedDefaultUsersAsync(); // Development-only: a sandbox payment gateway (all-zeros merchant id) and a demo marketplace // (nurses/variants/search rows, customers/patients) so the real-path screens aren't empty. Neither // belongs in a deployed DB — a production gateway is an admin action, so this never runs in // Production/Staging. Both are idempotent. if (app.Environment.IsDevelopment()) { await app.SeedPaymentGatewaysAsync(); await app.SeedDemoWorldAsync(); } } if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else app.UseExceptionHandler(_=>{}); // First in the pipeline so the resolved client IP (X-Forwarded-For, from a trusted proxy) is in place // before anything downstream — notably the rate limiter — reads HttpContext.Connection.RemoteIpAddress. app.UseForwardedHeaders(); app.UseSwaggerAndUi(); app.UseRouting(); // After UseRouting and before the rate limiter / authentication so a pre-flight OPTIONS is answered // (and not rejected as 429/401) before the browser sends the real cross-origin request. app.UseCors(CorsServiceExtension.PolicyName); app.UseRateLimiter(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.UseMetrics() .UseHealthChecks(); app.ConfigureGrpcPipeline(); await app.RunAsync(); /// Exposes the entry point to WebApplicationFactory<Program>-based integration tests. public partial class Program;