Updated the Transaction and Account services and controllers to separate owner entities based on authenticated user.

This commit is contained in:
William Lewis 2024-03-19 22:39:11 -05:00
parent dee572f013
commit 6f4fb29ae8
8 changed files with 175 additions and 85 deletions

View File

@ -9,6 +9,7 @@ using AAIntegration.SimmonsBank.API.Config;
using System.Collections.Generic;
using AAIntegration.SimmonsBank.API.Entities;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
[Authorize]
[ApiController]
@ -16,17 +17,23 @@ using Microsoft.AspNetCore.Authorization;
public class AccountsController : ControllerBase
{
private IAccountService _accountService;
private IUserService _userService;
private IMapper _mapper;
private readonly AppSettings _appSettings;
private readonly ILogger<AccountsController> _logger;
public AccountsController(
IAccountService accountService,
IUserService userService,
IMapper mapper,
IOptions<AppSettings> appSettings)
IOptions<AppSettings> appSettings,
ILogger<AccountsController> logger)
{
_accountService = accountService;
_userService = userService;
_mapper = mapper;
_appSettings = appSettings.Value;
_logger = logger;
}
[HttpGet]
@ -34,7 +41,7 @@ public class AccountsController : ControllerBase
{
List<AccountDTO> accountDtos = new List<AccountDTO>();
foreach (Account acc in _accountService.GetAll())
foreach (Account acc in _accountService.GetAll(GetCurrentUserId()))
accountDtos.Add(_mapper.Map<Account, AccountDTO>(acc));
return Ok(accountDtos);
@ -43,28 +50,42 @@ public class AccountsController : ControllerBase
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
Account account = _accountService.GetById(id);
Account account = _accountService.GetById(id, GetCurrentUserId());
return Ok(_mapper.Map<Account, AccountDTO>(account));
}
[HttpPost]
public IActionResult Create([FromBody]AccountCreateRequest model)
{
_accountService.Create(model);
_accountService.Create(model, GetCurrentUserId());
return Ok(new { message = "account created" });
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody]AccountUpdateRequest model)
{
_accountService.Update(id, model);
_accountService.Update(id, model, GetCurrentUserId());
return Ok(new { message = "account updated" });
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_accountService.Delete(id);
_accountService.Delete(id, GetCurrentUserId());
return Ok(new { message = "account deleted" });
}
// Helpers
private int GetCurrentUserId()
{
string apiKey = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (apiKey is null)
_logger.LogInformation($"ApiKey: is null");
_logger.LogInformation($"apiKey: {apiKey}");
return _userService.GetUser(apiKey).Id;
}
}

View File

@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
using AAIntegration.SimmonsBank.API.Entities;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
[Authorize]
[ApiController]
@ -17,17 +18,23 @@ using Microsoft.AspNetCore.Authorization;
public class TransactionsController : ControllerBase
{
private ITransactionService _transactionService;
private IUserService _userService;
private IMapper _mapper;
private readonly AppSettings _appSettings;
private readonly ILogger<TransactionsController> _logger;
public TransactionsController(
ITransactionService transactionService,
IUserService userService,
IMapper mapper,
IOptions<AppSettings> appSettings)
IOptions<AppSettings> appSettings,
ILogger<TransactionsController> logger)
{
_transactionService = transactionService;
_userService = userService;
_mapper = mapper;
_appSettings = appSettings.Value;
_logger = logger;
}
[HttpGet]
@ -35,7 +42,7 @@ public class TransactionsController : ControllerBase
{
List<TransactionDto> transactionDtos = new List<TransactionDto>();
foreach (Transaction tran in _transactionService.GetAll())
foreach (Transaction tran in _transactionService.GetAll(this.GetCurrentUserId()))
{
if (accountId.HasValue
&& (tran.DebitAccount == null || tran.DebitAccount.Id != accountId)
@ -54,35 +61,49 @@ public class TransactionsController : ControllerBase
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
Transaction tran = _transactionService.GetById(id);
Transaction tran = _transactionService.GetById(id, this.GetCurrentUserId());
return Ok(_mapper.Map<Transaction, TransactionDto>(tran));
}
[HttpPost("BulkAdd")]
public IActionResult BulkCreate([FromBody]List<TransactionCreate> model)
{
List<Transaction> trans = _transactionService.BulkCreate(model).ToList();
List<Transaction> trans = _transactionService.BulkCreate(model, this.GetCurrentUserId()).ToList();
return Ok(new { message = $"{trans.Count()} transaction(s) created." });
}
[HttpPost]
public IActionResult Create([FromBody]TransactionCreate model)
{
Transaction tran = _transactionService.Create(model);
Transaction tran = _transactionService.Create(model, this.GetCurrentUserId());
return Ok(new { message = $"transaction '{tran.Description}' created with id '{tran.Id}'." });
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody]TransactionUpdateRequest model)
{
_transactionService.Update(id, model);
_transactionService.Update(id, model, this.GetCurrentUserId());
return Ok(new { message = $"transaction with id '{id}' updated" });
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_transactionService.Delete(id);
_transactionService.Delete(id, this.GetCurrentUserId());
return Ok(new { message = "transaction deleted" });
}
// Helpers
private int GetCurrentUserId()
{
string apiKey = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (apiKey is null)
_logger.LogInformation($"ApiKey: is null");
_logger.LogInformation($"apiKey: {apiKey}");
return _userService.GetUser(apiKey).Id;
}
}

View File

@ -39,7 +39,7 @@ public class UsersController : ControllerBase
}
[Authorize]
[HttpPut("{id}")]
[HttpPut]
public IActionResult Update([FromBody]UserUpdateRequest model)
{
_userService.Update(this.GetCurrentUserApiKey(), model);
@ -47,8 +47,8 @@ public class UsersController : ControllerBase
}
[Authorize]
[HttpDelete("{id}")]
public IActionResult Delete(int id)
[HttpDelete]
public IActionResult Delete()
{
_userService.Delete(this.GetCurrentUserApiKey());
return Ok(new { message = "User deleted" });
@ -64,7 +64,6 @@ public class UsersController : ControllerBase
_logger.LogInformation($"ApiKey: is null");
_logger.LogInformation($"apiKey: {apiKey}");
Console.WriteLine($"User Id: " + apiKey);
return apiKey;
}

View File

@ -10,4 +10,5 @@ public class AccountCreateRequest
public string InitialBalance { get; set; }
public int Currency { get; set; }
public string ExternalAccountNumber { get; set; }
public int Owner { get; set; }
}

View File

@ -14,11 +14,11 @@ using Microsoft.EntityFrameworkCore;
public interface IAccountService
{
IEnumerable<Account> GetAll();
Account GetById(int accountId);
void Create(AccountCreateRequest model);
void Update(int accountId, AccountUpdateRequest model);
void Delete(int accountId);
IEnumerable<Account> GetAll(int ownerId);
Account GetById(int accountId, int ownerId);
void Create(AccountCreateRequest model, int ownerId);
void Update(int accountId, AccountUpdateRequest model, int ownerId);
void Delete(int accountId, int ownerId);
}
public class AccountService : IAccountService
@ -37,51 +37,57 @@ public class AccountService : IAccountService
_userService = userService;
}
public IEnumerable<Account> GetAll()
public IEnumerable<Account> GetAll(int ownerId)
{
return _context.Accounts;
return _context.Accounts
.Include(x => x.Owner)
.Where(x => x.Owner.Id == ownerId);
}
public Account GetById(int accountId)
public Account GetById(int accountId, int ownerId)
{
return getAccount(accountId);
return getAccount(accountId, ownerId);
}
public void Create(AccountCreateRequest model)
public void Create(AccountCreateRequest model, int ownerId)
{
// Check that account with same name or same external number doesn't exist
IEnumerable<Account> accountsWithSameName = _context.Accounts.Where(a => a.Name.ToUpper() == model.Name.ToUpper());
IEnumerable<Account> accountsWithSameName = _context.Accounts
.Include(x => x.Owner)
.Where(x => x.Name.ToUpper() == model.Name.ToUpper() && x.Owner.Id == ownerId);
if (accountsWithSameName.Count() > 0)
throw new AppException("Account with name '" + model.Name + "' already exists");
if (!string.IsNullOrWhiteSpace(model.ExternalAccountNumber))
{
IEnumerable<Account> matches = _context.Accounts.Where(a => a.ExternalAccountNumber == model.ExternalAccountNumber);
IEnumerable<Account> matches = _context.Accounts
.Include(x => x.Owner)
.Where(x => x.ExternalAccountNumber == model.ExternalAccountNumber && x.Owner.Id == ownerId);
if (matches.Count() > 0)
throw new AppException("Account with external account number '" + model.ExternalAccountNumber + "' already exists under account named '" + matches.First().Name + "'");
}
// map model to new account object
//var account = _mapper.Map<Account>(model);
Account account = new Account {
Name = model.Name,
Balance = Convert.ToDecimal(model.InitialBalance),
ExternalAccountNumber = model.ExternalAccountNumber
ExternalAccountNumber = model.ExternalAccountNumber,
Owner = getOwner(ownerId)
};
_context.Accounts.Add(account);
_context.SaveChanges();
}
public void Update(int accountId, AccountUpdateRequest model)
public void Update(int accountId, AccountUpdateRequest model, int ownerId)
{
Account account = getAccount(accountId);
Account account = getAccount(accountId, ownerId);
// validate
if (model.Name != account.Name && _context.Accounts.Any(x => x.Name == model.Name))
if (model.Name != account.Name && _context.Accounts
.Include(x => x.Owner)
.Any(x => x.Name == model.Name && x.Owner.Id == ownerId))
throw new AppException("Account with the name '" + model.Name + "' already exists");
// Name
@ -96,19 +102,31 @@ public class AccountService : IAccountService
_context.SaveChanges();
}
public void Delete(int accountId)
public void Delete(int accountId, int ownerId)
{
var account = getAccount(accountId);
var account = getAccount(accountId, ownerId);
_context.Accounts.Remove(account);
_context.SaveChanges();
}
// helper methods
private Account getAccount(int id)
private Account getAccount(int id, int ownerId)
{
var account = _context.Accounts.Find(id);
var account = _context.Accounts
.Include(x => x.Owner)
.FirstOrDefault(x => x.Id == id && x.Owner.Id == ownerId);
if (account == null) throw new KeyNotFoundException("Account not found");
return account;
}
private User getOwner(int ownerId)
{
User? owner = _context.Users.Find(ownerId);
if (owner == null)
throw new AppException($"Owner with ID of '{ownerId}' could not be found");
return owner;
}
}

View File

@ -11,29 +11,48 @@ public class CacheService : ICacheService
{
private readonly IMemoryCache _memoryCache;
private readonly IUserService _userService;
private readonly ILogger<ICacheService> _logger;
public CacheService(IMemoryCache memoryCache, IUserService userService)
public CacheService(IMemoryCache memoryCache, IUserService userService, ILogger<ICacheService> logger)
{
_memoryCache = memoryCache;
_userService = userService;
_logger = logger;
}
public int GetClientIdFromApiKey(string apiKey)
{
if (!_memoryCache.TryGetValue<Dictionary<string, int>>($"Authentication_ApiKeys", out var internalKeys))
{
_logger.LogInformation($"Could not find API key '{apiKey}' in cache.");
internalKeys = _userService.GetAllApiKeys();
_logger.LogInformation("Updated cache with new key list.");
PrintInternalKeys(internalKeys);
_memoryCache.Set($"Authentication_ApiKeys", internalKeys);
}
if (!internalKeys.TryGetValue(apiKey, out var clientId))
{
_logger.LogInformation("Could not find API key. Returning -1.");
return -1;
}
return clientId;
}
// helpers
private void PrintInternalKeys(Dictionary<string, int> keys)
{
string msg = "API Keys (dev only - needs to be moved to dev only)";
foreach (var item in keys)
msg += $"\n\t{item.Value} : {item.Key}";
_logger.LogInformation(msg);
}
/*public void InvalidateApiKey(string apiKey)
{

View File

@ -15,12 +15,12 @@ using System.Collections.Immutable;
public interface ITransactionService
{
IEnumerable<Transaction> GetAll();
Transaction GetById(int id);
IEnumerable<Transaction> BulkCreate(List<TransactionCreate> model);
Transaction Create(TransactionCreate model, bool errorOnFail = true);
void Update(int id, TransactionUpdateRequest model);
void Delete(int id);
IEnumerable<Transaction> GetAll(int ownerId);
Transaction GetById(int id, int ownerId);
IEnumerable<Transaction> BulkCreate(List<TransactionCreate> model, int ownerId);
Transaction Create(TransactionCreate model, int ownerId, bool errorOnFail = true);
void Update(int id, TransactionUpdateRequest model, int ownerId);
void Delete(int id, int ownerId);
}
public class TransactionService : ITransactionService
@ -39,17 +39,18 @@ public class TransactionService : ITransactionService
_logger = logger;
}
public IEnumerable<Transaction> GetAll()
public IEnumerable<Transaction> GetAll(int ownerId)
{
return _context.Transactions
.Include(t => t.DebitAccount)
.Include(t => t.CreditAccount)
.ToList();
.Include(t => t.Owner)
.Where(x => x.Owner.Id == ownerId);
}
public Transaction GetById(int id)
public Transaction GetById(int id, int ownerId)
{
return getTransaction(id);
return getTransaction(id, ownerId);
}
private Account prepareAccount(int? accountId)
@ -66,13 +67,13 @@ public class TransactionService : ITransactionService
return account;
}
public IEnumerable<Transaction> BulkCreate(List<TransactionCreate> model)
public IEnumerable<Transaction> BulkCreate(List<TransactionCreate> model, int ownerId)
{
List<Transaction> transactions = new List<Transaction>();
foreach (TransactionCreate tr in model)
{
var tran = this.Create(tr, false);
var tran = this.Create(tr, ownerId, false);
if (tran != null)
transactions.Add(tran);
}
@ -80,7 +81,7 @@ public class TransactionService : ITransactionService
return transactions;
}
public Transaction Create(TransactionCreate model, bool errorOnFail = true)
public Transaction Create(TransactionCreate model, int ownerId, bool errorOnFail = true)
{
Transaction transaction = new Transaction {
Description = model.Description,
@ -91,10 +92,11 @@ public class TransactionService : ITransactionService
DebitAccount = prepareAccount(model.DebitAccount),
CreditAccount = prepareAccount(model.CreditAccount),
Amount = Convert.ToDecimal(model.Amount),
Owner = this.getOwner(ownerId),
IsPending = model.IsPending
};
if (this.ValidateTransaction(transaction, errorOnFail) == false)
if (this.ValidateTransaction(transaction, ownerId, errorOnFail) == false)
{
_logger.LogInformation($"Aborted adding transaction '{transaction.Description}'.");
return null;
@ -110,9 +112,9 @@ public class TransactionService : ITransactionService
return transaction;
}
public void Update(int id, TransactionUpdateRequest model)
public void Update(int id, TransactionUpdateRequest model, int ownerId)
{
Transaction transaction = getTransaction(id);
Transaction transaction = getTransaction(id, ownerId);
// Transaction.Date
if (model.Date.HasValue)
@ -142,7 +144,7 @@ public class TransactionService : ITransactionService
if (model.IsPending.HasValue)
transaction.IsPending = model.IsPending.Value;
this.ValidateTransaction(transaction);
this.ValidateTransaction(transaction, ownerId);
transaction.UpdatedOn = DateTime.UtcNow;
@ -152,11 +154,15 @@ public class TransactionService : ITransactionService
_logger.LogInformation($"Transaction '{id}' successfully updated.");
}
private bool ValidateTransaction(int transactionId)
public void Delete(int id, int ownerId)
{
return this.ValidateTransaction(getTransaction(transactionId));
var transaction = getTransaction(id, ownerId);
_context.Transactions.Remove(transaction);
_context.SaveChanges();
}
// helpers
private bool ErrorOrFalse(bool error, string errorMessage)
{
if (error)
@ -166,7 +172,7 @@ public class TransactionService : ITransactionService
return false;
}
private bool ValidateTransaction(Transaction transaction, bool errorOnFail = true)
private bool ValidateTransaction(Transaction transaction, int ownerId, bool errorOnFail = true)
{
// There has to be at least 1 specified account
if (transaction.DebitAccount == null && transaction.CreditAccount == null)
@ -178,7 +184,10 @@ public class TransactionService : ITransactionService
return ErrorOrFalse(errorOnFail, "The debit and credit accounts of a transaction cannot be the same.");
// Transaction Duplication Check - External ID
if (!string.IsNullOrWhiteSpace(transaction.ExternalId) && _context.Transactions.Any(x => x.ExternalId == transaction.ExternalId))
if (!string.IsNullOrWhiteSpace(transaction.ExternalId)
&& _context.Transactions
.Include(x => x.Owner)
.Any(x => x.ExternalId == transaction.ExternalId && x.Owner.Id == ownerId))
return ErrorOrFalse(errorOnFail, "Transaction with the external ID '" + transaction.ExternalId + "' already exists");
// Transaction Duplication Check - All other fields
@ -195,23 +204,27 @@ public class TransactionService : ITransactionService
return true;
}
public void Delete(int id)
{
var transaction = getTransaction(id);
_context.Transactions.Remove(transaction);
_context.SaveChanges();
}
private Transaction getTransaction(int id)
private Transaction getTransaction(int id, int ownerId)
{
var transaction = _context.Transactions
.Include(t => t.DebitAccount)
.Include(t => t.CreditAccount)
.FirstOrDefault(t => t.Id == id);
.Include(t => t.Owner)
.FirstOrDefault(t => t.Id == id && t.Owner.Id == ownerId);
if (transaction == null)
throw new KeyNotFoundException("Transaction not found");
return transaction;
}
private User getOwner(int ownerId)
{
User? owner = _context.Users.Find(ownerId);
if (owner == null)
throw new AppException($"Owner with ID of '{ownerId}' could not be found");
return owner;
}
}

View File

@ -23,6 +23,7 @@ public interface IUserService
void Update(string apiKey, UserUpdateRequest model);
void Delete(string apiKey);
Dictionary<string, int> GetAllApiKeys();
User GetUser(string ApiKey);
/* Other cringe way
AuthenticateResponse Authenticate(AuthenticateRequest model);
@ -74,7 +75,7 @@ public class UserService : IUserService
public void Update(string apiKey, UserUpdateRequest model)
{
var user = getUser(apiKey);
var user = this.GetUser(apiKey);
// User.Username
if (model.Username != null)
@ -94,7 +95,7 @@ public class UserService : IUserService
public void Delete(string apiKey)
{
var user = getUser(apiKey);
var user = this.GetUser(apiKey);
_context.Users.Remove(user);
_context.SaveChanges();
}
@ -106,23 +107,20 @@ public class UserService : IUserService
.ToDictionary(u => u.ApiKey, u => u.Id);
}
public User GetUser(string ApiKey)
{
var user = _context.Users
.Where(u => u.ApiKey == ApiKey)
.FirstOrDefault() ?? throw new KeyNotFoundException("User not found");
return user;
}
// helper methods
private User getUser(int id)
{
var user = _context.Users.Find(id);
if (user == null) throw new KeyNotFoundException("User not found");
return user;
}
private User getUser(string ApiKey)
{
var user = _context.Users
.Where(u => u.ApiKey == ApiKey)
.FirstOrDefault();
if (user == null) throw new KeyNotFoundException("User not found");
var user = _context.Users.Find(id) ?? throw new KeyNotFoundException("User not found");
return user;
}