From c8635756f1cae1921d8cbb8cc5e189c26c19d465 Mon Sep 17 00:00:00 2001 From: "Tizian.Breuch" Date: Tue, 22 Jul 2025 17:26:04 +0200 Subject: [PATCH] bugs --- Webshop.Application/DTOs/ProductDto.cs | 19 +-- .../Services/Admin/AdminProductService.cs | 125 +++++++++++++++- .../Services/Admin/AdminUserService.cs | 61 +++++++- .../Services/Auth/AuthService.cs | 141 +++++++++++++++++- .../Services/Auth/IAuthService.cs | 16 +- .../Services/Public/ProductService.cs | 59 +++----- .../Webshop.Application.csproj | 13 +- Webshop.Domain/Entities/Product.cs | 2 +- 8 files changed, 359 insertions(+), 77 deletions(-) diff --git a/Webshop.Application/DTOs/ProductDto.cs b/Webshop.Application/DTOs/ProductDto.cs index 39cac7f..a223feb 100644 --- a/Webshop.Application/DTOs/ProductDto.cs +++ b/Webshop.Application/DTOs/ProductDto.cs @@ -1,18 +1,15 @@ -// src/Webshop.Application/DTOs/ProductDto.cs -namespace Webshop.Application.DTOs +namespace Webshop.Application.DTOs { public class ProductDto { - public Guid Id { get; set; } // Wird bei Erstellung oft vom Backend gesetzt + public Guid Id { get; set; } public string Name { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; // Kann null sein + public string Description { get; set; } = string.Empty; + public string SKU { get; set; } = string.Empty; // STELLEN SIE SICHER, DASS DIES DA IST public decimal Price { get; set; } - public string Sku { get; set; } = string.Empty; - public string ShortDescription { get; set; } = string.Empty; // Kann null sein, aber hier zur Vollständigkeit - public bool IsActive { get; set; } // Muss übergeben werden - public bool IsInStock { get; set; } // Muss übergeben werden - public int StockQuantity { get; set; } // Muss übergeben werden - public string Slug { get; set; } = string.Empty; // Muss übergeben werden - + public bool IsActive { get; set; } + public bool IsInStock { get; set; } + public int StockQuantity { get; set; } + public string? ImageUrl { get; set; } // STELLEN SIE SICHER, DASS DIES DA IST } } \ No newline at end of file diff --git a/Webshop.Application/Services/Admin/AdminProductService.cs b/Webshop.Application/Services/Admin/AdminProductService.cs index 6146d30..b273d05 100644 --- a/Webshop.Application/Services/Admin/AdminProductService.cs +++ b/Webshop.Application/Services/Admin/AdminProductService.cs @@ -1,12 +1,125 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// src/Webshop.Application/Services/Admin/AdminProductService.cs +using Webshop.Application.DTOs; // AdminProductDto +using Webshop.Domain.Entities; +using Webshop.Domain.Interfaces; +using System.Collections.Generic; // Sicherstellen, dass für IEnumerable vorhanden namespace Webshop.Application.Services.Admin { public class AdminProductService { + private readonly IProductRepository _productRepository; + + public AdminProductService(IProductRepository productRepository) + { + _productRepository = productRepository; + } + + public async Task> GetAllAdminProductsAsync() + { + var products = await _productRepository.GetAllProductsAsync(); + return products.Select(p => new AdminProductDto + { + Id = p.Id, + Name = p.Name, + Description = p.Description, + SKU = p.SKU, + Price = p.Price, + OldPrice = p.OldPrice, + IsActive = p.IsActive, + IsInStock = p.IsInStock, + StockQuantity = p.StockQuantity, + Weight = p.Weight, + ImageUrl = p.ImageUrl, + Slug = p.Slug, + CreatedDate = p.CreatedDate, + LastModifiedDate = p.LastModifiedDate, + SupplierId = p.SupplierId, + PurchasePrice = p.PurchasePrice + }).ToList(); + } + + public async Task GetAdminProductByIdAsync(Guid id) + { + var product = await _productRepository.GetProductByIdAsync(id); + if (product == null) return null; + + 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, + ImageUrl = product.ImageUrl, + Slug = product.Slug, + CreatedDate = product.CreatedDate, + LastModifiedDate = product.LastModifiedDate, + SupplierId = product.SupplierId, + PurchasePrice = product.PurchasePrice + }; + } + + public async Task CreateAdminProductAsync(AdminProductDto productDto) + { + var newProduct = new Product + { + Id = Guid.NewGuid(), + Name = productDto.Name, + Description = productDto.Description, + SKU = productDto.SKU, + Price = productDto.Price, + OldPrice = productDto.OldPrice, + IsActive = productDto.IsActive, + IsInStock = productDto.IsInStock, + StockQuantity = productDto.StockQuantity, + Weight = productDto.Weight, + ImageUrl = productDto.ImageUrl, + Slug = productDto.Slug, + CreatedDate = DateTimeOffset.UtcNow, + SupplierId = productDto.SupplierId, + PurchasePrice = productDto.PurchasePrice + }; + await _productRepository.AddProductAsync(newProduct); + productDto.Id = newProduct.Id; + return productDto; + } + + public async Task UpdateAdminProductAsync(AdminProductDto productDto) + { + var existingProduct = await _productRepository.GetProductByIdAsync(productDto.Id); + if (existingProduct == null) return false; + + existingProduct.Name = productDto.Name; + existingProduct.Description = productDto.Description; + existingProduct.SKU = productDto.SKU; + existingProduct.Price = productDto.Price; + existingProduct.OldPrice = productDto.OldPrice; + existingProduct.IsActive = productDto.IsActive; + existingProduct.IsInStock = productDto.IsInStock; + existingProduct.StockQuantity = productDto.StockQuantity; + existingProduct.Weight = productDto.Weight; + existingProduct.ImageUrl = productDto.ImageUrl; + existingProduct.Slug = productDto.Slug; + existingProduct.LastModifiedDate = DateTimeOffset.UtcNow; + existingProduct.SupplierId = productDto.SupplierId; + existingProduct.PurchasePrice = productDto.PurchasePrice; + + await _productRepository.UpdateProductAsync(existingProduct); + return true; + } + + public async Task DeleteAdminProductAsync(Guid id) + { + var product = await _productRepository.GetProductByIdAsync(id); + if (product == null) return false; + await _productRepository.DeleteProductAsync(product.Id); // Verwende product.Id + return true; + } } -} +} \ No newline at end of file diff --git a/Webshop.Application/Services/Admin/AdminUserService.cs b/Webshop.Application/Services/Admin/AdminUserService.cs index 813551d..fc38e71 100644 --- a/Webshop.Application/Services/Admin/AdminUserService.cs +++ b/Webshop.Application/Services/Admin/AdminUserService.cs @@ -1,12 +1,61 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// src/Webshop.Application/Services/Admin/AdminUserService.cs +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Webshop.Application.DTOs.Users; // UserDto +using System.Collections.Generic; // Sicherstellen, dass für IEnumerable vorhanden namespace Webshop.Application.Services.Admin { public class AdminUserService { + private readonly UserManager _userManager; + + public AdminUserService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task> GetAllUsersAsync() + { + var users = await _userManager.Users.ToListAsync(); + + var userDtos = new List(); + foreach (var user in users) + { + var roles = await _userManager.GetRolesAsync(user); + userDtos.Add(new UserDto + { + Id = user.Id, + Email = user.Email ?? string.Empty, + UserName = user.UserName ?? string.Empty, + Roles = roles.ToList(), + // LockoutEnd kann als Näherung für CreatedDate verwendet werden, + // da IdentityUser keine direkte CreatedDate-Eigenschaft hat. + // Ideal wäre es, ein CustomUser-Modell zu verwenden, das von IdentityUser erbt. + CreatedDate = user.LockoutEnd ?? DateTimeOffset.MinValue, + EmailConfirmed = user.EmailConfirmed + }); + } + return userDtos; + } + + public async Task GetUserByIdAsync(string userId) + { + var user = await _userManager.FindByIdAsync(userId); + if (user == null) return null; + + var roles = await _userManager.GetRolesAsync(user); + return new UserDto + { + Id = user.Id, + Email = user.Email ?? string.Empty, + UserName = user.UserName ?? string.Empty, + Roles = roles.ToList(), + CreatedDate = user.LockoutEnd ?? DateTimeOffset.MinValue, + EmailConfirmed = user.EmailConfirmed + }; + } + + // TODO: Methoden zum Aktualisieren von Rollen, Löschen von Benutzern etc. } -} +} \ No newline at end of file diff --git a/Webshop.Application/Services/Auth/AuthService.cs b/Webshop.Application/Services/Auth/AuthService.cs index bd3e760..04cc161 100644 --- a/Webshop.Application/Services/Auth/AuthService.cs +++ b/Webshop.Application/Services/Auth/AuthService.cs @@ -1,12 +1,143 @@ -using System; -using System.Collections.Generic; -using System.Linq; +// src/Webshop.Application/Services/Auth/AuthService.cs +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; using System.Text; +using Webshop.Application.DTOs.Auth; using System.Threading.Tasks; +using System.Collections.Generic; namespace Webshop.Application.Services.Auth { - public class AuthService + public class AuthService : IAuthService // Sicherstellen, dass IAuthService implementiert wird { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IConfiguration _configuration; + private readonly RoleManager _roleManager; + + public AuthService( + UserManager userManager, + SignInManager signInManager, + IConfiguration configuration, + RoleManager roleManager) + { + _userManager = userManager; + _signInManager = signInManager; + _configuration = configuration; + _roleManager = roleManager; + } + + public async Task RegisterUserAsync(RegisterRequestDto request) + { + var existingUser = await _userManager.FindByEmailAsync(request.Email); + if (existingUser != null) + { + return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = "E-Mail ist bereits registriert." }; + } + + var user = new IdentityUser { Email = request.Email, UserName = request.Email }; + var result = await _userManager.CreateAsync(user, request.Password); + + if (!result.Succeeded) + { + var errors = string.Join(" ", result.Errors.Select(e => e.Description)); + return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = errors }; + } + + if (!await _roleManager.RoleExistsAsync("Customer")) + { + await _roleManager.CreateAsync(new IdentityRole("Customer")); + } + await _userManager.AddToRoleAsync(user, "Customer"); + + var roles = await _userManager.GetRolesAsync(user); + var token = await GenerateJwtToken(user, roles); + + return new AuthResponseDto + { + IsAuthSuccessful = true, + Token = token, + UserId = user.Id, + Email = user.Email, + Roles = roles.ToList() + }; + } + + public async Task LoginUserAsync(LoginRequestDto request) + { + var user = await _userManager.FindByEmailAsync(request.Email); + if (user == null) + { + return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = "Ungültige Anmeldeinformationen." }; + } + + var signInResult = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false); + if (!signInResult.Succeeded) + { + return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = "Ungültige Anmeldeinformationen." }; + } + + var roles = await _userManager.GetRolesAsync(user); + var token = await GenerateJwtToken(user, roles); + + return new AuthResponseDto + { + IsAuthSuccessful = true, + Token = token, + UserId = user.Id, + Email = user.Email, + Roles = roles.ToList() + }; + } + + public async Task LoginAdminAsync(LoginRequestDto request) + { + var authResponse = await LoginUserAsync(request); + if (!authResponse.IsAuthSuccessful) + { + return authResponse; + } + + var user = await _userManager.FindByEmailAsync(request.Email); + if (user == null || !await _userManager.IsInRoleAsync(user, "Admin")) + { + return new AuthResponseDto { IsAuthSuccessful = false, ErrorMessage = "Keine Berechtigung." }; + } + + return authResponse; + } + + private async Task GenerateJwtToken(IdentityUser user, IList roles) + { + var claims = new List + { + new Claim(JwtRegisteredClaimNames.Sub, user.Id), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim(JwtRegisteredClaimNames.Email, user.Email!) + }; + + foreach (var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var jwtSettings = _configuration.GetSection("JwtSettings"); + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Secret"]!)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + var expires = DateTime.UtcNow.AddMinutes(double.Parse(jwtSettings["ExpirationMinutes"]!)); + + var token = new JwtSecurityToken( + issuer: jwtSettings["Issuer"], + audience: jwtSettings["Audience"], + claims: claims, + expires: expires, + signingCredentials: creds + ); + + return new JwtSecurityTokenHandler().WriteToken(token); + } } -} +} \ No newline at end of file diff --git a/Webshop.Application/Services/Auth/IAuthService.cs b/Webshop.Application/Services/Auth/IAuthService.cs index 82eceed..c0c4ec6 100644 --- a/Webshop.Application/Services/Auth/IAuthService.cs +++ b/Webshop.Application/Services/Auth/IAuthService.cs @@ -1,12 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// src/Webshop.Application/Services/Auth/IAuthService.cs +using Webshop.Application.DTOs.Auth; +using System.Threading.Tasks; // Sicherstellen, dass für Task vorhanden +using System.Collections.Generic; // Sicherstellen, dass für IList vorhanden namespace Webshop.Application.Services.Auth { - public class IAuthService + public interface IAuthService { + Task RegisterUserAsync(RegisterRequestDto request); + Task LoginUserAsync(LoginRequestDto request); + Task LoginAdminAsync(LoginRequestDto request); } -} +} \ No newline at end of file diff --git a/Webshop.Application/Services/Public/ProductService.cs b/Webshop.Application/Services/Public/ProductService.cs index bb8cbda..69830c8 100644 --- a/Webshop.Application/Services/Public/ProductService.cs +++ b/Webshop.Application/Services/Public/ProductService.cs @@ -1,11 +1,12 @@ -// src/Webshop.Application/Services/ProductService.cs -using Webshop.Application.DTOs; -using Webshop.Domain.Entities; +// src/Webshop.Application/Services/Public/ProductService.cs +using Webshop.Application.DTOs; // ProductDto using Webshop.Domain.Interfaces; +using System.Collections.Generic; // Sicherstellen, dass für IEnumerable vorhanden +using Webshop.Domain.Entities; namespace Webshop.Application.Services.Public { - public class ProductService + public class ProductService // Sie haben den Namen "ProductService" beibehalten { private readonly IProductRepository _productRepository; @@ -18,62 +19,40 @@ namespace Webshop.Application.Services.Public { var productsFromDb = await _productRepository.GetAllProductsAsync(); - var productDtos = productsFromDb.Select(p => new ProductDto + return productsFromDb.Select(p => new ProductDto { Id = p.Id, Name = p.Name, Description = p.Description, Price = p.Price, - Sku = p.SKU - }); - - return productDtos; + SKU = p.SKU, + IsActive = p.IsActive, + IsInStock = p.IsInStock, + StockQuantity = p.StockQuantity, + ImageUrl = p.ImageUrl + }).ToList(); } + // Beispiel: Methode zum Erstellen eines Produkts (wenn PublicService auch Schreiben erlaubt) + // Normalerweise wäre das im AdminProductService public async Task CreateProductAsync(ProductDto productDto) { var newProduct = new Product { - // Felder aus DTO + Id = Guid.NewGuid(), Name = productDto.Name, Description = productDto.Description, + SKU = productDto.SKU, Price = productDto.Price, - SKU = productDto.Sku, - ShortDescription = productDto.ShortDescription, - IsActive = productDto.IsActive, - IsInStock = productDto.IsInStock, + IsActive = true, // Annahme + IsInStock = true, // Annahme StockQuantity = productDto.StockQuantity, - Slug = productDto.Slug, - - - Id = Guid.NewGuid(), + ImageUrl = productDto.ImageUrl, CreatedDate = DateTimeOffset.UtcNow, - LastModifiedDate = null, - }; - - if (string.IsNullOrWhiteSpace(newProduct.Slug) && !string.IsNullOrWhiteSpace(newProduct.Name)) - { - newProduct.Slug = GenerateSlug(newProduct.Name); - } - await _productRepository.AddProductAsync(newProduct); - productDto.Id = newProduct.Id; return productDto; } - - private string GenerateSlug(string name) - { - if (string.IsNullOrWhiteSpace(name)) return Guid.NewGuid().ToString(); // Fallback - var slug = name.ToLowerInvariant() - .Replace(" ", "-") - .Replace("ä", "ae").Replace("ö", "oe").Replace("ü", "ue") - .Replace("ß", "ss") - .Trim(); - // Entferne ungültige Zeichen - slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[^a-z0-9-]", ""); - return slug; - } } } \ No newline at end of file diff --git a/Webshop.Application/Webshop.Application.csproj b/Webshop.Application/Webshop.Application.csproj index a1407df..5a4606c 100644 --- a/Webshop.Application/Webshop.Application.csproj +++ b/Webshop.Application/Webshop.Application.csproj @@ -8,9 +8,20 @@ + + + + - + + + + + + + + diff --git a/Webshop.Domain/Entities/Product.cs b/Webshop.Domain/Entities/Product.cs index 4407967..3a0542a 100644 --- a/Webshop.Domain/Entities/Product.cs +++ b/Webshop.Domain/Entities/Product.cs @@ -21,7 +21,7 @@ public class Product [Required] [MaxLength(50)] - public string SKU { get; set; } + public string SKU { get; set; } = string.Empty; [Required] public decimal Price { get; set; }