From c1ee56c81cac7b4797696320c780cb5b71677339 Mon Sep 17 00:00:00 2001 From: "Tizian.Breuch" Date: Tue, 29 Jul 2025 14:04:35 +0200 Subject: [PATCH] try --- .../Customers/ProfileController.cs | 70 +++++++----- Webshop.Api/Program.cs | 69 +++++++++--- .../SwaggerFilters/AddExampleSchemaFilter.cs | 2 +- .../DTOs/Auth/ChangePasswordRequestDto.cs | 19 ++++ .../Customers/UpdateCustomerProfileDto.cs | 18 ++- .../Users/AdminResetPasswordRequestDto.cs | 19 ++++ Webshop.Application/DTOs/Users/UserDto.cs | 27 +++-- .../Services/Admin/AdminProductService.cs | 1 + .../Services/Admin/AdminSupplierService.cs | 1 + .../Services/Admin/AdminUserService.cs | 1 + .../Admin/Interfaces/IAdminUserService.cs | 3 +- .../Services/Customers/CustomerService.cs | 104 +++++++++++++++--- .../Customers/Interfaces/ICustomerService.cs | 11 +- Webshop.Domain/Entities/ApplicationUser.cs | 13 --- Webshop.Domain/Entities/Customer.cs | 41 ++++--- Webshop.Domain/Identity/ApplicationUser.cs | 16 +++ .../Interfaces/ICustomerRepository.cs | 12 +- .../Repositories/CustomerRepository.cs | 20 ++-- .../Repositories/SupplierRepository.cs | 17 ++- 19 files changed, 339 insertions(+), 125 deletions(-) create mode 100644 Webshop.Application/DTOs/Auth/ChangePasswordRequestDto.cs create mode 100644 Webshop.Application/DTOs/Users/AdminResetPasswordRequestDto.cs delete mode 100644 Webshop.Domain/Entities/ApplicationUser.cs create mode 100644 Webshop.Domain/Identity/ApplicationUser.cs diff --git a/Webshop.Api/Controllers/Customers/ProfileController.cs b/Webshop.Api/Controllers/Customers/ProfileController.cs index 1dfea20..e795be6 100644 --- a/Webshop.Api/Controllers/Customers/ProfileController.cs +++ b/Webshop.Api/Controllers/Customers/ProfileController.cs @@ -1,15 +1,19 @@ +// src/Webshop.Api/Controllers/Customer/ProfileController.cs using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; +using Webshop.Application.DTOs; // CustomerDto +using Webshop.Application.DTOs.Auth; // ChangePasswordRequestDto +using Webshop.Application.DTOs.Customers; // UpdateCustomerProfileDto +using Webshop.Application.Services; using System.Threading.Tasks; -using Webshop.Application.DTOs.Customers; -using Webshop.Application.Services.Customers.Interfaces; +using Webshop.Application.Services.Customers; -namespace Webshop.Api.Controllers.Customers +namespace Webshop.Api.Controllers.Customer { [ApiController] - [Route("api/v1/customer/profile")] // Eindeutige Route für das Profil - [Authorize(Roles = "Customer")] // Nur für eingeloggte Kunden! + [Route("api/v1/customer/[controller]")] // z.B. /api/v1/customer/profile + [Authorize(Roles = "Customer")] public class ProfileController : ControllerBase { private readonly ICustomerService _customerService; @@ -19,33 +23,49 @@ namespace Webshop.Api.Controllers.Customers _customerService = customerService; } - // Hilfsmethode, um die ID des eingeloggten Benutzers aus dem Token zu holen - private string GetUserId() => User.FindFirstValue(ClaimTypes.NameIdentifier)!; - - [HttpGet("me")] // GET /api/v1/customer/profile/me + [HttpGet("me")] // /api/v1/customer/profile/me public async Task> GetMyProfile() { - var userId = GetUserId(); - var profile = await _customerService.GetMyProfileAsync(userId); + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(userId)) return Unauthorized(new { Message = "Benutzer-ID nicht im Token gefunden." }); - if (profile == null) - { - return NotFound("Kundenprofil nicht gefunden."); - } - return Ok(profile); + var customerProfile = await _customerService.GetMyProfileAsync(userId); + if (customerProfile == null) return NotFound(new { Message = "Kundenprofil nicht gefunden. Bitte erstellen Sie es." }); + + return Ok(customerProfile); } - [HttpPut("me")] // PUT /api/v1/customer/profile/me - public async Task UpdateMyProfile([FromBody] UpdateCustomerProfileDto profileDto) + [HttpPost("change-password")] // /api/v1/customer/profile/change-password + public async Task ChangePassword([FromBody] ChangePasswordRequestDto request) { - var userId = GetUserId(); - var success = await _customerService.UpdateMyProfileAsync(userId, profileDto); + if (!ModelState.IsValid) return BadRequest(ModelState); - if (!success) - { - return NotFound("Kundenprofil nicht gefunden."); - } - return NoContent(); // Standardantwort für ein erfolgreiches Update + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(userId)) return Unauthorized(new { Message = "Benutzer-ID nicht im Token gefunden." }); + + var (success, errorMessage) = await _customerService.ChangePasswordAsync(userId, request); + + if (!success) return BadRequest(new { Message = errorMessage }); + + return Ok(new { Message = "Passwort erfolgreich geändert. Bitte melden Sie sich mit dem neuen Passwort an." }); } + + // << NEUER/AKTUALISIERTER ENDPUNKT FÜR PROFIL-AKTUALISIERUNG >> + [HttpPost("update-profile")] // /api/v1/customer/profile/update-profile + public async Task UpdateProfile([FromBody] UpdateCustomerProfileDto request) + { + if (!ModelState.IsValid) return BadRequest(ModelState); + + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(userId)) return Unauthorized(new { Message = "Benutzer-ID nicht im Token gefunden." }); + + var (success, errorMessage) = await _customerService.UpdateMyProfileAsync(userId, request); + + if (!success) return BadRequest(new { Message = errorMessage }); + + return Ok(new { Message = "Profil erfolgreich aktualisiert." }); + } + + // << ENTFERNT: UpdateContactInfo Endpoint >> } } \ No newline at end of file diff --git a/Webshop.Api/Program.cs b/Webshop.Api/Program.cs index 5209b5e..962f057 100644 --- a/Webshop.Api/Program.cs +++ b/Webshop.Api/Program.cs @@ -18,6 +18,7 @@ using Webshop.Application.Services.Admin.Interfaces; using Webshop.Application.Services.Public.Interfaces; using Webshop.Application.Services.Customers.Interfaces; using Webshop.Application.Services.Customers; +using Webshop.Domain.Identity; var builder = WebApplication.CreateBuilder(args); @@ -136,6 +137,7 @@ builder.Services.AddSwaggerGen(c => var app = builder.Build(); // <-- Hier wird die App gebaut +// OPTIONALE BLÖCKE FÜR MIGRATION UND BENUTZERINITIALISIERUNG - DIESER CODE WIRD VOR APP.RUN() AUSGEFÜHRT // OPTIONALE BLÖCKE FÜR MIGRATION UND BENUTZERINITIALISIERUNG - DIESER CODE WIRD VOR APP.RUN() AUSGEFÜHRT using (var scope = app.Services.CreateScope()) { @@ -150,7 +152,7 @@ using (var scope = app.Services.CreateScope()) // Dieser Block erstellt Rollen und initiale Benutzer, falls sie noch nicht existieren. // BITTE ENTFERNEN ODER KOMMENTIEREN SIE DIESEN BLOCK AUS, NACHDEM SIE IHRE ERSTEN BENUTZER ERSTELLT HABEN! var roleManager = services.GetRequiredService>(); - var userManager = services.GetRequiredService>(); + var userManager = services.GetRequiredService>(); // << KORREKT: UserManager für ApplicationUser >> string[] roleNames = { "Admin", "Customer" }; @@ -162,23 +164,39 @@ using (var scope = app.Services.CreateScope()) } } - // Erstelle einen initialen Admin-Benutzer - var adminUser = await userManager.FindByEmailAsync("admin@yourwebshop.com"); + // Erstelle einen initialen Admin-Benutzer und sein Customer-Profil + var adminUser = await userManager.FindByEmailAsync("admin@yourwebshop.com"); // << ANPASSEN >> if (adminUser == null) { - // Erstellen Sie hier eine Instanz von ApplicationUser - adminUser = new ApplicationUser + adminUser = new ApplicationUser // << KORREKT: ERSTELLT ApplicationUser >> { - UserName = "admin@yourwebshop.com", - Email = "admin@yourwebshop.com", + UserName = "admin@yourwebshop.com", // << ANPASSEN >> + Email = "admin@yourwebshop.com", // << ANPASSEN >> EmailConfirmed = true, - CreatedDate = DateTimeOffset.UtcNow // Setzen Sie Ihr neues Feld! + CreatedDate = DateTimeOffset.UtcNow, // Custom Property auf ApplicationUser + LastActive = DateTimeOffset.UtcNow // Custom Property auf ApplicationUser }; - var createAdmin = await userManager.CreateAsync(adminUser, "SecureAdminPass123!"); + var createAdmin = await userManager.CreateAsync(adminUser, "SecureAdminPass123!"); // << ANPASSEN >> if (createAdmin.Succeeded) { await userManager.AddToRoleAsync(adminUser, "Admin"); Console.WriteLine("Admin user created."); + + // Erstelle Customer-Profil für Admin (falls Admins auch Kundenprofile haben sollen) + var adminCustomerProfile = await context.Customers.FirstOrDefaultAsync(c => c.AspNetUserId == adminUser.Id); // << KORREKT: SUCHT NACH AspNetUserId >> + if (adminCustomerProfile == null) + { + adminCustomerProfile = new Webshop.Domain.Entities.Customer + { + Id = Guid.NewGuid(), + AspNetUserId = adminUser.Id, // << KORREKT: VERKNÜPFUNG ÜBER AspNetUserId >> + FirstName = "Admin", + LastName = "User" + }; + context.Customers.Add(adminCustomerProfile); + await context.SaveChangesAsync(); + Console.WriteLine("Admin's Customer profile created."); + } } else { @@ -186,29 +204,46 @@ using (var scope = app.Services.CreateScope()) } } - // Erstelle einen initialen Kunden-Benutzer - var customerUser = await userManager.FindByEmailAsync("customer@yourwebshop.com"); + // Erstelle einen initialen Kunden-Benutzer und sein Customer-Profil (KOMBINIERT) + var customerUser = await userManager.FindByEmailAsync("customer@yourwebshop.com"); // << ANPASSEN >> if (customerUser == null) { - // Erstellen Sie auch hier eine Instanz von ApplicationUser - customerUser = new ApplicationUser + customerUser = new ApplicationUser // << KORREKT: ERSTELLT ApplicationUser >> { - UserName = "customer@yourwebshop.com", - Email = "customer@yourwebshop.com", + UserName = "customer@yourwebshop.com", // << ANPASSEN >> + Email = "customer@yourwebshop.com", // << ANPASSEN >> EmailConfirmed = true, - CreatedDate = DateTimeOffset.UtcNow // Setzen Sie Ihr neues Feld! + CreatedDate = DateTimeOffset.UtcNow, // Custom Property auf ApplicationUser + LastActive = DateTimeOffset.UtcNow // Custom Property auf ApplicationUser }; - var createCustomer = await userManager.CreateAsync(customerUser, "SecureCustomerPass123!"); + var createCustomer = await userManager.CreateAsync(customerUser, "SecureCustomerPass123!"); // << ANPASSEN >> if (createCustomer.Succeeded) { await userManager.AddToRoleAsync(customerUser, "Customer"); Console.WriteLine("Customer user created."); + + // Kombinierter Teil: Customer-Profil erstellen, direkt nach IdentityUser-Erstellung + var customerProfile = await context.Customers.FirstOrDefaultAsync(c => c.AspNetUserId == customerUser.Id); // << KORREKT: SUCHT NACH AspNetUserId >> + if (customerProfile == null) + { + customerProfile = new Webshop.Domain.Entities.Customer + { + Id = Guid.NewGuid(), + AspNetUserId = customerUser.Id, + FirstName = "Test", + LastName = "Kunde" + }; + context.Customers.Add(customerProfile); + await context.SaveChangesAsync(); + Console.WriteLine("Customer profile created for new customer user."); + } } else { Console.WriteLine($"Error creating customer user: {string.Join(", ", createCustomer.Errors.Select(e => e.Description))}"); } } + // --- ENDE DES TEMPORÄREN SETUP-BLOCKS --- } catch (Exception ex) { diff --git a/Webshop.Api/SwaggerFilters/AddExampleSchemaFilter.cs b/Webshop.Api/SwaggerFilters/AddExampleSchemaFilter.cs index 97d095e..80050e0 100644 --- a/Webshop.Api/SwaggerFilters/AddExampleSchemaFilter.cs +++ b/Webshop.Api/SwaggerFilters/AddExampleSchemaFilter.cs @@ -14,7 +14,7 @@ using Webshop.Application.DTOs.Products; using Webshop.Application.DTOs.Payments; using Webshop.Application.DTOs.Orders; using Webshop.Application.DTOs.Discounts; -using Webshop.Application.DTOs.Categorys; // Für Guid.NewGuid() +using Webshop.Application.DTOs.Categorys; namespace Webshop.Api.SwaggerFilters { diff --git a/Webshop.Application/DTOs/Auth/ChangePasswordRequestDto.cs b/Webshop.Application/DTOs/Auth/ChangePasswordRequestDto.cs new file mode 100644 index 0000000..15d812c --- /dev/null +++ b/Webshop.Application/DTOs/Auth/ChangePasswordRequestDto.cs @@ -0,0 +1,19 @@ +// src/Webshop.Application/DTOs/Auth/ChangePasswordRequestDto.cs +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Application.DTOs.Auth +{ + public class ChangePasswordRequestDto + { + [Required(ErrorMessage = "Altes Passwort ist erforderlich.")] + public string OldPassword { get; set; } = string.Empty; + + [Required(ErrorMessage = "Neues Passwort ist erforderlich.")] + [MinLength(6, ErrorMessage = "Passwort muss mindestens 6 Zeichen lang sein.")] + public string NewPassword { get; set; } = string.Empty; + + [Required(ErrorMessage = "Passwortbestätigung ist erforderlich.")] + [Compare("NewPassword", ErrorMessage = "Neues Passwort und Bestätigung stimmen nicht überein.")] + public string ConfirmNewPassword { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Webshop.Application/DTOs/Customers/UpdateCustomerProfileDto.cs b/Webshop.Application/DTOs/Customers/UpdateCustomerProfileDto.cs index 8bb453a..046d3c0 100644 --- a/Webshop.Application/DTOs/Customers/UpdateCustomerProfileDto.cs +++ b/Webshop.Application/DTOs/Customers/UpdateCustomerProfileDto.cs @@ -1,16 +1,26 @@ -using System.ComponentModel.DataAnnotations; +// src/Webshop.Application/DTOs/Customers/UpdateCustomerProfileDto.cs +using System.ComponentModel.DataAnnotations; namespace Webshop.Application.DTOs.Customers { public class UpdateCustomerProfileDto { [Required(ErrorMessage = "Vorname ist erforderlich.")] - [MaxLength(100)] + [StringLength(100)] public string FirstName { get; set; } = string.Empty; [Required(ErrorMessage = "Nachname ist erforderlich.")] - [MaxLength(100)] + [StringLength(100)] public string LastName { get; set; } = string.Empty; - + + [Phone(ErrorMessage = "Ungültiges Telefonnummernformat.")] + public string? PhoneNumber { get; set; } // Telefonnummer des Benutzers + + [EmailAddress(ErrorMessage = "Ungültiges E-Mail-Format.")] + public string? Email { get; set; } // E-Mail des Benutzers + + // Optional, aber gute Sicherheitspraxis: Aktuelles Passwort zur Bestätigung sensibler Änderungen + [Required(ErrorMessage = "Aktuelles Passwort ist zur Bestätigung erforderlich.")] + public string CurrentPassword { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/Webshop.Application/DTOs/Users/AdminResetPasswordRequestDto.cs b/Webshop.Application/DTOs/Users/AdminResetPasswordRequestDto.cs new file mode 100644 index 0000000..3eff903 --- /dev/null +++ b/Webshop.Application/DTOs/Users/AdminResetPasswordRequestDto.cs @@ -0,0 +1,19 @@ +// src/Webshop.Application/DTOs/Users/AdminResetPasswordRequestDto.cs +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Application.DTOs.Users +{ + public class AdminResetPasswordRequestDto + { + [Required(ErrorMessage = "Benutzer-ID ist erforderlich.")] + public string UserId { get; set; } = string.Empty; + + [Required(ErrorMessage = "Neues Passwort ist erforderlich.")] + [MinLength(6, ErrorMessage = "Passwort muss mindestens 6 Zeichen lang sein.")] + public string NewPassword { get; set; } = string.Empty; + + [Required(ErrorMessage = "Passwortbestätigung ist erforderlich.")] + [Compare("NewPassword", ErrorMessage = "Neues Passwort und Bestätigung stimmen nicht überein.")] + public string ConfirmNewPassword { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Webshop.Application/DTOs/Users/UserDto.cs b/Webshop.Application/DTOs/Users/UserDto.cs index 5d36b33..df381d8 100644 --- a/Webshop.Application/DTOs/Users/UserDto.cs +++ b/Webshop.Application/DTOs/Users/UserDto.cs @@ -1,18 +1,25 @@ // src/Webshop.Application/DTOs/Users/UserDto.cs +using System; +using System.Collections.Generic; + namespace Webshop.Application.DTOs.Users { public class UserDto { - public string Id { get; set; } = string.Empty; - public string Email { get; set; } = string.Empty; - public string UserName { get; set; } = string.Empty; - public List Roles { get; set; } = new List(); - public DateTimeOffset CreatedDate { get; set; } - public bool EmailConfirmed { get; set; } + public string Id { get; set; } = string.Empty; // Vom ApplicationUser.Id + public string Email { get; set; } = string.Empty; // Vom ApplicationUser.Email + public string UserName { get; set; } = string.Empty; // Vom ApplicationUser.UserName + public List Roles { get; set; } = new List(); // Aus Identity-System + public DateTimeOffset CreatedDate { get; set; } // Vom ApplicationUser.CreatedDate + public bool EmailConfirmed { get; set; } // Vom ApplicationUser.EmailConfirmed - // Hinzugefügte Felder - public DateTimeOffset? LastActive { get; set; } - public string FirstName { get; set; } = string.Empty; - public string LastName { get; set; } = string.Empty; + public DateTimeOffset? LastActive { get; set; } // Vom ApplicationUser.LastActive + public string? PhoneNumber { get; set; } // Vom ApplicationUser.PhoneNumber + + // << NEU: Customer-Felder, die NICHT in ApplicationUser sind >> + public string FirstName { get; set; } = string.Empty; // Vom Customer.FirstName + public string LastName { get; set; } = string.Empty; // Vom Customer.LastName + public Guid? DefaultShippingAddressId { get; set; } // Vom Customer + public Guid? DefaultBillingAddressId { get; set; } // Vom Customer } } \ No newline at end of file diff --git a/Webshop.Application/Services/Admin/AdminProductService.cs b/Webshop.Application/Services/Admin/AdminProductService.cs index 6eb944c..e5a21e3 100644 --- a/Webshop.Application/Services/Admin/AdminProductService.cs +++ b/Webshop.Application/Services/Admin/AdminProductService.cs @@ -8,6 +8,7 @@ using System.Linq; using Webshop.Application.DTOs.Products; using Webshop.Application.Services.Admin.Interfaces; // Für Select + namespace Webshop.Application.Services.Admin { public class AdminProductService : IAdminProductService // Sicherstellen, dass IAdminProductService implementiert wird diff --git a/Webshop.Application/Services/Admin/AdminSupplierService.cs b/Webshop.Application/Services/Admin/AdminSupplierService.cs index 00090fd..b11777d 100644 --- a/Webshop.Application/Services/Admin/AdminSupplierService.cs +++ b/Webshop.Application/Services/Admin/AdminSupplierService.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using Webshop.Application.DTOs.Suppliers; using Webshop.Application.Services.Admin.Interfaces; +using Webshop.Domain.Identity; namespace Webshop.Application.Services.Admin { diff --git a/Webshop.Application/Services/Admin/AdminUserService.cs b/Webshop.Application/Services/Admin/AdminUserService.cs index 6d94bd9..d0290f4 100644 --- a/Webshop.Application/Services/Admin/AdminUserService.cs +++ b/Webshop.Application/Services/Admin/AdminUserService.cs @@ -8,6 +8,7 @@ using Webshop.Application.DTOs.Users; using Webshop.Application.Services.Admin.Interfaces; using Webshop.Domain.Entities; using Webshop.Infrastructure.Data; // WICHTIG: Stellt sicher, dass ApplicationDbContext gefunden wird. +using Webshop.Domain.Identity; namespace Webshop.Application.Services.Admin { diff --git a/Webshop.Application/Services/Admin/Interfaces/IAdminUserService.cs b/Webshop.Application/Services/Admin/Interfaces/IAdminUserService.cs index 5525aec..7c434ed 100644 --- a/Webshop.Application/Services/Admin/Interfaces/IAdminUserService.cs +++ b/Webshop.Application/Services/Admin/Interfaces/IAdminUserService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +// src/Webshop.Application/Services/Admin/Interfaces/IAdminUserService.cs +using System.Collections.Generic; using System.Threading.Tasks; using Webshop.Application.DTOs.Users; diff --git a/Webshop.Application/Services/Customers/CustomerService.cs b/Webshop.Application/Services/Customers/CustomerService.cs index 56645c7..53bd6f1 100644 --- a/Webshop.Application/Services/Customers/CustomerService.cs +++ b/Webshop.Application/Services/Customers/CustomerService.cs @@ -1,52 +1,122 @@ +// src/Webshop.Application/Services/Customers/CustomerService.cs +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; using System.Threading.Tasks; -using Webshop.Application.DTOs.Customers; -using Webshop.Application.Services.Customers.Interfaces; -using Webshop.Domain.Interfaces; // Wichtig für ICustomerRepository +using Webshop.Application.DTOs; // CustomerDto +using Webshop.Application.DTOs.Auth; // ChangePasswordRequestDto +using Webshop.Application.DTOs.Customers; // UpdateCustomerProfileDto +using Webshop.Domain.Entities; // Customer Entity +using Webshop.Domain.Interfaces; // ICustomerRepository +using Webshop.Domain.Identity; // Für ApplicationUser +using System.Linq; // Für Select +using System.Collections.Generic; // Für IEnumerable namespace Webshop.Application.Services.Customers { public class CustomerService : ICustomerService { private readonly ICustomerRepository _customerRepository; + private readonly UserManager _userManager; - public CustomerService(ICustomerRepository customerRepository) + public CustomerService(ICustomerRepository customerRepository, UserManager userManager) { _customerRepository = customerRepository; + _userManager = userManager; } public async Task GetMyProfileAsync(string userId) { var customer = await _customerRepository.GetByUserIdAsync(userId); - if (customer == null) - { - return null; - } + if (customer == null) return null; + + var identityUser = await _userManager.FindByIdAsync(userId); + if (identityUser == null) return null; - // Mappe die Entity auf das CustomerDto return new CustomerDto { Id = customer.Id, UserId = customer.AspNetUserId, FirstName = customer.FirstName, LastName = customer.LastName, - // Fügen Sie hier weitere Felder hinzu, die der Kunde sehen soll (Email, Phone etc.) + Email = identityUser.Email ?? string.Empty, // E-Mail vom ApplicationUser + PhoneNumber = identityUser.PhoneNumber, // Telefonnummer vom ApplicationUser + DefaultShippingAddressId = customer.DefaultShippingAddressId, + DefaultBillingAddressId = customer.DefaultBillingAddressId }; } - public async Task UpdateMyProfileAsync(string userId, UpdateCustomerProfileDto profileDto) + public async Task<(bool Success, string ErrorMessage)> ChangePasswordAsync(string userId, ChangePasswordRequestDto request) { - var customer = await _customerRepository.GetByUserIdAsync(userId); - if (customer == null) + var user = await _userManager.FindByIdAsync(userId); + if (user == null) return (false, "Benutzer nicht gefunden."); + + var result = await _userManager.ChangePasswordAsync(user, request.OldPassword, request.NewPassword); + + if (!result.Succeeded) { - return false; // Kunde nicht gefunden + var errors = string.Join(" ", result.Errors.Select(e => e.Description)); + return (false, errors); } - // Aktualisiere die Felder + return (true, "Passwort erfolgreich geändert."); + } + + // << NEUE IMPLEMENTIERUNG: UpdateMyProfileAsync verarbeitet alle Felder >> + public async Task<(bool Success, string ErrorMessage)> UpdateMyProfileAsync(string userId, UpdateCustomerProfileDto profileDto) + { + var customer = await _customerRepository.GetByUserIdAsync(userId); + if (customer == null) return (false, "Kundenprofil nicht gefunden."); + + var identityUser = await _userManager.FindByIdAsync(userId); + if (identityUser == null) return (false, "Benutzerkonto nicht gefunden."); + + // 1. Aktuelles Passwort prüfen (für alle sensiblen Änderungen) + if (!await _userManager.CheckPasswordAsync(identityUser, profileDto.CurrentPassword)) + { + return (false, "Falsches aktuelles Passwort zur Bestätigung."); + } + + // 2. Felder der Customer-Entität aktualisieren (FirstName, LastName) customer.FirstName = profileDto.FirstName; customer.LastName = profileDto.LastName; + // customer.PhoneNumber = profileDto.PhoneNumber; // Entfernt, da es jetzt in ApplicationUser zentralisiert ist + await _customerRepository.UpdateAsync(customer); // Speichert Änderungen im Customer-Profil - await _customerRepository.UpdateAsync(customer); - return true; + // 3. Felder des ApplicationUser (IdentityUser) aktualisieren (Email, PhoneNumber) + bool identityUserChanged = false; + + // E-Mail aktualisieren (wenn anders und nicht leer) + if (!string.IsNullOrEmpty(profileDto.Email) && identityUser.Email != profileDto.Email) + { + identityUser.Email = profileDto.Email; + identityUser.NormalizedEmail = _userManager.NormalizeEmail(profileDto.Email); + identityUser.UserName = profileDto.Email; // Oft wird der UserName auch mit der E-Mail synchronisiert + identityUser.NormalizedUserName = _userManager.NormalizeName(profileDto.Email); + // Optional: user.EmailConfirmed = false; wenn Sie Bestätigungs-E-Mails senden + identityUserChanged = true; + } + + // Telefonnummer aktualisieren (wenn anders und nicht leer) + if (!string.IsNullOrEmpty(profileDto.PhoneNumber) && identityUser.PhoneNumber != profileDto.PhoneNumber) + { + identityUser.PhoneNumber = profileDto.PhoneNumber; + // Optional: identityUser.PhoneNumberConfirmed = false; + identityUserChanged = true; + } + + if (identityUserChanged) + { + var updateResult = await _userManager.UpdateAsync(identityUser); + if (!updateResult.Succeeded) + { + var errors = string.Join(" ", updateResult.Errors.Select(e => e.Description)); + return (false, $"Fehler beim Aktualisieren der Kontaktdaten: {errors}"); + } + } + + return (true, "Profil und Kontaktdaten erfolgreich aktualisiert."); } + + // << ENTFERNT: UpdateMyContactInfoAsync >> } } \ No newline at end of file diff --git a/Webshop.Application/Services/Customers/Interfaces/ICustomerService.cs b/Webshop.Application/Services/Customers/Interfaces/ICustomerService.cs index 9020bd9..4807362 100644 --- a/Webshop.Application/Services/Customers/Interfaces/ICustomerService.cs +++ b/Webshop.Application/Services/Customers/Interfaces/ICustomerService.cs @@ -1,11 +1,16 @@ +// src/Webshop.Application/Services/Customers/ICustomerService.cs using System.Threading.Tasks; -using Webshop.Application.DTOs.Customers; // Korrektes Using für DTOs +using Webshop.Application.DTOs; // CustomerDto +using Webshop.Application.DTOs.Auth; // ChangePasswordRequestDto +using Webshop.Application.DTOs.Customers; // UpdateCustomerProfileDto -namespace Webshop.Application.Services.Customers.Interfaces +namespace Webshop.Application.Services.Customers { public interface ICustomerService { Task GetMyProfileAsync(string userId); - Task UpdateMyProfileAsync(string userId, UpdateCustomerProfileDto profileDto); + Task<(bool Success, string ErrorMessage)> ChangePasswordAsync(string userId, ChangePasswordRequestDto request); + Task<(bool Success, string ErrorMessage)> UpdateMyProfileAsync(string userId, UpdateCustomerProfileDto profileDto); + } } \ No newline at end of file diff --git a/Webshop.Domain/Entities/ApplicationUser.cs b/Webshop.Domain/Entities/ApplicationUser.cs deleted file mode 100644 index 84f59b5..0000000 --- a/Webshop.Domain/Entities/ApplicationUser.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using System; -using System.Collections.Generic; - -namespace Webshop.Domain.Entities -{ - public class ApplicationUser : IdentityUser - { - public DateTimeOffset CreatedDate { get; set; } - public DateTimeOffset? LastActive { get; set; } - public virtual Customer Customer { get; set; } - } -} \ No newline at end of file diff --git a/Webshop.Domain/Entities/Customer.cs b/Webshop.Domain/Entities/Customer.cs index f877346..bc72e35 100644 --- a/Webshop.Domain/Entities/Customer.cs +++ b/Webshop.Domain/Entities/Customer.cs @@ -1,25 +1,40 @@ -using System; - +// src/Webshop.Domain/Entities/Customer.cs +using System; using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using Webshop.Domain.Identity; // Für ApplicationUser namespace Webshop.Domain.Entities { public class Customer { [Key] - public Guid Id { get; set; } - [Required] - public string AspNetUserId { get; set; } - [Required] - [MaxLength(100)] - public string FirstName { get; set; } - [Required] - [MaxLength(100)] - public string LastName { get; set; } + public Guid Id { get; set; } = Guid.NewGuid(); // Default-Wert setzen - public virtual ApplicationUser User { get; set; } + [Required] + public string AspNetUserId { get; set; } // Fremdschlüssel zu ApplicationUser.Id + + [Required] + [MaxLength(100)] + public string FirstName { get; set; } = string.Empty; + + [Required] + [MaxLength(100)] + public string LastName { get; set; } = string.Empty; + + // << ENTFERNT: Email ist auf ApplicationUser >> + // << ENTFERNT: PhoneNumber ist auf ApplicationUser >> + // << ENTFERNT: CreatedDate ist auf ApplicationUser >> + + public Guid? DefaultShippingAddressId { get; set; } // Fremdschlüssel zur Standardversandadresse + public Guid? DefaultBillingAddressId { get; set; } // Fremdschlüssel zur Standardrechnungsadresse + + // Navigation Property zum ApplicationUser + public virtual ApplicationUser User { get; set; } = default!; // Muss auf ApplicationUser verweisen + + // Navigation Properties zu Collections public virtual ICollection
Addresses { get; set; } = new List
(); public virtual ICollection Orders { get; set; } = new List(); public virtual ICollection Reviews { get; set; } = new List(); } -} +} \ No newline at end of file diff --git a/Webshop.Domain/Identity/ApplicationUser.cs b/Webshop.Domain/Identity/ApplicationUser.cs new file mode 100644 index 0000000..57073d5 --- /dev/null +++ b/Webshop.Domain/Identity/ApplicationUser.cs @@ -0,0 +1,16 @@ +// src/Webshop.Domain/Identity/ApplicationUser.cs +using Microsoft.AspNetCore.Identity; +using System; + +namespace Webshop.Domain.Identity // KORREKTER NAMESPACE +{ + public class ApplicationUser : IdentityUser + { + public DateTimeOffset CreatedDate { get; set; } = DateTimeOffset.UtcNow; // Setzt Standardwert + public DateTimeOffset? LastActive { get; set; } + + // Navigation Property zur Customer-Entität (One-to-One) + // Customer ist nullable, falls nicht jeder ApplicationUser ein Customer-Profil hat + public virtual Entities.Customer? Customer { get; set; } + } +} \ No newline at end of file diff --git a/Webshop.Domain/Interfaces/ICustomerRepository.cs b/Webshop.Domain/Interfaces/ICustomerRepository.cs index 14789a1..6742185 100644 --- a/Webshop.Domain/Interfaces/ICustomerRepository.cs +++ b/Webshop.Domain/Interfaces/ICustomerRepository.cs @@ -1,4 +1,5 @@ -using System; +// src/Webshop.Domain/Interfaces/ICustomerRepository.cs +using System.Collections.Generic; using System.Threading.Tasks; using Webshop.Domain.Entities; @@ -6,7 +7,12 @@ namespace Webshop.Domain.Interfaces { public interface ICustomerRepository { - Task GetByUserIdAsync(string userId); - Task UpdateAsync(Customer customer); + Task> GetAllAsync(); // Standard CRUD + Task GetByIdAsync(Guid id); // Standard CRUD + Task AddAsync(Customer entity); // Standard CRUD + Task UpdateAsync(Customer entity); // Standard CRUD + Task DeleteAsync(Guid id); // Standard CRUD + + Task GetByUserIdAsync(string userId); // << DIESE METHODE IST NEU UND WICHTIG >> } } \ No newline at end of file diff --git a/Webshop.Infrastructure/Repositories/CustomerRepository.cs b/Webshop.Infrastructure/Repositories/CustomerRepository.cs index e26277c..a9d854c 100644 --- a/Webshop.Infrastructure/Repositories/CustomerRepository.cs +++ b/Webshop.Infrastructure/Repositories/CustomerRepository.cs @@ -1,13 +1,15 @@ +// src/Webshop.Infrastructure/Repositories/CustomerRepository.cs using Microsoft.EntityFrameworkCore; using Webshop.Domain.Entities; using Webshop.Domain.Interfaces; using Webshop.Infrastructure.Data; using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Webshop.Infrastructure.Repositories { - public class CustomerRepository : ICustomerRepository + public class CustomerRepository : ICustomerRepository // MUSS public sein { private readonly ApplicationDbContext _context; @@ -16,15 +18,17 @@ namespace Webshop.Infrastructure.Repositories _context = context; } - public async Task GetByUserIdAsync(string userId) - { - return await _context.Customers.FirstOrDefaultAsync(c => c.AspNetUserId == userId); - } + // Beispiel-Implementierungen (wenn nicht schon vorhanden): + public async Task> GetAllAsync() => await _context.Customers.ToListAsync(); + public async Task GetByIdAsync(Guid id) => await _context.Customers.FindAsync(id); + public async Task AddAsync(Customer entity) { _context.Customers.Add(entity); await _context.SaveChangesAsync(); } + public async Task UpdateAsync(Customer entity) { _context.Customers.Update(entity); await _context.SaveChangesAsync(); } + public async Task DeleteAsync(Guid id) { var entity = await _context.Customers.FindAsync(id); if (entity != null) { _context.Customers.Remove(entity); await _context.SaveChangesAsync(); } } - public async Task UpdateAsync(Customer customer) + // KORREKTE IMPLEMENTIERUNG FÜR GetByUserIdAsync + public async Task GetByUserIdAsync(string aspNetUserId) { - _context.Customers.Update(customer); - await _context.SaveChangesAsync(); + return await _context.Customers.FirstOrDefaultAsync(c => c.AspNetUserId == aspNetUserId); } } } \ No newline at end of file diff --git a/Webshop.Infrastructure/Repositories/SupplierRepository.cs b/Webshop.Infrastructure/Repositories/SupplierRepository.cs index 5074a5a..befe7e6 100644 --- a/Webshop.Infrastructure/Repositories/SupplierRepository.cs +++ b/Webshop.Infrastructure/Repositories/SupplierRepository.cs @@ -1,16 +1,15 @@ // src/Webshop.Infrastructure/Repositories/SupplierRepository.cs -// Auto-generiert von CreateWebshopFiles.ps1 (aktualisiert um Implementierungen) -using Microsoft.EntityFrameworkCore; // Wichtig für ToListAsync, FindAsync etc. +using Microsoft.EntityFrameworkCore; using Webshop.Domain.Entities; using Webshop.Domain.Interfaces; using Webshop.Infrastructure.Data; -using System; // Für Guid +using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Webshop.Infrastructure.Repositories { - public class SupplierRepository : ISupplierRepository // Hier muss das Interface stehen + public class SupplierRepository : ISupplierRepository // MUSS public sein { private readonly ApplicationDbContext _context; @@ -19,8 +18,6 @@ namespace Webshop.Infrastructure.Repositories _context = context; } - // --- IMPLEMENTIERUNG DER ISupplierRepository METHODEN --- - public async Task> GetAllSuppliersAsync() { return await _context.Suppliers.ToListAsync(); @@ -34,7 +31,7 @@ namespace Webshop.Infrastructure.Repositories public async Task AddSupplierAsync(Supplier supplier) { _context.Suppliers.Add(supplier); - await _context.SaveChangesAsync(); // Änderungen in der DB speichern + await _context.SaveChangesAsync(); } public async Task UpdateSupplierAsync(Supplier supplier) @@ -45,11 +42,11 @@ namespace Webshop.Infrastructure.Repositories public async Task DeleteSupplierAsync(Guid id) { - var supplier = await _context.Suppliers.FindAsync(id); // Lieferant zuerst finden + var supplier = await _context.Suppliers.FindAsync(id); if (supplier != null) { - _context.Suppliers.Remove(supplier); // Lieferant entfernen - await _context.SaveChangesAsync(); // Änderungen speichern + _context.Suppliers.Remove(supplier); + await _context.SaveChangesAsync(); } } }