using FastEndpoints; using Microsoft.EntityFrameworkCore; using Socialize.Api.Data; using Socialize.Api.Modules.Identity.Data; using Socialize.Api.Modules.Organizations.Data; using Socialize.Api.Modules.Organizations.Services; using Socialize.Api.Modules.Workspaces.Handlers; namespace Socialize.Api.Modules.Organizations.Handlers; internal class GetOrganizationHandler( AppDbContext dbContext, OrganizationAccessService organizationAccessService) : EndpointWithoutRequest { public override void Configure() { Get("/api/organizations/{organizationId:guid}"); Options(o => o.WithTags("Organizations")); } public override async Task HandleAsync(CancellationToken ct) { Guid organizationId = Route("organizationId"); Organization? organization = await dbContext.Organizations .SingleOrDefaultAsync(candidate => candidate.Id == organizationId, ct); if (organization is null) { await SendNotFoundAsync(ct); return; } if (!await organizationAccessService.CanAccessOrganizationAsync(User, organizationId, ct)) { await SendForbiddenAsync(ct); return; } IReadOnlyCollection currentUserPermissions = await organizationAccessService.GetUserOrganizationPermissionsAsync( User, organizationId, ct); IReadOnlyCollection members = await GetMembersAsync(organizationId, ct); IReadOnlyCollection workspaces = await GetWorkspacesAsync(organizationId, ct); OrganizationUsageDto usage = await GetUsageAsync(organization, ct); await SendOkAsync( OrganizationDto.FromOrganization( organization, currentUserPermissions, members, workspaces, usage), ct); } private async Task> GetMembersAsync( Guid organizationId, CancellationToken ct) { var rows = await dbContext.OrganizationMemberships .Where(membership => membership.OrganizationId == organizationId) .Join( dbContext.Users, membership => membership.UserId, user => user.Id, (membership, user) => new { Membership = membership, User = user }) .OrderBy(row => row.User.Lastname) .ThenBy(row => row.User.Firstname) .ThenBy(row => row.User.Email) .ToListAsync(ct); return rows .Select(row => new OrganizationMemberDto( row.User.Id, BuildDisplayName(row.User), row.User.Email ?? string.Empty, row.User.PortraitUrl, row.Membership.Role, OrganizationPermissionRules.GetPermissionsForRole(row.Membership.Role), row.Membership.CreatedAt)) .ToArray(); } private async Task> GetWorkspacesAsync( Guid organizationId, CancellationToken ct) { var workspaces = await dbContext.Workspaces .Where(workspace => workspace.OrganizationId == organizationId) .OrderBy(workspace => workspace.Name) .ToListAsync(ct); return workspaces .Select(workspace => WorkspaceDto.FromWorkspace(workspace, [])) .ToArray(); } private async Task GetUsageAsync( Organization organization, CancellationToken ct) { Guid[] workspaceIds = await dbContext.Workspaces .Where(workspace => workspace.OrganizationId == organization.Id) .Select(workspace => workspace.Id) .ToArrayAsync(ct); Guid[] memberUserIds = await dbContext.OrganizationMemberships .Where(membership => membership.OrganizationId == organization.Id) .Select(membership => membership.UserId) .Distinct() .ToArrayAsync(ct); int userCount = memberUserIds .Append(organization.OwnerUserId) .Distinct() .Count(); int activeContentItemCount = workspaceIds.Length == 0 ? 0 : await dbContext.ContentItems .Where(contentItem => workspaceIds.Contains(contentItem.WorkspaceId) && contentItem.Status != "Approved" && contentItem.Status != "Scheduled") .CountAsync(ct); OrganizationUsageLimits limits = GetUsageLimits(organization.Name); return new OrganizationUsageDto( limits.PlanName, [ new OrganizationUsageItemDto("users", userCount, limits.UserLimit), new OrganizationUsageItemDto("workspaces", workspaceIds.Length, limits.WorkspaceLimit), new OrganizationUsageItemDto("activeContent", activeContentItemCount, limits.ActiveContentLimit), ]); } private static OrganizationUsageLimits GetUsageLimits(string organizationName) { return string.Equals(organizationName, "Northstar Agency", StringComparison.OrdinalIgnoreCase) ? new OrganizationUsageLimits("Agency", 25, 15, 250) : new OrganizationUsageLimits("Free", 2, 1, 3); } private sealed record OrganizationUsageLimits( string PlanName, int UserLimit, int WorkspaceLimit, int ActiveContentLimit); 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(); } }