482 lines
16 KiB
C#
482 lines
16 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Service for user management operations including registration, authentication, and profile management.
|
|
/// </summary>
|
|
public class UserService : IUserService
|
|
{
|
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
private readonly RoleManager<ApplicationRole> _roleManager;
|
|
private readonly AppDbContext _appDb;
|
|
private readonly IMapper _mapper;
|
|
private readonly AuthenticationStateProvider _authStateProvider;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of UserService.
|
|
/// </summary>
|
|
public UserService(SignInManager<ApplicationUser> signInManager,
|
|
UserManager<ApplicationUser> userManager,
|
|
RoleManager<ApplicationRole> roleManager,
|
|
AuthenticationStateProvider authStateProvider,
|
|
IMapper mapper, AppDbContext appDb)
|
|
{
|
|
_signInManager = signInManager;
|
|
_userManager = userManager;
|
|
_roleManager = roleManager;
|
|
_authStateProvider = authStateProvider;
|
|
_mapper = mapper;
|
|
_appDb = appDb;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<UserDto?> 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<Claim>
|
|
{
|
|
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);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task SignOutAsync()
|
|
{
|
|
await _signInManager.SignOutAsync();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<UserDto?> 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<UserDto>(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;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<UserDto?> 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);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IdentityResult> 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;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<string?> RequestPasswordResetAsync(string email, CancellationToken ct = default)
|
|
{
|
|
var user = await _userManager.FindByEmailAsync(email);
|
|
if (user == null)
|
|
return null;
|
|
|
|
return await _userManager.GeneratePasswordResetTokenAsync(user);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IdentityResult> 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);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<UserDto?> 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);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<bool> 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;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<bool> 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<UserProfileClubRoleAssignment>().RemoveRange(membership.RoleAssignments);
|
|
_appDb.UserProfileClubs.Remove(membership);
|
|
await _appDb.SaveChangesAsync(ct);
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<bool> 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<UserProfileClubRoleAssignment>().Add(roleAssignment);
|
|
await _appDb.SaveChangesAsync(ct);
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<bool> RemoveClubRoleAsync(Guid userProfileId, Guid clubId, string roleName, CancellationToken ct = default)
|
|
{
|
|
var roleAssignment = await _appDb.Set<UserProfileClubRoleAssignment>()
|
|
.FirstOrDefaultAsync(ra =>
|
|
ra.UserProfileId == userProfileId &&
|
|
ra.ClubId == clubId &&
|
|
ra.RoleName == roleName, ct);
|
|
|
|
if (roleAssignment == null)
|
|
return false;
|
|
|
|
_appDb.Set<UserProfileClubRoleAssignment>().Remove(roleAssignment);
|
|
await _appDb.SaveChangesAsync(ct);
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IReadOnlyList<UserDto>> 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<UserDto>();
|
|
|
|
foreach (var profile in profiles)
|
|
{
|
|
var identity = await _userManager.FindByIdAsync(profile.IdentityUserId.ToString());
|
|
if (identity == null)
|
|
continue;
|
|
|
|
var dto = _mapper.Map<UserDto>(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;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IReadOnlyList<UserDto>> 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<UserDto>();
|
|
|
|
foreach (var profile in profiles)
|
|
{
|
|
var identity = await _userManager.FindByIdAsync(profile.IdentityUserId.ToString());
|
|
if (identity == null)
|
|
continue;
|
|
|
|
var dto = _mapper.Map<UserDto>(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;
|
|
}
|
|
} |