diff --git a/GermanApp/GermanApp.csproj b/GermanApp/GermanApp.csproj
index da9d6b3..e71a865 100644
--- a/GermanApp/GermanApp.csproj
+++ b/GermanApp/GermanApp.csproj
@@ -19,6 +19,10 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/GermanApp/Program.cs b/GermanApp/Program.cs
index d672186..014fb9c 100644
--- a/GermanApp/Program.cs
+++ b/GermanApp/Program.cs
@@ -5,88 +5,137 @@ 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;
-var builder = WebApplication.CreateBuilder(args);
+// Configure Serilog
+Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .WriteTo.Console()
+ .CreateBootstrapLogger();
-// Add services to the container.
-// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
-builder.Services.AddOpenApi();
-
-// ============================================
-// INFRASTRUCTURE LAYER - Database & External Services
-// ============================================
-
-// Add DbContext with PostgreSQL
-builder.Services.AddDbContext(options =>
+try
{
- // 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())
+ 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();
+
+ // Configure CORS
+ builder.Services.AddCors(options =>
{
- options.EnableSensitiveDataLogging();
- options.EnableDetailedErrors();
+ 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(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();
+
+ // ============================================
+ // APPLICATION LAYER - Use Cases & Services
+ // ============================================
+
+ // Register command handlers
+ builder.Services.AddScoped, 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();
}
-});
-// Register repositories (Infrastructure implementations of Domain interfaces)
-builder.Services.AddScoped();
+ // Use CORS
+ app.UseCors("AllowAll");
-// ============================================
-// APPLICATION LAYER - Use Cases & Services
-// ============================================
+ // Use Health Checks
+ app.MapHealthChecks("/health");
-// Register command handlers
-builder.Services.AddScoped, CreateLessonCommandHandler>();
+ // Seed database with initial data
+ app.SeedDatabase();
-// ============================================
-// PRESENTATION LAYER - API
-// ============================================
+ // Map Clean Architecture endpoints
+ app.MapLessonsEndpoints();
-// Add services for Minimal APIs
-builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
-
-var app = builder.Build();
-
-// Configure the HTTP request pipeline.
-if (app.Environment.IsDevelopment())
-{
- app.MapOpenApi();
- app.UseSwagger();
- app.UseSwaggerUI();
-}
-
-// 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[]
+ // Keep original WeatherForecast endpoint for reference
+ app.MapGet("/weatherforecast", () =>
{
- "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");
+ 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();
+ 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)
diff --git a/GermanApp/Shared/Middleware/ExceptionMiddleware.cs b/GermanApp/Shared/Middleware/ExceptionMiddleware.cs
new file mode 100644
index 0000000..bc25881
--- /dev/null
+++ b/GermanApp/Shared/Middleware/ExceptionMiddleware.cs
@@ -0,0 +1,62 @@
+using System.Net;
+using System.Text.Json;
+using GermanApp.Shared.Models;
+
+namespace GermanApp.Shared.Middleware;
+
+///
+/// Global exception handling middleware.
+/// Catches exceptions and returns consistent error responses.
+///
+public class ExceptionMiddleware
+{
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+
+ public ExceptionMiddleware(RequestDelegate next, ILogger logger)
+ {
+ _next = next;
+ _logger = logger;
+ }
+
+ public async Task InvokeAsync(HttpContext httpContext)
+ {
+ try
+ {
+ await _next(httpContext);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Unhandled exception occurred: {Message}", ex.Message);
+ await HandleExceptionAsync(httpContext, ex);
+ }
+ }
+
+ private static Task HandleExceptionAsync(HttpContext context, Exception exception)
+ {
+ context.Response.ContentType = "application/json";
+ context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+
+ var response = new ApiErrorResponse(
+ "An unexpected error occurred. Please try again later.",
+ context.Response.StatusCode,
+ exception.Message
+ );
+
+ var options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+
+ return context.Response.WriteAsync(JsonSerializer.Serialize(response, options));
+ }
+}
+
+///
+/// Extension method to add exception middleware.
+///
+public static class ExceptionMiddlewareExtensions
+{
+ public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder builder) =>
+ builder.UseMiddleware();
+}
diff --git a/GermanApp/Shared/Models/ApiResponse.cs b/GermanApp/Shared/Models/ApiResponse.cs
new file mode 100644
index 0000000..a496c52
--- /dev/null
+++ b/GermanApp/Shared/Models/ApiResponse.cs
@@ -0,0 +1,47 @@
+namespace GermanApp.Shared.Models;
+
+///
+/// Base API response wrapper for consistent API responses.
+///
+/// The data type of the response
+public record ApiResponse(T Data, string Message = "Success", bool Success = true)
+{
+ ///
+ /// Creates a successful response.
+ ///
+ public static ApiResponse Ok(T data, string message = "Success") =>
+ new ApiResponse(data, message, true);
+}
+
+///
+/// API error response for consistent error handling.
+///
+public record ApiErrorResponse(string Message, int StatusCode, string? Details = null)
+{
+ public bool Success => false;
+
+ ///
+ /// Creates an error response.
+ ///
+ public static ApiErrorResponse Error(string message, int statusCode, string? details = null) =>
+ new ApiErrorResponse(message, statusCode, details);
+}
+
+///
+/// Paginated API response.
+///
+/// The data type of the items
+public record PaginatedResponse(IEnumerable Items, int PageNumber, int PageSize, int TotalCount, int TotalPages)
+{
+ ///
+ /// Creates a paginated response.
+ ///
+ public static PaginatedResponse Create(IEnumerable items, int pageNumber, int pageSize, int totalCount) =>
+ new PaginatedResponse(
+ items,
+ pageNumber,
+ pageSize,
+ totalCount,
+ (int)Math.Ceiling(totalCount / (double)pageSize)
+ );
+}