diff --git a/src/IO.Swagger/Controllers/WalletApi.cs b/src/IO.Swagger/Controllers/WalletApi.cs index 6363b1c..b0b7765 100644 --- a/src/IO.Swagger/Controllers/WalletApi.cs +++ b/src/IO.Swagger/Controllers/WalletApi.cs @@ -54,21 +54,19 @@ namespace IO.Swagger.Controllers [ValidateModelState] [SwaggerOperation("GetUserBalances")] [ProducesResponseType(typeof(WalletBalanceDto), 200)] - [ProducesResponseType(401)] public virtual async Task GetUserBalances() { var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value; if (!int.TryParse(userIdString, out int userId)) return Unauthorized(); - var transactions = await transactionRepository.GetTransactionsForUser(userId); - var balances = transactions.GroupBy(t => t.Currency) - .Select(g => new WalletBalanceDto + var balances = await transactionRepository.GetBalancesForUser(userId); + var res = balances.Select(t => new WalletBalanceDto { - Currency = mapper.Map(g.Key), - Balance = g.Sum(t =>t.Amount) + Currency = mapper.Map(t.Item1), + Balance = t.Item2 }); - return Ok(balances); + return Ok(res); } /// @@ -82,7 +80,6 @@ namespace IO.Swagger.Controllers [ValidateModelState] [SwaggerOperation("GetUserTransactions")] [ProducesResponseType(typeof(IEnumerable), 200)] - [ProducesResponseType(401)] public virtual async Task GetUserTransactions() { var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value; @@ -90,7 +87,6 @@ namespace IO.Swagger.Controllers return Unauthorized(); var transactions = await transactionRepository.GetTransactionsForUser(userId); - return Ok(transactions.Select(mapper.Map)); } @@ -132,18 +128,18 @@ namespace IO.Swagger.Controllers [Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)] [ValidateModelState] [SwaggerOperation("TransferPhysicalCurrency")] - public virtual 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(..), ... - // return StatusCode(200); + [ProducesResponseType(typeof(IEnumerable), 400)] + [ProducesResponseType(typeof(TransactionReturnCode), 409)] + public virtual async Task TransferPhysicalCurrency([FromBody]WalletTransferPhysicalBody body) + { + var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value; + 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(..), ... - // return StatusCode(400); - - //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(); + var transactionResult = await transactionRepository.TransferPhysical(body, userId); + return transactionResult == TransactionReturnCode.Success ? Ok() : StatusCode(409, (int)transactionResult); } } } diff --git a/src/IO.Swagger/Models/dto/WalletTransferPhysicalBody.cs b/src/IO.Swagger/Models/dto/WalletTransferPhysicalBody.cs index fca7ef9..5d68be0 100644 --- a/src/IO.Swagger/Models/dto/WalletTransferPhysicalBody.cs +++ b/src/IO.Swagger/Models/dto/WalletTransferPhysicalBody.cs @@ -27,25 +27,30 @@ namespace IO.Swagger.Models.dto public partial class WalletTransferPhysicalBody : IEquatable { /// - /// Gets or Sets Email + /// Gets or Sets DestUserEmail /// - [DataMember(Name = "email")] - public string Email { get; set; } + [DataMember(Name = "destUserEmail")] + public string DestUserEmail { get; set; } /// /// Gets or Sets Amount /// - + [Range(0.1, float.MaxValue, ErrorMessage="Please enter a valid transfer amount")] [DataMember(Name = "amount")] - public decimal? Amount { get; set; } + public float Amount { get; set; } /// /// Gets or Sets CurrencyId /// - + [Required] [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; } /// /// Returns the string presentation of the object @@ -55,7 +60,7 @@ namespace IO.Swagger.Models.dto { var sb = new StringBuilder(); 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(" CurrencyId: ").Append(CurrencyId).Append("\n"); sb.Append("}\n"); @@ -95,9 +100,9 @@ namespace IO.Swagger.Models.dto return ( - Email == other.Email || - Email != null && - Email.Equals(other.Email) + DestUserEmail == other.DestUserEmail || + DestUserEmail != null && + DestUserEmail.Equals(other.DestUserEmail) ) && ( Amount == other.Amount || @@ -121,8 +126,8 @@ namespace IO.Swagger.Models.dto { var hashCode = 41; // Suitable nullity checks etc, of course :) - if (Email != null) - hashCode = hashCode * 59 + Email.GetHashCode(); + if (DestUserEmail != null) + hashCode = hashCode * 59 + DestUserEmail.GetHashCode(); if (Amount != null) hashCode = hashCode * 59 + Amount.GetHashCode(); if (CurrencyId != null) diff --git a/src/IO.Swagger/Repositories/ITransactionRepository.cs b/src/IO.Swagger/Repositories/ITransactionRepository.cs index d0ffe9a..75f1401 100644 --- a/src/IO.Swagger/Repositories/ITransactionRepository.cs +++ b/src/IO.Swagger/Repositories/ITransactionRepository.cs @@ -1,11 +1,23 @@ using IO.Swagger.Models.db; +using IO.Swagger.Models.dto; +using System; using System.Collections.Generic; using System.Threading.Tasks; namespace IO.Swagger.Repositories { + public enum TransactionReturnCode + { + Success, + InsufficientFunds, + UnknownDestinationUser, + DbError + } + public interface ITransactionRepository { + Task>> GetBalancesForUser(int userId); Task> GetTransactionsForUser(int userId); + Task TransferPhysical(WalletTransferPhysicalBody request, int fromUserId); } } diff --git a/src/IO.Swagger/Repositories/TransactionRepository.cs b/src/IO.Swagger/Repositories/TransactionRepository.cs index c0263fc..3f0ff20 100644 --- a/src/IO.Swagger/Repositories/TransactionRepository.cs +++ b/src/IO.Swagger/Repositories/TransactionRepository.cs @@ -1,4 +1,5 @@ using IO.Swagger.Models.db; +using IO.Swagger.Models.dto; using IO.Swagger.Services; using Microsoft.EntityFrameworkCore; using System; @@ -8,6 +9,7 @@ using System.Threading.Tasks; namespace IO.Swagger.Repositories { + public class TransactionRepository : ITransactionRepository { BankDbContext context; @@ -31,5 +33,48 @@ namespace IO.Swagger.Repositories t.Amount *= -1; return transactions; } + + public async Task>> 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 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; + } + } }