using AVSCartonShipmentVerifier.Configuration; using AVSCartonShipmentVerifier.Data; using AVSCartonShipmentVerifier.Hosting; using AVSCartonShipmentVerifier.Repositories; using AVSCartonShipmentVerifier.ScanHistory; using AVSCartonShipmentVerifier.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.FileProviders; var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true); builder.Services .AddOptions() .Bind(builder.Configuration.GetSection(CartonVerificationOptions.SectionName)) .ValidateDataAnnotations() .ValidateOnStart(); builder.Services .AddOptions() .Bind(builder.Configuration.GetSection(ScanHistoryOptions.SectionName)) .ValidateDataAnnotations() .ValidateOnStart(); builder.Services .AddOptions() .Bind(builder.Configuration.GetSection(DesktopHostOptions.SectionName)); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddControllersWithViews(); builder.Services.AddDbContext((serviceProvider, options) => { var scanHistoryOptions = serviceProvider.GetRequiredService>().Value; var connectionString = ScanHistoryPath.EnsureDirectoryAndGetConnectionString(scanHistoryOptions); options.UseSqlite(connectionString); }); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); var app = builder.Build(); using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); db.Database.EnsureCreated(); // EnsureCreated does not add new tables to an existing SQLite file; create rejected-scan storage explicitly. db.Database.ExecuteSqlRaw( """ CREATE TABLE IF NOT EXISTS rejected_scan_entries ( Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, RawInputValue TEXT NOT NULL, ModelNumber TEXT NOT NULL, UniqueNumber TEXT NOT NULL, RejectionReason TEXT NOT NULL, ProcessedAtUtc TEXT NOT NULL ); """); db.Database.ExecuteSqlRaw( """ CREATE INDEX IF NOT EXISTS IX_rejected_scan_entries_ProcessedAtUtc ON rejected_scan_entries (ProcessedAtUtc); """); db.Database.OpenConnection(); try { var hasMovedAtColumn = false; using (var command = db.Database.GetDbConnection().CreateCommand()) { command.CommandText = "PRAGMA table_info(scan_history_entries);"; using var reader = command.ExecuteReader(); while (reader.Read()) { if (string.Equals(reader.GetString(1), "MovedToWarehouseAtUtc", StringComparison.OrdinalIgnoreCase)) { hasMovedAtColumn = true; break; } } } if (!hasMovedAtColumn) { db.Database.ExecuteSqlRaw( """ ALTER TABLE scan_history_entries ADD COLUMN MovedToWarehouseAtUtc TEXT NULL; """); } } finally { db.Database.CloseConnection(); } } if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } if (KestrelConfiguration.HasHttpsEndpoint(app.Configuration)) { app.UseHttpsRedirection(); } app.UseRouting(); app.UseAuthorization(); app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "Resources")), RequestPath = "/resources" }); app.MapStaticAssets(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}") .WithStaticAssets(); app.Run();