diff --git a/Webshop.Api/Controllers/Admin/AdminCategorieController.cs b/Webshop.Api/Controllers/Admin/AdminCategorieController.cs index f434f3a..646b403 100644 --- a/Webshop.Api/Controllers/Admin/AdminCategorieController.cs +++ b/Webshop.Api/Controllers/Admin/AdminCategorieController.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Categorie; using Webshop.Application.Services.Admin; @@ -37,41 +38,51 @@ namespace Webshop.Api.Controllers.Admin } [HttpPost] - public async Task> Createcategorie([FromBody] CreatecategorieDto categorieDto) + [Consumes("multipart/form-data")] + public async Task> CreateCategorie([FromForm] CreatecategorieDto categorieDto) { if (!ModelState.IsValid) return BadRequest(ModelState); - var (createdcategorie, errorMessage) = await _adminCategorieService.CreateAsync(categorieDto); + var result = await _adminCategorieService.CreateAsync(categorieDto); - if (createdcategorie == null) + if (result.Type == ServiceResultType.Success) { - return BadRequest(new { Message = errorMessage }); + return CreatedAtAction(nameof(GetcategorieById), new { id = result.Value!.Id }, result.Value); } - - return CreatedAtAction(nameof(GetcategorieById), new { id = createdcategorie.Id }, createdcategorie); + + return BadRequest(new { Message = result.ErrorMessage }); } [HttpPut("{id}")] - public async Task Updatecategorie(Guid id, [FromBody] CreatecategorieDto categorieDto) + [Consumes("multipart/form-data")] + public async Task UpdateCategorie(Guid id, [FromForm] UpdatecategorieDto categorieDto) { + if (id != categorieDto.Id) return BadRequest("ID in URL und Body stimmen nicht überein."); if (!ModelState.IsValid) return BadRequest(ModelState); - var (success, errorMessage) = await _adminCategorieService.UpdateAsync(id, categorieDto); + var result = await _adminCategorieService.UpdateAsync(categorieDto); - if (!success) + return result.Type switch { - return BadRequest(new { Message = errorMessage }); - } - - return NoContent(); + ServiceResultType.Success => NoContent(), + ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }), + ServiceResultType.InvalidInput => BadRequest(new { Message = result.ErrorMessage }), + _ => StatusCode(500, "Ein unerwarteter Fehler ist aufgetreten.") + }; } [HttpDelete("{id}")] - public async Task Deletecategorie(Guid id) + public async Task DeleteCategorie(Guid id) { - var success = await _adminCategorieService.DeleteAsync(id); - if (!success) return NotFound(); - return NoContent(); + var result = await _adminCategorieService.DeleteAsync(id); + + return result.Type switch + { + ServiceResultType.Success => NoContent(), + ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }), + ServiceResultType.Conflict => Conflict(new { Message = result.ErrorMessage }), + _ => StatusCode(500, "Ein unerwarteter Fehler ist aufgetreten.") + }; } } } \ No newline at end of file diff --git a/Webshop.Application/DTOs/Categories/CreateCategorieDto.cs b/Webshop.Application/DTOs/Categories/CreateCategorieDto.cs index 5df5220..7d75f4b 100644 --- a/Webshop.Application/DTOs/Categories/CreateCategorieDto.cs +++ b/Webshop.Application/DTOs/Categories/CreateCategorieDto.cs @@ -1,6 +1,8 @@ // Auto-generiert von CreateWebshopFiles.ps1 +using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; @@ -8,12 +10,20 @@ namespace Webshop.Application.DTOs.Categorie { public class CreatecategorieDto { + [Required] public string Name { get; set; } = string.Empty; - public string Slug { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; + + [Required] + public string Slug { get; set; } + + public string? Description { get; set; } public Guid? ParentcategorieId { get; set; } - public string? ImageUrl { get; set; } + + public IFormFile? ImageFile { get; set; } // << HINZUFÜGEN >> + public bool IsActive { get; set; } = true; public int DisplayOrder { get; set; } = 0; + + // public string? ImageUrl { get; set; } // << ENTFERNEN >> } } diff --git a/Webshop.Application/DTOs/Categories/UpdatecategorieDto.cs b/Webshop.Application/DTOs/Categories/UpdatecategorieDto.cs new file mode 100644 index 0000000..d98d96f --- /dev/null +++ b/Webshop.Application/DTOs/Categories/UpdatecategorieDto.cs @@ -0,0 +1,23 @@ +// src/Webshop.Application/DTOs/Categorie/UpdatecategorieDto.cs +using Microsoft.AspNetCore.Http; +using System; +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Application.DTOs.Categorie +{ + public class UpdatecategorieDto + { + [Required] + public Guid Id { get; set; } + [Required] + public string Name { get; set; } = string.Empty; + [Required] + public string Slug { get; set; } + public string? Description { get; set; } + public Guid? ParentcategorieId { get; set; } + public IFormFile? ImageFile { get; set; } + public string? ImageUrl { get; set; } // Zum Beibehalten oder Löschen der alten URL + public bool IsActive { get; set; } + public int DisplayOrder { get; set; } + } +} \ No newline at end of file diff --git a/Webshop.Application/ServiceResult.cs b/Webshop.Application/ServiceResult.cs new file mode 100644 index 0000000..0883a06 --- /dev/null +++ b/Webshop.Application/ServiceResult.cs @@ -0,0 +1,44 @@ +// src/Webshop.Application/ServiceResult.cs +namespace Webshop.Application +{ + public enum ServiceResultType + { + Success, + NotFound, + InvalidInput, + Conflict, + Failure + } + + public class ServiceResult + { + public ServiceResultType Type { get; } + public string? ErrorMessage { get; } + + protected ServiceResult(ServiceResultType type, string? errorMessage = null) + { + Type = type; + ErrorMessage = errorMessage; + } + + public static ServiceResult Ok() => new ServiceResult(ServiceResultType.Success); + public static ServiceResult Ok(T value) => new ServiceResult(value, ServiceResultType.Success); + + // Allgemeine Fail-Methode + public static ServiceResult Fail(ServiceResultType type, string errorMessage) => new ServiceResult(type, errorMessage); + + // Generische Fail-Methode + public static ServiceResult Fail(ServiceResultType type, string errorMessage) => new ServiceResult(default, type, errorMessage); + } + + public class ServiceResult : ServiceResult + { + public T? Value { get; } + + protected internal ServiceResult(T? value, ServiceResultType type, string? errorMessage = null) + : base(type, errorMessage) + { + Value = value; + } + } +} \ No newline at end of file diff --git a/Webshop.Application/Services/Admin/AdminCategorieService.cs b/Webshop.Application/Services/Admin/AdminCategorieService.cs index 40e45bd..02d774e 100644 --- a/Webshop.Application/Services/Admin/AdminCategorieService.cs +++ b/Webshop.Application/Services/Admin/AdminCategorieService.cs @@ -6,22 +6,115 @@ using System.Threading.Tasks; using Webshop.Application.DTOs.Categorie; using Webshop.Domain.Entities; using Webshop.Domain.Interfaces; +using Webshop.Application; // << NEU: Für ServiceResult >> namespace Webshop.Application.Services.Admin { public class AdminCategorieService : IAdminCategorieService { private readonly ICategorieRepository _categorieRepository; + private readonly IFileStorageService _fileStorageService; - public AdminCategorieService(ICategorieRepository categorieRepository) + public AdminCategorieService(ICategorieRepository categorieRepository, IFileStorageService fileStorageService) { _categorieRepository = categorieRepository; + _fileStorageService = fileStorageService; } public async Task> GetAllAsync() { var categories = await _categorieRepository.GetAllAsync(); - return categories.Select(c => new CategorieDto + return categories.Select(MapToDto).ToList(); + } + + public async Task GetByIdAsync(Guid id) + { + var categorie = await _categorieRepository.GetByIdAsync(id); + return categorie != null ? MapToDto(categorie) : null; + } + + public async Task> CreateAsync(CreatecategorieDto categorieDto) + { + var existing = await _categorieRepository.GetBySlugAsync(categorieDto.Slug); + if (existing != null) + { + return ServiceResult.Fail(ServiceResultType.InvalidInput, "Eine Kategorie mit diesem Slug existiert bereits."); + } + + string? imageUrl = null; + if (categorieDto.ImageFile != null) + { + await using var stream = categorieDto.ImageFile.OpenReadStream(); + imageUrl = await _fileStorageService.SaveFileAsync(stream, categorieDto.ImageFile.FileName, categorieDto.ImageFile.ContentType); + } + + var categorie = new Categorie + { + Name = categorieDto.Name, + Slug = categorieDto.Slug, + Description = categorieDto.Description, + ParentcategorieId = categorieDto.ParentcategorieId, + ImageUrl = imageUrl, + IsActive = categorieDto.IsActive, + DisplayOrder = categorieDto.DisplayOrder, + CreatedDate = DateTimeOffset.UtcNow + }; + + await _categorieRepository.AddAsync(categorie); + + var createdDto = MapToDto(categorie); + return ServiceResult.Ok(createdDto); + } + + public async Task UpdateAsync(UpdatecategorieDto categorieDto) + { + var existing = await _categorieRepository.GetByIdAsync(categorieDto.Id); + if (existing == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Kategorie nicht gefunden."); + + var slugExists = await _categorieRepository.GetBySlugAsync(categorieDto.Slug); + if (slugExists != null && slugExists.Id != categorieDto.Id) + { + return ServiceResult.Fail(ServiceResultType.InvalidInput, "Eine andere Kategorie mit diesem Slug existiert bereits."); + } + + string? imageUrl = existing.ImageUrl; + if (categorieDto.ImageFile != null) + { + await using var stream = categorieDto.ImageFile.OpenReadStream(); + imageUrl = await _fileStorageService.SaveFileAsync(stream, categorieDto.ImageFile.FileName, categorieDto.ImageFile.ContentType); + } + else if (string.IsNullOrEmpty(categorieDto.ImageUrl)) + { + imageUrl = null; + } + + + existing.Name = categorieDto.Name; + existing.Slug = categorieDto.Slug; + existing.Description = categorieDto.Description; + existing.ParentcategorieId = categorieDto.ParentcategorieId; + existing.ImageUrl = imageUrl; + existing.IsActive = categorieDto.IsActive; + existing.DisplayOrder = categorieDto.DisplayOrder; + existing.LastModifiedDate = DateTimeOffset.UtcNow; + + await _categorieRepository.UpdateAsync(existing); + return ServiceResult.Ok(); + } + + public async Task DeleteAsync(Guid id) + { + var categorie = await _categorieRepository.GetByIdAsync(id); + if (categorie == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Kategorie nicht gefunden."); + + await _categorieRepository.DeleteAsync(id); + return ServiceResult.Ok(); + } + + // Private Helper-Methode für konsistentes Mapping + private CategorieDto MapToDto(Categorie c) + { + return new CategorieDto { Id = c.Id, Name = c.Name, @@ -31,99 +124,7 @@ namespace Webshop.Application.Services.Admin ImageUrl = c.ImageUrl, IsActive = c.IsActive, DisplayOrder = c.DisplayOrder - }).ToList(); - } - - public async Task GetByIdAsync(Guid id) - { - var categorie = await _categorieRepository.GetByIdAsync(id); - if (categorie == null) return null; - - return new CategorieDto - { - Id = categorie.Id, - Name = categorie.Name, - Slug = categorie.Slug, - Description = categorie.Description, - ParentcategorieId = categorie.ParentcategorieId, - ImageUrl = categorie.ImageUrl, - IsActive = categorie.IsActive, - DisplayOrder = categorie.DisplayOrder }; } - - public async Task<(CategorieDto? Createdcategorie, string? ErrorMessage)> CreateAsync(CreatecategorieDto categorieDto) - { - var existingcategorie = await _categorieRepository.GetBySlugAsync(categorieDto.Slug); - if (existingcategorie != null) - { - return (null, "Eine Kategorie mit diesem Slug existiert bereits."); - } - - var categorie = new Categorie - { - Id = Guid.NewGuid(), - Name = categorieDto.Name, - Slug = categorieDto.Slug, - Description = categorieDto.Description, - ParentcategorieId = categorieDto.ParentcategorieId, - ImageUrl = categorieDto.ImageUrl, - IsActive = categorieDto.IsActive, - DisplayOrder = categorieDto.DisplayOrder, - CreatedDate = DateTimeOffset.UtcNow - }; - - await _categorieRepository.AddAsync(categorie); - - var createdDto = new CategorieDto - { - Id = categorie.Id, - Name = categorie.Name, - Slug = categorie.Slug, - Description = categorie.Description, - ParentcategorieId = categorie.ParentcategorieId, - ImageUrl = categorie.ImageUrl, - IsActive = categorie.IsActive, - DisplayOrder = categorie.DisplayOrder - }; - - return (createdDto, null); - } - - public async Task<(bool Success, string? ErrorMessage)> UpdateAsync(Guid id, CreatecategorieDto categorieDto) - { - var existingcategorie = await _categorieRepository.GetByIdAsync(id); - if (existingcategorie == null) - { - return (false, "Kategorie nicht gefunden."); - } - - var categorieWithSameSlug = await _categorieRepository.GetBySlugAsync(categorieDto.Slug); - if (categorieWithSameSlug != null && categorieWithSameSlug.Id != id) - { - return (false, "Eine andere Kategorie mit diesem Slug existiert bereits."); - } - - existingcategorie.Name = categorieDto.Name; - existingcategorie.Slug = categorieDto.Slug; - existingcategorie.Description = categorieDto.Description; - existingcategorie.ParentcategorieId = categorieDto.ParentcategorieId; - existingcategorie.ImageUrl = categorieDto.ImageUrl; - existingcategorie.IsActive = categorieDto.IsActive; - existingcategorie.DisplayOrder = categorieDto.DisplayOrder; - existingcategorie.LastModifiedDate = DateTimeOffset.UtcNow; - - await _categorieRepository.UpdateAsync(existingcategorie); - return (true, null); - } - - public async Task DeleteAsync(Guid id) - { - var categorie = await _categorieRepository.GetByIdAsync(id); - if (categorie == null) return false; - - await _categorieRepository.DeleteAsync(id); - return true; - } } } \ No newline at end of file diff --git a/Webshop.Application/Services/Admin/Interfaces/IAdminCategorieService.cs b/Webshop.Application/Services/Admin/Interfaces/IAdminCategorieService.cs index ac4632e..88dc602 100644 --- a/Webshop.Application/Services/Admin/Interfaces/IAdminCategorieService.cs +++ b/Webshop.Application/Services/Admin/Interfaces/IAdminCategorieService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Webshop.Application; // << HINZUFÜGEN >> using Webshop.Application.DTOs.Categorie; namespace Webshop.Application.Services.Admin @@ -10,8 +11,8 @@ namespace Webshop.Application.Services.Admin { Task> GetAllAsync(); Task GetByIdAsync(Guid id); - Task<(CategorieDto? Createdcategorie, string? ErrorMessage)> CreateAsync(CreatecategorieDto categorieDto); - Task<(bool Success, string? ErrorMessage)> UpdateAsync(Guid id, CreatecategorieDto categorieDto); - Task DeleteAsync(Guid id); + Task> CreateAsync(CreatecategorieDto categorieDto); + Task UpdateAsync(UpdatecategorieDto categorieDto); // << DTO-TYP GEÄNDERT >> + Task DeleteAsync(Guid id); } } \ No newline at end of file