-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #74 from RobinTTY/feature/implement-basic-data-ret…
…rieval Implement basic data retrieval operations
- Loading branch information
Showing
41 changed files
with
1,058 additions
and
254 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
name: PersonalFinanceDashboard | ||
|
||
services: | ||
seq: | ||
image: datalust/seq:latest | ||
container_name: seq | ||
environment: | ||
- ACCEPT_EULA=Y | ||
ports: | ||
- "5341:5341" | ||
- "8001:80" |
119 changes: 119 additions & 0 deletions
119
src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/ServiceCollectionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
/// <summary> | ||
/// Provides extensions for the <see cref="IServiceCollection"/> interface. | ||
/// </summary> | ||
public static class ServiceCollectionExtensions | ||
{ | ||
/// <summary> | ||
/// Registers all repositories in the service collection. | ||
/// </summary> | ||
/// <param name="services">The service collection to which to add the services.</param> | ||
/// <returns>A reference to the <see cref="IServiceCollection"/> after the operation has completed.</returns> | ||
// TODO: automatic registration of repositories via codegen? | ||
public static IServiceCollection AddRepositories(this IServiceCollection services) | ||
{ | ||
return services | ||
.AddScoped<BankAccountRepository>() | ||
.AddScoped<AuthenticationRequestRepository>() | ||
.AddScoped<BankingInstitutionRepository>() | ||
.AddScoped<TransactionRepository>() | ||
.AddScoped<ThirdPartyDataRetrievalMetadataRepository>(); | ||
} | ||
|
||
/// <summary> | ||
/// Registers all entity mappers in the service collection. | ||
/// </summary> | ||
/// <param name="services">The service collection to which to add the services.</param> | ||
/// <returns>A reference to the <see cref="IServiceCollection"/> after the operation has completed.</returns> | ||
public static IServiceCollection AddEntityMappers(this IServiceCollection services) | ||
{ | ||
return services | ||
.AddSingleton<TransactionMapper>() | ||
.AddSingleton<BankingInstitutionMapper>() | ||
.AddSingleton<BankAccountMapper>(); | ||
} | ||
|
||
/// <summary> | ||
/// Registers all necessary HotChocolate (GraphQL) features in the service collection. | ||
/// </summary> | ||
/// <param name="services">The service collection to which to add the services.</param> | ||
/// <returns>A reference to the <see cref="IServiceCollection"/> after the operation has completed.</returns> | ||
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) | ||
.AddResolvers() | ||
// TODO: Document what the different extensions methods do | ||
// AddQueryConventions: https://www.youtube.com/watch?v=yoW2Mt6C0Cg | ||
.AddQueryConventions() | ||
.AddMutationConventions(); | ||
|
||
return requestExecutorBuilder.Services; | ||
} | ||
|
||
/// <summary> | ||
/// Adds all necessary miscellaneous services for this application. | ||
/// </summary> | ||
/// <param name="services">The service collection to which to add the services.</param> | ||
/// <returns>A reference to the <see cref="IServiceCollection"/> after the operation has completed.</returns> | ||
public static IServiceCollection AddApplicationServices(this IServiceCollection services) | ||
{ | ||
return services | ||
.AddSingleton<GoCardlessDataProviderService>() | ||
.AddScoped<ThirdPartyDataRetrievalMetadataService>(); | ||
} | ||
|
||
/// <summary> | ||
/// Adds all necessary configuration data for this application. | ||
/// </summary> | ||
/// <param name="services">The service collection to which to add the services.</param> | ||
/// <returns>A reference to the <see cref="IServiceCollection"/> after the operation has completed.</returns> | ||
public static IServiceCollection AddApplicationConfiguration(this IServiceCollection services) | ||
{ | ||
var appConfig = AppConfigurationManager.AppConfiguration; | ||
return services.AddSingleton(new NordigenClientCredentials(appConfig.NordigenApi!.SecretId, | ||
appConfig.NordigenApi.SecretKey)); | ||
} | ||
|
||
/// <summary> | ||
/// Adds a configured <see cref="IHttpClientFactory"/> to the service collection. | ||
/// </summary> | ||
/// <param name="services">The service collection to which to add the services.</param> | ||
/// <returns>A reference to the <see cref="IServiceCollection"/> after the operation has completed.</returns> | ||
public static IServiceCollection AddConfiguredHttpClient(this IServiceCollection services) | ||
{ | ||
return services | ||
.AddHttpClient() | ||
.AddCors(options => | ||
{ | ||
// TODO: update to sensible policy | ||
options.AddDefaultPolicy(policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); | ||
}); | ||
} | ||
|
||
/// <summary> | ||
/// Adds the database the application uses to the service collection. | ||
/// </summary> | ||
/// <param name="services">The service collection to which to add the services.</param> | ||
/// <returns>A reference to the <see cref="IServiceCollection"/> after the operation has completed.</returns> | ||
public static IServiceCollection AddDatabase(this IServiceCollection services) | ||
{ | ||
// TODO: The filepath shouldn't be hardcoded => configuration | ||
return services.AddDbContextPool<ApplicationDbContext>(options => | ||
options.UseSqlite("Data Source=../RobinTTY.PersonalFinanceDashboard.Infrastructure/application.db")); | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
...erver/RobinTTY.PersonalFinanceDashboard.API/Extensions/WebApplicationBuilderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.Extensions.Hosting; | ||
|
||
namespace RobinTTY.PersonalFinanceDashboard.Api.Extensions; | ||
|
||
/// <summary> | ||
/// Provides extensions on the <see cref="IHostBuilder"/> type. | ||
/// </summary> | ||
public static class WebApplicationBuilderExtensions | ||
{ | ||
/// <summary> | ||
/// Configures the logging of the application via Serilog. | ||
/// </summary> | ||
/// <param name="webApplicationBuilder">The host to configure the logging for.</param> | ||
/// <returns>A reference to the <see cref="IHostBuilder"/> after the operation has completed.</returns> | ||
public static IHostBuilder ConfigureSerilog(this WebApplicationBuilder webApplicationBuilder) | ||
{ | ||
// TODO: Only do in development | ||
// Forward issues with Serilog itself to console | ||
if (webApplicationBuilder.Environment.IsDevelopment()) | ||
{ | ||
Serilog.Debugging.SelfLog.Enable(Console.Error); | ||
} | ||
|
||
return webApplicationBuilder.Host.UseSerilog((context, loggerConfig) => | ||
{ | ||
loggerConfig.ReadFrom.Configuration(context.Configuration); | ||
}); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/server/RobinTTY.PersonalFinanceDashboard.API/Extensions/WebApplicationExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
/// <summary> | ||
/// Provides extensions for the <see cref="WebApplication"/> type. | ||
/// </summary> | ||
public static class WebApplicationExtensions | ||
{ | ||
/// <summary> | ||
/// Configures the app to use all necessary GraphQL features. | ||
/// </summary> | ||
/// <param name="app">The <see cref="WebApplication"/> instance to configure.</param> | ||
/// <param name="args">Command line arguments to be passed to the host.</param> | ||
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); | ||
} | ||
|
||
/// <summary> | ||
/// Applies all outstanding migrations on the database. | ||
/// </summary> | ||
/// <param name="app">The <see cref="WebApplication"/> instance to use to resolve the db context.</param> | ||
public static void ApplyMigrations(this WebApplication app) | ||
{ | ||
using var scope = app.Services.CreateScope(); | ||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); | ||
db.Database.Migrate(); | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/server/RobinTTY.PersonalFinanceDashboard.API/GlobalUsingDirectives.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
95 changes: 11 additions & 84 deletions
95
src/server/RobinTTY.PersonalFinanceDashboard.API/Program.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,94 +1,21 @@ | ||
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 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); | ||
}); | ||
|
||
// 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<ApplicationDbContext>(options => | ||
options.UseSqlite("Data Source=../RobinTTY.PersonalFinanceDashboard.Infrastructure/application.db")); | ||
|
||
// Mappers | ||
builder.Services.AddSingleton<TransactionMapper>(); | ||
builder.Services.AddSingleton<BankingInstitutionMapper>(); | ||
builder.ConfigureSerilog(); | ||
|
||
// TODO: automatic registration of repositories via codegen? | ||
// Repositories | ||
builder.Services | ||
.AddScoped<AccountRepository>() | ||
.AddScoped<AuthenticationRequestRepository>() | ||
.AddScoped<BankingInstitutionRepository>() | ||
.AddScoped<TransactionRepository>() | ||
.AddScoped<ThirdPartyDataRetrievalMetadataRepository>(); | ||
|
||
// Services | ||
builder.Services | ||
.AddScoped<ThirdPartyDataRetrievalMetadataService>(); | ||
|
||
// Others | ||
builder.Services | ||
.AddSingleton(new NordigenClientCredentials(appConfig.NordigenApi!.SecretId, appConfig.NordigenApi.SecretKey)) | ||
.AddSingleton<GoCardlessDataProvider>(); | ||
|
||
// 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<ApplicationDbContext>(); | ||
db.Database.Migrate(); | ||
} | ||
|
||
app.ApplyMigrations(); | ||
app.UseSerilogRequestLogging(); | ||
app.UseCors(); | ||
app.MapGraphQL(); | ||
app.UseGraphQl(args); | ||
app.Run(); |
2 changes: 1 addition & 1 deletion
2
src/server/RobinTTY.PersonalFinanceDashboard.API/Properties/ModuleInfo.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
[assembly: Module("Types")] | ||
[assembly: Module("Resolvers")] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
src/server/RobinTTY.PersonalFinanceDashboard.API/Resolvers/Mutations/BankAccountMutations.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
using HotChocolate.Types; | ||
using RobinTTY.PersonalFinanceDashboard.Core.Models; | ||
using RobinTTY.PersonalFinanceDashboard.Infrastructure.Repositories; | ||
|
||
namespace RobinTTY.PersonalFinanceDashboard.Api.Resolvers.Mutations; | ||
|
||
/// <summary> | ||
/// <see cref="BankAccount"/> related mutation resolvers. | ||
/// </summary> | ||
[MutationType] | ||
public class BankAccountMutations | ||
{ | ||
/// <summary> | ||
/// Create a new banking account. | ||
/// </summary> | ||
/// <param name="repository">The injected repository to use for data retrieval.</param> | ||
/// <param name="bankAccount">The banking account to create.</param> | ||
public async Task<BankAccount> CreateBankAccount(BankAccountRepository repository, BankAccount bankAccount) | ||
{ | ||
return await repository.AddBankAccount(bankAccount); | ||
} | ||
|
||
/// <summary> | ||
/// Update an existing banking account. | ||
/// </summary> | ||
/// <param name="repository">The injected repository to use for data retrieval.</param> | ||
/// <param name="bankAccount">The banking account to update.</param> | ||
/// <returns></returns> | ||
public async Task<BankAccount> UpdateBankAccount(BankAccountRepository repository, BankAccount bankAccount) | ||
{ | ||
return await repository.UpdateBankAccount(bankAccount); | ||
} | ||
|
||
/// <summary> | ||
/// Delete an existing bank account. | ||
/// </summary> | ||
/// <param name="repository">The injected repository to use for data retrieval.</param> | ||
/// <param name="bankAccountId">The id of the bank account to delete.</param> | ||
public async Task<bool> DeleteBankAccount(BankAccountRepository repository, | ||
Guid bankAccountId) | ||
{ | ||
return await repository.DeleteBankAccount(bankAccountId); | ||
} | ||
} |
Oops, something went wrong.