bild update

This commit is contained in:
Tizian.Breuch
2025-08-08 16:00:42 +02:00
parent c2af53c5ce
commit 37dd4f2db1
4 changed files with 65 additions and 34 deletions

View File

@@ -1,13 +1,12 @@
// src/Webshop.Api/Controllers/Admin/AdminProductsController.cs // src/Webshop.Api/Controllers/Admin/AdminProductsController.cs
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; // F<>r IFormFile using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; 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 Webshop.Application.Services.Admin.Interfaces; using Webshop.Application.Services.Admin.Interfaces;
namespace Webshop.Api.Controllers.Admin namespace Webshop.Api.Controllers.Admin
{ {
[ApiController] [ApiController]
@@ -38,20 +37,29 @@ namespace Webshop.Api.Controllers.Admin
} }
[HttpPost] [HttpPost]
public async Task<ActionResult<AdminProductDto>> CreateAdminProduct([FromBody] CreateAdminProductDto productDto) // << ZUR<55>CK ZU [FromBody] >> [Consumes("multipart/form-data")]
public async Task<ActionResult<AdminProductDto>> CreateAdminProduct([FromForm] CreateAdminProductDto productDto)
{ {
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) 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) // << ZUR<55>CK ZU [FromBody] >> [Consumes("multipart/form-data")]
public async Task<IActionResult> UpdateAdminProduct(Guid id, [FromForm] UpdateAdminProductDto productDto)
{ {
if (id != productDto.Id) return BadRequest(); if (id != productDto.Id) return BadRequest("ID in URL und Body stimmen nicht <20>berein.");
if (!ModelState.IsValid) return BadRequest(ModelState); if (!ModelState.IsValid) return BadRequest(ModelState);
var success = await _adminProductService.UpdateAdminProductAsync(productDto); var success = await _adminProductService.UpdateAdminProductAsync(productDto);
if (!success) return NotFound(); if (!success) return NotFound();
return NoContent(); return NoContent();
} }

View File

@@ -1,4 +1,5 @@
// 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;
@@ -15,14 +16,13 @@ namespace Webshop.Application.DTOs.Products
[Required] [Required]
public decimal Price { get; set; } public decimal Price { get; set; }
public bool IsActive { get; set; } = true; public bool IsActive { get; set; } = true;
public bool IsInStock { get; set; } = true;
public int StockQuantity { get; set; } public int StockQuantity { get; set; }
[Required] [Required]
public string Slug { get; set; } public string Slug { get; set; }
// << KORREKTUR: Felder für Bild-URLs, keine IFormFile >> // << ZURÜCK ZU IFormFile >>
public string? MainImageUrl { get; set; } public IFormFile? MainImageFile { get; set; }
public List<string>? AdditionalImageUrls { get; set; } public List<IFormFile>? AdditionalImageFiles { get; set; }
public List<Guid> CategorieIds { get; set; } = new List<Guid>(); public List<Guid> CategorieIds { get; set; } = new List<Guid>();

View File

@@ -1,4 +1,5 @@
// 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;
@@ -17,14 +18,14 @@ namespace Webshop.Application.DTOs.Products
[Required] [Required]
public decimal Price { get; set; } public decimal Price { get; set; }
public bool IsActive { get; set; } public bool IsActive { get; set; }
public bool IsInStock { get; set; }
public int StockQuantity { get; set; } public int StockQuantity { get; set; }
[Required] [Required]
public string Slug { get; set; } public string Slug { get; set; }
// << KORREKTUR: Felder für Bild-URLs >> // << ZURÜCK ZU IFormFile >>
public string? MainImageUrl { get; set; } public IFormFile? MainImageFile { get; set; }
public List<string>? AdditionalImageUrls { get; set; } public List<IFormFile>? AdditionalImageFiles { get; set; }
public List<Guid>? ImagesToDelete { get; set; }
public List<Guid> CategorieIds { get; set; } = new List<Guid>(); public List<Guid> CategorieIds { get; set; } = new List<Guid>();

View File

@@ -15,13 +15,16 @@ 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,
ApplicationDbContext context) IFileStorageService fileStorageService,
ApplicationDbContext context)
{ {
_productRepository = productRepository; _productRepository = productRepository;
_fileStorageService = fileStorageService;
_context = context; _context = context;
} }
@@ -102,15 +105,21 @@ namespace Webshop.Application.Services.Admin
{ {
var images = new List<ProductImage>(); var images = new List<ProductImage>();
if (!string.IsNullOrEmpty(productDto.MainImageUrl)) // Hauptbild hochladen
if (productDto.MainImageFile != null)
{ {
images.Add(new ProductImage { Url = productDto.MainImageUrl, IsMainImage = true, DisplayOrder = 1 }); 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 });
} }
if (productDto.AdditionalImageUrls != null) // Weitere Bilder hochladen
if (productDto.AdditionalImageFiles != null)
{ {
int order = 2; int order = 2;
foreach (var url in productDto.AdditionalImageUrls) foreach (var file in productDto.AdditionalImageFiles)
{ {
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++ });
} }
} }
@@ -146,6 +155,34 @@ namespace Webshop.Application.Services.Admin
if (existingProduct == null) return false; if (existingProduct == null) return false;
// Bilder l<>schen
if (productDto.ImagesToDelete != null && productDto.ImagesToDelete.Any())
{
var imagesToRemove = existingProduct.Images.Where(img => productDto.ImagesToDelete.Contains(img.Id)).ToList();
_context.ProductImages.RemoveRange(imagesToRemove);
}
// Hauptbild aktualisieren/hochladen
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 });
}
// Weitere Bilder hinzuf<75>gen
if (productDto.AdditionalImageFiles != null && productDto.AdditionalImageFiles.Any())
{
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++ });
}
}
// Basisdaten aktualisieren // Basisdaten aktualisieren
existingProduct.Name = productDto.Name; existingProduct.Name = productDto.Name;
existingProduct.Description = productDto.Description; existingProduct.Description = productDto.Description;
@@ -170,21 +207,6 @@ namespace Webshop.Application.Services.Admin
} }
} }
// 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;
} }