Files
social-media/backend/src/Socialize.Api/Modules/Approvals/Handlers/GetApprovals.cs
2026-05-01 14:23:37 -04:00

134 lines
4.6 KiB
C#

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<ApprovalDecisionDto> Decisions);
public class GetApprovalsHandler(
AppDbContext dbContext,
AccessScopeService accessScopeService)
: Endpoint<GetApprovalsRequest, IReadOnlyCollection<ApprovalRequestDto>>
{
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<ApprovalRequest> approvals = await dbContext.ApprovalRequests
.Where(approval => approval.ContentItemId == request.ContentItemId)
.OrderByDescending(approval => approval.SentAt)
.ThenBy(approval => approval.WorkflowStepSortOrder)
.ToListAsync(ct);
List<Guid> approvalIds = approvals
.Select(approval => approval.Id)
.ToList();
List<ApprovalDecision> decisions = await dbContext.ApprovalDecisions
.Where(decision => approvalIds.Contains(decision.ApprovalRequestId))
.OrderByDescending(decision => decision.CreatedAt)
.ToListAsync(ct);
List<Guid> decidedByUserIds = decisions
.Where(decision => decision.DecidedByUserId.HasValue)
.Select(decision => decision.DecidedByUserId!.Value)
.Distinct()
.ToList();
Dictionary<Guid, string?> decisionPortraits = await dbContext.Users
.Where(user => decidedByUserIds.Contains(user.Id))
.ToDictionaryAsync(user => user.Id, user => user.PortraitUrl, ct);
List<ApprovalRequestDto> 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);
}
}