diff --git a/Webshop.Api/Controllers/Admin/AdminProductsController.cs b/Webshop.Api/Controllers/Admin/AdminProductsController.cs index 009fc40..d2514d9 100644 --- a/Webshop.Api/Controllers/Admin/AdminProductsController.cs +++ b/Webshop.Api/Controllers/Admin/AdminProductsController.cs @@ -1,12 +1,11 @@ -// Auto-generiert von CreateWebshopFiles.ps1 -using Microsoft.AspNetCore.Mvc; +// src/Webshop.Api/Controllers/Admin/AdminProductsController.cs using Microsoft.AspNetCore.Authorization; - +using Microsoft.AspNetCore.Http; // Für IFormFile +using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Threading.Tasks; using Webshop.Application.DTOs.Products; -using Microsoft.AspNetCore.Http; using Webshop.Application.Services.Admin.Interfaces; namespace Webshop.Api.Controllers.Admin @@ -39,25 +38,15 @@ namespace Webshop.Api.Controllers.Admin } [HttpPost] - [Consumes("multipart/form-data")] - public async Task> CreateAdminProduct([FromForm] CreateAdminProductDto productDto) // << NEU: [FromForm] und CreateAdminProductDto >> + public async Task> CreateAdminProduct([FromBody] CreateAdminProductDto productDto) // << ZURÜCK ZU [FromBody] >> { if (!ModelState.IsValid) return BadRequest(ModelState); - var createdProduct = await _adminProductService.CreateAdminProductAsync(productDto); - - if (createdProduct == null) - { - // Hier könnte eine spezifischere Fehlermeldung vom Service kommen - return BadRequest("Produkt konnte nicht erstellt werden."); - } - return CreatedAtAction(nameof(GetAdminProduct), new { id = createdProduct.Id }, createdProduct); } - [HttpPut("{id}")] - public async Task UpdateAdminProduct(Guid id, [FromBody] UpdateAdminProductDto productDto) + public async Task UpdateAdminProduct(Guid id, [FromBody] UpdateAdminProductDto productDto) // << ZURÜCK ZU [FromBody] >> { if (id != productDto.Id) return BadRequest(); if (!ModelState.IsValid) return BadRequest(ModelState); @@ -66,6 +55,7 @@ namespace Webshop.Api.Controllers.Admin return NoContent(); } + [HttpDelete("{id}")] public async Task DeleteAdminProduct(Guid id) { @@ -74,4 +64,4 @@ namespace Webshop.Api.Controllers.Admin return NoContent(); } } -} +} \ No newline at end of file diff --git a/Webshop.Api/Program.cs b/Webshop.Api/Program.cs index 8158db2..62fd40e 100644 --- a/Webshop.Api/Program.cs +++ b/Webshop.Api/Program.cs @@ -23,6 +23,7 @@ using Webshop.Domain.Identity; using Webshop.Domain.Interfaces; using Webshop.Infrastructure.Data; using Webshop.Infrastructure.Repositories; +using Webshop.Infrastructure.Services; var builder = WebApplication.CreateBuilder(args); @@ -108,6 +109,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Externe Dienste (Resend) diff --git a/Webshop.Application/DTOs/Products/CreateAdminProductDto.cs b/Webshop.Application/DTOs/Products/CreateAdminProductDto.cs index 7b97788..e51460a 100644 --- a/Webshop.Application/DTOs/Products/CreateAdminProductDto.cs +++ b/Webshop.Application/DTOs/Products/CreateAdminProductDto.cs @@ -1,5 +1,4 @@ // src/Webshop.Application/DTOs/Products/CreateAdminProductDto.cs -using Microsoft.AspNetCore.Http; // Für IFormFile using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -21,13 +20,12 @@ namespace Webshop.Application.DTOs.Products [Required] public string Slug { get; set; } - // << NEU: Felder für den Bildupload >> - public IFormFile? MainImageFile { get; set; } - public List? AdditionalImageFiles { get; set; } + // << KORREKTUR: Felder für Bild-URLs, keine IFormFile >> + public string? MainImageUrl { get; set; } + public List? AdditionalImageUrls { get; set; } public List CategorieIds { get; set; } = new List(); - // ... weitere Felder, die beim Erstellen benötigt werden (z.B. SupplierId, PurchasePrice etc.) ... public decimal? Weight { get; set; } public decimal? OldPrice { get; set; } public Guid? SupplierId { get; set; } diff --git a/Webshop.Application/DTOs/Products/ProductImageDto.cs b/Webshop.Application/DTOs/Products/ProductImageDto.cs index da5de87..4179800 100644 --- a/Webshop.Application/DTOs/Products/ProductImageDto.cs +++ b/Webshop.Application/DTOs/Products/ProductImageDto.cs @@ -10,4 +10,4 @@ namespace Webshop.Application.DTOs.Products public bool IsMainImage { get; set; } public int DisplayOrder { get; set; } } -} \ No newline at end of file +} diff --git a/Webshop.Application/DTOs/Products/UpdateAdminProductDto.cs b/Webshop.Application/DTOs/Products/UpdateAdminProductDto.cs index 5bf273a..d56f18d 100644 --- a/Webshop.Application/DTOs/Products/UpdateAdminProductDto.cs +++ b/Webshop.Application/DTOs/Products/UpdateAdminProductDto.cs @@ -1,5 +1,4 @@ // src/Webshop.Application/DTOs/Products/UpdateAdminProductDto.cs -using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -23,14 +22,12 @@ namespace Webshop.Application.DTOs.Products [Required] public string Slug { get; set; } - // << NEU: Felder für den Bildupload und -verwaltung >> - public IFormFile? MainImageFile { get; set; } // Optional: Neues Hauptbild hochladen - public List? AdditionalImageFiles { get; set; } // Optional: Weitere Bilder hochladen - public List? ImagesToDelete { get; set; } // Liste der IDs von Bildern, die gelöscht werden sollen + // << KORREKTUR: Felder für Bild-URLs >> + public string? MainImageUrl { get; set; } + public List? AdditionalImageUrls { get; set; } public List CategorieIds { get; set; } = new List(); - // ... weitere Felder, die beim Aktualisieren benötigt werden ... public decimal? Weight { get; set; } public decimal? OldPrice { get; set; } public Guid? SupplierId { get; set; } diff --git a/Webshop.Application/Services/Admin/AdminProductService.cs b/Webshop.Application/Services/Admin/AdminProductService.cs index 0e31749..56bc618 100644 --- a/Webshop.Application/Services/Admin/AdminProductService.cs +++ b/Webshop.Application/Services/Admin/AdminProductService.cs @@ -15,16 +15,13 @@ namespace Webshop.Application.Services.Admin public class AdminProductService : IAdminProductService { private readonly IProductRepository _productRepository; - private readonly IFileStorageService _fileStorageService; private readonly ApplicationDbContext _context; public AdminProductService( IProductRepository productRepository, - IFileStorageService fileStorageService, ApplicationDbContext context) { _productRepository = productRepository; - _fileStorageService = fileStorageService; _context = context; } @@ -33,6 +30,7 @@ namespace Webshop.Application.Services.Admin var products = await _context.Products .Include(p => p.Productcategories) .Include(p => p.Images) + .OrderBy(p => p.Name) .ToListAsync(); return products.Select(p => new AdminProductDto @@ -53,7 +51,7 @@ namespace Webshop.Application.Services.Admin SupplierId = p.SupplierId, PurchasePrice = p.PurchasePrice, categorieIds = p.Productcategories.Select(pc => pc.categorieId).ToList(), - Images = p.Images.Select(img => new ProductImageDto + Images = p.Images.OrderBy(i => i.DisplayOrder).Select(img => new ProductImageDto { Id = img.Id, Url = img.Url, @@ -90,7 +88,7 @@ namespace Webshop.Application.Services.Admin SupplierId = product.SupplierId, PurchasePrice = product.PurchasePrice, categorieIds = product.Productcategories.Select(pc => pc.categorieId).ToList(), - Images = product.Images.Select(img => new ProductImageDto + Images = product.Images.OrderBy(i => i.DisplayOrder).Select(img => new ProductImageDto { Id = img.Id, Url = img.Url, @@ -100,23 +98,19 @@ namespace Webshop.Application.Services.Admin }; } - public async Task CreateAdminProductAsync(CreateAdminProductDto productDto) + public async Task CreateAdminProductAsync(CreateAdminProductDto productDto) { var images = new List(); - if (productDto.MainImageFile != null) + if (!string.IsNullOrEmpty(productDto.MainImageUrl)) { - await using var stream = productDto.MainImageFile.OpenReadStream(); - var url = await _fileStorageService.SaveFileAsync(stream, productDto.MainImageFile.FileName, productDto.MainImageFile.ContentType); - images.Add(new ProductImage { Url = url, IsMainImage = true, DisplayOrder = 1 }); + images.Add(new ProductImage { Url = productDto.MainImageUrl, IsMainImage = true, DisplayOrder = 1 }); } - if (productDto.AdditionalImageFiles != null) + if (productDto.AdditionalImageUrls != null) { int order = 2; - foreach (var file in productDto.AdditionalImageFiles) + foreach (var url in productDto.AdditionalImageUrls) { - await using var stream = file.OpenReadStream(); - var url = await _fileStorageService.SaveFileAsync(stream, file.FileName, file.ContentType); images.Add(new ProductImage { Url = url, IsMainImage = false, DisplayOrder = order++ }); } } @@ -130,13 +124,17 @@ namespace Webshop.Application.Services.Admin IsActive = productDto.IsActive, StockQuantity = productDto.StockQuantity, Slug = productDto.Slug, + Weight = productDto.Weight, + OldPrice = productDto.OldPrice, + SupplierId = productDto.SupplierId, + PurchasePrice = productDto.PurchasePrice, Images = images, Productcategories = productDto.CategorieIds.Select(cId => new Productcategorie { categorieId = cId }).ToList() }; await _productRepository.AddProductAsync(newProduct); - return (await GetAdminProductByIdAsync(newProduct.Id))!; + return await GetAdminProductByIdAsync(newProduct.Id); } public async Task UpdateAdminProductAsync(UpdateAdminProductDto productDto) @@ -148,34 +146,44 @@ namespace Webshop.Application.Services.Admin if (existingProduct == null) return false; - if (productDto.ImagesToDelete != null) - { - var imagesToRemove = existingProduct.Images.Where(img => productDto.ImagesToDelete.Contains(img.Id)).ToList(); - _context.ProductImages.RemoveRange(imagesToRemove); - } - if (productDto.MainImageFile != null) - { - var existingMainImage = existingProduct.Images.FirstOrDefault(img => img.IsMainImage); - if (existingMainImage != null) _context.ProductImages.Remove(existingMainImage); + // Basisdaten aktualisieren + existingProduct.Name = productDto.Name; + existingProduct.Description = productDto.Description; + existingProduct.SKU = productDto.SKU; + existingProduct.Price = productDto.Price; + existingProduct.IsActive = productDto.IsActive; + existingProduct.StockQuantity = productDto.StockQuantity; + existingProduct.Slug = productDto.Slug; + existingProduct.Weight = productDto.Weight; + existingProduct.OldPrice = productDto.OldPrice; + existingProduct.SupplierId = productDto.SupplierId; + existingProduct.PurchasePrice = productDto.PurchasePrice; + existingProduct.LastModifiedDate = DateTimeOffset.UtcNow; - await using var stream = productDto.MainImageFile.OpenReadStream(); - var url = await _fileStorageService.SaveFileAsync(stream, productDto.MainImageFile.FileName, productDto.MainImageFile.ContentType); - existingProduct.Images.Add(new ProductImage { Url = url, IsMainImage = true, DisplayOrder = 1 }); - } - if (productDto.AdditionalImageFiles != null) + // Kategorien synchronisieren + existingProduct.Productcategories.Clear(); + if (productDto.CategorieIds != null) { - int displayOrder = (existingProduct.Images.Any() ? existingProduct.Images.Max(i => i.DisplayOrder) : 0) + 1; - foreach (var file in productDto.AdditionalImageFiles) + foreach (var categorieId in productDto.CategorieIds) { - await using var stream = file.OpenReadStream(); - var url = await _fileStorageService.SaveFileAsync(stream, file.FileName, file.ContentType); - existingProduct.Images.Add(new ProductImage { Url = url, IsMainImage = false, DisplayOrder = displayOrder++ }); + existingProduct.Productcategories.Add(new Productcategorie { categorieId = categorieId }); } } - existingProduct.Name = productDto.Name; - existingProduct.Description = productDto.Description; - // ... (restliche Felder aktualisieren) ... + // Bilder synchronisieren + existingProduct.Images.Clear(); + if (!string.IsNullOrEmpty(productDto.MainImageUrl)) + { + existingProduct.Images.Add(new ProductImage { Url = productDto.MainImageUrl, IsMainImage = true, DisplayOrder = 1 }); + } + if (productDto.AdditionalImageUrls != null) + { + int order = 2; + foreach (var url in productDto.AdditionalImageUrls) + { + existingProduct.Images.Add(new ProductImage { Url = url, IsMainImage = false, DisplayOrder = order++ }); + } + } await _productRepository.UpdateProductAsync(existingProduct); return true; diff --git a/Webshop.Application/Services/Admin/Interfaces/IAdminProductService.cs b/Webshop.Application/Services/Admin/Interfaces/IAdminProductService.cs index 28b708e..66070ad 100644 --- a/Webshop.Application/Services/Admin/Interfaces/IAdminProductService.cs +++ b/Webshop.Application/Services/Admin/Interfaces/IAdminProductService.cs @@ -1,4 +1,5 @@ -// src/Webshop.Application/Services/Admin/Interfaces/IAdminProductService.cs +// src/Webshop.Application/Services/Admin/IAdminProductService.cs +using System; using System.Collections.Generic; using System.Threading.Tasks; using Webshop.Application.DTOs.Products; @@ -9,8 +10,8 @@ namespace Webshop.Application.Services.Admin.Interfaces { Task> GetAllAdminProductsAsync(); Task GetAdminProductByIdAsync(Guid id); - Task CreateAdminProductAsync(CreateAdminProductDto productDto); // << NEUER TYP >> - Task UpdateAdminProductAsync(UpdateAdminProductDto productDto); // << NEUER TYP >> + Task CreateAdminProductAsync(CreateAdminProductDto productDto); // << DTO-TYP GEÄNDERT >> + Task UpdateAdminProductAsync(UpdateAdminProductDto productDto); // << DTO-TYP GEÄNDERT >> Task DeleteAdminProductAsync(Guid id); } } \ No newline at end of file