This repository has been archived by the owner on Jul 12, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
731b5d8
commit d667ea5
Showing
9 changed files
with
138 additions
and
1 deletion.
There are no files selected for viewing
6 changes: 6 additions & 0 deletions
6
src/RookieShop.ApiService/Endpoints/Reports/OrdersGrownByDay.Request.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace RookieShop.ApiService.Endpoints.Reports; | ||
|
||
public sealed class OrdersGrownByDayRequest | ||
{ | ||
public DateTime? CurrentDate { get; set; } | ||
} |
32 changes: 32 additions & 0 deletions
32
src/RookieShop.ApiService/Endpoints/Reports/OrdersGrownByDay.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using MediatR; | ||
using Microsoft.AspNetCore.Http.HttpResults; | ||
using RookieShop.Application.Reports.DTOs; | ||
using RookieShop.Application.Reports.Queries.OrdersGrownByDay; | ||
using RookieShop.Infrastructure.Endpoints.Abstractions; | ||
using RookieShop.Infrastructure.RateLimiter; | ||
|
||
namespace RookieShop.ApiService.Endpoints.Reports; | ||
|
||
public sealed class OrdersGrownByDay(ISender sender) : IEndpoint<Ok<OrdersGrownByDayDto>, OrdersGrownByDayRequest> | ||
{ | ||
public void MapEndpoint(IEndpointRouteBuilder app) => | ||
app.MapGet("/reports/orders-grown-by-day", async (DateTime? currentDate) => await HandleAsync(new() | ||
{ | ||
CurrentDate = currentDate ?? DateTime.UtcNow | ||
})) | ||
.Produces<Ok<OrdersGrownByDayDto>>() | ||
.WithTags(nameof(Reports)) | ||
.WithName("Orders Grown By Day") | ||
.MapToApiVersion(new(1, 0)) | ||
.RequirePerIpRateLimit(); | ||
|
||
public async Task<Ok<OrdersGrownByDayDto>> HandleAsync(OrdersGrownByDayRequest request, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
OrdersGrownByDayQuery query = new(request.CurrentDate); | ||
|
||
var result = await sender.Send(query, cancellationToken); | ||
|
||
return TypedResults.Ok(result.Value); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/RookieShop.Application/Reports/DTOs/OrdersGrownByDayDto.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace RookieShop.Application.Reports.DTOs; | ||
|
||
public sealed class OrdersGrownByDayDto | ||
{ | ||
public long TodayCount { get; set; } | ||
public long YesterdayCount { get; set; } | ||
public long GrowthPercentage { get; set; } | ||
public DateTime CurrentDate { get; set; } | ||
} |
54 changes: 54 additions & 0 deletions
54
src/RookieShop.Application/Reports/Queries/OrdersGrownByDay/OrdersGrownByDayHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using Ardalis.Result; | ||
using Dapper; | ||
using RookieShop.Application.Reports.DTOs; | ||
using RookieShop.Domain.SharedKernel; | ||
using RookieShop.Persistence; | ||
|
||
namespace RookieShop.Application.Reports.Queries.OrdersGrownByDay; | ||
|
||
public sealed class OrdersGrownByDayHandler(IDatabaseFactory factory) | ||
: IQueryHandler<OrdersGrownByDayQuery, Result<OrdersGrownByDayDto>> | ||
{ | ||
public async Task<Result<OrdersGrownByDayDto>> Handle(OrdersGrownByDayQuery request, | ||
CancellationToken cancellationToken) | ||
{ | ||
const string sql = $""" | ||
WITH orders_by_day AS ( | ||
SELECT | ||
DATE_TRUNC('day', o.created_date AT TIME ZONE 'UTC') AS order_date, | ||
COUNT(*) AS order_count | ||
FROM orders o | ||
GROUP BY order_date | ||
), | ||
today_orders AS ( | ||
SELECT order_count AS today_count | ||
FROM orders_by_day | ||
WHERE order_date = DATE_TRUNC('day', @CurrentDate) | ||
), | ||
yesterday_orders AS ( | ||
SELECT order_count AS yesterday_count | ||
FROM orders_by_day | ||
WHERE order_date = DATE_TRUNC('day', @CurrentDate) - INTERVAL '1 day' | ||
) | ||
SELECT | ||
COALESCE(today_orders.today_count, 0) AS {nameof(OrdersGrownByDayDto.TodayCount)}, | ||
COALESCE(yesterday_orders.yesterday_count, 0) AS {nameof(OrdersGrownByDayDto.YesterdayCount)}, | ||
CASE | ||
WHEN COALESCE(yesterday_orders.yesterday_count, 0) = 0 THEN 0 | ||
ELSE (COALESCE(today_orders.today_count, 0) - COALESCE(yesterday_orders.yesterday_count, 0)) / COALESCE(yesterday_orders.yesterday_count, 0)::decimal * 100 | ||
END AS {nameof(OrdersGrownByDayDto.GrowthPercentage)}, | ||
DATE_TRUNC('day', @CurrentDate) AS {nameof(OrdersGrownByDayDto.CurrentDate)} | ||
FROM | ||
(SELECT 1) AS dummy | ||
LEFT JOIN today_orders ON TRUE | ||
LEFT JOIN yesterday_orders ON TRUE; | ||
"""; | ||
|
||
using var conn = factory.GetOpenConnection(); | ||
|
||
var result = await conn.QueryAsync<OrdersGrownByDayDto>(sql, | ||
new { request.CurrentDate }); | ||
|
||
return result.First(); | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/RookieShop.Application/Reports/Queries/OrdersGrownByDay/OrdersGrownByDayQuery.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
using Ardalis.Result; | ||
using RookieShop.Application.Reports.DTOs; | ||
using RookieShop.Domain.SharedKernel; | ||
|
||
namespace RookieShop.Application.Reports.Queries.OrdersGrownByDay; | ||
|
||
public sealed record OrdersGrownByDayQuery(DateTime? CurrentDate) : IQuery<Result<OrdersGrownByDayDto>>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { useQuery } from "@tanstack/react-query" | ||
|
||
import reportService from "./report.service" | ||
|
||
export default function useGetOrderGrownByDay() { | ||
return useQuery({ | ||
queryKey: ["order-grown-by-day"], | ||
queryFn: () => reportService.getOrderGrownByDay(), | ||
}) | ||
} |