// --- 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.Reflection; 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; using Webshop.Infrastructure.Services; var options = new WebApplicationOptions { Args = args, // Diese Zeile setzt den WebRootPath explizit auf den wwwroot-Ordner. WebRootPath = "wwwroot" }; var builder = WebApplication.CreateBuilder(options); var allowedOrigins = new string[] { "https://shopsolution-frontend.tzbre.dev", "http://localhost:4200" }; builder.Services.AddCors(options => { options.AddPolicy(name: "AllowSpecificOrigins", policy => { policy.WithOrigins(allowedOrigins) .AllowAnyHeader() .AllowAnyMethod(); }); }); // --- 1. Dependency Injection Konfiguration --- // Datenbank-Kontext mit Split-Query-Verhalten für Performance-Optimierung builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"), o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)) ); // ASP.NET Core Identity mit ApplicationUser builder.Services.AddIdentity(options => { // In Produktion auf 'true' setzen, für Entwicklung ist 'false' einfacher. options.SignIn.RequireConfirmedEmail = false; }) .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(); // Infrastruktur & Externe Dienste builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.Configure(options => { options.ApiToken = builder.Configuration["Resend:ApiToken"]!; }); builder.Services.AddTransient(); // 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(); 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(); 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(); // Controller und API-Infrastruktur builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); options.JsonSerializerOptions.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull; }); builder.Services.AddEndpointsApiExplorer(); // Swagger / OpenAPI Konfiguration builder.Services.AddSwaggerGen(c => { c.TagActionsBy(api => { var controllerName = api.ActionDescriptor.RouteValues["controller"]; if (controllerName == null) return new[] { "Default" }; 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 }; }); c.OrderActionsBy(apiDesc => { var relativePath = apiDesc.RelativePath ?? ""; if (relativePath.StartsWith("api/v1/auth")) return "1"; if (relativePath.StartsWith("api/v1/public")) return "2"; if (relativePath.StartsWith("api/v1/customer")) return "3"; if (relativePath.StartsWith("api/v1/admin")) return "4"; return "5"; }); 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.SchemaFilter(); c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); var apiXmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var apiXmlPath = Path.Combine(AppContext.BaseDirectory, apiXmlFile); c.IncludeXmlComments(apiXmlPath); var appXmlFile = "Webshop.Application.xml"; // Der Name der DLL des Application-Projekts + .xml var appXmlPath = Path.Combine(AppContext.BaseDirectory, appXmlFile); if (File.Exists(appXmlPath)) { c.IncludeXmlComments(appXmlPath); } }); var app = builder.Build(); // --- 2. HTTP Request Pipeline Konfiguration --- // 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(); // 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 createAdminResult = await userManager.CreateAsync(adminUser, "SecureAdminPass123!"); if (createAdminResult.Succeeded) { await userManager.AddToRoleAsync(adminUser, "Admin"); Console.WriteLine("INFO: Admin user created successfully."); } else { Console.WriteLine($"ERROR: Could not create admin user. Errors: {string.Join(", ", createAdminResult.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 createCustomerResult = await userManager.CreateAsync(customerUser, "SecureCustomerPass123!"); if (createCustomerResult.Succeeded) { await userManager.AddToRoleAsync(customerUser, "Customer"); Console.WriteLine("INFO: Customer user created successfully."); 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("INFO: Customer profile created for new customer user."); } } else { Console.WriteLine($"ERROR: Could not create customer user. Errors: {string.Join(", ", createCustomerResult.Errors.Select(e => e.Description))}"); } } } catch (Exception ex) { var logger = services.GetRequiredService>(); logger.LogError(ex, "FATAL: An error occurred during database migration or user initialization."); } } // Middleware für Reverse-Proxies app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); app.UseCors("AllowSpecificOrigins"); // Statische Dateien aus wwwroot bereitstellen (z.B. /uploads/xyz.jpg) app.UseStaticFiles(); // 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(); //zuletzt überarbeitet categories und discounds fehlermeldungen und results. next is order //new image build