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;
}
}