diff --git a/src/Application/Posts/Commands/CommentPost.cs b/src/Application/Posts/Commands/CommentPost.cs new file mode 100644 index 0000000000000000000000000000000000000000..6c6d64c8edccfc75c04d176e33117ccf8f28595b --- /dev/null +++ b/src/Application/Posts/Commands/CommentPost.cs @@ -0,0 +1,45 @@ +using ErrorOr; +using MediatR; +using SocialNetwork.Social.Application.Common.Interfaces; +using SocialNetwork.Social.Domain.Entities.Feed; + +namespace SocialNetwork.Social.Application.Posts.Commands; + +public record CommentPostCommand(Guid PostId, Guid CommenterId, string Content) : IRequest<ErrorOr<Comment>>; + +public class CommentPostCommandHandler(IApplicationDbContext dbContext) : IRequestHandler<CommentPostCommand, ErrorOr<Comment>> +{ + public async Task<ErrorOr<Comment>> Handle(CommentPostCommand request, CancellationToken cancellationToken) + { + var postId = request.PostId; + var post = await dbContext.Posts.FindAsync([postId], cancellationToken: cancellationToken); + if (post is null) return PostErrors.PostNotFound(postId); + + var comment = new Comment {AuthorId = request.CommenterId, Content = request.Content, PostId = request.PostId,}; + + await dbContext.Comments.AddAsync(comment, cancellationToken); + post.Comments.Add(comment); + await dbContext.SaveChangesAsync(cancellationToken); + + dbContext.Posts.Update(post); + await dbContext.SaveChangesAsync(cancellationToken); + + return comment; + } +} + +public class CommentPostCommandValidator : AbstractValidator<CommentPostCommand> +{ + public CommentPostCommandValidator() + { + RuleFor(p => p.PostId) + .NotEmpty(); + + RuleFor(p => p.CommenterId) + .NotEmpty(); + + RuleFor(p => p.Content) + .NotEmpty() + .Length(3, 512); + } +} diff --git a/src/Application/Posts/Commands/CreatePost.cs b/src/Application/Posts/Commands/CreatePost.cs new file mode 100644 index 0000000000000000000000000000000000000000..601e53a3aa0ac01790ed3900700cce12255dff8f --- /dev/null +++ b/src/Application/Posts/Commands/CreatePost.cs @@ -0,0 +1,40 @@ +using ErrorOr; +using MediatR; +using SocialNetwork.Social.Application.Common.Interfaces; +using SocialNetwork.Social.Domain.Entities.Feed; + +namespace SocialNetwork.Social.Application.Posts.Commands; + +public record PublishPostCommand(Guid AuthorId, string Title, string Content) : IRequest<ErrorOr<Post>>; + +public class PublishPostCommandHandler(IApplicationDbContext dbContext) : IRequestHandler<PublishPostCommand, ErrorOr<Post>> +{ + public async Task<ErrorOr<Post>> Handle(PublishPostCommand request, CancellationToken cancellationToken) + { + var post = new Post {AuthorId = request.AuthorId, Title = request.Title, Content = request.Content}; + + await dbContext.Posts.AddAsync(post, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + + // TODO fire event PostPublished + + return post; + } +} + +public class PublishPostCommandValidator : AbstractValidator<PublishPostCommand> +{ + public PublishPostCommandValidator() + { + RuleFor(p => p.AuthorId) + .NotEmpty(); + + RuleFor(p => p.Title) + .NotEmpty() + .Length(3, 64); + + RuleFor(p => p.Content) + .NotEmpty() + .Length(3, 512); + } +} diff --git a/src/Application/Posts/Commands/GetPostById.cs b/src/Application/Posts/Commands/GetPostById.cs index 31fbf0ae063be4202a966103da8f30cbbe1dc972..0428a860e55a31c9cd10df56b9cd14d7c7290051 100644 --- a/src/Application/Posts/Commands/GetPostById.cs +++ b/src/Application/Posts/Commands/GetPostById.cs @@ -16,7 +16,7 @@ public class GetPostByIdCommandHandler(IApplicationDbContext dbContext) : IReque .Posts .FindAsync(new object?[] {postIdToFind}, cancellationToken: cancellationToken); - if (post is null) return Error.NotFound("PostNotFound", $"Post with id: {postIdToFind} not found"); + if (post is null) return PostErrors.PostNotFound(postIdToFind); return post; } diff --git a/src/Application/Posts/Commands/LikePost.cs b/src/Application/Posts/Commands/LikePost.cs new file mode 100644 index 0000000000000000000000000000000000000000..d633d8fb73be3c7c6f8ed3b2cd10d3bf9b0ac174 --- /dev/null +++ b/src/Application/Posts/Commands/LikePost.cs @@ -0,0 +1,24 @@ +using ErrorOr; +using MediatR; +using SocialNetwork.Social.Application.Common.Interfaces; + +namespace SocialNetwork.Social.Application.Posts.Commands; + +public record LikePostCommand(Guid PostId, Guid LikerId) : IRequest<ErrorOr<Success>>; + +public class LikePostCommandHandler(IApplicationDbContext dbContext) : IRequestHandler<LikePostCommand, ErrorOr<Success>> +{ + public async Task<ErrorOr<Success>> Handle(LikePostCommand request, CancellationToken cancellationToken) + { + var requestPostId = request.PostId; + var post = await dbContext.Posts.FindAsync(requestPostId); + if (post is null) return PostErrors.PostNotFound(requestPostId); + + post.LikesCount++; // I would create a whole table for likers and postId to add who liked the post + + // TODO fire post liked event + + await dbContext.SaveChangesAsync(cancellationToken); + return Result.Success; + } +} diff --git a/src/Application/Posts/PostErrors.cs b/src/Application/Posts/PostErrors.cs new file mode 100644 index 0000000000000000000000000000000000000000..e0a30d978d9fd84a9a92431eccbb7ec83614345e --- /dev/null +++ b/src/Application/Posts/PostErrors.cs @@ -0,0 +1,9 @@ +using ErrorOr; + +namespace SocialNetwork.Social.Application.Posts; + +public static class PostErrors +{ + public static Error PostNotFound(Guid postId) => + Error.NotFound("PostNotFound", $"Post with id: {postId} not found"); +} diff --git a/src/WebApi/Endpoints/PostEndpoints.cs b/src/WebApi/Endpoints/PostEndpoints.cs index a8d88771050fe13cd2c321f8974ff2240421e5cc..3a7676e1f65550328e10bd745bddf273be7dab32 100644 --- a/src/WebApi/Endpoints/PostEndpoints.cs +++ b/src/WebApi/Endpoints/PostEndpoints.cs @@ -1,9 +1,6 @@ using ErrorOr; using MediatR; -using Microsoft.EntityFrameworkCore; -using SocialNetwork.Social.Application.Common.Interfaces; using SocialNetwork.Social.Application.Posts.Commands; -using SocialNetwork.Social.Domain.Entities.Feed; namespace WebApi.Endpoints; @@ -27,55 +24,56 @@ public class PostEndpoints private async Task<IResult> GetPostById(Guid postId, ISender sender) { - var post = await sender.Send(new GetPostByIdCommand(postId)); - - return post.MatchFirst( + var errorOrPost = await sender.Send(new GetPostByIdCommand(postId)); + return errorOrPost.MatchFirst( Results.Ok, error => error.Type switch { - ErrorType.NotFound => Results.NotFound(error.Code), - _ => Results.BadRequest(error.Code) + ErrorType.NotFound => Results.NotFound(error.Description), + _ => Results.BadRequest(error.Description) } ); } - private async Task<IResult> CommentPost(Guid postId, Comment comment, IApplicationDbContext dbContext) + private async Task<IResult> CreatePost(PublishPostCommand command, ISender sender) { - var post = await dbContext.Posts.FindAsync(postId); - if (post is null) return Results.NotFound("Post not found"); - if (post.Comments.Any(c => c.Id == comment.Id)) return Results.Conflict("Comment already exists"); - - comment.PostId = postId; - await dbContext.Comments.AddAsync(comment); - post.Comments.Add(comment); - await dbContext.SaveChangesAsync(); - - dbContext.Posts.Update(post); - await dbContext.SaveChangesAsync(); - - return Results.Created($"api/posts/{postId}", comment); + var errorOrCreatedPost = await sender.Send(command); + return errorOrCreatedPost.MatchFirst( + createdPost => Results.Created($"/api/posts/{createdPost.Id}", createdPost), + error => error.Type switch + { + ErrorType.Validation => Results.BadRequest(error.Description), + ErrorType.Failure => Results.Problem(error.Description), + _ => Results.BadRequest(error.Description) + } + ); } - private async Task<IResult> CreatePost(Post post, IApplicationDbContext dbContext) + private async Task<IResult> CommentPost(Guid postId, CommentPostCommand command, ISender sender) { - var doesPostExist = await dbContext.Posts.AnyAsync(p => p.Id == post.Id); - if (doesPostExist) return Results.Conflict(); - - var entry = dbContext.Posts.Add(post); - await dbContext.SaveChangesAsync(); - - var persistedPost = entry.Entity; - return Results.Created($"/api/posts/{persistedPost.Id}", persistedPost); + var errorOrComment = await sender.Send(command with {PostId = postId}); + return errorOrComment.MatchFirst( + comment => Results.Created($"api/posts/{postId}", comment), + error => error.Type switch + { + ErrorType.Validation => Results.BadRequest(error.Description), + ErrorType.NotFound => Results.NotFound(error.Description), + _ => Results.BadRequest(error.Description) + } + ); } - private async Task<IResult> LikePost(Guid postId, Guid likerId, IApplicationDbContext dbContext) + private async Task<IResult> LikePost(Guid postId, LikePostCommand command, ISender sender) { - var post = await dbContext.Posts.FindAsync(postId); - if (post is null) return Results.NotFound(); - - post.LikesCount++; - // TODO add who liked the post - await dbContext.SaveChangesAsync(); - return Results.Ok(); + var errorOrSuccess = await sender.Send(command with {PostId = postId}); + return errorOrSuccess.MatchFirst( + _ => Results.Ok(), + error => error.Type switch + { + ErrorType.Validation => Results.BadRequest(error.Description), + ErrorType.NotFound => Results.NotFound(error.Description), + _ => Results.BadRequest(error.Description) + } + ); } }