test bilder
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 12:12:46 +01:00
parent 63684313aa
commit 2839eccf71
2 changed files with 125 additions and 10 deletions
@@ -35,5 +35,7 @@ namespace Webshop.Application.DTOs.Products
public decimal? PurchasePrice { get; set; } public decimal? PurchasePrice { get; set; }
public bool IsFeatured { get; set; } public bool IsFeatured { get; set; }
public int FeaturedDisplayOrder { get; set; } public int FeaturedDisplayOrder { get; set; }
public uint RowVersion { get; set; }
}
} }
} }
@@ -111,18 +111,131 @@ namespace Webshop.Application.Services.Admin
#region Unchanged Methods #region Unchanged Methods
public async Task<ServiceResult> UpdateAdminProductAsync(UpdateAdminProductDto productDto) 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); // 1. Produkt aus der DB laden (inklusive der zugehörigen Bilder und Kategorien)
if (existingProduct == null) { return ServiceResult.Fail(ServiceResultType.NotFound, $"Produkt mit ID '{productDto.Id}' nicht gefunden."); } 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.");
}
// 2. Validierung (SKU und Slug auf Einzigartigkeit prüfen)
var skuExists = await _context.Products.AnyAsync(p => p.SKU == productDto.SKU && p.Id != productDto.Id); 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); 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 (slugExists)
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 }); } return ServiceResult.Fail(ServiceResultType.Conflict, $"Ein anderes Produkt mit dem Slug '{productDto.Slug}' existiert bereits.");
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 }); } } // 3. WICHTIG: Den Concurrency Token setzen!
await _productRepository.UpdateProductAsync(existingProduct); return ServiceResult.Ok(); // Sagt Entity Framework, welche Version des Produkts der Benutzer ursprünglich hatte.
// "xmin" ist der Name der Concurrency-Spalte in PostgreSQL.
_context.Entry(existingProduct).Property("xmin").OriginalValue = productDto.RowVersion;
// 4. Bilder-Logik (aufgeräumt und klarer)
await HandleImageUpdates(productDto, existingProduct);
// 5. Produkt-Eigenschaften mappen
MapDtoToEntity(productDto, existingProduct);
// 6. Kategorie-Verknüpfungen aktualisieren
UpdateProductCategories(productDto, existingProduct);
// 7. Speichern
try
{
await _productRepository.UpdateProductAsync(existingProduct);
return ServiceResult.Ok();
}
catch (DbUpdateConcurrencyException)
{
// Dieser Fehler wird jetzt ausgelöst, wenn jemand anderes das Produkt in der Zwischenzeit geändert hat.
return ServiceResult.Fail(ServiceResultType.Conflict, "Die Produktdaten wurden von einem anderen Benutzer geändert. Bitte laden Sie die Seite neu.");
}
}
private void MapDtoToEntity(UpdateAdminProductDto dto, Product entity)
{
entity.Name = dto.Name;
entity.Description = dto.Description;
entity.SKU = dto.SKU;
entity.Price = dto.Price;
entity.IsActive = dto.IsActive;
entity.StockQuantity = dto.StockQuantity;
entity.Slug = dto.Slug;
entity.Weight = dto.Weight;
entity.OldPrice = dto.OldPrice;
entity.SupplierId = dto.SupplierId;
entity.PurchasePrice = dto.PurchasePrice;
entity.LastModifiedDate = DateTimeOffset.UtcNow;
entity.IsFeatured = dto.IsFeatured;
entity.FeaturedDisplayOrder = dto.FeaturedDisplayOrder;
}
private async Task HandleImageUpdates(UpdateAdminProductDto dto, Product entity)
{
// Bilder zum Löschen entfernen
if (dto.ImagesToDelete != null && dto.ImagesToDelete.Any())
{
var imagesToRemove = entity.Images.Where(img => dto.ImagesToDelete.Contains(img.Id)).ToList();
foreach (var image in imagesToRemove)
{
// Optional: Datei auch aus dem Storage löschen
// await _fileStorageService.DeleteFileAsync(image.Url);
}
_context.ProductImages.RemoveRange(imagesToRemove);
}
// Hauptbild aktualisieren/hinzufügen
if (dto.MainImageFile != null)
{
// Altes Hauptbild als "nicht Hauptbild" markieren oder löschen
var oldMainImage = entity.Images.FirstOrDefault(i => i.IsMainImage);
if (oldMainImage != null) oldMainImage.IsMainImage = false;
await using var stream = dto.MainImageFile.OpenReadStream();
var url = await _fileStorageService.SaveFileAsync(stream, dto.MainImageFile.FileName, dto.MainImageFile.ContentType);
entity.Images.Add(new ProductImage { Url = url, IsMainImage = true });
}
else if (dto.MainImageId.HasValue) // Wenn ein existierendes Bild zum Hauptbild gemacht wird
{
entity.Images.ToList().ForEach(i => i.IsMainImage = (i.Id == dto.MainImageId.Value));
}
// Zusätzliche neue Bilder hinzufügen
if (dto.AdditionalImageFiles != null && dto.AdditionalImageFiles.Any())
{
foreach (var file in dto.AdditionalImageFiles)
{
await using var stream = file.OpenReadStream();
var url = await _fileStorageService.SaveFileAsync(stream, file.FileName, file.ContentType);
entity.Images.Add(new ProductImage { Url = url, IsMainImage = false });
}
}
// DisplayOrder neu berechnen
int displayOrder = 1;
var orderedImages = entity.Images.OrderByDescending(i => i.IsMainImage).ThenBy(i => i.CreatedDate).ToList();
orderedImages.ForEach(i => i.DisplayOrder = displayOrder++);
}
private void UpdateProductCategories(UpdateAdminProductDto dto, Product entity)
{
entity.Productcategories.Clear();
if (dto.CategorieIds != null)
{
foreach (var categoryId in dto.CategorieIds)
{
entity.Productcategories.Add(new Productcategorie { categorieId = categoryId });
}
}
} }
public async Task<ServiceResult> DeleteAdminProductAsync(Guid id) public async Task<ServiceResult> DeleteAdminProductAsync(Guid id)