Files
Extrudex/backend/Program.cs

153 lines
7.0 KiB
C#
Raw Normal View History

2026-04-25 18:51:05 +00:00
using System.Reflection;
using Extrudex.API.Filters;
2026-04-25 18:51:05 +00:00
using Extrudex.API.Hubs;
using Extrudex.API.Jobs;
2026-04-25 18:51:05 +00:00
using Extrudex.Domain.Interfaces;
using Extrudex.Infrastructure.Configuration;
2026-04-25 18:51:05 +00:00
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<ExtrudexDbContext>(options =>
options.UseNpgsql(connectionString));
// ── API Services ───────────────────────────────────────────
builder.Services.AddControllers(options =>
{
options.Filters.AddService<FluentValidationFilter>();
});
2026-04-25 18:51:05 +00:00
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<IQrCodeService, QrCodeService>();
// ── Cost Per Print Calculation ─────────────────────────────
builder.Services.AddScoped<ICostPerPrintService, CostPerPrintService>();
// ── Low Stock Detection ────────────────────────────────────
builder.Services.AddSingleton<ILowStockDetector, LowStockDetector>();
// ── Usage Logging ───────────────────────────────────────────
builder.Services.AddScoped<IUsageLogService, UsageLogService>();
// ── Filament Usage Service ──────────────────────────────────
builder.Services.AddScoped<IFilamentUsageService, FilamentUsageService>();
// ── Moonraker Usage Poller (Background Service) ─────────────
builder.Services.Configure<MoonrakerPollerOptions>(
builder.Configuration.GetSection("MoonrakerPoller"));
builder.Services.AddHostedService<MoonrakerUsagePoller>();
2026-04-25 18:51:05 +00:00
// ── 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<FluentValidationFilter>();
2026-04-25 18:51:05 +00:00
// ── 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<FilamentUsageSyncOptions>(
builder.Configuration.GetSection(FilamentUsageSyncOptions.SectionName));
builder.Services.AddHttpClient<IMoonrakerClient, MoonrakerClient>(client =>
{
client.DefaultRequestHeaders.Add("User-Agent", "Extrudex/1.0");
});
builder.Services.AddScoped<IFilamentUsageSyncService, FilamentUsageSyncService>();
builder.Services.AddHostedService<FilamentUsageSyncJob>();
// ── Moonraker Printer Sync (Background Service) ──────────
builder.Services.Configure<MoonrakerPrinterSyncOptions>(
builder.Configuration.GetSection(MoonrakerPrinterSyncOptions.SectionName));
builder.Services.AddScoped<IMoonrakerPrinterSyncService, MoonrakerPrinterSyncService>();
builder.Services.AddHostedService<MoonrakerPrinterSyncJob>();
// ── Health Checks ───────────────────────────────────────────
builder.Services.AddHealthChecks()
.AddNpgSql(connectionString);
2026-04-25 18:51:05 +00:00
var app = builder.Build();
// ── Middleware ──────────────────────────────────────────────
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors();
app.UseAuthorization();
app.MapControllers();
// ── Hub Endpoints ───────────────────────────────────────────
app.MapHub<PrinterHub>("/hubs/printer");
// ── Health Check Endpoint ──────────────────────────────────
app.MapHealthChecks("/health");
2026-04-25 18:51:05 +00:00
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}";
}