diff --git a/AAIntegration.SimmonsBank.API/Controllers/AccountController.cs b/AAIntegration.SimmonsBank.API/Controllers/AccountController.cs index bcb047e..edf8959 100644 --- a/AAIntegration.SimmonsBank.API/Controllers/AccountController.cs +++ b/AAIntegration.SimmonsBank.API/Controllers/AccountController.cs @@ -1,42 +1,30 @@ namespace AAIntegration.SimmonsBank.API.Controllers; -using AutoMapper; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using AAIntegration.SimmonsBank.API.Models.Accounts; using AAIntegration.SimmonsBank.API.Services; -using AAIntegration.SimmonsBank.API.Config; using System.Collections.Generic; using AAIntegration.SimmonsBank.API.Entities; using Microsoft.AspNetCore.Authorization; using System.Security.Claims; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; +using AAIntegration.SimmonsBank.API.Models.Transactions; [Authorize] [ApiController] [Route("[controller]")] public class AccountsController : ControllerBase { - private IAccountService _accountService; private IUserService _userService; - private IMapper _mapper; - private readonly AppSettings _appSettings; private readonly ILogger _logger; private IPuppeteerService _puppeteerService; public AccountsController( - IAccountService accountService, IUserService userService, - IMapper mapper, - IOptions appSettings, ILogger logger, IPuppeteerService puppeteerService) { - _accountService = accountService; _userService = userService; - _mapper = mapper; - _appSettings = appSettings.Value; _logger = logger; _puppeteerService = puppeteerService; } @@ -44,56 +32,35 @@ public class AccountsController : ControllerBase [HttpGet] public async Task GetAllAsync() { - List accounts = await _puppeteerService.GetAccounts(_userService.GetUser(User.FindFirstValue(ClaimTypes.NameIdentifier))); - + List accounts = await _puppeteerService.GetAccounts(GetCurrentUser()); return Ok(accounts); - /*List accountDtos = new List(); - - foreach (Account acc in _accountService.GetAll(GetCurrentUserId())) - accountDtos.Add(_mapper.Map(acc)); - - return Ok(accountDtos);*/ } - [HttpGet("{id}")] - public IActionResult GetById(int id) + [HttpGet("{account_guid}")] + public async Task GetByGUIDAsync(string account_guid) { - Account account = _accountService.GetById(id, GetCurrentUserId()); - return Ok(_mapper.Map(account)); + return Ok(await GetAccountAsync(account_guid)); } - [HttpPost] - public IActionResult Create([FromBody]AccountCreateRequest model) + [HttpGet("{account_guid}/transactions")] + public async Task GetTransactionsAsync(string account_guid, uint offset = 0, uint limit = 500) { - _accountService.Create(model, GetCurrentUserId()); - return Ok(new { message = "account created" }); - } - - [HttpPut("{id}")] - public IActionResult Update(int id, [FromBody]AccountUpdateRequest model) - { - _accountService.Update(id, model, GetCurrentUserId()); - return Ok(new { message = "account updated" }); - } - - [HttpDelete("{id}")] - public IActionResult Delete(int id) - { - _accountService.Delete(id, GetCurrentUserId()); - return Ok(new { message = "account deleted" }); + List transactions = await _puppeteerService.GetTransactions(GetCurrentUser(), (await GetAccountAsync(account_guid)).Id, offset, limit); + return Ok(transactions); } // Helpers - private int GetCurrentUserId() + private async Task GetAccountAsync(string account_guid) + { + List accounts = await _puppeteerService.GetAccounts(GetCurrentUser()); + AccountDTO account = accounts.FirstOrDefault(a => a.Id == account_guid) ?? throw new KeyNotFoundException("Account not found"); + return account; + } + + private User GetCurrentUser() { string apiKey = User.FindFirstValue(ClaimTypes.NameIdentifier); - - if (apiKey is null) - _logger.LogInformation($"ApiKey: is null"); - - _logger.LogInformation($"apiKey: {apiKey}"); - - return _userService.GetUser(apiKey).Id; + return _userService.GetUser(apiKey); } } \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Controllers/TransactionController.cs b/AAIntegration.SimmonsBank.API/Controllers/TransactionController.cs deleted file mode 100644 index b8a7383..0000000 --- a/AAIntegration.SimmonsBank.API/Controllers/TransactionController.cs +++ /dev/null @@ -1,109 +0,0 @@ -namespace AAIntegration.SimmonsBank.API.Controllers; - -using AutoMapper; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using AAIntegration.SimmonsBank.API.Models.Transactions; -using AAIntegration.SimmonsBank.API.Services; -using AAIntegration.SimmonsBank.API.Config; -using System.Runtime.InteropServices; -using AAIntegration.SimmonsBank.API.Entities; -using System.Collections.Generic; -using Microsoft.AspNetCore.Authorization; -using System.Security.Claims; - -[Authorize] -[ApiController] -[Route("[controller]")] -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, - ILogger logger) - { - _transactionService = transactionService; - _userService = userService; - _mapper = mapper; - _appSettings = appSettings.Value; - _logger = logger; - } - - [HttpGet] - public IActionResult GetAll(int? accountId = null) - { - List transactionDtos = new List(); - - foreach (Transaction tran in _transactionService.GetAll(this.GetCurrentUserId())) - { - if (accountId.HasValue - && (tran.DebitAccount == null || tran.DebitAccount.Id != accountId) - && (tran.CreditAccount == null || tran.CreditAccount.Id != accountId)) - continue; - - transactionDtos.Add(_mapper.Map(tran)); - } - - // Sort by Date - transactionDtos.Sort((t1, t2) => t2.Date.CompareTo(t1.Date)); - - return Ok(transactionDtos); - } - - [HttpGet("{id}")] - public IActionResult GetById(int 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, this.GetCurrentUserId()).ToList(); - return Ok(new { message = $"{trans.Count()} transaction(s) created." }); - } - - [HttpPost] - public IActionResult Create([FromBody]TransactionCreate 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, this.GetCurrentUserId()); - return Ok(new { message = $"transaction with id '{id}' updated" }); - } - - [HttpDelete("{id}")] - public IActionResult Delete(int 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 409ee33..72c80b7 100644 --- a/AAIntegration.SimmonsBank.API/Controllers/UserController.cs +++ b/AAIntegration.SimmonsBank.API/Controllers/UserController.cs @@ -58,13 +58,6 @@ public class UsersController : ControllerBase private string GetCurrentUserApiKey() { - string apiKey = User.FindFirstValue(ClaimTypes.NameIdentifier); - - if (apiKey is null) - _logger.LogInformation($"ApiKey: is null"); - - _logger.LogInformation($"apiKey: {apiKey}"); - - return apiKey; + return User.FindFirstValue(ClaimTypes.NameIdentifier); } } \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Entities/Account.cs b/AAIntegration.SimmonsBank.API/Entities/Account.cs deleted file mode 100644 index cf4bb8b..0000000 --- a/AAIntegration.SimmonsBank.API/Entities/Account.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AAIntegration.SimmonsBank.API.Entities; - -public class Account -{ - public int Id { get; set; } - public string Name { get; set; } - public decimal Balance { get; set; } - public string ExternalAccountNumber { get; set; } - public User Owner { get; set; } -} \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Entities/Transaction.cs b/AAIntegration.SimmonsBank.API/Entities/Transaction.cs deleted file mode 100644 index 68f527b..0000000 --- a/AAIntegration.SimmonsBank.API/Entities/Transaction.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using AAIntegration.SimmonsBank.API.Services; - -namespace AAIntegration.SimmonsBank.API.Entities; - -public class Transaction -{ - public int Id { get; set; } - public DateTime Date { get; set; } - public DateTime CreatedOn { get; set; } - public DateTime UpdatedOn { get; set; } - public string ExternalId { get; set; } - public string Description { get; set; } - public Account? DebitAccount { get; set; } - public Account? CreditAccount { get; set; } - public decimal Amount { get; set; } - public bool IsPending { get; set; } - public User Owner { get; set; } -} \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Helpers/AutoMapperProfile.cs b/AAIntegration.SimmonsBank.API/Helpers/AutoMapperProfile.cs index 39e63a4..d3e2e9e 100644 --- a/AAIntegration.SimmonsBank.API/Helpers/AutoMapperProfile.cs +++ b/AAIntegration.SimmonsBank.API/Helpers/AutoMapperProfile.cs @@ -3,14 +3,9 @@ namespace AAIntegration.SimmonsBank.API.Config; using AutoMapper; using AAIntegration.SimmonsBank.API.Entities; using AAIntegration.SimmonsBank.API.Models.Users; -using AAIntegration.SimmonsBank.API.Models.Accounts; -using AAIntegration.SimmonsBank.API.Services; -using System.Runtime.Serialization; -using AAIntegration.SimmonsBank.API.Models.Transactions; public class AutoMapperProfile : Profile { - public AutoMapperProfile() { // UserUpdateRequest -> User CreateMap() @@ -26,55 +21,5 @@ public class AutoMapperProfile : Profile return true; } )); - - // AccountUpdateRequest -> Account - CreateMap(); - - // AccountCreateRequest -> Account - CreateMap(); - /*.ForMember( - dest => dest.OwnerId, - opt => opt.MapFrom(src => src.Owner) - ); - /*.ForAllMembers(x => x.Condition( - (src, dest, prop) => - { - // ignore both null & empty string properties - if (prop == null) return false; - if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false; - - return true; - } - ))*/ - - // Account -> AccountGet - CreateMap() - .ForAllMembers(x => x.Condition( - (src, dest, prop) => - { - // ignore both null & empty string properties - if (prop == null) return false; - if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false; - - return true; - } - )); - - - // Transaction -> TransactionDto - CreateMap() - .ForMember(dest => dest.DebitAccountId, opt => opt.MapFrom(src => src.DebitAccount.Id)) - .ForMember(dest => dest.CreditAccountId, opt => opt.MapFrom(src => src.CreditAccount.Id)) - .ForAllMembers(x => x.Condition( - (src, dest, prop) => - { - // ignore both null & empty string properties - if (prop == null) return false; - if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false; - - return true; - } - )); - } } \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Helpers/DataContext.cs b/AAIntegration.SimmonsBank.API/Helpers/DataContext.cs index 2e4ebd1..1e82cd5 100644 --- a/AAIntegration.SimmonsBank.API/Helpers/DataContext.cs +++ b/AAIntegration.SimmonsBank.API/Helpers/DataContext.cs @@ -31,6 +31,4 @@ public class DataContext : DbContext }*/ public DbSet Users { get; set; } - public DbSet Accounts { get; set; } - public DbSet Transactions { get; set; } } \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Models/Accounts/AccountCreateRequest.cs b/AAIntegration.SimmonsBank.API/Models/Accounts/AccountCreateRequest.cs deleted file mode 100644 index 71f4285..0000000 --- a/AAIntegration.SimmonsBank.API/Models/Accounts/AccountCreateRequest.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace AAIntegration.SimmonsBank.API.Models.Accounts; - -using System.ComponentModel.DataAnnotations; -using System.Runtime.InteropServices; -using AAIntegration.SimmonsBank.API.Entities; - -public class AccountCreateRequest -{ - public string Name { get; set; } - public string InitialBalance { get; set; } - public int Currency { get; set; } - public string ExternalAccountNumber { get; set; } -} \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Models/Accounts/AccountDTO.cs b/AAIntegration.SimmonsBank.API/Models/Accounts/AccountDTO.cs index c20e588..6a6915f 100644 --- a/AAIntegration.SimmonsBank.API/Models/Accounts/AccountDTO.cs +++ b/AAIntegration.SimmonsBank.API/Models/Accounts/AccountDTO.cs @@ -12,4 +12,8 @@ public class AccountDTO public string Numbers { get; set; } public decimal? Balance { get; set; } public decimal? AvailableBalance { get; set; } + public string AccountType { get; set; } + public string AccountSubType { get; set; } + public DateTime? PaymentDueDate { get; set; } + public decimal? PaymentDueAmount { get; set; } } \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Models/Accounts/AccountUpdateRequest.cs b/AAIntegration.SimmonsBank.API/Models/Accounts/AccountUpdateRequest.cs deleted file mode 100644 index 5a6f983..0000000 --- a/AAIntegration.SimmonsBank.API/Models/Accounts/AccountUpdateRequest.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace AAIntegration.SimmonsBank.API.Models.Accounts; - -public class AccountUpdateRequest -{ - public string? Name { get; set; } = null; - public string? Balance { get; set; } = null; - public string? ExternalAccountNumber { get; set; } = null; -} \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionCreate.cs b/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionCreate.cs deleted file mode 100644 index b5ceda3..0000000 --- a/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionCreate.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using System.Runtime; -using AAIntegration.SimmonsBank.API.Services; - -namespace AAIntegration.SimmonsBank.API.Models.Transactions; - -public class TransactionCreate -{ - public DateTime Date { get; set; } - public string ExternalId { get; set; } - public string Description { get; set; } - public int? DebitAccount { get; set; } - public int? CreditAccount { get; set; } - public decimal Amount { get; set; } - public bool IsPending { get; set; } -} \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionDTO.cs b/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionDTO.cs new file mode 100644 index 0000000..04031d8 --- /dev/null +++ b/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionDTO.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Runtime; +using AAIntegration.SimmonsBank.API.Services; + +namespace AAIntegration.SimmonsBank.API.Models.Transactions; + +public class TransactionDTO +{ + public string Id { get; set; } + public string AccountId { get; set; } + public string Type { get; set; } + public decimal? Amount { get; set; } + public decimal? RunningBalance { get; set; } + public DateTime? DatePosted { get; set; } + public DateTime? Date { get; set; } + public DateTime? LastUpdated { get; set; } + public string PendingStatus { get; set; } + public string Memo { get; set; } + public string FilteredMemo { get; set; } + public string DisplayName { get; set; } +} \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionDto.cs b/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionDto.cs deleted file mode 100644 index 3315364..0000000 --- a/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionDto.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using System.Runtime; -using AAIntegration.SimmonsBank.API.Services; - -namespace AAIntegration.SimmonsBank.API.Models.Transactions; - -public class TransactionDto -{ - public int Id { get; set; } - public DateTime Date { get; set; } - public DateTime CreatedOn { get; set; } - public DateTime UpdatedOn { get; set; } - public string ExternalId { get; set; } - public string Description { get; set; } - public int DebitAccountId { get; set; } - public int CreditAccountId { get; set; } - public decimal Amount { get; set; } - public bool IsPending { get; set; } -} \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionUpdateRequest.cs b/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionUpdateRequest.cs deleted file mode 100644 index c926ff5..0000000 --- a/AAIntegration.SimmonsBank.API/Models/Transactions/TransactionUpdateRequest.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using AAIntegration.SimmonsBank.API.Entities; - -namespace AAIntegration.SimmonsBank.API.Models.Transactions; - -public class TransactionUpdateRequest -{ - public DateTime? Date { get; set; } = null; - public string? ExternalId { get; set; } = null; - public string? Description { get; set; } = null; - public int? DebitAccount { get; set; } = null; - public int? CreditAccount { get; set; } = null; - public decimal? Amount { get; set; } = null; - public bool? IsPending { get; set; } = null; -} \ No newline at end of file diff --git a/AAIntegration.SimmonsBank.API/Processes/PuppeteerProcess.cs b/AAIntegration.SimmonsBank.API/Processes/PuppeteerProcess.cs index f48ae9d..945df93 100644 --- a/AAIntegration.SimmonsBank.API/Processes/PuppeteerProcess.cs +++ b/AAIntegration.SimmonsBank.API/Processes/PuppeteerProcess.cs @@ -46,16 +46,16 @@ public class PuppeteerProcess : IPuppeteerProcess public async Task StayLoggedIn(User user) { - _logger.LogInformation($"... doing work and processing for user {user.Id} ..."); + string prefix = $"Task::StayLoggedIn - {user.Id} - "; if (!await _puppeteerService.IsLoggedIn(user, _stoppingToken)) { - _logger.LogInformation("User determined to not be logged in"); + _logger.LogInformation(prefix + "User is not logged in"); await _puppeteerService.Login(user, _stoppingToken); } else { - _logger.LogInformation("User is already logged in"); + _logger.LogInformation(prefix + "User is still logged in"); } } diff --git a/AAIntegration.SimmonsBank.API/Program.cs b/AAIntegration.SimmonsBank.API/Program.cs index 9ec572f..21a435d 100644 --- a/AAIntegration.SimmonsBank.API/Program.cs +++ b/AAIntegration.SimmonsBank.API/Program.cs @@ -80,8 +80,6 @@ internal class Program opt.UseNpgsql(dbConfig.GetConnectionString())); builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/AAIntegration.SimmonsBank.API/Services/AccountService.cs b/AAIntegration.SimmonsBank.API/Services/AccountService.cs deleted file mode 100644 index 8cd6497..0000000 --- a/AAIntegration.SimmonsBank.API/Services/AccountService.cs +++ /dev/null @@ -1,132 +0,0 @@ -namespace AAIntegration.SimmonsBank.API.Services; - -using AutoMapper; -using BCrypt.Net; -using AAIntegration.SimmonsBank.API.Entities; -using AAIntegration.SimmonsBank.API.Config; -using AAIntegration.SimmonsBank.API.Models.Accounts; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System; -using Internal; -using Microsoft.EntityFrameworkCore; - -public interface IAccountService -{ - 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 -{ - private DataContext _context; - private readonly IMapper _mapper; - private IUserService _userService; - - public AccountService( - DataContext context, - IMapper mapper, - IUserService userService) - { - _context = context; - _mapper = mapper; - _userService = userService; - } - - public IEnumerable GetAll(int ownerId) - { - return _context.Accounts - .Include(x => x.Owner) - .Where(x => x.Owner.Id == ownerId); - } - - public Account GetById(int accountId, int ownerId) - { - return getAccount(accountId, ownerId); - } - - public void Create(AccountCreateRequest model, int ownerId) - { - // Check that account with same name or same external number doesn't exist - 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 - .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 + "'"); - } - - Account account = new Account { - Name = model.Name, - Balance = Convert.ToDecimal(model.InitialBalance), - ExternalAccountNumber = model.ExternalAccountNumber, - Owner = getOwner(ownerId) - }; - - _context.Accounts.Add(account); - _context.SaveChanges(); - } - - public void Update(int accountId, AccountUpdateRequest model, int ownerId) - { - Account account = getAccount(accountId, ownerId); - - // validate - 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 - if (!string.IsNullOrWhiteSpace(model.Name)) - account.Name = model.Name; - - // External Account Number - if (!string.IsNullOrWhiteSpace(model.ExternalAccountNumber)) - account.ExternalAccountNumber = model.ExternalAccountNumber; - - _context.Accounts.Update(account); - _context.SaveChanges(); - } - - public void Delete(int accountId, int ownerId) - { - var account = getAccount(accountId, ownerId); - _context.Accounts.Remove(account); - _context.SaveChanges(); - } - - // helper methods - - private Account getAccount(int id, int ownerId) - { - 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/PuppeteerService.cs b/AAIntegration.SimmonsBank.API/Services/PuppeteerService.cs index f158ea0..70a74ba 100644 --- a/AAIntegration.SimmonsBank.API/Services/PuppeteerService.cs +++ b/AAIntegration.SimmonsBank.API/Services/PuppeteerService.cs @@ -24,17 +24,19 @@ using NuGet.Protocol; using Microsoft.Extensions.Logging; using NuGet.Protocol.Core.Types; using AAIntegration.SimmonsBank.API.Models.Accounts; +using AAIntegration.SimmonsBank.API.Models.Transactions; public interface IPuppeteerService { Task Login(User user, CancellationToken cancellationToken); Task IsLoggedIn(User user, CancellationToken cancellationToken); Task> GetAccounts(User user); + Task> GetTransactions(User user, string accountGuid, uint offset = 0, uint limit = 500); } public class PuppeteerService : IPuppeteerService { - private const string API_BASE_PATH = "/a/consumer/api"; + private const string API_BASE_PATH = "/a/consumer/api/v0"; private const string DASHBOARD_SELECTOR = "body > banno-web > bannoweb-layout > bannoweb-dashboard"; private const string USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"; private readonly PuppeteerConfig _config; @@ -65,6 +67,7 @@ public class PuppeteerService : IPuppeteerService public async Task Login(User user, CancellationToken cancellationToken) { + string prefix = $"Task::Login - {user.Id} - "; TimeSpan timeout = TimeSpan.FromSeconds(_config.BrowserOperationTimeoutSeconds); // Setup Page @@ -105,7 +108,7 @@ public class PuppeteerService : IPuppeteerService } else { - _logger.LogError("Failed to find Sign-In button"); + _logger.LogError(prefix + "Failed to find Sign-In button"); return false; } @@ -131,7 +134,6 @@ public class PuppeteerService : IPuppeteerService //IPage page = sender as IPage; page.Response -= LoginResponseHandler; - _logger.LogInformation("-----PARSING JSON-----"); JToken json = await args.Response.JsonAsync(); string userId = json["id"].Value(); _cacheService.SetCachedUserValue(user, PuppeteerConstants.USER_SB_ID, userId); @@ -147,7 +149,7 @@ public class PuppeteerService : IPuppeteerService } else { - _logger.LogError("Failed to find Verify button"); + _logger.LogError(prefix + "Failed to find Verify button"); return false; } @@ -157,21 +159,22 @@ public class PuppeteerService : IPuppeteerService } catch(TimeoutException) { - _logger.LogWarning($"Dashboard isn't loading after login for user '{user.Id}'"); + _logger.LogWarning(prefix + $"Dashboard isn't loading after login"); return false; } - _logger.LogInformation($"Dashboard found for '{user.Id}'"); + _logger.LogInformation(prefix + $"Login success"); + return true; } catch (TaskCanceledException) { - _logger.LogError($"Login Task for user '{user.Id}' was canceled"); + _logger.LogError(prefix + $"Login Task was canceled"); } catch (TimeoutException ex) { //_logger.LogWarning($"Login Task timed out for user '{user.Id}' after {timeout} seconds"); - _logger.LogError(0, ex, $"Login Task timed out for user '{user.Id}' after {timeout} seconds"); + _logger.LogError(0, ex, prefix + $"Login Task timed out after {timeout} seconds"); return false; } finally @@ -179,8 +182,7 @@ public class PuppeteerService : IPuppeteerService await page.CloseAsync(); } - _logger.LogInformation($"Login completed for user {user.Id}"); - return true; + return false; } public async Task IsLoggedIn(User user, CancellationToken cancellationToken) @@ -191,7 +193,7 @@ public class PuppeteerService : IPuppeteerService string userSbId = _cacheService.GetCachedUserValue(user, PuppeteerConstants.USER_SB_ID, ""); if (string.IsNullOrWhiteSpace(userSbId)) { - _logger.LogInformation(prefix + $"User SimmonsBank ID not found. User is not logged in."); + //_logger.LogInformation(prefix + $"User SimmonsBank ID not found. User is not logged in."); return false; } @@ -207,22 +209,21 @@ public class PuppeteerService : IPuppeteerService try { IResponse response = await page.GoToAsync(url).WaitAsync(TimeSpan.FromSeconds(_config.BrowserOperationTimeoutSeconds), cancellationToken); - _logger.LogInformation(prefix + $"Request response code '{response.Status}'. Url: '{url}'"); + //_logger.LogInformation(prefix + $"Request response code '{response.Status}'. Url: '{url}'"); return response.Status == System.Net.HttpStatusCode.OK; } catch(TaskCanceledException) { - _logger.LogWarning(prefix + $"Task was canceled"); + _logger.LogError(prefix + $"Task was canceled"); } catch(TimeoutException) { - _logger.LogWarning(prefix + $"Request to '{url}' timed out"); + _logger.LogError(prefix + $"Request to '{url}' timed out"); } return false; } - public async Task> GetAccounts(User user) { string prefix = $"Task::GetAccounts - {user.Id} - "; @@ -231,7 +232,7 @@ public class PuppeteerService : IPuppeteerService string userSbId = _cacheService.GetCachedUserValue(user, PuppeteerConstants.USER_SB_ID, ""); if (string.IsNullOrWhiteSpace(userSbId)) { - _logger.LogInformation(prefix + $"User SimmonsBank ID not found. User is not logged in."); + _logger.LogWarning(prefix + $"User SimmonsBank ID not found. User is not logged in."); return null; } @@ -247,7 +248,7 @@ public class PuppeteerService : IPuppeteerService try { IResponse response = await page.GoToAsync(url).WaitAsync(TimeSpan.FromSeconds(_config.BrowserOperationTimeoutSeconds)); - _logger.LogInformation(prefix + $"Request response code '{response.Status}'. Url: '{url}'"); + //_logger.LogInformation(prefix + $"Request response code '{response.Status}'. Url: '{url}'"); if (response.Status == System.Net.HttpStatusCode.OK) { @@ -259,11 +260,57 @@ public class PuppeteerService : IPuppeteerService } catch(TaskCanceledException) { - _logger.LogWarning(prefix + $"Task was canceled"); + _logger.LogError(prefix + $"Task was canceled"); } catch(TimeoutException) { - _logger.LogWarning(prefix + $"Request to '{url}' timed out"); + _logger.LogError(prefix + $"Request to '{url}' timed out"); + } + + return null; + } + + public async Task> GetTransactions(User user, string accountGuid, uint offset = 0, uint limit = 500) + { + string prefix = $"Task::GetTransactions - {user.Id} - "; + + // Get User ID + string userSbId = _cacheService.GetCachedUserValue(user, PuppeteerConstants.USER_SB_ID, ""); + if (string.IsNullOrWhiteSpace(userSbId)) + { + _logger.LogWarning(prefix + $"User SimmonsBank ID not found. User is not logged in."); + return null; + } + + // Setup Page + IBrowser browser = await GetUserBrowserAsync(user, new CancellationToken()); + await using IPage page = await browser.NewPageAsync(); + await page.SetUserAgentAsync(USER_AGENT); + await page.SetViewportAsync(new ViewPortOptions { Width = 1200, Height = 720 }); + + // Fetch transactions + string url = _config.SimmonsBankBaseUrl + API_BASE_PATH + $"/users/{userSbId}/accounts/{accountGuid}/transactions?offset={offset}&limit={limit}"; + + try + { + IResponse response = await page.GoToAsync(url).WaitAsync(TimeSpan.FromSeconds(_config.BrowserOperationTimeoutSeconds)); + //_logger.LogInformation(prefix + $"Request response code '{response.Status}'. Url: '{url}'"); + + if (response.Status == System.Net.HttpStatusCode.OK) + { + JToken transactions = await response.JsonAsync(); + return transactions.SelectToken("transactions").ToObject>(); + } + else + _logger.LogError(prefix + $"Received unexpected status code '{response.Status}'"); + } + catch(TaskCanceledException) + { + _logger.LogError(prefix + $"Task was canceled"); + } + catch(TimeoutException) + { + _logger.LogError(prefix + $"Request to '{url}' timed out"); } return null; @@ -278,7 +325,7 @@ public class PuppeteerService : IPuppeteerService if (cachedBrowser != null) return cachedBrowser; - _logger.LogInformation($"Could NOT find the browser for user with id '{user.Id}'. About to create one..."); + //_logger.LogInformation($"Could NOT find the browser for user with id '{user.Id}'. About to create one..."); using var browserFetcher = new BrowserFetcher(); await browserFetcher.DownloadAsync().WaitAsync(TimeSpan.FromSeconds(_config.BrowserOperationTimeoutSeconds * 20), cancellationToken); diff --git a/AAIntegration.SimmonsBank.API/Services/TransactionService.cs b/AAIntegration.SimmonsBank.API/Services/TransactionService.cs deleted file mode 100644 index e4a0f13..0000000 --- a/AAIntegration.SimmonsBank.API/Services/TransactionService.cs +++ /dev/null @@ -1,230 +0,0 @@ -namespace AAIntegration.SimmonsBank.API.Services; - -using AutoMapper; -using BCrypt.Net; -using AAIntegration.SimmonsBank.API.Entities; -using AAIntegration.SimmonsBank.API.Config; -using AAIntegration.SimmonsBank.API.Models.Transactions; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System; -using Microsoft.EntityFrameworkCore; -using Internal; -using System.Collections.Immutable; - -public interface ITransactionService -{ - 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 -{ - private DataContext _context; - private readonly IMapper _mapper; - private readonly ILogger _logger; - - public TransactionService( - DataContext context, - IMapper mapper, - ILogger logger) - { - _context = context; - _mapper = mapper; - _logger = logger; - } - - public IEnumerable GetAll(int ownerId) - { - return _context.Transactions - .Include(t => t.DebitAccount) - .Include(t => t.CreditAccount) - .Include(t => t.Owner) - .Where(x => x.Owner.Id == ownerId); - } - - public Transaction GetById(int id, int ownerId) - { - return getTransaction(id, ownerId); - } - - private Account prepareAccount(int? accountId) - { - if (accountId == null || accountId.Value == 0) - { - return null; - } - - Account account = _context.Accounts.Find(accountId.Value); - if (account == null) - throw new AppException("Could not find account with ID of '" + accountId.Value + "'."); - - return account; - } - - public IEnumerable BulkCreate(List model, int ownerId) - { - List transactions = new List(); - - foreach (TransactionCreate tr in model) - { - var tran = this.Create(tr, ownerId, false); - if (tran != null) - transactions.Add(tran); - } - - return transactions; - } - - public Transaction Create(TransactionCreate model, int ownerId, bool errorOnFail = true) - { - Transaction transaction = new Transaction { - Description = model.Description, - Date = model.Date.Date.ToUniversalTime(), - CreatedOn = DateTime.UtcNow, - UpdatedOn = DateTime.UtcNow, - ExternalId = string.IsNullOrWhiteSpace(model.ExternalId) ? "" : model.ExternalId, - DebitAccount = prepareAccount(model.DebitAccount), - CreditAccount = prepareAccount(model.CreditAccount), - Amount = Convert.ToDecimal(model.Amount), - Owner = this.getOwner(ownerId), - IsPending = model.IsPending - }; - - if (this.ValidateTransaction(transaction, ownerId, errorOnFail) == false) - { - _logger.LogInformation($"Aborted adding transaction '{transaction.Description}'."); - return null; - } - - // At this point transaction itself is valid - - _context.Transactions.Add(transaction); - _context.SaveChanges(); - - _logger.LogInformation("New transaction successfully created."); - - return transaction; - } - - public void Update(int id, TransactionUpdateRequest model, int ownerId) - { - Transaction transaction = getTransaction(id, ownerId); - - // Transaction.Date - if (model.Date.HasValue) - transaction.Date = model.Date.Value; - - // Transaction.ExternalId - if (model.ExternalId != null) - transaction.ExternalId = model.ExternalId; - - // Transaction.Description - if (model.Description != null) - transaction.Description = model.Description; - - // Transaction.DebitAccount - if (model.DebitAccount.HasValue) - transaction.DebitAccount = prepareAccount(model.DebitAccount); - - // Transaction.CreditAccount - if (model.CreditAccount.HasValue) - transaction.CreditAccount = prepareAccount(model.CreditAccount.Value); - - // Transaction.Amount - if (model.Amount.HasValue) - transaction.Amount = model.Amount.Value; - - // Transaction.IsPending - if (model.IsPending.HasValue) - transaction.IsPending = model.IsPending.Value; - - this.ValidateTransaction(transaction, ownerId); - - transaction.UpdatedOn = DateTime.UtcNow; - - _context.Transactions.Update(transaction); - _context.SaveChanges(); - - _logger.LogInformation($"Transaction '{id}' successfully updated."); - } - - public void Delete(int id, int ownerId) - { - var transaction = getTransaction(id, ownerId); - _context.Transactions.Remove(transaction); - _context.SaveChanges(); - } - - // helpers - - private bool ErrorOrFalse(bool error, string errorMessage) - { - if (error) - throw new AppException(errorMessage); - - _logger.LogWarning(errorMessage); - return false; - } - - 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) - return ErrorOrFalse(errorOnFail, "There must be an envelope or account chosen for a transaction."); - - // Accounts cannot be the same - if (transaction.DebitAccount != null && transaction.CreditAccount != null && - transaction.DebitAccount.Id == transaction.CreditAccount.Id) - 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 - .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 - /*if (_context.Transactions.Any(x => - x.Description == transaction.Description - && x.Date == transaction.Date - && x.DebitAccount == transaction.DebitAccount - && x.CreditAccount == transaction.CreditAccount - && x.Amount == transaction.Amount)) - { - return ErrorOrFalse(errorOnFail, "Transaction with the same fields already exists"); - }*/ - - return true; - } - - private Transaction getTransaction(int id, int ownerId) - { - var transaction = _context.Transactions - .Include(t => t.DebitAccount) - .Include(t => t.CreditAccount) - .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 3039985..51ea72c 100644 --- a/AAIntegration.SimmonsBank.API/Services/UserService.cs +++ b/AAIntegration.SimmonsBank.API/Services/UserService.cs @@ -1,59 +1,30 @@ namespace AAIntegration.SimmonsBank.API.Services; -using AutoMapper; -using BCrypt.Net; using AAIntegration.SimmonsBank.API.Entities; using AAIntegration.SimmonsBank.API.Config; using AAIntegration.SimmonsBank.API.Models.Users; using System; -using System.Collections; using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.IdentityModel.Tokens; -using System.Text; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using Microsoft.Extensions.Options; using System.Security.Cryptography; public interface IUserService { - // New Based way string Create(UserCreateRequest model); void Update(string apiKey, UserUpdateRequest model); void Delete(string apiKey); Dictionary GetAllApiKeys(); User GetUser(string ApiKey); IEnumerable GetAll(); - - /* Other cringe way - AuthenticateResponse Authenticate(AuthenticateRequest model); - void Register(RegisterRequest model); - IEnumerable GetAll(); - User GetById(int id); - void Update(int id, UserUpdateRequest model); - void Delete(int id); - Dictionary GetAllApiKeys(); - string GetUserApiKey(int id); - void InvalidateApiKey(string apiKey); - string CreateUserApiKey(int id); - */ } public class UserService : IUserService { private DataContext _context; - private readonly IMapper _mapper; - private readonly IOptions _appSettings; public UserService( - DataContext context, - IMapper mapper, - IOptions appSettings) + DataContext context) { _context = context; - _mapper = mapper; - _appSettings = appSettings; } public string Create(UserCreateRequest model) @@ -130,7 +101,7 @@ public class UserService : IUserService return user; } - private const string _prefix = "CT-"; + private const string _prefix = "SB-"; private const int _numberOfSecureBytesToGenerate = 32; private const int _lengthOfKey = 32; diff --git a/README.md b/README.md index 0c1cda9..02dc1c3 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ This is an integration for ActiveAllocator. The type is Transaction Importer, specifically created for interfacing with SimmonsBank's online banking website. +## Guides + +[Pass parameters to HTTP GET action](https://code-maze.com/aspnetcore-pass-parameters-to-http-get-action/) + ## Dependencies [Otp.NET library](https://github.com/kspearrin/Otp.NET)