180 lines
6.2 KiB
C#
180 lines
6.2 KiB
C#
using FastEndpoints;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Socialize.Api.Data;
|
|
using Socialize.Api.Infrastructure.Observability;
|
|
using Socialize.Api.Infrastructure.Security;
|
|
using Socialize.Api.Modules.ContentItems.Contracts;
|
|
using Socialize.Api.Modules.Notifications.Contracts;
|
|
using Socialize.Api.Modules.ContentItems.Data;
|
|
using Socialize.Api.Modules.Workspaces.Data;
|
|
using System.Text.Json;
|
|
|
|
namespace Socialize.Api.Modules.ContentItems.Handlers;
|
|
|
|
internal record CreateContentItemRequest(
|
|
Guid WorkspaceId,
|
|
Guid ClientId,
|
|
Guid CampaignId,
|
|
string Title,
|
|
string PublicationMessage,
|
|
string PublicationTargets,
|
|
string? Hashtags,
|
|
DateTimeOffset? DueDate);
|
|
|
|
internal class CreateContentItemRequestValidator
|
|
: Validator<CreateContentItemRequest>
|
|
{
|
|
public CreateContentItemRequestValidator()
|
|
{
|
|
RuleFor(x => x.WorkspaceId).NotEmpty();
|
|
RuleFor(x => x.ClientId).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);
|
|
RuleFor(x => x.Hashtags).MaximumLength(1024);
|
|
}
|
|
}
|
|
|
|
internal class CreateContentItemHandler(
|
|
AppDbContext dbContext,
|
|
AccessScopeService accessScopeService,
|
|
IContentItemActivityWriter activityWriter,
|
|
INotificationEventWriter notificationEventWriter,
|
|
SocializeMetrics metrics)
|
|
: Endpoint<CreateContentItemRequest, ContentItemDto>
|
|
{
|
|
public override void Configure()
|
|
{
|
|
Post("/api/content-items");
|
|
Options(o => o.WithTags("Content Items"));
|
|
}
|
|
|
|
public override async Task HandleAsync(CreateContentItemRequest request, CancellationToken ct)
|
|
{
|
|
if (!await accessScopeService.CanContributeToCampaignAsync(User, request.WorkspaceId, request.ClientId, request.CampaignId, ct))
|
|
{
|
|
await SendForbiddenAsync(ct);
|
|
return;
|
|
}
|
|
|
|
bool workspaceExists = await dbContext.Workspaces
|
|
.AnyAsync(workspace => workspace.Id == request.WorkspaceId, ct);
|
|
|
|
if (!workspaceExists)
|
|
{
|
|
AddError(request => request.WorkspaceId, "The selected workspace does not exist.");
|
|
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
|
return;
|
|
}
|
|
|
|
bool clientExists = await dbContext.Clients
|
|
.AnyAsync(
|
|
client => client.Id == request.ClientId && client.WorkspaceId == request.WorkspaceId,
|
|
ct);
|
|
|
|
if (!clientExists)
|
|
{
|
|
AddError(request => request.ClientId, "The selected client does not belong to the active workspace.");
|
|
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
|
return;
|
|
}
|
|
|
|
bool campaignExists = await dbContext.Campaigns
|
|
.AnyAsync(
|
|
campaign => campaign.Id == request.CampaignId &&
|
|
campaign.WorkspaceId == request.WorkspaceId &&
|
|
campaign.ClientId == request.ClientId,
|
|
ct);
|
|
|
|
if (!campaignExists)
|
|
{
|
|
AddError(request => request.CampaignId, "The selected campaign does not belong to the selected client.");
|
|
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
|
return;
|
|
}
|
|
|
|
ContentItem item = new()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
WorkspaceId = request.WorkspaceId,
|
|
ClientId = request.ClientId,
|
|
CampaignId = request.CampaignId,
|
|
Title = request.Title.Trim(),
|
|
PublicationMessage = request.PublicationMessage.Trim(),
|
|
PublicationTargets = request.PublicationTargets.Trim(),
|
|
Hashtags = string.IsNullOrWhiteSpace(request.Hashtags) ? null : request.Hashtags.Trim(),
|
|
Status = "Draft",
|
|
DueDate = request.DueDate,
|
|
CurrentRevisionLabel = "v1",
|
|
CurrentRevisionNumber = 1,
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
|
};
|
|
|
|
dbContext.ContentItems.Add(item);
|
|
dbContext.ContentItemRevisions.Add(new ContentItemRevision
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ContentItemId = item.Id,
|
|
RevisionNumber = 1,
|
|
RevisionLabel = "v1",
|
|
Title = item.Title,
|
|
PublicationMessage = item.PublicationMessage,
|
|
PublicationTargets = item.PublicationTargets,
|
|
Hashtags = item.Hashtags,
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
|
});
|
|
await dbContext.SaveChangesAsync(ct);
|
|
metrics.RecordContentItemCreated(item.WorkspaceId);
|
|
|
|
await activityWriter.WriteAsync(
|
|
new ContentItemActivityWriteModel(
|
|
item.WorkspaceId,
|
|
item.Id,
|
|
"content-item.created",
|
|
"ContentItem",
|
|
item.Id,
|
|
$"Content item {item.Title} was created.",
|
|
User.GetUserId(),
|
|
User.GetEmail(),
|
|
JsonSerializer.Serialize(new
|
|
{
|
|
status = item.Status,
|
|
revisionLabel = item.CurrentRevisionLabel,
|
|
dueDate = item.DueDate,
|
|
publicationTargets = item.PublicationTargets,
|
|
hashtags = item.Hashtags,
|
|
})),
|
|
ct);
|
|
|
|
await notificationEventWriter.WriteAsync(
|
|
new NotificationEventWriteModel(
|
|
item.WorkspaceId,
|
|
item.Id,
|
|
"content-item.created",
|
|
"ContentItem",
|
|
item.Id,
|
|
$"Content item {item.Title} was created.",
|
|
null,
|
|
null,
|
|
$$"""{"status":"{{item.Status}}","revisionLabel":"{{item.CurrentRevisionLabel}}"}"""),
|
|
ct);
|
|
|
|
ContentItemDto dto = new(
|
|
item.Id,
|
|
item.WorkspaceId,
|
|
item.ClientId,
|
|
item.CampaignId,
|
|
item.Title,
|
|
item.PublicationMessage,
|
|
item.PublicationTargets,
|
|
item.Hashtags,
|
|
item.Status,
|
|
item.DueDate,
|
|
item.CurrentRevisionLabel,
|
|
item.CurrentRevisionNumber);
|
|
|
|
await SendAsync(dto, StatusCodes.Status201Created, ct);
|
|
}
|
|
}
|