test bilder erneut

This commit is contained in:
Tizian.Breuch
2025-08-08 15:50:01 +02:00
parent 562d720c4a
commit c2af53c5ce
7 changed files with 65 additions and 69 deletions

View File

@@ -1,12 +1,11 @@
// Auto-generiert von CreateWebshopFiles.ps1 // src/Webshop.Api/Controllers/Admin/AdminProductsController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; // F<>r IFormFile
using Microsoft.AspNetCore.Mvc;
using System; using System;
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 Microsoft.AspNetCore.Http;
using Webshop.Application.Services.Admin.Interfaces; using Webshop.Application.Services.Admin.Interfaces;
namespace Webshop.Api.Controllers.Admin namespace Webshop.Api.Controllers.Admin
@@ -39,25 +38,15 @@ namespace Webshop.Api.Controllers.Admin
} }
[HttpPost] [HttpPost]
[Consumes("multipart/form-data")] public async Task<ActionResult<AdminProductDto>> CreateAdminProduct([FromBody] CreateAdminProductDto productDto) // << ZUR<55>CK ZU [FromBody] >>
public async Task<ActionResult<AdminProductDto>> CreateAdminProduct([FromForm] CreateAdminProductDto productDto) // << NEU: [FromForm] und CreateAdminProductDto >>
{ {
if (!ModelState.IsValid) return BadRequest(ModelState); if (!ModelState.IsValid) return BadRequest(ModelState);
var createdProduct = await _adminProductService.CreateAdminProductAsync(productDto); 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); return CreatedAtAction(nameof(GetAdminProduct), new { id = createdProduct.Id }, createdProduct);
} }
[HttpPut("{id}")] [HttpPut("{id}")]
public async Task<IActionResult> UpdateAdminProduct(Guid id, [FromBody] UpdateAdminProductDto productDto) public async Task<IActionResult> UpdateAdminProduct(Guid id, [FromBody] UpdateAdminProductDto productDto) // << ZUR<55>CK ZU [FromBody] >>
{ {
if (id != productDto.Id) return BadRequest(); if (id != productDto.Id) return BadRequest();
if (!ModelState.IsValid) return BadRequest(ModelState); if (!ModelState.IsValid) return BadRequest(ModelState);
@@ -66,6 +55,7 @@ namespace Webshop.Api.Controllers.Admin
return NoContent(); return NoContent();
} }
[HttpDelete("{id}")] [HttpDelete("{id}")]
public async Task<IActionResult> DeleteAdminProduct(Guid id) public async Task<IActionResult> DeleteAdminProduct(Guid id)
{ {

View File

@@ -23,6 +23,7 @@ using Webshop.Domain.Identity;
using Webshop.Domain.Interfaces; using Webshop.Domain.Interfaces;
using Webshop.Infrastructure.Data; using Webshop.Infrastructure.Data;
using Webshop.Infrastructure.Repositories; using Webshop.Infrastructure.Repositories;
using Webshop.Infrastructure.Services;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -108,6 +109,7 @@ builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IAddressService, AddressService>(); builder.Services.AddScoped<IAddressService, AddressService>();
builder.Services.AddScoped<ICheckoutService, CheckoutService>(); builder.Services.AddScoped<ICheckoutService, CheckoutService>();
builder.Services.AddScoped<IReviewService, ReviewService>(); builder.Services.AddScoped<IReviewService, ReviewService>();
builder.Services.AddScoped<IFileStorageService, LocalFileStorageService>();
// Externe Dienste (Resend) // Externe Dienste (Resend)

View File

@@ -1,5 +1,4 @@
// src/Webshop.Application/DTOs/Products/CreateAdminProductDto.cs // src/Webshop.Application/DTOs/Products/CreateAdminProductDto.cs
using Microsoft.AspNetCore.Http; // Für IFormFile
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
@@ -21,13 +20,12 @@ namespace Webshop.Application.DTOs.Products
[Required] [Required]
public string Slug { get; set; } public string Slug { get; set; }
// << NEU: Felder für den Bildupload >> // << KORREKTUR: Felder für Bild-URLs, keine IFormFile >>
public IFormFile? MainImageFile { get; set; } public string? MainImageUrl { get; set; }
public List<IFormFile>? AdditionalImageFiles { get; set; } public List<string>? AdditionalImageUrls { get; set; }
public List<Guid> CategorieIds { get; set; } = new List<Guid>(); public List<Guid> CategorieIds { get; set; } = new List<Guid>();
// ... weitere Felder, die beim Erstellen benötigt werden (z.B. SupplierId, PurchasePrice etc.) ...
public decimal? Weight { get; set; } public decimal? Weight { get; set; }
public decimal? OldPrice { get; set; } public decimal? OldPrice { get; set; }
public Guid? SupplierId { get; set; } public Guid? SupplierId { get; set; }

View File

@@ -1,5 +1,4 @@
// src/Webshop.Application/DTOs/Products/UpdateAdminProductDto.cs // src/Webshop.Application/DTOs/Products/UpdateAdminProductDto.cs
using Microsoft.AspNetCore.Http;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
@@ -23,14 +22,12 @@ namespace Webshop.Application.DTOs.Products
[Required] [Required]
public string Slug { get; set; } public string Slug { get; set; }
// << NEU: Felder für den Bildupload und -verwaltung >> // << KORREKTUR: Felder für Bild-URLs >>
public IFormFile? MainImageFile { get; set; } // Optional: Neues Hauptbild hochladen public string? MainImageUrl { get; set; }
public List<IFormFile>? AdditionalImageFiles { get; set; } // Optional: Weitere Bilder hochladen public List<string>? AdditionalImageUrls { get; set; }
public List<Guid>? ImagesToDelete { get; set; } // Liste der IDs von Bildern, die gelöscht werden sollen
public List<Guid> CategorieIds { get; set; } = new List<Guid>(); public List<Guid> CategorieIds { get; set; } = new List<Guid>();
// ... weitere Felder, die beim Aktualisieren benötigt werden ...
public decimal? Weight { get; set; } public decimal? Weight { get; set; }
public decimal? OldPrice { get; set; } public decimal? OldPrice { get; set; }
public Guid? SupplierId { get; set; } public Guid? SupplierId { get; set; }

View File

@@ -15,16 +15,13 @@ namespace Webshop.Application.Services.Admin
public class AdminProductService : IAdminProductService public class AdminProductService : IAdminProductService
{ {
private readonly IProductRepository _productRepository; private readonly IProductRepository _productRepository;
private readonly IFileStorageService _fileStorageService;
private readonly ApplicationDbContext _context; private readonly ApplicationDbContext _context;
public AdminProductService( public AdminProductService(
IProductRepository productRepository, IProductRepository productRepository,
IFileStorageService fileStorageService,
ApplicationDbContext context) ApplicationDbContext context)
{ {
_productRepository = productRepository; _productRepository = productRepository;
_fileStorageService = fileStorageService;
_context = context; _context = context;
} }
@@ -33,6 +30,7 @@ namespace Webshop.Application.Services.Admin
var products = await _context.Products var products = await _context.Products
.Include(p => p.Productcategories) .Include(p => p.Productcategories)
.Include(p => p.Images) .Include(p => p.Images)
.OrderBy(p => p.Name)
.ToListAsync(); .ToListAsync();
return products.Select(p => new AdminProductDto return products.Select(p => new AdminProductDto
@@ -53,7 +51,7 @@ namespace Webshop.Application.Services.Admin
SupplierId = p.SupplierId, SupplierId = p.SupplierId,
PurchasePrice = p.PurchasePrice, PurchasePrice = p.PurchasePrice,
categorieIds = p.Productcategories.Select(pc => pc.categorieId).ToList(), 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, Id = img.Id,
Url = img.Url, Url = img.Url,
@@ -90,7 +88,7 @@ namespace Webshop.Application.Services.Admin
SupplierId = product.SupplierId, SupplierId = product.SupplierId,
PurchasePrice = product.PurchasePrice, PurchasePrice = product.PurchasePrice,
categorieIds = product.Productcategories.Select(pc => pc.categorieId).ToList(), 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, Id = img.Id,
Url = img.Url, Url = img.Url,
@@ -100,23 +98,19 @@ namespace Webshop.Application.Services.Admin
}; };
} }
public async Task<AdminProductDto> CreateAdminProductAsync(CreateAdminProductDto productDto) public async Task<AdminProductDto?> CreateAdminProductAsync(CreateAdminProductDto productDto)
{ {
var images = new List<ProductImage>(); var images = new List<ProductImage>();
if (productDto.MainImageFile != null) if (!string.IsNullOrEmpty(productDto.MainImageUrl))
{ {
await using var stream = productDto.MainImageFile.OpenReadStream(); images.Add(new ProductImage { Url = productDto.MainImageUrl, IsMainImage = true, DisplayOrder = 1 });
var url = await _fileStorageService.SaveFileAsync(stream, productDto.MainImageFile.FileName, productDto.MainImageFile.ContentType);
images.Add(new ProductImage { Url = url, IsMainImage = true, DisplayOrder = 1 });
} }
if (productDto.AdditionalImageFiles != null) if (productDto.AdditionalImageUrls != null)
{ {
int order = 2; 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++ }); images.Add(new ProductImage { Url = url, IsMainImage = false, DisplayOrder = order++ });
} }
} }
@@ -130,13 +124,17 @@ namespace Webshop.Application.Services.Admin
IsActive = productDto.IsActive, IsActive = productDto.IsActive,
StockQuantity = productDto.StockQuantity, StockQuantity = productDto.StockQuantity,
Slug = productDto.Slug, Slug = productDto.Slug,
Weight = productDto.Weight,
OldPrice = productDto.OldPrice,
SupplierId = productDto.SupplierId,
PurchasePrice = productDto.PurchasePrice,
Images = images, Images = images,
Productcategories = productDto.CategorieIds.Select(cId => new Productcategorie { categorieId = cId }).ToList() Productcategories = productDto.CategorieIds.Select(cId => new Productcategorie { categorieId = cId }).ToList()
}; };
await _productRepository.AddProductAsync(newProduct); await _productRepository.AddProductAsync(newProduct);
return (await GetAdminProductByIdAsync(newProduct.Id))!; return await GetAdminProductByIdAsync(newProduct.Id);
} }
public async Task<bool> UpdateAdminProductAsync(UpdateAdminProductDto productDto) public async Task<bool> UpdateAdminProductAsync(UpdateAdminProductDto productDto)
@@ -148,34 +146,44 @@ namespace Webshop.Application.Services.Admin
if (existingProduct == null) return false; if (existingProduct == null) return false;
if (productDto.ImagesToDelete != null) // Basisdaten aktualisieren
{
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);
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)
{
int displayOrder = (existingProduct.Images.Any() ? existingProduct.Images.Max(i => i.DisplayOrder) : 0) + 1;
foreach (var file in productDto.AdditionalImageFiles)
{
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.Name = productDto.Name; existingProduct.Name = productDto.Name;
existingProduct.Description = productDto.Description; existingProduct.Description = productDto.Description;
// ... (restliche Felder aktualisieren) ... 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;
// Kategorien synchronisieren
existingProduct.Productcategories.Clear();
if (productDto.CategorieIds != null)
{
foreach (var categorieId in productDto.CategorieIds)
{
existingProduct.Productcategories.Add(new Productcategorie { categorieId = categorieId });
}
}
// 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); await _productRepository.UpdateProductAsync(existingProduct);
return true; return true;

View File

@@ -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.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Webshop.Application.DTOs.Products; using Webshop.Application.DTOs.Products;
@@ -9,8 +10,8 @@ namespace Webshop.Application.Services.Admin.Interfaces
{ {
Task<IEnumerable<AdminProductDto>> GetAllAdminProductsAsync(); Task<IEnumerable<AdminProductDto>> GetAllAdminProductsAsync();
Task<AdminProductDto?> GetAdminProductByIdAsync(Guid id); Task<AdminProductDto?> GetAdminProductByIdAsync(Guid id);
Task<AdminProductDto> CreateAdminProductAsync(CreateAdminProductDto productDto); // << NEUER TYP >> Task<AdminProductDto?> CreateAdminProductAsync(CreateAdminProductDto productDto); // << DTO-TYP GEÄNDERT >>
Task<bool> UpdateAdminProductAsync(UpdateAdminProductDto productDto); // << NEUER TYP >> Task<bool> UpdateAdminProductAsync(UpdateAdminProductDto productDto); // << DTO-TYP GEÄNDERT >>
Task<bool> DeleteAdminProductAsync(Guid id); Task<bool> DeleteAdminProductAsync(Guid id);
} }
} }