using Koogle.Application.DTOs; using Koogle.Application.Interfaces; using Koogle.Domain.Entities; using Koogle.Infrastructure.Identity; using KoogleApp.Data; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; using AutoMapper; using Koogle.Infrastructure.Data; namespace Koogle.Application.Services; /// /// Service for user management operations including registration, authentication, and profile management. /// public class UserService : IUserService { private readonly SignInManager _signInManager; private readonly UserManager _userManager; private readonly RoleManager _roleManager; private readonly AppDbContext _appDb; private readonly IMapper _mapper; private readonly AuthenticationStateProvider _authStateProvider; /// /// Initializes a new instance of UserService. /// public UserService(SignInManager signInManager, UserManager userManager, RoleManager roleManager, AuthenticationStateProvider authStateProvider, IMapper mapper, AppDbContext appDb) { _signInManager = signInManager; _userManager = userManager; _roleManager = roleManager; _authStateProvider = authStateProvider; _mapper = mapper; _appDb = appDb; } /// public async Task LoginAsync(LoginDto login, CancellationToken cancellationToken) { // 1) User finden var user = await _userManager.FindByEmailAsync(login.Email); if (user == null) { return null; } // 2) Passwort prüfen var result = await _signInManager.CheckPasswordSignInAsync(user, login.Password, lockoutOnFailure: false); if (!result.Succeeded) { return null; } // 3) Club anhand Name finden (case-insensitive) var clubName = login.ClubName?.Trim(); if (string.IsNullOrWhiteSpace(clubName)) { return null; } var club = await _appDb.Clubs .IgnoreQueryFilters() // optional, falls du SoftDelete filterst und trotzdem finden willst .FirstOrDefaultAsync(c => c.Name.ToLower() == clubName.ToLower() && !c.IsDeleted); if (club == null) { return null; } // 4) Berechtigung prüfen: SuperAdmin ODER Mitglied im Club var isSuperAdmin = await _userManager.IsInRoleAsync(user, "SuperAdmin"); if (!isSuperAdmin) { var profile = await _appDb.UserProfiles .FirstOrDefaultAsync(p => p.IdentityUserId == user.Id); if (profile == null) { return null; } // Mitgliedschaft prüfen (UserProfileClub) var isMember = await _appDb.UserProfileClubs.AnyAsync(upc => upc.UserProfileId == profile.Id && upc.ClubId == club.Id && !upc.Club.IsDeleted && !upc.UserProfile.IsDeleted); if (!isMember) { return null; } } // 5) Claims für aktuellen Club setzen (im Cookie!) var additionalClaims = new List { new Claim("current_club_id", club.Id.ToString()), new Claim("current_club_name", club.Name) }; // 6) SignIn mit zusätzlichen Claims (werden im Cookie gespeichert) [1](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.signinmanager-1.signinwithclaimsasync?view=aspnetcore-9.0) try { await _signInManager.SignInWithClaimsAsync(user, isPersistent: login.RememberMe, additionalClaims: additionalClaims); } catch (Exception) { throw; } return await GetByIdentityUserIdAsync(user.Id,cancellationToken); } /// public async Task SignOutAsync() { await _signInManager.SignOutAsync(); } /// public async Task GetByIdentityUserIdAsync(Guid identityUserId, CancellationToken ct = default) { // Domain-Profil laden mit Club-Memberships (SoftDelete Filter wirkt) var profile = await _appDb.UserProfiles .AsNoTracking() .Include(p => p.Clubs) .ThenInclude(c => c.Club) .Include(p => p.Clubs) .ThenInclude(c => c.RoleAssignments) .SingleOrDefaultAsync(p => p.IdentityUserId == identityUserId, ct); if (profile == null) return null; // Identity-User laden var identity = await _userManager.FindByIdAsync(identityUserId.ToString()); if (identity == null) return null; // Map Profile + Identity -> DTO var dto = _mapper.Map(new UserComposite(profile, identity)); // Rollen ergänzen (async) var roles = await _userManager.GetRolesAsync(identity); dto.Identity.Roles = roles.ToList(); // Map club memberships dto.ClubMemberships = profile.Clubs.Select(c => new UserClubMembershipDto { ClubId = c.ClubId, ClubName = c.Club?.Name ?? "", IsDefault = c.IsDefault, AssignedAt = c.AssignedAt, Roles = c.RoleAssignments.Select(ra => ra.RoleName).ToList() }).ToList(); return dto; } /// public async Task GetCurrentUserAsync(CancellationToken ct = default) { var authState = await _authStateProvider.GetAuthenticationStateAsync(); var user = authState.User; var idValue = user.FindFirstValue(ClaimTypes.NameIdentifier); var identityUserId = Guid.TryParse(idValue, out var id) ? id : Guid.Empty; return await GetByIdentityUserIdAsync(identityUserId, ct); } /// public async Task RegisterUserAsync(RegisterUserDto dto, CancellationToken ct = default) { // Create Identity user var identityUser = new ApplicationUser { UserName = dto.Email, Email = dto.Email }; var result = await _userManager.CreateAsync(identityUser, dto.Password); if (!result.Succeeded) return result; // Create UserProfile in domain database var profile = new UserProfile { Id = Guid.NewGuid(), IdentityUserId = identityUser.Id, DisplayName = dto.DisplayName, CreatedAt = DateTime.UtcNow }; _appDb.UserProfiles.Add(profile); // Optionally assign to club if specified if (!string.IsNullOrWhiteSpace(dto.ClubName)) { var club = await _appDb.Clubs .FirstOrDefaultAsync(c => c.Name.ToLower() == dto.ClubName.ToLower() && !c.IsDeleted, ct); if (club != null) { var membership = new UserProfileClub { UserProfileId = profile.Id, ClubId = club.Id, IsDefault = true, AssignedAt = DateTime.UtcNow, AssignedById = profile.Id // Self-assigned during registration }; _appDb.UserProfileClubs.Add(membership); } } await _appDb.SaveChangesAsync(ct); return result; } /// public async Task RequestPasswordResetAsync(string email, CancellationToken ct = default) { var user = await _userManager.FindByEmailAsync(email); if (user == null) return null; return await _userManager.GeneratePasswordResetTokenAsync(user); } /// public async Task ResetPasswordAsync(ResetPasswordDto dto, CancellationToken ct = default) { var user = await _userManager.FindByEmailAsync(dto.Email); if (user == null) return IdentityResult.Failed(new IdentityError { Description = "User not found." }); return await _userManager.ResetPasswordAsync(user, dto.Token, dto.NewPassword); } /// public async Task UpdateProfileAsync(UpdateUserProfileDto dto, CancellationToken ct = default) { var currentUser = await GetCurrentUserAsync(ct); if (currentUser == null) return null; var profile = await _appDb.UserProfiles .Include(p => p.Clubs) .FirstOrDefaultAsync(p => p.Id == currentUser.ProfileId, ct); if (profile == null) return null; // Update profile fields if (dto.DisplayName != null) profile.DisplayName = dto.DisplayName; if (dto.Locale != null) profile.Locale = dto.Locale; if (dto.TimeZone != null) profile.TimeZone = dto.TimeZone; profile.ModifiedAt = DateTime.UtcNow; // Update default club if specified if (dto.DefaultClubId.HasValue) { var memberships = await _appDb.UserProfileClubs .Where(upc => upc.UserProfileId == profile.Id) .ToListAsync(ct); foreach (var m in memberships) { m.IsDefault = m.ClubId == dto.DefaultClubId.Value; } } await _appDb.SaveChangesAsync(ct); return await GetByIdentityUserIdAsync(profile.IdentityUserId, ct); } /// public async Task AssignUserToClubAsync(Guid userProfileId, Guid clubId, Guid assignedById, CancellationToken ct = default) { // Check if already member var exists = await _appDb.UserProfileClubs .AnyAsync(upc => upc.UserProfileId == userProfileId && upc.ClubId == clubId, ct); if (exists) return false; // Verify profile and club exist var profileExists = await _appDb.UserProfiles.AnyAsync(p => p.Id == userProfileId && !p.IsDeleted, ct); var clubExists = await _appDb.Clubs.AnyAsync(c => c.Id == clubId && !c.IsDeleted, ct); if (!profileExists || !clubExists) return false; var membership = new UserProfileClub { UserProfileId = userProfileId, ClubId = clubId, IsDefault = false, AssignedAt = DateTime.UtcNow, AssignedById = assignedById }; _appDb.UserProfileClubs.Add(membership); await _appDb.SaveChangesAsync(ct); return true; } /// public async Task RemoveUserFromClubAsync(Guid userProfileId, Guid clubId, CancellationToken ct = default) { var membership = await _appDb.UserProfileClubs .Include(upc => upc.RoleAssignments) .FirstOrDefaultAsync(upc => upc.UserProfileId == userProfileId && upc.ClubId == clubId, ct); if (membership == null) return false; // Remove all role assignments for this membership _appDb.Set().RemoveRange(membership.RoleAssignments); _appDb.UserProfileClubs.Remove(membership); await _appDb.SaveChangesAsync(ct); return true; } /// public async Task AssignClubRoleAsync(Guid userProfileId, Guid clubId, string roleName, Guid assignedById, CancellationToken ct = default) { // Verify membership exists var membership = await _appDb.UserProfileClubs .Include(upc => upc.RoleAssignments) .FirstOrDefaultAsync(upc => upc.UserProfileId == userProfileId && upc.ClubId == clubId, ct); if (membership == null) return false; // Check if role already assigned if (membership.RoleAssignments.Any(ra => ra.RoleName == roleName)) return false; // Get role from Identity var role = await _roleManager.FindByNameAsync(roleName); var roleId = role?.Id ?? Guid.Empty; var roleAssignment = new UserProfileClubRoleAssignment { Id = Guid.NewGuid(), UserProfileId = userProfileId, ClubId = clubId, RoleId = roleId, RoleName = roleName, AssignedAt = DateTime.UtcNow, AssignedById = assignedById, CreatedAt = DateTime.UtcNow }; _appDb.Set().Add(roleAssignment); await _appDb.SaveChangesAsync(ct); return true; } /// public async Task RemoveClubRoleAsync(Guid userProfileId, Guid clubId, string roleName, CancellationToken ct = default) { var roleAssignment = await _appDb.Set() .FirstOrDefaultAsync(ra => ra.UserProfileId == userProfileId && ra.ClubId == clubId && ra.RoleName == roleName, ct); if (roleAssignment == null) return false; _appDb.Set().Remove(roleAssignment); await _appDb.SaveChangesAsync(ct); return true; } /// public async Task> GetAllUsersAsync(CancellationToken ct = default) { var profiles = await _appDb.UserProfiles .AsNoTracking() .Include(p => p.Clubs) .ThenInclude(c => c.Club) .Include(p => p.Clubs) .ThenInclude(c => c.RoleAssignments) .Where(p => !p.IsDeleted) .ToListAsync(ct); var result = new List(); foreach (var profile in profiles) { var identity = await _userManager.FindByIdAsync(profile.IdentityUserId.ToString()); if (identity == null) continue; var dto = _mapper.Map(new UserComposite(profile, identity)); var roles = await _userManager.GetRolesAsync(identity); dto.Identity.Roles = roles.ToList(); // Map club memberships dto.ClubMemberships = profile.Clubs.Select(c => new UserClubMembershipDto { ClubId = c.ClubId, ClubName = c.Club?.Name ?? "", IsDefault = c.IsDefault, AssignedAt = c.AssignedAt, Roles = c.RoleAssignments.Select(ra => ra.RoleName).ToList() }).ToList(); result.Add(dto); } return result; } /// public async Task> GetUsersByClubAsync(Guid clubId, CancellationToken ct = default) { var profileIds = await _appDb.UserProfileClubs .Where(upc => upc.ClubId == clubId) .Select(upc => upc.UserProfileId) .ToListAsync(ct); var profiles = await _appDb.UserProfiles .AsNoTracking() .Include(p => p.Clubs) .ThenInclude(c => c.Club) .Include(p => p.Clubs) .ThenInclude(c => c.RoleAssignments) .Where(p => profileIds.Contains(p.Id) && !p.IsDeleted) .ToListAsync(ct); var result = new List(); foreach (var profile in profiles) { var identity = await _userManager.FindByIdAsync(profile.IdentityUserId.ToString()); if (identity == null) continue; var dto = _mapper.Map(new UserComposite(profile, identity)); var roles = await _userManager.GetRolesAsync(identity); dto.Identity.Roles = roles.ToList(); // Map club memberships dto.ClubMemberships = profile.Clubs.Select(c => new UserClubMembershipDto { ClubId = c.ClubId, ClubName = c.Club?.Name ?? "", IsDefault = c.IsDefault, AssignedAt = c.AssignedAt, Roles = c.RoleAssignments.Select(ra => ra.RoleName).ToList() }).ToList(); result.Add(dto); } return result; } }