using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Filters; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Health; using PostFundManagement.Domain.Mediator; using static PostFundManagement.Domain.Extensions.Constants; var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer() .AddApiVersioning(options => options.ApiVersionReader = new HeaderApiVersionReader()); builder.Services.AddApiVersioning().AddApiExplorer(); builder.Services.AddScoped(); builder.Services.AddEndpoints(Assembly.GetExecutingAssembly()); builder.Services.AddApiServices(builder.Configuration); builder.Services.AddSharedServices(); builder.Services.AddRegisterServicesWithCaching(builder.Configuration); builder.Services.AddEndpointAndServiceCaching(builder.Configuration); builder.Services.AddMediator(); builder.Services.AddSecurityApiSdk(builder.Configuration); builder.Services.AddApiSecurity(builder.Configuration); builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TelemetryPipelineBehavior<,>)); builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingPipelineBehavior<,>)); builder.Services.AddQuartzScheduler(DefaultSchedulerName, builder.Configuration); builder.Services.AddEmailServices(builder.Configuration); builder.Services.AddHashServices(builder.Configuration); builder.Services.AddGarageS3(builder.Configuration); builder.Services.AddHttpClient(); builder.Services.AddDataProtectionDatabase(builder.Configuration); builder.Services.AddApplicationDbContext(builder.Configuration); builder.Services.AddHealthChecks() .AddCheck("QuartzScheduler") .AddCheck("PostgresDatabase") .AddCheck("Self", () => HealthCheckResult.Healthy()); IEnumerable identityScopes = ["openid", "profile", "roles", "email", "address"]; builder.Services.AddValidation() .AddEndpointValidation(); builder.Services.AddOpenApi(options => { options.AddDocumentTransformer((document, context, cancellationToken) => { document.Components ??= new OpenApiComponents(); document.Components?.SecuritySchemes!["OAuth2"] = new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, Description = "Interactive OAuth2 flow for authentication and authorization", Flows = new OpenApiOAuthFlows { AuthorizationCode = new OpenApiOAuthFlow { AuthorizationUrl = new Uri($"{builder.Configuration["SecuritySettings:Authority"]}/connect/authorize"), TokenUrl = new Uri($"{builder.Configuration["SecuritySettings:Authority"]}/connect/token"), Scopes = new Dictionary { { "openid", "OpenID Connect" }, { "profile", "User Profile" }, { "roles", "User Roles" }, { "email", "User Email" }, { "address", "User Address" }, { builder.Configuration["SecuritySettings:Audience"]!, "PFM API Scope" } } } } }; return Task.CompletedTask; }); options.AddOperationTransformer((operation, context, cancellationToken) => { operation.Security ??= []; var schemeReference = new OpenApiSecuritySchemeReference("OAuth2"); operation.Security.Add(new OpenApiSecurityRequirement { [schemeReference] = [.. identityScopes, builder.Configuration["SecuritySettings:Audience"]!] }); return Task.CompletedTask; }); }); builder.Services.AddCors(options => options.AddPolicy("AllowFrontend", policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials())); var app = builder.Build(); var schedulerFactory = app.Services.GetRequiredService(); var scheduler = await schedulerFactory.GetScheduler(DefaultSchedulerName); if (!scheduler!.IsStarted) await scheduler.Start(); app.UseHsts(); app.UseHttpsRedirection(); app.UseRouting(); app.UseCors("AllowFrontend"); app.UseOutputCache(); app.UseAuthentication(); app.UseAuthorization(); ApiVersionSet versionSet = app.NewApiVersionSet("v1") .HasApiVersion(new ApiVersion(1)) .ReportApiVersions() .Build(); var versionGroups = new Dictionary { { 1, app.MapGroup("v{version:apiVersion}").WithApiVersionSet(versionSet) .RequireAuthorization("ApiScope").AddEndpointFilter() } }; app.MapEndpoints(versionGroups); app.UseHealthChecks("/health", new HealthCheckOptions { Predicate = _ => true, AllowCachingResponses = true, ResponseWriter = HealthChecks.UI.Client.UIResponseWriter.WriteHealthCheckUIResponse }); app.MapHealthChecksUI(options => { options.UIPath = "/healthui"; }); app.UseHealthChecks("/ready"); app.MapOpenApi(); var apiVersions = app.DescribeApiVersions() .OrderByDescending(o => o.ApiVersion.MajorVersion) .ToList(); foreach (var description in app.DescribeApiVersions().OrderByDescending(o => o.ApiVersion.MajorVersion)) { app.MapScalarApiReference($"/openapi/{description.GroupName}", (options, context) => { options.AddServer(new ScalarServer($"https://{context.Request.Host}")); options.WithOpenApiRoutePattern($"/openapi/{description.GroupName}.json"); options.WithTheme(ScalarTheme.DeepSpace); options.Agent = new ScalarAgentOptions { Disabled = true }; options.AddPreferredSecuritySchemes("OAuth2") .AddOAuth2Flows("OAuth2", flow => { flow.AuthorizationCode = new AuthorizationCodeFlow { ClientId = builder.Configuration["SecuritySettings:ClientId"]!, Pkce = Pkce.Sha256, SelectedScopes = [.. identityScopes, builder.Configuration["SecuritySettings:Audience"]!] }; }); }); } var latestVersionGroup = apiVersions.FirstOrDefault()?.GroupName ?? "v1"; app.MapGet("/", () => Results.Redirect($"/openapi/{latestVersionGroup}")) .ExcludeFromDescription(); if (!app.Environment.IsDevelopment()) app.UseExceptionHandler("/Error", createScopeForErrors: true); app.Run();