Implemented user transactions
This commit is contained in:
@ -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)]
|
||||||
//TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
|
public virtual async Task<IActionResult> TransferPhysicalCurrency([FromBody]WalletTransferPhysicalBody body)
|
||||||
// return StatusCode(200);
|
{
|
||||||
|
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(..), ...
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user