117 lines
5.1 KiB
C#
117 lines
5.1 KiB
C#
using System.Text;
|
|
using AccountTracking.Api.Data;
|
|
using AccountTracking.Api.Services;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using Polly;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// ── Configuration ──────────────────────────────────────────────────────────
|
|
var connectionString = builder.Configuration["DB_CONNECTION"];
|
|
var jwtSecret = builder.Configuration["JWT_SECRET"]
|
|
?? throw new InvalidOperationException("JWT_SECRET is required");
|
|
var appUsername = builder.Configuration["APP_USERNAME"]
|
|
?? throw new InvalidOperationException("APP_USERNAME is required");
|
|
var appPassword = builder.Configuration["APP_PASSWORD"]
|
|
?? throw new InvalidOperationException("APP_PASSWORD is required");
|
|
var allowedOrigin = builder.Configuration["ALLOWED_ORIGIN"] ?? "http://localhost:3000";
|
|
|
|
// ── Database ────────────────────────────────────────────────────────────────
|
|
if (!string.IsNullOrEmpty(connectionString))
|
|
{
|
|
builder.Services.AddDbContext<AppDbContext>(opts =>
|
|
opts.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
|
|
}
|
|
else
|
|
{
|
|
// NSwag build-time spec generation: no real DB available
|
|
builder.Services.AddDbContext<AppDbContext>(opts =>
|
|
opts.UseInMemoryDatabase("nswag_gen"));
|
|
}
|
|
|
|
// ── Services ────────────────────────────────────────────────────────────────
|
|
builder.Services.AddScoped<CsvImportService>();
|
|
builder.Services.AddScoped<DashboardService>();
|
|
|
|
// ── Auth ────────────────────────────────────────────────────────────────────
|
|
var keyBytes = Convert.FromBase64String(jwtSecret);
|
|
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
.AddJwtBearer(opts =>
|
|
{
|
|
opts.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuerSigningKey = true,
|
|
IssuerSigningKey = new SymmetricSecurityKey(keyBytes),
|
|
ValidateIssuer = false,
|
|
ValidateAudience = false,
|
|
ClockSkew = TimeSpan.Zero
|
|
};
|
|
});
|
|
builder.Services.AddAuthorization();
|
|
|
|
// Store credentials for use by AuthController
|
|
builder.Services.AddSingleton(new AppCredentials(appUsername, appPassword, keyBytes));
|
|
|
|
// ── CORS ────────────────────────────────────────────────────────────────────
|
|
builder.Services.AddCors(opts =>
|
|
{
|
|
opts.AddDefaultPolicy(policy =>
|
|
policy.WithOrigins(allowedOrigin, "http://localhost:3000")
|
|
.AllowAnyHeader()
|
|
.AllowAnyMethod());
|
|
});
|
|
|
|
// ── Controllers & OpenAPI ───────────────────────────────────────────────────
|
|
builder.Services.AddControllers();
|
|
builder.Services.AddOpenApiDocument(config =>
|
|
{
|
|
config.Title = "AccountTracking API";
|
|
config.Version = "v1";
|
|
config.AddSecurity("Bearer", new NSwag.OpenApiSecurityScheme
|
|
{
|
|
Type = NSwag.OpenApiSecuritySchemeType.Http,
|
|
Scheme = "bearer",
|
|
BearerFormat = "JWT"
|
|
});
|
|
});
|
|
|
|
// ── Kestrel: limit upload to 10 MB ─────────────────────────────────────────
|
|
builder.WebHost.ConfigureKestrel(opts =>
|
|
opts.Limits.MaxRequestBodySize = 10 * 1024 * 1024);
|
|
|
|
var app = builder.Build();
|
|
|
|
// ── Migrate DB (skip when running without a real DB) ───────────────────────
|
|
if (!string.IsNullOrEmpty(connectionString))
|
|
{
|
|
var retryPolicy = Policy
|
|
.Handle<Exception>()
|
|
.WaitAndRetry(
|
|
5,
|
|
attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt - 1)),
|
|
(ex, delay, attempt, _) =>
|
|
Console.WriteLine($"DB migration attempt {attempt} failed: {ex.Message}. Retrying in {delay.TotalSeconds}s..."));
|
|
|
|
using var scope = app.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
retryPolicy.Execute(() => db.Database.Migrate());
|
|
}
|
|
|
|
// ── Middleware pipeline ─────────────────────────────────────────────────────
|
|
app.UseCors();
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
app.UseOpenApi();
|
|
app.UseSwaggerUi();
|
|
app.MapControllers();
|
|
|
|
app.Run();
|
|
|
|
// Accessible by tests
|
|
public partial class Program { }
|
|
|
|
// Credential container registered in DI
|
|
public record AppCredentials(string Username, string PasswordHash, byte[] JwtKeyBytes);
|