diff --git a/Webshop.Api/Controllers/Public/ProductsController.cs b/Webshop.Api/Controllers/Public/ProductsController.cs index 77316a9..a546f5b 100644 --- a/Webshop.Api/Controllers/Public/ProductsController.cs +++ b/Webshop.Api/Controllers/Public/ProductsController.cs @@ -4,16 +4,17 @@ using Microsoft.AspNetCore.Authorization; using System.Collections.Generic; using System.Threading.Tasks; using Webshop.Application.DTOs.Products; -using Webshop.Application.Services.Public.Interfaces; // <-- WICHTIGES USING HINZUFÜGEN +using Webshop.Application.Services.Public.Interfaces; +using Microsoft.AspNetCore.Http; +using Webshop.Application; namespace Webshop.Api.Controllers.Public { [ApiController] - [Route("api/v1/public/[controller]")] // Route explizit gemacht für Klarheit + [Route("api/v1/public/[controller]")] [AllowAnonymous] public class ProductsController : ControllerBase { - // --- KORREKTUR: Abhängigkeit vom Interface, nicht von der Klasse --- private readonly IProductService _productService; public ProductsController(IProductService productService) @@ -21,43 +22,35 @@ namespace Webshop.Api.Controllers.Public _productService = productService; } - /// - /// Ruft eine Liste aller öffentlichen, aktiven Produkte ab. - /// [HttpGet] - [ProducesResponseType(typeof(IEnumerable), 200)] - public async Task>> GetAllProducts() + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetAllProducts() { - var products = await _productService.GetAllProductsAsync(); - return Ok(products); + var result = await _productService.GetAllProductsAsync(); + return Ok(result.Value); } - /// - /// Ruft ein einzelnes öffentliches Produkt anhand seines URL-Slugs ab. - /// - [HttpGet("{slug}")] // Ergibt die Route z.B. /api/v1/public/products/mein-produkt-slug - [ProducesResponseType(typeof(ProductDto), 200)] - [ProducesResponseType(404)] // Not Found - public async Task> GetProductBySlug(string slug) + [HttpGet("{slug}")] + [ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task GetProductBySlug(string slug) { - var product = await _productService.GetProductBySlugAsync(slug); + var result = await _productService.GetProductBySlugAsync(slug); - if (product == null) + return result.Type switch { - return NotFound(); - } - - return Ok(product); + ServiceResultType.Success => Ok(result.Value), + ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }), + _ => StatusCode(StatusCodes.Status500InternalServerError, "Ein unerwarteter Fehler ist aufgetreten.") + }; } - /// - /// Ruft eine Liste der Sonderangebote für die Startseite ab, sortiert nach Anzeigereihenfolge. - /// + [HttpGet("featured")] - [ProducesResponseType(typeof(IEnumerable), 200)] - public async Task>> GetFeaturedProducts() + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetFeaturedProducts() { - var products = await _productService.GetFeaturedProductsAsync(); - return Ok(products); + var result = await _productService.GetFeaturedProductsAsync(); + return Ok(result.Value); } } } \ No newline at end of file diff --git a/Webshop.Application/DTOs/Products/ProductDto.cs b/Webshop.Application/DTOs/Products/ProductDto.cs index 6baa677..5494259 100644 --- a/Webshop.Application/DTOs/Products/ProductDto.cs +++ b/Webshop.Application/DTOs/Products/ProductDto.cs @@ -12,6 +12,7 @@ namespace Webshop.Application.DTOs.Products public string Description { get; set; } = string.Empty; public string SKU { get; set; } = string.Empty; public decimal Price { get; set; } + public decimal? OldPrice { get; set; } public bool IsActive { get; set; } public bool IsInStock { get; set; } public int StockQuantity { get; set; } diff --git a/Webshop.Application/Services/Public/Interfaces/IProductService.cs b/Webshop.Application/Services/Public/Interfaces/IProductService.cs index 035509b..6ecf3e4 100644 --- a/Webshop.Application/Services/Public/Interfaces/IProductService.cs +++ b/Webshop.Application/Services/Public/Interfaces/IProductService.cs @@ -1,15 +1,15 @@ -// RICHTIG - vollständig +// src/Webshop.Application/Services/Public/Interfaces/IProductService.cs using System.Collections.Generic; using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Products; namespace Webshop.Application.Services.Public.Interfaces { public interface IProductService { - Task> GetAllProductsAsync(); - - Task GetProductBySlugAsync(string slug); - Task> GetFeaturedProductsAsync(); // << NEU >> + Task>> GetAllProductsAsync(); + Task> GetProductBySlugAsync(string slug); + Task>> GetFeaturedProductsAsync(); } } \ No newline at end of file diff --git a/Webshop.Application/Services/Public/ProductService.cs b/Webshop.Application/Services/Public/ProductService.cs index cc0de72..a745af1 100644 --- a/Webshop.Application/Services/Public/ProductService.cs +++ b/Webshop.Application/Services/Public/ProductService.cs @@ -3,10 +3,11 @@ using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Categorie; using Webshop.Application.DTOs.Products; using Webshop.Application.Services.Public.Interfaces; -using Webshop.Domain.Interfaces; +using Webshop.Domain.Entities; using Webshop.Infrastructure.Data; namespace Webshop.Application.Services.Public @@ -20,77 +21,34 @@ namespace Webshop.Application.Services.Public _context = context; } - public async Task> GetAllProductsAsync() + public async Task>> GetAllProductsAsync() { var products = await _context.Products .Include(p => p.Productcategories).ThenInclude(pc => pc.categorie) - .Include(p => p.Images) // Lade Bilder mit + .Include(p => p.Images) .Where(p => p.IsActive) .ToListAsync(); - return products.Select(p => new ProductDto - { - Id = p.Id, - Name = p.Name, - Description = p.ShortDescription, - Price = p.Price, - SKU = p.SKU, - IsInStock = p.IsInStock, - Slug = p.Slug, - categories = p.Productcategories.Select(pc => new CategorieDto - { - Id = pc.categorie.Id, - Name = pc.categorie.Name, - Slug = pc.categorie.Slug - }).ToList(), - Images = p.Images.Select(img => new ProductImageDto - { - Id = img.Id, - Url = img.Url, - IsMainImage = img.IsMainImage, - DisplayOrder = img.DisplayOrder - }).ToList() - }).ToList(); + var dtos = products.Select(p => MapToDto(p, useShortDescription: true)).ToList(); + return ServiceResult.Ok>(dtos); } - public async Task GetProductBySlugAsync(string slug) + public async Task> GetProductBySlugAsync(string slug) { var product = await _context.Products .Include(p => p.Productcategories).ThenInclude(pc => pc.categorie) - .Include(p => p.Images) // Lade Bilder mit + .Include(p => p.Images) .FirstOrDefaultAsync(p => p.Slug == slug && p.IsActive); if (product == null) { - return null; + return ServiceResult.Fail(ServiceResultType.NotFound, $"Produkt mit dem Slug '{slug}' wurde nicht gefunden."); } - return new ProductDto - { - Id = product.Id, - Name = product.Name, - Description = product.Description, - Price = product.Price, - SKU = product.SKU, - IsInStock = product.IsInStock, - Slug = product.Slug, - categories = product.Productcategories.Select(pc => new CategorieDto - { - Id = pc.categorie.Id, - Name = pc.categorie.Name, - Slug = pc.categorie.Slug - }).ToList(), - Images = product.Images.Select(img => new ProductImageDto - { - Id = img.Id, - Url = img.Url, - IsMainImage = img.IsMainImage, - DisplayOrder = img.DisplayOrder - }).ToList() - }; + return ServiceResult.Ok(MapToDto(product, useShortDescription: false)); } - public async Task> GetFeaturedProductsAsync() + public async Task>> GetFeaturedProductsAsync() { var products = await _context.Products .Where(p => p.IsActive && p.IsFeatured) @@ -99,32 +57,38 @@ namespace Webshop.Application.Services.Public .Include(p => p.Productcategories).ThenInclude(pc => pc.categorie) .ToListAsync(); - return products.Select(p => new ProductDto + var dtos = products.Select(p => MapToDto(p, useShortDescription: true)).ToList(); + return ServiceResult.Ok>(dtos); + } + + // Private Hilfsmethode, um Codeduplizierung zu vermeiden + private ProductDto MapToDto(Product product, bool useShortDescription) + { + return new ProductDto { - Id = p.Id, - Name = p.Name, - Description = p.ShortDescription, // Für die Startseite ist die Kurzbeschreibung ideal - SKU = p.SKU, - Price = p.Price, - IsActive = p.IsActive, - IsInStock = p.IsInStock, - StockQuantity = p.StockQuantity, - Slug = p.Slug, - categories = p.Productcategories.Select(pc => new CategorieDto + Id = product.Id, + Name = product.Name, + Description = useShortDescription ? product.ShortDescription : product.Description, + Price = product.Price, + OldPrice = product.OldPrice, + SKU = product.SKU, + IsInStock = product.IsInStock, + StockQuantity = product.StockQuantity, + Slug = product.Slug, + categories = product.Productcategories.Select(pc => new CategorieDto { Id = pc.categorie.Id, Name = pc.categorie.Name, Slug = pc.categorie.Slug }).ToList(), - Images = p.Images.OrderBy(i => i.DisplayOrder).Select(img => new ProductImageDto + Images = product.Images.OrderBy(i => i.DisplayOrder).Select(img => new ProductImageDto { Id = img.Id, Url = img.Url, IsMainImage = img.IsMainImage, DisplayOrder = img.DisplayOrder }).ToList() - }).ToList(); + }; } - } } \ No newline at end of file