This commit is contained in:
Tizian.Breuch
2025-09-08 11:24:10 +02:00
parent 471f4a2e7a
commit 5b84ccc575
15 changed files with 352 additions and 63 deletions

View File

@@ -0,0 +1,45 @@
// src/Webshop.Api/Controllers/Admin/AdminReviewsController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using Webshop.Application.Services.Admin;
namespace Webshop.Api.Controllers.Admin
{
[ApiController]
[Route("api/v1/admin/[controller]")]
[Authorize(Roles = "Admin")]
public class AdminReviewsController : ControllerBase
{
private readonly IAdminReviewService _adminReviewService;
public AdminReviewsController(IAdminReviewService adminReviewService)
{
_adminReviewService = adminReviewService;
}
[HttpGet]
public async Task<IActionResult> GetAllReviews()
{
var reviews = await _adminReviewService.GetAllReviewsAsync();
return Ok(reviews);
}
[HttpPost("{id}/approve")]
public async Task<IActionResult> ApproveReview(Guid id)
{
var result = await _adminReviewService.ApproveReviewAsync(id);
if (result.Type == Application.ServiceResultType.Success) return NoContent();
return NotFound(new { Message = result.ErrorMessage });
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteReview(Guid id)
{
var result = await _adminReviewService.DeleteReviewAsync(id);
if (result.Type == Application.ServiceResultType.Success) return NoContent();
return NotFound(new { Message = result.ErrorMessage });
}
}
}

View File

@@ -1,18 +1,36 @@
// Auto-generiert von CreateWebshopFiles.ps1 // src/Webshop.Api/Controllers/Customer/ReviewsController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System; using System.Security.Claims;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Webshop.Application.DTOs.Reviews;
using Webshop.Application.Services.Customers;
namespace Webshop.Api.Controllers.Customers namespace Webshop.Api.Controllers.Customer
{ {
[ApiController] [ApiController]
[Route("api/v1/customer/[controller]")] [Route("api/v1/customer/[controller]")]
[Authorize(Roles = "Customer")] [Authorize(Roles = "Customer")]
public class ReviewsController : ControllerBase public class ReviewsController : ControllerBase
{ {
private readonly ICustomerReviewService _customerReviewService;
public ReviewsController(ICustomerReviewService customerReviewService)
{
_customerReviewService = customerReviewService;
}
[HttpPost]
public async Task<IActionResult> CreateReview([FromBody] CreateReviewDto reviewDto)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _customerReviewService.CreateReviewAsync(reviewDto, userId);
if (result.Type == Application.ServiceResultType.Success)
{
return Ok(result.Value);
}
return BadRequest(new { Message = result.ErrorMessage });
}
} }
} }

View File

@@ -0,0 +1,32 @@
// src/Webshop.Api/Controllers/Public/ReviewsController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Webshop.Application.DTOs.Products; // Für ProductDto
using Webshop.Application.DTOs.Reviews;
using Webshop.Application.Services.Public;
namespace Webshop.Api.Controllers.Public
{
[ApiController]
[Route("api/v1/public/products/{productId:guid}/[controller]")]
[AllowAnonymous]
public class ReviewsController : ControllerBase
{
private readonly IReviewService _reviewService;
public ReviewsController(IReviewService reviewService)
{
_reviewService = reviewService;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<ReviewDto>>> GetApprovedReviews(Guid productId)
{
var reviews = await _reviewService.GetApprovedReviewsByProductIdAsync(productId);
return Ok(reviews);
}
}
}

View File

@@ -119,8 +119,6 @@ builder.Services.AddScoped<IDiscountRepository, DiscountRepository>();
builder.Services.AddScoped<ISettingRepository, SettingRepository>(); builder.Services.AddScoped<ISettingRepository, SettingRepository>();
builder.Services.AddScoped<IShopInfoRepository, ShopInfoRepository>(); builder.Services.AddScoped<IShopInfoRepository, ShopInfoRepository>();
builder.Services.AddScoped<IDiscountRepository, DiscountRepository>(); builder.Services.AddScoped<IDiscountRepository, DiscountRepository>();
//fehlt noch
builder.Services.AddScoped<IReviewRepository, ReviewRepository>(); builder.Services.AddScoped<IReviewRepository, ReviewRepository>();
// Services // Services
@@ -148,10 +146,9 @@ builder.Services.AddScoped<IAdminSettingService, AdminSettingService>();
builder.Services.AddScoped<IAdminDiscountService, AdminDiscountService>(); builder.Services.AddScoped<IAdminDiscountService, AdminDiscountService>();
builder.Services.AddScoped<ICheckoutService, CheckoutService>(); builder.Services.AddScoped<ICheckoutService, CheckoutService>();
builder.Services.AddScoped<IDiscountService, DiscountService>(); builder.Services.AddScoped<IDiscountService, DiscountService>();
//fehlt noch
builder.Services.AddScoped<IReviewService, ReviewService>(); builder.Services.AddScoped<IReviewService, ReviewService>();
builder.Services.AddScoped<ICustomerReviewService, CustomerReviewService>();
builder.Services.AddScoped<IAdminReviewService, AdminReviewService>();
// Controller und API-Infrastruktur // Controller und API-Infrastruktur
builder.Services.AddControllers() builder.Services.AddControllers()

View File

@@ -1,16 +1,23 @@
// Auto-generiert von CreateWebshopFiles.ps1 // src/Webshop.Application/DTOs/Reviews/CreateReviewDto.cs
using System; using System;
using System.Collections.Generic; using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace Webshop.Application.DTOs.Reviews namespace Webshop.Application.DTOs.Reviews
{ {
public class CreateReviewDto public class CreateReviewDto
{ {
[Required]
public Guid ProductId { get; set; } public Guid ProductId { get; set; }
[Required]
[Range(1, 5)]
public int Rating { get; set; } public int Rating { get; set; }
public string? Title { get; set; }
public string Comment { get; set; } = string.Empty; [Required]
[MaxLength(255)]
public string Title { get; set; } = string.Empty;
[MaxLength(2000)]
public string? Comment { get; set; }
} }
} }

View File

@@ -1,8 +1,5 @@
// Auto-generiert von CreateWebshopFiles.ps1 // src/Webshop.Application/DTOs/Reviews/ReviewDto.cs
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Webshop.Application.DTOs.Reviews namespace Webshop.Application.DTOs.Reviews
{ {
@@ -10,11 +7,12 @@ namespace Webshop.Application.DTOs.Reviews
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid ProductId { get; set; } public Guid ProductId { get; set; }
public Guid? CustomerId { get; set; } public string? ProductName { get; set; } // F<>r Admin-Ansicht
public string CustomerName { get; set; } = string.Empty;
public int Rating { get; set; } public int Rating { get; set; }
public string? Title { get; set; } public string Title { get; set; } = string.Empty;
public string Comment { get; set; } = string.Empty; public string? Comment { get; set; }
public DateTimeOffset ReviewDate { get; set; } public DateTimeOffset ReviewDate { get; set; }
public bool IsApproved { get; set; } public bool IsApproved { get; set; }
} }
} }

View File

@@ -0,0 +1,56 @@
// src/Webshop.Application/Services/Admin/AdminReviewService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Webshop.Application.DTOs.Reviews;
using Webshop.Domain.Interfaces;
namespace Webshop.Application.Services.Admin
{
public class AdminReviewService : IAdminReviewService
{
private readonly IReviewRepository _reviewRepository;
public AdminReviewService(IReviewRepository reviewRepository)
{
_reviewRepository = reviewRepository;
}
public async Task<IEnumerable<ReviewDto>> GetAllReviewsAsync()
{
var reviews = await _reviewRepository.GetAllAsync();
return reviews.Select(r => new ReviewDto
{
Id = r.Id,
ProductId = r.ProductId,
ProductName = r.Product?.Name,
CustomerName = $"{r.Customer?.FirstName} {r.Customer?.LastName}",
Rating = r.Rating,
Title = r.Title,
Comment = r.Comment,
ReviewDate = r.ReviewDate,
IsApproved = r.IsApproved
});
}
public async Task<ServiceResult> ApproveReviewAsync(Guid reviewId)
{
var review = await _reviewRepository.GetByIdAsync(reviewId);
if (review == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Bewertung nicht gefunden.");
review.IsApproved = true;
await _reviewRepository.UpdateAsync(review);
return ServiceResult.Ok();
}
public async Task<ServiceResult> DeleteReviewAsync(Guid reviewId)
{
var review = await _reviewRepository.GetByIdAsync(reviewId);
if (review == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Bewertung nicht gefunden.");
await _reviewRepository.DeleteAsync(reviewId);
return ServiceResult.Ok();
}
}
}

View File

@@ -0,0 +1,16 @@
// src/Webshop.Application/Services/Admin/IAdminReviewService.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Webshop.Application;
using Webshop.Application.DTOs.Reviews;
namespace Webshop.Application.Services.Admin
{
public interface IAdminReviewService
{
Task<IEnumerable<ReviewDto>> GetAllReviewsAsync();
Task<ServiceResult> ApproveReviewAsync(Guid reviewId);
Task<ServiceResult> DeleteReviewAsync(Guid reviewId);
}
}

View File

@@ -1,16 +1,12 @@
// Auto-generiert von CreateWebshopFiles.ps1 // src/Webshop.Application/Services/Customers/Interfaces/IReviewService.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Webshop.Application.DTOs; using Webshop.Application;
using Webshop.Application.DTOs.Auth; using Webshop.Application.DTOs.Reviews;
using Webshop.Application.DTOs.Users;
namespace Webshop.Application.Services.Customers
namespace Webshop.Application.Services.Customers.Interfaces
{ {
public interface IReviewService public interface ICustomerReviewService
{ {
// Fügen Sie hier Methodensignaturen hinzu Task<ServiceResult<ReviewDto>> CreateReviewAsync(CreateReviewDto reviewDto, string userId);
} }
} }

View File

@@ -1,18 +1,52 @@
// Auto-generiert von CreateWebshopFiles.ps1 // src/Webshop.Application/Services/Customers/ReviewService.cs
using System; using System;
using System.Collections.Generic; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Webshop.Application.Services.Customers.Interfaces; using Webshop.Application.DTOs.Reviews;
using Webshop.Domain.Entities;
using Webshop.Domain.Interfaces;
using Webshop.Infrastructure.Data;
namespace Webshop.Application.Services.Customers namespace Webshop.Application.Services.Customers
{ {
public class ReviewService : IReviewService public class CustomerReviewService : ICustomerReviewService
{ {
// Fügen Sie hier Abhängigkeiten per Dependency Injection hinzu (z.B. Repositories) private readonly IReviewRepository _reviewRepository;
private readonly ICustomerRepository _customerRepository;
private readonly ApplicationDbContext _context;
// public ReviewService(IYourRepository repository) { } public CustomerReviewService(IReviewRepository reviewRepository, ICustomerRepository customerRepository, ApplicationDbContext context)
{
_reviewRepository = reviewRepository;
_customerRepository = customerRepository;
_context = context;
}
// Fügen Sie hier Service-Methoden hinzu public async Task<ServiceResult<ReviewDto>> CreateReviewAsync(CreateReviewDto reviewDto, string userId)
{
var customer = await _customerRepository.GetByUserIdAsync(userId);
if (customer == null) return ServiceResult.Fail<ReviewDto>(ServiceResultType.Unauthorized, "Kunde nicht gefunden.");
// Business-Regel: Hat der Kunde dieses Produkt gekauft?
// bool hasPurchased = await _context.OrderItems
// .AnyAsync(oi => oi.ProductId == reviewDto.ProductId && oi.Order.CustomerId == customer.Id);
// if (!hasPurchased) return ServiceResult.Fail<ReviewDto>(ServiceResultType.Forbidden, "Sie können nur Produkte bewerten, die Sie gekauft haben.");
var newReview = new Review
{
ProductId = reviewDto.ProductId,
CustomerId = customer.Id,
Rating = reviewDto.Rating,
Title = reviewDto.Title,
Comment = reviewDto.Comment,
ReviewDate = DateTimeOffset.UtcNow,
IsApproved = false // Bewertungen müssen vom Admin freigegeben werden
};
await _reviewRepository.AddAsync(newReview);
var resultDto = new ReviewDto { /* Mapping */ };
return ServiceResult.Ok(resultDto);
}
} }
} }

View File

@@ -0,0 +1,13 @@
// src/Webshop.Application/Services/Public/Interfaces/IReviewService.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Webshop.Application.DTOs.Reviews;
namespace Webshop.Application.Services.Public
{
public interface IReviewService
{
Task<IEnumerable<ReviewDto>> GetApprovedReviewsByProductIdAsync(Guid productId);
}
}

View File

@@ -0,0 +1,34 @@
// src/Webshop.Application/Services/Public/ReviewService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Webshop.Application.DTOs.Reviews;
using Webshop.Domain.Interfaces;
namespace Webshop.Application.Services.Public
{
public class ReviewService : IReviewService
{
private readonly IReviewRepository _reviewRepository;
public ReviewService(IReviewRepository reviewRepository)
{
_reviewRepository = reviewRepository;
}
public async Task<IEnumerable<ReviewDto>> GetApprovedReviewsByProductIdAsync(Guid productId)
{
var reviews = await _reviewRepository.GetApprovedByProductIdAsync(productId);
return reviews.Select(r => new ReviewDto
{
// Mapping zu DTO, CustomerName wird für Anonymität oft gekürzt
CustomerName = $"{r.Customer?.FirstName} {r.Customer?.LastName?.FirstOrDefault()}.",
Rating = r.Rating,
Title = r.Title,
Comment = r.Comment,
ReviewDate = r.ReviewDate
});
}
}
}

View File

@@ -1,14 +1,18 @@
// Auto-generiert von CreateWebshopFiles.ps1 // src/Webshop.Domain/Interfaces/IReviewRepository.cs
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Webshop.Domain.Entities; using Webshop.Domain.Entities;
namespace Webshop.Domain.Interfaces namespace Webshop.Domain.Interfaces
{ {
public interface IReviewRepository public interface IReviewRepository
{ {
// Fügen Sie hier Methodensignaturen hinzu Task<IEnumerable<Review>> GetAllAsync();
Task<IEnumerable<Review>> GetApprovedByProductIdAsync(Guid productId);
Task<Review?> GetByIdAsync(Guid id);
Task AddAsync(Review review);
Task UpdateAsync(Review review);
Task DeleteAsync(Guid id);
} }
} }

View File

@@ -33,6 +33,7 @@ namespace Webshop.Infrastructure.Data
public DbSet<ShopInfo> ShopInfos { get; set; } = default!; public DbSet<ShopInfo> ShopInfos { get; set; } = default!;
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);

View File

@@ -1,11 +1,12 @@
// Auto-generiert von CreateWebshopFiles.ps1 // src/Webshop.Infrastructure/Repositories/ReviewRepository.cs
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Webshop.Domain.Entities; using Webshop.Domain.Entities;
using Webshop.Domain.Interfaces; using Webshop.Domain.Interfaces;
using Webshop.Infrastructure.Data; using Webshop.Infrastructure.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Webshop.Infrastructure.Repositories namespace Webshop.Infrastructure.Repositories
{ {
@@ -18,12 +19,49 @@ namespace Webshop.Infrastructure.Repositories
_context = context; _context = context;
} }
// Fügen Sie hier Repository-Methoden hinzu public async Task<IEnumerable<Review>> GetAllAsync()
// Beispiel: {
// public async Task<IEnumerable<T>> GetAllAsync() { return await _context.Set<T>().ToListAsync(); } return await _context.Reviews
// public async Task<T?> GetByIdAsync(Guid id) { return await _context.Set<T>().FindAsync(id); } .Include(r => r.Customer)
// public async Task AddAsync(T entity) { _context.Set<T>().Add(entity); await _context.SaveChangesAsync(); } .Include(r => r.Product)
// public async Task UpdateAsync(T entity) { _context.Set<T>().Update(entity); await _context.SaveChangesAsync(); } .OrderByDescending(r => r.ReviewDate)
// public async Task DeleteAsync(Guid id) { var entity = await _context.Set<T>().FindAsync(id); if (entity != null) { _context.Set<T>().Remove(entity); await _context.SaveChangesAsync(); } } .ToListAsync();
}
public async Task<IEnumerable<Review>> GetApprovedByProductIdAsync(Guid productId)
{
return await _context.Reviews
.Include(r => r.Customer)
.Where(r => r.ProductId == productId && r.IsApproved)
.OrderByDescending(r => r.ReviewDate)
.ToListAsync();
}
public async Task<Review?> GetByIdAsync(Guid id)
{
return await _context.Reviews.FindAsync(id);
}
public async Task AddAsync(Review review)
{
await _context.Reviews.AddAsync(review);
await _context.SaveChangesAsync();
}
public async Task UpdateAsync(Review review)
{
_context.Reviews.Update(review);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(Guid id)
{
var review = await _context.Reviews.FindAsync(id);
if (review != null)
{
_context.Reviews.Remove(review);
await _context.SaveChangesAsync();
}
}
} }
} }