Files
step3/api/QuestionsApi/Program.cs
T
2026-07-15 20:55:13 -05:00

140 lines
4.5 KiB
C#

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using QuestionsApi.Services;
using MongoDB.Bson;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<MongoDatabaseProvider>();
builder.Services.AddSingleton<QuestionStore>();
builder.Services.AddSingleton<QuestionAttemptStore>();
builder.Services.AddSingleton<AnswerStatisticsStore>();
builder.Services.AddSingleton<RatingStore>();
builder.Services.AddSingleton<QuestionMediaStore>();
builder.Services.AddSingleton<QuestionReportStore>();
builder.Services.AddSingleton<ExamSimulationStore>();
builder.Services.AddSingleton<CcsStore>();
builder.Services.AddSingleton<CcsRatingStore>();
builder.Services.AddSingleton<CcsCaseReportStore>();
builder.Services.AddSingleton<AppFeedbackStore>();
builder.Services.AddSingleton<ServiceMetrics>();
builder.Services.AddHostedService<AnswerStatisticsMaintenanceService>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Questions API", Version = "v1" });
c.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
Description = "Paste your access token here (no 'Bearer ' prefix needed)."
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{ Type = ReferenceType.SecurityScheme, Id = "bearerAuth" }
},
Array.Empty<string>()
}
});
});
var cfg = builder.Configuration;
var jwtKey = cfg["Jwt:Key"] ?? throw new Exception("Jwt:Key missing");
var issuer = cfg["Jwt:Issuer"] ?? "promiscuity";
var aud = cfg["Jwt:Audience"] ?? issuer;
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = aud,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.Use(async (context, next) =>
{
var started = System.Diagnostics.Stopwatch.StartNew();
try
{
await next();
}
finally
{
context.RequestServices.GetRequiredService<ServiceMetrics>().HttpRequest(started.Elapsed);
}
});
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var feature = context.Features.Get<IExceptionHandlerFeature>();
var exception = feature?.Error;
var logger = context.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("GlobalException");
var traceId = context.TraceIdentifier;
if (exception is not null)
{
logger.LogError(
exception,
"Unhandled exception for {Method} {Path}. TraceId={TraceId}",
context.Request.Method,
context.Request.Path,
traceId
);
}
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/problem+json";
await context.Response.WriteAsJsonAsync(new
{
type = "https://httpstatuses.com/500",
title = "Internal Server Error",
status = 500,
detail = exception?.Message ?? "An unexpected server error occurred.",
traceId
});
});
});
app.MapGet("/healthz", () => Results.Ok("ok"));
app.MapGet("/readyz", async (MongoDatabaseProvider mongo) =>
{
await mongo.Database.RunCommandAsync<BsonDocument>(new BsonDocument("ping", 1));
return Results.Ok("ready");
});
app.MapGet("/metrics", (ServiceMetrics metrics) => Results.Text(metrics.RenderPrometheus(), "text/plain; version=0.0.4"));
app.UseSwagger();
app.UseSwaggerUI(o =>
{
o.SwaggerEndpoint("/swagger/v1/swagger.json", "Questions API v1");
o.RoutePrefix = "swagger";
});
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();