using System.Text; using BimAI.API.Services; using BimAI.Infrastructure.Data; using BimAI.Infrastructure.Jobs; using BimAI.Infrastructure.Sync; using Hangfire; using Hangfire.SqlServer; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.AddControllers(); // Start Hangfire section builder.Services.AddHangfire(configuration => configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions { CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), QueuePollInterval = TimeSpan.Zero, UseRecommendedIsolationLevel = true, DisableGlobalLocks = true, SchemaName = "Hangfire" } ) ); builder.Services.AddHangfireServer(options => { options.ServerName = builder.Configuration["Hangfire:ServerName"]; options.WorkerCount = builder.Configuration.GetValue("Hangfire:WorkerCount", 5); }); // End Hangfire section // Start auth section var jwtSettings = builder.Configuration.GetSection("JwtSettings"); var secretKey = jwtSettings["SecretKey"]; var issuer = jwtSettings["Issuer"]; var audience = jwtSettings["Audience"]; builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = issuer, ValidAudience = audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)), ClockSkew = TimeSpan.Zero, }; }); builder.Services.AddAuthentication(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddCors(options => { options.AddPolicy("AllowAll", policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); // End auth section var app = builder.Build(); // Auto-apply migrations on startup using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); try { var pending = await db.Database.GetPendingMigrationsAsync(); if (pending.Any()) { app.Logger.LogWarning("Applying {Count} pending migrations: {List}", pending.Count(), string.Join(", ", pending)); await db.Database.MigrateAsync(); app.Logger.LogInformation("Migrations applied successfully."); } else { app.Logger.LogInformation("No pending migrations."); } } catch (Exception ex) { app.Logger.LogCritical(ex, "Migration failed - application will not start."); throw; // stop startup } } app.UseHttpsRedirection(); app.UseCors("AllowAll"); app.UseHangfireDashboard(builder.Configuration["Hangfire:DashboardPath"] ?? "/hangfire", new DashboardOptions { AsyncAuthorization = new[] { new HangfireAuthorizationFilter() }, DashboardTitle = "BimAI - Job Dashboard" }); app.UseAuthorization(); app.UseAuthorization(); app.MapControllers(); app.MapGet("/health", () => Results.Ok(new { status = "OK", timestamp = DateTime.UtcNow })) .AllowAnonymous(); RecurringJob.AddOrUpdate( "product-sync", job => job.ExecuteAsync(), Cron.Daily(2, 0), // Every day at 2:00 AM new RecurringJobOptions { TimeZone = TimeZoneInfo.Local, MisfireHandling = app.Environment.IsDevelopment() ? MisfireHandlingMode.Relaxed : MisfireHandlingMode.Strict }); app.Run();