using FastEndpoints; using Microsoft.EntityFrameworkCore; using System.Security.Claims; using Socialize.Api.Data; using Socialize.Api.Modules.Identity.Data; using Socialize.Api.Infrastructure.Security; using Socialize.Api.Modules.Workspaces.Data; namespace Socialize.Api.Modules.Workspaces.Handlers; public record WorkspaceMemberDto( Guid Id, string DisplayName, string Email, string? PortraitUrl, string RelationshipCategory, IReadOnlyCollection Roles); public class GetWorkspaceMembersHandler( AppDbContext dbContext, AccessScopeService accessScopeService) : EndpointWithoutRequest> { public override void Configure() { Get("/api/workspaces/{workspaceId:guid}/members"); Options(o => o.WithTags("Workspaces")); } public override async Task HandleAsync(CancellationToken ct) { Guid workspaceId = Route("workspaceId"); if (!await accessScopeService.CanManageWorkspaceAsync(User, workspaceId, ct)) { await SendForbiddenAsync(ct); return; } Workspace? workspace = await dbContext.Workspaces .SingleOrDefaultAsync(candidate => candidate.Id == workspaceId, ct); if (workspace is null) { await SendNotFoundAsync(ct); return; } string workspaceClaimValue = workspaceId.ToString(); var users = await dbContext.Users .Where(candidate => dbContext.UserClaims.Any(claim => claim.UserId == candidate.Id && claim.ClaimType == KnownClaims.WorkspaceScope && claim.ClaimValue == workspaceClaimValue) || dbContext.OrganizationMemberships.Any(membership => membership.UserId == candidate.Id && membership.OrganizationId == workspace.OrganizationId) || candidate.Id == workspace.OwnerUserId) .OrderBy(candidate => candidate.Lastname) .ThenBy(candidate => candidate.Firstname) .ThenBy(candidate => candidate.Email) .ToListAsync(ct); var userIds = users .Select(candidate => candidate.Id) .ToList(); Dictionary> rolesByUserId = await dbContext.UserRoles .Where(candidate => userIds.Contains(candidate.UserId)) .Join( dbContext.Roles, userRole => userRole.RoleId, role => role.Id, (userRole, role) => new { userRole.UserId, role.Name }) .GroupBy(candidate => candidate.UserId) .ToDictionaryAsync( group => group.Key, group => (IReadOnlyCollection)group .Select(candidate => candidate.Name) .Where(name => !string.IsNullOrWhiteSpace(name)) .Cast() .OrderBy(name => name) .ToArray(), ct); HashSet organizationMemberUserIds = await dbContext.OrganizationMemberships .Where(membership => membership.OrganizationId == workspace.OrganizationId) .Select(membership => membership.UserId) .ToHashSetAsync(ct); organizationMemberUserIds.Add(workspace.OwnerUserId); var members = users .Select(candidate => new WorkspaceMemberDto( candidate.Id, BuildDisplayName(candidate), candidate.Email ?? string.Empty, candidate.PortraitUrl, organizationMemberUserIds.Contains(candidate.Id) ? "Organization Member" : "External Collaborator", rolesByUserId.GetValueOrDefault(candidate.Id) ?? Array.Empty())) .ToList(); await SendOkAsync(members, ct); } private static string BuildDisplayName(User user) { if (!string.IsNullOrWhiteSpace(user.Alias)) { return user.Alias; } string fullName = $"{user.Firstname} {user.Lastname}".Trim(); if (!string.IsNullOrWhiteSpace(fullName)) { return fullName; } return user.Email ?? user.UserName ?? user.Id.ToString(); } }