diff --git a/Webshop.Api/Program.cs b/Webshop.Api/Program.cs index 3066767..5902651 100644 --- a/Webshop.Api/Program.cs +++ b/Webshop.Api/Program.cs @@ -1,45 +1,47 @@ +// --- Core Frameworks & Libraries --- using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.HttpOverrides; // For UseForwardedHeaders +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; // For ILogger using Microsoft.IdentityModel.Tokens; -using Microsoft.OpenApi.Models; // For Swagger OpenAPI models +using Microsoft.OpenApi.Models; using Resend; using System.Text; using System.Text.Json.Serialization; -using Webshop.Api.SwaggerFilters; // For AuthorizeOperationFilter -using Webshop.Application.Services.Admin; // AdminUserService, AdminProductService + +// --- Eigene Namespaces --- +using Webshop.Api.SwaggerFilters; +using Webshop.Application.Services.Admin; using Webshop.Application.Services.Admin.Interfaces; -using Webshop.Application.Services.Auth; // IAuthService, AuthService +using Webshop.Application.Services.Auth; using Webshop.Application.Services.Customers; using Webshop.Application.Services.Customers.Interfaces; -using Webshop.Application.Services.Public; // ProductService +using Webshop.Application.Services.Public; using Webshop.Application.Services.Public.Interfaces; using Webshop.Domain.Entities; using Webshop.Domain.Identity; -using Webshop.Domain.Interfaces; // IProductRepository -using Webshop.Infrastructure.Data; // ApplicationDbContext -using Webshop.Infrastructure.Repositories; // ProductRepository +using Webshop.Domain.Interfaces; +using Webshop.Infrastructure.Data; +using Webshop.Infrastructure.Repositories; var builder = WebApplication.CreateBuilder(args); -// --- START: DIENSTE ZUM CONTAINER HINZUFÜGEN --- +// --- 1. Dependency Injection Konfiguration --- -// 1. Datenbank-Kontext (DbContext) registrieren +// Datenbank-Kontext builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")) ); -// 2. ASP.NET Core Identity für Benutzerverwaltung registrieren +// ASP.NET Core Identity mit ApplicationUser builder.Services.AddIdentity(options => { - options.SignIn.RequireConfirmedAccount = true; + options.SignIn.RequireConfirmedEmail = true; }) -.AddEntityFrameworkStores() // Stellen Sie sicher, dass Ihr DbContext-Name hier korrekt ist +.AddEntityFrameworkStores() .AddDefaultTokenProviders(); -// Optional: Passe die Anforderungen für Passwörter für die Entwicklung an +// Passwort-Anforderungen für die Entwicklung lockern builder.Services.Configure(options => { options.Password.RequireDigit = false; @@ -47,14 +49,11 @@ builder.Services.Configure(options => options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequireLowercase = false; - options.SignIn.RequireConfirmedEmail = true; // Für einfache Entwicklung/Tests }); - -// 3. JWT-Authentifizierung konfigurieren +// 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; @@ -73,9 +72,8 @@ builder.Services.AddAuthentication(options => IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)) }; }); -builder.Services.AddAuthorization(); // Aktiviert die Autorisierung +builder.Services.AddAuthorization(); -// 4. Unsere eigenen Interfaces und Klassen registrieren (Dependency Injection) // Repositories builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -85,114 +83,94 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -// AUTH Services -builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); -// PUBLIC Services +// Services +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); - -// ADMIN Services builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); // Hinzugefügt für Konsistenz +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - -//builder.Services.AddScoped(); // Hinzugefügt für Konsistenz -builder.Services.AddScoped(); // Hinzugefügt für Konsistenz -//builder.Services.AddScoped(); // Hinzugefügt für Konsistenz - -// CUSTOMER Services (später Implementierungen hinzufügen) +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); // Hinzugefügt für Konsistenz +builder.Services.AddScoped(); builder.Services.AddScoped(); -//builder.Services.AddScoped(); // Hinzugefügt für Konsistenz -//builder.Services.AddScoped(); // Hinzugefügt für Konsistenz +builder.Services.AddScoped(); +builder.Services.AddScoped(); -// --- NEU: Resend E-Mail-Dienst konfigurieren --- -builder.Services.AddOptions(); // Stellt sicher, dass Options konfiguriert werden können -builder.Services.AddHttpClient(); // Fügt HttpClient für Resend hinzu +// Externe Dienste (Resend) +builder.Services.AddHttpClient(); builder.Services.Configure(options => { - // Der API-Token kommt aus den Konfigurationen (z.B. appsettings.json oder Umgebungsvariablen) - options.ApiToken = builder.Configuration["Resend:ApiToken"]!; // << ANPASSEN >> + options.ApiToken = builder.Configuration["Resend:ApiToken"]!; }); builder.Services.AddTransient(); - -// 5. Controller und Swagger/OpenAPI hinzufügen +// 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. JWT Security Definition hinzufügen + c.TagActionsBy(api => + { + if (api.RelativePath.StartsWith("api/v1/admin")) return new[] { "Admin" }; + if (api.RelativePath.StartsWith("api/v1/auth")) return new[] { "Auth" }; + if (api.RelativePath.StartsWith("api/v1/customer")) return new[] { "Customer" }; + if (api.RelativePath.StartsWith("api/v1/public")) return new[] { "Public" }; + return new[] { "Default" }; + }); + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { - Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Description = "JWT Authorization header. Example: \"Authorization: Bearer {token}\"", Name = "Authorization", - In = ParameterLocation.Header, // Der Token wird im Header gesendet - Type = SecuritySchemeType.Http, // Dies ist ein HTTP-Schema - Scheme = "Bearer" // Das Schema ist "Bearer" + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = "Bearer" }); - // 2. Security Requirement für alle Operationen hinzufügen - c.AddSecurityRequirement(new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Bearer" // Verweist auf die oben definierte "Bearer" Sicherheit - } - }, - new string[] {} // Keine spezifischen "Scopes" für JWT (leer lassen) - } - }); c.OperationFilter(); - - c.SchemaFilter(); - c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); + c.OperationFilter(); + c.SchemaFilter(); }); -// --- ENDE: DIENSTE ZUM CONTAINER HINZUFÜGEN --- -var app = builder.Build(); // <-- Hier wird die App gebaut +// --- 2. HTTP Request Pipeline Konfiguration --- -// OPTIONALE BLÖCKE FÜR MIGRATION UND BENUTZERINITIALISIERUNG - DIESER CODE WIRD VOR APP.RUN() AUSGEFÜHRT -// OPTIONALE BLÖCKE FÜR MIGRATION UND BENUTZERINITIALISIERUNG - DIESER CODE WIRD VOR APP.RUN() AUSGEFÜHRT +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(); - // Wendet ausstehende Datenbank-Migrationen an (sehr nützlich für Entwicklung/Tests) context.Database.Migrate(); - // --- TEMPORÄRER INITIALER ADMIN- UND KUNDEN-SETUP (NUR FÜR ERSTE ENTWICKLUNG!) --- - // Dieser Block erstellt Rollen und initiale Benutzer, falls sie noch nicht existieren. - // BITTE ENTFERNEN ODER KOMMENTIEREN SIE DIESEN BLOCK AUS, NACHDEM SIE IHRE ERSTEN BENUTZER ERSTELLT HABEN! + // 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>(); // << KORREKT: UserManager für ApplicationUser >> - + var userManager = services.GetRequiredService>(); string[] roleNames = { "Admin", "Customer" }; - foreach (var roleName in roleNames) { if (!await roleManager.RoleExistsAsync(roleName)) @@ -201,102 +179,66 @@ using (var scope = app.Services.CreateScope()) } } - // Erstelle einen initialen Admin-Benutzer und sein Customer-Profil - var adminUser = await userManager.FindByEmailAsync("admin@yourwebshop.com"); // << ANPASSEN >> + var adminUser = await userManager.FindByEmailAsync("admin@yourwebshop.com"); if (adminUser == null) { - adminUser = new ApplicationUser // << KORREKT: ERSTELLT ApplicationUser >> - { - UserName = "admin@yourwebshop.com", // << ANPASSEN >> - Email = "admin@yourwebshop.com", // << ANPASSEN >> - EmailConfirmed = true, - CreatedDate = DateTimeOffset.UtcNow, // Custom Property auf ApplicationUser - LastActive = DateTimeOffset.UtcNow // Custom Property auf ApplicationUser - }; - var createAdmin = await userManager.CreateAsync(adminUser, "SecureAdminPass123!"); // << ANPASSEN >> + 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))}"); - } + else { Console.WriteLine($"Error creating admin user: {string.Join(", ", createAdmin.Errors.Select(e => e.Description))}"); } } - // Erstelle einen initialen Kunden-Benutzer und sein Customer-Profil (KOMBINIERT) - var customerUser = await userManager.FindByEmailAsync("customer@yourwebshop.com"); // << ANPASSEN >> + var customerUser = await userManager.FindByEmailAsync("customer@yourwebshop.com"); if (customerUser == null) { - customerUser = new ApplicationUser // << KORREKT: ERSTELLT ApplicationUser >> - { - UserName = "customer@yourwebshop.com", // << ANPASSEN >> - Email = "customer@yourwebshop.com", // << ANPASSEN >> - EmailConfirmed = true, - CreatedDate = DateTimeOffset.UtcNow, // Custom Property auf ApplicationUser - LastActive = DateTimeOffset.UtcNow // Custom Property auf ApplicationUser - }; - var createCustomer = await userManager.CreateAsync(customerUser, "SecureCustomerPass123!"); // << ANPASSEN >> + 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."); - // Kombinierter Teil: Customer-Profil erstellen, direkt nach IdentityUser-Erstellung - var customerProfile = await context.Customers.FirstOrDefaultAsync(c => c.AspNetUserId == customerUser.Id); // << KORREKT: SUCHT NACH AspNetUserId >> - if (customerProfile == null) + if (!await context.Customers.AnyAsync(c => c.AspNetUserId == customerUser.Id)) { - customerProfile = new Webshop.Domain.Entities.Customer - { - Id = Guid.NewGuid(), - AspNetUserId = customerUser.Id, - FirstName = "Test", - LastName = "Kunde" - }; + 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))}"); - } + else { Console.WriteLine($"Error creating customer user: {string.Join(", ", createCustomer.Errors.Select(e => e.Description))}"); } } - // --- ENDE DES TEMPORÄREN SETUP-BLOCKS --- } catch (Exception ex) { var logger = services.GetRequiredService>(); logger.LogError(ex, "An error occurred during database migration or user initialization."); } -} // <-- Hier endet der using-Scope +} -// --- START: HTTP REQUEST PIPELINE KONFIGURIEREN --- - -// Middleware für Forwarded Headers (wichtig bei Reverse-Proxies wie Nginx oder Load Balancern) +// Middleware für Reverse-Proxies app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); // Swagger/SwaggerUI für API-Dokumentation aktivieren -// Für die Produktion wäre es sicherer, dies an `app.Environment.IsDevelopment()` zu binden -// if (app.Environment.IsDevelopment()) -// { -app.UseSwagger(); -app.UseSwaggerUI(); -// } +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} -// app.UseHttpsRedirection(); // Auskommentiert für Docker HTTP-Entwicklung (da der Proxy HTTPS übernimmt) +// app.UseHttpsRedirection(); // Auskommentiert für Docker HTTP-Entwicklung -// WICHTIG: Die Reihenfolge der Middleware ist entscheidend! -app.UseAuthentication(); // Zuerst prüfen, wer der Benutzer ist -app.UseAuthorization(); // Dann prüfen, was der Benutzer darf +// WICHTIG: Die Reihenfolge ist entscheidend! +app.UseAuthentication(); +app.UseAuthorization(); -app.MapControllers(); // Stellt sicher, dass Ihre Controller als Endpunkte gemappt werden +app.MapControllers(); -// --- ENDE: HTTP REQUEST PIPELINE KONFIGURIEREN --- - -app.Run(); // Hier startet die Anwendung und blockiert die weitere Ausführung \ No newline at end of file +app.Run(); \ No newline at end of file diff --git a/Webshop.Api/SwaggerFilters/AddExampleSchemaFilter.cs b/Webshop.Api/SwaggerFilters/AddExampleSchemaFilter.cs index 3a4065b..8d68e20 100644 --- a/Webshop.Api/SwaggerFilters/AddExampleSchemaFilter.cs +++ b/Webshop.Api/SwaggerFilters/AddExampleSchemaFilter.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using Webshop.Application.DTOs; using Webshop.Application.DTOs.Auth; +using Webshop.Application.DTOs.Categories; // Korrigierter Namespace using Webshop.Application.DTOs.Categorys; using Webshop.Application.DTOs.Customers; using Webshop.Application.DTOs.Discounts; @@ -55,7 +56,7 @@ namespace Webshop.Api.SwaggerFilters { ["isAuthSuccessful"] = new OpenApiBoolean(true), ["errorMessage"] = new OpenApiString(""), - ["token"] = new OpenApiString("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"), + ["token"] = new OpenApiString("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."), ["userId"] = new OpenApiString(Guid.NewGuid().ToString()), ["email"] = new OpenApiString("loggedin@example.com"), ["roles"] = new OpenApiArray { new OpenApiString("Customer") } @@ -81,10 +82,7 @@ namespace Webshop.Api.SwaggerFilters } else if (type == typeof(ResendEmailConfirmationRequestDto)) { - schema.Example = new OpenApiObject - { - ["email"] = new OpenApiString("unconfirmed.user@example.com") - }; + schema.Example = new OpenApiObject { ["email"] = new OpenApiString("unconfirmed.user@example.com") }; } else if (type == typeof(ChangePasswordRequestDto)) { @@ -149,43 +147,10 @@ namespace Webshop.Api.SwaggerFilters ["lastModifiedDate"] = new OpenApiNull(), ["supplierId"] = new OpenApiNull(), ["purchasePrice"] = new OpenApiDouble(80.00), - ["categoryIds"] = new OpenApiArray - { - new OpenApiString("EXISTING_CATEGORY_ID_1"), - new OpenApiString("EXISTING_CATEGORY_ID_2") - } - }; - } - else if (type == typeof(ProductVariantDto)) - { - schema.Example = new OpenApiObject - { - ["id"] = new OpenApiString(Guid.Empty.ToString()), - ["productId"] = new OpenApiString("EIN_PRODUKT_GUID_HIER"), - ["name"] = new OpenApiString("Variante: Schwarz L"), - ["value"] = new OpenApiString("Schwarz, L"), - ["sku"] = new OpenApiString($"VARIANT-ABC-{uniqueId}"), - ["priceAdjustment"] = new OpenApiDouble(5.00), - ["stockQuantity"] = new OpenApiInteger(25), - ["imageUrl"] = new OpenApiString("https://example.com/images/prod-variant-red.jpg"), - ["isActive"] = new OpenApiBoolean(true) + ["categoryIds"] = new OpenApiArray { new OpenApiString("EXISTING_CATEGORY_ID_HERE") } }; } // --- Kategorien --- - else if (type == typeof(CategoryDto)) - { - schema.Example = new OpenApiObject - { - ["id"] = new OpenApiString(Guid.NewGuid().ToString()), - ["name"] = new OpenApiString($"Elektronik {uniqueId}"), - ["slug"] = new OpenApiString($"elektronik-{uniqueId}"), - ["description"] = new OpenApiString("Produkte rund um Elektronik."), - ["parentCategoryId"] = new OpenApiNull(), - ["imageUrl"] = new OpenApiString("https://example.com/images/category_electronics.jpg"), - ["isActive"] = new OpenApiBoolean(true), - ["displayOrder"] = new OpenApiInteger(1) - }; - } else if (type == typeof(CreateCategoryDto)) { schema.Example = new OpenApiObject @@ -204,7 +169,7 @@ namespace Webshop.Api.SwaggerFilters { schema.Example = new OpenApiObject { - ["id"] = new OpenApiString(Guid.Empty.ToString()), + ["id"] = new OpenApiString(Guid.NewGuid().ToString()), ["street"] = new OpenApiString("Musterstraße"), ["houseNumber"] = new OpenApiString("123a"), ["city"] = new OpenApiString("Musterstadt"), @@ -214,20 +179,6 @@ namespace Webshop.Api.SwaggerFilters }; } // --- Kunden --- - else if (type == typeof(CustomerDto)) - { - schema.Example = new OpenApiObject - { - ["id"] = new OpenApiString(Guid.NewGuid().ToString()), - ["userId"] = new OpenApiString("EIN_IDENTITY_USER_ID_HIER"), - ["firstName"] = new OpenApiString("Max"), - ["lastName"] = new OpenApiString("Mustermann"), - ["email"] = new OpenApiString($"max.mustermann.{uniqueId}@example.com"), - ["phoneNumber"] = new OpenApiString("+491719876543"), - ["defaultShippingAddressId"] = new OpenApiNull(), - ["defaultBillingAddressId"] = new OpenApiNull() - }; - } else if (type == typeof(UpdateCustomerDto)) { schema.Example = new OpenApiObject @@ -240,32 +191,11 @@ namespace Webshop.Api.SwaggerFilters ["defaultBillingAddressId"] = new OpenApiNull() }; } - // --- Rabatte --- - else if (type == typeof(DiscountDto)) - { - schema.Example = new OpenApiObject - { - ["id"] = new OpenApiString(Guid.Empty.ToString()), - ["name"] = new OpenApiString($"Sommerrabatt {uniqueId}"), - ["description"] = new OpenApiString("10% Rabatt auf alles"), - ["type"] = new OpenApiString(DiscountType.Percentage.ToString()), - ["value"] = new OpenApiDouble(10.00), - ["couponCode"] = new OpenApiString($"SOMMER-{uniqueId}"), - ["startDate"] = new OpenApiString(DateTimeOffset.UtcNow.AddDays(1).ToString("o")), - ["endDate"] = new OpenApiString(DateTimeOffset.UtcNow.AddDays(30).ToString("o")), - ["isActive"] = new OpenApiBoolean(true), - ["maxUses"] = new OpenApiInteger(100), - ["currentUses"] = new OpenApiInteger(0) - }; - } // --- Bestellungen --- else if (type == typeof(CreateOrderDto)) { schema.Example = new OpenApiObject { - ["customerId"] = new OpenApiString("CUSTOMER_ID_HERE"), - ["guestEmail"] = new OpenApiString($"gast.besteller.{uniqueId}@example.com"), - ["guestPhoneNumber"] = new OpenApiString("+4917611223344"), ["shippingAddressId"] = new OpenApiString("VALID_ADDRESS_ID_HERE"), ["billingAddressId"] = new OpenApiString("VALID_ADDRESS_ID_HERE"), ["paymentMethodId"] = new OpenApiString("VALID_PAYMENT_METHOD_ID_HERE"), @@ -280,110 +210,6 @@ namespace Webshop.Api.SwaggerFilters } }; } - else if (type == typeof(CreateOrderItemDto)) - { - schema.Example = new OpenApiObject - { - ["productId"] = new OpenApiString("VALID_PRODUCT_ID_HERE"), - ["productVariantId"] = new OpenApiNull(), - ["quantity"] = new OpenApiInteger(1) - }; - } - else if (type == typeof(OrderSummaryDto)) - { - schema.Example = new OpenApiObject - { - ["id"] = new OpenApiString(Guid.NewGuid().ToString()), - ["orderNumber"] = new OpenApiString($"WS-2025-{uniqueId}"), - ["orderDate"] = new OpenApiString(DateTimeOffset.UtcNow.ToString("o")), - ["status"] = new OpenApiString(OrderStatus.Processing.ToString()), - ["totalAmount"] = new OpenApiDouble(123.45), - ["paymentStatus"] = new OpenApiString(PaymentStatus.Paid.ToString()), - ["paymentMethodName"] = new OpenApiString("Kreditkarte"), - ["shippingMethodName"] = new OpenApiString("Standardversand") - }; - } - else if (type == typeof(OrderDetailDto)) - { - schema.Example = new OpenApiObject - { - ["id"] = new OpenApiString(Guid.NewGuid().ToString()), - ["orderNumber"] = new OpenApiString($"WS-2025-{uniqueId}-Detail"), - ["customerId"] = new OpenApiString(Guid.NewGuid().ToString()), - ["orderDate"] = new OpenApiString(DateTimeOffset.UtcNow.ToString("o")), - ["status"] = new OpenApiString(OrderStatus.Shipped.ToString()), - ["totalAmount"] = new OpenApiDouble(245.67), - ["shippingAddressId"] = new OpenApiString("EXISTING_ADDRESS_ID"), - ["billingAddressId"] = new OpenApiString("EXISTING_ADDRESS_ID"), - ["paymentMethodId"] = new OpenApiString("EXISTING_PAYMENT_METHOD_ID"), - ["orderItems"] = new OpenApiArray { - new OpenApiObject { ["id"] = new OpenApiString(Guid.NewGuid().ToString()), ["productId"] = new OpenApiString("PROD_B_ID"), ["productName"] = new OpenApiString("Produkt B"), ["quantity"] = new OpenApiInteger(1), ["unitPrice"] = new OpenApiDouble(199.99), ["totalPrice"] = new OpenApiDouble(199.99) } - } - }; - } - // --- Bewertungen --- - else if (type == typeof(ReviewDto)) - { - schema.Example = new OpenApiObject - { - ["id"] = new OpenApiString(Guid.NewGuid().ToString()), - ["productId"] = new OpenApiString("EXISTING_PRODUCT_ID_HERE"), - ["customerId"] = new OpenApiString("EXISTING_CUSTOMER_ID_HERE"), - ["rating"] = new OpenApiInteger(5), - ["title"] = new OpenApiString($"Super Produkt! ({uniqueId})"), - ["comment"] = new OpenApiString("Ich bin sehr zufrieden mit diesem Produkt."), - ["reviewDate"] = new OpenApiString(DateTimeOffset.UtcNow.ToString("o")), - ["isApproved"] = new OpenApiBoolean(false) - }; - } - else if (type == typeof(CreateReviewDto)) - { - schema.Example = new OpenApiObject - { - ["productId"] = new OpenApiString("EXISTING_PRODUCT_ID_HERE"), - ["rating"] = new OpenApiInteger(4), - ["title"] = new OpenApiString($"Gute Qualität ({uniqueId})"), - ["comment"] = new OpenApiString("Das Produkt ist gut, aber die Lieferung dauerte etwas.") - }; - } - // --- Einstellungen --- - else if (type == typeof(SettingDto)) - { - schema.Example = new OpenApiObject - { - ["key"] = new OpenApiString($"GlobalTaxRate-{uniqueId}"), - ["value"] = new OpenApiString("0.19"), - ["description"] = new OpenApiString("Allgemeiner Mehrwertsteuersatz"), - ["lastModifiedDate"] = new OpenApiString(DateTimeOffset.UtcNow.ToString("o")) - }; - } - // --- Zahlungsmethoden --- - else if (type == typeof(PaymentMethodDto)) - { - schema.Example = new OpenApiObject - { - ["id"] = new OpenApiString(Guid.Empty.ToString()), - ["name"] = new OpenApiString($"Kreditkarte (Stripe) {uniqueId}"), - ["description"] = new OpenApiString("Online-Zahlungsdienstleister."), - ["isActive"] = new OpenApiBoolean(true), - ["gatewayType"] = new OpenApiString(PaymentGatewayType.Stripe.ToString()), - ["processingFee"] = new OpenApiDouble(0.035) - }; - } - // --- Versandmethoden --- - else if (type == typeof(ShippingMethodDto)) - { - schema.Example = new OpenApiObject - { - ["id"] = new OpenApiString(Guid.Empty.ToString()), - ["name"] = new OpenApiString($"Expressversand UPS {uniqueId}"), - ["description"] = new OpenApiString("Lieferung am nächsten Werktag."), - ["cost"] = new OpenApiDouble(12.50), - ["isActive"] = new OpenApiBoolean(true), - ["minDeliveryDays"] = new OpenApiInteger(1), - ["maxDeliveryDays"] = new OpenApiInteger(1) - }; - } } } } \ No newline at end of file diff --git a/Webshop.Api/SwaggerFilters/ShippingMethodExampleOperationFilter.cs b/Webshop.Api/SwaggerFilters/ShippingMethodExampleOperationFilter.cs new file mode 100644 index 0000000..099dafd --- /dev/null +++ b/Webshop.Api/SwaggerFilters/ShippingMethodExampleOperationFilter.cs @@ -0,0 +1,59 @@ +// src/Webshop.Api/SwaggerFilters/ShippingMethodExampleOperationFilter.cs +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using Microsoft.OpenApi.Any; +using System.Net.Mime; +using System.Collections.Generic; + +namespace Webshop.Api.SwaggerFilters +{ + public class ShippingMethodExampleOperationFilter : IOperationFilter + { + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + if (context.MethodInfo.DeclaringType == typeof(Controllers.Admin.AdminShippingMethodsController) && + context.MethodInfo.Name == "CreateShippingMethod") + { + if (operation.RequestBody == null || + !operation.RequestBody.Content.TryGetValue(MediaTypeNames.Application.Json, out var mediaType)) + { + return; + } + + if (mediaType.Examples == null) + { + mediaType.Examples = new Dictionary(); + } + mediaType.Examples.Clear(); + + mediaType.Examples["Standard"] = new OpenApiExample + { + Summary = "Beispiel: Standardversand", + Value = new OpenApiObject + { + ["name"] = new OpenApiString("Standardversand (DHL)"), + ["description"] = new OpenApiString("Lieferung in 2-3 Werktagen."), + ["cost"] = new OpenApiDouble(4.99), + ["isActive"] = new OpenApiBoolean(true), + ["minDeliveryDays"] = new OpenApiInteger(2), + ["maxDeliveryDays"] = new OpenApiInteger(3) + } + }; + + mediaType.Examples["Express"] = new OpenApiExample + { + Summary = "Beispiel: Expressversand", + Value = new OpenApiObject + { + ["name"] = new OpenApiString("Expressversand (UPS)"), + ["description"] = new OpenApiString("Lieferung am nächsten Werktag."), + ["cost"] = new OpenApiDouble(12.99), + ["isActive"] = new OpenApiBoolean(true), + ["minDeliveryDays"] = new OpenApiInteger(1), + ["maxDeliveryDays"] = new OpenApiInteger(1) + } + }; + } + } + } +} \ No newline at end of file