using System.Reflection; using Extrudex.API.Filters; using Extrudex.API.Hubs; using Extrudex.API.Jobs; using Extrudex.Domain.Interfaces; using Extrudex.Infrastructure.Configuration; using Extrudex.Infrastructure.Data; using Extrudex.Infrastructure.Services; using FluentValidation; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); // ── Database ─────────────────────────────────────────────── // Connection string resolution (highest priority first): // 1. EXTRUDEX_DB_CONNECTION_STRING env var (Docker / production) // 2. Individual env vars: EXTRUDEX_DB_HOST, EXTRUDEX_DB_PORT, etc. // 3. appsettings.json ConnectionStrings:ExtrudexDb // 4. Hardcoded default for local dev var connectionString = Environment.GetEnvironmentVariable("EXTRUDEX_DB_CONNECTION_STRING") ?? BuildConnectionStringFromEnvVars() ?? builder.Configuration.GetConnectionString("ExtrudexDb") ?? "Host=localhost;Port=5432;Database=extrudex;Username=extrudex;Password=changeme"; builder.Services.AddDbContext(options => options.UseNpgsql(connectionString)); // ── API Services ─────────────────────────────────────────── builder.Services.AddControllers(options => { options.Filters.AddService(); }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new() { Title = "Extrudex API", Version = "v1", Description = "Filament inventory and print tracking system" }); // Include XML doc comments in Swagger output var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); if (File.Exists(xmlPath)) { c.IncludeXmlComments(xmlPath); } }); // ── QR Code Generation ────────────────────────────────────── builder.Services.AddSingleton(); // ── Cost Per Print Calculation ───────────────────────────── builder.Services.AddScoped(); // ── Low Stock Detection ──────────────────────────────────── builder.Services.AddSingleton(); // ── Usage Logging ─────────────────────────────────────────── builder.Services.AddScoped(); // ── Filament Usage Service ────────────────────────────────── builder.Services.AddScoped(); // ── Moonraker Usage Poller (Background Service) ───────────── builder.Services.Configure( builder.Configuration.GetSection("MoonrakerPoller")); builder.Services.AddHostedService(); // ── FluentValidation ────────────────────────────────────── // Registers all validators from the API assembly into DI. builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); // Register the FluentValidation action filter so validators run automatically // on all API controller actions before the action executes. builder.Services.AddScoped(); // ── CORS (kiosk + remote browser) ───────────────────────── // AllowAnyOrigin disallows credentials by spec; this is fine for // REST API calls. SignalR WebSockets negotiate without credentials // by default, so no special CORS policy is needed. If browser clients // require credentials (cookies, auth headers), replace AllowAnyOrigin // with .WithOrigins(...) and add .AllowCredentials(). builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); // ── SignalR (real-time printer updates) ──────────────────── builder.Services.AddSignalR(); // ── Filament Usage Sync (Background Job) ────────────────── builder.Services.Configure( builder.Configuration.GetSection(FilamentUsageSyncOptions.SectionName)); builder.Services.AddHttpClient(client => { client.DefaultRequestHeaders.Add("User-Agent", "Extrudex/1.0"); }); builder.Services.AddScoped(); builder.Services.AddHostedService(); // ── Moonraker Printer Sync (Background Service) ────────── builder.Services.Configure( builder.Configuration.GetSection(MoonrakerPrinterSyncOptions.SectionName)); builder.Services.AddScoped(); builder.Services.AddHostedService(); // ── Health Checks ─────────────────────────────────────────── builder.Services.AddHealthChecks() .AddNpgSql(connectionString); var app = builder.Build(); // ── Middleware ────────────────────────────────────────────── if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseCors(); app.UseAuthorization(); app.MapControllers(); // ── Hub Endpoints ─────────────────────────────────────────── app.MapHub("/hubs/printer"); // ── Health Check Endpoint ────────────────────────────────── app.MapHealthChecks("/health"); app.Run(); // Helper: builds a connection string from individual env vars. // Returns null if EXTRUDEX_DB_HOST is not set. static string? BuildConnectionStringFromEnvVars() { var host = Environment.GetEnvironmentVariable("EXTRUDEX_DB_HOST"); if (string.IsNullOrEmpty(host)) return null; var port = Environment.GetEnvironmentVariable("EXTRUDEX_DB_PORT") ?? "5432"; var database = Environment.GetEnvironmentVariable("EXTRUDEX_DB_NAME") ?? "extrudex"; var username = Environment.GetEnvironmentVariable("EXTRUDEX_DB_USER") ?? "extrudex"; var password = Environment.GetEnvironmentVariable("EXTRUDEX_DB_PASSWORD") ?? "changeme"; return $"Host={host};Port={port};Database={database};Username={username};Password={password}"; }