This commit is contained in:
Tizian.Breuch
2025-09-25 16:35:20 +02:00
parent 88f520a51b
commit d3c03ef431
4 changed files with 60 additions and 102 deletions

View File

@@ -4,16 +4,17 @@ using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Webshop.Application.DTOs.Products; using Webshop.Application.DTOs.Products;
using Webshop.Application.Services.Public.Interfaces; // <-- WICHTIGES USING HINZUF<55>GEN using Webshop.Application.Services.Public.Interfaces;
using Microsoft.AspNetCore.Http;
using Webshop.Application;
namespace Webshop.Api.Controllers.Public namespace Webshop.Api.Controllers.Public
{ {
[ApiController] [ApiController]
[Route("api/v1/public/[controller]")] // Route explizit gemacht f<>r Klarheit [Route("api/v1/public/[controller]")]
[AllowAnonymous] [AllowAnonymous]
public class ProductsController : ControllerBase public class ProductsController : ControllerBase
{ {
// --- KORREKTUR: Abh<62>ngigkeit vom Interface, nicht von der Klasse ---
private readonly IProductService _productService; private readonly IProductService _productService;
public ProductsController(IProductService productService) public ProductsController(IProductService productService)
@@ -21,43 +22,35 @@ namespace Webshop.Api.Controllers.Public
_productService = productService; _productService = productService;
} }
/// <summary>
/// Ruft eine Liste aller <20>ffentlichen, aktiven Produkte ab.
/// </summary>
[HttpGet] [HttpGet]
[ProducesResponseType(typeof(IEnumerable<ProductDto>), 200)] [ProducesResponseType(typeof(IEnumerable<ProductDto>), StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<ProductDto>>> GetAllProducts() public async Task<IActionResult> GetAllProducts()
{ {
var products = await _productService.GetAllProductsAsync(); var result = await _productService.GetAllProductsAsync();
return Ok(products); return Ok(result.Value);
} }
/// <summary> [HttpGet("{slug}")]
/// Ruft ein einzelnes <20>ffentliches Produkt anhand seines URL-Slugs ab. [ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
/// </summary> [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[HttpGet("{slug}")] // Ergibt die Route z.B. /api/v1/public/products/mein-produkt-slug public async Task<IActionResult> GetProductBySlug(string slug)
[ProducesResponseType(typeof(ProductDto), 200)]
[ProducesResponseType(404)] // Not Found
public async Task<ActionResult<ProductDto>> GetProductBySlug(string slug)
{ {
var product = await _productService.GetProductBySlugAsync(slug); var result = await _productService.GetProductBySlugAsync(slug);
if (product == null) return result.Type switch
{ {
return NotFound(); ServiceResultType.Success => Ok(result.Value),
} ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Ein unerwarteter Fehler ist aufgetreten.")
return Ok(product); };
} }
/// <summary>
/// Ruft eine Liste der Sonderangebote f<>r die Startseite ab, sortiert nach Anzeigereihenfolge.
/// </summary>
[HttpGet("featured")] [HttpGet("featured")]
[ProducesResponseType(typeof(IEnumerable<ProductDto>), 200)] [ProducesResponseType(typeof(IEnumerable<ProductDto>), StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<ProductDto>>> GetFeaturedProducts() public async Task<IActionResult> GetFeaturedProducts()
{ {
var products = await _productService.GetFeaturedProductsAsync(); var result = await _productService.GetFeaturedProductsAsync();
return Ok(products); return Ok(result.Value);
} }
} }
} }

View File

@@ -12,6 +12,7 @@ namespace Webshop.Application.DTOs.Products
public string Description { get; set; } = string.Empty; public string Description { get; set; } = string.Empty;
public string SKU { get; set; } = string.Empty; public string SKU { get; set; } = string.Empty;
public decimal Price { get; set; } public decimal Price { get; set; }
public decimal? OldPrice { get; set; }
public bool IsActive { get; set; } public bool IsActive { get; set; }
public bool IsInStock { get; set; } public bool IsInStock { get; set; }
public int StockQuantity { get; set; } public int StockQuantity { get; set; }

View File

@@ -1,15 +1,15 @@
// RICHTIG - vollständig // src/Webshop.Application/Services/Public/Interfaces/IProductService.cs
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Webshop.Application;
using Webshop.Application.DTOs.Products; using Webshop.Application.DTOs.Products;
namespace Webshop.Application.Services.Public.Interfaces namespace Webshop.Application.Services.Public.Interfaces
{ {
public interface IProductService public interface IProductService
{ {
Task<IEnumerable<ProductDto>> GetAllProductsAsync(); Task<ServiceResult<IEnumerable<ProductDto>>> GetAllProductsAsync();
Task<ServiceResult<ProductDto>> GetProductBySlugAsync(string slug);
Task<ProductDto?> GetProductBySlugAsync(string slug); Task<ServiceResult<IEnumerable<ProductDto>>> GetFeaturedProductsAsync();
Task<IEnumerable<ProductDto>> GetFeaturedProductsAsync(); // << NEU >>
} }
} }

View File

@@ -3,10 +3,11 @@ using Microsoft.EntityFrameworkCore;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Webshop.Application;
using Webshop.Application.DTOs.Categorie; using Webshop.Application.DTOs.Categorie;
using Webshop.Application.DTOs.Products; using Webshop.Application.DTOs.Products;
using Webshop.Application.Services.Public.Interfaces; using Webshop.Application.Services.Public.Interfaces;
using Webshop.Domain.Interfaces; using Webshop.Domain.Entities;
using Webshop.Infrastructure.Data; using Webshop.Infrastructure.Data;
namespace Webshop.Application.Services.Public namespace Webshop.Application.Services.Public
@@ -20,77 +21,34 @@ namespace Webshop.Application.Services.Public
_context = context; _context = context;
} }
public async Task<IEnumerable<ProductDto>> GetAllProductsAsync() public async Task<ServiceResult<IEnumerable<ProductDto>>> GetAllProductsAsync()
{ {
var products = await _context.Products var products = await _context.Products
.Include(p => p.Productcategories).ThenInclude(pc => pc.categorie) .Include(p => p.Productcategories).ThenInclude(pc => pc.categorie)
.Include(p => p.Images) // Lade Bilder mit .Include(p => p.Images)
.Where(p => p.IsActive) .Where(p => p.IsActive)
.ToListAsync(); .ToListAsync();
return products.Select(p => new ProductDto var dtos = products.Select(p => MapToDto(p, useShortDescription: true)).ToList();
{ return ServiceResult.Ok<IEnumerable<ProductDto>>(dtos);
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();
} }
public async Task<ProductDto?> GetProductBySlugAsync(string slug) public async Task<ServiceResult<ProductDto>> GetProductBySlugAsync(string slug)
{ {
var product = await _context.Products var product = await _context.Products
.Include(p => p.Productcategories).ThenInclude(pc => pc.categorie) .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); .FirstOrDefaultAsync(p => p.Slug == slug && p.IsActive);
if (product == null) if (product == null)
{ {
return null; return ServiceResult.Fail<ProductDto>(ServiceResultType.NotFound, $"Produkt mit dem Slug '{slug}' wurde nicht gefunden.");
} }
return new ProductDto return ServiceResult.Ok(MapToDto(product, useShortDescription: false));
{
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()
};
} }
public async Task<IEnumerable<ProductDto>> GetFeaturedProductsAsync() public async Task<ServiceResult<IEnumerable<ProductDto>>> GetFeaturedProductsAsync()
{ {
var products = await _context.Products var products = await _context.Products
.Where(p => p.IsActive && p.IsFeatured) .Where(p => p.IsActive && p.IsFeatured)
@@ -99,32 +57,38 @@ namespace Webshop.Application.Services.Public
.Include(p => p.Productcategories).ThenInclude(pc => pc.categorie) .Include(p => p.Productcategories).ThenInclude(pc => pc.categorie)
.ToListAsync(); .ToListAsync();
return products.Select(p => new ProductDto var dtos = products.Select(p => MapToDto(p, useShortDescription: true)).ToList();
return ServiceResult.Ok<IEnumerable<ProductDto>>(dtos);
}
// Private Hilfsmethode, um Codeduplizierung zu vermeiden
private ProductDto MapToDto(Product product, bool useShortDescription)
{
return new ProductDto
{ {
Id = p.Id, Id = product.Id,
Name = p.Name, Name = product.Name,
Description = p.ShortDescription, // F<>r die Startseite ist die Kurzbeschreibung ideal Description = useShortDescription ? product.ShortDescription : product.Description,
SKU = p.SKU, Price = product.Price,
Price = p.Price, OldPrice = product.OldPrice,
IsActive = p.IsActive, SKU = product.SKU,
IsInStock = p.IsInStock, IsInStock = product.IsInStock,
StockQuantity = p.StockQuantity, StockQuantity = product.StockQuantity,
Slug = p.Slug, Slug = product.Slug,
categories = p.Productcategories.Select(pc => new CategorieDto categories = product.Productcategories.Select(pc => new CategorieDto
{ {
Id = pc.categorie.Id, Id = pc.categorie.Id,
Name = pc.categorie.Name, Name = pc.categorie.Name,
Slug = pc.categorie.Slug Slug = pc.categorie.Slug
}).ToList(), }).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, Id = img.Id,
Url = img.Url, Url = img.Url,
IsMainImage = img.IsMainImage, IsMainImage = img.IsMainImage,
DisplayOrder = img.DisplayOrder DisplayOrder = img.DisplayOrder
}).ToList() }).ToList()
}).ToList(); };
} }
} }
} }