using FastEndpoints; using Microsoft.EntityFrameworkCore; using Socialize.Api.Data; using Socialize.Api.Infrastructure.Security; using Socialize.Api.Modules.ContentItems.Data; using Socialize.Api.Modules.Notifications.Contracts; namespace Socialize.Api.Modules.ContentItems.Handlers; public record CreateContentItemRevisionRequest( string Title, string PublicationMessage, string PublicationTargets, string? Hashtags, string? ChangeSummary); public class CreateContentItemRevisionRequestValidator : Validator { 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, INotificationEventWriter notificationEventWriter) : Endpoint { 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("id"); ContentItem? item = await dbContext.ContentItems.SingleOrDefaultAsync(candidate => candidate.Id == id, ct); if (item is null) { await SendNotFoundAsync(ct); return; } if (!accessScopeService.CanContributeToCampaign(User, item.WorkspaceId, item.ClientId, item.CampaignId)) { await SendForbiddenAsync(ct); return; } int revisionNumber = item.CurrentRevisionNumber + 1; string revisionLabel = $"v{revisionNumber}"; item.Title = request.Title.Trim(); item.PublicationMessage = request.PublicationMessage.Trim(); item.PublicationTargets = request.PublicationTargets.Trim(); item.Hashtags = string.IsNullOrWhiteSpace(request.Hashtags) ? null : request.Hashtags.Trim(); 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); 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); } }