Files
social-media/backend/Modules/ContentItems/Handlers/CreateContentItem.cs
Jonathan Bourdon df3e602015
Some checks failed
Backend CI/CD / build_and_deploy (push) Has been cancelled
Frontend CI/CD / build_and_deploy (push) Has been cancelled
feat: pivot to social media workflow app
2026-04-24 12:58:35 -04:00

149 lines
5.0 KiB
C#

using Socialize.Infrastructure.Security;
using Socialize.Modules.Notifications.Contracts;
namespace Socialize.Modules.ContentItems.Handlers;
public record CreateContentItemRequest(
Guid WorkspaceId,
Guid ClientId,
Guid ProjectId,
string Title,
string PublicationMessage,
string PublicationTargets,
string? Hashtags,
DateTimeOffset? DueDate);
public class CreateContentItemRequestValidator
: Validator<CreateContentItemRequest>
{
public CreateContentItemRequestValidator()
{
RuleFor(x => x.WorkspaceId).NotEmpty();
RuleFor(x => x.ClientId).NotEmpty();
RuleFor(x => x.ProjectId).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);
}
}
public class CreateContentItemHandler(
AppDbContext dbContext,
AccessScopeService accessScopeService,
INotificationEventWriter notificationEventWriter)
: 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 (!accessScopeService.CanContributeToProject(User, request.WorkspaceId, request.ClientId, request.ProjectId))
{
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 projectExists = await dbContext.Projects
.AnyAsync(
project => project.Id == request.ProjectId &&
project.WorkspaceId == request.WorkspaceId &&
project.ClientId == request.ClientId,
ct);
if (!projectExists)
{
AddError(request => request.ProjectId, "The selected project 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,
ProjectId = request.ProjectId,
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);
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.ProjectId,
item.Title,
item.PublicationMessage,
item.PublicationTargets,
item.Hashtags,
item.Status,
item.DueDate,
item.CurrentRevisionLabel,
item.CurrentRevisionNumber);
await SendAsync(dto, StatusCodes.Status201Created, ct);
}
}