diff --git a/PostFundManagement.Api/PostFundManagement.Api.csproj b/PostFundManagement.Api/PostFundManagement.Api.csproj
index 891a5fe..746d861 100644
--- a/PostFundManagement.Api/PostFundManagement.Api.csproj
+++ b/PostFundManagement.Api/PostFundManagement.Api.csproj
@@ -8,18 +8,96 @@
..\PostFundManagement.snk
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PostFundManagement.Api/Program.cs b/PostFundManagement.Api/Program.cs
index aaf8987..4f84139 100644
--- a/PostFundManagement.Api/Program.cs
+++ b/PostFundManagement.Api/Program.cs
@@ -1,23 +1,100 @@
-using Scalar.AspNetCore;
+using PostFundManagement.Api.Extensions;
+using PostFundManagement.Domain.Extensions;
+using PostFundManagement.Domain.Mediator;
+using PostFundManagement.Infrastructure.Extensions;
+using static PostFundManagement.Domain.Extensions.Constants;
var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddApiVersioning();
-builder.Services.AddOpenApi();
+builder.Services.AddEndpointsApiExplorer()
+ .AddApiVersioning(options => options.ApiVersionReader = new HeaderApiVersionReader());
+
+builder.Services.AddApiVersioning().AddApiExplorer();
+
+builder.Services.AddEndpoints(Assembly.GetExecutingAssembly());
+builder.Services.AddApiServices(builder.Configuration);
+
+builder.Services.AddMediator();
+builder.Services.AddSecurityApiSdk(builder.Configuration);
+builder.Services.AddWebSecurity(builder.Configuration);
+
+builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TelemetryPipelineBehavior<,>));
+builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingPipelineBehavior<,>));
+
+builder.Services.AddQuartzSchedulerClient(DefaultSchedulerName, builder.Configuration);
+
+builder.Services.AddEmailServices(builder.Configuration);
+
+builder.Services.AddHttpClient();
+builder.Services.AddHashServices(builder.Configuration);
+builder.Services.AddDataProtectionDatabase(builder.Configuration);
+builder.Services.AddApplicationDbContext(builder.Configuration);
+
+// TODO: add healthcheck services
+// builder.Services.AddMidrandShopPostgresHealthCheck();
+// builder.Services.AddMidrandShopQuartzHealthCheck();
+// builder.Services.AddHealthChecksSupport(builder.Configuration);
var app = builder.Build();
-if (app.Environment.IsDevelopment())
-{
- app.MapScalarApiReference(options =>
- {
- options.WithTitle("Post-fund Management API")
- .WithTheme(ScalarTheme.BluePlanet);
- });
+var schedulerFactory = app.Services.GetRequiredService();
+var scheduler = await schedulerFactory.GetScheduler(DefaultSchedulerName);
- app.MapOpenApi();
-}
+if (!scheduler!.IsStarted)
+ await scheduler.Start();
+app.UseHsts();
app.UseHttpsRedirection();
-app.Run();
+app.UseRouting();
+app.UseAuthentication();
+app.UseAuthorization();
+
+ApiVersionSet versionSet = app.NewApiVersionSet("v1")
+ .HasApiVersion(new ApiVersion(1))
+ .HasApiVersion(new ApiVersion(2))
+ .ReportApiVersions()
+ .Build();
+
+var versionGroups = new Dictionary
+{
+ { 1, app.MapGroup("v{version:apiVersion}").WithApiVersionSet(versionSet) }
+};
+
+app.MapEndpoints(versionGroups);
+
+app.UseHealthChecks("/health", new HealthCheckOptions
+{
+ Predicate = _ => true,
+ AllowCachingResponses = true,
+ ResponseWriter = HealthChecks.UI.Client.UIResponseWriter.WriteHealthCheckUIResponse
+});
+
+app.MapHealthChecksUI(options => { options.UIPath = "/healthui"; });
+app.UseHealthChecks("/ready");
+
+app.MapOpenApi();
+
+var apiVersions = app.DescribeApiVersions()
+ .OrderByDescending(o => o.ApiVersion.MajorVersion)
+ .ToList();
+
+foreach (var description in app.DescribeApiVersions().OrderByDescending(o => o.ApiVersion.MajorVersion))
+ app.MapScalarApiReference($"/openapi/{description.GroupName}", (options, context) =>
+ {
+ options.AddServer(new ScalarServer($"https://{context.Request.Host}"));
+ options.WithOpenApiRoutePattern($"/openapi/{description.GroupName}.json");
+ options.WithTheme(ScalarTheme.DeepSpace);
+ options.Agent = new ScalarAgentOptions { Disabled = true };
+ options.Authentication = new ScalarAuthenticationOptions { PreferredSecuritySchemes = ["Bearer"] };
+ });
+
+var latestVersionGroup = apiVersions.FirstOrDefault()?.GroupName ?? "v1";
+
+app.MapGet("/", () => Results.Redirect($"/openapi/{latestVersionGroup}"))
+ .ExcludeFromDescription();
+
+if (!app.Environment.IsDevelopment())
+ app.UseExceptionHandler("/Error", createScopeForErrors: true);
+
+app.Run();
\ No newline at end of file
diff --git a/PostFundManagement.Api/appsettings.json b/PostFundManagement.Api/appsettings.json
index a2002e3..9cefeec 100644
--- a/PostFundManagement.Api/appsettings.json
+++ b/PostFundManagement.Api/appsettings.json
@@ -1,5 +1,15 @@
{
- "MachineIdentity": {
+ "GeneralS3Settings": {
+ "ServiceUrl": "http://192.168.1.177:30900",
+ "Region": "garage",
+ "BucketName": "pfm.general",
+ "CdnBaseUrl": "https://pfm.general.cdn.khongisa.co.za"
+ },
+ "Monitoring": {
+ "Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889",
+ "ServiceName": "MidrandBooks.DEV"
+ },
+ "SecuritySettings": {
"Authority": "https://sts.security.khongisa.co.za",
"Audience": "pfm-api-dev"
},
@@ -10,4 +20,4 @@
}
},
"AllowedHosts": "*"
-}
+}
\ No newline at end of file
diff --git a/PostFundManagement.Domain/Extensions/Constants.cs b/PostFundManagement.Domain/Extensions/Constants.cs
index c16be94..1d7d474 100644
--- a/PostFundManagement.Domain/Extensions/Constants.cs
+++ b/PostFundManagement.Domain/Extensions/Constants.cs
@@ -2,6 +2,8 @@ namespace PostFundManagement.Domain.Extensions;
public static class Constants
{
+ public const string DefaultSchedulerName = "pfm-scheduler";
+
public const string GeneralS3SettingsSection = "PfmS3Settings";
public const string EvidenceS3SettingsSection = "EvidenceS3Settings";
diff --git a/PostFundManagement.Domain/Extensions/Email.cs b/PostFundManagement.Domain/Extensions/Email.cs
new file mode 100644
index 0000000..3e2da51
--- /dev/null
+++ b/PostFundManagement.Domain/Extensions/Email.cs
@@ -0,0 +1,23 @@
+using PostFundManagement.Domain.Configuration.Email;
+using PostFundManagement.Domain.Services;
+
+namespace PostFundManagement.Domain.Extensions;
+
+public static class Email
+{
+ public const string EmailFromName = "PFM Team";
+ public const string EmailFromAddress = "info@pfm.co.za"; // TODO: replace with real address
+
+ public static IServiceCollection AddEmailServices(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.Configure(configuration.GetSection("Email"));
+
+ services.AddSingleton();
+
+ services.AddOpenTelemetry()
+ .WithTracing(tracing => tracing.AddSource("Pfm.EmailService"))
+ .WithMetrics(metrics => metrics.AddMeter("Pfm.EmailService"));
+
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/PostFundManagement.Domain/Extensions/Quartz.cs b/PostFundManagement.Domain/Extensions/Quartz.cs
index f8aeaec..e52d909 100644
--- a/PostFundManagement.Domain/Extensions/Quartz.cs
+++ b/PostFundManagement.Domain/Extensions/Quartz.cs
@@ -6,8 +6,6 @@ namespace PostFundManagement.Domain.Extensions;
public static class Quartz
{
- public const string DefaultSchedulerName = "tech-shop";
-
public static IServiceCollection AddQuartzSchedulerClient(this IServiceCollection services, string schedulerName, IConfiguration configuration)
{
var connectionString = configuration.GetConnectionString(DatabaseConfigName);