DeutschLernen/GermanApp/Program.cs
Lasse Rune Hansen 4ee2b2568a feat(backend/infra): add health checks, CORS, Serilog logging, and exception middleware
- Add Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore package
- Add Serilog.AspNetCore and Serilog.Sinks.Console packages
- Configure Serilog with bootstrap logger and configuration
- Add Health Checks endpoint at /health with DbContext check
- Configure CORS with AllowAll policy for development
- Create ApiResponse, ApiErrorResponse, and PaginatedResponse models
- Create ExceptionMiddleware for global error handling
- Restructure Program.cs with try-catch-finally for proper logging

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-05-31 19:52:00 +02:00

144 lines
4.1 KiB
C#

using GermanApp.Application.DTOs;
using GermanApp.Application.UseCases.Commands;
using GermanApp.Domain.Interfaces;
using GermanApp.Infrastructure.Data.DbContext;
using GermanApp.Infrastructure.Data.Repositories;
using GermanApp.Infrastructure.Data.SeedData;
using GermanApp.Presentation.Endpoints;
using GermanApp.Shared.Middleware;
using Microsoft.EntityFrameworkCore;
using Serilog;
// Configure Serilog
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateBootstrapLogger();
try
{
var builder = WebApplication.CreateBuilder(args);
// Add Serilog to the builder
builder.Host.UseSerilog((ctx, lc) => lc
.MinimumLevel.Debug()
.WriteTo.Console()
.ReadFrom.Configuration(ctx.Configuration));
// ============================================
// PRESENTATION LAYER - API Configuration
// ============================================
// Add services to the container.
builder.Services.AddOpenApi();
// Add Health Checks
builder.Services.AddHealthChecks()
.AddDbContextCheck<AppDbContext>();
// Configure CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
// Add services for Minimal APIs
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// ============================================
// INFRASTRUCTURE LAYER - Database & External Services
// ============================================
// Add DbContext with PostgreSQL
builder.Services.AddDbContext<AppDbContext>(options =>
{
// Using PostgreSQL for production and development
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")
?? "Host=localhost;Port=5432;Database=DeutschLernen;Username=postgres;Password=postgres");
// Enable sensitive data logging in development
if (builder.Environment.IsDevelopment())
{
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
}
});
// Register repositories (Infrastructure implementations of Domain interfaces)
builder.Services.AddScoped<ILessonRepository, LessonRepository>();
// ============================================
// APPLICATION LAYER - Use Cases & Services
// ============================================
// Register command handlers
builder.Services.AddScoped<ICommandHandler<CreateLessonCommand, LessonDto>, CreateLessonCommandHandler>();
var app = builder.Build();
// Configure the HTTP request pipeline.
// Use exception middleware first (to catch all exceptions)
app.UseExceptionMiddleware();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwagger();
app.UseSwaggerUI();
}
// Use CORS
app.UseCors("AllowAll");
// Use Health Checks
app.MapHealthChecks("/health");
// Seed database with initial data
app.SeedDatabase();
// Map Clean Architecture endpoints
app.MapLessonsEndpoints();
// Keep original WeatherForecast endpoint for reference
app.MapGet("/weatherforecast", () =>
{
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
// Existing record for reference
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}