Skip to content

Commit

Permalink
merge(#23): add pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
undrcrxwn authored Feb 19, 2024
2 parents 200b4c4 + 2ac81c2 commit 6ded245
Show file tree
Hide file tree
Showing 23 changed files with 604 additions and 558 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using CrowdParlay.Social.Application.Abstractions;
using MassTransit;

namespace CrowdParlay.Social.Application.Consumers;
namespace CrowdParlay.Social.Api.Consumers;

public class UserEventConsumer : IConsumer<UserCreatedEvent>, IConsumer<UserUpdatedEvent>, IConsumer<UserDeletedEvent>
{
Expand Down
2 changes: 1 addition & 1 deletion src/CrowdParlay.Social.Api/Extensions/ConfigureServices.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using CrowdParlay.Communication;
using CrowdParlay.Social.Api.Consumers;
using CrowdParlay.Social.Api.Middlewares;
using CrowdParlay.Social.Application.Consumers;
using MassTransit;

namespace CrowdParlay.Social.Api.Extensions;
Expand Down
16 changes: 8 additions & 8 deletions src/CrowdParlay.Social.Api/v1/Controllers/CommentsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ public async Task<CommentDto> GetCommentById([FromRoute] Guid commentId) =>
[ProducesResponseType(typeof(IEnumerable<CommentDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(Problem), (int)HttpStatusCode.InternalServerError)]
[ProducesResponseType(typeof(ValidationProblem), (int)HttpStatusCode.BadRequest)]
public async Task<IEnumerable<CommentDto>> SearchComments(
public async Task<Page<CommentDto>> SearchComments(
[FromQuery] Guid? discussionId,
[FromQuery] Guid? authorId,
[FromQuery, BindRequired] int page,
[FromQuery, BindRequired] int size) =>
await _comments.SearchAsync(discussionId, authorId, page, size);
[FromQuery, BindRequired] int offset,
[FromQuery, BindRequired] int count) =>
await _comments.SearchAsync(discussionId, authorId, offset, count);

/// <summary>
/// Creates a top-level comment in discussion.
Expand Down Expand Up @@ -66,11 +66,11 @@ public async Task<ActionResult<CommentDto>> Create([FromBody] CommentRequest req
[ProducesResponseType(typeof(Problem), (int)HttpStatusCode.InternalServerError)]
[ProducesResponseType(typeof(ValidationProblem), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(Problem), (int)HttpStatusCode.NotFound)]
public async Task<IEnumerable<CommentDto>> GetRepliesToComment(
public async Task<Page<CommentDto>> GetRepliesToComment(
[FromRoute] Guid parentCommentId,
[FromQuery, BindRequired] int page,
[FromQuery, BindRequired] int size) =>
await _comments.GetRepliesToCommentAsync(parentCommentId, page, size);
[FromQuery, BindRequired] int offset,
[FromQuery, BindRequired] int count) =>
await _comments.GetRepliesToCommentAsync(parentCommentId, offset, count);

/// <summary>
/// Creates a reply to the comment with the specified ID.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ namespace CrowdParlay.Social.Application.Abstractions;
public interface ICommentRepository
{
public Task<CommentDto> GetByIdAsync(Guid id);
public Task<IEnumerable<CommentDto>> SearchAsync(Guid? discussionId, Guid? authorId, int page, int size);
public Task<Page<CommentDto>> SearchAsync(Guid? discussionId, Guid? authorId, int offset, int count);
public Task<CommentDto> CreateAsync(Guid authorId, Guid discussionId, string content);
public Task<IEnumerable<CommentDto>> GetRepliesToCommentAsync(Guid parentCommentId, int page, int size);
public Task<Page<CommentDto>> GetRepliesToCommentAsync(Guid parentCommentId, int offset, int count);
public Task<CommentDto> ReplyToCommentAsync(Guid authorId, Guid parentCommentId, string content);
public Task DeleteAsync(Guid id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageReference Include="Neo4jClient" Version="5.1.11" />
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Settings.AppSettings" Version="2.2.2" />
Expand Down
2 changes: 1 addition & 1 deletion src/CrowdParlay.Social.Application/DTOs/CommentDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public class CommentDto
public Guid Id { get; set; }
public string Content { get; set; }

Check warning on line 6 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Build & test

Non-nullable property 'Content' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 6 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Build & test

Non-nullable property 'Content' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 6 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Produce openapi.yaml

Non-nullable property 'Content' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 6 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Produce openapi.yaml

Non-nullable property 'Content' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 6 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Publish API / Produce openapi.yaml

Non-nullable property 'Content' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 6 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Publish API / Produce openapi.yaml

Non-nullable property 'Content' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public AuthorDto Author { get; set; }

Check warning on line 7 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Build & test

Non-nullable property 'Author' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 7 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Build & test

Non-nullable property 'Author' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 7 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Produce openapi.yaml

Non-nullable property 'Author' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 7 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Produce openapi.yaml

Non-nullable property 'Author' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 7 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Publish API / Produce openapi.yaml

Non-nullable property 'Author' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 7 in src/CrowdParlay.Social.Application/DTOs/CommentDto.cs

View workflow job for this annotation

GitHub Actions / Publish API / Produce openapi.yaml

Non-nullable property 'Author' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public DateTime CreatedAt { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public int ReplyCount { get; set; }
public IEnumerable<AuthorDto> FirstRepliesAuthors { get; set; } = Enumerable.Empty<AuthorDto>();
}
13 changes: 13 additions & 0 deletions src/CrowdParlay.Social.Application/DTOs/Page.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace CrowdParlay.Social.Application.DTOs;

public class Page<T>
{
public required int TotalCount { get; set; }
public required IEnumerable<T> Items { get; set; }

public static Page<T> Empty => new()
{
TotalCount = 0,
Items = Enumerable.Empty<T>()
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using CrowdParlay.Social.Infrastructure.Persistence.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Neo4jClient;
using Neo4j.Driver;

namespace CrowdParlay.Social.Infrastructure.Persistence;

Expand Down Expand Up @@ -30,7 +30,7 @@ private static IServiceCollection AddNeo4j(this IServiceCollection services, ICo
configuration["NEO4J_PASSWORD"] ??
throw new InvalidOperationException("NEO4J_PASSWORD is not set!");

var client = new BoltGraphClient(uri, username, password);
return services.AddSingleton<IGraphClient>(client);
var driver = GraphDatabase.Driver(uri, AuthTokens.Basic(username, password));
return services.AddSingleton(driver);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
<PackageReference Include="Neo4jClient" Version="5.1.11" />
<PackageReference Include="Neo4j.Driver" Version="5.17.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,89 +1,127 @@
using CrowdParlay.Social.Application.Abstractions;
using CrowdParlay.Social.Application.DTOs;
using CrowdParlay.Social.Application.Exceptions;
using Neo4jClient;
using Mapster;
using Neo4j.Driver;

namespace CrowdParlay.Social.Infrastructure.Persistence.Services;

public class AuthorRepository : IAuthorRepository
{
private readonly IGraphClient _graphClient;
private readonly IDriver _driver;

public AuthorRepository(IGraphClient graphClient) => _graphClient = graphClient;
public AuthorRepository(IDriver driver) => _driver = driver;

public async Task<AuthorDto> GetByIdAsync(Guid id)
{
var results = await _graphClient.Cypher
.WithParams(new { id })
.Match("(a:Author { Id: $id })")
.Return<AuthorDto>("a")
.ResultsAsync;
await using var session = _driver.AsyncSession();
return await session.ExecuteReadAsync(async runner =>
{
var data = await runner.RunAsync(
"""
MATCH (author:Author { Id: $id })
RETURN {
Id: author.Id,
Username: author.Username,
DisplayName: author.DisplayName,
AvatarUrl: author.AvatarUrl
}
""",
new { id = id.ToString() });

return
results.SingleOrDefault()
?? throw new NotFoundException();
var record = await data.SingleAsync();
return record[0].Adapt<AuthorDto>();
});
}

public async Task<AuthorDto> CreateAsync(Guid id, string username, string displayName, string? avatarUrl)
{
var results = await _graphClient.Cypher
.WithParams(new
{
id,
username,
displayName,
avatarUrl
})
.Create(
await using var session = _driver.AsyncSession();
return await session.ExecuteWriteAsync(async runner =>
{
var data = await runner.RunAsync(
"""
(a:Author {
CREATE (author:Author {
Id: $id,
Username: $username,
DisplayName: $displayName,
AvatarUrl: $avatarUrl
})
""")
.Return<AuthorDto>("a")
.ResultsAsync;
RETURN {
Id: author.Id,
Username: author.Username,
DisplayName: author.DisplayName,
AvatarUrl: author.AvatarUrl
}
""",
new
{
id = id.ToString(),
username,
displayName,
avatarUrl
});

return results.Single();
var record = await data.SingleAsync();
return record[0].Adapt<AuthorDto>();
});
}

public async Task<AuthorDto> UpdateAsync(Guid id, string username, string displayName, string? avatarUrl)
{
var results = await _graphClient.Cypher
.WithParams(new
{
id,
username,
displayName,
avatarUrl
})
.Match("(a:Author { Id: $id })")
.Set(
await using var session = _driver.AsyncSession();
return await session.ExecuteWriteAsync(async runner =>
{
var data = await runner.RunAsync(
"""
a.Username = $username,
a.DisplayName = $displayName,
a.AvatarUrl = $avatarUrl
""")
.Return<AuthorDto>("a")
.ResultsAsync;
CREATE (author:Author {
Id: $id,
Username: $username,
DisplayName: $displayName,
AvatarUrl: $avatarUrl
})
MATCH (author:Author { Id: $id })
SET author.Username = $username,
author.DisplayName = $displayName,
author.AvatarUrl = $avatarUrl
RETURN {
Id: author.Id,
Username: author.Username,
DisplayName: author.DisplayName,
AvatarUrl: author.AvatarUrl
}
""",
new
{
id = id.ToString(),
username,
displayName,
avatarUrl
});

return
results.SingleOrDefault()
?? throw new NotFoundException();
var record = await data.SingleAsync();
return record[0].Adapt<AuthorDto>();
});
}

public async Task DeleteAsync(Guid id)
{
var results = await _graphClient.Cypher
.WithParams(new { id })
.OptionalMatch("(a:Author { Id: $id })")
.Delete("a")
.Return<bool>("COUNT(a) = 0")
.ResultsAsync;
await using var session = _driver.AsyncSession();
var notFount = await session.ExecuteWriteAsync(async runner =>
{
var data = await runner.RunAsync(
"""
OPTIONAL MATCH (author:Author { Id: $id })
DETACH DELETE author
RETURN COUNT(author) = 0
""",
new { id = id.ToString() });

var record = await data.SingleAsync();
return record[0].As<bool>();
});

if (results.Single())
if (notFount)
throw new NotFoundException();
}
}
Loading

0 comments on commit 6ded245

Please sign in to comment.