From 4db3e568112d32700de6cf3f889ab7fd75793916 Mon Sep 17 00:00:00 2001 From: minhtrannhat Date: Fri, 27 Dec 2024 12:00:00 -0500 Subject: [PATCH] feat(api): configure API application startup --- src/IncidentOps.Api/Program.cs | 108 +++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/IncidentOps.Api/Program.cs diff --git a/src/IncidentOps.Api/Program.cs b/src/IncidentOps.Api/Program.cs new file mode 100644 index 0000000..5759d8a --- /dev/null +++ b/src/IncidentOps.Api/Program.cs @@ -0,0 +1,108 @@ +using System.Text; +using FluentMigrator.Runner; +using Hangfire; +using Hangfire.Redis.StackExchange; +using IncidentOps.Api.Auth; +using IncidentOps.Infrastructure; +using IncidentOps.Infrastructure.Auth; +using IncidentOps.Infrastructure.Migrations; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.IdentityModel.Tokens; +using StackExchange.Redis; + +var builder = WebApplication.CreateBuilder(args); + +// Add controllers +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddOpenApi(); + +// Configure JWT settings +var jwtSettings = new JwtSettings +{ + Issuer = builder.Configuration["Jwt:Issuer"] ?? "incidentops", + Audience = builder.Configuration["Jwt:Audience"] ?? "incidentops", + SigningKey = builder.Configuration["Jwt:SigningKey"] ?? throw new InvalidOperationException("JWT signing key not configured"), + AccessTokenExpirationMinutes = builder.Configuration.GetValue("Jwt:AccessTokenExpirationMinutes", 15), + RefreshTokenExpirationDays = builder.Configuration.GetValue("Jwt:RefreshTokenExpirationDays", 7) +}; + +// Configure Infrastructure +var connectionString = builder.Configuration.GetConnectionString("Postgres") + ?? throw new InvalidOperationException("Postgres connection string not configured"); +builder.Services.AddInfrastructure(connectionString, jwtSettings); + +// Configure FluentMigrator +builder.Services.AddFluentMigratorCore() + .ConfigureRunner(rb => rb + .AddPostgres() + .WithGlobalConnectionString(connectionString) + .ScanIn(typeof(Migration0001_InitialSchema).Assembly).For.Migrations()) + .AddLogging(lb => lb.AddFluentMigratorConsole()); + +// Configure JWT Authentication +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = jwtSettings.Issuer, + ValidAudience = jwtSettings.Audience, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SigningKey)) + }; + }); + +// Configure Authorization +builder.Services.AddSingleton(); +builder.Services.AddAuthorizationBuilder() + .AddPolicy("Viewer", policy => policy.Requirements.Add(new RoleRequirement(IncidentOps.Domain.Enums.OrgRole.Viewer))) + .AddPolicy("Member", policy => policy.Requirements.Add(new RoleRequirement(IncidentOps.Domain.Enums.OrgRole.Member))) + .AddPolicy("Admin", policy => policy.Requirements.Add(new RoleRequirement(IncidentOps.Domain.Enums.OrgRole.Admin))); + +// Configure Hangfire (client only - server runs in Worker) +var redisConnectionString = builder.Configuration["Redis:ConnectionString"] + ?? throw new InvalidOperationException("Redis connection string not configured"); +builder.Services.AddHangfire(configuration => configuration + .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseRedisStorage(ConnectionMultiplexer.Connect(redisConnectionString))); + +// Add CORS +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(policy => + { + policy.WithOrigins(builder.Configuration.GetSection("Cors:Origins").Get() ?? ["http://localhost:3000"]) + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); +}); + +var app = builder.Build(); + +// Run migrations +using (var scope = app.Services.CreateScope()) +{ + var runner = scope.ServiceProvider.GetRequiredService(); + runner.MigrateUp(); +} + +// Configure the HTTP request pipeline +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseCors(); +app.UseAuthentication(); +app.UseAuthorization(); +app.MapControllers(); + +app.Run();