using Fitness_core_BE.Exceptions; using Microsoft.EntityFrameworkCore; namespace Fitness_core_BE.Helpers { public class UserRoleHelper { // Admins/Customer/Client are central-bucket; Staff and CustomerSubscriptions are // tenant-bucket. private readonly CentralDbContext _central; private readonly TenantDbContext _context; public UserRoleHelper(CentralDbContext central, TenantDbContext context) { _central = central; _context = context; } public async Task GetAdminId(Guid userId) { var admin = await _central.Admins .AsNoTracking() .Where(a => a.FKUserId == userId && a.IsActive) .Select(a => a.AdminId) .FirstOrDefaultAsync(); return admin == Guid.Empty ? null : admin; } public async Task GetCusId(Guid userId) { var cus = await _central.Customer .AsNoTracking() .Where(a => a.FKUserId == userId && a.IsActive) .Select(a => a.CusId) .FirstOrDefaultAsync(); if (cus == Guid.Empty) throw new NotFoundException("Customer not found or inactive"); return cus; } public async Task GetStaffId(Guid userId) { var staff = await _context.Staff .AsNoTracking() .Where(s => s.FKUserId == userId && s.IsActive) .Select(s => s.StaffId) .FirstOrDefaultAsync(); return staff == Guid.Empty ? null : staff; } public async Task GetClientId(Guid userId) { var clientId = await _central.Client .AsNoTracking() .Where(c => c.FKUserId == userId) .Select(c => c.ClientId) .FirstOrDefaultAsync(); // Staff can use the same tenant-scoped dashboard and real-time hub. if (clientId == Guid.Empty) { clientId = await _context.Staff .AsNoTracking() .Where(s => s.FKUserId == userId && s.IsActive) .Select(s => s.FKClientId) .FirstOrDefaultAsync(); } if (clientId == Guid.Empty) throw new NotFoundException("Client not found"); return clientId; } // Strict owner lookup used by RBAC decisions. GetClientId intentionally falls // back to a staff member's tenant so normal tenant-scoped data queries work; // that fallback must never make staff users appear to be gym owners. public async Task GetOwnedClientId(Guid userId) { var clientId = await _central.Client .AsNoTracking() .Where(c => c.FKUserId == userId) .Select(c => c.ClientId) .FirstOrDefaultAsync(); return clientId == Guid.Empty ? null : clientId; } // Single-club deployment: exactly one Client row exists, so any caller that just needs // "the club" (rather than a specific user's club) can use this instead of a per-user // lookup. Used by TenantResolutionAttribute's Customer branch, which used to read a // caller-supplied X-Tenant-Id header to pick from many gyms -- there's only one now. public async Task GetDefaultClientId() { var clientId = await _central.Client .AsNoTracking() .Select(c => c.ClientId) .FirstOrDefaultAsync(); if (clientId == Guid.Empty) throw new NotFoundException("Club is not configured yet"); return clientId; } public async Task GetClientIdByCusId(Guid cusId) { var clientId = await _context.CustomerSubscriptions .AsNoTracking() .Where(s => s.FKCustomerId == cusId && s.IsActive) .Select(s => s.CustomerPlan.FKClientId) .FirstOrDefaultAsync(); if (clientId == Guid.Empty) throw new NotFoundException("No active subscription found for customer"); return clientId; } public async Task GetUserType(Guid userId) { var isAdmin = await _central.Admins .AsNoTracking() .AnyAsync(a => a.FKUserId == userId && a.IsActive); if (isAdmin) return "Admin"; var isCustomer = await _central.Customer .AsNoTracking() .AnyAsync(c => c.FKUserId == userId && c.IsActive); if (isCustomer) return "Customer"; var isClient = await _central.Client .AsNoTracking() .AnyAsync(cl => cl.FKUserId == userId); if (isClient) return "Client"; var isStaff = await _context.Staff .AsNoTracking() .AnyAsync(s => s.FKUserId == userId && s.IsActive); if (isStaff) return "Staff"; throw new NotFoundException("User role not found or inactive"); } } }