From c5b27421a3a002afd1718379fa1cecac77f44b44 Mon Sep 17 00:00:00 2001 From: Toastie Date: Wed, 30 Oct 2024 23:56:52 +1300 Subject: [PATCH] finance api implementation --- src/EllieBot.GrpcApiBase/protos/fin.proto | 60 +++++++++++++ .../Modules/Games/NCanvas/NCanvasService.cs | 4 +- src/EllieBot/Services/GrpcApi/FinSvc.cs | 89 +++++++++++++++++++ .../_common/Currency/ICurrencyService.cs | 7 ++ .../Services/Currency/CurrencyService.cs | 41 +++++++-- 5 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 src/EllieBot.GrpcApiBase/protos/fin.proto create mode 100644 src/EllieBot/Services/GrpcApi/FinSvc.cs diff --git a/src/EllieBot.GrpcApiBase/protos/fin.proto b/src/EllieBot.GrpcApiBase/protos/fin.proto new file mode 100644 index 0000000..03c15be --- /dev/null +++ b/src/EllieBot.GrpcApiBase/protos/fin.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +option csharp_namespace = "EllieBot.GrpcApi"; + +import "google/protobuf/timestamp.proto"; + +package fin; + +service GrpcFin { + rpc GetTransactions(GetTransactionsRequest) returns (GetTransactionsReply); + rpc GetHoldings(GetHoldingsRequest) returns (GetHoldingsReply); + rpc Withdraw(WithdrawRequest) returns (WithdrawReply); + rpc Deposit(DepositRequest) returns (DepositReply); +} + +message GetTransactionsRequest { + int32 page = 1; + uint64 userId = 2; +} + +message GetTransactionsReply { + repeated TransactionReply transactions = 1; + int32 total = 2; +} + +message TransactionReply { + int64 amount = 1; + string note = 2; + string type = 3; + string extra = 4; + google.protobuf.Timestamp timestamp = 5; + string id = 6; +} + +message GetHoldingsRequest { + uint64 userId = 1; +} + +message GetHoldingsReply { + int64 cash = 1; + int64 bank = 2; +} + +message WithdrawRequest { + uint64 userId = 1; + int64 amount = 2; +} + +message WithdrawReply { + bool success = 1; +} + +message DepositRequest { + uint64 userId = 1; + int64 amount = 2; +} + +message DepositReply { + bool success = 1; +} diff --git a/src/EllieBot/Modules/Games/NCanvas/NCanvasService.cs b/src/EllieBot/Modules/Games/NCanvas/NCanvasService.cs index 2e4a57b..c0a4b1b 100644 --- a/src/EllieBot/Modules/Games/NCanvas/NCanvasService.cs +++ b/src/EllieBot/Modules/Games/NCanvas/NCanvasService.cs @@ -110,7 +110,7 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService var wallet = await _cs.GetWalletAsync(userId); - var paid = await wallet.Take(price, new("canvas", "pixel", $"Bought pixel #{position}")); + var paid = await wallet.Take(price, new("canvas", "pixel-buy", $"Bought pixel {new kwum(position)}")); if (!paid) { return SetPixelResult.NotEnoughMoney; @@ -138,7 +138,7 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService if (!success) { - await wallet.Add(price, new("canvas", "pixel-refund", $"Refund pixel #{position} purchase")); + await wallet.Add(price, new("canvas", "pixel-refund", $"Refund pixel {new kwum(position)} purchase")); } return success ? SetPixelResult.Success : SetPixelResult.InsufficientPayment; diff --git a/src/EllieBot/Services/GrpcApi/FinSvc.cs b/src/EllieBot/Services/GrpcApi/FinSvc.cs new file mode 100644 index 0000000..e0a92e9 --- /dev/null +++ b/src/EllieBot/Services/GrpcApi/FinSvc.cs @@ -0,0 +1,89 @@ +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using EllieBot.Db.Models; +using EllieBot.Modules.Gambling.Bank; +using EllieBot.Modules.EllieExpressions; +using EllieBot.Modules.Utility; + +namespace EllieBot.GrpcApi; + +public class FinSvc : GrpcFin.GrpcFinBase, IGrpcSvc, IEService +{ + private readonly ICurrencyService _cs; + private readonly IBankService _bank; + + public FinSvc(ICurrencyService cs, IBankService bank) + { + _cs = cs; + _bank = bank; + } + + public ServerServiceDefinition Bind() + => GrpcFin.BindService(this); + + [GrpcNoAuthRequired] + public override async Task Deposit(DepositRequest request, ServerCallContext context) + { + if (request.Amount <= 0) + throw new RpcException(new Status(StatusCode.InvalidArgument, "Amount must be greater than 0")); + + var succ = await _bank.DepositAsync(request.UserId, request.Amount); + + return new DepositReply + { + Success = succ + }; + } + + [GrpcNoAuthRequired] + public override async Task Withdraw(WithdrawRequest request, ServerCallContext context) + { + if (request.Amount <= 0) + throw new RpcException(new Status(StatusCode.InvalidArgument, "Amount must be greater than 0")); + + var succ = await _bank.WithdrawAsync(request.UserId, request.Amount); + + return new WithdrawReply + { + Success = succ + }; + } + + [GrpcNoAuthRequired] + public override async Task GetHoldings(GetHoldingsRequest request, ServerCallContext context) + { + return new GetHoldingsReply + { + Bank = await _bank.GetBalanceAsync(request.UserId), + Cash = await _cs.GetBalanceAsync(request.UserId) + }; + } + + [GrpcNoAuthRequired] + public override async Task GetTransactions( + GetTransactionsRequest request, + ServerCallContext context) + { + if (request.Page < 1) + throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be greater than 0")); + + var trs = await _cs.GetTransactionsAsync(request.UserId, request.Page - 1); + + var reply = new GetTransactionsReply + { + Total = await _cs.GetTransactionsCountAsync(request.UserId) + }; + + reply.Transactions.AddRange(trs.Select(x => new TransactionReply() + { + Id = new kwum(x.Id).ToString(), + Timestamp = Timestamp.FromDateTime(DateTime.UtcNow), + Amount = x.Amount, + Extra = x.Extra ?? string.Empty, + Note = x.Note ?? string.Empty, + Type = x.Type ?? string.Empty, + })); + + return reply; + } +} diff --git a/src/EllieBot/_common/Currency/ICurrencyService.cs b/src/EllieBot/_common/Currency/ICurrencyService.cs index 35e8273..4fe8b5c 100644 --- a/src/EllieBot/_common/Currency/ICurrencyService.cs +++ b/src/EllieBot/_common/Currency/ICurrencyService.cs @@ -40,4 +40,11 @@ public interface ICurrencyService TxData? txData); Task> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9); + + Task> GetTransactionsAsync( + ulong userId, + int page, + int perPage = 15); + + Task GetTransactionsCountAsync(ulong userId); } \ No newline at end of file diff --git a/src/EllieBot/_common/Services/Currency/CurrencyService.cs b/src/EllieBot/_common/Services/Currency/CurrencyService.cs index 66029ff..a6428e1 100644 --- a/src/EllieBot/_common/Services/Currency/CurrencyService.cs +++ b/src/EllieBot/_common/Services/Currency/CurrencyService.cs @@ -55,14 +55,14 @@ public sealed class CurrencyService : ICurrencyService, IEService { await using var ctx = _db.GetDbContext(); await ctx - .GetTable() - .Where(x => userIds.Contains(x.UserId)) - .UpdateAsync(du => new() - { - CurrencyAmount = du.CurrencyAmount >= amount - ? du.CurrencyAmount - amount - : 0 - }); + .GetTable() + .Where(x => userIds.Contains(x.UserId)) + .UpdateAsync(du => new() + { + CurrencyAmount = du.CurrencyAmount >= amount + ? du.CurrencyAmount - amount + : 0 + }); await ctx.SaveChangesAsync(); return; } @@ -112,4 +112,29 @@ public sealed class CurrencyService : ICurrencyService, IEService await using var uow = _db.GetDbContext(); return await uow.Set().GetTopRichest(ignoreId, page, perPage); } + + public async Task> GetTransactionsAsync( + ulong userId, + int page, + int perPage = 15) + { + await using var uow = _db.GetDbContext(); + + var trs = await uow.GetTable() + .Where(x => x.UserId == userId) + .OrderByDescending(x => x.DateAdded) + .Skip(perPage * page) + .Take(perPage) + .ToListAsyncLinqToDB(); + + return trs; + } + + public async Task GetTransactionsCountAsync(ulong userId) + { + await using var uow = _db.GetDbContext(); + return await uow.GetTable() + .Where(x => x.UserId == userId) + .CountAsyncLinqToDB(); + } } \ No newline at end of file