119 lines
3.9 KiB
C#
119 lines
3.9 KiB
C#
using System.Security.Cryptography;
|
|
using Socialize.Infrastructure.Security;
|
|
using Socialize.Modules.Notifications.Contracts;
|
|
|
|
namespace Socialize.Modules.Approvals.Handlers;
|
|
|
|
public record CreateApprovalRequestRequest(
|
|
Guid WorkspaceId,
|
|
Guid ContentItemId,
|
|
string Stage,
|
|
string ReviewerName,
|
|
string ReviewerEmail,
|
|
DateTimeOffset? DueAt);
|
|
|
|
public class CreateApprovalRequestRequestValidator
|
|
: Validator<CreateApprovalRequestRequest>
|
|
{
|
|
public CreateApprovalRequestRequestValidator()
|
|
{
|
|
RuleFor(x => x.WorkspaceId).NotEmpty();
|
|
RuleFor(x => x.ContentItemId).NotEmpty();
|
|
RuleFor(x => x.Stage).NotEmpty().MaximumLength(64);
|
|
RuleFor(x => x.ReviewerName).NotEmpty().MaximumLength(256);
|
|
RuleFor(x => x.ReviewerEmail).NotEmpty().MaximumLength(256).EmailAddress();
|
|
}
|
|
}
|
|
|
|
public class CreateApprovalRequestHandler(
|
|
AppDbContext dbContext,
|
|
AccessScopeService accessScopeService,
|
|
INotificationEventWriter notificationEventWriter)
|
|
: Endpoint<CreateApprovalRequestRequest, ApprovalRequestDto>
|
|
{
|
|
public override void Configure()
|
|
{
|
|
Post("/api/approvals");
|
|
Options(o => o.WithTags("Approvals"));
|
|
}
|
|
|
|
public override async Task HandleAsync(CreateApprovalRequestRequest request, CancellationToken ct)
|
|
{
|
|
ContentItem? contentItem = await dbContext.ContentItems
|
|
.SingleOrDefaultAsync(
|
|
candidate => candidate.Id == request.ContentItemId && candidate.WorkspaceId == request.WorkspaceId,
|
|
ct);
|
|
|
|
if (contentItem is null)
|
|
{
|
|
AddError(request => request.ContentItemId, "The selected content item does not exist in the active workspace.");
|
|
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
|
return;
|
|
}
|
|
|
|
if (!accessScopeService.CanManageWorkspace(User, contentItem.WorkspaceId))
|
|
{
|
|
await SendForbiddenAsync(ct);
|
|
return;
|
|
}
|
|
|
|
ApprovalRequest approval = new()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
WorkspaceId = request.WorkspaceId,
|
|
ContentItemId = request.ContentItemId,
|
|
Stage = request.Stage.Trim(),
|
|
ReviewerName = request.ReviewerName.Trim(),
|
|
ReviewerEmail = request.ReviewerEmail.Trim(),
|
|
RequestedByUserId = User.GetUserId(),
|
|
DueAt = request.DueAt,
|
|
State = "Pending",
|
|
AccessToken = Convert.ToHexString(RandomNumberGenerator.GetBytes(16)).ToLowerInvariant(),
|
|
SentAt = DateTimeOffset.UtcNow,
|
|
};
|
|
|
|
dbContext.ApprovalRequests.Add(approval);
|
|
|
|
if (approval.Stage == "Internal")
|
|
{
|
|
contentItem.Status = "In internal review";
|
|
}
|
|
else if (approval.Stage == "Client")
|
|
{
|
|
contentItem.Status = "In client review";
|
|
}
|
|
|
|
await dbContext.SaveChangesAsync(ct);
|
|
|
|
await notificationEventWriter.WriteAsync(
|
|
new NotificationEventWriteModel(
|
|
approval.WorkspaceId,
|
|
approval.ContentItemId,
|
|
"approval.requested",
|
|
"ApprovalRequest",
|
|
approval.Id,
|
|
$"Approval requested from {approval.ReviewerName} for {contentItem.Title}.",
|
|
null,
|
|
approval.ReviewerEmail,
|
|
$$"""{"stage":"{{approval.Stage}}","accessToken":"{{approval.AccessToken}}"}"""),
|
|
ct);
|
|
|
|
ApprovalRequestDto dto = new(
|
|
approval.Id,
|
|
approval.WorkspaceId,
|
|
approval.ContentItemId,
|
|
approval.Stage,
|
|
approval.ReviewerName,
|
|
approval.ReviewerEmail,
|
|
approval.RequestedByUserId,
|
|
approval.DueAt,
|
|
approval.State,
|
|
approval.AccessToken,
|
|
approval.SentAt,
|
|
approval.CompletedAt,
|
|
[]);
|
|
|
|
await SendAsync(dto, StatusCodes.Status201Created, ct);
|
|
}
|
|
}
|