using Fitness_core_BE.DTOs.Rbac; using Fitness_core_BE.Exceptions; using Fitness_core_BE.Helpers; using Fitness_core_BE.Infra.UoW; using Fitness_core_BE.Interfaces; using Fitness_core_BE.Interfaces.Services; using Fitness_core_BE.Models; using Microsoft.AspNetCore.Http; namespace Fitness_core_BE.Services { public class GymRoleService : IGymRoleService { private readonly IGymRoleRepository _gymRoleRepository; private readonly INavItemRepository _navItemRepository; private readonly UserRoleHelper _userRoleHelper; private readonly IUnitOfWork _uow; public GymRoleService( IGymRoleRepository gymRoleRepository, INavItemRepository navItemRepository, UserRoleHelper userRoleHelper, IUnitOfWork uow) { _gymRoleRepository = gymRoleRepository; _navItemRepository = navItemRepository; _userRoleHelper = userRoleHelper; _uow = uow; } public async Task> GetRolesAsync(HttpContext httpContext) { var clientId = await GetOwnerClientId(httpContext); var roles = await _gymRoleRepository.GetRolesByClientId(clientId); return roles.Select(ToDto).ToList(); } public async Task CreateRoleAsync(CreateGymRoleRequest request, HttpContext httpContext) { var clientId = await GetOwnerClientId(httpContext); var role = new GymRole { FKClientId = clientId, Name = request.Name }; var created = await _gymRoleRepository.CreateRole(role); await _uow.SaveChangesAsync(); return ToDto(created); } public async Task UpdateRoleAsync(Guid gymRoleId, UpdateGymRoleRequest request, HttpContext httpContext) { var clientId = await GetOwnerClientId(httpContext); var role = await GetOwnedRole(gymRoleId, clientId); if (request.Name != null) role.Name = request.Name; if (request.IsActive.HasValue) role.IsActive = request.IsActive.Value; var updated = await _gymRoleRepository.UpdateRole(role); await _uow.SaveChangesAsync(); return ToDto(updated); } public async Task DeleteRoleAsync(Guid gymRoleId, HttpContext httpContext) { var clientId = await GetOwnerClientId(httpContext); var role = await GetOwnedRole(gymRoleId, clientId); await _gymRoleRepository.DeleteRole(role); await _uow.SaveChangesAsync(); } public async Task SetRolePermissionsAsync(Guid gymRoleId, SetRolePermissionsRequest request, HttpContext httpContext) { var clientId = await GetOwnerClientId(httpContext); var role = await GetOwnedRole(gymRoleId, clientId); var requestedKeys = request.NavKeys?.Distinct().ToList() ?? new List(); var assignableKeys = (await _navItemRepository.GetActiveNavItemsOrdered()) .Where(item => !item.IsOwnerOnly && item.Key != "dashboard") .Select(item => item.Key) .ToHashSet(); var invalidKeys = requestedKeys.Where(key => !assignableKeys.Contains(key)).ToList(); if (invalidKeys.Count > 0) throw new BadRequestException($"Invalid role permission: {string.Join(", ", invalidKeys)}."); await _gymRoleRepository.ReplacePermissions(gymRoleId, requestedKeys); await _uow.SaveChangesAsync(); var refreshed = await _gymRoleRepository.GetRoleById(gymRoleId) ?? throw new NotFoundException("Role not found."); return ToDto(refreshed); } public async Task AssignStaffRoleAsync(Guid staffId, AssignStaffRoleRequest request, HttpContext httpContext) { var clientId = await GetOwnerClientId(httpContext); var assigned = await _gymRoleRepository.AssignStaffRole(staffId, clientId, request.GymRoleId); if (!assigned) throw new NotFoundException("Staff member or role not found for this gym."); await _uow.SaveChangesAsync(); } // Every public method funnels through here: only a caller who resolves to a Client // (the gym owner) can manage roles. Staff — regardless of any nav-section permission // they've been granted, including "staff" — are rejected before any GymRole/ // GymRolePermission data is ever touched. private async Task GetOwnerClientId(HttpContext httpContext) { var userId = DataExtractor.GetUserIdFromContext(httpContext); return await _userRoleHelper.GetOwnedClientId(userId) ?? throw new UnauthorizedAccessException("Only the gym owner can manage roles and permissions."); } private async Task GetOwnedRole(Guid gymRoleId, Guid clientId) { var role = await _gymRoleRepository.GetRoleById(gymRoleId) ?? throw new NotFoundException("Role not found."); if (role.FKClientId != clientId) throw new NotFoundException("Role not found."); return role; } private static GymRoleDto ToDto(GymRole role) => new() { GymRoleId = role.GymRoleId, Name = role.Name, IsActive = role.IsActive, NavKeys = role.Permissions?.Select(p => p.NavItemKey).ToList() ?? new List() }; } }