From 6f4fb29ae82cbc88d86d2f9863b4f028376fc25b Mon Sep 17 00:00:00 2001 From: William Lewis Date: Tue, 19 Mar 2024 22:39:11 -0500 Subject: [PATCH] Updated the Transaction and Account services and controllers to separate owner entities based on authenticated user. --- .../Controllers/AccountController.cs | 33 +++++++-- .../Controllers/TransactionController.cs | 35 +++++++-- .../Controllers/UserController.cs | 7 +- .../Models/Accounts/AccountCreateRequest.cs | 1 + .../Services/AccountService.cs | 64 ++++++++++------ .../Services/CacheService.cs | 21 +++++- .../Services/TransactionService.cs | 73 +++++++++++-------- .../Services/UserService.cs | 26 +++---- 8 files changed, 175 insertions(+), 85 deletions(-) diff --git a/AAIntegration.SimmonsBank.API/Controllers/AccountController.cs b/AAIntegration.SimmonsBank.API/Controllers/AccountController.cs index ddf1623..6ec6680 100644 --- a/AAIntegration.SimmonsBank.API/Controllers/AccountController.cs +++ b/AAIntegration.SimmonsBank.API/Controllers/AccountController.cs @@ -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 _logger; public AccountsController( IAccountService accountService, + IUserService userService, IMapper mapper, - IOptions appSettings) + IOptions appSettings, + ILogger logger) { _accountService = accountService; + _userService = userService; _mapper = mapper; _appSettings = appSettings.Value; + _logger = logger; } [HttpGet] @@ -34,7 +41,7 @@ public class AccountsController : ControllerBase { List accountDtos = new List(); - foreach (Account acc in _accountService.GetAll()) + foreach (Account acc in _accountService.GetAll(GetCurrentUserId())) accountDtos.Add(_mapper.Map(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)); } [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; + } } \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Controllers/TransactionController.cs b/AAIntegration.SimmonsBank.API/Controllers/TransactionController.cs index 3ae5c74..b8a7383 100644 --- a/AAIntegration.SimmonsBank.API/Controllers/TransactionController.cs +++ b/AAIntegration.SimmonsBank.API/Controllers/TransactionController.cs @@ -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 _logger; public TransactionsController( ITransactionService transactionService, + IUserService userService, IMapper mapper, - IOptions appSettings) + IOptions appSettings, + ILogger logger) { _transactionService = transactionService; + _userService = userService; _mapper = mapper; _appSettings = appSettings.Value; + _logger = logger; } [HttpGet] @@ -35,7 +42,7 @@ public class TransactionsController : ControllerBase { List transactionDtos = new List(); - 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(tran)); } [HttpPost("BulkAdd")] public IActionResult BulkCreate([FromBody]List model) { - List trans = _transactionService.BulkCreate(model).ToList(); + List 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; + } } \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Controllers/UserController.cs b/AAIntegration.SimmonsBank.API/Controllers/UserController.cs index d526dc9..409ee33 100644 --- a/AAIntegration.SimmonsBank.API/Controllers/UserController.cs +++ b/AAIntegration.SimmonsBank.API/Controllers/UserController.cs @@ -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; } diff --git a/AAIntegration.SimmonsBank.API/Models/Accounts/AccountCreateRequest.cs b/AAIntegration.SimmonsBank.API/Models/Accounts/AccountCreateRequest.cs index 71f4285..24cc71e 100644 --- a/AAIntegration.SimmonsBank.API/Models/Accounts/AccountCreateRequest.cs +++ b/AAIntegration.SimmonsBank.API/Models/Accounts/AccountCreateRequest.cs @@ -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; } } \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Services/AccountService.cs b/AAIntegration.SimmonsBank.API/Services/AccountService.cs index 22529c1..8cd6497 100644 --- a/AAIntegration.SimmonsBank.API/Services/AccountService.cs +++ b/AAIntegration.SimmonsBank.API/Services/AccountService.cs @@ -14,11 +14,11 @@ using Microsoft.EntityFrameworkCore; public interface IAccountService { - IEnumerable GetAll(); - Account GetById(int accountId); - void Create(AccountCreateRequest model); - void Update(int accountId, AccountUpdateRequest model); - void Delete(int accountId); + IEnumerable 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 GetAll() + public IEnumerable 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 accountsWithSameName = _context.Accounts.Where(a => a.Name.ToUpper() == model.Name.ToUpper()); + IEnumerable 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 matches = _context.Accounts.Where(a => a.ExternalAccountNumber == model.ExternalAccountNumber); + IEnumerable 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(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; + } } \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Services/CacheService.cs b/AAIntegration.SimmonsBank.API/Services/CacheService.cs index e2ab0fd..7c05a37 100644 --- a/AAIntegration.SimmonsBank.API/Services/CacheService.cs +++ b/AAIntegration.SimmonsBank.API/Services/CacheService.cs @@ -11,29 +11,48 @@ public class CacheService : ICacheService { private readonly IMemoryCache _memoryCache; private readonly IUserService _userService; + private readonly ILogger _logger; - public CacheService(IMemoryCache memoryCache, IUserService userService) + public CacheService(IMemoryCache memoryCache, IUserService userService, ILogger logger) { _memoryCache = memoryCache; _userService = userService; + _logger = logger; } public int GetClientIdFromApiKey(string apiKey) { if (!_memoryCache.TryGetValue>($"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 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) { diff --git a/AAIntegration.SimmonsBank.API/Services/TransactionService.cs b/AAIntegration.SimmonsBank.API/Services/TransactionService.cs index 62e5e16..e4a0f13 100644 --- a/AAIntegration.SimmonsBank.API/Services/TransactionService.cs +++ b/AAIntegration.SimmonsBank.API/Services/TransactionService.cs @@ -15,12 +15,12 @@ using System.Collections.Immutable; public interface ITransactionService { - IEnumerable GetAll(); - Transaction GetById(int id); - IEnumerable BulkCreate(List model); - Transaction Create(TransactionCreate model, bool errorOnFail = true); - void Update(int id, TransactionUpdateRequest model); - void Delete(int id); + IEnumerable GetAll(int ownerId); + Transaction GetById(int id, int ownerId); + IEnumerable BulkCreate(List 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 GetAll() + public IEnumerable 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 BulkCreate(List model) + public IEnumerable BulkCreate(List model, int ownerId) { List transactions = new List(); 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; + } } \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Services/UserService.cs b/AAIntegration.SimmonsBank.API/Services/UserService.cs index cc1c3d6..16d0774 100644 --- a/AAIntegration.SimmonsBank.API/Services/UserService.cs +++ b/AAIntegration.SimmonsBank.API/Services/UserService.cs @@ -23,6 +23,7 @@ public interface IUserService void Update(string apiKey, UserUpdateRequest model); void Delete(string apiKey); Dictionary 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; }