using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Moonlight.Api.Database; using Moonlight.Api.Database.Entities; using Moonlight.Api.Mappers; using Moonlight.Shared; using Moonlight.Shared.Http.Responses; using Moonlight.Shared.Http.Responses.Users; namespace Moonlight.Api.Http.Controllers.Admin; [ApiController] [Authorize(Policy = Permissions.Roles.Members)] [Route("api/admin/roles/{roleId:int}/members")] public class RoleMembersController : Controller { private readonly DatabaseRepository UsersRepository; private readonly DatabaseRepository RolesRepository; private readonly DatabaseRepository RoleMembersRepository; public RoleMembersController( DatabaseRepository usersRepository, DatabaseRepository rolesRepository, DatabaseRepository roleMembersRepository ) { UsersRepository = usersRepository; RolesRepository = rolesRepository; RoleMembersRepository = roleMembersRepository; } [HttpGet] public async Task>> GetAsync( [FromRoute] int roleId, [FromQuery] int startIndex, [FromQuery] int length, [FromQuery] string? searchTerm ) { // Validation if (startIndex < 0) return Problem("Invalid start index specified", statusCode: 400); if (length is < 1 or > 100) return Problem("Invalid length specified"); // Query building var query = RoleMembersRepository .Query() .Where(x => x.Role.Id == roleId) .Select(x => x.User); // Filtering if (!string.IsNullOrWhiteSpace(searchTerm)) { query = query.Where(x => EF.Functions.ILike(x.Username, $"%{searchTerm}%") || EF.Functions.ILike(x.Email, $"%{searchTerm}%") ); } // Pagination var items = query .OrderBy(x => x.Id) .Skip(startIndex) .Take(length) .ProjectToDto() .ToArray(); var totalCount = await query.CountAsync(); return new PagedData(items, totalCount); } [HttpGet("available")] public async Task>> GetAvailableAsync( [FromRoute] int roleId, [FromQuery] int startIndex, [FromQuery] int length, [FromQuery] string? searchTerm ) { // Validation if (startIndex < 0) return Problem("Invalid start index specified", statusCode: 400); if (length is < 1 or > 100) return Problem("Invalid length specified"); // Query building var query = UsersRepository .Query() .Where(x => x.RoleMemberships.All(y => y.Role.Id != roleId)); // Filtering if (!string.IsNullOrWhiteSpace(searchTerm)) { query = query.Where(x => EF.Functions.ILike(x.Username, $"%{searchTerm}%") || EF.Functions.ILike(x.Email, $"%{searchTerm}%") ); } // Pagination var items = query .OrderBy(x => x.Id) .Skip(startIndex) .Take(length) .ProjectToDto() .ToArray(); var totalCount = await query.CountAsync(); return new PagedData(items, totalCount); } [HttpPut("{userId:int}")] public async Task AddMemberAsync([FromRoute] int roleId, [FromRoute] int userId) { // Check and load role var role = await RolesRepository .Query() .FirstOrDefaultAsync(x => x.Id == roleId); if (role == null) return Problem("Role not found", statusCode: 404); // Check and load user var user = await UsersRepository .Query() .FirstOrDefaultAsync(x => x.Id == userId); if (user == null) return Problem("User not found", statusCode: 404); // Check if a role member already exists with these details var isUserInRole = await RoleMembersRepository .Query() .AnyAsync(x => x.Role.Id == roleId && x.User.Id == userId); if (isUserInRole) return Problem("User is already a member of this role", statusCode: 400); var roleMember = new RoleMember { Role = role, User = user }; await RoleMembersRepository.AddAsync(roleMember); return NoContent(); } [HttpDelete("{userId:int}")] public async Task RemoveMemberAsync([FromRoute] int roleId, [FromRoute] int userId) { var roleMember = await RoleMembersRepository .Query() .FirstOrDefaultAsync(x => x.User.Id == userId && x.Role.Id == roleId); if (roleMember == null) return Problem("User is not a member of this role, the role does not exist or the user does not exist", statusCode: 404); await RoleMembersRepository.RemoveAsync(roleMember); return NoContent(); } }