diff --git a/src/IO.Swagger/Controllers/AuthApi.cs b/src/IO.Swagger/Controllers/AuthApi.cs
index dee5b2b..b2af6b9 100644
--- a/src/IO.Swagger/Controllers/AuthApi.cs
+++ b/src/IO.Swagger/Controllers/AuthApi.cs
@@ -23,6 +23,8 @@ using System.Threading.Tasks;
using System.Linq;
using IO.Swagger.Services;
using System.Security.Claims;
+using AutoMapper;
+using Newtonsoft.Json.Linq;
namespace IO.Swagger.Controllers
{
@@ -34,17 +36,20 @@ namespace IO.Swagger.Controllers
{
private readonly IUserRepository repository;
private readonly JwtService jwt;
+ private readonly IMapper mapper;
///
/// The controller for the authotization endpoints
///
///
///
+ ///
///
- public AuthApiController(IUserRepository repository, JwtService jwt)
+ public AuthApiController(IUserRepository repository, JwtService jwt, IMapper mapper)
{
this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
this.jwt = jwt ?? throw new ArgumentNullException(nameof(jwt));
+ this.mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
///
@@ -57,13 +62,15 @@ namespace IO.Swagger.Controllers
[Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)]
[ValidateModelState]
[SwaggerOperation("GetUserDetails")]
+ [ProducesResponseType(typeof(UserDto), 200)]
+ [ProducesResponseType(401)]
public virtual async Task GetUserDetails()
{
var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
if (!int.TryParse(userIdString, out int userId))
return Unauthorized();
var user = await repository.RetrieveUser(userId);
- return user == null ? NoContent() : Ok(user);
+ return Ok(mapper.Map(user));
}
///
@@ -71,17 +78,21 @@ namespace IO.Swagger.Controllers
///
///
/// Logged in successfully
+ /// Bad Request
/// Unauthorized
[HttpPost]
[Route("/v1/api/auth/login")]
[ValidateModelState]
[SwaggerOperation("LoginUser")]
+ [ProducesResponseType(typeof(TokenDto), 200)]
+ [ProducesResponseType(typeof(IEnumerable), 400)]
+ [ProducesResponseType(401)]
public virtual async Task LoginUser([FromBody]AuthLoginBody body)
{
if (!ModelState.IsValid)
return BadRequest(ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage)));
var user = await repository.LoginUser(body);
- return user == null ? Unauthorized() : Ok(new { token = jwt.GenerateJwt(user.Id) });
+ return user == null ? Unauthorized() : Ok(new TokenDto{ Token = jwt.GenerateJwt(user.Id) });
}
///
@@ -96,13 +107,16 @@ namespace IO.Swagger.Controllers
[ValidateModelState]
[SwaggerOperation("RegisterUser")]
+ [ProducesResponseType(typeof(TokenDto), 200)]
+ [ProducesResponseType(typeof(IEnumerable), 400)]
+ [ProducesResponseType(409)]
public async Task RegisterUser([FromBody]AuthRegisterBody body)
{
if (!ModelState.IsValid)
return BadRequest(ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage)));
-
+
var user = await repository.RegisterUser(body);
- return user == null ? StatusCode(409) : Ok(new { token = jwt.GenerateJwt(user.Id) });
+ return user == null ? StatusCode(409) : Ok(new TokenDto{ Token = jwt.GenerateJwt(user.Id) });
}
}
}
diff --git a/src/IO.Swagger/Controllers/CurrencyApi.cs b/src/IO.Swagger/Controllers/CurrencyApi.cs
index b0be919..9a74118 100644
--- a/src/IO.Swagger/Controllers/CurrencyApi.cs
+++ b/src/IO.Swagger/Controllers/CurrencyApi.cs
@@ -103,6 +103,10 @@ namespace IO.Swagger.Controllers
[Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)]
[ValidateModelState]
[SwaggerOperation("CreateCurrency")]
+ [ProducesResponseType(201)]
+ [ProducesResponseType(typeof(IEnumerable), 400)]
+ [ProducesResponseType(401)]
+ [ProducesResponseType(422)]
public virtual async Task CreateCurrency([FromBody]CurrencyCreateBody body)
{
var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
@@ -129,6 +133,10 @@ namespace IO.Swagger.Controllers
[Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)]
[ValidateModelState]
[SwaggerOperation("MintCurrency")]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(typeof(IEnumerable), 400)]
+ [ProducesResponseType(401)]
+ [ProducesResponseType(409)]
public virtual async Task MintCurrency([FromBody]CurrencyMintBody body)
{
var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
diff --git a/src/IO.Swagger/Controllers/WalletApi.cs b/src/IO.Swagger/Controllers/WalletApi.cs
index 0b22031..6363b1c 100644
--- a/src/IO.Swagger/Controllers/WalletApi.cs
+++ b/src/IO.Swagger/Controllers/WalletApi.cs
@@ -18,6 +18,11 @@ using IO.Swagger.Attributes;
using IO.Swagger.Security;
using Microsoft.AspNetCore.Authorization;
using IO.Swagger.Models.dto;
+using System.Linq;
+using System.Security.Claims;
+using IO.Swagger.Repositories;
+using System.Threading.Tasks;
+using AutoMapper;
namespace IO.Swagger.Controllers
{
@@ -26,26 +31,67 @@ namespace IO.Swagger.Controllers
///
[ApiController]
public class WalletApiController : ControllerBase
- {
+ {
+ private readonly ITransactionRepository transactionRepository;
+ private readonly IMapper mapper;
+
+ public WalletApiController(ITransactionRepository transactionRepository, IMapper mapper)
+ {
+ this.transactionRepository = transactionRepository ?? throw new ArgumentNullException(nameof(transactionRepository));
+ this.mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ }
+
+
+
///
- /// Get user's wallet
+ /// Get user's wallet balances
///
/// Successful response
/// Unauthorized
[HttpGet]
- [Route("/v1/api/wallet")]
+ [Route("/v1/api/wallet/balances")]
[Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)]
[ValidateModelState]
- [SwaggerOperation("GetUserWallet")]
- public virtual IActionResult GetUserWallet()
- {
- //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
- // return StatusCode(200);
+ [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();
- //TODO: Uncomment the next line to return response 401 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
- // return StatusCode(401);
+ var transactions = await transactionRepository.GetTransactionsForUser(userId);
+ var balances = transactions.GroupBy(t => t.Currency)
+ .Select(g => new WalletBalanceDto
+ {
+ Currency = mapper.Map(g.Key),
+ Balance = g.Sum(t =>t.Amount)
+ });
+ return Ok(balances);
+ }
- throw new NotImplementedException();
+ ///
+ /// Get user's wallet transactions
+ ///
+ /// Successful response
+ /// Unauthorized
+ [HttpGet]
+ [Route("/v1/api/wallet/transactions")]
+ [Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)]
+ [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;
+ if (!int.TryParse(userIdString, out int userId))
+ return Unauthorized();
+
+ var transactions = await transactionRepository.GetTransactionsForUser(userId);
+
+ return Ok(transactions.Select(mapper.Map));
}
///
diff --git a/src/IO.Swagger/IO.Swagger.csproj b/src/IO.Swagger/IO.Swagger.csproj
index 854fe09..e956357 100644
--- a/src/IO.Swagger/IO.Swagger.csproj
+++ b/src/IO.Swagger/IO.Swagger.csproj
@@ -9,6 +9,7 @@
IO.Swagger
+
diff --git a/src/IO.Swagger/Models/db/Currency.cs b/src/IO.Swagger/Models/db/Currency.cs
index d820511..6cc3e39 100644
--- a/src/IO.Swagger/Models/db/Currency.cs
+++ b/src/IO.Swagger/Models/db/Currency.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
namespace IO.Swagger.Models.db
{
@@ -17,7 +18,6 @@ namespace IO.Swagger.Models.db
[ForeignKey("FK_Currency_UserId")]
public int UserId { get; set; }
public User User { get; set; }
-
public ICollection Transactions { get; set; }
}
}
diff --git a/src/IO.Swagger/Models/db/Transaction.cs b/src/IO.Swagger/Models/db/Transaction.cs
index 79e3814..eef81d3 100644
--- a/src/IO.Swagger/Models/db/Transaction.cs
+++ b/src/IO.Swagger/Models/db/Transaction.cs
@@ -2,6 +2,7 @@
using Microsoft.VisualBasic;
using System;
using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
namespace IO.Swagger.Models.db
{
@@ -19,7 +20,7 @@ namespace IO.Swagger.Models.db
[Required]
[StringLength(32, MinimumLength = 2)]
public string Memo { get; set; }
-
+
public Currency Currency { get; set; }
public int CurrencyId { get; set; }
diff --git a/src/IO.Swagger/Models/db/User.cs b/src/IO.Swagger/Models/db/User.cs
index acc80c0..4c0b45b 100644
--- a/src/IO.Swagger/Models/db/User.cs
+++ b/src/IO.Swagger/Models/db/User.cs
@@ -1,12 +1,14 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
namespace IO.Swagger.Models.db
{
public class User
{
public int Id { get; set; }
+
public string Email { get; set; }
[StringLength(32, MinimumLength = 3)]
public string FirstName { get; set; }
@@ -14,9 +16,7 @@ namespace IO.Swagger.Models.db
public string LastName { get; set; }
public string PasswordHash { get; set; }
public string Salt { get; set; }
-
public ICollection Currencies { get; set; }
-
public ICollection TransactionsFrom { get; set; }
public ICollection TransactionsTo { get; set; }
}
diff --git a/src/IO.Swagger/Models/dto/CurrencyDto.cs b/src/IO.Swagger/Models/dto/CurrencyDto.cs
new file mode 100644
index 0000000..24038c0
--- /dev/null
+++ b/src/IO.Swagger/Models/dto/CurrencyDto.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace IO.Swagger.Models.dto
+{
+ public class CurrencyDto
+ {
+ public int CurrencyId { get; set; }
+ [StringLength(32, MinimumLength = 1)]
+ public string Name { get; set; }
+ [StringLength(4, MinimumLength = 1)]
+ public string Symbol { get; set; }
+ }
+}
diff --git a/src/IO.Swagger/Models/dto/TokenDto.cs b/src/IO.Swagger/Models/dto/TokenDto.cs
new file mode 100644
index 0000000..11e6f84
--- /dev/null
+++ b/src/IO.Swagger/Models/dto/TokenDto.cs
@@ -0,0 +1,7 @@
+namespace IO.Swagger.Models.dto
+{
+ public class TokenDto
+ {
+ public string Token { get; set; }
+ }
+}
diff --git a/src/IO.Swagger/Models/dto/TransactionDto.cs b/src/IO.Swagger/Models/dto/TransactionDto.cs
new file mode 100644
index 0000000..510994e
--- /dev/null
+++ b/src/IO.Swagger/Models/dto/TransactionDto.cs
@@ -0,0 +1,16 @@
+using Microsoft.SqlServer.Server;
+using Microsoft.VisualBasic;
+using System;
+
+namespace IO.Swagger.Models.dto
+{
+ public class TransactionDto
+ {
+ public UserDto FromUser { get; set; }
+ public UserDto ToUser { get; set; }
+ public float Amount { get; set; }
+ public CurrencyDto Currency { get; set; }
+ public string Memo { get; set; }
+ public DateTime TransactionTime { get; set; }
+ }
+}
diff --git a/src/IO.Swagger/Models/dto/UserDto.cs b/src/IO.Swagger/Models/dto/UserDto.cs
new file mode 100644
index 0000000..8fb115b
--- /dev/null
+++ b/src/IO.Swagger/Models/dto/UserDto.cs
@@ -0,0 +1,13 @@
+using IO.Swagger.Models.db;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace IO.Swagger.Models.dto
+{
+ public class UserDto
+ {
+ public string Email { get; set; }
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ }
+}
diff --git a/src/IO.Swagger/Models/dto/WalletBalanceDto.cs b/src/IO.Swagger/Models/dto/WalletBalanceDto.cs
new file mode 100644
index 0000000..d3f34cf
--- /dev/null
+++ b/src/IO.Swagger/Models/dto/WalletBalanceDto.cs
@@ -0,0 +1,8 @@
+namespace IO.Swagger.Models.dto
+{
+ public class WalletBalanceDto
+ {
+ public CurrencyDto Currency { get; set; }
+ public float Balance { get; set; }
+ }
+}
diff --git a/src/IO.Swagger/Repositories/ITransactionRepository.cs b/src/IO.Swagger/Repositories/ITransactionRepository.cs
new file mode 100644
index 0000000..d0ffe9a
--- /dev/null
+++ b/src/IO.Swagger/Repositories/ITransactionRepository.cs
@@ -0,0 +1,11 @@
+using IO.Swagger.Models.db;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace IO.Swagger.Repositories
+{
+ public interface ITransactionRepository
+ {
+ Task> GetTransactionsForUser(int userId);
+ }
+}
diff --git a/src/IO.Swagger/Repositories/TransactionRepository.cs b/src/IO.Swagger/Repositories/TransactionRepository.cs
new file mode 100644
index 0000000..c0263fc
--- /dev/null
+++ b/src/IO.Swagger/Repositories/TransactionRepository.cs
@@ -0,0 +1,35 @@
+using IO.Swagger.Models.db;
+using IO.Swagger.Services;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace IO.Swagger.Repositories
+{
+ public class TransactionRepository : ITransactionRepository
+ {
+ BankDbContext context;
+
+ public TransactionRepository(BankDbContext context)
+ {
+ this.context = context ?? throw new ArgumentNullException(nameof(context));
+ }
+
+ public async Task> GetTransactionsForUser(int userId)
+ {
+ var transactions = await context.Transactions.Where(t => t.ToUserId == userId || t.FromUserId == userId)
+ .Include(t => t.Currency)
+ .Include(t => t.FromUser)
+ .Include(t => t.ToUser)
+ .OrderByDescending(t => t.TransactionTime)
+ .ToListAsync();
+
+ foreach (var t in transactions)
+ if (t.ToUserId != t.FromUserId && t.FromUserId == userId)
+ t.Amount *= -1;
+ return transactions;
+ }
+ }
+}
diff --git a/src/IO.Swagger/Services/MapperProfile.cs b/src/IO.Swagger/Services/MapperProfile.cs
new file mode 100644
index 0000000..0f885b5
--- /dev/null
+++ b/src/IO.Swagger/Services/MapperProfile.cs
@@ -0,0 +1,16 @@
+using AutoMapper;
+using IO.Swagger.Models.db;
+using IO.Swagger.Models.dto;
+
+namespace IO.Swagger.Services
+{
+ public class MapperProfile : Profile
+ {
+ public MapperProfile()
+ {
+ CreateMap();
+ CreateMap();
+ CreateMap();
+ }
+ }
+}
diff --git a/src/IO.Swagger/Startup.cs b/src/IO.Swagger/Startup.cs
index 530da98..90197ba 100644
--- a/src/IO.Swagger/Startup.cs
+++ b/src/IO.Swagger/Startup.cs
@@ -26,6 +26,7 @@ using IO.Swagger.Security;
using IO.Swagger.Repositories;
using IO.Swagger.Services;
using Microsoft.EntityFrameworkCore;
+using AutoMapper;
namespace IO.Swagger
{
@@ -122,6 +123,14 @@ namespace IO.Swagger
});
});
+ // Auto Mapper Configurations
+ var mapperConfig = new MapperConfiguration(mc =>
+ {
+ mc.AddProfile(new MapperProfile());
+ });
+
+
+ // CORS sucks
services.AddCors(opt => {
opt.AddDefaultPolicy(po =>
{
@@ -132,17 +141,22 @@ namespace IO.Swagger
});
});
+ //Datase connections
string connectionString = Environment.GetEnvironmentVariable("DATABASE_CONNECTION_STRING");
if (string.IsNullOrEmpty(connectionString))
{
throw new Exception("Database connection string not found in environment variable.");
}
- Console.WriteLine(connectionString);
+
+ // DI setup
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddDbContext(x => x.UseSqlServer(connectionString: connectionString));
services.AddSingleton();
+ IMapper mapper = mapperConfig.CreateMapper();
+ services.AddSingleton(mapper);
}
///