using System.Text.Json.Serialization; using Extensions.Hosting.AsyncInitialization; using Hangfire; using Hangfire.PostgreSql; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi; using Scalar.AspNetCore; using Serilog; using Tnsu.ApprovalRoute.Api.Auth; using Tnsu.ApprovalRoute.Api.Filters; using Tnsu.ApprovalRoute.Application; using Tnsu.ApprovalRoute.Application.ListPartitioning; using Tnsu.ApprovalRoute.Application.Reminders; using Tnsu.ApprovalRoute.Application.Common.Interfaces; using Tnsu.ApprovalRoute.Infrastructure; const string CorsPolicyName = "Frontend"; var builder = WebApplication.CreateBuilder(args); // Локальные оверрайды, не попадающие в git (см. .gitignore) — на своей машине // можно, например, занизить ListPartitioning:ItemCountThreshold, чтобы не // накапливать тысячи тестовых заявок для проверки создания партиций. builder.Configuration.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true); builder.Host.UseSerilog((context, services, configuration) => configuration .ReadFrom.Configuration(context.Configuration) .Enrich.FromLogContext() .WriteTo.Console() .WriteTo.File( "logs/log-.txt", rollingInterval: RollingInterval.Month, retainedFileCountLimit: 6)); builder.Services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownIPNetworks.Clear(); options.KnownProxies.Clear(); }); var corsAllowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get() ?? []; builder.Services.AddCors(options => { options.AddPolicy(CorsPolicyName, policy => policy .WithOrigins(corsAllowedOrigins) .AllowAnyHeader() .AllowAnyMethod()); }); builder.Services.AddSingleton(TimeProvider.System); builder.Services.AddApprovalRouteInfrastructure(builder.Configuration); builder.Services.AddApprovalRouteApplication(); builder.Services.Configure(builder.Configuration.GetSection("Reminders")); builder.Services.Configure(builder.Configuration.GetSection("ListPartitioning")); builder.Services.AddHangfire(configuration => configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() .UsePostgreSqlStorage(postgres => postgres.UseNpgsqlConnection(builder.Configuration.GetConnectionString("Postgres")))); builder.Services.AddHangfireServer(); builder.Services.AddApprovalRouteAuth(builder.Configuration); builder.Services.AddScoped(); builder.Services.AddOpenApi(options => { options.AddDocumentTransformer((document, _, _) => { document.Components ??= new OpenApiComponents(); document.Components.SecuritySchemes ??= new Dictionary(); document.Components.SecuritySchemes["Bearer"] = new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", Description = "Введите JWT токен" }; return Task.CompletedTask; }); options.AddOperationTransformer((operation, context, _) => { operation.Security = [ new OpenApiSecurityRequirement { [new OpenApiSecuritySchemeReference("Bearer", context.Document)] = [] } ]; return Task.CompletedTask; }); }); builder.Services.AddHealthChecks(); builder.Services.AddControllers(options => options.Filters.Add()) .AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); builder.Services.ConfigureHttpJsonOptions(o => o.SerializerOptions.Converters.Add(new JsonStringEnumConverter())); var app = builder.Build(); app.UseForwardedHeaders(); app.MapOpenApi(); app.MapScalarApiReference(options => { options .WithTitle("Tnsu Approval Route") .AddPreferredSecuritySchemes("Bearer") .AddHttpAuthentication("Bearer", _ => { }); }); app.MapHealthChecks("/health"); app.UseCors(CorsPolicyName); app.UseAuthentication(); app.UseAuthorization(); app.UseHangfireDashboard("/hangfire", new DashboardOptions { Authorization = [ new HangfireDashboardBasicAuthFilter( app.Configuration["Hangfire:Dashboard:Username"] ?? string.Empty, app.Configuration["Hangfire:Dashboard:Password"] ?? string.Empty) ] }); app.MapControllers(); await app.InitAndRunAsync(); public partial class Program;