diff --git a/src/CrowdParlay.Social.Api/v1/Controllers/CommentsController.cs b/src/CrowdParlay.Social.Api/v1/Controllers/CommentsController.cs index 5e542e5..cb628ac 100644 --- a/src/CrowdParlay.Social.Api/v1/Controllers/CommentsController.cs +++ b/src/CrowdParlay.Social.Api/v1/Controllers/CommentsController.cs @@ -4,6 +4,7 @@ using CrowdParlay.Social.Application.DTOs; using CrowdParlay.Social.Application.Exceptions; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace CrowdParlay.Social.Api.v1.Controllers; @@ -22,12 +23,12 @@ public async Task GetCommentById([FromRoute] Guid commentId) => await _comments.GetByIdAsync(commentId); /// - /// Returns all comments created by author with the specified ID. + /// Returns all comments created in discussion or by author with the specified ID. /// [HttpGet] - public async Task> GetCommentsByAuthor - ([FromQuery] Guid authorId, [FromQuery] int page, [FromQuery] int size) => - await _comments.GetByAuthorAsync(authorId, page, size); + public async Task> SearchComments + ([FromQuery] Guid? discussionId, [FromQuery] Guid? authorId, [FromQuery, BindRequired] int page, [FromQuery, BindRequired] int size) => + await _comments.SearchAsync(discussionId, authorId, page, size); /// /// Creates a top-level comment in discussion. diff --git a/src/CrowdParlay.Social.Application/Abstractions/ICommentRepository.cs b/src/CrowdParlay.Social.Application/Abstractions/ICommentRepository.cs index ed3a4f5..5a8f76f 100644 --- a/src/CrowdParlay.Social.Application/Abstractions/ICommentRepository.cs +++ b/src/CrowdParlay.Social.Application/Abstractions/ICommentRepository.cs @@ -5,7 +5,7 @@ namespace CrowdParlay.Social.Application.Abstractions; public interface ICommentRepository { public Task GetByIdAsync(Guid id); - public Task> GetByAuthorAsync(Guid authorId, int page, int size); + public Task> SearchAsync(Guid? discussionId, Guid? authorId, int page, int size); public Task CreateAsync(Guid authorId, Guid discussionId, string content); public Task ReplyToCommentAsync(Guid authorId, Guid parentCommentId, string content); public Task DeleteAsync(Guid id); diff --git a/src/CrowdParlay.Social.Infrastructure.Persistence/Services/CommentRepository.cs b/src/CrowdParlay.Social.Infrastructure.Persistence/Services/CommentRepository.cs index d695729..78e10a9 100644 --- a/src/CrowdParlay.Social.Infrastructure.Persistence/Services/CommentRepository.cs +++ b/src/CrowdParlay.Social.Infrastructure.Persistence/Services/CommentRepository.cs @@ -52,13 +52,13 @@ AS c ?? throw new NotFoundException(); } - public async Task> GetByAuthorAsync(Guid authorId, int page, int size) => await _graphClient.Cypher - .WithParams(new { authorId }) - .Match("(c:Comment)-[:AUTHORED_BY]->(a:Author { Id: $authorId })") + public async Task> GetByDiscussionAsync(Guid discussionId, int page, int size) => await _graphClient.Cypher + .WithParams(new { discussionId }) + .Match("(a:Author)<-[:AUTHORED_BY]-(c:Comment)-[:REPLIES_TO]->(d:Discussion { Id: $discussionId })") .OptionalMatch("(ra:Author)<-[:AUTHORED_BY]-(r:Comment)-[:REPLIES_TO]->(c)") .With( """ - c, a, COUNT(r) AS rc, + c, a, d, COUNT(r) AS rc, CASE WHEN COUNT(r) > 0 THEN COLLECT(DISTINCT { Id: ra.Id, Username: ra.Username, @@ -88,6 +88,54 @@ AS c .Limit(size) .ResultsAsync; + public async Task> SearchAsync(Guid? discussionId, Guid? authorId, int page, int size) + { + var query = _graphClient.Cypher.WithParams(new { discussionId, authorId }); + + var matchSelector = (discussionId, authorId) switch + { + (not null, not null) => "(a:Author { Id: $authorId })<-[:AUTHORED_BY]-(c:Comment)-[:REPLIES_TO]->(d:Discussion { Id: $discussionId })", + (not null, null) => "(a:Author)<-[:AUTHORED_BY]-(c:Comment)-[:REPLIES_TO]->(d:Discussion { Id: $discussionId })", + (null, not null) => "(a:Author { Id: $authorId })<-[:AUTHORED_BY]-(c:Comment)", + (null, null) => "(a:Author)<-[:AUTHORED_BY]-(c:Comment)" + }; + + return await query + .Match(matchSelector) + .OptionalMatch("(ra:Author)<-[:AUTHORED_BY]-(r:Comment)-[:REPLIES_TO]->(c)") + .With( + """ + c, a, COUNT(r) AS rc, + CASE WHEN COUNT(r) > 0 THEN COLLECT(DISTINCT { + Id: ra.Id, + Username: ra.Username, + DisplayName: ra.DisplayName, + AvatarUrl: ra.AvatarUrl + })[0..3] ELSE [] END AS fras + """) + .With( + """ + { + Id: c.Id, + Content: c.Content, + Author: { + Id: a.Id, + Username: a.Username, + DisplayName: a.DisplayName, + AvatarUrl: a.AvatarUrl + }, + CreatedAt: c.CreatedAt, + ReplyCount: rc, + FirstRepliesAuthors: fras + } + AS c + """) + .Return("c") + .Skip(page * size) + .Limit(size) + .ResultsAsync; + } + public async Task CreateAsync(Guid authorId, Guid discussionId, string content) { var results = await _graphClient.Cypher