Skip to content

Commit

Permalink
Add Blog overview at Admin
Browse files Browse the repository at this point in the history
  • Loading branch information
oveldman committed Oct 18, 2023
1 parent d8130ad commit 0e47adf
Show file tree
Hide file tree
Showing 19 changed files with 158 additions and 15 deletions.
1 change: 1 addition & 0 deletions MadWorld/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="Radzen.Blazor" Version="4.16.0" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="TngTech.ArchUnitNET" Version="0.10.6" />
<PackageVersion Include="TngTech.ArchUnitNET.xUnit" Version="0.10.6" />
<PackageVersion Include="WireMock.Net" Version="1.5.35" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public GetBlogs(IGetBlogsUseCase useCase)
[OpenApiOperation(operationId: "GetBlogs", tags: new[] { "Blog" }, Summary = "List all blog posts")]
[OpenApiParameter("page", Type = typeof(int))]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(GetAccountResponse), Description = "The OK response")]
public Result<GetBlogsResponse> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "Blogs/{page}")] HttpRequestData request,
public async Task<Result<GetBlogsResponse>> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "Blogs/{page}")] HttpRequestData request,
FunctionContext executionContext,
string page)
{
Expand All @@ -31,6 +31,6 @@ public Result<GetBlogsResponse> Run([HttpTrigger(AuthorizationLevel.Anonymous, "
Page = page
};

return _useCase.GetBlogs(getBlogsRequest);
return await _useCase.GetBlogsAsync(getBlogsRequest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public GetBlogs(IGetBlogsUseCase useCase)
[OpenApiOperation(operationId: "GetBlogs", tags: new[] { "Blog" }, Summary = "List all blog posts")]
[OpenApiParameter("page", Type = typeof(int))]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(GetBlogsResponse), Description = "The OK response")]
public Result<GetBlogsResponse> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "Blogs/{page}")] HttpRequestData request,
public async Task<Result<GetBlogsResponse>> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "Blogs/{page}")] HttpRequestData request,
FunctionContext executionContext,
string page)
{
Expand All @@ -37,6 +37,6 @@ public Result<GetBlogsResponse> Run([HttpTrigger(AuthorizationLevel.Anonymous, "
Page = page
};

return _useCase.GetBlogs(getBlogsRequest);
return await _useCase.GetBlogsAsync(getBlogsRequest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public GetBlogsTests(AuthorizedApiDockerStartupFactory factory)
}

[Fact]
public void GetBlogs_Regularly_ShouldReturnExpectedResult()
public async Task GetBlogs_Regularly_ShouldReturnExpectedResult()
{
// Arrange
const string page = "0";
Expand All @@ -36,13 +36,14 @@ public void GetBlogs_Regularly_ShouldReturnExpectedResult()
var request = Substitute.For<HttpRequestData>(context);

// Act
var response = _function.Run(request, context, page);
var response = await _function.Run(request, context, page);

// Assert
var contract = response.Match(
b => b,
_ => default!);

contract.Count.ShouldBe(2);
contract.Blogs.Count.ShouldBe(2);
contract.Blogs.First().Id.ShouldBe("935eedd2-be0e-4308-aaa5-bd5bc7d5dceb");
contract.Blogs.First().Title.ShouldBe("How to create unittest");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public GetBlogsUseCase(IBlogRepository repository)
_repository = repository;
}

public Result<GetBlogsResponse> GetBlogs(GetBlogsRequest request)
public async Task<Result<GetBlogsResponse>> GetBlogsAsync(GetBlogsRequest request)
{
if (!int.TryParse(request.Page, out var pageNumber))
{
Expand All @@ -28,8 +28,9 @@ public Result<GetBlogsResponse> GetBlogs(GetBlogsRequest request)
return new Result<GetBlogsResponse>(new ValidationException($"{nameof(request.Page)} must be a higher than {MinimumPageNumber}"));
}

var blogCount = await _repository.CountBlogs();
var blogs = _repository.GetBlogs(pageNumber);
var blogContracts = blogs.Select(b => b.ToContract()).ToList();
return new GetBlogsResponse(blogContracts);
return new GetBlogsResponse(blogCount, blogContracts);
}
}
1 change: 1 addition & 0 deletions MadWorld/MadWorld.Backend.Domain/Blogs/IBlogRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace MadWorld.Backend.Domain.Blogs;

public interface IBlogRepository
{
Task<int> CountBlogs();
Result<Unit> DeleteBlog(Blog blog);
IReadOnlyList<Blog> GetBlogs(int page);
Option<Blog> GetBlog(GuidId id);
Expand Down
2 changes: 1 addition & 1 deletion MadWorld/MadWorld.Backend.Domain/Blogs/IGetBlogsUseCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace MadWorld.Backend.Domain.Blogs;

public interface IGetBlogsUseCase
{
Result<GetBlogsResponse> GetBlogs(GetBlogsRequest request);
Task<Result<GetBlogsResponse>> GetBlogsAsync(GetBlogsRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Graph" />
<PackageReference Include="System.Linq.Async" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MadWorld.Backend.Domain\MadWorld.Backend.Domain.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ public BlogRepository(TableServiceClient client)
_table = client.GetTableClient(TableName);
}

public async Task<int> CountBlogs()
{
var results = _table.QueryAsync<BlogEntity>(b
=> b.PartitionKey == BlogEntity.PartitionKeyName && !b.IsDeleted,
select: new[] { "PartitionKey" });
return await results.CountAsync();
}

public Result<Unit> DeleteBlog(Blog blog)
{
var entity = ToBlobEntity(blog);
Expand All @@ -31,8 +39,8 @@ public Result<Unit> DeleteBlog(Blog blog)
public IReadOnlyList<Blog> GetBlogs(int page)
{
var blogs = _table
.Query<BlogEntity>(c
=> c.PartitionKey == BlogEntity.PartitionKeyName && !c.IsDeleted)
.Query<BlogEntity>(b
=> b.PartitionKey == BlogEntity.PartitionKeyName && !b.IsDeleted)
.AsPages(pageSizeHint: TableStorageConfigurationsManager.DefaultPageSize)
.Skip(page)
.FirstOrDefault()?
Expand Down
18 changes: 18 additions & 0 deletions MadWorld/MadWorld.Frontend.Application/Blogs/GetBlogsUseCase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using MadWorld.Frontend.Domain.Blogs;
using MadWorld.Shared.Contracts.Anonymous.Blog;

namespace MadWorld.Frontend.Application.Blogs;

public class GetBlogsUseCase : IGetBlogsUseCase
{
private readonly IBlogService _service;
public GetBlogsUseCase(IBlogService service)
{
_service = service;
}

public async Task<GetBlogsResponse> GetBlogsAsync(int pageNumber)
{
return await _service.GetBlogs(pageNumber);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using MadWorld.Frontend.Application.Accounts;
using MadWorld.Frontend.Application.Blogs;
using MadWorld.Frontend.Application.CurriculaVitae;
using MadWorld.Frontend.Domain.Accounts;
using MadWorld.Frontend.Domain.Blogs;
using MadWorld.Frontend.Domain.CurriculaVitae;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -12,6 +14,7 @@ public static IServiceCollection AddApplication(this IServiceCollection services
{
services.AddScoped<IGetAccountUseCase, GetAccountUseCase>();
services.AddScoped<IGetAccountsUseCase, GetAccountsUseCase>();
services.AddScoped<IGetBlogsUseCase, GetBlogsUseCase>();
services.AddScoped<IGetCurriculumVitaeUseCase, GetCurriculumVitaeUseCase>();
services.AddScoped<IPatchAccountUseCase, PatchAccountUseCase>();
services.AddScoped<IPatchCurriculumVitaeUseCase, PatchCurriculumVitaeUseCase>();
Expand Down
8 changes: 8 additions & 0 deletions MadWorld/MadWorld.Frontend.Domain/Blogs/IBlogService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using MadWorld.Shared.Contracts.Anonymous.Blog;

namespace MadWorld.Frontend.Domain.Blogs;

public interface IBlogService
{
Task<GetBlogsResponse> GetBlogs(int pageNumber);
}
8 changes: 8 additions & 0 deletions MadWorld/MadWorld.Frontend.Domain/Blogs/IGetBlogsUseCase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using MadWorld.Shared.Contracts.Anonymous.Blog;

namespace MadWorld.Frontend.Domain.Blogs;

public interface IGetBlogsUseCase
{
Task<GetBlogsResponse> GetBlogsAsync(int pageNumber);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Net.Http.Json;
using MadWorld.Frontend.Domain.Api;
using MadWorld.Frontend.Domain.Blogs;
using MadWorld.Shared.Contracts.Anonymous.Blog;

namespace MadWorld.Frontend.Infrastructure.BlogService;

public class BlogService : IBlogService
{
private const string Endpoint = "Blogs";

private readonly HttpClient _client;

public BlogService(IHttpClientFactory clientFactory)
{
_client = clientFactory.CreateClient(ApiTypes.MadWorldApiAuthorized);
}
public async Task<GetBlogsResponse> GetBlogs(int pageNumber)
{
return await _client.GetFromJsonAsync<GetBlogsResponse>($"{Endpoint}/{pageNumber}") ?? new GetBlogsResponse(0, Array.Empty<BlogContract>());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using MadWorld.Frontend.Application.Status;
using MadWorld.Frontend.Application.Test;
using MadWorld.Frontend.Domain.Accounts;
using MadWorld.Frontend.Domain.Blogs;
using MadWorld.Frontend.Domain.CurriculaVitae;
using MadWorld.Frontend.Infrastructure.Accounts;
using MadWorld.Frontend.Infrastructure.CurriculumVitae;
Expand All @@ -15,6 +16,7 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
{
services.AddScoped<IAccountService, AccountService>();
services.AddScoped<IBlogService, BlogService.BlogService>();
services.AddScoped<ICurriculumVitaeService, CurriculumVitaeService>();
services.AddScoped<IPingService, PingService>();
services.AddScoped<IStatusService, StatusService>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public sealed partial class AccountDetails
[Parameter]
public string Id { get; set; } = string.Empty;

public bool IsReady { get; set; }
public bool IsReady { get; private set; }
private bool IsSaved { get; set; }
private bool HasError { get; set; }
private string ErrorMessage { get; set; } = string.Empty;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,23 @@
@using MadWorld.Frontend.UI.Shared.Components
@using MadWorld.Shared.Contracts.Anonymous.Blog

@page "/Blogs"
<h3>BlogOverview</h3>

<PageTitle>Blog Overview</PageTitle>

<h3>Blog Overview</h3>
@if (_isReady)
{
<RadzenDataGrid @ref="dataGrid" IsLoading=@_isLoading Count="_totalRecords" Data="@_blogs"
LoadData="@LoadData" AllowSorting="false" AllowFiltering="false"
AllowPaging="true" PageSize="20" PagerHorizontalAlign="HorizontalAlign.Center"
TItem="BlogContract" ShowPagingSummary="true" PagingSummaryFormat="@pagingSummaryFormat">
<Columns>
<RadzenDataGridColumn TItem="BlogContract" Property="Title" Title="Title" />
</Columns>
</RadzenDataGrid>
}
else
{
<LoadingSpinner />
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,52 @@
using JetBrains.Annotations;
using MadWorld.Frontend.Domain.Blogs;
using MadWorld.Shared.Contracts.Anonymous.Blog;
using Microsoft.AspNetCore.Components;
using Radzen;
using Radzen.Blazor;

namespace MadWorld.Frontend.UI.Admin.Pages.Blogs;

[UsedImplicitly]
public partial class BlogOverview
{

private string pagingSummaryFormat = "Displaying page {0} of {1} <b>(total {2} records)</b>";
private RadzenDataGrid<BlogContract> dataGrid;

private int PagePosition = 0;

private bool _isReady;
private bool _isLoading => !_isReady;

private int _totalRecords = 0;
private IReadOnlyCollection<BlogContract> _blogs = Array.Empty<BlogContract>();

[Inject]
private IGetBlogsUseCase GetBlogsUseCase { get; set; } = null!;

protected override async Task OnInitializedAsync()
{
await LoadBlogs();

_isReady = true;
await base.OnInitializedAsync();
}

private async Task LoadData(LoadDataArgs args)
{
var skip = args.Skip ?? 0;
var top = args.Top ?? 0;

PagePosition = skip / top;

await LoadBlogs();
}

private async Task LoadBlogs()
{
var result = await GetBlogsUseCase.GetBlogsAsync(PagePosition);

_blogs = result.Blogs;
_totalRecords = result.Count;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ namespace MadWorld.Shared.Contracts.Anonymous.Blog;

public class GetBlogsResponse
{
public int Count { get; private set; }
public IReadOnlyCollection<BlogContract> Blogs { get; private set; }

public GetBlogsResponse(IReadOnlyCollection<BlogContract> blogs)
public GetBlogsResponse(int count, IReadOnlyCollection<BlogContract> blogs)
{
Count = count;
Blogs = blogs;
}
}

0 comments on commit 0e47adf

Please sign in to comment.
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy