chore: add missing multi-level editor for approval workflow, rename projects to campaings.
This commit is contained in:
@@ -11,7 +11,7 @@ namespace Socialize.Api.Modules.ContentItems.Handlers;
|
||||
public record CreateContentItemRequest(
|
||||
Guid WorkspaceId,
|
||||
Guid ClientId,
|
||||
Guid ProjectId,
|
||||
Guid CampaignId,
|
||||
string Title,
|
||||
string PublicationMessage,
|
||||
string PublicationTargets,
|
||||
@@ -25,7 +25,7 @@ public class CreateContentItemRequestValidator
|
||||
{
|
||||
RuleFor(x => x.WorkspaceId).NotEmpty();
|
||||
RuleFor(x => x.ClientId).NotEmpty();
|
||||
RuleFor(x => x.ProjectId).NotEmpty();
|
||||
RuleFor(x => x.CampaignId).NotEmpty();
|
||||
RuleFor(x => x.Title).NotEmpty().MaximumLength(256);
|
||||
RuleFor(x => x.PublicationMessage).NotEmpty().MaximumLength(4000);
|
||||
RuleFor(x => x.PublicationTargets).NotEmpty().MaximumLength(512);
|
||||
@@ -47,7 +47,7 @@ public class CreateContentItemHandler(
|
||||
|
||||
public override async Task HandleAsync(CreateContentItemRequest request, CancellationToken ct)
|
||||
{
|
||||
if (!accessScopeService.CanContributeToProject(User, request.WorkspaceId, request.ClientId, request.ProjectId))
|
||||
if (!accessScopeService.CanContributeToCampaign(User, request.WorkspaceId, request.ClientId, request.CampaignId))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
@@ -75,16 +75,16 @@ public class CreateContentItemHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
bool projectExists = await dbContext.Projects
|
||||
bool campaignExists = await dbContext.Campaigns
|
||||
.AnyAsync(
|
||||
project => project.Id == request.ProjectId &&
|
||||
project.WorkspaceId == request.WorkspaceId &&
|
||||
project.ClientId == request.ClientId,
|
||||
campaign => campaign.Id == request.CampaignId &&
|
||||
campaign.WorkspaceId == request.WorkspaceId &&
|
||||
campaign.ClientId == request.ClientId,
|
||||
ct);
|
||||
|
||||
if (!projectExists)
|
||||
if (!campaignExists)
|
||||
{
|
||||
AddError(request => request.ProjectId, "The selected project does not belong to the selected client.");
|
||||
AddError(request => request.CampaignId, "The selected campaign does not belong to the selected client.");
|
||||
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
||||
return;
|
||||
}
|
||||
@@ -94,7 +94,7 @@ public class CreateContentItemHandler(
|
||||
Id = Guid.NewGuid(),
|
||||
WorkspaceId = request.WorkspaceId,
|
||||
ClientId = request.ClientId,
|
||||
ProjectId = request.ProjectId,
|
||||
CampaignId = request.CampaignId,
|
||||
Title = request.Title.Trim(),
|
||||
PublicationMessage = request.PublicationMessage.Trim(),
|
||||
PublicationTargets = request.PublicationTargets.Trim(),
|
||||
@@ -138,7 +138,7 @@ public class CreateContentItemHandler(
|
||||
item.Id,
|
||||
item.WorkspaceId,
|
||||
item.ClientId,
|
||||
item.ProjectId,
|
||||
item.CampaignId,
|
||||
item.Title,
|
||||
item.PublicationMessage,
|
||||
item.PublicationTargets,
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CreateContentItemRevisionHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanContributeToProject(User, item.WorkspaceId, item.ClientId, item.ProjectId))
|
||||
if (!accessScopeService.CanContributeToCampaign(User, item.WorkspaceId, item.ClientId, item.CampaignId))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
@@ -66,15 +66,6 @@ public class CreateContentItemRevisionHandler(
|
||||
item.CurrentRevisionNumber = revisionNumber;
|
||||
item.CurrentRevisionLabel = revisionLabel;
|
||||
|
||||
if (item.Status == "Changes requested internally")
|
||||
{
|
||||
item.Status = "Internal changes in progress";
|
||||
}
|
||||
else if (item.Status == "Changes requested by client")
|
||||
{
|
||||
item.Status = "Client changes in progress";
|
||||
}
|
||||
|
||||
ContentItemRevision revision = new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
|
||||
@@ -10,7 +10,7 @@ public record ContentItemDetailDto(
|
||||
Guid Id,
|
||||
Guid WorkspaceId,
|
||||
Guid ClientId,
|
||||
Guid ProjectId,
|
||||
Guid CampaignId,
|
||||
string Title,
|
||||
string PublicationMessage,
|
||||
string PublicationTargets,
|
||||
@@ -42,7 +42,7 @@ public class GetContentItemHandler(
|
||||
candidate.Id,
|
||||
candidate.WorkspaceId,
|
||||
candidate.ClientId,
|
||||
candidate.ProjectId,
|
||||
candidate.CampaignId,
|
||||
candidate.Title,
|
||||
candidate.PublicationMessage,
|
||||
candidate.PublicationTargets,
|
||||
@@ -60,7 +60,7 @@ public class GetContentItemHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId))
|
||||
if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.CampaignId))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class GetContentItemRevisionsHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId))
|
||||
if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.CampaignId))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -6,13 +6,13 @@ using Socialize.Api.Modules.ContentItems.Data;
|
||||
|
||||
namespace Socialize.Api.Modules.ContentItems.Handlers;
|
||||
|
||||
public record GetContentItemsRequest(Guid? WorkspaceId, Guid? ClientId, Guid? ProjectId);
|
||||
public record GetContentItemsRequest(Guid? WorkspaceId, Guid? ClientId, Guid? CampaignId);
|
||||
|
||||
public record ContentItemDto(
|
||||
Guid Id,
|
||||
Guid WorkspaceId,
|
||||
Guid ClientId,
|
||||
Guid ProjectId,
|
||||
Guid CampaignId,
|
||||
string Title,
|
||||
string PublicationMessage,
|
||||
string PublicationTargets,
|
||||
@@ -41,7 +41,7 @@ public class GetContentItemsHandler(
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = User.GetWorkspaceScopeIds();
|
||||
IReadOnlyCollection<Guid> clientScopeIds = User.GetClientScopeIds();
|
||||
IReadOnlyCollection<Guid> projectScopeIds = User.GetProjectScopeIds();
|
||||
IReadOnlyCollection<Guid> campaignScopeIds = User.GetCampaignScopeIds();
|
||||
|
||||
query = query.Where(item => workspaceScopeIds.Contains(item.WorkspaceId));
|
||||
|
||||
@@ -50,9 +50,9 @@ public class GetContentItemsHandler(
|
||||
query = query.Where(item => clientScopeIds.Contains(item.ClientId));
|
||||
}
|
||||
|
||||
if (projectScopeIds.Count > 0)
|
||||
if (campaignScopeIds.Count > 0)
|
||||
{
|
||||
query = query.Where(item => projectScopeIds.Contains(item.ProjectId));
|
||||
query = query.Where(item => campaignScopeIds.Contains(item.CampaignId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +61,9 @@ public class GetContentItemsHandler(
|
||||
query = query.Where(item => item.WorkspaceId == request.WorkspaceId.Value);
|
||||
}
|
||||
|
||||
if (request.ProjectId.HasValue)
|
||||
if (request.CampaignId.HasValue)
|
||||
{
|
||||
query = query.Where(item => item.ProjectId == request.ProjectId.Value);
|
||||
query = query.Where(item => item.CampaignId == request.CampaignId.Value);
|
||||
}
|
||||
|
||||
if (request.ClientId.HasValue)
|
||||
@@ -78,7 +78,7 @@ public class GetContentItemsHandler(
|
||||
item.Id,
|
||||
item.WorkspaceId,
|
||||
item.ClientId,
|
||||
item.ProjectId,
|
||||
item.CampaignId,
|
||||
item.Title,
|
||||
item.PublicationMessage,
|
||||
item.PublicationTargets,
|
||||
|
||||
@@ -2,8 +2,10 @@ using FastEndpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Approvals.Services;
|
||||
using Socialize.Api.Modules.ContentItems.Data;
|
||||
using Socialize.Api.Modules.Notifications.Contracts;
|
||||
using Socialize.Api.Modules.Workspaces.Data;
|
||||
|
||||
namespace Socialize.Api.Modules.ContentItems.Handlers;
|
||||
|
||||
@@ -21,24 +23,18 @@ public class UpdateContentItemStatusRequestValidator
|
||||
public class UpdateContentItemStatusHandler(
|
||||
AppDbContext dbContext,
|
||||
AccessScopeService accessScopeService,
|
||||
ApprovalWorkflowRuntimeService approvalWorkflowRuntimeService,
|
||||
INotificationEventWriter notificationEventWriter)
|
||||
: Endpoint<UpdateContentItemStatusRequest, ContentItemDetailDto>
|
||||
{
|
||||
private static readonly HashSet<string> AllowedStatuses =
|
||||
[
|
||||
"Draft",
|
||||
"In internal review",
|
||||
"Changes requested internally",
|
||||
"Internal changes in progress",
|
||||
"Ready for client review",
|
||||
"In client review",
|
||||
"Changes requested by client",
|
||||
"Client changes in progress",
|
||||
"In production",
|
||||
"In approval",
|
||||
"Approved",
|
||||
"Rejected",
|
||||
"Ready to publish",
|
||||
"Scheduled",
|
||||
"Published",
|
||||
"Archived",
|
||||
];
|
||||
|
||||
public override void Configure()
|
||||
@@ -72,7 +68,64 @@ public class UpdateContentItemStatusHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
item.Status = normalizedStatus;
|
||||
Workspace? workspace = await dbContext.Workspaces.SingleOrDefaultAsync(candidate => candidate.Id == item.WorkspaceId, ct);
|
||||
if (workspace is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizedStatus == "In approval" && workspace.ApprovalMode == ApprovalModes.MultiLevel)
|
||||
{
|
||||
ApprovalWorkflowStartResult startResult = await approvalWorkflowRuntimeService.StartMultiLevelWorkflowAsync(
|
||||
item,
|
||||
workspace,
|
||||
User.GetUserId(),
|
||||
ct);
|
||||
|
||||
if (!startResult.Succeeded)
|
||||
{
|
||||
AddError(request => request.Status, startResult.ErrorMessage ?? "The approval workflow could not be started.");
|
||||
await SendErrorsAsync(StatusCodes.Status409Conflict, ct);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (ApprovalWorkflowRules.IsApprovalCompletionStatus(normalizedStatus) &&
|
||||
ApprovalWorkflowRules.BlocksManualApprovedOrScheduledStatus(workspace.ApprovalMode))
|
||||
{
|
||||
if (workspace.ApprovalMode == ApprovalModes.MultiLevel)
|
||||
{
|
||||
bool hasCompletedWorkflow = await approvalWorkflowRuntimeService.HasCompletedMultiLevelWorkflowAsync(item.Id, ct);
|
||||
|
||||
if (!hasCompletedWorkflow)
|
||||
{
|
||||
AddError(request => request.Status, "This workspace requires the multi-level approval workflow to complete before content can be approved or scheduled.");
|
||||
await SendErrorsAsync(StatusCodes.Status409Conflict, ct);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool hasApprovedDecision = await dbContext.ApprovalRequests.AnyAsync(
|
||||
approval => approval.ContentItemId == item.Id &&
|
||||
approval.WorkspaceId == item.WorkspaceId &&
|
||||
approval.State == "Approved" &&
|
||||
approval.CompletedAt.HasValue,
|
||||
ct);
|
||||
|
||||
if (!hasApprovedDecision)
|
||||
{
|
||||
AddError(request => request.Status, "This workspace requires approval before content can be approved or scheduled.");
|
||||
await SendErrorsAsync(StatusCodes.Status409Conflict, ct);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.Status != "In approval" || normalizedStatus != "In approval")
|
||||
{
|
||||
item.Status = normalizedStatus;
|
||||
}
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
await notificationEventWriter.WriteAsync(
|
||||
@@ -92,7 +145,7 @@ public class UpdateContentItemStatusHandler(
|
||||
item.Id,
|
||||
item.WorkspaceId,
|
||||
item.ClientId,
|
||||
item.ProjectId,
|
||||
item.CampaignId,
|
||||
item.Title,
|
||||
item.PublicationMessage,
|
||||
item.PublicationTargets,
|
||||
|
||||
Reference in New Issue
Block a user