using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Filters; using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Health; using PostFundManagement.Domain.Mediator; using PostFundManagement.Infrastructure.Database.Services; using static PostFundManagement.Domain.Extensions.Constants; var builder = WebApplication.CreateBuilder(args); builder.Services.AddFeatureManagement(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; options.ApiVersionReader = ApiVersionReader.Combine( new UrlSegmentApiVersionReader(), new HeaderApiVersionReader("x-api-version"), new MediaTypeApiVersionReader("version") ); }) .AddApiExplorer(options => { options.GroupNameFormat = "'v'VVV"; options.SubstituteApiVersionInUrl = true; }); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddTransient(); 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.AddSkorubaApiService(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; }); }); var allowedOrigins = builder.Configuration.GetSection("CorsOrigins").Get() ?? []; builder.Services.AddCors(options => { options.AddPolicy(CorsPolicy, policy => { policy.WithOrigins(allowedOrigins) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); var app = builder.Build(); var schedulerFactory = app.Services.GetRequiredService(); var scheduler = await schedulerFactory.GetScheduler(); if (scheduler!.Status != SchedulerStatus.Running) await scheduler.Start(); app.UseHsts(); app.UseHttpsRedirection(); app.UseRouting(); app.UseCors(CorsPolicy); 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) .RequireCors(CorsPolicy) .AddEndpointFilter(async (context, next) => { if (HttpMethods.IsOptions(context.HttpContext.Request.Method)) return await next(context); var filter = context.HttpContext.RequestServices.GetRequiredService(); return await filter.InvokeAsync(context, next); }) .AddEndpointFilter(async (context, next) => { if (HttpMethods.IsOptions(context.HttpContext.Request.Method)) return await next(context); var filter = context.HttpContext.RequestServices.GetRequiredService(); return await filter.InvokeAsync(context, next); }) } }; 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();