Implemented user transactions

This commit is contained in:
2023-08-19 17:38:32 -04:00
parent dc9ab74598
commit ff327d0a03
4 changed files with 91 additions and 33 deletions

View File

@ -54,21 +54,19 @@ namespace IO.Swagger.Controllers
[ValidateModelState] [ValidateModelState]
[SwaggerOperation("GetUserBalances")] [SwaggerOperation("GetUserBalances")]
[ProducesResponseType(typeof(WalletBalanceDto), 200)] [ProducesResponseType(typeof(WalletBalanceDto), 200)]
[ProducesResponseType(401)]
public virtual async Task<IActionResult> GetUserBalances() public virtual async Task<IActionResult> GetUserBalances()
{ {
var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value; var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
if (!int.TryParse(userIdString, out int userId)) if (!int.TryParse(userIdString, out int userId))
return Unauthorized(); return Unauthorized();
var transactions = await transactionRepository.GetTransactionsForUser(userId); var balances = await transactionRepository.GetBalancesForUser(userId);
var balances = transactions.GroupBy(t => t.Currency) var res = balances.Select(t => new WalletBalanceDto
.Select(g => new WalletBalanceDto
{ {
Currency = mapper.Map<CurrencyDto>(g.Key), Currency = mapper.Map<CurrencyDto>(t.Item1),
Balance = g.Sum(t =>t.Amount) Balance = t.Item2
}); });
return Ok(balances); return Ok(res);
} }
/// <summary> /// <summary>
@ -82,7 +80,6 @@ namespace IO.Swagger.Controllers
[ValidateModelState] [ValidateModelState]
[SwaggerOperation("GetUserTransactions")] [SwaggerOperation("GetUserTransactions")]
[ProducesResponseType(typeof(IEnumerable<TransactionDto>), 200)] [ProducesResponseType(typeof(IEnumerable<TransactionDto>), 200)]
[ProducesResponseType(401)]
public virtual async Task<IActionResult> GetUserTransactions() public virtual async Task<IActionResult> GetUserTransactions()
{ {
var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value; var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
@ -90,7 +87,6 @@ namespace IO.Swagger.Controllers
return Unauthorized(); return Unauthorized();
var transactions = await transactionRepository.GetTransactionsForUser(userId); var transactions = await transactionRepository.GetTransactionsForUser(userId);
return Ok(transactions.Select(mapper.Map<TransactionDto>)); return Ok(transactions.Select(mapper.Map<TransactionDto>));
} }
@ -132,18 +128,18 @@ namespace IO.Swagger.Controllers
[Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)] [Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)]
[ValidateModelState] [ValidateModelState]
[SwaggerOperation("TransferPhysicalCurrency")] [SwaggerOperation("TransferPhysicalCurrency")]
public virtual IActionResult TransferPhysicalCurrency([FromBody]WalletTransferPhysicalBody body) [ProducesResponseType(typeof(IEnumerable<string>), 400)]
[ProducesResponseType(typeof(TransactionReturnCode), 409)]
public virtual async Task<IActionResult> TransferPhysicalCurrency([FromBody]WalletTransferPhysicalBody body)
{ {
//TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
// return StatusCode(200); if (!int.TryParse(userIdString, out int userId))
return Unauthorized();
if (!ModelState.IsValid)
return BadRequest(ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage)));
//TODO: Uncomment the next line to return response 400 or use other options such as return this.NotFound(), return this.BadRequest(..), ... var transactionResult = await transactionRepository.TransferPhysical(body, userId);
// return StatusCode(400); return transactionResult == TransactionReturnCode.Success ? Ok() : StatusCode(409, (int)transactionResult);
//TODO: Uncomment the next line to return response 401 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
// return StatusCode(401);
throw new NotImplementedException();
} }
} }
} }

View File

@ -27,25 +27,30 @@ namespace IO.Swagger.Models.dto
public partial class WalletTransferPhysicalBody : IEquatable<WalletTransferPhysicalBody> public partial class WalletTransferPhysicalBody : IEquatable<WalletTransferPhysicalBody>
{ {
/// <summary> /// <summary>
/// Gets or Sets Email /// Gets or Sets DestUserEmail
/// </summary> /// </summary>
[DataMember(Name = "email")] [DataMember(Name = "destUserEmail")]
public string Email { get; set; } public string DestUserEmail { get; set; }
/// <summary> /// <summary>
/// Gets or Sets Amount /// Gets or Sets Amount
/// </summary> /// </summary>
[Range(0.1, float.MaxValue, ErrorMessage="Please enter a valid transfer amount")]
[DataMember(Name = "amount")] [DataMember(Name = "amount")]
public decimal? Amount { get; set; } public float Amount { get; set; }
/// <summary> /// <summary>
/// Gets or Sets CurrencyId /// Gets or Sets CurrencyId
/// </summary> /// </summary>
[Required]
[DataMember(Name = "currencyId")] [DataMember(Name = "currencyId")]
public int? CurrencyId { get; set; } public int CurrencyId { get; set; }
[DataMember(Name = "memo")]
[StringLength(32, MinimumLength = 2)]
[Required]
public string Memo { get; set; }
/// <summary> /// <summary>
/// Returns the string presentation of the object /// Returns the string presentation of the object
@ -55,7 +60,7 @@ namespace IO.Swagger.Models.dto
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("class WalletTransferPhysicalBody {\n"); sb.Append("class WalletTransferPhysicalBody {\n");
sb.Append(" Email: ").Append(Email).Append("\n"); sb.Append(" DestUserEmail: ").Append(DestUserEmail).Append("\n");
sb.Append(" Amount: ").Append(Amount).Append("\n"); sb.Append(" Amount: ").Append(Amount).Append("\n");
sb.Append(" CurrencyId: ").Append(CurrencyId).Append("\n"); sb.Append(" CurrencyId: ").Append(CurrencyId).Append("\n");
sb.Append("}\n"); sb.Append("}\n");
@ -95,9 +100,9 @@ namespace IO.Swagger.Models.dto
return return
( (
Email == other.Email || DestUserEmail == other.DestUserEmail ||
Email != null && DestUserEmail != null &&
Email.Equals(other.Email) DestUserEmail.Equals(other.DestUserEmail)
) && ) &&
( (
Amount == other.Amount || Amount == other.Amount ||
@ -121,8 +126,8 @@ namespace IO.Swagger.Models.dto
{ {
var hashCode = 41; var hashCode = 41;
// Suitable nullity checks etc, of course :) // Suitable nullity checks etc, of course :)
if (Email != null) if (DestUserEmail != null)
hashCode = hashCode * 59 + Email.GetHashCode(); hashCode = hashCode * 59 + DestUserEmail.GetHashCode();
if (Amount != null) if (Amount != null)
hashCode = hashCode * 59 + Amount.GetHashCode(); hashCode = hashCode * 59 + Amount.GetHashCode();
if (CurrencyId != null) if (CurrencyId != null)

View File

@ -1,11 +1,23 @@
using IO.Swagger.Models.db; using IO.Swagger.Models.db;
using IO.Swagger.Models.dto;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace IO.Swagger.Repositories namespace IO.Swagger.Repositories
{ {
public enum TransactionReturnCode
{
Success,
InsufficientFunds,
UnknownDestinationUser,
DbError
}
public interface ITransactionRepository public interface ITransactionRepository
{ {
Task<List<Tuple<Currency, float>>> GetBalancesForUser(int userId);
Task<List<Transaction>> GetTransactionsForUser(int userId); Task<List<Transaction>> GetTransactionsForUser(int userId);
Task<TransactionReturnCode> TransferPhysical(WalletTransferPhysicalBody request, int fromUserId);
} }
} }

View File

@ -1,4 +1,5 @@
using IO.Swagger.Models.db; using IO.Swagger.Models.db;
using IO.Swagger.Models.dto;
using IO.Swagger.Services; using IO.Swagger.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System; using System;
@ -8,6 +9,7 @@ using System.Threading.Tasks;
namespace IO.Swagger.Repositories namespace IO.Swagger.Repositories
{ {
public class TransactionRepository : ITransactionRepository public class TransactionRepository : ITransactionRepository
{ {
BankDbContext context; BankDbContext context;
@ -31,5 +33,48 @@ namespace IO.Swagger.Repositories
t.Amount *= -1; t.Amount *= -1;
return transactions; return transactions;
} }
public async Task<List<Tuple<Currency, float>>> GetBalancesForUser(int userId)
{
var transactions = await context.Transactions.Where(t => t.ToUserId == userId || t.FromUserId == userId)
.Include(t => t.Currency)
.GroupBy(t => t.Currency)
.Select(g => Tuple.Create(
g.Key,
g.Sum(t =>t.ToUserId != t.FromUserId && t.FromUserId == userId
? -t.Amount
: t.Amount
)
)
)
.ToListAsync();
return transactions;
}
public async Task<TransactionReturnCode> TransferPhysical(WalletTransferPhysicalBody request, int fromUserId)
{
var trimmedDest = request.DestUserEmail.Trim().ToLower();
var destUser = await context.Users.FirstOrDefaultAsync(u => u.Email == trimmedDest);
if (destUser == null)
return TransactionReturnCode.UnknownDestinationUser;
var balances = await GetBalancesForUser(fromUserId);
var balance = balances.FirstOrDefault(b => b.Item1.CurrencyId == request.CurrencyId);
if (balance == null || balance.Item2 < request.Amount)
return TransactionReturnCode.InsufficientFunds;
await context.Transactions.AddAsync(new Transaction
{
Amount = request.Amount,
CurrencyId = request.CurrencyId,
ToUserId = destUser.Id,
FromUserId = fromUserId,
Memo = request.Memo
});
return await context.SaveChangesAsync() > 0 ? TransactionReturnCode.Success : TransactionReturnCode.DbError;
}
} }
} }