Files
social-media/backend/src/Socialize.Api/Modules/ContentItems/Handlers/CreateContentItemRevision.cs
Jonathan Bourdon b66c10b681
Some checks failed
Backend CI/CD / build_and_deploy (push) Has been cancelled
Frontend CI/CD / build_and_deploy (push) Has been cancelled
Add calendar integrations and collaboration updates
2026-05-05 15:25:53 -04:00

171 lines
6.3 KiB
C#

using FastEndpoints;
using Microsoft.EntityFrameworkCore;
using Socialize.Api.Data;
using Socialize.Api.Infrastructure.Security;
using Socialize.Api.Modules.ContentItems.Contracts;
using Socialize.Api.Modules.ContentItems.Data;
using Socialize.Api.Modules.Notifications.Contracts;
using System.Text.Json;
namespace Socialize.Api.Modules.ContentItems.Handlers;
public record CreateContentItemRevisionRequest(
string Title,
string PublicationMessage,
string PublicationTargets,
string? Hashtags,
string? ChangeSummary,
DateTimeOffset? DueDate);
public class CreateContentItemRevisionRequestValidator
: Validator<CreateContentItemRevisionRequest>
{
public CreateContentItemRevisionRequestValidator()
{
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);
RuleFor(x => x.ChangeSummary).MaximumLength(1024);
}
}
public class CreateContentItemRevisionHandler(
AppDbContext dbContext,
AccessScopeService accessScopeService,
IContentItemActivityWriter activityWriter,
INotificationEventWriter notificationEventWriter)
: Endpoint<CreateContentItemRevisionRequest, ContentItemRevisionDto>
{
public override void Configure()
{
Post("/api/content-items/{id}/revisions");
Options(o => o.WithTags("Content Items"));
}
public override async Task HandleAsync(CreateContentItemRevisionRequest request, CancellationToken ct)
{
Guid id = Route<Guid>("id");
ContentItem? item = await dbContext.ContentItems.SingleOrDefaultAsync(candidate => candidate.Id == id, ct);
if (item is null)
{
await SendNotFoundAsync(ct);
return;
}
if (!await accessScopeService.CanContributeToCampaignAsync(User, item.WorkspaceId, item.ClientId, item.CampaignId, ct))
{
await SendForbiddenAsync(ct);
return;
}
int revisionNumber = item.CurrentRevisionNumber + 1;
string revisionLabel = $"v{revisionNumber}";
string previousTitle = item.Title;
string previousPublicationMessage = item.PublicationMessage;
string previousPublicationTargets = item.PublicationTargets;
string? previousHashtags = item.Hashtags;
DateTimeOffset? previousDueDate = item.DueDate;
string newTitle = request.Title.Trim();
string newPublicationMessage = request.PublicationMessage.Trim();
string newPublicationTargets = request.PublicationTargets.Trim();
string? newHashtags = string.IsNullOrWhiteSpace(request.Hashtags) ? null : request.Hashtags.Trim();
item.Title = newTitle;
item.PublicationMessage = newPublicationMessage;
item.PublicationTargets = newPublicationTargets;
item.Hashtags = newHashtags;
item.DueDate = request.DueDate;
item.CurrentRevisionNumber = revisionNumber;
item.CurrentRevisionLabel = revisionLabel;
ContentItemRevision revision = new()
{
Id = Guid.NewGuid(),
ContentItemId = item.Id,
RevisionNumber = revisionNumber,
RevisionLabel = revisionLabel,
Title = item.Title,
PublicationMessage = item.PublicationMessage,
PublicationTargets = item.PublicationTargets,
Hashtags = item.Hashtags,
ChangeSummary = string.IsNullOrWhiteSpace(request.ChangeSummary) ? null : request.ChangeSummary.Trim(),
CreatedByUserId = User.GetUserId(),
CreatedAt = DateTimeOffset.UtcNow,
};
dbContext.ContentItemRevisions.Add(revision);
await dbContext.SaveChangesAsync(ct);
List<object> changedFields = [];
AddChangedField(changedFields, "title", previousTitle, item.Title);
AddChangedField(changedFields, "publicationMessage", previousPublicationMessage, item.PublicationMessage);
AddChangedField(changedFields, "publicationTargets", previousPublicationTargets, item.PublicationTargets);
AddChangedField(changedFields, "hashtags", previousHashtags, item.Hashtags);
AddChangedField(changedFields, "dueDate", previousDueDate, item.DueDate);
await activityWriter.WriteAsync(
new ContentItemActivityWriteModel(
item.WorkspaceId,
item.Id,
"content-item.revision.created",
"ContentItemRevision",
revision.Id,
$"Revision {revisionLabel} was created for {item.Title}.",
User.GetUserId(),
User.GetEmail(),
JsonSerializer.Serialize(new
{
revisionLabel,
revisionNumber,
changeSummary = revision.ChangeSummary,
changedFields,
})),
ct);
await notificationEventWriter.WriteAsync(
new NotificationEventWriteModel(
item.WorkspaceId,
item.Id,
"content-item.revision.created",
"ContentItemRevision",
revision.Id,
$"Revision {revisionLabel} was created for {item.Title}.",
User.GetUserId(),
User.GetEmail(),
$$"""{"revisionLabel":"{{revisionLabel}}","status":"{{item.Status}}"}"""),
ct);
ContentItemRevisionDto dto = new(
revision.Id,
revision.ContentItemId,
revision.RevisionNumber,
revision.RevisionLabel,
revision.Title,
revision.PublicationMessage,
revision.PublicationTargets,
revision.Hashtags,
revision.ChangeSummary,
revision.CreatedByUserId,
revision.CreatedAt);
await SendAsync(dto, StatusCodes.Status201Created, ct);
}
private static void AddChangedField<T>(List<object> changedFields, string field, T oldValue, T newValue)
{
if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
{
return;
}
changedFields.Add(new
{
field,
oldValue,
newValue,
});
}
}