using FastEndpoints; using Microsoft.EntityFrameworkCore; using Socialize.Api.Data; using Socialize.Api.Modules.Approvals.Data; using Socialize.Api.Modules.ContentItems.Data; using Socialize.Api.Infrastructure.Security; namespace Socialize.Api.Modules.Approvals.Handlers; public record GetApprovalsRequest(Guid ContentItemId); public record ApprovalDecisionDto( Guid Id, Guid ApprovalRequestId, string Decision, string? Comment, Guid? DecidedByUserId, string DecidedByName, string DecidedByEmail, string? DecidedByPortraitUrl, DateTimeOffset CreatedAt); public record ApprovalRequestDto( Guid Id, Guid WorkspaceId, Guid ContentItemId, Guid? WorkflowInstanceId, int? WorkflowStepSortOrder, string? WorkflowStepTargetType, string? WorkflowStepTargetValue, int? WorkflowStepRequiredApproverCount, string Stage, string ReviewerName, string ReviewerEmail, Guid RequestedByUserId, DateTimeOffset? DueAt, string State, string AccessToken, DateTimeOffset SentAt, DateTimeOffset? CompletedAt, IReadOnlyCollection Decisions); public class GetApprovalsHandler( AppDbContext dbContext, AccessScopeService accessScopeService) : Endpoint> { public override void Configure() { Get("/api/approvals"); Options(o => o.WithTags("Approvals")); } public override async Task HandleAsync(GetApprovalsRequest request, CancellationToken ct) { ContentItem? item = await dbContext.ContentItems .SingleOrDefaultAsync(candidate => candidate.Id == request.ContentItemId, ct); if (item is null) { await SendNotFoundAsync(ct); return; } if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId)) { await SendForbiddenAsync(ct); return; } List approvals = await dbContext.ApprovalRequests .Where(approval => approval.ContentItemId == request.ContentItemId) .OrderByDescending(approval => approval.SentAt) .ThenBy(approval => approval.WorkflowStepSortOrder) .ToListAsync(ct); List approvalIds = approvals .Select(approval => approval.Id) .ToList(); List decisions = await dbContext.ApprovalDecisions .Where(decision => approvalIds.Contains(decision.ApprovalRequestId)) .OrderByDescending(decision => decision.CreatedAt) .ToListAsync(ct); List decidedByUserIds = decisions .Where(decision => decision.DecidedByUserId.HasValue) .Select(decision => decision.DecidedByUserId!.Value) .Distinct() .ToList(); Dictionary decisionPortraits = await dbContext.Users .Where(user => decidedByUserIds.Contains(user.Id)) .ToDictionaryAsync(user => user.Id, user => user.PortraitUrl, ct); List dtos = approvals .Select(approval => new ApprovalRequestDto( approval.Id, approval.WorkspaceId, approval.ContentItemId, approval.WorkflowInstanceId, approval.WorkflowStepSortOrder, approval.WorkflowStepTargetType, approval.WorkflowStepTargetValue, approval.WorkflowStepRequiredApproverCount, approval.Stage, approval.ReviewerName, approval.ReviewerEmail, approval.RequestedByUserId, approval.DueAt, approval.State, approval.AccessToken, approval.SentAt, approval.CompletedAt, decisions .Where(decision => decision.ApprovalRequestId == approval.Id) .Select(decision => new ApprovalDecisionDto( decision.Id, decision.ApprovalRequestId, decision.Decision, decision.Comment, decision.DecidedByUserId, decision.DecidedByName, decision.DecidedByEmail, decision.DecidedByUserId.HasValue ? decisionPortraits.GetValueOrDefault(decision.DecidedByUserId.Value) : null, decision.CreatedAt)) .ToList())) .ToList(); await SendOkAsync(dtos, ct); } }