diff --git a/Directory.Build.props b/Directory.Build.props
index 7786cc4..055150f 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -44,6 +44,7 @@
+
diff --git a/PostFundManagement.Api/Program.cs b/PostFundManagement.Api/Program.cs
index c75e660..6819b5d 100644
--- a/PostFundManagement.Api/Program.cs
+++ b/PostFundManagement.Api/Program.cs
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Health;
using PostFundManagement.Domain.Mediator;
using PostFundManagement.Infrastructure.Database.Services;
-using static PostFundManagement.Domain.Extensions.Constants;
var builder = WebApplication.CreateBuilder(args);
@@ -45,7 +44,7 @@ builder.Services.AddSkorubaApiService(builder.Configuration);
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TelemetryPipelineBehavior<,>));
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingPipelineBehavior<,>));
-builder.Services.AddQuartzScheduler(DefaultSchedulerName, builder.Configuration);
+builder.Services.AddQuartzSchedulerClient(DefaultSchedulerName, builder.Configuration);
builder.Services.AddEmailServices(builder.Configuration);
builder.Services.AddHashServices(builder.Configuration);
builder.Services.AddGarageS3(builder.Configuration);
@@ -119,7 +118,9 @@ builder.Services.AddCors(options =>
policy.SetIsOriginAllowed(origin =>
{
if (string.IsNullOrWhiteSpace(origin)) return false;
+
var clean = origin.TrimEnd('/');
+
if (allowedOrigins.Any(a => string.Equals(a.TrimEnd('/'), clean, StringComparison.OrdinalIgnoreCase)))
return true;
@@ -133,10 +134,7 @@ builder.Services.AddCors(options =>
return true;
}
}
- catch
- {
- // ignore parse failure
- }
+ catch { /* eat all exceptions */ }
return false;
})
diff --git a/PostFundManagement.Api/appsettings.json b/PostFundManagement.Api/appsettings.json
index 40d57aa..71aab85 100644
--- a/PostFundManagement.Api/appsettings.json
+++ b/PostFundManagement.Api/appsettings.json
@@ -31,14 +31,7 @@
"Microsoft.EntityFrameworkCore.Database": "Error",
"Microsoft.EntityFrameworkCore.Update": "Error"
}
- },
- "EmailProcessorCron": "0 */5 * * * ?",
- "Email": {
- "SmtpServer": "smtp.example.com",
- "SmtpPort": 587,
- "SenderEmail": "noreply@example.com",
- "SenderName": "Example"
- },
+ },
"HasherSettings": {
"MinHashLength": 11
},
diff --git a/PostFundManagement.Domain/Configuration/Email/SmtpSettings.cs b/PostFundManagement.Domain/Configuration/Email/SmtpSettings.cs
index 9ba2ac5..4c816e7 100644
--- a/PostFundManagement.Domain/Configuration/Email/SmtpSettings.cs
+++ b/PostFundManagement.Domain/Configuration/Email/SmtpSettings.cs
@@ -9,4 +9,6 @@ public sealed class SmtpSettings
public string? Host { get; set; }
public bool UseSsl { get; set; }
+
+ public string? ProcessorJobCron { get; set; }
}
\ No newline at end of file
diff --git a/PostFundManagement.Infrastructure/Database/Services/HeadlessTenantContextService.cs b/PostFundManagement.Infrastructure/Database/Services/HeadlessTenantContextService.cs
new file mode 100644
index 0000000..2b1e438
--- /dev/null
+++ b/PostFundManagement.Infrastructure/Database/Services/HeadlessTenantContextService.cs
@@ -0,0 +1,16 @@
+using PostFundManagement.Domain.Abstractions;
+
+namespace PostFundManagement.Infrastructure.Database.Services;
+
+public sealed class HeadlessTenantContextService : ITenantContextService
+{
+ public string? TenantName => "Ifri Technologies";
+
+ public long TenantId => 1;
+
+ public bool ByPassTenantQueryFilter { get; set; } = true;
+
+ public bool IsAdmin => true;
+
+ public bool HasTenantFilter => true;
+}
diff --git a/PostFundManagement.Jobs/Dockerfile b/PostFundManagement.Jobs/Dockerfile
new file mode 100644
index 0000000..7b5e8fe
--- /dev/null
+++ b/PostFundManagement.Jobs/Dockerfile
@@ -0,0 +1,40 @@
+# Stage 1: Runtime base
+FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
+USER app
+WORKDIR /app
+EXPOSE 8080
+
+# Stage 2: Build & Restore
+FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
+WORKDIR /src
+
+# Copy props and csproj files for layer caching
+COPY ["Directory.Build.props", "./"]
+COPY ["Directory.Packages.props", "./"]
+COPY ["PostFundManagement.snk", "./"]
+COPY ["LICENSE", "./"]
+COPY ["PostFundManagement.Jobs/PostFundManagement.Jobs.csproj", "PostFundManagement.Jobs/"]
+COPY ["PostFundManagement.Application/PostFundManagement.Application.csproj", "PostFundManagement.Application/"]
+COPY ["PostFundManagement.Domain/PostFundManagement.Domain.csproj", "PostFundManagement.Domain/"]
+COPY ["PostFundManagement.Infrastructure/PostFundManagement.Infrastructure.csproj", "PostFundManagement.Infrastructure/"]
+
+# Restore packages
+RUN dotnet restore "PostFundManagement.Jobs/PostFundManagement.Jobs.csproj"
+
+# Copy full source code
+COPY . .
+
+# Build from the project folder using relative filename
+WORKDIR "/src/PostFundManagement.Jobs"
+RUN dotnet build "PostFundManagement.Jobs.csproj" -c Release -o /app/build
+
+# Stage 3: Publish
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "PostFundManagement.Jobs.csproj" -c Release -o /app/publish /p:UseAppHost=false
+
+# Stage 4: Final Runtime
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "PostFundManagement.Jobs.dll"]
diff --git a/PostFundManagement.Jobs/JobsWorker.cs b/PostFundManagement.Jobs/JobsWorker.cs
new file mode 100644
index 0000000..8806a7d
--- /dev/null
+++ b/PostFundManagement.Jobs/JobsWorker.cs
@@ -0,0 +1,21 @@
+using PostFundManagement.Domain.Abstractions;
+
+namespace PostFundManagement.Jobs;
+
+public class JobsWorker(IJobOrchestrator jobOrchestrator, IFeatureManager featureManager,
+ ILogger logger) : BackgroundService
+{
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ if(!await featureManager.IsEnabledAsync(nameof(JobsWorker)))
+ {
+ logger.LogInformation("JobsWorker is disabled.");
+
+ return;
+ }
+
+ // TODO: scheduler reccuring jobs
+
+ logger.LogInformation("JobsWorker is stopping.");
+ }
+}
diff --git a/PostFundManagement.Jobs/PostFundManagement.Jobs.csproj b/PostFundManagement.Jobs/PostFundManagement.Jobs.csproj
index ed98a43..73c3997 100644
--- a/PostFundManagement.Jobs/PostFundManagement.Jobs.csproj
+++ b/PostFundManagement.Jobs/PostFundManagement.Jobs.csproj
@@ -1,4 +1,4 @@
-
+
net10.0
@@ -7,6 +7,20 @@
dotnet-PostFundManagement.Jobs-97b98845-fddb-4212-b877-8dac9f7f86e3
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/PostFundManagement.Jobs/Program.cs b/PostFundManagement.Jobs/Program.cs
index d744e20..53055b8 100644
--- a/PostFundManagement.Jobs/Program.cs
+++ b/PostFundManagement.Jobs/Program.cs
@@ -1,7 +1,38 @@
+using PostFundManagement.Application.Extensions;
+using PostFundManagement.Domain.Abstractions;
+using PostFundManagement.Domain.Mediator;
+using PostFundManagement.Infrastructure.Database.Services;
using PostFundManagement.Jobs;
var builder = Host.CreateApplicationBuilder(args);
-builder.Services.AddHostedService();
+
+builder.Services.AddFeatureManagement();
+
+builder.Services.AddSharedServices();
+builder.Services.AddHostedService();
+builder.Services.AddRegisterServicesWithCaching(builder.Configuration);
+builder.Services.AddEndpointAndServiceCaching(builder.Configuration);
+builder.Services.AddTransient();
+
+builder.Services.AddMediator();
+builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TelemetryPipelineBehavior<,>));
+builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingPipelineBehavior<,>));
+
+builder.Services.AddQuartzScheduler(DefaultSchedulerName, builder.Configuration);
+builder.Services.AddEmailServices(builder.Configuration);
+builder.Services.AddHashServices(builder.Configuration);
+builder.Services.AddGarageS3(builder.Configuration);
+
+builder.Services.AddHttpClient();
+builder.Services.AddDataProtectionDatabase(builder.Configuration);
+builder.Services.AddApplicationDbContext(builder.Configuration);
var host = builder.Build();
+
+var schedulerFactory = host.Services.GetRequiredService();
+var scheduler = await schedulerFactory.GetScheduler();
+
+if (scheduler!.Status != SchedulerStatus.Running)
+ await scheduler.Start();
+
host.Run();
diff --git a/PostFundManagement.Jobs/Worker.cs b/PostFundManagement.Jobs/Worker.cs
deleted file mode 100644
index 99a49bb..0000000
--- a/PostFundManagement.Jobs/Worker.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace PostFundManagement.Jobs
-{
- public class Worker(ILogger logger) : BackgroundService
- {
- protected override async Task ExecuteAsync(CancellationToken stoppingToken)
- {
- while (!stoppingToken.IsCancellationRequested)
- {
- if (logger.IsEnabled(LogLevel.Information))
- {
- logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
- }
- await Task.Delay(1000, stoppingToken);
- }
- }
- }
-}
diff --git a/PostFundManagement.Jobs/appsettings.json b/PostFundManagement.Jobs/appsettings.json
index b2dcdb6..4f50c71 100644
--- a/PostFundManagement.Jobs/appsettings.json
+++ b/PostFundManagement.Jobs/appsettings.json
@@ -1,8 +1,37 @@
{
+ "FeatureManagement": {
+ "JobsWorker": true,
+ "EmailProcessorJobEnabled": true,
+ "ServiceCaching": false
+ },
+ "GeneralS3Settings": {
+ "ServiceUrl": "https://s3.garage.khongisa.co.za",
+ "Region": "garage",
+ "BucketName": "pfm",
+ "CdnBaseUrl": "https://pfm.cdn.khongisa.co.za"
+ },
+ "Monitoring": {
+ "Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889",
+ "ServiceName": "Pfm.Jobs.DEV"
+ },
"Logging": {
"LogLevel": {
- "Default": "Information",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Default": "Warning",
+ "Quartz": "Error",
+ "Microsoft.AspNetCore": "Warning",
+ "Microsoft.EntityFrameworkCore.Database": "Error",
+ "Microsoft.EntityFrameworkCore.Update": "Error"
}
- }
-}
+ },
+ "Email": {
+ "ProcessorJobCron": "0 */5 * * * ?",
+ "SmtpServer": "smtp.example.com",
+ "SmtpPort": 587,
+ "SenderEmail": "noreply@example.com",
+ "SenderName": "Example"
+ },
+ "HasherSettings": {
+ "MinHashLength": 11
+ },
+ "AllowedHosts": "*"
+}
\ No newline at end of file
diff --git a/PostFundManagement.slnx b/PostFundManagement.slnx
index c6fe96b..6e5173b 100644
--- a/PostFundManagement.slnx
+++ b/PostFundManagement.slnx
@@ -1,4 +1,13 @@
+
+
+
+
+
+
+
+
+
@@ -9,10 +18,7 @@
-
-
-
-
-
-
+
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
index 9499da8..9054275 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,4 +1,55 @@
services:
+ pfm-scheduler:
+ image: pfm-scheduler
+ build:
+ context: .
+ dockerfile: ./PostFundManagement.Jobs/Dockerfile
+ network: host
+ container_name: pfm-scheduler
+ environment:
+ # Service layer caching feature flag
+ - FeatureManagement__JobsWorker=${FEATURE_JOBS_WORKER:-true}}
+ - FeatureManagement__EmailProcessorJobEnabled=${FEATURE_EMAIL_PROCESSOR_JOB_ENABLED:-true}}
+ - FeatureManagement__ServiceCaching=${FEATURE_SERVICE_CACHING:-false}}
+
+ # Header forwarding overrides
+ - DOTNET_ENVIRONMENT=${ENVIRONMENT:-Development}
+
+ # Postgres Database Overrides
+ - ConnectionStrings__PfmDatabase=Server=${POSTGRES_HOST:-192.168.1.170};Port=${POSTGRES_PORT:-5432};Database=${POSTGRES_DB:-pfm};User Id=${POSTGRES_USER:-pfm-user};Password=${POSTGRES_PASSWORD:-};
+ - ConnectionStrings__PfmSchedulerDatabase=Server=${POSTGRES_HOST:-192.168.1.170};Port=${POSTGRES_PORT:-5432};Database=pfm-scheduler;User Id=${POSTGRES_USER:-pfm-user};Password=${POSTGRES_PASSWORD:-};
+
+ # Garage S3 Settings
+ - GeneralS3Settings__ServiceUrl=${GENERAL_S3_SERVICE_URL:-https://s3.garage.khongisa.co.za}
+ - GeneralS3Settings__Region=${GENERAL_S3_REGION:-garage}
+ - GeneralS3Settings__BucketName=${GENERAL_S3_BUCKET_NAME:-pfm}
+ - GeneralS3Settings__CdnBaseUrl=${GENERAL_S3_CDN_BASE_URL:-https://pfm.cdn.khongisa.co.za}
+ - GeneralS3Settings__AccessKey=${GENERAL_S3_ACCESS_KEY:-}
+ - GeneralS3Settings__SecretKey=${GENERAL_S3_SECRET_KEY:-}
+
+ # HashIds overrides
+ - HasherSettings__MinHashLength=${HASHER_MIN_HASH_LENGTH:-11}
+ - HasherSettings__Salt=${HASHER_SALT:-}
+
+ # Data Protection Overrides
+ - DataProtection__Certificate=${DATA_PROTECTION_CERTIFICATE:-}
+ - DataProtection__Password=${DATA_PROTECTION_CERTIFICATE_PASSWORD:-}
+
+ # Monitoring Overrides
+ - Monitoring__Address=${MONITORING_ADDRESS:-http://aspire-dashboard-service.aspire.svc.cluster.local:18889}
+ - Monitoring__ServiceName=${MONITORING_SERVICE_NAME:-Pfm.DEV}
+ - Monitoring__ApiKey=${MONITORING_API_KEY:-}
+
+ # Email Processor Overrides
+ - Email__ProcessorJobCron=${EMAIL_PROCESSOR_CRON:-0 */5 * * * ?}
+ - Email__Credentials__Password=${EMAIL_CREDENTIALS_PASSWORD:-}
+ - Email__Credentials__Username=${EMAIL_CREDENTIALS_USERNAME:-}
+ - Email__SmtpServer=${EMAIL_SMTP_SERVER:-}
+ - Email__SmtpPort=${EMAIL_SMTP_PORT:-587}
+
+ # Redis overrides
+ - ConnectionStrings__Redis=${REDIS_CONNECTION_STRING:-192.168.1.171:6379,ssl=false,abortConnect=false}
+ restart: unless-stopped
pfm-api:
image: pfm-api
build:
@@ -8,10 +59,7 @@ services:
container_name: pfm-api
ports:
- "8075:8080"
- environment:
- # Service layer caching feature flag
- - EnableServiceCaching=false
-
+ environment:
# Header forwarding overrides
- ASPNETCORE_ENVIRONMENT=${ENVIRONMENT:-Development}
- ForwardedHeadersConfiguration__Enabled=true
@@ -61,13 +109,6 @@ services:
- Monitoring__Address=${MONITORING_ADDRESS:-http://aspire-dashboard-service.aspire.svc.cluster.local:18889}
- Monitoring__ServiceName=${MONITORING_SERVICE_NAME:-Pfm.DEV}
- Monitoring__ApiKey=${MONITORING_API_KEY:-}
-
- # Email Processor Overrides
- - EmailProcessorCron=${EMAIL_PROCESSOR_CRON:-}
- - Email__Credentials__Password=${EMAIL_CREDENTIALS_PASSWORD:-}
- - Email__Credentials__Username=${EMAIL_CREDENTIALS_USERNAME:-}
- - Email__SmtpServer=${EMAIL_SMTP_SERVER:-}
- - Email__SmtpPort=${EMAIL_SMTP_PORT:-587}
# Redis overrides
- ConnectionStrings__Redis=${REDIS_CONNECTION_STRING:-192.168.1.171:6379,ssl=false,abortConnect=false}