using System.IdentityModel.Tokens.Jwt; using System.Text; using Asp.Versioning; using Asp.Versioning.ApiExplorer; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Swashbuckle.AspNetCore.SwaggerGen; using TarninaHub.Application.Abstractions; using TarninaHub.Application.Security; using TarninaHub.Configuration; using TarninaHub.Errors; using TarninaHub.Infrastructure; using TarninaHub.Infrastructure.Auth; var builder = WebApplication.CreateBuilder(args); // Layered infrastructure: SurrealDB client, settings and persistence services. builder.Services.AddInfrastructure(builder.Configuration); // Authentication (JWT bearer) and role-based authorization policies. var authSettings = builder.Configuration.GetSection("Auth").Get() ?? new AuthSettings(); if (!authSettings.HasUsableSigningKey && !builder.Environment.IsDevelopment()) { throw new InvalidOperationException( "Auth signing key is missing or too short. Configure 'Auth:SigningKey' " + "(env 'Auth__SigningKey') with at least 32 characters."); } var signingKey = authSettings.HasUsableSigningKey ? authSettings.SigningKey : "development-only-signing-key-please-override-in-production"; builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.MapInboundClaims = false; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = authSettings.Issuer, ValidateAudience = true, ValidAudience = authSettings.Audience, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)), ValidateLifetime = true, ClockSkew = TimeSpan.FromSeconds(30), NameClaimType = JwtRegisteredClaimNames.Sub, RoleClaimType = "role", }; }); builder.Services.AddAuthorization(options => { options.AddPolicy(Policies.Staff, policy => policy.RequireRole(RoleNames.Operator, RoleNames.Manager, RoleNames.Admin)); options.AddPolicy(Policies.OperatorOrAdmin, policy => policy.RequireRole(RoleNames.Operator, RoleNames.Admin)); options.AddPolicy(Policies.ManagerOrAbove, policy => policy.RequireRole(RoleNames.Manager, RoleNames.Operator, RoleNames.Admin)); options.AddPolicy(Policies.AdminOnly, policy => policy.RequireRole(RoleNames.Admin)); options.AddPolicy(Policies.JstOrAbove, policy => policy.RequireRole(RoleNames.Jst, RoleNames.Operator, RoleNames.Manager, RoleNames.Admin)); }); builder.Services.AddControllers(); // Uniform error responses (RFC 7807) and a global exception handler. builder.Services.AddProblemDetails(); builder.Services.AddExceptionHandler(); // API versioning: `?api-version=1.0` or header `X-Api-Version`; default 1.0. builder.Services .AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; options.ApiVersionReader = ApiVersionReader.Combine( new QueryStringApiVersionReader("api-version"), new HeaderApiVersionReader("X-Api-Version")); options.ReportApiVersions = true; }) .AddMvc() .AddApiExplorer(options => { options.GroupNameFormat = "'v'VVV"; options.SubstituteApiVersionInUrl = true; }); // OpenAPI document + Swagger UI (Swashbuckle), one document per API version. builder.Services.AddEndpointsApiExplorer(); builder.Services.AddTransient, ConfigureSwaggerOptions>(); builder.Services.AddSwaggerGen(); var app = builder.Build(); using (var scope = app.Services.CreateScope()) { var migrationRunner = scope.ServiceProvider.GetRequiredService(); var storageService = scope.ServiceProvider.GetRequiredService(); await migrationRunner.ApplyAsync(CancellationToken.None); await storageService.InitializeAsync(CancellationToken.None); } app.UseExceptionHandler(); app.UseStatusCodePages(); app.UseSwagger(); app.UseSwaggerUI(options => { var provider = app.Services.GetRequiredService(); foreach (var description in provider.ApiVersionDescriptions) { options.SwaggerEndpoint( $"/swagger/{description.GroupName}/swagger.json", $"TarninaHub API {description.GroupName.ToUpperInvariant()}"); } }); if (!app.Environment.IsDevelopment()) { app.UseHttpsRedirection(); } app.UseDefaultFiles(); app.UseStaticFiles(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapFallbackToFile("index.html"); app.Run();