Skip to content

Commit

Permalink
feat: list transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrior committed Aug 24, 2024
1 parent 1285ce8 commit f1f044a
Show file tree
Hide file tree
Showing 13 changed files with 604 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ relatórios diários, semanais e mensais para um gerenciamento mais eficaz, bem
### :money_with_wings: Transações

- ✅ criar transação;
- listar transações;
- listar transações;
- ✅ visualizar transação;
- ⬜ editar transação;
- ⬜ excluir transação;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using MyWallet.Shared.Errors;
using MyWallet.Shared.Features;

namespace MyWallet.Features.Transactions.List;

public sealed class ListTransactionEndpoint : IEndpoint
{
public void Build(IEndpointRouteBuilder builder) =>
builder.MapGet("transactions", ListTransactionsAsync)
.RequireAuthorization();

private static Task<IResult> ListTransactionsAsync(
[AsParameters] ListTransactionsRequest request,
ISender sender,
HttpContext context,
CancellationToken cancellationToken)
{
return sender.Send(request.ToQuery(), cancellationToken)
.ToResponseAsync(Results.Ok, context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using MyWallet.Domain.Wallets;
using MyWallet.Shared.Features;
using MyWallet.Shared.Persistence;

namespace MyWallet.Features.Transactions.List;

public sealed class ListTransactionsHandler(IWalletRepository walletRepository, IDbContext db)
: IQueryHandler<ListTransactionsQuery, ListTransactionsResponse>
{
public async Task<ErrorOr<ListTransactionsResponse>> Handle(ListTransactionsQuery query,
CancellationToken cancellationToken)
{
if (!await walletRepository.ExistsAsync(new WalletId(query.WalletId), cancellationToken))
{
return Shared.TransactionErrors.WalletNotFound;
}

var total = await CountTotalTransactionsAsync(query, cancellationToken);
if (total is 0)
{
return new ListTransactionsResponse(
Items: [],
Page: query.Page,
Limit: query.Limit,
Total: total)
{
WalletId = query.WalletId,
From = query.From,
To = query.To
};
}

var transactions = await ListTransactionsAsync(query, cancellationToken);

return new ListTransactionsResponse(
Items: transactions,
Page: query.Page,
Limit: query.Limit,
Total: total)
{
WalletId = query.WalletId,
From = query.From,
To = query.To
};
}

private Task<int> CountTotalTransactionsAsync(ListTransactionsQuery query,
CancellationToken cancellationToken)
{
return db.ExecuteScalarAsync<int>(
sql: """
SELECT COUNT(*)
FROM transactions t
WHERE t.wallet_id = @WalletId AND (t.date BETWEEN @From AND @To)
""",
param: query,
cancellationToken);
}

private async Task<IEnumerable<TransactionResponse>> ListTransactionsAsync(ListTransactionsQuery query,
CancellationToken cancellationToken)
{
return await db.QueryAsync<TransactionResponse>(
sql: """
SELECT
t.id,
t.type,
t.name,
(SELECT c.name FROM categories c WHERE c.id = t.category_id) AS category,
t.amount,
t.currency,
t.date
FROM transactions t
WHERE t.wallet_id = @WalletId AND (t.date BETWEEN @From AND @To)
ORDER BY t.date DESC, t.created_at DESC
LIMIT @Limit OFFSET @Offset
""",
param: query,
cancellationToken);
}
}
20 changes: 20 additions & 0 deletions src/MyWallet/Features/Transactions/LIst/ListTransactions.Query.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using MyWallet.Shared.Features;

namespace MyWallet.Features.Transactions.List;

public sealed record ListTransactionsQuery : IQuery<ListTransactionsResponse>, IHaveUser
{
public required Ulid WalletId { get; init; }

public required DateOnly From { get; init; }

public required DateOnly To { get; init; }

public required int Page { get; init; }

public required int Limit { get; init; }

public Ulid UserId { get; set; }

public int Offset => (Page - 1) * Limit;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace MyWallet.Features.Transactions.List;

public sealed record ListTransactionsRequest
{
public required Ulid WalletId { get; init; }

public required DateOnly From { get; init; }

public DateOnly? To { get; init; }

public int? Page { get; init; }

public int? Limit { get; init; }

public ListTransactionsQuery ToQuery() => new()
{
WalletId = WalletId,
From = From,
To = To ?? DateOnly.MaxValue,
Page = Page ?? 1,
Limit = Limit ?? 10
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using MyWallet.Shared.Contracts;

namespace MyWallet.Features.Transactions.List;

public sealed record TransactionResponse
{
public required Ulid Id { get; init; }

public required string Type { get; init; }

public required string Name { get; init; }

public required string Category { get; init; }

public required decimal Amount { get; init; }

public required string Currency { get; init; }

public required DateOnly Date { get; init; }
}

public sealed record ListTransactionsResponse(
IEnumerable<TransactionResponse> Items,
int Page,
int Limit,
int Total) : PageResponse<TransactionResponse>(Items, Page, Limit, Total)
{
public required Ulid WalletId { get; init; }

public required DateOnly From { get; init; }

public required DateOnly To { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using MyWallet.Shared.Security;

namespace MyWallet.Features.Transactions.List;

public sealed class ListTransactionsSecurity : IAuthorizer<ListTransactionsQuery>
{
public IEnumerable<IRequirement> GetRequirements(ListTransactionsQuery query)
{
yield return new WalletOwnerRequirement(query.UserId, query.WalletId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using MyWallet.Shared.Validations;

namespace MyWallet.Features.Transactions.List;

public sealed class ListTransactionsValidator : AbstractValidator<ListTransactionsQuery>
{
public ListTransactionsValidator()
{
RuleFor(q => q.To)
.Must((query, to) => query.From <= to)
.WithMessage("Must be greater than or equal to 'from' date.");

RuleFor(q => q.Page)
.PageNumber();

RuleFor(q => q.Limit)
.PageLimit();
}
}
2 changes: 1 addition & 1 deletion src/MyWallet/Shared/Contracts/PageResponse.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace MyWallet.Shared.Contracts;

public sealed record PageResponse<T>(IEnumerable<T> Items, int Page, int Limit, int Total)
public record PageResponse<T>(IEnumerable<T> Items, int Page, int Limit, int Total)
{
public static PageResponse<T> Empty(int page, int limit) => new([], page, limit, 0);

Expand Down
2 changes: 2 additions & 0 deletions src/MyWallet/_requests/transactions/list-transactions.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GET {{baseUrl}}/transactions?walletId=01J5GXMBHYMZAT9YN8H7J3DS4J&from=2024-08-17
Authorization: Bearer {{accessToken}}
Loading

0 comments on commit f1f044a

Please sign in to comment.