Compare commits
12
Commits
1931899d53
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7f360cc2a | ||
|
|
67c08b40ca | ||
|
|
7535c60ca6 | ||
|
|
de20ace27d | ||
|
|
9f1146130e | ||
|
|
5a325b0a75 | ||
|
|
ab2ef9da31 | ||
|
|
77517579e3 | ||
|
|
8f7af90344 | ||
|
|
b48c663756 | ||
|
|
e3058e0439 | ||
|
|
25cba4a3d5 |
+287
@@ -0,0 +1,287 @@
|
||||
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||
root = true
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
# IDE0250: Prefer make struct 'readonly'
|
||||
dotnet_diagnostic.IDE0250.severity = warning
|
||||
|
||||
# IDE0060: Remove unused parameters (Good cleanup pairing)
|
||||
dotnet_diagnostic.IDE0060.severity = warning
|
||||
|
||||
# CA1852: Seal internal types (Available in modern .NET)
|
||||
dotnet_diagnostic.CA1852.severity = warning
|
||||
|
||||
# MA0018: Add sealed modifier to types that are never inherited
|
||||
dotnet_diagnostic.MA0018.severity = warning
|
||||
|
||||
# Enforce that classes should be sealed
|
||||
dotnet_diagnostic.MA0053.severity = warning
|
||||
|
||||
# CRITICAL: Force the analyzer to also flag PUBLIC classes, not just internal ones
|
||||
meziantou_analyzer.MA0053.public_class_should_be_sealed = true
|
||||
MA0053.public_class_should_be_sealed = true
|
||||
|
||||
# Keep the rule active as a warning by default
|
||||
dotnet_diagnostic.MA0048.severity = warning
|
||||
|
||||
# Specific exclusions for Meziantou.Analyzer MA0048
|
||||
# Disable the rule for enums
|
||||
meziantou_analyzer.MA0048.exclude_enums = true
|
||||
|
||||
# Disable the rule for records
|
||||
meziantou_analyzer.MA0048.exclude_records = true
|
||||
|
||||
#EXCLUDE specific files that are meant to hold grouped enums/records
|
||||
dotnet_diagnostic.MA0048.severity = warning
|
||||
|
||||
# Disable the requirement to specify ConfigureAwait(false)
|
||||
dotnet_diagnostic.MA0004.severity = none
|
||||
|
||||
# ALTERNATIVE: Exclude any file ending with 'Enums.cs' or 'Records.cs'
|
||||
# (e.g., BillingEnums.cs, CustomerRecords.cs)
|
||||
[**/*{Enums,Records}.cs]
|
||||
dotnet_diagnostic.MA0048.severity = none
|
||||
|
||||
#### Core EditorConfig Options ####
|
||||
|
||||
# Indentation and spacing
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
# New line preferences
|
||||
end_of_line = crlf
|
||||
insert_final_newline = false
|
||||
|
||||
#### .NET Code Actions ####
|
||||
|
||||
# Type members
|
||||
dotnet_hide_advanced_members = false
|
||||
dotnet_member_insertion_location = with_other_members_of_the_same_kind
|
||||
dotnet_property_generation_behavior = prefer_throwing_properties
|
||||
|
||||
# Symbol search
|
||||
dotnet_search_reference_assemblies = true
|
||||
|
||||
#### .NET Coding Conventions ####
|
||||
|
||||
# Organize usings
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = false
|
||||
file_header_template = unset
|
||||
|
||||
# this. and Me. preferences
|
||||
dotnet_style_qualification_for_event = false
|
||||
dotnet_style_qualification_for_field = false
|
||||
dotnet_style_qualification_for_method = false
|
||||
dotnet_style_qualification_for_property = false
|
||||
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||
dotnet_style_predefined_type_for_member_access = true
|
||||
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
|
||||
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members
|
||||
|
||||
# Expression-level preferences
|
||||
dotnet_prefer_system_hash_code = true
|
||||
dotnet_style_coalesce_expression = true
|
||||
dotnet_style_collection_initializer = true
|
||||
dotnet_style_explicit_tuple_names = true
|
||||
dotnet_style_namespace_match_folder = true
|
||||
dotnet_style_null_propagation = true
|
||||
dotnet_style_object_initializer = true
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
dotnet_style_prefer_auto_properties = true
|
||||
dotnet_style_prefer_collection_expression = when_types_loosely_match
|
||||
dotnet_style_prefer_compound_assignment = true
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true
|
||||
dotnet_style_prefer_conditional_expression_over_return = true
|
||||
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||
dotnet_style_prefer_inferred_tuple_names = true
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||
dotnet_style_prefer_non_hidden_explicit_cast_in_source = true
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||
dotnet_style_prefer_simplified_interpolation = true
|
||||
|
||||
# Field preferences
|
||||
dotnet_style_readonly_field = true
|
||||
|
||||
# Parameter preferences
|
||||
dotnet_code_quality_unused_parameters = all
|
||||
|
||||
# Suppression preferences
|
||||
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||
|
||||
# New line preferences
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = true
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = true
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = false
|
||||
csharp_style_var_for_built_in_types = false
|
||||
csharp_style_var_when_type_is_apparent = false
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_accessors = true
|
||||
csharp_style_expression_bodied_constructors = false
|
||||
csharp_style_expression_bodied_indexers = true
|
||||
csharp_style_expression_bodied_lambdas = true
|
||||
csharp_style_expression_bodied_local_functions = true
|
||||
csharp_style_expression_bodied_methods = false
|
||||
csharp_style_expression_bodied_operators = false
|
||||
csharp_style_expression_bodied_properties = true
|
||||
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||
csharp_style_prefer_extended_property_pattern = true
|
||||
csharp_style_prefer_not_pattern = true
|
||||
csharp_style_prefer_pattern_matching = true
|
||||
csharp_style_prefer_switch_expression = true
|
||||
|
||||
# Null-checking preferences
|
||||
csharp_style_conditional_delegate_call = true
|
||||
|
||||
# Modifier preferences
|
||||
csharp_prefer_static_anonymous_function = true
|
||||
csharp_prefer_static_local_function = true
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
|
||||
csharp_style_prefer_readonly_struct = true
|
||||
csharp_style_prefer_readonly_struct_member = true
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = true
|
||||
csharp_prefer_simple_using_statement = true
|
||||
csharp_prefer_system_threading_lock = true
|
||||
csharp_style_namespace_declarations = block_scoped
|
||||
csharp_style_prefer_method_group_conversion = true
|
||||
csharp_style_prefer_primary_constructors = true
|
||||
csharp_style_prefer_simple_property_accessors = true
|
||||
csharp_style_prefer_top_level_statements = true
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true
|
||||
csharp_style_deconstructed_variable_declaration = true
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true
|
||||
csharp_style_inlined_variable_declaration = true
|
||||
csharp_style_prefer_implicitly_typed_lambda_expression = true
|
||||
csharp_style_prefer_index_operator = true
|
||||
csharp_style_prefer_local_over_anonymous_function = true
|
||||
csharp_style_prefer_null_check_over_type_check = true
|
||||
csharp_style_prefer_range_operator = true
|
||||
csharp_style_prefer_tuple_swap = true
|
||||
csharp_style_prefer_unbound_generic_type_in_nameof = true
|
||||
csharp_style_prefer_utf8_string_literals = true
|
||||
csharp_style_throw_expression = true
|
||||
csharp_style_unused_value_assignment_preference = discard_variable
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||
|
||||
# 'using' directive preferences
|
||||
csharp_using_directive_placement = outside_namespace
|
||||
|
||||
# New line preferences
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
|
||||
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
|
||||
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
|
||||
# New line preferences
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
@@ -58,6 +58,8 @@ bld/
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
.vscode/
|
||||
bin/
|
||||
certificates/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
PROPRIETARY LICENSE
|
||||
|
||||
Copyright (c) 2026 IFRI TECHNOLOGIES (PTY) Ltd. All rights reserved.
|
||||
|
||||
This software and its associated documentation (the "Software") are the
|
||||
proprietary property of IFRI TECHNOLOGIES (PTY) Ltd.
|
||||
|
||||
The Software is provided for internal use only. Unauthorized copying,
|
||||
distribution, modification, or use of this file via any medium is
|
||||
strictly prohibited.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using PostFundManagement.Infrastructure.Database;
|
||||
using static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
namespace PostFundManagement.Infrastructure.Extensions;
|
||||
namespace PostFundManagement.Api.Extensions;
|
||||
|
||||
public static class Postgres
|
||||
{
|
||||
+3
-1
@@ -6,7 +6,7 @@ using PostFundManagement.Infrastructure.Database;
|
||||
|
||||
namespace PostFundManagement.Api.Extensions;
|
||||
|
||||
public static class Api
|
||||
public static class Security
|
||||
{
|
||||
public static IServiceCollection AddSecurityApiSdk(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
@@ -42,6 +42,8 @@ public static class Api
|
||||
|
||||
public static IServiceCollection AddWebSecurity(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddAuthorization();
|
||||
|
||||
var certString = configuration["DataProtection:Certificate"] ?? configuration["DataProtection__Certificate"];
|
||||
var certPassword = configuration["DataProtection:Password"] ?? configuration["DataProtection__Password"];
|
||||
|
||||
@@ -5,20 +5,86 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>59a05094-4ac2-472d-af04-3407e909432d</UserSecretsId>
|
||||
<AssemblyOriginatorKeyFile>..\PostFundManagement.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>True</SignAssembly>
|
||||
<PackageLicenseFile>..\LICENSE</PackageLicenseFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Security (IODC)-->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.11" />
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="2.12.0" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="2.16.20" />
|
||||
</ItemGroup>
|
||||
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
|
||||
<PackageReference Include="IdentityModel.AspNetCore.OAuth2introspection" Version="6.2.0" />
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
|
||||
<PackageReference Include="IdentityModel" Version="6.2.0" />
|
||||
<PackageReference Include="KubernetesClient" Version="19.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Certificate" Version="10.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.11" />
|
||||
|
||||
<!-- Global Usings -->
|
||||
<ItemGroup>
|
||||
<Using Include="System.Security.Cryptography.X509Certificates" />
|
||||
<Using Include="Microsoft.AspNetCore.Authentication" />
|
||||
<Using Include="Microsoft.AspNetCore.Authentication.Cookies" />
|
||||
<Using Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Health Checks -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" Version="9.0.0" />
|
||||
|
||||
<Using Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
|
||||
<Using Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- API Versioning -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AccessTokenClient.Extensions" Version="5.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Abstractions" Version="10.2.1" />
|
||||
<PackageReference Include="Asp.Versioning.Http" Version="10.2.2" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="10.2.1" />
|
||||
|
||||
<Using Include="Asp.Versioning" />
|
||||
<Using Include="Asp.Versioning.Builder" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- API Documentation -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.11" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="2.17.1" />
|
||||
|
||||
<Using Include="Scalar.AspNetCore" />
|
||||
<Using Include="Microsoft.OpenApi" />
|
||||
<Using Include="Microsoft.AspNetCore.OpenApi" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- file nesting -->
|
||||
<ItemGroup>
|
||||
<ProjectCapability Include="ConfigurableFileNesting" />
|
||||
<ProjectCapability Include="ConfigurableFileNestingFeatureEnabled" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- CQRS -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<Using Include="FluentResults" />
|
||||
<Using Include="Mediator" />
|
||||
<Using Include="Quartz" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Shared Global Usings -->
|
||||
<ItemGroup>
|
||||
<Using Include="System.Web" />
|
||||
<Using Include="System.Diagnostics" />
|
||||
<Using Include="System.Reflection" />
|
||||
<Using Include="Microsoft.AspNetCore.Mvc" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore" />
|
||||
<Using Include="System.ComponentModel.DataAnnotations" />
|
||||
<Using Include="Microsoft.Extensions.DependencyInjection.Extensions" />
|
||||
<Using Include="System.Security.Cryptography.X509Certificates" />
|
||||
<Using Include="Microsoft.AspNetCore.DataProtection" />
|
||||
<Using Include="Refit" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,22 +1,100 @@
|
||||
using Scalar.AspNetCore;
|
||||
using PostFundManagement.Api.Extensions;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
using PostFundManagement.Domain.Health;
|
||||
using PostFundManagement.Domain.Mediator;
|
||||
using static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
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.AddQuartzScheduler(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);
|
||||
|
||||
builder.Services.AddHealthChecks()
|
||||
.AddCheck<QuartzHealthCheck>("QuartzScheduler")
|
||||
.AddCheck<PostgresHealthCheck>("PostgresDatabase")
|
||||
.AddCheck("Self", () => HealthCheckResult.Healthy());
|
||||
|
||||
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<ISchedulerFactory>();
|
||||
var scheduler = await schedulerFactory.GetScheduler(DefaultSchedulerName);
|
||||
|
||||
app.MapOpenApi();
|
||||
}
|
||||
if (!scheduler!.IsStarted)
|
||||
await scheduler.Start();
|
||||
|
||||
app.UseHsts();
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
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<int, RouteGroupBuilder>
|
||||
{
|
||||
{ 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();
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -2,17 +2,19 @@ namespace PostFundManagement.Domain.Extensions;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public const string DefaultSchedulerName = "pfm-scheduler";
|
||||
|
||||
public const string GeneralS3SettingsSection = "PfmS3Settings";
|
||||
|
||||
public const string EvidenceS3SettingsSection = "EvidenceS3Settings";
|
||||
|
||||
public const string ContractS3SettingsSection = "ContractS3Settings";
|
||||
|
||||
public const string GeneralQuotesBucketName = "pfm.general";
|
||||
public const string GeneralBucketName = "pfm.general";
|
||||
|
||||
public const string EvidenceQuotesBucketName = "pfm.evidence";
|
||||
public const string EvidenceBucketName = "pfm.evidence";
|
||||
|
||||
public const string ContractQuotesBucketName = "pfm.contract";
|
||||
public const string ContractBucketName = "pfm.contract";
|
||||
|
||||
public const string DatabaseConfigName = "PfmDatabase";
|
||||
|
||||
|
||||
@@ -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<SmtpSettings>(configuration.GetSection("Email"));
|
||||
|
||||
services.AddSingleton<EmailService>();
|
||||
|
||||
services.AddOpenTelemetry()
|
||||
.WithTracing(tracing => tracing.AddSource("Pfm.EmailService"))
|
||||
.WithMetrics(metrics => metrics.AddMeter("Pfm.EmailService"));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -29,9 +27,12 @@ public static class Quartz
|
||||
|
||||
storage.UseSystemTextJsonSerializer();
|
||||
storage.SetProperty("quartz.jobStore.clustered", "true");
|
||||
storage.SetProperty("quartz.jobStore.tablePrefix", "qrtz_");
|
||||
|
||||
storage.UsePostgres(connectionString!);
|
||||
storage.UsePostgres(options =>
|
||||
{
|
||||
options.ConnectionString = connectionString!;
|
||||
options.TablePrefix = "quartz.qrtz_";
|
||||
});
|
||||
storage.UseClustering(cluster =>
|
||||
{
|
||||
cluster.CheckinInterval = TimeSpan.FromSeconds(30);
|
||||
@@ -71,9 +72,12 @@ public static class Quartz
|
||||
|
||||
storage.UseSystemTextJsonSerializer();
|
||||
storage.SetProperty("quartz.jobStore.clustered", "true");
|
||||
storage.SetProperty("quartz.jobStore.tablePrefix", "qrtz_");
|
||||
|
||||
storage.UsePostgres(connectionString!);
|
||||
storage.UsePostgres(options =>
|
||||
{
|
||||
options.ConnectionString = connectionString!;
|
||||
options.TablePrefix = "quartz.qrtz_";
|
||||
});
|
||||
storage.UseClustering(cluster =>
|
||||
{
|
||||
cluster.CheckinInterval = TimeSpan.FromSeconds(30);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Services;
|
||||
using static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
namespace PostFundManagement.Domain.Extensions;
|
||||
|
||||
public static class S3
|
||||
{
|
||||
public static IServiceCollection AddGarageS3(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(configuration.GetSection($"{GeneralS3SettingsSection}:ServiceUrl").Value))
|
||||
{
|
||||
services.AddKeyedSingleton<IAmazonS3, AmazonS3Client>(GeneralBucketName, (provider, client) =>
|
||||
new AmazonS3Client(new BasicAWSCredentials(configuration.GetSection($"{GeneralS3SettingsSection}:AccessKey").Value,
|
||||
configuration.GetSection($"{GeneralS3SettingsSection}:SecretKey").Value),
|
||||
new AmazonS3Config
|
||||
{
|
||||
ServiceURL = configuration.GetSection($"{GeneralS3SettingsSection}:ServiceUrl").Value,
|
||||
AuthenticationRegion = configuration.GetSection($"{GeneralS3SettingsSection}:Region").Value,
|
||||
ForcePathStyle = true,
|
||||
EndpointDiscoveryEnabled = true,
|
||||
UseHttp = configuration.GetSection($"{GeneralS3SettingsSection}:ServiceUrl").Value!.Contains("http://", StringComparison.InvariantCultureIgnoreCase),
|
||||
}));
|
||||
|
||||
services.AddKeyedScoped<IS3Service, GeneralS3Service>(GeneralBucketName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(configuration.GetSection($"{EvidenceS3SettingsSection}:ServiceUrl").Value))
|
||||
{
|
||||
services.AddKeyedSingleton<IAmazonS3, AmazonS3Client>(EvidenceS3SettingsSection, (provider, client) =>
|
||||
new AmazonS3Client(new BasicAWSCredentials(configuration.GetSection($"{EvidenceS3SettingsSection}:AccessKey").Value,
|
||||
configuration.GetSection($"{EvidenceS3SettingsSection}:SecretKey").Value),
|
||||
new AmazonS3Config
|
||||
{
|
||||
ServiceURL = configuration.GetSection($"{EvidenceS3SettingsSection}:ServiceUrl").Value,
|
||||
AuthenticationRegion = configuration.GetSection($"{EvidenceS3SettingsSection}:Region").Value,
|
||||
ForcePathStyle = true,
|
||||
EndpointDiscoveryEnabled = true,
|
||||
UseHttp = configuration.GetSection($"{EvidenceS3SettingsSection}:ServiceUrl").Value!.Contains("http://", StringComparison.InvariantCultureIgnoreCase),
|
||||
}));
|
||||
|
||||
services.AddKeyedScoped<IS3Service, EvidenceS3Service>(EvidenceS3SettingsSection);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(configuration.GetSection($"{ContractS3SettingsSection}:ServiceUrl").Value))
|
||||
{
|
||||
services.AddKeyedSingleton<IAmazonS3, AmazonS3Client>(ContractS3SettingsSection, (provider, client) =>
|
||||
new AmazonS3Client(new BasicAWSCredentials(configuration.GetSection($"{ContractS3SettingsSection}:AccessKey").Value,
|
||||
configuration.GetSection($"{ContractS3SettingsSection}:SecretKey").Value),
|
||||
new AmazonS3Config
|
||||
{
|
||||
ServiceURL = configuration.GetSection($"{ContractS3SettingsSection}:ServiceUrl").Value,
|
||||
AuthenticationRegion = configuration.GetSection($"{ContractS3SettingsSection}:Region").Value,
|
||||
ForcePathStyle = true,
|
||||
EndpointDiscoveryEnabled = true,
|
||||
UseHttp = configuration.GetSection($"{ContractS3SettingsSection}:ServiceUrl").Value!.Contains("http://", StringComparison.InvariantCultureIgnoreCase),
|
||||
}));
|
||||
|
||||
services.AddKeyedScoped<IS3Service, ContractS3Service>(ContractS3SettingsSection);
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
namespace PostFundManagement.Domain.Health;
|
||||
|
||||
public sealed class PostgresHealthCheck(IConfiguration configuration) : IHealthCheck
|
||||
{
|
||||
private readonly string connectionString = configuration.GetConnectionString(DatabaseConfigName)!;
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var dataSource = NpgsqlDataSource.Create(connectionString);
|
||||
await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
|
||||
|
||||
await using var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT 1";
|
||||
|
||||
await command.ExecuteScalarAsync(cancellationToken);
|
||||
|
||||
return HealthCheckResult.Healthy($"{DatabaseConfigName} is responsive.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy($"{DatabaseConfigName} is unreachable.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
namespace PostFundManagement.Domain.Health;
|
||||
|
||||
public sealed class QuartzHealthCheck(ISchedulerFactory schedulerFactory) : IHealthCheck
|
||||
{
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var scheduler = await schedulerFactory.GetScheduler(DefaultSchedulerName, cancellationToken);
|
||||
|
||||
if(scheduler == null)
|
||||
return HealthCheckResult.Unhealthy($"Scheduler with name '{DefaultSchedulerName}' not found.");
|
||||
|
||||
if (!scheduler.IsStarted)
|
||||
return HealthCheckResult.Unhealthy($"{DefaultSchedulerName} Quartz scheduler is not running");
|
||||
|
||||
await scheduler.CheckExists(new JobKey(Guid.NewGuid().ToString()), cancellationToken);
|
||||
|
||||
return HealthCheckResult.Healthy($"{DefaultSchedulerName} Quartz scheduler is ready");
|
||||
}
|
||||
catch (SchedulerException)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy($"{DefaultSchedulerName} Quartz scheduler cannot connect to the store");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyOriginatorKeyFile>..\PostFundManagement.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>True</SignAssembly>
|
||||
<PackageLicenseFile>..\LICENSE</PackageLicenseFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Transformation -->
|
||||
@@ -37,7 +40,7 @@
|
||||
|
||||
<!-- API SDK Composer-->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="15.1.0" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="15.2.0" />
|
||||
|
||||
<Using Include="Refit" />
|
||||
</ItemGroup>
|
||||
@@ -55,7 +58,7 @@
|
||||
<!-- API Documentation -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.11" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="2.16.20" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="2.17.1" />
|
||||
|
||||
<Using Include="Scalar.AspNetCore" />
|
||||
<Using Include="Microsoft.OpenApi" />
|
||||
@@ -65,7 +68,7 @@
|
||||
<!-- Quartz Scheduler-->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Hashids.net" Version="1.7.0" />
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="3.0.157">
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="3.0.177">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -171,8 +174,8 @@
|
||||
|
||||
<!-- Amazon S3 SDK -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AWSSDK.Extensions.NetCore.Setup" Version="4.0.100.8" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="4.0.102.1" />
|
||||
<PackageReference Include="AWSSDK.Extensions.NetCore.Setup" Version="4.0.101" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="4.0.102.3" />
|
||||
|
||||
<!-- global Usings -->
|
||||
<Using Include="Amazon.S3" />
|
||||
|
||||
@@ -3,7 +3,7 @@ using static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
namespace PostFundManagement.Domain.Services;
|
||||
|
||||
public sealed class ContractS3Service(IConfiguration configuration, [FromKeyedServices(ContractQuotesBucketName)] IAmazonS3 amazonS3) :
|
||||
public sealed class ContractS3Service(IConfiguration configuration, [FromKeyedServices(ContractBucketName)] IAmazonS3 amazonS3) :
|
||||
S3ServiceBase(amazonS3), IS3Service
|
||||
{
|
||||
protected override string BucketName => configuration.GetSection($"{ContractS3SettingsSection}:BucketName").Value ?? "";
|
||||
|
||||
@@ -3,7 +3,7 @@ using static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
namespace PostFundManagement.Domain.Services;
|
||||
|
||||
public sealed class EvidenceS3Service(IConfiguration configuration, [FromKeyedServices(EvidenceQuotesBucketName)] IAmazonS3 amazonS3) :
|
||||
public sealed class EvidenceS3Service(IConfiguration configuration, [FromKeyedServices(EvidenceBucketName)] IAmazonS3 amazonS3) :
|
||||
S3ServiceBase(amazonS3), IS3Service
|
||||
{
|
||||
protected override string BucketName => configuration.GetSection($"{EvidenceS3SettingsSection}:BucketName").Value ?? "";
|
||||
|
||||
@@ -3,7 +3,7 @@ using static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
namespace PostFundManagement.Domain.Services;
|
||||
|
||||
public sealed class GeneralS3Service(IConfiguration configuration, [FromKeyedServices(GeneralQuotesBucketName)] IAmazonS3 amazonS3) :
|
||||
public sealed class GeneralS3Service(IConfiguration configuration, [FromKeyedServices(GeneralBucketName)] IAmazonS3 amazonS3) :
|
||||
S3ServiceBase(amazonS3), IS3Service
|
||||
{
|
||||
protected override string BucketName => configuration.GetSection($"{GeneralS3SettingsSection}:BucketName").Value ?? "";
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
-- This script is for PostgreSQL
|
||||
|
||||
-- This initializes the database to pristine for Quartz, by first removing any existing Quartz tables
|
||||
-- and then recreating them from scratch.
|
||||
-- Should you only require it to create the tables, set DropDb to 0.
|
||||
-- 1. Create the dedicated schema
|
||||
CREATE SCHEMA IF NOT EXISTS quartz;
|
||||
|
||||
-- 2. Set the search path so the creation script targets this schema
|
||||
SET search_path TO quartz;
|
||||
|
||||
DO $$
|
||||
DECLARE DropDb INT := 1; -- Set this to 0 to skip DROP statements, 1 to include them
|
||||
BEGIN
|
||||
IF DropDb = 1 THEN
|
||||
SET client_min_messages = WARNING;
|
||||
DROP TABLE IF EXISTS qrtz_fired_triggers;
|
||||
DROP TABLE IF EXISTS qrtz_paused_trigger_grps;
|
||||
DROP TABLE IF EXISTS qrtz_scheduler_state;
|
||||
DROP TABLE IF EXISTS qrtz_locks;
|
||||
DROP TABLE IF EXISTS qrtz_simprop_triggers;
|
||||
DROP TABLE IF EXISTS qrtz_simple_triggers;
|
||||
DROP TABLE IF EXISTS qrtz_cron_triggers;
|
||||
DROP TABLE IF EXISTS qrtz_blob_triggers;
|
||||
DROP TABLE IF EXISTS qrtz_triggers;
|
||||
DROP TABLE IF EXISTS qrtz_job_details;
|
||||
DROP TABLE IF EXISTS qrtz_calendars;
|
||||
SET client_min_messages = NOTICE;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE qrtz_job_details
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
job_name TEXT NOT NULL,
|
||||
job_group TEXT NOT NULL,
|
||||
description TEXT NULL,
|
||||
job_class_name TEXT NOT NULL,
|
||||
is_durable BOOL NOT NULL,
|
||||
is_nonconcurrent BOOL NOT NULL,
|
||||
is_update_data BOOL NOT NULL,
|
||||
requests_recovery BOOL NOT NULL,
|
||||
job_data BYTEA NULL,
|
||||
PRIMARY KEY (sched_name, job_name, job_group)
|
||||
);
|
||||
|
||||
CREATE TABLE qrtz_triggers
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
trigger_name TEXT NOT NULL,
|
||||
trigger_group TEXT NOT NULL,
|
||||
job_name TEXT NOT NULL,
|
||||
job_group TEXT NOT NULL,
|
||||
description TEXT NULL,
|
||||
next_fire_time BIGINT NULL,
|
||||
prev_fire_time BIGINT NULL,
|
||||
priority INTEGER NULL,
|
||||
trigger_state TEXT NOT NULL,
|
||||
trigger_type TEXT NOT NULL,
|
||||
start_time BIGINT NOT NULL,
|
||||
end_time BIGINT NULL,
|
||||
calendar_name TEXT NULL,
|
||||
misfire_instr SMALLINT NULL,
|
||||
misfire_orig_fire_time BIGINT NULL,
|
||||
execution_group VARCHAR(200) NULL,
|
||||
preferred_node VARCHAR(200) NULL,
|
||||
preferred_node_auto BOOL NOT NULL DEFAULT FALSE,
|
||||
job_data BYTEA NULL,
|
||||
PRIMARY KEY (sched_name, trigger_name, trigger_group),
|
||||
FOREIGN KEY (sched_name, job_name, job_group)
|
||||
REFERENCES qrtz_job_details (sched_name, job_name, job_group)
|
||||
);
|
||||
|
||||
CREATE TABLE qrtz_simple_triggers
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
trigger_name TEXT NOT NULL,
|
||||
trigger_group TEXT NOT NULL,
|
||||
repeat_count BIGINT NOT NULL,
|
||||
repeat_interval BIGINT NOT NULL,
|
||||
times_triggered BIGINT NOT NULL,
|
||||
PRIMARY KEY (sched_name, trigger_name, trigger_group),
|
||||
FOREIGN KEY (sched_name, trigger_name, trigger_group)
|
||||
REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE qrtz_simprop_triggers
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
trigger_name TEXT NOT NULL,
|
||||
trigger_group TEXT NOT NULL,
|
||||
str_prop_1 TEXT NULL,
|
||||
str_prop_2 TEXT NULL,
|
||||
str_prop_3 TEXT NULL,
|
||||
int_prop_1 INTEGER NULL,
|
||||
int_prop_2 INTEGER NULL,
|
||||
long_prop_1 BIGINT NULL,
|
||||
long_prop_2 BIGINT NULL,
|
||||
dec_prop_1 NUMERIC NULL,
|
||||
dec_prop_2 NUMERIC NULL,
|
||||
bool_prop_1 BOOL NULL,
|
||||
bool_prop_2 BOOL NULL,
|
||||
time_zone_id TEXT NULL,
|
||||
PRIMARY KEY (sched_name, trigger_name, trigger_group),
|
||||
FOREIGN KEY (sched_name, trigger_name, trigger_group)
|
||||
REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE qrtz_cron_triggers
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
trigger_name TEXT NOT NULL,
|
||||
trigger_group TEXT NOT NULL,
|
||||
cron_expression TEXT NOT NULL,
|
||||
time_zone_id TEXT,
|
||||
PRIMARY KEY (sched_name, trigger_name, trigger_group),
|
||||
FOREIGN KEY (sched_name, trigger_name, trigger_group)
|
||||
REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE qrtz_blob_triggers
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
trigger_name TEXT NOT NULL,
|
||||
trigger_group TEXT NOT NULL,
|
||||
blob_data BYTEA NULL,
|
||||
PRIMARY KEY (sched_name, trigger_name, trigger_group),
|
||||
FOREIGN KEY (sched_name, trigger_name, trigger_group)
|
||||
REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE qrtz_calendars
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
calendar_name TEXT NOT NULL,
|
||||
calendar BYTEA NOT NULL,
|
||||
PRIMARY KEY (sched_name, calendar_name)
|
||||
);
|
||||
|
||||
CREATE TABLE qrtz_paused_trigger_grps
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
trigger_group TEXT NOT NULL,
|
||||
PRIMARY KEY (sched_name, trigger_group)
|
||||
);
|
||||
|
||||
CREATE TABLE qrtz_fired_triggers
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
entry_id TEXT NOT NULL,
|
||||
trigger_name TEXT NOT NULL,
|
||||
trigger_group TEXT NOT NULL,
|
||||
instance_name TEXT NOT NULL,
|
||||
fired_time BIGINT NOT NULL,
|
||||
sched_time BIGINT NOT NULL,
|
||||
priority INTEGER NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
job_name TEXT NULL,
|
||||
job_group TEXT NULL,
|
||||
is_nonconcurrent BOOL NOT NULL,
|
||||
requests_recovery BOOL NULL,
|
||||
execution_group VARCHAR(200) NULL,
|
||||
PRIMARY KEY (sched_name, entry_id)
|
||||
);
|
||||
|
||||
CREATE TABLE qrtz_scheduler_state
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
instance_name TEXT NOT NULL,
|
||||
last_checkin_time BIGINT NOT NULL,
|
||||
checkin_interval BIGINT NOT NULL,
|
||||
PRIMARY KEY (sched_name, instance_name)
|
||||
);
|
||||
|
||||
CREATE TABLE qrtz_locks
|
||||
(
|
||||
sched_name TEXT NOT NULL,
|
||||
lock_name TEXT NOT NULL,
|
||||
PRIMARY KEY (sched_name, lock_name)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_qrtz_j_g_n ON qrtz_job_details (sched_name, job_group, job_name);
|
||||
CREATE INDEX idx_qrtz_t_j ON qrtz_triggers (sched_name, job_name, job_group);
|
||||
CREATE INDEX idx_qrtz_t_c ON qrtz_triggers (sched_name, calendar_name);
|
||||
CREATE INDEX idx_qrtz_t_g_n ON qrtz_triggers (sched_name, trigger_group, trigger_name);
|
||||
CREATE INDEX idx_qrtz_t_nft_st ON qrtz_triggers (sched_name, trigger_state, next_fire_time);
|
||||
CREATE INDEX idx_qrtz_ft_inst_job_req_rcvry ON qrtz_fired_triggers (sched_name, instance_name, requests_recovery);
|
||||
CREATE INDEX idx_qrtz_ft_j_g ON qrtz_fired_triggers (sched_name, job_name, job_group);
|
||||
CREATE INDEX idx_qrtz_ft_t_g ON qrtz_fired_triggers (sched_name, trigger_name, trigger_group);
|
||||
@@ -5,6 +5,9 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<UserSecretsId>c4131f8d-ff78-432e-86d2-f6e131bd4cd3</UserSecretsId>
|
||||
<AssemblyOriginatorKeyFile>..\PostFundManagement.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>True</SignAssembly>
|
||||
<PackageLicenseFile>..\LICENSE</PackageLicenseFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Database -->
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,120 @@
|
||||
# PostFundManagement (PFM) Platform
|
||||
|
||||
An enterprise system designed to manage the entire post-funding grant and project lifecycle. PFM provides end-to-end oversight across strategic portfolios, programme allocation, project execution tracking, financial disbursements, monitoring & evaluation (M&E), and candidate notifications.
|
||||
|
||||
---
|
||||
|
||||
## 🏛️ System Architecture & Domain Model
|
||||
|
||||
The application follows a modular, hierarchical architecture where high-level funding directives cascade into measurable project outcomes and evidence verification.
|
||||
|
||||
[Portfolios] ───> [Programmes] ───> [Projects] ───> [Awards] ───> [Contracts] ───> [Beneficiaries] ──> [Outcomes]
|
||||
│ │ │
|
||||
▼ │ ▼
|
||||
[Rules] ├─> [Milestones] ──> [Activities] ──> [Evidences]
|
||||
├─> [Disbursements]
|
||||
├─> [Indicators]
|
||||
├─> [Risks] & [Issues]
|
||||
└─> [SiteVisits] & [ComplianceItems]
|
||||
|
||||
### **1. Portfolio & Programme Governance**
|
||||
* **Portfolios**: Top-level containers for strategic investment themes.
|
||||
* **Programmes**: Direct implementation vehicles linked to specific financial instruments (`Instruments`) and operational guidelines (`Rules`).
|
||||
|
||||
### **2. Project Execution & Monitoring**
|
||||
* **Projects**: Central operational units categorized by `IndustrySector` and `ProjectType`.
|
||||
* **Milestones & Activities**: Project progress is tracked through structured milestones. Project Managers author `Activities` against specific milestones to log ground work.
|
||||
* **Risks, Issues & Compliance**: Managed via dedicated tracking logs (`Risks`, `Issues`, `ComplianceItems`, `ChangeRequests`) to maintain project integrity.
|
||||
|
||||
### **3. Grants, Contracting & Disbursements**
|
||||
* **Awards & Contracts**: Binding grant agreements signed with target `Organisations`.
|
||||
* **Beneficiaries & Outcomes**: Social and economic impact measurement down to individual beneficiary groups.
|
||||
* **Disbursements**: Milestone-gated tranche releases tied to approved evidence and verification audits.
|
||||
|
||||
### **4. Monitoring, Evaluation & Evidence (M&E)**
|
||||
* **Indicators**: KPI baseline, target, and actual metrics tracking.
|
||||
* **Evidences**: Verifiable artifacts (`EvidenceType`) uploaded alongside activities, indicators, or milestones.
|
||||
* **Site Visits**: Independent audit logs (`SiteVisits`) submitted by field inspectors.
|
||||
|
||||
### **5. Notifications & Platform Services**
|
||||
* Multi-channel delivery engine (`NotificationPlatform`) tracking candidate invitations, reminders, and status updates (`NotificationStatus`) across Email, SMS, WhatsApp, In-App, and Webhooks.
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Key System Enums
|
||||
|
||||
| Enum | Scope | Purpose |
|
||||
| :--- | :--- | :--- |
|
||||
| `IndustrySector` | Project Domain | Standardizes reporting across sectors (e.g., ICT, Agriculture, Manufacturing). |
|
||||
| `ProjectType` | Project Domain | Classifies project scope (e.g., R&D, Infrastructure, Capacity Building). |
|
||||
| `EvidenceType` | M&E / Verification | Categorizes uploaded proof (e.g., Attendance Register, Geotagged Photo, Bank Statement). |
|
||||
| `NotificationPlatform` | System / Comms | Outlines dispatch channels (Email, SMS, WhatsApp, Push, Webhooks). |
|
||||
| `NotificationStatus` | System / Comms | Tracks communication lifecycle (Pending, Queued, Sent, Delivered, Failed). |
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Tech Stack & Patterns
|
||||
|
||||
* **Backend Framework**: .NET 8 / 9 C# Web API
|
||||
* **Database**: PostgreSQL
|
||||
* **ORM**: Entity Framework Core (Fluent API Configuration)
|
||||
* **Architecture**: Clean Architecture / Domain-Driven Design (DDD) with CQRS (`MediatR`)
|
||||
* **Utilities**: `Humanizer` (Automated entity-to-table pluralization)
|
||||
|
||||
---
|
||||
|
||||
## 📁 Solution Structure
|
||||
|
||||
```text
|
||||
src/
|
||||
├── PostFundManagement.Domain/ # Core Domain Entities, Enums & Interfaces
|
||||
├── PostFundManagement.Infrastructure/ # EF Core Configurations, DbContext, Migrations
|
||||
├── PostFundManagement.Application/ # CQRS Handlers, DTOs, Business Rules & Logic
|
||||
└── PostFundManagement.Api/ # REST Controllers, Middleware, Auth & Swagger
|
||||
```
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
.NET 8.0 SDK or higher
|
||||
PostgreSQL 14+
|
||||
dotnet-ef CLI Tool (dotnet tool install --global dotnet-ef)
|
||||
|
||||
## Setup & Execution
|
||||
|
||||
## Clone Repository
|
||||
|
||||
1. Clone Repository:
|
||||
|
||||
```bash
|
||||
git clone [https://github.com/your-org/PostFundManagement.git](https://github.com/your-org/PostFundManagement.git)
|
||||
cd PostFundManagement
|
||||
```
|
||||
|
||||
2. Configure Database Connection:
|
||||
Update appsettings.Development.json in the API project with your local PostgreSQL details:
|
||||
|
||||
```json
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=localhost;Database=pfm_db;Username=postgres;Password=your_password"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Apply Database Migrations:
|
||||
|
||||
```bash
|
||||
dotnet ef database update --project src/PostFundManagement.Infrastructure
|
||||
```
|
||||
|
||||
4. Launch Application:
|
||||
|
||||
```bash
|
||||
dotnet run --project src/PostFundManagement.Api
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
Internal Proprietary Software — All rights reserved.
|
||||
Reference in New Issue
Block a user