// --- Core Frameworks & Libraries --- using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Resend; using System.Text; using System.Text.Json.Serialization; // --- Eigene Namespaces --- using Webshop.Api.SwaggerFilters; using Webshop.Application.Services.Admin; using Webshop.Application.Services.Admin.Interfaces; using Webshop.Application.Services.Auth; using Webshop.Application.Services.Customers; using Webshop.Application.Services.Customers.Interfaces; using Webshop.Application.Services.Public; using Webshop.Application.Services.Public.Interfaces; using Webshop.Domain.Entities; using Webshop.Domain.Identity; using Webshop.Domain.Interfaces; using Webshop.Infrastructure.Data; using Webshop.Infrastructure.Repositories; var builder = WebApplication.CreateBuilder(args); // --- 1. Dependency Injection Konfiguration --- // Datenbank-Kontext builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")) ); // ASP.NET Core Identity mit ApplicationUser builder.Services.AddIdentity(options => { options.SignIn.RequireConfirmedEmail = true; }) .AddEntityFrameworkStores() .AddDefaultTokenProviders(); // Passwort-Anforderungen für die Entwicklung lockern builder.Services.Configure(options => { options.Password.RequireDigit = false; options.Password.RequiredLength = 6; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequireLowercase = false; }); // JWT-Authentifizierung var jwtSettings = builder.Configuration.GetSection("JwtSettings"); var secretKey = jwtSettings["Secret"] ?? throw new InvalidOperationException("JWT Secret not found in configuration."); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtSettings["Issuer"], ValidAudience = jwtSettings["Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)) }; }); builder.Services.AddAuthorization(); // Repositories builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Services builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Externe Dienste (Resend) builder.Services.AddHttpClient(); builder.Services.Configure(options => { options.ApiToken = builder.Configuration["Resend:ApiToken"]!; }); builder.Services.AddTransient(); // Controller und API-Infrastruktur builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); builder.Services.AddEndpointsApiExplorer(); // Swagger / OpenAPI Konfiguration builder.Services.AddSwaggerGen(c => { // --- 1. Gruppierung der Endpunkte (visuelle Tags) --- c.TagActionsBy(api => { var controllerName = api.ActionDescriptor.RouteValues["controller"]; if (controllerName == null) return new[] { "Default" }; // Bereinige den Controller-Namen für einen sauberen Tag var tag = controllerName.Replace("Admin", "").Replace("Controller", ""); if (api.RelativePath.StartsWith("api/v1/auth")) { return new[] { "Auth" }; } if (api.RelativePath.StartsWith("api/v1/admin")) { return new[] { $"Admin - {tag}" }; } if (api.RelativePath.StartsWith("api/v1/customer")) { return new[] { $"Customer - {tag}" }; } if (api.RelativePath.StartsWith("api/v1/public")) { return new[] { $"Public - {tag}" }; } return new[] { tag }; }); // --- NEU: Sortierung der Gruppen in der UI --- c.OrderActionsBy(apiDesc => { var relativePath = apiDesc.RelativePath ?? ""; // Die Reihenfolge der if-Bedingungen HIER ist entscheidend für die Sortierung! if (relativePath.StartsWith("api/v1/auth")) return "1"; // Auth ganz oben if (relativePath.StartsWith("api/v1/public")) return "2"; // Public als nächstes if (relativePath.StartsWith("api/v1/customer")) return "3"; // Customer danach if (relativePath.StartsWith("api/v1/admin")) return "4"; // Admin als letztes return "5"; // Fallback }); // --- 2. JWT Security Konfiguration --- c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT Authorization header. Example: \"Authorization: Bearer {token}\"", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.Http, Scheme = "Bearer" }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, new string[] {} } }); c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); c.SchemaFilter(); }); // --- 2. HTTP Request Pipeline Konfiguration --- var app = builder.Build(); // Führe Datenbank-Migrationen und Initialisierungslogik beim Start aus using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; try { var context = services.GetRequiredService(); context.Database.Migrate(); // TEMPORÄRER INITIALER ADMIN- UND KUNDEN-SETUP // HINWEIS: Dieser Block sollte in der Produktion entfernt oder deaktiviert werden. var roleManager = services.GetRequiredService>(); var userManager = services.GetRequiredService>(); string[] roleNames = { "Admin", "Customer" }; foreach (var roleName in roleNames) { if (!await roleManager.RoleExistsAsync(roleName)) { await roleManager.CreateAsync(new IdentityRole(roleName)); } } var adminUser = await userManager.FindByEmailAsync("admin@yourwebshop.com"); if (adminUser == null) { adminUser = new ApplicationUser { UserName = "admin@yourwebshop.com", Email = "admin@yourwebshop.com", EmailConfirmed = true, CreatedDate = DateTimeOffset.UtcNow, LastActive = DateTimeOffset.UtcNow }; var createAdmin = await userManager.CreateAsync(adminUser, "SecureAdminPass123!"); if (createAdmin.Succeeded) { await userManager.AddToRoleAsync(adminUser, "Admin"); Console.WriteLine("Admin user created."); } else { Console.WriteLine($"Error creating admin user: {string.Join(", ", createAdmin.Errors.Select(e => e.Description))}"); } } var customerUser = await userManager.FindByEmailAsync("customer@yourwebshop.com"); if (customerUser == null) { customerUser = new ApplicationUser { UserName = "customer@yourwebshop.com", Email = "customer@yourwebshop.com", EmailConfirmed = true, CreatedDate = DateTimeOffset.UtcNow, LastActive = DateTimeOffset.UtcNow }; var createCustomer = await userManager.CreateAsync(customerUser, "SecureCustomerPass123!"); if (createCustomer.Succeeded) { await userManager.AddToRoleAsync(customerUser, "Customer"); Console.WriteLine("Customer user created."); if (!await context.Customers.AnyAsync(c => c.AspNetUserId == customerUser.Id)) { var customerProfile = new Customer { AspNetUserId = customerUser.Id, FirstName = "Test", LastName = "Kunde" }; context.Customers.Add(customerProfile); await context.SaveChangesAsync(); Console.WriteLine("Customer profile created for new customer user."); } } else { Console.WriteLine($"Error creating customer user: {string.Join(", ", createCustomer.Errors.Select(e => e.Description))}"); } } } catch (Exception ex) { var logger = services.GetRequiredService>(); logger.LogError(ex, "An error occurred during database migration or user initialization."); } } // Middleware für Reverse-Proxies app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); // Swagger/SwaggerUI für API-Dokumentation aktivieren //if (app.Environment.IsDevelopment()) //{ app.UseSwagger(); app.UseSwaggerUI(); //} // app.UseHttpsRedirection(); // Auskommentiert für Docker HTTP-Entwicklung // WICHTIG: Die Reihenfolge ist entscheidend! app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();