From f07bfb731c94c979dc6ffc19f026b564095dc178 Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 30 Sep 2024 22:14:13 +0200 Subject: [PATCH 01/15] [Server] Add BankAccount retrieval logic --- .../Program.cs | 7 +- ...olvers.cs => BankAccountQueryResolvers.cs} | 13 +- .../Utility/MockDataAccessService.cs | 2 +- .../Models/Account.cs | 6 +- .../Models/BankAccount.cs | 12 +- .../ApplicationDbContext.cs | 2 + .../Entities/BankAccountEntity.cs | 66 +++++ ...yDataRetrievalMetadataTypeConfiguration.cs | 2 +- .../Mappers/BankAccountMapper.cs | 15 ++ ...240930194714_AddBankAccountSet.Designer.cs | 243 ++++++++++++++++++ .../20240930194714_AddBankAccountSet.cs | 83 ++++++ .../ApplicationDbContextModelSnapshot.cs | 69 +++++ .../Repositories/AccountRepository.cs | 46 ---- .../Repositories/BankAccountRepository.cs | 41 +++ .../GoCardlessDataProvider.cs | 89 ++++--- 15 files changed, 597 insertions(+), 99 deletions(-) rename src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/{AccountQueryResolvers.cs => BankAccountQueryResolvers.cs} (57%) create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Entities/BankAccountEntity.cs create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Mappers/BankAccountMapper.cs create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/20240930194714_AddBankAccountSet.Designer.cs create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/20240930194714_AddBankAccountSet.cs delete mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AccountRepository.cs create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs index 68c0458..26781ae 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs @@ -5,7 +5,6 @@ global using System.Threading.Tasks; global using Serilog; global using HotChocolate; - using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -26,6 +25,9 @@ loggerConfig.ReadFrom.Configuration(context.Configuration); }); +// Forward issues with Serilog itself to console +Serilog.Debugging.SelfLog.Enable(Console.Error); + // HTTP setup builder.Services .AddHttpClient() @@ -43,11 +45,12 @@ // Mappers builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); // TODO: automatic registration of repositories via codegen? // Repositories builder.Services - .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/AccountQueryResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/BankAccountQueryResolvers.cs similarity index 57% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/AccountQueryResolvers.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/BankAccountQueryResolvers.cs index 852abd1..7b2db44 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/AccountQueryResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/BankAccountQueryResolvers.cs @@ -8,28 +8,25 @@ namespace RobinTTY.PersonalFinanceDashboard.Api.Types.Queries; /// related query resolvers. /// [QueryType] -public sealed class AccountQueryResolvers +public sealed class BankAccountQueryResolvers { - // TODO: probably should be GetBankAccount and then separate route for investment accounts /// /// Look up an account by its id. /// /// The repository to use for data retrieval. /// The id of the account to lookup. - public async Task GetAccount(AccountRepository repository, string accountId) + public async Task GetAccount(BankAccountRepository repository, Guid accountId) { - return await repository.GetAccount(accountId); + return await repository.GetBankAccount(accountId); } /// /// Look up accounts by a list of ids. /// /// The injected repository to use for data retrieval. - /// The ids of the accounts to retrieve. [UsePaging] - public async Task> GetAccounts(AccountRepository repository, - IEnumerable accountIds) + public async Task> GetAccounts(BankAccountRepository repository) { - return await repository.GetAccounts(accountIds); + return await repository.GetBankAccounts(); } } diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/MockDataAccessService.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/MockDataAccessService.cs index 15b5865..9a6e5ac 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/MockDataAccessService.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/MockDataAccessService.cs @@ -43,7 +43,7 @@ public static List GetAccounts(int amount) { return new Faker() .CustomInstantiator(f => new Account( - id: Guid.NewGuid().ToString(), + id: Guid.NewGuid(), name: f.Person.FullName, description: f.Finance.AccountName(), balance: f.Finance.Amount(0, 5_000), diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/Account.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/Account.cs index 105d5e5..11478c2 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/Account.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/Account.cs @@ -10,7 +10,7 @@ public class Account /// /// The id of the account. /// - public string Id { get; set; } + public Guid Id { get; set; } /// /// The name of the account. /// @@ -41,7 +41,7 @@ public class Account /// The current balance of the account. /// The currency this account is denominated in. /// Transactions that are associated with this account. - public Account(string id, string? name, string? description, decimal? balance, string? currency, List transactions) + public Account(Guid id, string? name, string? description, decimal? balance, string? currency, List transactions) { Id = id; Name = name; @@ -53,7 +53,7 @@ public Account(string id, string? name, string? description, decimal? balance, s } /// -/// Identifies the type of an account. +/// Identifies the type of account. /// public enum AccountType { diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/BankAccount.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/BankAccount.cs index 9d62719..cde0101 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/BankAccount.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/BankAccount.cs @@ -27,9 +27,9 @@ public class BankAccount : Account /// public string? AccountType { get; set; } /// - /// The id of the banking institution this account belongs to. + /// The banking institution this account belongs to. /// - public string InstitutionId { get; set; } + public BankingInstitution AssociatedInstitution { get; set; } /// /// Creates a new instance of . @@ -47,9 +47,9 @@ public class BankAccount : Account /// Name of the legal account owner. If there is more than one owner, /// then two names might be noted here. /// Specifies the nature, or use, of the account. - /// The id of the banking institution this account belongs to. - public BankAccount(string id, string? name, string? description, decimal? balance, string? currency, List transactions, - string? iban, string? bic, string? bban, string? ownerName, string? accountType, string institutionId) + /// The banking institution this account belongs to. + public BankAccount(Guid id, string? name, string? description, decimal? balance, string? currency, List transactions, + string? iban, string? bic, string? bban, string? ownerName, string? accountType, BankingInstitution associatedInstitution) : base(id, name, description, balance, currency, transactions) { Iban = iban; @@ -57,6 +57,6 @@ public BankAccount(string id, string? name, string? description, decimal? balanc Bban = bban; OwnerName = ownerName; AccountType = accountType; - InstitutionId = institutionId; + AssociatedInstitution = associatedInstitution; } } diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/ApplicationDbContext.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/ApplicationDbContext.cs index 229c25e..97f3707 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/ApplicationDbContext.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/ApplicationDbContext.cs @@ -26,6 +26,8 @@ public sealed class ApplicationDbContext : DbContext public DbSet ThirdPartyDataRetrievalMetadata => Set(); + public DbSet BankAccounts => Set(); + /// /// Creates a new instance of . /// diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Entities/BankAccountEntity.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Entities/BankAccountEntity.cs new file mode 100644 index 0000000..dcc9826 --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Entities/BankAccountEntity.cs @@ -0,0 +1,66 @@ +namespace RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities; + +public class BankAccountEntity : BaseEntity +{ + /// + /// The name of the account. + /// + public string? Name { get; set; } + /// + /// A description of this account. + /// + public string? Description { get; set; } + /// + /// The current balance of the account. + /// + public decimal? Balance { get; set; } + /// + /// The currency this account is denominated in. + /// + public string? Currency { get; set; } + /// + /// Transactions that are associated with this account. + /// + public ICollection Transactions { get; set; } + /// + /// The IBAN of the bank account. + /// + public string? Iban { get; set; } + /// + /// The BIC (Business Identifier Code) associated with the account. + /// + public string? Bic { get; set; } + /// + /// Basic Bank Account Number represents a country-specific bank account number. + /// This data element is used for payment accounts which have no IBAN. + /// + public string? Bban { get; set; } + /// + /// Name of the legal account owner. If there is more than one owner, then two names might be noted here. + /// + public string? OwnerName { get; set; } + /// + /// Specifies the nature, or use, of the account. + /// + public string? AccountType { get; set; } + /// + /// The banking institution this account belongs to. + /// + public BankingInstitutionEntity? AssociatedInstitution { get; set; } + + public BankAccountEntity(Guid id, string? name, string? description, decimal? balance, string? currency, + string? iban, string? bic, string? bban, string? ownerName, string? accountType) : base(id) + { + Name = name; + Description = description; + Balance = balance; + Currency = currency; + Transactions = new List(); + Iban = iban; + Bic = bic; + Bban = bban; + OwnerName = ownerName; + AccountType = accountType; + AssociatedInstitution = null; + } +} diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Entities/Configurations/ThirdPartyDataRetrievalMetadataTypeConfiguration.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Entities/Configurations/ThirdPartyDataRetrievalMetadataTypeConfiguration.cs index 3e9ea39..51e4f97 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Entities/Configurations/ThirdPartyDataRetrievalMetadataTypeConfiguration.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Entities/Configurations/ThirdPartyDataRetrievalMetadataTypeConfiguration.cs @@ -9,7 +9,7 @@ public class { public void Configure(EntityTypeBuilder builder) { - builder.HasData(new ThirdPartyDataRetrievalMetadataEntity(Guid.NewGuid(), + builder.HasData(new ThirdPartyDataRetrievalMetadataEntity(new Guid("f948e52f-ad17-44b8-9cdf-e0b952f139b3"), ThirdPartyDataType.BankingInstitutions, ThirdPartyDataSource.GoCardless, DateTime.MinValue, TimeSpan.FromDays(7))); } diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Mappers/BankAccountMapper.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Mappers/BankAccountMapper.cs new file mode 100644 index 0000000..e08343b --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Mappers/BankAccountMapper.cs @@ -0,0 +1,15 @@ +using Riok.Mapperly.Abstractions; +using RobinTTY.PersonalFinanceDashboard.Core.Models; +using RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities; + +namespace RobinTTY.PersonalFinanceDashboard.Infrastructure.Mappers; + +/// +/// TODO +/// +[Mapper] +public partial class BankAccountMapper +{ + public partial BankAccount EntityToModel(BankAccountEntity bankAccountEntity); + public partial BankAccountEntity ModelToEntity(BankAccount bankAccount); +} diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/20240930194714_AddBankAccountSet.Designer.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/20240930194714_AddBankAccountSet.Designer.cs new file mode 100644 index 0000000..2ba2703 --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/20240930194714_AddBankAccountSet.Designer.cs @@ -0,0 +1,243 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RobinTTY.PersonalFinanceDashboard.Infrastructure; + +#nullable disable + +namespace RobinTTY.PersonalFinanceDashboard.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240930194714_AddBankAccountSet")] + partial class AddBankAccountSet + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankAccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountType") + .HasColumnType("TEXT"); + + b.Property("AssociatedInstitutionId") + .HasColumnType("TEXT"); + + b.Property("Balance") + .HasColumnType("TEXT"); + + b.Property("Bban") + .HasColumnType("TEXT"); + + b.Property("Bic") + .HasColumnType("TEXT"); + + b.Property("Currency") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Iban") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OwnerName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AssociatedInstitutionId"); + + b.ToTable("BankAccounts"); + }); + + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankingInstitutionEntity", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Bic") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Countries") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LogoUri") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("BankingInstitutions"); + }); + + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.TagEntity", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Color") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.ThirdPartyDataRetrievalMetadataEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DataSource") + .HasColumnType("INTEGER"); + + b.Property("DataType") + .HasColumnType("INTEGER"); + + b.Property("LastRetrievalTime") + .HasColumnType("TEXT"); + + b.Property("RetrievalInterval") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ThirdPartyDataRetrievalMetadata"); + + b.HasData( + new + { + Id = new Guid("f948e52f-ad17-44b8-9cdf-e0b952f139b3"), + DataSource = 1, + DataType = 1, + LastRetrievalTime = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + RetrievalInterval = new TimeSpan(7, 0, 0, 0, 0) + }); + }); + + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.TransactionEntity", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("BankAccountEntityId") + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Payee") + .HasColumnType("TEXT"); + + b.Property("Payer") + .HasColumnType("TEXT"); + + b.Property("ValueDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BankAccountEntityId"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("TagEntityTransactionEntity", b => + { + b.Property("TagsId") + .HasColumnType("TEXT"); + + b.Property("TransactionsId") + .HasColumnType("TEXT"); + + b.HasKey("TagsId", "TransactionsId"); + + b.HasIndex("TransactionsId"); + + b.ToTable("TagEntityTransactionEntity"); + }); + + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankAccountEntity", b => + { + b.HasOne("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankingInstitutionEntity", "AssociatedInstitution") + .WithMany() + .HasForeignKey("AssociatedInstitutionId"); + + b.Navigation("AssociatedInstitution"); + }); + + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.TransactionEntity", b => + { + b.HasOne("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankAccountEntity", null) + .WithMany("Transactions") + .HasForeignKey("BankAccountEntityId"); + }); + + modelBuilder.Entity("TagEntityTransactionEntity", b => + { + b.HasOne("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.TagEntity", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.TransactionEntity", null) + .WithMany() + .HasForeignKey("TransactionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankAccountEntity", b => + { + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/20240930194714_AddBankAccountSet.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/20240930194714_AddBankAccountSet.cs new file mode 100644 index 0000000..6c115c1 --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/20240930194714_AddBankAccountSet.cs @@ -0,0 +1,83 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RobinTTY.PersonalFinanceDashboard.Infrastructure.Migrations +{ + /// + public partial class AddBankAccountSet : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "BankAccountEntityId", + table: "Transactions", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateTable( + name: "BankAccounts", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + Description = table.Column(type: "TEXT", nullable: true), + Balance = table.Column(type: "TEXT", nullable: true), + Currency = table.Column(type: "TEXT", nullable: true), + Iban = table.Column(type: "TEXT", nullable: true), + Bic = table.Column(type: "TEXT", nullable: true), + Bban = table.Column(type: "TEXT", nullable: true), + OwnerName = table.Column(type: "TEXT", nullable: true), + AccountType = table.Column(type: "TEXT", nullable: true), + AssociatedInstitutionId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BankAccounts", x => x.Id); + table.ForeignKey( + name: "FK_BankAccounts_BankingInstitutions_AssociatedInstitutionId", + column: x => x.AssociatedInstitutionId, + principalTable: "BankingInstitutions", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_Transactions_BankAccountEntityId", + table: "Transactions", + column: "BankAccountEntityId"); + + migrationBuilder.CreateIndex( + name: "IX_BankAccounts_AssociatedInstitutionId", + table: "BankAccounts", + column: "AssociatedInstitutionId"); + + migrationBuilder.AddForeignKey( + name: "FK_Transactions_BankAccounts_BankAccountEntityId", + table: "Transactions", + column: "BankAccountEntityId", + principalTable: "BankAccounts", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Transactions_BankAccounts_BankAccountEntityId", + table: "Transactions"); + + migrationBuilder.DropTable( + name: "BankAccounts"); + + migrationBuilder.DropIndex( + name: "IX_Transactions_BankAccountEntityId", + table: "Transactions"); + + migrationBuilder.DropColumn( + name: "BankAccountEntityId", + table: "Transactions"); + } + } +} diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index d6ca933..a4c93e3 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,6 +17,49 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankAccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccountType") + .HasColumnType("TEXT"); + + b.Property("AssociatedInstitutionId") + .HasColumnType("TEXT"); + + b.Property("Balance") + .HasColumnType("TEXT"); + + b.Property("Bban") + .HasColumnType("TEXT"); + + b.Property("Bic") + .HasColumnType("TEXT"); + + b.Property("Currency") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Iban") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OwnerName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AssociatedInstitutionId"); + + b.ToTable("BankAccounts"); + }); + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankingInstitutionEntity", b => { b.Property("Id") @@ -110,6 +153,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Amount") .HasColumnType("TEXT"); + b.Property("BankAccountEntityId") + .HasColumnType("TEXT"); + b.Property("Category") .IsRequired() .HasColumnType("TEXT"); @@ -133,6 +179,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("BankAccountEntityId"); + b.ToTable("Transactions"); }); @@ -151,6 +199,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("TagEntityTransactionEntity"); }); + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankAccountEntity", b => + { + b.HasOne("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankingInstitutionEntity", "AssociatedInstitution") + .WithMany() + .HasForeignKey("AssociatedInstitutionId"); + + b.Navigation("AssociatedInstitution"); + }); + + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.TransactionEntity", b => + { + b.HasOne("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankAccountEntity", null) + .WithMany("Transactions") + .HasForeignKey("BankAccountEntityId"); + }); + modelBuilder.Entity("TagEntityTransactionEntity", b => { b.HasOne("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.TagEntity", null) @@ -165,6 +229,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); + + modelBuilder.Entity("RobinTTY.PersonalFinanceDashboard.Infrastructure.Entities.BankAccountEntity", b => + { + b.Navigation("Transactions"); + }); #pragma warning restore 612, 618 } } diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AccountRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AccountRepository.cs deleted file mode 100644 index 889d533..0000000 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AccountRepository.cs +++ /dev/null @@ -1,46 +0,0 @@ -using RobinTTY.PersonalFinanceDashboard.Core.Models; -using RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders; - -namespace RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; - -/// -/// Manages data retrieval. -/// -public class AccountRepository -{ - private readonly ApplicationDbContext _dbContext; - private readonly GoCardlessDataProvider _dataProvider; - - /// - /// Creates a new instance of . - /// - /// The to use for data retrieval. - /// The data provider to use for data retrieval. - public AccountRepository(ApplicationDbContext dbContext, GoCardlessDataProvider dataProvider) - { - _dbContext = dbContext; - _dataProvider = dataProvider; - } - - /// - /// Gets the matching the specified id. - /// - /// The id of the account to retrieve. - /// The if one ist matched otherwise . - public async Task GetAccount(string accountId) - { - var account = await _dataProvider.GetBankAccount(accountId); - return account.Result; - } - - /// - /// Gets the s matching the specified ids. - /// - /// The ids of the accounts to retrieve. - /// A list of the matched s. - public async Task> GetAccounts(IEnumerable accountIds) - { - var accounts = await _dataProvider.GetBankAccounts(accountIds); - return accounts.Select(query => query.Result)!; - } -} \ No newline at end of file diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs new file mode 100644 index 0000000..b59ee29 --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore; +using RobinTTY.PersonalFinanceDashboard.Core.Models; +using RobinTTY.PersonalFinanceDashboard.Infrastructure.Mappers; + +namespace RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; + +public class BankAccountRepository +{ + private readonly ApplicationDbContext _dbContext; + private readonly BankAccountMapper _bankAccountMapper; + + /// + /// TODO + /// + /// + /// + public BankAccountRepository(ApplicationDbContext dbContext, BankAccountMapper bankAccountMapper) + { + _dbContext = dbContext; + _bankAccountMapper = bankAccountMapper; + } + + /// + /// TODO + /// + /// + /// + public async Task GetBankAccount(Guid accountId) + { + var accountEntity = + await _dbContext.BankAccounts.SingleOrDefaultAsync(account => account.Id == accountId); + + return accountEntity is not null ? _bankAccountMapper.EntityToModel(accountEntity) : null; + } + + public async Task> GetBankAccounts() + { + var accountEntities = await _dbContext.BankAccounts.ToListAsync(); + return accountEntities.Select(_bankAccountMapper.EntityToModel).ToList(); + } +} diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/GoCardlessDataProvider.cs b/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/GoCardlessDataProvider.cs index da69d5e..00c4eca 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/GoCardlessDataProvider.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/GoCardlessDataProvider.cs @@ -24,12 +24,14 @@ public GoCardlessDataProvider(HttpClient httpClient, NordigenClientCredentials c _client = new NordigenClient(httpClient, credentials); } - public async Task> GetBankingInstitution(string institutionId) + public async Task> GetBankingInstitution( + string institutionId) { var response = await _client.InstitutionsEndpoint.GetInstitution(institutionId); BankingInstitution? result = null; - if(response.IsSuccess) - result = new BankingInstitution(response.Result.Id, response.Result.Bic, response.Result.Name, response.Result.Logo, response.Result.Countries); + if (response.IsSuccess) + result = new BankingInstitution(response.Result.Id, response.Result.Bic, response.Result.Name, + response.Result.Logo, response.Result.Countries); return new ThirdPartyResponse(response.IsSuccess, result, response.Error); } @@ -38,15 +40,18 @@ public GoCardlessDataProvider(HttpClient httpClient, NordigenClientCredentials c /// /// Optional country filter to apply to the query. /// The available banking s. - public async Task, BasicResponse>> GetBankingInstitutions(string? country = null) + public async Task, BasicResponse>> GetBankingInstitutions( + string? country = null) { var response = await _client.InstitutionsEndpoint.GetInstitutions(country); if (response.IsSuccess) { var institutions = - response.Result.Select(inst => new BankingInstitution(inst.Id, inst.Bic, inst.Name, inst.Logo, inst.Countries)); - return new ThirdPartyResponse, BasicResponse>(true, institutions.AsQueryable(), null); + response.Result.Select(inst => + new BankingInstitution(inst.Id, inst.Bic, inst.Name, inst.Logo, inst.Countries)); + return new ThirdPartyResponse, BasicResponse>(true, + institutions.AsQueryable(), null); } return new ThirdPartyResponse, BasicResponse>(false, null, response.Error); @@ -57,14 +62,17 @@ public async Task, BasicRespon /// /// The institutionId of the requisition to be retrieved. /// The which matches the institutionId, as far as it exists. - public async Task> GetAuthenticationRequest(string requisitionId) + public async Task> GetAuthenticationRequest( + string requisitionId) { var response = await _client.RequisitionsEndpoint.GetRequisition(requisitionId); // TODO: handle request failure var requisition = response.Result!; - var result = new AuthenticationRequest(requisition.Id.ToString(), requisition.Accounts.Select(guid => guid.ToString()), + var result = new AuthenticationRequest(requisition.Id.ToString(), + requisition.Accounts.Select(guid => guid.ToString()), ConvertRequisitionStatus(requisition.Status), requisition.AuthenticationLink); - return new ThirdPartyResponse(response.IsSuccess, result, response.Error); + return new ThirdPartyResponse(response.IsSuccess, result, + response.Error); } // TODO: Cancellation token support @@ -73,15 +81,18 @@ public async Task, BasicRespon /// /// The maximum number of requisitions to get. /// All existing s. - public async Task, BasicResponse?>> GetAuthenticationRequests(int requisitionLimit) + public async Task, BasicResponse?>> GetAuthenticationRequests( + int requisitionLimit) { var response = await _client.RequisitionsEndpoint.GetRequisitions(requisitionLimit, 0); // TODO: handle request failure var requisitions = response.Result!.Results; var result = requisitions.Select(req => new AuthenticationRequest(req.Id.ToString(), - req.Accounts.Select(guid => guid.ToString()), ConvertRequisitionStatus(req.Status), req.AuthenticationLink)).AsQueryable(); - return new ThirdPartyResponse, BasicResponse?>(response.IsSuccess, result, response.Error); - + req.Accounts.Select(guid => guid.ToString()), ConvertRequisitionStatus(req.Status), + req.AuthenticationLink)) + .AsQueryable(); + return new ThirdPartyResponse, BasicResponse?>(response.IsSuccess, result, + response.Error); } /// @@ -90,28 +101,36 @@ public async Task, BasicRespon /// Id of the institution for which to create the requisition. /// which will be redirected too when the user completes authentication for the given requisition. /// The created . - public async Task> CreateAuthenticationRequest(string institutionId, Uri redirectUri) + public async Task> CreateAuthenticationRequest( + string institutionId, Uri redirectUri) { - var response = await _client.RequisitionsEndpoint.CreateRequisition(institutionId, redirectUri, reference: Guid.NewGuid().ToString()); + var response = + await _client.RequisitionsEndpoint.CreateRequisition(institutionId, redirectUri, + reference: Guid.NewGuid().ToString()); if (response.IsSuccess) { var requisition = response.Result; - var authenticationRequest = new AuthenticationRequest(requisition.Id.ToString(), requisition.Accounts.Select(guid => guid.ToString()), + var authenticationRequest = new AuthenticationRequest(requisition.Id.ToString(), + requisition.Accounts.Select(guid => guid.ToString()), ConvertRequisitionStatus(requisition.Status), requisition.AuthenticationLink); - return new ThirdPartyResponse(response.IsSuccess, authenticationRequest, null); + return new ThirdPartyResponse(response.IsSuccess, + authenticationRequest, null); } - return new ThirdPartyResponse(response.IsSuccess, null, response.Error); + return new ThirdPartyResponse(response.IsSuccess, null, + response.Error); } - public async Task> DeleteAuthenticationRequest(string authenticationId) + public async Task> DeleteAuthenticationRequest( + string authenticationId) { var response = await _client.RequisitionsEndpoint.DeleteRequisition(authenticationId); - return new ThirdPartyResponse(response.IsSuccess, response.Result, response.Error); + return new ThirdPartyResponse(response.IsSuccess, response.Result, + response.Error); } - public async Task> GetBankAccount(string accountId) + public async Task> GetBankAccount(Guid accountId) { var accountDetailsTask = _client.AccountsEndpoint.GetAccountDetails(accountId); var balanceTask = _client.AccountsEndpoint.GetBalances(accountId); @@ -127,7 +146,8 @@ public async Task> GetBankAccount accountDetailsRequest.IsSuccess && balanceRequest.IsSuccess, bankAccount, accountDetailsRequest.Error); } - public async Task>> GetBankAccounts(IEnumerable accountIds) + public async Task>> GetBankAccounts( + IEnumerable accountIds) { var tasks = accountIds.Select(GetBankAccount).ToList(); await Task.WhenAll(tasks); @@ -138,12 +158,15 @@ public async Task, AccountsError>> G { var response = await _client.AccountsEndpoint.GetTransactions(accountId); // TODO: Also return pending transactions - var transactions = response.Result!.BookedTransactions.Select(transaction => - new Transaction(transaction.InternalTransactionId ?? Guid.NewGuid().ToString(), accountId, transaction.ValueDateTime ?? transaction.ValueDate, - transaction.CreditorName, transaction.DebtorName, transaction.TransactionAmount.Amount, transaction.TransactionAmount.Currency, + var transactions = response.Result!.BookedTransactions.Select(transaction => + new Transaction(transaction.InternalTransactionId ?? Guid.NewGuid().ToString(), accountId, + transaction.ValueDateTime ?? transaction.ValueDate, + transaction.CreditorName, transaction.DebtorName, transaction.TransactionAmount.Amount, + transaction.TransactionAmount.Currency, "example-category", new List(), "example-notes") ); - return new ThirdPartyResponse, AccountsError>(response.IsSuccess, transactions, response.Error); + return new ThirdPartyResponse, AccountsError>(response.IsSuccess, transactions, + response.Error); } private AuthenticationStatus ConvertRequisitionStatus(RequisitionStatus status) @@ -164,26 +187,28 @@ private AuthenticationStatus ConvertRequisitionStatus(RequisitionStatus status) }; } - private BankAccount ConvertAccount(string accountId, BankAccountDetails account, List balances) + private BankAccount ConvertAccount(Guid accountId, BankAccountDetails account, List balances) { return new BankAccount ( // TODO: Convert Account id: accountId, - accountType: account.CashAccountType.HasValue ? Enum.GetName(typeof(CashAccountType), account.CashAccountType) : null, + accountType: account.CashAccountType.HasValue + ? Enum.GetName(typeof(CashAccountType), account.CashAccountType) + : null, name: account.Product, description: account.Details, // TODO: The balance type could be something different (which one should be grabbed?) - balance: balances.First(bal => - bal.BalanceType is BalanceType.ClosingBooked or BalanceType.InterimAvailable) + balance: balances.First(bal => + bal.BalanceType is BalanceType.ClosingBooked or BalanceType.InterimAvailable) .BalanceAmount.Amount, currency: account.Currency, iban: account.Iban, bic: account.Bic, bban: account.Bban, ownerName: account.OwnerName, - transactions: new List(), - institutionId: "TODO" + transactions: [], + associatedInstitution: new BankingInstitution("id", "bic", "name", new Uri("http://www.example.com"), []) ); } } From bb47d12f90eb992e19a9309291086a4a9ff3107a Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Tue, 1 Oct 2024 23:49:58 +0200 Subject: [PATCH 02/15] [WIP] Add missing GraphQL operations for BankingInstitution type --- .../Program.cs | 1 + .../BankingInstitutionMutationResolvers.cs | 30 ++++++++++++++ .../AuthenticationRequestRepository.cs | 4 +- .../Repositories/BankAccountRepository.cs | 19 ++++++--- .../BankingInstitutionRepository.cs | 41 +++++++++++++++---- .../Repositories/BaseRepository.cs | 6 +++ .../Repositories/TransactionRepository.cs | 8 ++-- 7 files changed, 88 insertions(+), 21 deletions(-) create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BaseRepository.cs diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs index 26781ae..49ae391 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs @@ -25,6 +25,7 @@ loggerConfig.ReadFrom.Configuration(context.Configuration); }); +// TODO: Only do in development // Forward issues with Serilog itself to console Serilog.Debugging.SelfLog.Enable(Console.Error); diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs new file mode 100644 index 0000000..87e8c58 --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs @@ -0,0 +1,30 @@ +using HotChocolate.Types; +using RobinTTY.PersonalFinanceDashboard.Core.Models; +using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; + +namespace RobinTTY.PersonalFinanceDashboard.Api.Types.Mutations; + +/// +/// related mutation resolvers. +/// +[MutationType] +public class BankingInstitutionMutationResolvers +{ + public async Task CreateBankingInstitution(BankingInstitutionRepository repository, + BankingInstitution bankingInstitution) + { + return await repository.AddBankingInstitution(bankingInstitution); + } + + /// + /// TODO: Return something more than bool? Generalized response across all resolvers? + /// + /// + /// + /// + public async Task DeleteBankingInstitution(BankingInstitutionRepository repository, + string bankingInstitutionId) + { + return await repository.DeleteBankingInstitution(bankingInstitutionId); + } +} diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AuthenticationRequestRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AuthenticationRequestRepository.cs index e88ba49..33f0f30 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AuthenticationRequestRepository.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AuthenticationRequestRepository.cs @@ -35,9 +35,9 @@ public AuthenticationRequestRepository(ApplicationDbContext dbContext, GoCardles } /// - /// Gets all s. + /// Gets a list of s. /// - /// A list of all s. + /// A list of s. public async Task> GetAuthenticationRequests() { // TODO: limit diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs index b59ee29..7015ecd 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs @@ -4,16 +4,19 @@ namespace RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; +/// +/// Manages data retrieval. +/// public class BankAccountRepository { private readonly ApplicationDbContext _dbContext; private readonly BankAccountMapper _bankAccountMapper; /// - /// TODO + /// Creates a new instance of . /// - /// - /// + /// The to use for data retrieval. + /// The mapper used to map ef entities to the domain model. public BankAccountRepository(ApplicationDbContext dbContext, BankAccountMapper bankAccountMapper) { _dbContext = dbContext; @@ -21,10 +24,10 @@ public BankAccountRepository(ApplicationDbContext dbContext, BankAccountMapper b } /// - /// TODO + /// Gets the matching the specified id. /// - /// - /// + /// The id of the to retrieve. + /// The if one ist matched otherwise . public async Task GetBankAccount(Guid accountId) { var accountEntity = @@ -33,6 +36,10 @@ public BankAccountRepository(ApplicationDbContext dbContext, BankAccountMapper b return accountEntity is not null ? _bankAccountMapper.EntityToModel(accountEntity) : null; } + /// + /// Gets a list of s. + /// + /// A list of s. public async Task> GetBankAccounts() { var accountEntities = await _dbContext.BankAccounts.ToListAsync(); diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs index c12e8c4..f66c631 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs @@ -26,7 +26,7 @@ public class BankingInstitutionRepository /// The to use for data retrieval. /// The data provider to use for data retrieval. /// The mapper used to map ef entities to the domain model. - /// TODO + /// Service used to determine if the database data is stale. public BankingInstitutionRepository( ILogger logger, ApplicationDbContext dbContext, @@ -57,11 +57,10 @@ public BankingInstitutionRepository( : _bankingInstitutionMapper.EntityToModel(bankingInstitutionEntity); } - // TODO: Refactor this into appropriate classes /// /// Gets all s. /// - /// A list of all s. + /// A list of s. public async Task> GetBankingInstitutions(string? countryCode) { await RefreshBankingInstitutionsIfStale(); @@ -81,9 +80,9 @@ public async Task> GetBankingInstitutions(string } /// - /// TODO + /// Adds a list of new s. /// - /// + /// The list of s to add. public async Task AddBankingInstitutions(IEnumerable bankingInstitutions) { var institutionEntities = @@ -92,20 +91,44 @@ public async Task AddBankingInstitutions(IEnumerable banking await _dbContext.BankingInstitutions.AddRangeAsync(institutionEntities); await _dbContext.SaveChangesAsync(); } + + /// + /// Adds a new . + /// + /// The to add. + public async Task AddBankingInstitution(BankingInstitution bankingInstitution) + { + var institutionEntities =_bankingInstitutionMapper.ModelToEntity(bankingInstitution); + var result = await _dbContext.BankingInstitutions.AddAsync(institutionEntities); + await _dbContext.SaveChangesAsync(); + + return _bankingInstitutionMapper.EntityToModel(result.Entity); + } /// - /// TODO + /// Deletes all existing s. /// - /// + /// The number of deleted records. public async Task DeleteBankingInstitutions() { return await _dbContext.BankingInstitutions.ExecuteDeleteAsync(); } + + /// + /// Deletes an existing . + /// + /// The id of the to delete. + /// Boolean value indicating whether the operation was successful. + public async Task DeleteBankingInstitution(string institutionId) + { + var result = await _dbContext.BankingInstitutions.Where(t => t.Id == institutionId).ExecuteDeleteAsync(); + return Convert.ToBoolean(result); + } /// - /// TODO + /// Refreshes the list of banking institutions if the data has gone stale. /// - /// + /// TODO private async Task RefreshBankingInstitutionsIfStale() { var dataIsStale = await _dataRetrievalMetadataService.DataIsStale(ThirdPartyDataType.BankingInstitutions); diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BaseRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BaseRepository.cs new file mode 100644 index 0000000..a419c1f --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BaseRepository.cs @@ -0,0 +1,6 @@ +namespace RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; + +// public class BaseRepository +// { +// public T Get() +// } diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/TransactionRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/TransactionRepository.cs index 6a1c8b8..465b0db 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/TransactionRepository.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/TransactionRepository.cs @@ -37,7 +37,7 @@ public async Task> GetTransactions(CancellationToken ca var transactionModels = transactionEntities .Select(t => _transactionMapper.EntityToModel(t)) .ToList(); - + return transactionModels; } @@ -54,7 +54,7 @@ public async Task> GetTransactionsByAccountId(string ac var transactionModels = transactionEntities .Select(t => _transactionMapper.EntityToModel(t)) .ToList(); - + return transactionModels; } @@ -62,7 +62,7 @@ public async Task> GetTransactionsByAccountId(string ac /// Adds a new . /// /// The to add. - /// The added . + /// The to add. // TODO: This should use a TransactionRequest class not Transaction itself public async Task AddTransaction(Transaction transaction) { @@ -90,7 +90,7 @@ public async Task UpdateTransaction(TransactionEntity transac /// /// Deletes an existing . /// - /// The id of the transaction to delete. + /// The id of the to delete. /// Boolean value indicating whether the operation was successful. public async Task DeleteTransaction(string transactionId) { From 172f31ba2630c1f1b61d0b6e8cb2eed54ae18212 Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 7 Oct 2024 01:10:25 +0200 Subject: [PATCH 03/15] Add docker compose for seq --- deployment/docker-compose.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 deployment/docker-compose.yml diff --git a/deployment/docker-compose.yml b/deployment/docker-compose.yml new file mode 100644 index 0000000..927af70 --- /dev/null +++ b/deployment/docker-compose.yml @@ -0,0 +1,11 @@ +name: PersonalFinanceDashboard + +services: + seq: + image: datalust/seq + container_name: seq + environment: + - ACCEPT_EULA=Y + ports: + - "5341:5341" + - "8001:80" From 4cca6b4b7bf8ff100eeee47398f6111d38a22187 Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 7 Oct 2024 02:21:47 +0200 Subject: [PATCH 04/15] Modify Serilog logging options --- .../appsettings.Development.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/appsettings.Development.json b/src/server/RobinTTY.PersonalFinanceDashboard.API/appsettings.Development.json index 05c7fc9..b290338 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/appsettings.Development.json +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/appsettings.Development.json @@ -7,7 +7,8 @@ "MinimumLevel": { "Default": "Information", "Override": { - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" } }, "WriteTo": [ @@ -21,6 +22,6 @@ } } ], - "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ] + "Enrich": [ "FromLogContext" ] } } From 27ade8ca6b3c0055f88a67f905b0f68aa6aa549f Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 7 Oct 2024 02:36:51 +0200 Subject: [PATCH 05/15] Update xml documentation and docker compose --- deployment/docker-compose.yml | 2 +- .../Mutations/BankingInstitutionMutationResolvers.cs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/deployment/docker-compose.yml b/deployment/docker-compose.yml index 927af70..b0dc673 100644 --- a/deployment/docker-compose.yml +++ b/deployment/docker-compose.yml @@ -2,7 +2,7 @@ name: PersonalFinanceDashboard services: seq: - image: datalust/seq + image: datalust/seq:latest container_name: seq environment: - ACCEPT_EULA=Y diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs index 87e8c58..3e387f3 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs @@ -10,17 +10,23 @@ namespace RobinTTY.PersonalFinanceDashboard.Api.Types.Mutations; [MutationType] public class BankingInstitutionMutationResolvers { + /// + /// Create a new banking institution. + /// + /// The injected repository to use for data retrieval. + /// The banking institution to create. public async Task CreateBankingInstitution(BankingInstitutionRepository repository, BankingInstitution bankingInstitution) { return await repository.AddBankingInstitution(bankingInstitution); } + // TODO: Return something more than bool? Generalized response across all resolvers? /// - /// TODO: Return something more than bool? Generalized response across all resolvers? + /// Delete an existing banking institution. /// - /// - /// + /// The injected repository to use for data retrieval. + /// The id of the banking institution to delete. /// public async Task DeleteBankingInstitution(BankingInstitutionRepository repository, string bankingInstitutionId) From 03f94d0e55925e78b8debbe57f5cdb63224ede29 Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Sat, 12 Oct 2024 06:38:32 +0200 Subject: [PATCH 06/15] Add missing operations for BankingInstitutions. Resolves #66 --- .../BankingInstitutionMutationResolvers.cs | 12 +++++++++++- .../BankingInstitutionRepository.cs | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs index 3e387f3..8e8058a 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs @@ -21,13 +21,23 @@ public async Task CreateBankingInstitution(BankingInstitutio return await repository.AddBankingInstitution(bankingInstitution); } + /// + /// Update an existing banking institution. + /// + /// The injected repository to use for data retrieval. + /// The banking institution to update. + public async Task UpdateBankingInstitution(BankingInstitutionRepository repository, + BankingInstitution bankingInstitution) + { + return await repository.UpdateBankingInstitution(bankingInstitution); + } + // TODO: Return something more than bool? Generalized response across all resolvers? /// /// Delete an existing banking institution. /// /// The injected repository to use for data retrieval. /// The id of the banking institution to delete. - /// public async Task DeleteBankingInstitution(BankingInstitutionRepository repository, string bankingInstitutionId) { diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs index f66c631..ad11b80 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs @@ -48,6 +48,8 @@ public BankingInstitutionRepository( /// The if one ist matched otherwise . public async Task GetBankingInstitution(string institutionId) { + await RefreshBankingInstitutionsIfStale(); + // var request = await _dataProvider.GetBankingInstitution(institutionId); var bankingInstitutionEntity = await _dbContext.BankingInstitutions .SingleOrDefaultAsync(institution => institution.Id == institutionId); @@ -105,6 +107,20 @@ public async Task AddBankingInstitution(BankingInstitution b return _bankingInstitutionMapper.EntityToModel(result.Entity); } + /// + /// Updates an existing . + /// + /// The banking institution to update. + /// The updated . + public async Task UpdateBankingInstitution(BankingInstitution bankingInstitution) + { + var institutionEntity = _bankingInstitutionMapper.ModelToEntity(bankingInstitution); + var updatedEntity = _dbContext.BankingInstitutions.Update(institutionEntity); + await _dbContext.SaveChangesAsync(); + + return _bankingInstitutionMapper.EntityToModel(updatedEntity.Entity); + } + /// /// Deletes all existing s. /// @@ -148,6 +164,7 @@ private async Task RefreshBankingInstitutionsIfStale() else { // TODO: What to do in case of failure should depend on if we already have data + // Log failure and continue, maybe also send a notification to frontend throw new NotImplementedException(); } } From 30d62b403a11bbb34adad98a931578874e2bdb4f Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 14 Oct 2024 01:12:59 +0200 Subject: [PATCH 07/15] Cleanup Program.cs, use C# 13 --- .../Extensions/HostBuilderExtensions.cs | 26 ++++ .../Extensions/ServiceCollectionExtensions.cs | 119 ++++++++++++++++++ .../Extensions/WebApplicationExtensions.cs | 38 ++++++ .../Program.cs | 91 ++------------ ...binTTY.PersonalFinanceDashboard.API.csproj | 2 + .../Utility/AppConfigurationManager.cs | 1 + ...inTTY.PersonalFinanceDashboard.Core.csproj | 1 + ...PersonalFinanceDashboard.DataImport.csproj | 1 + .../AuthenticationRequestRepository.cs | 16 +-- .../BankingInstitutionRepository.cs | 10 +- ...onalFinanceDashboard.Infrastructure.csproj | 1 + ...er.cs => GoCardlessDataProviderService.cs} | 4 +- ...ceDashboard.ThirdPartyDataProviders.csproj | 1 + 13 files changed, 216 insertions(+), 95 deletions(-) create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/HostBuilderExtensions.cs create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/WebApplicationExtensions.cs rename src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/{GoCardlessDataProvider.cs => GoCardlessDataProviderService.cs} (98%) diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/HostBuilderExtensions.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/HostBuilderExtensions.cs new file mode 100644 index 0000000..20310be --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/HostBuilderExtensions.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Hosting; + +namespace RobinTTY.PersonalFinanceDashboard.Api.Extensions; + +/// +/// Provides extensions on the type. +/// +public static class HostBuilderExtensions +{ + /// + /// Configures the logging of the application via Serilog. + /// + /// The host to configure the logging for. + /// A reference to the after the operation has completed. + public static IHostBuilder ConfigureSerilog(this IHostBuilder hostBuilder) + { + // TODO: Only do in development + // Forward issues with Serilog itself to console + Serilog.Debugging.SelfLog.Enable(Console.Error); + + return hostBuilder.UseSerilog((context, loggerConfig) => + { + loggerConfig.ReadFrom.Configuration(context.Configuration); + }); + } +} diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/ServiceCollectionExtensions.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..899bd51 --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,119 @@ +using System.Net.Http; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using RobinTTY.NordigenApiClient.Models; +using RobinTTY.PersonalFinanceDashboard.API.Utility; +using RobinTTY.PersonalFinanceDashboard.Infrastructure; +using RobinTTY.PersonalFinanceDashboard.Infrastructure.Mappers; +using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; +using RobinTTY.PersonalFinanceDashboard.Infrastructure.Services; +using RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders; + +namespace RobinTTY.PersonalFinanceDashboard.Api.Extensions; + +/// +/// Provides extensions for the interface. +/// +public static class ServiceCollectionExtensions +{ + /// + /// Registers all repositories in the service collection. + /// + /// The service collection to which to add the services. + /// A reference to the after the operation has completed. + // TODO: automatic registration of repositories via codegen? + public static IServiceCollection AddRepositories(this IServiceCollection services) + { + return services + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped(); + } + + /// + /// Registers all entity mappers in the service collection. + /// + /// The service collection to which to add the services. + /// A reference to the after the operation has completed. + public static IServiceCollection AddEntityMappers(this IServiceCollection services) + { + return services + .AddSingleton() + .AddSingleton() + .AddSingleton(); + } + + /// + /// Registers all necessary HotChocolate (GraphQL) features in the service collection. + /// + /// The service collection to which to add the services. + /// A reference to the after the operation has completed. + public static IServiceCollection AddGraphQlServices(this IServiceCollection services) + { + var requestExecutorBuilder = services + // TODO: Configure cost analyzer at some point (enforces maximum query costs) + .AddGraphQLServer(disableCostAnalyzer: true) + // Adds all GraphQL query and mutation types using the code generator (looks for attributes) + .AddTypes() + // TODO: Document what the different extensions methods do + // AddQueryConventions: https://www.youtube.com/watch?v=yoW2Mt6C0Cg + .AddQueryConventions() + .AddMutationConventions(); + + return requestExecutorBuilder.Services; + } + + /// + /// Adds all necessary miscellaneous services for this application. + /// + /// The service collection to which to add the services. + /// A reference to the after the operation has completed. + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + return services + .AddSingleton() + .AddScoped(); + } + + /// + /// Adds all necessary configuration data for this application. + /// + /// The service collection to which to add the services. + /// A reference to the after the operation has completed. + public static IServiceCollection AddApplicationConfiguration(this IServiceCollection services) + { + var appConfig = AppConfigurationManager.AppConfiguration; + return services.AddSingleton(new NordigenClientCredentials(appConfig.NordigenApi!.SecretId, + appConfig.NordigenApi.SecretKey)); + } + + /// + /// Adds a configured to the service collection. + /// + /// The service collection to which to add the services. + /// A reference to the after the operation has completed. + public static IServiceCollection AddConfiguredHttpClient(this IServiceCollection services) + { + return services + .AddHttpClient() + .AddCors(options => + { + // TODO: update to sensible policy + options.AddDefaultPolicy(policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); + }); + } + + /// + /// Adds the database the application uses to the service collection. + /// + /// The service collection to which to add the services. + /// A reference to the after the operation has completed. + public static IServiceCollection AddDatabase(this IServiceCollection services) + { + // TODO: The filepath shouldn't be hardcoded => configuration + return services.AddDbContextPool(options => + options.UseSqlite("Data Source=../RobinTTY.PersonalFinanceDashboard.Infrastructure/application.db")); + } +} diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/WebApplicationExtensions.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/WebApplicationExtensions.cs new file mode 100644 index 0000000..38d0b6f --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/WebApplicationExtensions.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using RobinTTY.PersonalFinanceDashboard.Infrastructure; + +namespace RobinTTY.PersonalFinanceDashboard.Api.Extensions; + +/// +/// Provides extensions for the type. +/// +public static class WebApplicationExtensions +{ + /// + /// Configures the app to use all necessary GraphQL features. + /// + /// The instance to configure. + /// Command line arguments to be passed to the host. + public static void UseGraphQl(this WebApplication app, string[] args) + { + app.MapGraphQL(); + + // Allows exporting the GraphQL Schema via the command line: dotnet run -- schema export --output schema.graphql + // https://chillicream.com/docs/hotchocolate/v13/server/command-line + app.RunWithGraphQLCommands(args); + } + + /// + /// Applies all outstanding migrations on the database. + /// + /// The instance to use to resolve the db context. + public static void ApplyMigrations(this WebApplication app) + { + using var scope = app.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); + } +} diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs index 49ae391..d83d8f4 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs @@ -6,93 +6,24 @@ global using Serilog; global using HotChocolate; using Microsoft.AspNetCore.Builder; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using RobinTTY.PersonalFinanceDashboard.API.Utility; -using RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders; -using RobinTTY.NordigenApiClient.Models; -using RobinTTY.PersonalFinanceDashboard.Infrastructure; -using RobinTTY.PersonalFinanceDashboard.Infrastructure.Mappers; -using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; -using RobinTTY.PersonalFinanceDashboard.Infrastructure.Services; +using RobinTTY.PersonalFinanceDashboard.Api.Extensions; var builder = WebApplication.CreateBuilder(args); -var appConfig = AppConfigurationManager.AppConfiguration; -// Configure logging -builder.Host.UseSerilog((context, loggerConfig) => -{ - loggerConfig.ReadFrom.Configuration(context.Configuration); -}); +builder.Host.ConfigureSerilog(); -// TODO: Only do in development -// Forward issues with Serilog itself to console -Serilog.Debugging.SelfLog.Enable(Console.Error); - -// HTTP setup -builder.Services - .AddHttpClient() - .AddCors(options => - { - // TODO: update to sensible policy - options.AddDefaultPolicy(policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); - }); - -// DB setup -// TODO: The filepath shouldn't be hardcoded -builder.Services.AddDbContextPool(options => - options.UseSqlite("Data Source=../RobinTTY.PersonalFinanceDashboard.Infrastructure/application.db")); - -// Mappers -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); - -// TODO: automatic registration of repositories via codegen? -// Repositories -builder.Services - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped(); - -// Services -builder.Services - .AddScoped(); - -// Others -builder.Services - .AddSingleton(new NordigenClientCredentials(appConfig.NordigenApi!.SecretId, appConfig.NordigenApi.SecretKey)) - .AddSingleton(); - -// TODO: Create a high level overview of the architecture that should apply -// - There can be many data providers -// - Data providers could be configured via the front-end -// - For the beginning it is smart to start with one provider to keep complexity low -// - Since the authentication/retrieval logic will differ from provider to provider, it will be difficult -// to abstract this logic away into one unified interface, so maybe I will need to refine/scrap this idea later... - -// HotChocolate GraphQL setup -builder.Services - // TODO: Configure cost analyzer at some point (enforces maximum query costs) - .AddGraphQLServer(disableCostAnalyzer: true) - // TODO: Document what the different extensions methods do - .AddTypes() - // AddQueryConventions: https://www.youtube.com/watch?v=yoW2Mt6C0Cg - .AddQueryConventions() - .AddMutationConventions(); +builder.Services.AddDatabase(); +builder.Services.AddConfiguredHttpClient(); +builder.Services.AddApplicationConfiguration(); +builder.Services.AddApplicationServices(); +builder.Services.AddEntityMappers(); +builder.Services.AddRepositories(); +builder.Services.AddGraphQlServices(); var app = builder.Build(); -// Apply database migrations at startup -using (var scope = app.Services.CreateScope()) -{ - var db = scope.ServiceProvider.GetRequiredService(); - db.Database.Migrate(); -} - +app.ApplyMigrations(); app.UseSerilogRequestLogging(); app.UseCors(); -app.MapGraphQL(); +app.UseGraphQl(args); app.Run(); diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/RobinTTY.PersonalFinanceDashboard.API.csproj b/src/server/RobinTTY.PersonalFinanceDashboard.API/RobinTTY.PersonalFinanceDashboard.API.csproj index 8ac6ee4..e6fdf0c 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/RobinTTY.PersonalFinanceDashboard.API.csproj +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/RobinTTY.PersonalFinanceDashboard.API.csproj @@ -7,11 +7,13 @@ true RobinTTY.PersonalFinanceDashboard.Api RobinTTY.PersonalFinanceDashboard.Api + default + all diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs index efce00a..c7eadf4 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs @@ -28,6 +28,7 @@ public static AppConfiguration AppConfiguration /// The created . private static IServiceProvider GetServiceProvider() { + // TODO: Add environment variables for configuration through Docker? var configuration = new ConfigurationBuilder() .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) .AddJsonFile("appsettings.json", false, true) diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Core/RobinTTY.PersonalFinanceDashboard.Core.csproj b/src/server/RobinTTY.PersonalFinanceDashboard.Core/RobinTTY.PersonalFinanceDashboard.Core.csproj index da5c1df..b762a78 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Core/RobinTTY.PersonalFinanceDashboard.Core.csproj +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Core/RobinTTY.PersonalFinanceDashboard.Core.csproj @@ -5,6 +5,7 @@ net8.0 enable enable + default diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.DataImport/RobinTTY.PersonalFinanceDashboard.DataImport.csproj b/src/server/RobinTTY.PersonalFinanceDashboard.DataImport/RobinTTY.PersonalFinanceDashboard.DataImport.csproj index b7886ec..c64551a 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.DataImport/RobinTTY.PersonalFinanceDashboard.DataImport.csproj +++ b/src/server/RobinTTY.PersonalFinanceDashboard.DataImport/RobinTTY.PersonalFinanceDashboard.DataImport.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + default diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AuthenticationRequestRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AuthenticationRequestRepository.cs index 33f0f30..3bed6f4 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AuthenticationRequestRepository.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/AuthenticationRequestRepository.cs @@ -10,17 +10,17 @@ namespace RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; public class AuthenticationRequestRepository { private readonly ApplicationDbContext _dbContext; - private readonly GoCardlessDataProvider _dataProvider; + private readonly GoCardlessDataProviderService _dataProviderService; /// /// Creates a new instance of . /// /// The to use for data retrieval. - /// The data provider to use for data retrieval. - public AuthenticationRequestRepository(ApplicationDbContext dbContext, GoCardlessDataProvider dataProvider) + /// The data provider to use for data retrieval. + public AuthenticationRequestRepository(ApplicationDbContext dbContext, GoCardlessDataProviderService dataProviderService) { _dbContext = dbContext; - _dataProvider = dataProvider; + _dataProviderService = dataProviderService; } /// @@ -30,7 +30,7 @@ public AuthenticationRequestRepository(ApplicationDbContext dbContext, GoCardles /// The if one ist matched otherwise . public async Task GetAuthenticationRequest(string authenticationId) { - var requests = await _dataProvider.GetAuthenticationRequest(authenticationId); + var requests = await _dataProviderService.GetAuthenticationRequest(authenticationId); return requests.Result!; } @@ -41,7 +41,7 @@ public AuthenticationRequestRepository(ApplicationDbContext dbContext, GoCardles public async Task> GetAuthenticationRequests() { // TODO: limit - var requests = await _dataProvider.GetAuthenticationRequests(100); + var requests = await _dataProviderService.GetAuthenticationRequests(100); return requests.Result!; } @@ -54,7 +54,7 @@ public async Task> GetAuthenticationRequests( public async Task AddAuthenticationRequest(string institutionId, string redirectUri) { // TODO: URI validation (here or in resolver?) - var request = await _dataProvider.CreateAuthenticationRequest(institutionId, new Uri(redirectUri)); + var request = await _dataProviderService.CreateAuthenticationRequest(institutionId, new Uri(redirectUri)); return request.Result!; } @@ -65,7 +65,7 @@ public async Task AddAuthenticationRequest(string institu /// TODO public async Task DeleteAuthenticationRequest(string authenticationId) { - var request = await _dataProvider.DeleteAuthenticationRequest(authenticationId); + var request = await _dataProviderService.DeleteAuthenticationRequest(authenticationId); return request.Result!; } } \ No newline at end of file diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs index ad11b80..ebc2fa6 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankingInstitutionRepository.cs @@ -15,7 +15,7 @@ public class BankingInstitutionRepository { private readonly ILogger _logger; private readonly ApplicationDbContext _dbContext; - private readonly GoCardlessDataProvider _dataProvider; + private readonly GoCardlessDataProviderService _dataProviderService; private readonly BankingInstitutionMapper _bankingInstitutionMapper; private readonly ThirdPartyDataRetrievalMetadataService _dataRetrievalMetadataService; @@ -24,19 +24,19 @@ public class BankingInstitutionRepository /// /// Logger used for monitoring purposes. /// The to use for data retrieval. - /// The data provider to use for data retrieval. + /// The data provider to use for data retrieval. /// The mapper used to map ef entities to the domain model. /// Service used to determine if the database data is stale. public BankingInstitutionRepository( ILogger logger, ApplicationDbContext dbContext, - GoCardlessDataProvider dataProvider, + GoCardlessDataProviderService dataProviderService, BankingInstitutionMapper bankingInstitutionMapper, ThirdPartyDataRetrievalMetadataService dataRetrievalMetadataService) { _logger = logger; _dbContext = dbContext; - _dataProvider = dataProvider; + _dataProviderService = dataProviderService; _bankingInstitutionMapper = bankingInstitutionMapper; _dataRetrievalMetadataService = dataRetrievalMetadataService; } @@ -150,7 +150,7 @@ private async Task RefreshBankingInstitutionsIfStale() var dataIsStale = await _dataRetrievalMetadataService.DataIsStale(ThirdPartyDataType.BankingInstitutions); if (dataIsStale) { - var response = await _dataProvider.GetBankingInstitutions(); + var response = await _dataProviderService.GetBankingInstitutions(); if (response.IsSuccessful) { await DeleteBankingInstitutions(); diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/RobinTTY.PersonalFinanceDashboard.Infrastructure.csproj b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/RobinTTY.PersonalFinanceDashboard.Infrastructure.csproj index 2847271..348c402 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/RobinTTY.PersonalFinanceDashboard.Infrastructure.csproj +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/RobinTTY.PersonalFinanceDashboard.Infrastructure.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + default diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/GoCardlessDataProvider.cs b/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/GoCardlessDataProviderService.cs similarity index 98% rename from src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/GoCardlessDataProvider.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/GoCardlessDataProviderService.cs index 00c4eca..7083d16 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/GoCardlessDataProvider.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/GoCardlessDataProviderService.cs @@ -14,11 +14,11 @@ namespace RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders; /// // TODO: Probably should already return a generic ThirdPartyError (type) here // TODO: Cancellation token support -public class GoCardlessDataProvider +public class GoCardlessDataProviderService { private readonly NordigenClient _client; - public GoCardlessDataProvider(HttpClient httpClient, NordigenClientCredentials credentials) + public GoCardlessDataProviderService(HttpClient httpClient, NordigenClientCredentials credentials) { // TODO: injection _client = new NordigenClient(httpClient, credentials); diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders.csproj b/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders.csproj index d481613..ba9ea32 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders.csproj +++ b/src/server/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders/RobinTTY.PersonalFinanceDashboard.ThirdPartyDataProviders.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + default From 0e7edaceabec79c027fae51c1d243435b66f2f01 Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 14 Oct 2024 02:30:41 +0200 Subject: [PATCH 08/15] Move global usings to its own file --- .../GlobalUsingDirectives.cs | 7 +++++++ .../RobinTTY.PersonalFinanceDashboard.API/Program.cs | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.API/GlobalUsingDirectives.cs diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/GlobalUsingDirectives.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/GlobalUsingDirectives.cs new file mode 100644 index 0000000..c3680c0 --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/GlobalUsingDirectives.cs @@ -0,0 +1,7 @@ +global using System; +global using System.Linq; +global using System.Threading; +global using System.Collections.Generic; +global using System.Threading.Tasks; +global using Serilog; +global using HotChocolate; diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs index d83d8f4..11703f4 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs @@ -1,10 +1,3 @@ -global using System; -global using System.Linq; -global using System.Threading; -global using System.Collections.Generic; -global using System.Threading.Tasks; -global using Serilog; -global using HotChocolate; using Microsoft.AspNetCore.Builder; using RobinTTY.PersonalFinanceDashboard.Api.Extensions; From 0aae825b03a69c240260060469a1ceaaca033eb1 Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 14 Oct 2024 02:34:19 +0200 Subject: [PATCH 09/15] Remove outdated logger logic --- .../Utility/LoggerManager.cs | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/LoggerManager.cs diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/LoggerManager.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/LoggerManager.cs deleted file mode 100644 index 80c7923..0000000 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/LoggerManager.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace RobinTTY.PersonalFinanceDashboard.API.Utility; - -// TODO: Setup logger -/// -/// Manages the global logger instance. -/// -public class LoggerManager -{ - /// - /// Sets up the logger and returns it. - /// - /// Instance of . - public static ILogger GetDefaultLogger() - { - var logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .WriteTo.Console() - .WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day) - .CreateLogger(); - - Log.Logger = logger; - return logger; - } -} \ No newline at end of file From 9a047b8c559ccf51c3bc07e4c16e239d10d2aeb4 Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 14 Oct 2024 02:40:07 +0200 Subject: [PATCH 10/15] Move MockDataAccessService to infrastructure project --- .../RobinTTY.PersonalFinanceDashboard.API.csproj | 1 - .../RobinTTY.PersonalFinanceDashboard.Infrastructure.csproj | 1 + .../Services}/MockDataAccessService.cs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/server/{RobinTTY.PersonalFinanceDashboard.API/Utility => RobinTTY.PersonalFinanceDashboard.Infrastructure/Services}/MockDataAccessService.cs (96%) diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/RobinTTY.PersonalFinanceDashboard.API.csproj b/src/server/RobinTTY.PersonalFinanceDashboard.API/RobinTTY.PersonalFinanceDashboard.API.csproj index e6fdf0c..d4d8c93 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/RobinTTY.PersonalFinanceDashboard.API.csproj +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/RobinTTY.PersonalFinanceDashboard.API.csproj @@ -11,7 +11,6 @@ - diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/RobinTTY.PersonalFinanceDashboard.Infrastructure.csproj b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/RobinTTY.PersonalFinanceDashboard.Infrastructure.csproj index 348c402..33efbb3 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/RobinTTY.PersonalFinanceDashboard.Infrastructure.csproj +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/RobinTTY.PersonalFinanceDashboard.Infrastructure.csproj @@ -13,6 +13,7 @@ + diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/MockDataAccessService.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Services/MockDataAccessService.cs similarity index 96% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/MockDataAccessService.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Services/MockDataAccessService.cs index 9a6e5ac..29fc107 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/MockDataAccessService.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Services/MockDataAccessService.cs @@ -1,7 +1,7 @@ using Bogus; using RobinTTY.PersonalFinanceDashboard.Core.Models; -namespace RobinTTY.PersonalFinanceDashboard.API.Utility; +namespace RobinTTY.PersonalFinanceDashboard.Infrastructure.Services; /// /// Provides mocked data for testing/demo purposes. From 3ca7c945a48042939ed5197eb467dc09d1fa7a1e Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 14 Oct 2024 02:48:09 +0200 Subject: [PATCH 11/15] Restructure types/resolvers --- .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../Properties/ModuleInfo.cs | 2 +- .../Mutations/AuthenticationMutationResolvers.cs | 2 +- .../Mutations/BankingInstitutionMutationResolvers.cs | 2 +- .../Mutations/TransactionMutationResolvers.cs | 2 +- .../Queries/AuthenticationRequestQueryResolvers.cs | 2 +- .../{Types => Resolvers}/Queries/BankAccountQueryResolvers.cs | 2 +- .../Queries/BankingInstitutionQueryResolvers.cs | 3 +-- .../{Types => Resolvers}/Queries/TransactionQueryResolvers.cs | 2 +- .../{Types => Utility}/AppConfiguration.cs | 2 +- .../Utility/AppConfigurationManager.cs | 1 + 11 files changed, 11 insertions(+), 11 deletions(-) rename src/server/RobinTTY.PersonalFinanceDashboard.API/{Types => Resolvers}/Mutations/AuthenticationMutationResolvers.cs (95%) rename src/server/RobinTTY.PersonalFinanceDashboard.API/{Types => Resolvers}/Mutations/BankingInstitutionMutationResolvers.cs (96%) rename src/server/RobinTTY.PersonalFinanceDashboard.API/{Types => Resolvers}/Mutations/TransactionMutationResolvers.cs (95%) rename src/server/RobinTTY.PersonalFinanceDashboard.API/{Types => Resolvers}/Queries/AuthenticationRequestQueryResolvers.cs (94%) rename src/server/RobinTTY.PersonalFinanceDashboard.API/{Types => Resolvers}/Queries/BankAccountQueryResolvers.cs (93%) rename src/server/RobinTTY.PersonalFinanceDashboard.API/{Types => Resolvers}/Queries/BankingInstitutionQueryResolvers.cs (91%) rename src/server/RobinTTY.PersonalFinanceDashboard.API/{Types => Resolvers}/Queries/TransactionQueryResolvers.cs (95%) rename src/server/RobinTTY.PersonalFinanceDashboard.API/{Types => Utility}/AppConfiguration.cs (96%) diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/ServiceCollectionExtensions.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/ServiceCollectionExtensions.cs index 899bd51..9d1427b 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/ServiceCollectionExtensions.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/ServiceCollectionExtensions.cs @@ -56,7 +56,7 @@ public static IServiceCollection AddGraphQlServices(this IServiceCollection serv // TODO: Configure cost analyzer at some point (enforces maximum query costs) .AddGraphQLServer(disableCostAnalyzer: true) // Adds all GraphQL query and mutation types using the code generator (looks for attributes) - .AddTypes() + .AddResolvers() // TODO: Document what the different extensions methods do // AddQueryConventions: https://www.youtube.com/watch?v=yoW2Mt6C0Cg .AddQueryConventions() diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Properties/ModuleInfo.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Properties/ModuleInfo.cs index 4cfb15d..fd1b95d 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Properties/ModuleInfo.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Properties/ModuleInfo.cs @@ -1 +1 @@ -[assembly: Module("Types")] \ No newline at end of file +[assembly: Module("Resolvers")] \ No newline at end of file diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/AuthenticationMutationResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/AuthenticationMutationResolvers.cs similarity index 95% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/AuthenticationMutationResolvers.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/AuthenticationMutationResolvers.cs index 5d393f0..38744f3 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/AuthenticationMutationResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/AuthenticationMutationResolvers.cs @@ -3,7 +3,7 @@ using RobinTTY.PersonalFinanceDashboard.Core.Models; using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; -namespace RobinTTY.PersonalFinanceDashboard.Api.Types.Mutations; +namespace RobinTTY.PersonalFinanceDashboard.Api.Resolvers.Mutations; /// /// related mutation resolvers. diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/BankingInstitutionMutationResolvers.cs similarity index 96% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/BankingInstitutionMutationResolvers.cs index 8e8058a..54f7731 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/BankingInstitutionMutationResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/BankingInstitutionMutationResolvers.cs @@ -2,7 +2,7 @@ using RobinTTY.PersonalFinanceDashboard.Core.Models; using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; -namespace RobinTTY.PersonalFinanceDashboard.Api.Types.Mutations; +namespace RobinTTY.PersonalFinanceDashboard.Api.Resolvers.Mutations; /// /// related mutation resolvers. diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/TransactionMutationResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/TransactionMutationResolvers.cs similarity index 95% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/TransactionMutationResolvers.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/TransactionMutationResolvers.cs index 40a66e0..a993ba1 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Mutations/TransactionMutationResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/TransactionMutationResolvers.cs @@ -2,7 +2,7 @@ using RobinTTY.PersonalFinanceDashboard.Core.Models; using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; -namespace RobinTTY.PersonalFinanceDashboard.Api.Types.Mutations; +namespace RobinTTY.PersonalFinanceDashboard.Api.Resolvers.Mutations; /// /// related mutation resolvers. diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/AuthenticationRequestQueryResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/AuthenticationRequestQueryResolvers.cs similarity index 94% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/AuthenticationRequestQueryResolvers.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/AuthenticationRequestQueryResolvers.cs index c5608eb..e3eec80 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/AuthenticationRequestQueryResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/AuthenticationRequestQueryResolvers.cs @@ -2,7 +2,7 @@ using RobinTTY.PersonalFinanceDashboard.Core.Models; using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; -namespace RobinTTY.PersonalFinanceDashboard.Api.Types.Queries; +namespace RobinTTY.PersonalFinanceDashboard.Api.Resolvers.Queries; /// /// related query resolvers. diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/BankAccountQueryResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/BankAccountQueryResolvers.cs similarity index 93% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/BankAccountQueryResolvers.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/BankAccountQueryResolvers.cs index 7b2db44..0f9f531 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/BankAccountQueryResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/BankAccountQueryResolvers.cs @@ -2,7 +2,7 @@ using RobinTTY.PersonalFinanceDashboard.Core.Models; using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; -namespace RobinTTY.PersonalFinanceDashboard.Api.Types.Queries; +namespace RobinTTY.PersonalFinanceDashboard.Api.Resolvers.Queries; /// /// related query resolvers. diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/BankingInstitutionQueryResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/BankingInstitutionQueryResolvers.cs similarity index 91% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/BankingInstitutionQueryResolvers.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/BankingInstitutionQueryResolvers.cs index ae4187e..401f959 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/BankingInstitutionQueryResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/BankingInstitutionQueryResolvers.cs @@ -1,9 +1,8 @@ using HotChocolate.Types; using RobinTTY.PersonalFinanceDashboard.Core.Models; using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; -using RobinTTY.PersonalFinanceDashboard.Infrastructure.Services; -namespace RobinTTY.PersonalFinanceDashboard.Api.Types.Queries; +namespace RobinTTY.PersonalFinanceDashboard.Api.Resolvers.Queries; /// /// related query resolvers. diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/TransactionQueryResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/TransactionQueryResolvers.cs similarity index 95% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/TransactionQueryResolvers.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/TransactionQueryResolvers.cs index be99278..ce5e872 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/Queries/TransactionQueryResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/TransactionQueryResolvers.cs @@ -2,7 +2,7 @@ using RobinTTY.PersonalFinanceDashboard.Core.Models; using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; -namespace RobinTTY.PersonalFinanceDashboard.Api.Types.Queries; +namespace RobinTTY.PersonalFinanceDashboard.Api.Resolvers.Queries; /// /// related query resolvers. diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/AppConfiguration.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfiguration.cs similarity index 96% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Types/AppConfiguration.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfiguration.cs index 526cfc3..8dc9db5 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Types/AppConfiguration.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfiguration.cs @@ -1,4 +1,4 @@ -namespace RobinTTY.PersonalFinanceDashboard.Api.Types; +namespace RobinTTY.PersonalFinanceDashboard.Api.Utility; /// /// The configuration of the application server. diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs index c7eadf4..b5c87a6 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using RobinTTY.PersonalFinanceDashboard.Api.Types; +using RobinTTY.PersonalFinanceDashboard.Api.Utility; namespace RobinTTY.PersonalFinanceDashboard.API.Utility; From 898e36f330143d73404bfeb9f4e4ae1b90041121 Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 14 Oct 2024 02:50:36 +0200 Subject: [PATCH 12/15] Remove outdated import --- .../Utility/AppConfigurationManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs index b5c87a6..d0c4b21 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Utility/AppConfigurationManager.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using RobinTTY.PersonalFinanceDashboard.Api.Types; using RobinTTY.PersonalFinanceDashboard.Api.Utility; namespace RobinTTY.PersonalFinanceDashboard.API.Utility; From faab31758c181ce4a0a99f7a42374c2cd8b86e11 Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Mon, 14 Oct 2024 03:08:29 +0200 Subject: [PATCH 13/15] Only log Serilog errors in development environment --- ...ons.cs => WebApplicationBuilderExtensions.cs} | 16 ++++++++++------ .../Program.cs | 3 +-- 2 files changed, 11 insertions(+), 8 deletions(-) rename src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/{HostBuilderExtensions.cs => WebApplicationBuilderExtensions.cs} (51%) diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/HostBuilderExtensions.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/WebApplicationBuilderExtensions.cs similarity index 51% rename from src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/HostBuilderExtensions.cs rename to src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/WebApplicationBuilderExtensions.cs index 20310be..948d14c 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/HostBuilderExtensions.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/WebApplicationBuilderExtensions.cs @@ -1,24 +1,28 @@ -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; namespace RobinTTY.PersonalFinanceDashboard.Api.Extensions; /// /// Provides extensions on the type. /// -public static class HostBuilderExtensions +public static class WebApplicationBuilderExtensions { /// /// Configures the logging of the application via Serilog. /// - /// The host to configure the logging for. + /// The host to configure the logging for. /// A reference to the after the operation has completed. - public static IHostBuilder ConfigureSerilog(this IHostBuilder hostBuilder) + public static IHostBuilder ConfigureSerilog(this WebApplicationBuilder webApplicationBuilder) { // TODO: Only do in development // Forward issues with Serilog itself to console - Serilog.Debugging.SelfLog.Enable(Console.Error); + if (webApplicationBuilder.Environment.IsDevelopment()) + { + Serilog.Debugging.SelfLog.Enable(Console.Error); + } - return hostBuilder.UseSerilog((context, loggerConfig) => + return webApplicationBuilder.Host.UseSerilog((context, loggerConfig) => { loggerConfig.ReadFrom.Configuration(context.Configuration); }); diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs index 11703f4..3c08ab5 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs @@ -2,8 +2,7 @@ using RobinTTY.PersonalFinanceDashboard.Api.Extensions; var builder = WebApplication.CreateBuilder(args); - -builder.Host.ConfigureSerilog(); +builder.ConfigureSerilog(); builder.Services.AddDatabase(); builder.Services.AddConfiguredHttpClient(); From 7606718b3a05d38b1fd7ebe8455aceaa09dfc19d Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Fri, 18 Oct 2024 23:14:59 +0200 Subject: [PATCH 14/15] Implement remaining GraphQL operations for bank accounts. Resolves #67 --- .../Mutations/BankAccountMutations.cs | 44 +++++++++++++++++++ .../Queries/BankAccountQueryResolvers.cs | 5 ++- .../Models/BankAccount.cs | 4 +- .../Repositories/BankAccountRepository.cs | 38 ++++++++++++++++ 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/BankAccountMutations.cs diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/BankAccountMutations.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/BankAccountMutations.cs new file mode 100644 index 0000000..96e7f85 --- /dev/null +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/BankAccountMutations.cs @@ -0,0 +1,44 @@ +using HotChocolate.Types; +using RobinTTY.PersonalFinanceDashboard.Core.Models; +using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; + +namespace RobinTTY.PersonalFinanceDashboard.Api.Resolvers.Mutations; + +/// +/// related mutation resolvers. +/// +[MutationType] +public class BankAccountMutations +{ + /// + /// Create a new banking account. + /// + /// The injected repository to use for data retrieval. + /// The banking account to create. + public async Task CreateBankAccount(BankAccountRepository repository, BankAccount bankAccount) + { + return await repository.AddBankAccount(bankAccount); + } + + /// + /// Update an existing banking account. + /// + /// The injected repository to use for data retrieval. + /// The banking account to update. + /// + public async Task UpdateBankAccount(BankAccountRepository repository, BankAccount bankAccount) + { + return await repository.UpdateBankAccount(bankAccount); + } + + /// + /// Delete an existing bank account. + /// + /// The injected repository to use for data retrieval. + /// The id of the bank account to delete. + public async Task DeleteBankAccount(BankAccountRepository repository, + Guid bankAccountId) + { + return await repository.DeleteBankAccount(bankAccountId); + } +} diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/BankAccountQueryResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/BankAccountQueryResolvers.cs index 0f9f531..b64e22d 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/BankAccountQueryResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Queries/BankAccountQueryResolvers.cs @@ -15,17 +15,18 @@ public sealed class BankAccountQueryResolvers /// /// The repository to use for data retrieval. /// The id of the account to lookup. - public async Task GetAccount(BankAccountRepository repository, Guid accountId) + public async Task GetBankAccount(BankAccountRepository repository, Guid accountId) { return await repository.GetBankAccount(accountId); } + // TODO: there is no input here like the comment indicates /// /// Look up accounts by a list of ids. /// /// The injected repository to use for data retrieval. [UsePaging] - public async Task> GetAccounts(BankAccountRepository repository) + public async Task> GetBankAccounts(BankAccountRepository repository) { return await repository.GetBankAccounts(); } diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/BankAccount.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/BankAccount.cs index cde0101..0501df5 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/BankAccount.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Core/Models/BankAccount.cs @@ -29,7 +29,7 @@ public class BankAccount : Account /// /// The banking institution this account belongs to. /// - public BankingInstitution AssociatedInstitution { get; set; } + public BankingInstitution? AssociatedInstitution { get; set; } /// /// Creates a new instance of . @@ -49,7 +49,7 @@ public class BankAccount : Account /// Specifies the nature, or use, of the account. /// The banking institution this account belongs to. public BankAccount(Guid id, string? name, string? description, decimal? balance, string? currency, List transactions, - string? iban, string? bic, string? bban, string? ownerName, string? accountType, BankingInstitution associatedInstitution) + string? iban, string? bic, string? bban, string? ownerName, string? accountType, BankingInstitution? associatedInstitution) : base(id, name, description, balance, currency, transactions) { Iban = iban; diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs index 7015ecd..748fa12 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/BankAccountRepository.cs @@ -45,4 +45,42 @@ public async Task> GetBankAccounts() var accountEntities = await _dbContext.BankAccounts.ToListAsync(); return accountEntities.Select(_bankAccountMapper.EntityToModel).ToList(); } + + /// + /// Adds a new . + /// + /// The s to add. + public async Task AddBankAccount(BankAccount bankAccount) + { + var bankAccountEntity = _bankAccountMapper.ModelToEntity(bankAccount); + var entityEntry = _dbContext.BankAccounts.Add(bankAccountEntity); + await _dbContext.SaveChangesAsync(); + + return _bankAccountMapper.EntityToModel(entityEntry.Entity); + } + + /// + /// Updates an existing . + /// + /// The bank account to update. + /// The updated . + public async Task UpdateBankAccount(BankAccount bankAccount) + { + var accountEntity = _bankAccountMapper.ModelToEntity(bankAccount); + var entityEntry = _dbContext.BankAccounts.Update(accountEntity); + await _dbContext.SaveChangesAsync(); + + return _bankAccountMapper.EntityToModel(entityEntry.Entity); + } + + /// + /// Deletes an existing . + /// + /// The id of the to delete. + /// Boolean value indicating whether the operation was successful. + public async Task DeleteBankAccount(Guid bankAccountId) + { + var result = await _dbContext.BankAccounts.Where(t => t.Id == bankAccountId).ExecuteDeleteAsync(); + return Convert.ToBoolean(result); + } } From 9cfa526e41f7c5ec5a27c4fb11a2b5bba632f94d Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Fri, 18 Oct 2024 23:22:12 +0200 Subject: [PATCH 15/15] Implement remaining GraphQL operations for transactions. Resolves #68 --- .../Mutations/TransactionMutationResolvers.cs | 11 +++++++++++ .../Repositories/TransactionRepository.cs | 11 ++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/TransactionMutationResolvers.cs b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/TransactionMutationResolvers.cs index a993ba1..fc3ec69 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/TransactionMutationResolvers.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/TransactionMutationResolvers.cs @@ -22,6 +22,17 @@ public async Task CreateTransaction(TransactionRepository repositor return transaction; } + /// + /// Update an existing transaction. + /// + /// The injected repository to use for data retrieval. + /// The transaction to update. + public async Task UpdateTransaction(TransactionRepository repository, + Transaction transaction) + { + return await repository.UpdateTransaction(transaction); + } + /// /// Delete an existing transaction. /// diff --git a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/TransactionRepository.cs b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/TransactionRepository.cs index 465b0db..76337a4 100644 --- a/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/TransactionRepository.cs +++ b/src/server/RobinTTY.PersonalFinanceDashboard.Infrastructure/Repositories/TransactionRepository.cs @@ -77,14 +77,15 @@ public async Task AddTransaction(Transaction transaction) /// /// Updates an existing . /// - /// The to update. + /// The to update. /// The updated . - /// TODO: return Transaction not TransactionEntity - public async Task UpdateTransaction(TransactionEntity transactionDto) + public async Task UpdateTransaction(Transaction transaction) { - var updateEntry = _dbContext.Transactions.Update(transactionDto); + var transactionEntity = _transactionMapper.ModelToEntity(transaction); + var updateEntry = _dbContext.Transactions.Update(transactionEntity); await _dbContext.SaveChangesAsync(); - return updateEntry.Entity; + + return _transactionMapper.EntityToModel(updateEntry.Entity); } ///