test bilder upload
Some checks failed
Branch - test - Build and Push Backend API Docker Image / build-and-push (push) Failing after 20s

This commit is contained in:
Tizian.Breuch
2025-11-07 09:49:51 +01:00
parent c078526800
commit 7aa5ec9500
4 changed files with 164 additions and 48 deletions

View File

@@ -1,4 +1,5 @@
// src/Webshop.Application/Services/Admin/AdminProductService.cs
using Microsoft.EntityFrameworkCore;
using Webshop.Domain.Entities;
using Webshop.Domain.Interfaces;
@@ -29,47 +30,42 @@ namespace Webshop.Application.Services.Admin
_context = context;
}
// ... (GetAllAdminProductsAsync und GetAdminProductByIdAsync bleiben unver<65>ndert) ...
#region Unchanged Methods
public async Task<ServiceResult<IEnumerable<AdminProductDto>>> GetAllAdminProductsAsync()
{
var products = await _context.Products.Include(p => p.Productcategories).Include(p => p.Images).OrderBy(p => p.Name).ToListAsync();
var products = await _context.Products
.Include(p => p.Images)
.Include(p => p.Productcategories)
.OrderBy(p => p.Name)
.ToListAsync();
var dtos = products.Select(MapToAdminDto).ToList();
return ServiceResult.Ok<IEnumerable<AdminProductDto>>(dtos);
}
public async Task<ServiceResult<AdminProductDto>> GetAdminProductByIdAsync(Guid id)
{
var product = await _context.Products.Include(p => p.Productcategories).Include(p => p.Images).FirstOrDefaultAsync(p => p.Id == id);
if (product == null) { return ServiceResult.Fail<AdminProductDto>(ServiceResultType.NotFound, $"Produkt mit ID '{id}' nicht gefunden."); }
var product = await _productRepository.GetProductByIdForUpdateAsync(id);
if (product == null)
{
return ServiceResult.Fail<AdminProductDto>(ServiceResultType.NotFound, $"Produkt mit ID '{id}' nicht gefunden.");
}
return ServiceResult.Ok(MapToAdminDto(product));
}
#endregion
public async Task<ServiceResult<AdminProductDto>> CreateAdminProductAsync(CreateAdminProductDto productDto)
{
// Ihre Create-Logik hier... (unver<65>ndert)
var skuExists = await _context.Products.AnyAsync(p => p.SKU == productDto.SKU);
if (skuExists)
{
return ServiceResult.Fail<AdminProductDto>(ServiceResultType.Conflict, $"Ein Produkt mit der SKU '{productDto.SKU}' existiert bereits.");
}
if (skuExists) { return ServiceResult.Fail<AdminProductDto>(ServiceResultType.Conflict, $"Ein Produkt mit der SKU '{productDto.SKU}' existiert bereits."); }
var slugExists = await _context.Products.AnyAsync(p => p.Slug == productDto.Slug);
if (slugExists)
{
return ServiceResult.Fail<AdminProductDto>(ServiceResultType.Conflict, $"Ein Produkt mit dem Slug '{productDto.Slug}' existiert bereits.");
}
if (slugExists) { return ServiceResult.Fail<AdminProductDto>(ServiceResultType.Conflict, $"Ein Produkt mit dem Slug '{productDto.Slug}' existiert bereits."); }
var images = new List<ProductImage>();
// << KORREKTUR: Null-Pr<50>fung f<>r das Hauptbild >>
if (productDto.MainImageFile != null && productDto.MainImageFile.Length > 0)
{
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 });
}
// << KORREKTUR: Null-Pr<50>fung f<>r zus<75>tzliche Bilder >>
if (productDto.AdditionalImageFiles != null)
{
int order = 2;
@@ -83,17 +79,19 @@ namespace Webshop.Application.Services.Admin
}
}
}
var newProduct = new Product
{
Name = productDto.Name,
Description = productDto.Description,
SKU = productDto.SKU,
Price = productDto.Price,
IsActive = productDto.IsActive,
StockQuantity = productDto.StockQuantity,
IsInStock = productDto.StockQuantity > 0,
Slug = productDto.Slug,
Weight = productDto.Weight,
OldPrice = productDto.OldPrice,
SupplierId = productDto.SupplierId,
PurchasePrice = productDto.PurchasePrice,
@@ -102,40 +100,115 @@ namespace Webshop.Application.Services.Admin
Images = images,
Productcategories = productDto.CategorieIds.Select(cId => new Productcategorie { categorieId = cId }).ToList()
};
await _productRepository.AddProductAsync(newProduct);
return ServiceResult.Ok(MapToAdminDto(newProduct));
}
// ... (UpdateAdminProductAsync und DeleteAdminProductAsync und MapToAdminDto bleiben unver<65>ndert) ...
#region Unchanged Methods
public async Task<ServiceResult> UpdateAdminProductAsync(UpdateAdminProductDto productDto)
{
var existingProduct = await _context.Products.Include(p => p.Images).Include(p => p.Productcategories).FirstOrDefaultAsync(p => p.Id == productDto.Id);
if (existingProduct == null) { return ServiceResult.Fail(ServiceResultType.NotFound, $"Produkt mit ID '{productDto.Id}' nicht gefunden."); }
var existingProduct = await _productRepository.GetProductByIdForUpdateAsync(productDto.Id);
if (existingProduct == null)
{
return ServiceResult.Fail(ServiceResultType.NotFound, $"Produkt mit ID '{productDto.Id}' nicht gefunden.");
}
var skuExists = await _context.Products.AnyAsync(p => p.SKU == productDto.SKU && p.Id != productDto.Id);
if (skuExists) { return ServiceResult.Fail(ServiceResultType.Conflict, $"Ein anderes Produkt mit der SKU '{productDto.SKU}' existiert bereits."); }
if (skuExists)
{
return ServiceResult.Fail(ServiceResultType.Conflict, $"Ein anderes Produkt mit der SKU '{productDto.SKU}' existiert bereits.");
}
var slugExists = await _context.Products.AnyAsync(p => p.Slug == productDto.Slug && p.Id != productDto.Id);
if (slugExists) { return ServiceResult.Fail(ServiceResultType.Conflict, $"Ein anderes Produkt mit dem Slug '{productDto.Slug}' existiert bereits."); }
if (productDto.ImagesToDelete != null && productDto.ImagesToDelete.Any()) { 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 && 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++ }); } }
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; existingProduct.IsFeatured = productDto.IsFeatured; existingProduct.FeaturedDisplayOrder = productDto.FeaturedDisplayOrder;
existingProduct.Productcategories.Clear(); if (productDto.CategorieIds != null) { foreach (var categorieId in productDto.CategorieIds) { existingProduct.Productcategories.Add(new Productcategorie { categorieId = categorieId }); } }
await _productRepository.UpdateProductAsync(existingProduct); return ServiceResult.Ok();
if (slugExists)
{
return ServiceResult.Fail(ServiceResultType.Conflict, $"Ein anderes Produkt mit dem Slug '{productDto.Slug}' existiert bereits.");
}
// --- KORRIGIERTER METHODENAUFRUF ---
existingProduct.UpdateFromDto(
productDto.Name, productDto.Description, productDto.SKU, productDto.Price, productDto.OldPrice,
productDto.IsActive, productDto.StockQuantity, productDto.Slug, productDto.Weight,
productDto.SupplierId, productDto.PurchasePrice, productDto.IsFeatured, productDto.FeaturedDisplayOrder,
productDto.CategorieIds
);
// Logik f<>r Bilder (unver<65>ndert)
if (productDto.ImagesToDelete != null && productDto.ImagesToDelete.Any())
{
var imagesToRemove = existingProduct.Images.Where(img => productDto.ImagesToDelete.Contains(img.Id)).ToList();
foreach (var image in imagesToRemove)
{
existingProduct.Images.Remove(image);
}
}
if (productDto.MainImageFile != null)
{
var oldMainImage = existingProduct.Images.FirstOrDefault(i => i.IsMainImage);
if (oldMainImage != null) { existingProduct.Images.Remove(oldMainImage); }
foreach (var img in existingProduct.Images) { img.IsMainImage = false; }
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 });
}
if (productDto.AdditionalImageFiles != null && productDto.AdditionalImageFiles.Any())
{
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 });
}
}
int currentOrder = 1;
foreach (var image in existingProduct.Images.OrderBy(img => !img.IsMainImage).ThenBy(img => img.Id))
{
image.DisplayOrder = currentOrder++;
}
await _productRepository.UpdateProductAsync();
return ServiceResult.Ok();
}
public async Task<ServiceResult> DeleteAdminProductAsync(Guid id)
{
var product = await _productRepository.GetProductByIdAsync(id);
if (product == null) { return ServiceResult.Fail(ServiceResultType.NotFound, $"Produkt mit ID '{id}' nicht gefunden."); }
await _productRepository.DeleteProductAsync(id); return ServiceResult.Ok();
if (product == null)
{
return ServiceResult.Fail(ServiceResultType.NotFound, $"Produkt mit ID '{id}' nicht gefunden.");
}
await _productRepository.DeleteProductAsync(id);
return ServiceResult.Ok();
}
private AdminProductDto MapToAdminDto(Product product)
{
return new AdminProductDto { Id = product.Id, Name = product.Name, Description = product.Description, SKU = product.SKU, Price = product.Price, OldPrice = product.OldPrice, IsActive = product.IsActive, IsInStock = product.IsInStock, StockQuantity = product.StockQuantity, Weight = product.Weight, Slug = product.Slug, CreatedDate = product.CreatedDate, LastModifiedDate = product.LastModifiedDate, SupplierId = product.SupplierId, PurchasePrice = product.PurchasePrice, IsFeatured = product.IsFeatured, FeaturedDisplayOrder = product.FeaturedDisplayOrder, categorieIds = product.Productcategories.Select(pc => pc.categorieId).ToList(), Images = product.Images.OrderBy(i => i.DisplayOrder).Select(img => new ProductImageDto { Id = img.Id, Url = img.Url, IsMainImage = img.IsMainImage, DisplayOrder = img.DisplayOrder }).ToList() };
}
#endregion
return new AdminProductDto
{
Id = product.Id,
Name = product.Name,
Description = product.Description,
SKU = product.SKU,
Price = product.Price,
OldPrice = product.OldPrice,
IsActive = product.IsActive,
IsInStock = product.IsInStock,
StockQuantity = product.StockQuantity,
Weight = product.Weight,
Slug = product.Slug,
CreatedDate = product.CreatedDate,
LastModifiedDate = product.LastModifiedDate,
SupplierId = product.SupplierId,
PurchasePrice = product.PurchasePrice,
IsFeatured = product.IsFeatured,
FeaturedDisplayOrder = product.FeaturedDisplayOrder,
categorieIds = product.Productcategories.Select(pc => pc.categorieId).ToList(),
Images = product.Images.OrderBy(i => i.DisplayOrder).Select(img => new ProductImageDto { Id = img.Id, Url = img.Url, IsMainImage = img.IsMainImage, DisplayOrder = img.DisplayOrder }).ToList()
};
}
}
}

View File

@@ -16,8 +16,7 @@ namespace Webshop.Domain.Entities
[MaxLength(4000)]
public string? Description { get; set; }
[MaxLength(500)]
public string? ShortDescription { get; set; }
[Required, MaxLength(50)]
public string SKU { get; set; } = string.Empty;
[Required]
public decimal Price { get; set; }
@@ -29,9 +28,7 @@ namespace Webshop.Domain.Entities
[Required]
public int StockQuantity { get; set; }
public decimal? Weight { get; set; }
public decimal? Width { get; set; }
public decimal? Height { get; set; }
public decimal? Length { get; set; }
[Required, MaxLength(255)]
public string Slug { get; set; } = string.Empty;
@@ -53,5 +50,42 @@ namespace Webshop.Domain.Entities
public virtual ICollection<ProductDiscount> ProductDiscounts { get; set; } = new List<ProductDiscount>();
public virtual ICollection<Productcategorie> Productcategories { get; set; } = new List<Productcategorie>();
public virtual ICollection<ProductImage> Images { get; set; } = new List<ProductImage>();
// --- HINZUGEFÜGTE METHODE ZUR AKTUALISIERUNG ---
// Diese Methode kapselt die Logik zur Aktualisierung der Entität aus einem DTO.
// Sie stellt sicher, dass alle relevanten Felder konsistent geändert werden.
public void UpdateFromDto(
string name, string? description, string sku, decimal price, decimal? oldPrice,
bool isActive, int stockQuantity, string slug, decimal? weight,
Guid? supplierId, decimal? purchasePrice, bool isFeatured, int featuredDisplayOrder,
List<Guid> categorieIds)
{
// 1. Einfache Eigenschaften aktualisieren
Name = name;
Description = description;
SKU = sku;
Price = price;
OldPrice = oldPrice;
IsActive = isActive;
StockQuantity = stockQuantity;
IsInStock = stockQuantity > 0; // Abgeleitete Logik
Slug = slug;
Weight = weight;
SupplierId = supplierId;
PurchasePrice = purchasePrice;
IsFeatured = isFeatured;
FeaturedDisplayOrder = featuredDisplayOrder;
LastModifiedDate = DateTimeOffset.UtcNow;
// 2. Kategori-Beziehungen sauber neu aufbauen
Productcategories.Clear();
if (categorieIds != null)
{
foreach (var catId in categorieIds)
{
Productcategories.Add(new Productcategorie { ProductId = this.Id, categorieId = catId });
}
}
}
}
}

View File

@@ -12,7 +12,8 @@ namespace Webshop.Domain.Interfaces
Task<Product?> GetProductByIdAsync(Guid id);
Task<Product?> GetBySlugAsync(string slug);
Task AddProductAsync(Product product);
Task UpdateProductAsync(Product product);
Task<Product?> GetProductByIdForUpdateAsync(Guid id); // NEU
Task UpdateProductAsync(); // GE<47>NDERT (parameterlos)
Task DeleteProductAsync(Guid id);
}
}

View File

@@ -1,3 +1,4 @@
// src/Webshop.Infrastructure/Repositories/ProductRepository.cs
using Microsoft.EntityFrameworkCore;
using Webshop.Domain.Entities;
using Webshop.Domain.Interfaces;
@@ -8,7 +9,7 @@ using System.Threading.Tasks;
namespace Webshop.Infrastructure.Repositories
{
public class ProductRepository : IProductRepository
public class ProductRepository : IProductRepository // Implementiert das korrigierte Interface
{
private readonly ApplicationDbContext _context;
@@ -27,22 +28,29 @@ namespace Webshop.Infrastructure.Repositories
return await _context.Products.FindAsync(id);
}
// --- HIER DIE NEUE METHODE IMPLEMENTIEREN ---
public async Task<Product?> GetBySlugAsync(string slug)
{
// Sucht nur nach aktiven Produkten, was für die öffentliche Ansicht korrekt ist.
return await _context.Products.FirstOrDefaultAsync(p => p.Slug == slug && p.IsActive);
}
// --- NEUE METHODE ---
public async Task<Product?> GetProductByIdForUpdateAsync(Guid id)
{
return await _context.Products
.Include(p => p.Images)
.Include(p => p.Productcategories)
.FirstOrDefaultAsync(p => p.Id == id);
}
public async Task AddProductAsync(Product product)
{
await _context.Products.AddAsync(product);
await _context.SaveChangesAsync();
}
public async Task UpdateProductAsync(Product product)
// --- KORRIGIERTE UPDATE-METHODE (OHNE PARAMETER) ---
public async Task UpdateProductAsync()
{
//_context.Products.Update(product);
await _context.SaveChangesAsync();
}