using Socialize.Infrastructure.Security; using Socialize.Modules.Notifications.Contracts; namespace Socialize.Modules.Comments.Handlers; public record CreateCommentRequest( Guid WorkspaceId, Guid ContentItemId, Guid? ParentCommentId, string Body); public class CreateCommentRequestValidator : Validator { public CreateCommentRequestValidator() { RuleFor(x => x.WorkspaceId).NotEmpty(); RuleFor(x => x.ContentItemId).NotEmpty(); RuleFor(x => x.Body).NotEmpty().MaximumLength(4000); } } public class CreateCommentHandler( AppDbContext dbContext, AccessScopeService accessScopeService, INotificationEventWriter notificationEventWriter) : Endpoint { public override void Configure() { Post("/api/comments"); Options(o => o.WithTags("Comments")); } public override async Task HandleAsync(CreateCommentRequest request, CancellationToken ct) { ContentItem? contentItem = await dbContext.ContentItems .SingleOrDefaultAsync( candidate => candidate.Id == request.ContentItemId && candidate.WorkspaceId == request.WorkspaceId, ct); if (contentItem is null) { AddError(request => request.ContentItemId, "The selected content item does not exist in the active workspace."); await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); return; } if (!accessScopeService.CanReviewContent(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.ProjectId)) { await SendForbiddenAsync(ct); return; } if (request.ParentCommentId.HasValue) { bool parentExists = await dbContext.Comments .AnyAsync( comment => comment.Id == request.ParentCommentId.Value && comment.ContentItemId == request.ContentItemId, ct); if (!parentExists) { AddError(request => request.ParentCommentId, "The selected parent comment does not exist."); await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); return; } } Comment comment = new() { Id = Guid.NewGuid(), WorkspaceId = request.WorkspaceId, ContentItemId = request.ContentItemId, ParentCommentId = request.ParentCommentId, AuthorUserId = User.GetUserId(), AuthorDisplayName = User.GetAlias() ?? User.GetName(), AuthorEmail = User.GetEmail(), Body = request.Body.Trim(), CreatedAt = DateTimeOffset.UtcNow, }; dbContext.Comments.Add(comment); await dbContext.SaveChangesAsync(ct); string? authorPortraitUrl = await dbContext.Users .Where(candidate => candidate.Id == comment.AuthorUserId) .Select(candidate => candidate.PortraitUrl) .SingleOrDefaultAsync(ct); await notificationEventWriter.WriteAsync( new NotificationEventWriteModel( comment.WorkspaceId, comment.ContentItemId, "comment.created", "Comment", comment.Id, $"{comment.AuthorDisplayName} commented on {contentItem.Title}.", null, null, $$"""{"parentCommentId":"{{comment.ParentCommentId}}"}"""), ct); CommentDto dto = new( comment.Id, comment.WorkspaceId, comment.ContentItemId, comment.ParentCommentId, comment.AuthorUserId, comment.AuthorDisplayName, comment.AuthorEmail, authorPortraitUrl, comment.Body, comment.IsResolved, comment.CreatedAt, comment.ResolvedAt); await SendAsync(dto, StatusCodes.Status201Created, ct); } }