Program.cs 4.25 KB
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
using Microsoft.IdentityModel.Tokens;
using RobotProductionSystem.Api.Endpoints;
using RobotProductionSystem.Api.Infrastructure;
using RobotProductionSystem.Api.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));
var jwtOptions = builder.Configuration.GetSection("Jwt").Get<JwtOptions>() ?? new JwtOptions();

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddScoped<JwtTokenService>();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = jwtOptions.Issuer,
            ValidAudience = jwtOptions.Audience,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.Secret)),
            ClockSkew = TimeSpan.FromSeconds(30)
        };
    });
builder.Services.AddAuthorization();
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    options.SerializerOptions.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.Never;
});
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
var configuredUploadsRoot = builder.Configuration["FileStorage:UploadsRoot"]?.Trim();
var uploadsRoot = string.IsNullOrWhiteSpace(configuredUploadsRoot)
    ? Path.Combine(app.Environment.ContentRootPath, "uploads")
    : Path.GetFullPath(configuredUploadsRoot);
Directory.CreateDirectory(uploadsRoot);

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(uploadsRoot),
    RequestPath = "/uploads"
});
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/health", () => new { status = "ok" });
app.MapRobotApi();

await using (var scope = app.Services.CreateAsyncScope())
{
    var logger = scope.ServiceProvider
        .GetRequiredService<ILoggerFactory>()
        .CreateLogger("DatabaseStartup");
    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    var maxRetries = builder.Configuration.GetValue<int?>("Database:Migration:MaxRetries") ?? 10;
    var retryDelaySeconds = builder.Configuration.GetValue<int?>("Database:Migration:RetryDelaySeconds") ?? 3;

    for (var attempt = 1; ; attempt++)
    {
        try
        {
            logger.LogInformation("Starting database migration and seed.");

            var pendingMigrations = (await db.Database.GetPendingMigrationsAsync()).ToArray();
            if (pendingMigrations.Length > 0)
            {
                logger.LogInformation(
                    "Pending migrations detected ({Count}): {Migrations}",
                    pendingMigrations.Length,
                    string.Join(", ", pendingMigrations));
            }
            else
            {
                logger.LogInformation("No pending migrations detected.");
            }

            await db.Database.MigrateAsync();
            await SeedData.InitializeAsync(db);

            logger.LogInformation("Database migration and seed completed successfully.");
            break;
        }
        catch (Exception ex) when (attempt < maxRetries)
        {
            logger.LogWarning(
                ex,
                "Database initialization failed on attempt {Attempt}/{MaxRetries}. Retrying in {RetryDelaySeconds}s.",
                attempt,
                maxRetries,
                retryDelaySeconds);
            await Task.Delay(TimeSpan.FromSeconds(retryDelaySeconds));
        }
    }
}

await app.RunAsync();