diff --git a/Webshop.Api/Controllers/Admin/AdminReviewsController.cs b/Webshop.Api/Controllers/Admin/AdminReviewsController.cs new file mode 100644 index 0000000..7ef23de --- /dev/null +++ b/Webshop.Api/Controllers/Admin/AdminReviewsController.cs @@ -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 GetAllReviews() + { + var reviews = await _adminReviewService.GetAllReviewsAsync(); + return Ok(reviews); + } + + [HttpPost("{id}/approve")] + public async Task 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 DeleteReview(Guid id) + { + var result = await _adminReviewService.DeleteReviewAsync(id); + if (result.Type == Application.ServiceResultType.Success) return NoContent(); + return NotFound(new { Message = result.ErrorMessage }); + } + } +} \ No newline at end of file diff --git a/Webshop.Api/Controllers/Customers/ReviewsController.cs b/Webshop.Api/Controllers/Customers/ReviewsController.cs index 3cf0149..d236891 100644 --- a/Webshop.Api/Controllers/Customers/ReviewsController.cs +++ b/Webshop.Api/Controllers/Customers/ReviewsController.cs @@ -1,18 +1,36 @@ -// Auto-generiert von CreateWebshopFiles.ps1 -using Microsoft.AspNetCore.Mvc; +// src/Webshop.Api/Controllers/Customer/ReviewsController.cs using Microsoft.AspNetCore.Authorization; - -using System; -using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; 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] [Route("api/v1/customer/[controller]")] [Authorize(Roles = "Customer")] public class ReviewsController : ControllerBase { + private readonly ICustomerReviewService _customerReviewService; + public ReviewsController(ICustomerReviewService customerReviewService) + { + _customerReviewService = customerReviewService; + } + + [HttpPost] + public async Task 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 }); + } } -} +} \ No newline at end of file diff --git a/Webshop.Api/Controllers/Public/ReviewsController.cs b/Webshop.Api/Controllers/Public/ReviewsController.cs new file mode 100644 index 0000000..71db101 --- /dev/null +++ b/Webshop.Api/Controllers/Public/ReviewsController.cs @@ -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>> GetApprovedReviews(Guid productId) + { + var reviews = await _reviewService.GetApprovedReviewsByProductIdAsync(productId); + return Ok(reviews); + } + } +} \ No newline at end of file diff --git a/Webshop.Api/Program.cs b/Webshop.Api/Program.cs index b14c10a..7eb282f 100644 --- a/Webshop.Api/Program.cs +++ b/Webshop.Api/Program.cs @@ -119,8 +119,6 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - -//fehlt noch builder.Services.AddScoped(); // Services @@ -148,10 +146,9 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - -//fehlt noch builder.Services.AddScoped(); - +builder.Services.AddScoped(); +builder.Services.AddScoped(); // Controller und API-Infrastruktur builder.Services.AddControllers() diff --git a/Webshop.Application/DTOs/Reviews/CreateReviewDto.cs b/Webshop.Application/DTOs/Reviews/CreateReviewDto.cs index d5a31ba..1912e96 100644 --- a/Webshop.Application/DTOs/Reviews/CreateReviewDto.cs +++ b/Webshop.Application/DTOs/Reviews/CreateReviewDto.cs @@ -1,16 +1,23 @@ -// Auto-generiert von CreateWebshopFiles.ps1 +// src/Webshop.Application/DTOs/Reviews/CreateReviewDto.cs using System; -using System.Collections.Generic; -using System.Threading.Tasks; - +using System.ComponentModel.DataAnnotations; namespace Webshop.Application.DTOs.Reviews { public class CreateReviewDto { + [Required] public Guid ProductId { get; set; } + + [Required] + [Range(1, 5)] 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; } } -} +} \ No newline at end of file diff --git a/Webshop.Application/DTOs/Reviews/ReviewDto.cs b/Webshop.Application/DTOs/Reviews/ReviewDto.cs index 3a56542..f5aca60 100644 --- a/Webshop.Application/DTOs/Reviews/ReviewDto.cs +++ b/Webshop.Application/DTOs/Reviews/ReviewDto.cs @@ -1,8 +1,5 @@ -// Auto-generiert von CreateWebshopFiles.ps1 +// src/Webshop.Application/DTOs/Reviews/ReviewDto.cs using System; -using System.Collections.Generic; -using System.Threading.Tasks; - namespace Webshop.Application.DTOs.Reviews { @@ -10,11 +7,12 @@ namespace Webshop.Application.DTOs.Reviews { public Guid Id { 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 string? Title { get; set; } - public string Comment { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; + public string? Comment { get; set; } public DateTimeOffset ReviewDate { get; set; } public bool IsApproved { get; set; } } -} +} \ No newline at end of file diff --git a/Webshop.Application/Services/Admin/AdminReviewService.cs b/Webshop.Application/Services/Admin/AdminReviewService.cs new file mode 100644 index 0000000..5c9d5b1 --- /dev/null +++ b/Webshop.Application/Services/Admin/AdminReviewService.cs @@ -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> 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 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 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(); + } + } +} \ No newline at end of file diff --git a/Webshop.Application/Services/Admin/Interfaces/IAdminReviewService.cs b/Webshop.Application/Services/Admin/Interfaces/IAdminReviewService.cs new file mode 100644 index 0000000..905ea85 --- /dev/null +++ b/Webshop.Application/Services/Admin/Interfaces/IAdminReviewService.cs @@ -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> GetAllReviewsAsync(); + Task ApproveReviewAsync(Guid reviewId); + Task DeleteReviewAsync(Guid reviewId); + } +} \ No newline at end of file diff --git a/Webshop.Application/Services/Customers/Interfaces/IReviewService.cs b/Webshop.Application/Services/Customers/Interfaces/IReviewService.cs index 5b37b34..f667f4a 100644 --- a/Webshop.Application/Services/Customers/Interfaces/IReviewService.cs +++ b/Webshop.Application/Services/Customers/Interfaces/IReviewService.cs @@ -1,16 +1,12 @@ -// Auto-generiert von CreateWebshopFiles.ps1 -using System; -using System.Collections.Generic; +// src/Webshop.Application/Services/Customers/Interfaces/IReviewService.cs using System.Threading.Tasks; -using Webshop.Application.DTOs; -using Webshop.Application.DTOs.Auth; -using Webshop.Application.DTOs.Users; +using Webshop.Application; +using Webshop.Application.DTOs.Reviews; - -namespace Webshop.Application.Services.Customers.Interfaces +namespace Webshop.Application.Services.Customers { - public interface IReviewService + public interface ICustomerReviewService { - // Fügen Sie hier Methodensignaturen hinzu + Task> CreateReviewAsync(CreateReviewDto reviewDto, string userId); } -} +} \ No newline at end of file diff --git a/Webshop.Application/Services/Customers/ReviewService.cs b/Webshop.Application/Services/Customers/ReviewService.cs index 7f12bae..b4114de 100644 --- a/Webshop.Application/Services/Customers/ReviewService.cs +++ b/Webshop.Application/Services/Customers/ReviewService.cs @@ -1,18 +1,52 @@ -// Auto-generiert von CreateWebshopFiles.ps1 +// src/Webshop.Application/Services/Customers/ReviewService.cs using System; -using System.Collections.Generic; +using System.Linq; 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 { - 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> CreateReviewAsync(CreateReviewDto reviewDto, string userId) + { + var customer = await _customerRepository.GetByUserIdAsync(userId); + if (customer == null) return ServiceResult.Fail(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(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); + } } -} +} \ No newline at end of file diff --git a/Webshop.Application/Services/Public/Interfaces/IReviewService.cs b/Webshop.Application/Services/Public/Interfaces/IReviewService.cs new file mode 100644 index 0000000..fa58ecb --- /dev/null +++ b/Webshop.Application/Services/Public/Interfaces/IReviewService.cs @@ -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> GetApprovedReviewsByProductIdAsync(Guid productId); + } +} \ No newline at end of file diff --git a/Webshop.Application/Services/Public/ReviewService.cs b/Webshop.Application/Services/Public/ReviewService.cs new file mode 100644 index 0000000..7c32ae0 --- /dev/null +++ b/Webshop.Application/Services/Public/ReviewService.cs @@ -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> 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 + }); + } + } +} \ No newline at end of file diff --git a/Webshop.Domain/Interfaces/IReviewRepository.cs b/Webshop.Domain/Interfaces/IReviewRepository.cs index bcc249b..05cc7b8 100644 --- a/Webshop.Domain/Interfaces/IReviewRepository.cs +++ b/Webshop.Domain/Interfaces/IReviewRepository.cs @@ -1,14 +1,18 @@ -// Auto-generiert von CreateWebshopFiles.ps1 +// src/Webshop.Domain/Interfaces/IReviewRepository.cs using System; using System.Collections.Generic; using System.Threading.Tasks; using Webshop.Domain.Entities; - namespace Webshop.Domain.Interfaces { public interface IReviewRepository { -// Fügen Sie hier Methodensignaturen hinzu + Task> GetAllAsync(); + Task> GetApprovedByProductIdAsync(Guid productId); + Task GetByIdAsync(Guid id); + Task AddAsync(Review review); + Task UpdateAsync(Review review); + Task DeleteAsync(Guid id); } -} +} \ No newline at end of file diff --git a/Webshop.Infrastructure/Data/ApplicationDbContext.cs b/Webshop.Infrastructure/Data/ApplicationDbContext.cs index beaa2c5..04888a4 100644 --- a/Webshop.Infrastructure/Data/ApplicationDbContext.cs +++ b/Webshop.Infrastructure/Data/ApplicationDbContext.cs @@ -33,6 +33,7 @@ namespace Webshop.Infrastructure.Data public DbSet ShopInfos { get; set; } = default!; + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/Webshop.Infrastructure/Repositories/ReviewRepository.cs b/Webshop.Infrastructure/Repositories/ReviewRepository.cs index 85b2b0f..0f8ea60 100644 --- a/Webshop.Infrastructure/Repositories/ReviewRepository.cs +++ b/Webshop.Infrastructure/Repositories/ReviewRepository.cs @@ -1,11 +1,12 @@ -// Auto-generiert von CreateWebshopFiles.ps1 +// src/Webshop.Infrastructure/Repositories/ReviewRepository.cs using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Webshop.Domain.Entities; using Webshop.Domain.Interfaces; using Webshop.Infrastructure.Data; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; namespace Webshop.Infrastructure.Repositories { @@ -18,12 +19,49 @@ namespace Webshop.Infrastructure.Repositories _context = context; } - // Fügen Sie hier Repository-Methoden hinzu - // Beispiel: - // public async Task> GetAllAsync() { return await _context.Set().ToListAsync(); } - // public async Task GetByIdAsync(Guid id) { return await _context.Set().FindAsync(id); } - // public async Task AddAsync(T entity) { _context.Set().Add(entity); await _context.SaveChangesAsync(); } - // public async Task UpdateAsync(T entity) { _context.Set().Update(entity); await _context.SaveChangesAsync(); } - // public async Task DeleteAsync(Guid id) { var entity = await _context.Set().FindAsync(id); if (entity != null) { _context.Set().Remove(entity); await _context.SaveChangesAsync(); } } + public async Task> GetAllAsync() + { + return await _context.Reviews + .Include(r => r.Customer) + .Include(r => r.Product) + .OrderByDescending(r => r.ReviewDate) + .ToListAsync(); + } + + public async Task> 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 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(); + } + } } -} +} \ No newline at end of file