Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67c08b40ca |
-287
@@ -1,287 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
*.env
|
*.env
|
||||||
*.env.json
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
@@ -37,7 +36,6 @@ obj/x64/
|
|||||||
[Rr]eleases/x86/
|
[Rr]eleases/x86/
|
||||||
bin/x86/
|
bin/x86/
|
||||||
obj/x86/
|
obj/x86/
|
||||||
bin/
|
|
||||||
|
|
||||||
[Ww][Ii][Nn]32/
|
[Ww][Ii][Nn]32/
|
||||||
[Aa][Rr][Mm]/
|
[Aa][Rr][Mm]/
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
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,41 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Communications;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetInvitesEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/communications/invites", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Invites.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderByDescending(i => i.CreatedAt)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of sent invitations")
|
|
||||||
.WithName(typeof(GetInvitesEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Communications);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Communications;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class SendInviteEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record InviteRequest(long OrganisationId, long InvitedOrganisationId, long AwardId, long ContractId, Guid Recipient, string Message, NotificationPlatform Platform);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/communications/invite", async (InviteRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var currentUserId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var invite = new Domain.Entities.Invite
|
|
||||||
{
|
|
||||||
OrganisationId = request.OrganisationId,
|
|
||||||
InvitedOrganisationId = request.InvitedOrganisationId,
|
|
||||||
AwardId = request.AwardId,
|
|
||||||
ContractId = request.ContractId,
|
|
||||||
Recipient = request.Recipient,
|
|
||||||
CreatedBy = currentUserId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Invites.Add(invite);
|
|
||||||
|
|
||||||
context.Notifications.Add(new Domain.Entities.Notification
|
|
||||||
{
|
|
||||||
OrganisationId = request.OrganisationId,
|
|
||||||
Recipient = request.Recipient,
|
|
||||||
Platform = request.Platform,
|
|
||||||
Status = NotificationStatus.Pending,
|
|
||||||
Subject = "Invitation to Onboard",
|
|
||||||
Message = request.Message,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
});
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Ok(invite)
|
|
||||||
: Results.BadRequest("Failed to create invite");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Trigger an invite to an organization or candidate (specifying NotificationPlatform)")
|
|
||||||
.WithName(typeof(SendInviteEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Invite>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Communications);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Communications;
|
|
||||||
|
|
||||||
public class SyncEvent : EventBase, IEvent
|
|
||||||
{
|
|
||||||
public string Name { get; set; } = nameof(SyncEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SyncEventHandler : INotificationHandler<SyncEvent>
|
|
||||||
{
|
|
||||||
public ValueTask Handle(SyncEvent notification, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
Trace.WriteLine($"Integration Sync executed. Correlation ID: {notification.CorrelationId}");
|
|
||||||
|
|
||||||
return ValueTask.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class SyncEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/integration/sync", async (IJobOrchestrator jobOrchestrator,
|
|
||||||
CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var syncEvent = new SyncEvent();
|
|
||||||
|
|
||||||
await jobOrchestrator.SendAsync(syncEvent, cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Queued = true, syncEvent.CorrelationId });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Trigger integration and data synchronization in the background")
|
|
||||||
.WithName(typeof(SyncEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Communications);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Evidence;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class CreateIndicatorEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateIndicatorRequest(long ProjectId, string Name, UnitOfMeasure UnitOfMeasure, decimal BaselineAmount, decimal TargetAmount);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/evidence/indicators", async (CreateIndicatorRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken))
|
|
||||||
return Results.BadRequest($"Project with ID {request.ProjectId} does not exist.");
|
|
||||||
|
|
||||||
var indicator = new Domain.Entities.Indicator
|
|
||||||
{
|
|
||||||
ProjectId = request.ProjectId,
|
|
||||||
Name = request.Name,
|
|
||||||
UnitOfMeasure = request.UnitOfMeasure,
|
|
||||||
BaselineAmount = request.BaselineAmount,
|
|
||||||
TargetAmount = request.TargetAmount,
|
|
||||||
ActualAmount = request.BaselineAmount,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Indicators.Add(indicator);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/evidence/indicators/{indicator.Id}", indicator)
|
|
||||||
: Results.BadRequest("Failed to create indicator");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Define indicators and baselines/targets (M08)")
|
|
||||||
.WithName(typeof(CreateIndicatorEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<PostFundManagement.Domain.Entities.Indicator>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Evidence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Evidence;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class CreateMilestoneEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateMilestoneRequest(long ProjectId, DateTime DueAt, string Name, Priority Priority);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/evidence/milestones", async (CreateMilestoneRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken))
|
|
||||||
return Results.BadRequest($"Project with ID {request.ProjectId} does not exist.");
|
|
||||||
|
|
||||||
var milestone = new Domain.Entities.Milestone
|
|
||||||
{
|
|
||||||
ProjectId = request.ProjectId,
|
|
||||||
DueAt = request.DueAt,
|
|
||||||
Name = request.Name,
|
|
||||||
Priority = request.Priority,
|
|
||||||
Status = ApprovalStatus.Pending,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Milestones.Add(milestone);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/evidence/milestones/{milestone.Id}", milestone)
|
|
||||||
: Results.BadRequest("Failed to create a new milestone");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Define contractual milestones (M07)")
|
|
||||||
.WithName(typeof(CreateMilestoneRequest).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Milestone>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Evidence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Evidence;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetEvidenceFilesEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/evidence/files", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Evidences.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderByDescending(e => e.CreatedAt)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of all uploaded evidence files metadata")
|
|
||||||
.WithName(typeof(GetEvidenceFilesEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Evidence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Evidence;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetIndicatorsEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record RecordActualRequest(decimal ActualAmount);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/evidence/indicators", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Indicators.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderByDescending(i => i.CreatedAt)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of indicators")
|
|
||||||
.WithName(typeof(GetIndicatorsEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Evidence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Evidence;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetMilestonesEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/evidence/milestones", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Milestones.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderByDescending(m => m.CreatedAt)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of all milestones")
|
|
||||||
.WithName(typeof(GetMilestonesEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Evidence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Evidence;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetProjectMilestonesEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/evidence/milestones/project/{projectId:long}", async (long projectId,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var milestones = await context.Milestones.AsNoTracking()
|
|
||||||
.Where(m => m.ProjectId == projectId)
|
|
||||||
.OrderBy(m => m.DueAt)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(milestones);
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of milestones for a project")
|
|
||||||
.WithName(typeof(GetProjectMilestonesEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Evidence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Evidence;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class RecordIndicatorActualAmountEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/evidence/indicators/{id:long}/actuals/{actualAmount:decimal}", async (long id, decimal actualAmount,
|
|
||||||
ClaimsPrincipal principal, IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var indicator = await context.Indicators.FirstOrDefaultAsync(i => i.Id == id, cancellationToken);
|
|
||||||
|
|
||||||
if (indicator is null)
|
|
||||||
return Results.NotFound($"Indicator with ID {id} does not exist.");
|
|
||||||
|
|
||||||
indicator.ActualAmount = actualAmount;
|
|
||||||
|
|
||||||
context.Indicators.Update(indicator);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Ok(indicator)
|
|
||||||
: Results.BadRequest("Failed to record actual amount");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Record actual values for indicators (M08)")
|
|
||||||
.WithName(typeof(RecordIndicatorActualAmountEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Indicator>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Evidence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Evidence;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class UploadEvidenceFilesEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/evidence/files", async (HttpRequest request, ClaimsPrincipal principal, IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
[FromKeyedServices(Constants.EvidenceS3SettingsSection)] IS3Service s3Service, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
if (!request.HasFormContentType)
|
|
||||||
return Results.BadRequest("Request must be a multipart form.");
|
|
||||||
|
|
||||||
var form = await request.ReadFormAsync(cancellationToken);
|
|
||||||
var file = form.Files.GetFile("file");
|
|
||||||
|
|
||||||
if (file == null || file.Length == 0)
|
|
||||||
return Results.BadRequest("No file uploaded or file is empty.");
|
|
||||||
|
|
||||||
_ = long.TryParse(form["projectId"], out var projectId);
|
|
||||||
_ = long.TryParse(form["milestoneId"], out var milestoneId);
|
|
||||||
_ = long.TryParse(form["indicatorId"], out var indicatorId);
|
|
||||||
_ = long.TryParse(form["activityId"], out var activityId);
|
|
||||||
_ = Enum.TryParse<EvidenceType>(form["type"], out var type);
|
|
||||||
|
|
||||||
using var stream = file.OpenReadStream();
|
|
||||||
|
|
||||||
var uploadResult = await s3Service.UploadFileAsync(file.FileName, stream, file.ContentType, cancellationToken);
|
|
||||||
|
|
||||||
if (uploadResult.IsFailed)
|
|
||||||
return Results.BadRequest(uploadResult.Errors.FirstOrDefault()?.Message ?? "File upload failed.");
|
|
||||||
|
|
||||||
var documentUrl = uploadResult.Value;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var evidence = new Domain.Entities.Evidence
|
|
||||||
{
|
|
||||||
ProjectId = projectId,
|
|
||||||
MilestoneId = milestoneId,
|
|
||||||
IndicatorId = indicatorId,
|
|
||||||
ActivityId = activityId,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow,
|
|
||||||
Version = 1,
|
|
||||||
Type = type,
|
|
||||||
DocumentUrl = documentUrl,
|
|
||||||
Status = ApprovalStatus.Pending
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Evidences.Add(evidence);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/evidence/files/{evidence.Id}", evidence)
|
|
||||||
: Results.BadRequest("Evidence upload failed");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.DisableAntiforgery()
|
|
||||||
.WithDescription("Upload supporting evidence file to S3 and register metadata (M14)")
|
|
||||||
.WithName(typeof(UploadEvidenceFilesEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<PostFundManagement.Domain.Entities.Evidence>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Evidence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Execution;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class ApproveDisbursementEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/execution/disbursements/{id:long}/approve", async (long id, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var disbursement = await context.Disbursements.FirstOrDefaultAsync(d => d.Id == id, cancellationToken);
|
|
||||||
|
|
||||||
if (disbursement == null)
|
|
||||||
return Results.NotFound($"Disbursement with ID {id} does not exist.");
|
|
||||||
|
|
||||||
disbursement.Status = ApprovalStatus.Approved;
|
|
||||||
disbursement.UpdatedBy = userId;
|
|
||||||
disbursement.UpdatedAt = DateTime.UtcNow;
|
|
||||||
|
|
||||||
context.Disbursements.Update(disbursement);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Ok(disbursement)
|
|
||||||
: Results.BadRequest("Failed to approve payment");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Approve disbursement and release payment (M06)")
|
|
||||||
.WithName(typeof(ApproveDisbursementEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Disbursement>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Execution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Execution;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class CreateDisbursementEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateDisbursementRequest(long ProjectId, long? MilestoneId, decimal Amount);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/execution/disbursements", async (CreateDisbursementRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
if (request.Amount <= 0)
|
|
||||||
return Results.BadRequest("Amount must be greater than zero.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken))
|
|
||||||
return Results.BadRequest($"Project with ID {request.ProjectId} does not exist.");
|
|
||||||
|
|
||||||
var disbursement = new Domain.Entities.Disbursement
|
|
||||||
{
|
|
||||||
ProjectId = request.ProjectId,
|
|
||||||
MilestoneId = request.MilestoneId,
|
|
||||||
Amount = request.Amount,
|
|
||||||
Status = ApprovalStatus.Pending,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Disbursements.Add(disbursement);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/execution/disbursements/{disbursement.Id}", disbursement)
|
|
||||||
: Results.BadRequest("Failed to create disbursement");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Create a new disbursement schedule (M06)")
|
|
||||||
.WithName(typeof(CreateDisbursementEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Disbursement>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Execution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Execution;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class CreateRiskEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateRiskRequest(long ProjectId, RiskLikelihood Likelihood, RiskImpact Impact, string Name, string Description);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/execution/risks", async (CreateRiskRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken))
|
|
||||||
return Results.BadRequest($"Project with ID {request.ProjectId} does not exist.");
|
|
||||||
|
|
||||||
var risk = new Domain.Entities.Risk
|
|
||||||
{
|
|
||||||
ProjectId = request.ProjectId,
|
|
||||||
Likelihood = request.Likelihood,
|
|
||||||
Impact = request.Impact,
|
|
||||||
Name = request.Name,
|
|
||||||
Description = request.Description,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Risks.Add(risk);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/execution/risks/{risk.Id}", risk)
|
|
||||||
: Results.BadRequest("Failed to create project risk");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Add risk to risk register (M09)")
|
|
||||||
.WithName(typeof(CreateRiskEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Risk>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Execution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Execution;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetDisbursementsEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/execution/disbursements", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Disbursements.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderByDescending(d => d.CreatedAt)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of disbursements")
|
|
||||||
.WithName(typeof(GetDisbursementsEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Execution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Execution;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetProjectRisksEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/execution/risks/project/{projectId:long}", async (long projectId,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var risks = await context.Risks.AsNoTracking()
|
|
||||||
.Where(r => r.ProjectId == projectId)
|
|
||||||
.OrderByDescending(r => r.CreatedAt)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(risks);
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of risks for a project")
|
|
||||||
.WithName(typeof(GetProjectRisksEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Execution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Execution;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetRisksEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/execution/risks", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Risks.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderByDescending(r => r.CreatedAt)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of all risks")
|
|
||||||
.WithName(typeof(GetRisksEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Execution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Governance;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class CreateOrganisationEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateOrganisationRequest(string Name, string RegistrationNo, string Email, OrganisationType Type);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/governance/organisations", async (CreateOrganisationRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var organisation = new Domain.Entities.Organisation
|
|
||||||
{
|
|
||||||
Name = request.Name,
|
|
||||||
RegistrationNo = request.RegistrationNo,
|
|
||||||
Email = request.Email,
|
|
||||||
Type = request.Type,
|
|
||||||
Status = ActivityStatus.Active,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Organisations.Add(organisation);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/governance/organisations/{organisation.Id}", organisation)
|
|
||||||
: Results.BadRequest("Failed to create organisation");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Create a new organization profile")
|
|
||||||
.WithName(typeof(CreateOrganisationEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Organisation>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Governance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Governance;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class DashboardEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/governance/dashboard", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var totalPortfolioValue = await context.Awards.SumAsync(a => a.Amount, cancellationToken);
|
|
||||||
var activeAwardsCount = await context.Awards.CountAsync(a => a.Status == ApprovalStatus.Approved, cancellationToken);
|
|
||||||
|
|
||||||
var disbursedAmount = await context.Disbursements.Where(d => d.Status == ApprovalStatus.Approved)
|
|
||||||
.SumAsync(d => d.Amount, cancellationToken);
|
|
||||||
|
|
||||||
var totalProjects = await context.Projects.CountAsync(cancellationToken);
|
|
||||||
var onTrackProjects = await context.Projects.CountAsync(p => p.Status == ApprovalStatus.Approved, cancellationToken);
|
|
||||||
|
|
||||||
var onTrackPercentage = totalProjects > 0
|
|
||||||
? Math.Round((double)onTrackProjects / totalProjects * 100, 1)
|
|
||||||
: 100.0;
|
|
||||||
|
|
||||||
var atRiskCount = await context.Risks.CountAsync(r => r.Impact == RiskImpact.Critical || r.Impact == RiskImpact.Major, cancellationToken);
|
|
||||||
|
|
||||||
var beneficiariesReached = await context.Beneficiaries.SumAsync(b => b.ReachedCount, cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new
|
|
||||||
{
|
|
||||||
PortfolioValue = totalPortfolioValue,
|
|
||||||
ActiveAwards = activeAwardsCount,
|
|
||||||
Disbursed = disbursedAmount,
|
|
||||||
OnTrackPercentage = onTrackPercentage,
|
|
||||||
AtRiskCount = atRiskCount,
|
|
||||||
BeneficiariesReached = beneficiariesReached
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get executive portfolio dashboard metrics and KPIs")
|
|
||||||
.WithName(typeof(DashboardEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Governance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Governance;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetInstrumentsEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/governance/instruments", async (IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var instruments = await context.Instruments.AsNoTracking().ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(instruments);
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get all funding instruments")
|
|
||||||
.WithName(typeof(GetInstrumentsEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<List<Domain.Entities.Instrument>>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Governance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Governance;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetOrganisationByIdEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/governance/organisations/{id:long}", async (long id,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var organisation = await context.Organisations.AsNoTracking().FirstOrDefaultAsync(o => o.Id == id, cancellationToken);
|
|
||||||
|
|
||||||
return organisation != null ? Results.Ok(organisation) : Results.NotFound();
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get organization profile by ID")
|
|
||||||
.WithName(typeof(GetOrganisationByIdEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Organisation>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Governance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Governance;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetOrganisationsEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateOrganisationRequest(string Name, string RegistrationNo, string Email, OrganisationType Type);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/governance/organisations", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Organisations.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderBy(o => o.Name)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a paginated directory of organizations")
|
|
||||||
.WithName(typeof(GetOrganisationsEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Governance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Governance;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetPortfoliosEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/governance/portfolios", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var portfolios = await context.Portfolios.AsNoTracking().ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(portfolios);
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get all portfolios")
|
|
||||||
.WithName(typeof(GetPortfoliosEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<List<Domain.Entities.Portfolio>>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Governance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Governance;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetProgrammesEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/governance/programmes", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var programmes = await context.Programmes.AsNoTracking().ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(programmes);
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get all programmes")
|
|
||||||
.WithName(typeof(GetProgrammesEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<List<Domain.Entities.Programme>>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Governance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Grants;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class CreateAwardEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateAwardRequest(long OrganisationId, long ProgrammeId, decimal Amount);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/grants/awards", async (CreateAwardRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
if (request.Amount <= 0)
|
|
||||||
return Results.BadRequest("Approved amount must be greater than zero.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Organisations.AnyAsync(o => o.Id == request.OrganisationId, cancellationToken))
|
|
||||||
return Results.BadRequest($"Organisation with ID {request.OrganisationId} does not exist.");
|
|
||||||
|
|
||||||
if (!await context.Programmes.AnyAsync(p => p.Id == request.ProgrammeId, cancellationToken))
|
|
||||||
return Results.BadRequest($"Programme with ID {request.ProgrammeId} does not exist.");
|
|
||||||
|
|
||||||
var award = new Domain.Entities.Award
|
|
||||||
{
|
|
||||||
OrganisationId = request.OrganisationId,
|
|
||||||
ProgrammeId = request.ProgrammeId,
|
|
||||||
Amount = request.Amount,
|
|
||||||
Status = ApprovalStatus.Pending,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Awards.Add(award);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/grants/awards/{award.Id}", award)
|
|
||||||
: Results.BadRequest("Failred to create award");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Register an approved funding award (M01)")
|
|
||||||
.WithName(typeof(CreateAwardEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Award>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Grants);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Grants;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class CreateBudgetEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateBudgetRequest(long ProjectId, string Name, decimal Amount);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/grants/budgets", async (CreateBudgetRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
if (request.Amount <= 0)
|
|
||||||
return Results.BadRequest("Budget amount must be greater than zero.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken))
|
|
||||||
return Results.BadRequest($"Project with ID {request.ProjectId} does not exist.");
|
|
||||||
|
|
||||||
var budget = new Domain.Entities.Budget
|
|
||||||
{
|
|
||||||
ProjectId = request.ProjectId,
|
|
||||||
Name = request.Name,
|
|
||||||
Amount = request.Amount,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Budgets.Add(budget);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/grants/budgets/{budget.Id}", budget)
|
|
||||||
: Results.BadRequest("Failed to create budget");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Create a new budget line (M05)")
|
|
||||||
.WithName(typeof(CreateBudgetEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Budget>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Grants);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Grants;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class CreateContractEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateContractRequest(long AwardId, decimal TotalValue, DateTime EffectiveAt, DateTime ExpiresAt);
|
|
||||||
public record ExecuteContractRequest(string ExternalSignatureId, string SignedDocumentUrl);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/grants/contracts", async (CreateContractRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var award = await context.Awards.FirstOrDefaultAsync(a => a.Id == request.AwardId, cancellationToken);
|
|
||||||
|
|
||||||
if (award == null)
|
|
||||||
return Results.BadRequest($"Award with ID {request.AwardId} does not exist.");
|
|
||||||
|
|
||||||
var contract = new Domain.Entities.Contract
|
|
||||||
{
|
|
||||||
AwardId = request.AwardId,
|
|
||||||
TotalValue = request.TotalValue,
|
|
||||||
EffectiveAt = request.EffectiveAt,
|
|
||||||
ExpiresAt = request.ExpiresAt,
|
|
||||||
Status = ContractStatus.Draft,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Contracts.Add(contract);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/grants/contracts/{contract.Id}", contract)
|
|
||||||
: Results.BadRequest("Failed to create the contract");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Create a contract record from an approved award (M02)")
|
|
||||||
.WithName(typeof(CreateContractEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Contract>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Grants);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Grants;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class ExecuteContractEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record ExecuteContractRequest(string ExternalSignatureId, string SignedDocumentUrl);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/grants/contracts/{id:long}/execute", async (long id, ExecuteContractRequest request,
|
|
||||||
ClaimsPrincipal principal, IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var contract = await context.Contracts.FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
|
|
||||||
|
|
||||||
if (contract == null)
|
|
||||||
return Results.NotFound($"Contract with ID {id} does not exist.");
|
|
||||||
|
|
||||||
contract.ExternalSignatureId = request.ExternalSignatureId;
|
|
||||||
contract.SignedDocumentUrl = request.SignedDocumentUrl;
|
|
||||||
contract.Status = ContractStatus.Active;
|
|
||||||
|
|
||||||
context.Contracts.Update(contract);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Ok(contract)
|
|
||||||
: Results.BadRequest("Failed to execute contract");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Execute the contract and lock the contractual version")
|
|
||||||
.WithName(typeof(ExecuteContractEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Contract>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Grants);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Grants;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetAwardByIdEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/grants/awards/{id:long}", async (long id, IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var award = await context.Awards.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
|
|
||||||
|
|
||||||
return award != null ? Results.Ok(award) : Results.NotFound();
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get award by ID")
|
|
||||||
.WithName(typeof(GetAwardByIdEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Award>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Grants);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Grants;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetAwardsEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/grants/awards", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Awards.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of awards")
|
|
||||||
.WithName(typeof(GetAwardsEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Grants);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Grants;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetBudgetByIdEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateBudgetRequest(long ProjectId, string Name, decimal Amount);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/grants/budgets/{id:long}", async (long id, IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var budget = await context.Budgets.AsNoTracking().FirstOrDefaultAsync(b => b.Id == id, cancellationToken);
|
|
||||||
|
|
||||||
return budget != null ? Results.Ok(budget) : Results.NotFound();
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get budget by ID")
|
|
||||||
.WithName(typeof(GetBudgetByIdEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Budget>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Grants);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Grants;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetBudgetsEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/grants/budgets", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Budgets.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderByDescending(b => b.CreatedAt)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get all budgets")
|
|
||||||
.WithName(typeof(GetBudgetsEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Grants);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Grants;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetContractByIdEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateContractRequest(long AwardId, decimal TotalValue, DateTime EffectiveAt, DateTime ExpiresAt);
|
|
||||||
public record ExecuteContractRequest(string ExternalSignatureId, string SignedDocumentUrl);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/grants/contracts/{id:long}", async (long id, IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
var contract = await context.Contracts.AsNoTracking().FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
|
|
||||||
return contract != null ? Results.Ok(contract) : Results.NotFound();
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get contract by ID")
|
|
||||||
.WithName(typeof(GetContractByIdEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Contract>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Grants);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Grants;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetContractsEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/grants/contracts", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Contracts.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderByDescending(c => c.CreatedAt)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get all contracts")
|
|
||||||
.WithName(typeof(GetContractsEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Grants);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Projects;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class CreateActivityEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateActivityRequest(long? MilestoneId, string Category, decimal Amount, string Title, string Description, DateTime? PerformedAt);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/projects/{projectId:long}/activities", async (long projectId, CreateActivityRequest request,
|
|
||||||
ClaimsPrincipal principal, IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Projects.AnyAsync(p => p.Id == projectId, cancellationToken))
|
|
||||||
return Results.BadRequest($"Project with ID {projectId} does not exist.");
|
|
||||||
|
|
||||||
if (request.MilestoneId.HasValue)
|
|
||||||
if (!await context.Milestones.AnyAsync(m => m.Id == request.MilestoneId.Value, cancellationToken))
|
|
||||||
return Results.BadRequest($"Milestone with ID {request.MilestoneId.Value} does not exist.");
|
|
||||||
|
|
||||||
var activity = new Domain.Entities.Activity
|
|
||||||
{
|
|
||||||
ProjectId = projectId,
|
|
||||||
MilestoneId = request.MilestoneId,
|
|
||||||
Category = request.Category,
|
|
||||||
Amount = request.Amount,
|
|
||||||
Title = request.Title,
|
|
||||||
Description = request.Description,
|
|
||||||
PerformedAt = request.PerformedAt,
|
|
||||||
Status = ApprovalStatus.Pending,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Activities.Add(activity);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/projects/{projectId}/activities/{activity.Id}", activity)
|
|
||||||
: Results.BadRequest("Failed to create activity");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Add a new activity under project (M04)")
|
|
||||||
.WithName(typeof(CreateActivityEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Activity>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Projects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Projects;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class CreateProjectEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record CreateProjectRequest(long ProgrammeId, string Name, string Description, IndustrySector Sector, ProjectType ProjectType);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/projects", async (CreateProjectRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Programmes.AnyAsync(p => p.Id == request.ProgrammeId, cancellationToken))
|
|
||||||
return Results.BadRequest($"Programme with ID {request.ProgrammeId} does not exist.");
|
|
||||||
|
|
||||||
var project = new Domain.Entities.Project
|
|
||||||
{
|
|
||||||
ProgrammeId = request.ProgrammeId,
|
|
||||||
Name = request.Name,
|
|
||||||
Description = request.Description,
|
|
||||||
Sector = request.Sector,
|
|
||||||
ProjectType = request.ProjectType,
|
|
||||||
Status = ApprovalStatus.Pending,
|
|
||||||
CreatedBy = userId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Projects.Add(project);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Created($"api/projects/{project.Id}", project)
|
|
||||||
: Results.BadRequest("Failed to create prject");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Create a new project (M04)")
|
|
||||||
.WithName(typeof(CreateProjectEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Project>(StatusCodes.Status201Created)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Projects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Projects;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetActivitiesEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/projects/{projectId:long}/activities", async (long projectId,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var activities = await context.Activities.AsNoTracking()
|
|
||||||
.Where(a => a.ProjectId == projectId)
|
|
||||||
.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(activities);
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of activities for a project")
|
|
||||||
.WithName(typeof(GetActivitiesEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Projects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Projects;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetProjectByIdEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/projects/{id:long}", async (long id, IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var project = await context.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
|
|
||||||
|
|
||||||
return project != null
|
|
||||||
? Results.Ok(project)
|
|
||||||
: Results.NotFound();
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get project by ID")
|
|
||||||
.WithName(typeof(GetProjectByIdEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Project>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Projects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Projects;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetProjectsEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/projects", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Projects.AsNoTracking();
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.OrderByDescending(p => p.CreatedAt)
|
|
||||||
.Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of projects")
|
|
||||||
.WithName(typeof(GetProjectsEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Projects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Projects;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class UpdateProjectEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public record UpdateProjectStatusRequest(ApprovalStatus Status);
|
|
||||||
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/projects/{id:long}/status", async (long id, UpdateProjectStatusRequest request, ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
return Results.BadRequest("Missing or invalid 'sid' claim.");
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var project = await context.Projects.FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
|
|
||||||
|
|
||||||
if (project == null)
|
|
||||||
return Results.NotFound($"Project with ID {id} does not exist.");
|
|
||||||
|
|
||||||
project.Status = request.Status;
|
|
||||||
project.UpdatedBy = userId;
|
|
||||||
project.UpdatedAt = DateTime.UtcNow;
|
|
||||||
|
|
||||||
context.Projects.Update(project);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Ok(project)
|
|
||||||
: Results.BadRequest("Failed to update project");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Update project status (M04)")
|
|
||||||
.WithName(typeof(UpdateProjectEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.Project>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Projects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Users;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class GetUsersEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapGet("api/users", async (IDbContextFactory<ApplicationDbContext> contextFactory,
|
|
||||||
int page = 1, int pageSize = 10, ActivityStatus? status = null, string? email = null, string?
|
|
||||||
sortBy = null, bool sortDescending = false, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
if (page < 1) page = 1;
|
|
||||||
if (pageSize < 1) pageSize = 10;
|
|
||||||
if (pageSize > 100) pageSize = 100;
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var query = context.Users.AsNoTracking();
|
|
||||||
|
|
||||||
if (status.HasValue)
|
|
||||||
query = query.Where(u => u.Status == status.Value);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(email))
|
|
||||||
query = query.Where(u => u.Email != null && u.Email.Contains(email));
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(sortBy))
|
|
||||||
query = sortBy.ToLowerInvariant() switch
|
|
||||||
{
|
|
||||||
"email" => sortDescending ? query.OrderByDescending(u => u.Email) : query.OrderBy(u => u.Email),
|
|
||||||
"lastloginat" => sortDescending ? query.OrderByDescending(u => u.LastLoginAt) : query.OrderBy(u => u.LastLoginAt),
|
|
||||||
"status" => sortDescending ? query.OrderByDescending(u => u.Status) : query.OrderBy(u => u.Status),
|
|
||||||
_ => sortDescending ? query.OrderByDescending(u => u.Id) : query.OrderBy(u => u.Id)
|
|
||||||
};
|
|
||||||
else
|
|
||||||
query = query.OrderBy(u => u.Id);
|
|
||||||
|
|
||||||
var totalCount = await query.CountAsync(cancellationToken);
|
|
||||||
|
|
||||||
var items = await query.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Paginated, searchable directory of system users. Supports filtering by ActivityStatus, searching by Email, and sorting")
|
|
||||||
.WithName(typeof(GetUsersEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Users);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
using PostFundManagement.Domain;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Api;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Endpoints.Users;
|
|
||||||
|
|
||||||
[ApiVersionTarget(1)]
|
|
||||||
public class SyncUserEndpoint : IEndpoint
|
|
||||||
{
|
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
|
||||||
{
|
|
||||||
builder.MapPost("api/users/sync", async (ClaimsPrincipal principal,
|
|
||||||
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
|
|
||||||
{
|
|
||||||
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
|
|
||||||
var emailClaim = principal.FindFirst("email")?.Value ?? principal.FindFirst(ClaimTypes.Email)?.Value;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(sidClaim))
|
|
||||||
return Results.BadRequest("Missing 'sid' claim in user token.");
|
|
||||||
|
|
||||||
if (!Guid.TryParse(sidClaim, out var userId))
|
|
||||||
userId = new Guid(System.Security.Cryptography.MD5.HashData(System.Text.Encoding.UTF8.GetBytes(sidClaim)));
|
|
||||||
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var user = await context.Users.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
|
||||||
var isNew = user == null;
|
|
||||||
|
|
||||||
if (isNew)
|
|
||||||
{
|
|
||||||
user = new Domain.Entities.User
|
|
||||||
{
|
|
||||||
Id = userId,
|
|
||||||
Email = emailClaim,
|
|
||||||
Status = ActivityStatus.Active,
|
|
||||||
LastLoginAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Users.Add(user);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
user!.Email = emailClaim;
|
|
||||||
user.LastLoginAt = DateTime.UtcNow;
|
|
||||||
context.Users.Update(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Results.Ok(user)
|
|
||||||
: Results.BadRequest("Failed to sync user");
|
|
||||||
})
|
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Synchronise caller user profile using id_token's SID, inserting/updating user details in database")
|
|
||||||
.WithName(typeof(SyncUserEndpoint).ToEndpointName())
|
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
|
||||||
.Produces<Domain.Entities.User>(StatusCodes.Status200OK)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.WithTags(EndpointTags.Users);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Extensions;
|
|
||||||
|
|
||||||
public static class Postgres
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddDataProtectionDatabase(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var connectionString = configuration.GetConnectionString(DatabaseConfigName);
|
|
||||||
|
|
||||||
services.AddPooledDbContextFactory<DataProtectionDbContext>(options =>
|
|
||||||
options.UseNpgsql(connectionString));
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddApplicationDbContext(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var connectionString = configuration.GetConnectionString(DatabaseConfigName)
|
|
||||||
?? throw new InvalidOperationException($"Connection string '{DatabaseConfigName}' was not found in configuration.");
|
|
||||||
|
|
||||||
services.AddPooledDbContextFactory<ApplicationDbContext>(builder => builder.UseNpgsql(connectionString));
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Api.Configuration;
|
|
||||||
using PostFundManagement.Domain.Extensions;
|
|
||||||
using PostFundManagement.Domain.Sdk;
|
|
||||||
using PostFundManagement.Domain.Services;
|
|
||||||
using PostFundManagement.Infrastructure.Database;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Api.Extensions;
|
|
||||||
|
|
||||||
public static class Security
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddSecurityApiSdk(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var configSection = configuration.GetSection(nameof(SecurityClientSettings));
|
|
||||||
|
|
||||||
var authOptions = new SecurityClientSettings();
|
|
||||||
configSection.Bind(authOptions);
|
|
||||||
|
|
||||||
services.Configure<SecurityClientSettings>(configSection);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(authOptions.Authority))
|
|
||||||
return services;
|
|
||||||
|
|
||||||
if (!authOptions.Authority.EndsWith("/", StringComparison.Ordinal)) authOptions.Authority += "/";
|
|
||||||
|
|
||||||
services.AddRefitClient<ISecurityConnectApi>()
|
|
||||||
.ConfigureHttpClient(config =>
|
|
||||||
{
|
|
||||||
config.BaseAddress = new Uri(authOptions.Authority);
|
|
||||||
config.Timeout = TimeSpan.FromSeconds(15);
|
|
||||||
})
|
|
||||||
.AddStandardResilienceHandler(options =>
|
|
||||||
{
|
|
||||||
options.Retry.MaxRetryAttempts = 3;
|
|
||||||
options.Retry.Delay = TimeSpan.FromSeconds(1);
|
|
||||||
options.Retry.BackoffType = Polly.DelayBackoffType.Exponential;
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddScoped<TokenService>();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
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"];
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(certString))
|
|
||||||
throw new InvalidOperationException("Data Protection Certificate configuration is missing.");
|
|
||||||
|
|
||||||
var certificate = X509CertificateLoader.LoadPkcs12(Convert.FromBase64String(certString), certPassword);
|
|
||||||
|
|
||||||
services.AddDataProtection().PersistKeysToDbContext<DataProtectionDbContext>()
|
|
||||||
.ProtectKeysWithCertificate(certificate)
|
|
||||||
.SetApplicationName("PfmApp");
|
|
||||||
|
|
||||||
services.Configure<DataProtectionOptions>(options => options.ApplicationDiscriminator = "PfmApp");
|
|
||||||
|
|
||||||
services.ConfigureCookieOidcSameSiteSupport();
|
|
||||||
|
|
||||||
var configSection = configuration.GetSection(nameof(SecurityClientSettings));
|
|
||||||
|
|
||||||
var authOptions = new SecurityClientSettings();
|
|
||||||
configSection.Bind(authOptions);
|
|
||||||
|
|
||||||
services.Configure<SecurityClientSettings>(configSection);
|
|
||||||
|
|
||||||
services.AddAuthentication(options =>
|
|
||||||
{
|
|
||||||
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
|
||||||
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
|
|
||||||
})
|
|
||||||
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
|
|
||||||
{
|
|
||||||
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
|
||||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
|
||||||
options.Cookie.Name = "PfmApp.Session";
|
|
||||||
})
|
|
||||||
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
|
|
||||||
{
|
|
||||||
options.Authority = authOptions.Authority;
|
|
||||||
options.ClientId = authOptions.ClientId;
|
|
||||||
options.ClientSecret = authOptions.ClientSecret;
|
|
||||||
options.ResponseType = "code";
|
|
||||||
|
|
||||||
options.SaveTokens = true;
|
|
||||||
options.GetClaimsFromUserInfoEndpoint = true;
|
|
||||||
options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
|
|
||||||
options.CorrelationCookie.SameSite = SameSiteMode.None;
|
|
||||||
|
|
||||||
options.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
|
|
||||||
options.NonceCookie.SameSite = SameSiteMode.None;
|
|
||||||
|
|
||||||
options.ForwardSignOut = CookieAuthenticationDefaults.AuthenticationScheme;
|
|
||||||
|
|
||||||
options.Scope.Clear();
|
|
||||||
options.Scope.Add("openid");
|
|
||||||
options.Scope.Add("profile");
|
|
||||||
options.Scope.Add("email");
|
|
||||||
|
|
||||||
options.Events = new OpenIdConnectEvents
|
|
||||||
{
|
|
||||||
OnRedirectToIdentityProviderForSignOut = context =>
|
|
||||||
{
|
|
||||||
var idToken = context.ProtocolMessage.IdTokenHint;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(idToken))
|
|
||||||
{
|
|
||||||
var tokens = context.Properties.GetTokens();
|
|
||||||
var idTokenItem = tokens.FirstOrDefault(t => string.Equals(t.Name, "id_token", StringComparison.Ordinal));
|
|
||||||
|
|
||||||
if (idTokenItem != null) context.ProtocolMessage.IdTokenHint = idTokenItem.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddCascadingAuthenticationState();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddApiSecurity(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var configSection = configuration.GetSection(nameof(SecuritySettings));
|
|
||||||
|
|
||||||
var authOptions = new SecuritySettings();
|
|
||||||
configSection.Bind(authOptions);
|
|
||||||
|
|
||||||
services.Configure<SecuritySettings>(configSection);
|
|
||||||
|
|
||||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
||||||
.AddJwtBearer(options =>
|
|
||||||
{
|
|
||||||
options.Authority = authOptions.Authority;
|
|
||||||
options.Audience = authOptions.Audience;
|
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
|
||||||
{
|
|
||||||
ValidIssuer = authOptions.Authority,
|
|
||||||
ValidateAudience = true,
|
|
||||||
ValidateIssuer = true,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddAuthorization();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<UserSecretsId>59a05094-4ac2-472d-af04-3407e909432d</UserSecretsId>
|
|
||||||
<AssemblyOriginatorKeyFile>..\PostFundManagement.snk</AssemblyOriginatorKeyFile>
|
|
||||||
<SignAssembly>True</SignAssembly>
|
|
||||||
<PackageLicenseFile>..\LICENSE</PackageLicenseFile>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Security (IODC)-->
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
|
||||||
<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" />
|
|
||||||
|
|
||||||
<Using Include="Microsoft.AspNetCore.Authentication" />
|
|
||||||
<Using Include="Microsoft.AspNetCore.Authentication.Cookies" />
|
|
||||||
<Using Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
|
|
||||||
<Using Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
|
|
||||||
<Using Include="Microsoft.IdentityModel.Tokens" />
|
|
||||||
<Using Include="System.Security.Claims" />
|
|
||||||
</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>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\PostFundManagement.Domain\PostFundManagement.Domain.csproj" />
|
|
||||||
<ProjectReference Include="..\PostFundManagement.Infrastructure\PostFundManagement.Infrastructure.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@PostFundManagement.Api_HostAddress = http://localhost:5112
|
||||||
|
|
||||||
|
GET {{PostFundManagement.Api_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
@@ -1,99 +1,41 @@
|
|||||||
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);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddEndpointsApiExplorer()
|
// Add services to the container.
|
||||||
.AddApiVersioning(options => options.ApiVersionReader = new HeaderApiVersionReader());
|
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||||
|
builder.Services.AddOpenApi();
|
||||||
builder.Services.AddApiVersioning().AddApiExplorer();
|
|
||||||
|
|
||||||
builder.Services.AddEndpoints(Assembly.GetExecutingAssembly());
|
|
||||||
builder.Services.AddApiServices(builder.Configuration);
|
|
||||||
|
|
||||||
builder.Services.AddMediator();
|
|
||||||
builder.Services.AddSecurityApiSdk(builder.Configuration);
|
|
||||||
builder.Services.AddApiSecurity(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.AddHashServices(builder.Configuration);
|
|
||||||
|
|
||||||
builder.Services.AddHttpClient();
|
|
||||||
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();
|
var app = builder.Build();
|
||||||
|
|
||||||
var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>();
|
// Configure the HTTP request pipeline.
|
||||||
var scheduler = await schedulerFactory.GetScheduler(DefaultSchedulerName);
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.MapOpenApi();
|
||||||
|
}
|
||||||
|
|
||||||
if (!scheduler!.IsStarted)
|
|
||||||
await scheduler.Start();
|
|
||||||
|
|
||||||
app.UseHsts();
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
app.UseRouting();
|
var summaries = new[]
|
||||||
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) }
|
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
||||||
};
|
};
|
||||||
|
|
||||||
app.MapEndpoints(versionGroups);
|
app.MapGet("/weatherforecast", () =>
|
||||||
|
|
||||||
app.UseHealthChecks("/health", new HealthCheckOptions
|
|
||||||
{
|
{
|
||||||
Predicate = _ => true,
|
var forecast = Enumerable.Range(1, 5).Select(index =>
|
||||||
AllowCachingResponses = true,
|
new WeatherForecast
|
||||||
ResponseWriter = HealthChecks.UI.Client.UIResponseWriter.WriteHealthCheckUIResponse
|
(
|
||||||
});
|
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
||||||
|
Random.Shared.Next(-20, 55),
|
||||||
app.MapHealthChecksUI(options => { options.UIPath = "/healthui"; });
|
summaries[Random.Shared.Next(summaries.Length)]
|
||||||
app.UseHealthChecks("/ready");
|
))
|
||||||
|
.ToArray();
|
||||||
app.MapOpenApi();
|
return forecast;
|
||||||
|
})
|
||||||
var apiVersions = app.DescribeApiVersions()
|
.WithName("GetWeatherForecast");
|
||||||
.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();
|
app.Run();
|
||||||
|
|
||||||
|
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
|
||||||
|
{
|
||||||
|
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,4 @@
|
|||||||
{
|
{
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
@hostname = api.example.com
|
|
||||||
@port = 8080
|
|
||||||
@host = {{hostname}}:{{port}}
|
|
||||||
@contentType = application/json
|
|
||||||
|
|
||||||
###
|
|
||||||
# @prompt username
|
|
||||||
# @prompt refCode Your reference code display on webpage
|
|
||||||
# @prompt otp Your one-time password in your mailbox
|
|
||||||
POST https://{{host}}/verify-otp/{{refCode}} HTTP/1.1
|
|
||||||
Content-Type: {{contentType}}
|
|
||||||
|
|
||||||
{
|
|
||||||
"username": "{{username}}",
|
|
||||||
"otp": "{{otp}}"
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
### Token Request
|
|
||||||
POST https://{{authority}}/connect/token
|
|
||||||
Content-Type: application/x-www-form-urlencoded
|
|
||||||
Accept-Encoding: identity
|
|
||||||
|
|
||||||
grant_type={{grantType}}&client_id={{clientId}}&client_secret={{clientSecret}}&scope={{scope}}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Timezones;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Abstractions;
|
|
||||||
|
|
||||||
public abstract class EventBase
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; } = Guid.CreateVersion7();
|
|
||||||
|
|
||||||
public DateTimeOffset EnqueueAt { get; set; } = (DateTimeOffset)SouthAfricanTimeZone.UtcNow();
|
|
||||||
|
|
||||||
public string CorrelationId { get; set; } = Guid.CreateVersion7().ToString();
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Abstractions;
|
|
||||||
|
|
||||||
public interface IEndpoint
|
|
||||||
{
|
|
||||||
void Map(IEndpointRouteBuilder builder);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Abstractions;
|
|
||||||
|
|
||||||
public interface IEvent : INotification
|
|
||||||
{
|
|
||||||
Guid Id { get; set; }
|
|
||||||
|
|
||||||
string Name { get; set; }
|
|
||||||
|
|
||||||
DateTimeOffset EnqueueAt { get; set; }
|
|
||||||
|
|
||||||
string CorrelationId { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Abstractions;
|
|
||||||
|
|
||||||
public interface IJobOrchestrator
|
|
||||||
{
|
|
||||||
ValueTask SendAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
|
||||||
where TNotification : IEvent;
|
|
||||||
|
|
||||||
ValueTask ScheduleAsync<TNotification>(TNotification notification, string cronExpression, CancellationToken cancellationToken = default)
|
|
||||||
where TNotification : IEvent;
|
|
||||||
|
|
||||||
ValueTask<bool> InterruptAsync(string eventName, string? correlationId = null, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Abstractions;
|
|
||||||
|
|
||||||
public interface IS3Service
|
|
||||||
{
|
|
||||||
Task<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default);
|
|
||||||
Task<Result> DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Abstractions;
|
|
||||||
|
|
||||||
public interface IService;
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
using PostFundManagement.Domain.Services;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Abstractions;
|
|
||||||
|
|
||||||
public abstract class S3ServiceBase(IAmazonS3 amazonS3)
|
|
||||||
{
|
|
||||||
protected readonly IAmazonS3 Client = amazonS3;
|
|
||||||
|
|
||||||
protected abstract string BucketName { get; }
|
|
||||||
protected abstract string CdnBaseUrl { get; }
|
|
||||||
|
|
||||||
public virtual async Task<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(BucketName))
|
|
||||||
return Result.Fail<string>("Bucket name is not configured.");
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(CdnBaseUrl))
|
|
||||||
return Result.Fail<string>("CDN base URL is not configured.");
|
|
||||||
|
|
||||||
using var stream = new MemoryStream();
|
|
||||||
|
|
||||||
await fileStream.CopyToAsync(stream, cancellationToken);
|
|
||||||
await fileStream.DisposeAsync();
|
|
||||||
|
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
var fileHash = HashService.StreamToSha256Hash(stream);
|
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(fileHash))
|
|
||||||
return Result.Fail<string>("Failed to compute file hash.");
|
|
||||||
|
|
||||||
var fileKey = $"{fileHash.ToLower(CultureInfo.InvariantCulture)}{Path.GetExtension(fileName)}";
|
|
||||||
|
|
||||||
var putRequest = new PutObjectRequest
|
|
||||||
{
|
|
||||||
BucketName = BucketName,
|
|
||||||
Key = fileKey,
|
|
||||||
InputStream = stream,
|
|
||||||
ContentType = contentType,
|
|
||||||
UseChunkEncoding = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
var response = await Client.PutObjectAsync(putRequest, cancellationToken);
|
|
||||||
|
|
||||||
return response.HttpStatusCode != System.Net.HttpStatusCode.OK
|
|
||||||
? Result.Fail<string>($"Failed to upload {fileName} to S3.")
|
|
||||||
: Result.Ok($"{CdnBaseUrl}/{fileKey}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<string>(new Error($"Error uploading {fileName} to S3: {ex.Message}").CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<Result> DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(BucketName))
|
|
||||||
return Result.Fail("Bucket name is not configured.");
|
|
||||||
|
|
||||||
var deleteRequest = new DeleteObjectRequest
|
|
||||||
{
|
|
||||||
BucketName = BucketName,
|
|
||||||
Key = fileKey
|
|
||||||
};
|
|
||||||
|
|
||||||
var response = await Client.DeleteObjectAsync(deleteRequest, cancellationToken);
|
|
||||||
|
|
||||||
return response.HttpStatusCode != System.Net.HttpStatusCode.NoContent
|
|
||||||
? Result.Fail($"Failed to delete {fileKey} from S3.")
|
|
||||||
: Result.Ok();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error($"Error deleting {fileKey} from S3: {ex.Message}").CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Api;
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
|
||||||
public sealed class ApiVersionTargetAttribute(int majorVersion) : Attribute
|
|
||||||
{
|
|
||||||
public int MajorVersion { get; } = majorVersion;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Api.Configuration;
|
|
||||||
|
|
||||||
public sealed class SecurityClientSettings
|
|
||||||
{
|
|
||||||
public string? Authority { get; set; }
|
|
||||||
|
|
||||||
public string? GrantType { get; set; }
|
|
||||||
|
|
||||||
public string? ClientId { get; set; }
|
|
||||||
|
|
||||||
public string? ClientSecret { get; set; }
|
|
||||||
|
|
||||||
public string? Scope { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Api.Configuration;
|
|
||||||
|
|
||||||
public sealed class SecuritySettings
|
|
||||||
{
|
|
||||||
public string? Authority { get; set; }
|
|
||||||
|
|
||||||
public string? ClientId { get; set; }
|
|
||||||
|
|
||||||
public string? ClientSecret { get; set; }
|
|
||||||
|
|
||||||
public string? Audience { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Api.Models;
|
|
||||||
|
|
||||||
public sealed class TokenErrorResponse
|
|
||||||
{
|
|
||||||
[JsonPropertyName("error")]
|
|
||||||
public string? Error { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("error_description")]
|
|
||||||
public string? ErrorDescription { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("error_uri")]
|
|
||||||
public string? ErrorUri { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Api.Models;
|
|
||||||
|
|
||||||
public sealed class TokenRequest
|
|
||||||
{
|
|
||||||
[JsonPropertyName("grant_type")]
|
|
||||||
[AliasAs("grant_type")]
|
|
||||||
public string? GrantType { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("client_id")]
|
|
||||||
[AliasAs("client_id")]
|
|
||||||
public string? ClientId { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("client_secret")]
|
|
||||||
[AliasAs("client_secret")]
|
|
||||||
public string? ClientSecret { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("scope")]
|
|
||||||
[AliasAs("scope")]
|
|
||||||
public string? Scope { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Api.Models;
|
|
||||||
|
|
||||||
public sealed class TokenResponse
|
|
||||||
{
|
|
||||||
[JsonPropertyName("access_token")]
|
|
||||||
public string? AccessToken { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("expires_in")]
|
|
||||||
public int ExpiresIn { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("token_type")]
|
|
||||||
public string? TokenType { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("scope")]
|
|
||||||
public string? Scope { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Api;
|
|
||||||
|
|
||||||
public sealed class OpenApiBearerSecuritySchemeTransformer : IOpenApiDocumentTransformer
|
|
||||||
{
|
|
||||||
public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var bearerScheme = new OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
Type = SecuritySchemeType.Http,
|
|
||||||
Scheme = "bearer",
|
|
||||||
Description = "JWT Authorization header using the Bearer scheme",
|
|
||||||
};
|
|
||||||
|
|
||||||
document.AddComponent("Bearer", bearerScheme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace PostFundManagement.Domain;
|
||||||
|
|
||||||
|
public class Class1
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Configuration.Email;
|
|
||||||
|
|
||||||
public sealed class Account
|
|
||||||
{
|
|
||||||
public string? Username { get; set; }
|
|
||||||
|
|
||||||
public string? Password { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Configuration.Email;
|
|
||||||
|
|
||||||
public sealed class SmtpSettings
|
|
||||||
{
|
|
||||||
public Account? Credentials { get; set; }
|
|
||||||
|
|
||||||
public int Port { get; set; }
|
|
||||||
|
|
||||||
public string? Host { get; set; }
|
|
||||||
|
|
||||||
public bool UseSsl { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Activity : IEntityTypeConfiguration<Domain.Entities.Activity>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Activity> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Activity).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.MilestoneId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.UpdatedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.ApprovedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.PerformedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Category).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasDefaultValueSql("1");
|
|
||||||
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
|
|
||||||
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Milestone)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.MilestoneId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Updator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.UpdatedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Approver)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ApprovedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class AuditLog : IEntityTypeConfiguration<Domain.Entities.AuditLog>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.AuditLog> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.AuditLog).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.EntityName).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.EntityId).IsRequired();
|
|
||||||
builder.Property(f => f.Action).IsRequired().HasMaxLength(ShortLabelLength);
|
|
||||||
builder.Property(f => f.Changes).IsRequired(false).HasColumnType("jsonb");
|
|
||||||
builder.Property(f => f.PerformedBy).IsRequired();
|
|
||||||
builder.Property(f => f.Timestamp).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Performer)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.PerformedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Award : IEntityTypeConfiguration<Domain.Entities.Award>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Award> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Award).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.OrganisationId).IsRequired();
|
|
||||||
builder.Property(f => f.ProgrammeId).IsRequired();
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.UpdatedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.ApprovedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Organisation)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.OrganisationId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Programme)
|
|
||||||
.WithMany(f => f.Awards)
|
|
||||||
.HasForeignKey(fk => fk.ProgrammeId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany(f => f.Awards)
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Updator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.UpdatedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Approver)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ApprovedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Beneficiary : IEntityTypeConfiguration<Domain.Entities.Beneficiary>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Beneficiary> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Beneficiary).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.UserId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Category).IsRequired().HasMaxLength(ShortLabelLength);
|
|
||||||
builder.Property(f => f.TargetCount).IsRequired();
|
|
||||||
builder.Property(f => f.ReachedCount).IsRequired();
|
|
||||||
builder.Property(f => f.Location).IsRequired();
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.User)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.UserId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Budget : IEntityTypeConfiguration<Domain.Entities.Budget>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Budget> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Budget).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.ApprovedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Approver)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ApprovedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class ChangeRequest : IEntityTypeConfiguration<Domain.Entities.ChangeRequest>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.ChangeRequest> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.ChangeRequest).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.ApprovedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Justification).IsRequired().HasMaxLength(TextLength);
|
|
||||||
builder.Property(f => f.RevisedBudget).IsRequired(false).HasPrecision(18, 2);
|
|
||||||
builder.Property(f => f.RevisedEndedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Approver)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ApprovedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class ComplianceItem : IEntityTypeConfiguration<Domain.Entities.ComplianceItem>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.ComplianceItem> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.ComplianceItem).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.VerifiedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Requirements).IsRequired().HasMaxLength(MediumTextLength);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Verifier)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.VerifiedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Contract : IEntityTypeConfiguration<Domain.Entities.Contract>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Contract> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Contract).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.HasIndex(f => f.AwardId).IsUnique();
|
|
||||||
|
|
||||||
builder.Property(f => f.AwardId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.ExternalSignatureId).IsRequired(false);
|
|
||||||
builder.Property(f => f.SignedDocumentUrl).IsRequired(false);
|
|
||||||
builder.Property(f => f.EffectiveAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.ExpiresAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.TotalValue).IsRequired().HasPrecision(18, 2);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Award)
|
|
||||||
.WithOne(f => f.Contract)
|
|
||||||
.HasForeignKey<Domain.Entities.Contract>(fk => fk.AwardId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Disbursement : IEntityTypeConfiguration<Domain.Entities.Disbursement>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Disbursement> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Disbursement).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.MilestoneId).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.UpdatedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Milestone)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.MilestoneId)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Updator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.UpdatedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Evidence : IEntityTypeConfiguration<Domain.Entities.Evidence>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Evidence> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Evidence).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(f => f.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.MilestoneId).IsRequired();
|
|
||||||
builder.Property(f => f.IndicatorId).IsRequired();
|
|
||||||
builder.Property(f => f.ActivityId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.UpdatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Version).IsRequired().HasDefaultValue(1);
|
|
||||||
builder.Property(f => f.DocumentUrl).IsRequired();
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
builder.Property(f => f.Type).IsRequired().HasConversion<int>().HasDefaultValueSql("9");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Milestone)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.MilestoneId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Indicator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.IndicatorId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Activity)
|
|
||||||
.WithMany(f => f.Evidences)
|
|
||||||
.HasForeignKey(fk => fk.ActivityId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Updator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.UpdatedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Indicator : IEntityTypeConfiguration<Domain.Entities.Indicator>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Indicator> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Indicator).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.UnitOfMeasure).IsRequired().HasConversion<int>().HasDefaultValueSql("2");
|
|
||||||
builder.Property(f => f.BaselineAmount).IsRequired().HasPrecision(18, 2);
|
|
||||||
builder.Property(f => f.TargetAmount).IsRequired().HasPrecision(18, 2);
|
|
||||||
builder.Property(f => f.ActualAmount).IsRequired().HasPrecision(18, 2);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Instrument : IEntityTypeConfiguration<Domain.Entities.Instrument>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Instrument> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Instrument).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProgrammeId).IsRequired();
|
|
||||||
builder.Property(f => f.Code).IsRequired().HasMaxLength(ShortLabelLength);
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Description).IsRequired(false).HasMaxLength(TextLength);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("2");
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Programme)
|
|
||||||
.WithMany(f => f.Instruments)
|
|
||||||
.HasForeignKey(fk => fk.ProgrammeId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Invite : IEntityTypeConfiguration<Domain.Entities.Invite>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Invite> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Invite).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.OrganisationId).IsRequired();
|
|
||||||
builder.Property(f => f.InvitedOrganisationId).IsRequired();
|
|
||||||
builder.Property(f => f.AwardId).IsRequired();
|
|
||||||
builder.Property(f => f.ContractId).IsRequired();
|
|
||||||
builder.Property(f => f.Recipient).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.UpdatedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Organisation)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.OrganisationId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.InvitedOrganisation)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.InvitedOrganisationId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Award)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.AwardId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Contract)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ContractId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.RecipientUser)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.Recipient)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Issue : IEntityTypeConfiguration<Domain.Entities.Issue>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Issue> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Issue).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.ResolvedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.AssignedTo).IsRequired(false);
|
|
||||||
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
|
|
||||||
builder.Property(f => f.Priority).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
builder.Property(f => f.Resolution).IsRequired();
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Assignee)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.AssignedTo)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Milestone : IEntityTypeConfiguration<Domain.Entities.Milestone>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Milestone> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Milestone).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.UpdatedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.DueAt).IsRequired();
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
builder.Property(f => f.Priority).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Updator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.UpdatedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Notification : IEntityTypeConfiguration<Domain.Entities.Notification>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Notification> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Notification).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.SentAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Recipient).IsRequired();
|
|
||||||
builder.Property(f => f.OrganisationId).IsRequired();
|
|
||||||
builder.Property(f => f.Platform).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
builder.Property(f => f.Subject).IsRequired(false).HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Message).IsRequired().HasMaxLength(LargeTextLength);
|
|
||||||
builder.Property(f => f.Destination).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Error).IsRequired(false);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Organisation)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.OrganisationId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.RecipientUser)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.Recipient)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Organisation : IEntityTypeConfiguration<Domain.Entities.Organisation>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Organisation> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Organisation).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.RegistrationNo).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Email).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Type).IsRequired().HasConversion<int>().HasDefaultValueSql("5");
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Outcome : IEntityTypeConfiguration<Domain.Entities.Outcome>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Outcome> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Outcome).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.BeneficiaryId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Beneficiary)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.BeneficiaryId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Portfolio : IEntityTypeConfiguration<Domain.Entities.Portfolio>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Portfolio> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Portfolio).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.OwnedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Description).IsRequired(false).HasMaxLength(TextLength);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Owner)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.OwnedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Programme : IEntityTypeConfiguration<Domain.Entities.Programme>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Programme> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Programme).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.PortfolioId).IsRequired();
|
|
||||||
builder.Property(f => f.OwnedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.UpdatedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Portfolio)
|
|
||||||
.WithMany(f => f.Programmes)
|
|
||||||
.HasForeignKey(fk => fk.PortfolioId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Owner)
|
|
||||||
.WithMany()
|
|
||||||
.IsRequired()
|
|
||||||
.HasForeignKey(fk => fk.OwnedBy)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Project : IEntityTypeConfiguration<Domain.Entities.Project>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Project> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Project).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProgrammeId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.UpdatedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.StartedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.EndedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
|
|
||||||
builder.Property(f => f.Sector).IsRequired().HasConversion<int>().HasDefaultValueSql("11");
|
|
||||||
builder.Property(f => f.ProjectType).IsRequired().HasConversion<int>().HasDefaultValueSql("10");
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Programme)
|
|
||||||
.WithMany(f => f.Projects)
|
|
||||||
.HasForeignKey(fk => fk.ProgrammeId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Updator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.UpdatedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Risk : IEntityTypeConfiguration<Domain.Entities.Risk>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Risk> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Risk).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.Likelihood).IsRequired().HasConversion<int>().HasDefaultValueSql("3");
|
|
||||||
builder.Property(f => f.Impact).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class Rule : IEntityTypeConfiguration<Domain.Entities.Rule>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.Rule> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.Rule).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(fk => fk.Id);
|
|
||||||
builder.Property(f => f.ProgrammeId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.UpdatedBy).IsRequired(false);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Version).IsRequired().HasDefaultValue(1);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("2");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Programme)
|
|
||||||
.WithMany(f => f.Rules)
|
|
||||||
.HasForeignKey(fk => fk.ProgrammeId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Updator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.UpdatedBy)
|
|
||||||
.IsRequired(false)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class SiteVisit : IEntityTypeConfiguration<Domain.Entities.SiteVisit>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.SiteVisit> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.SiteVisit).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.ProjectId).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedBy).IsRequired();
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.VisitedAt).IsRequired();
|
|
||||||
builder.Property(f => f.InspectorName).IsRequired();
|
|
||||||
builder.Property(f => f.Findings).IsRequired(false).HasMaxLength(LargeTextLength);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1");
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Project)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.ProjectId)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Creator)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(fk => fk.CreatedBy)
|
|
||||||
.IsRequired()
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Domain.Configuration.Entities;
|
|
||||||
|
|
||||||
public sealed class User : IEntityTypeConfiguration<Domain.Entities.User>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Domain.Entities.User> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Domain.Entities.User).Pluralize());
|
|
||||||
|
|
||||||
builder.HasKey(pk => pk.Id);
|
|
||||||
builder.Property(f => f.Email).IsRequired().HasMaxLength(LabelLength);
|
|
||||||
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("2");
|
|
||||||
builder.Property(f => f.LastLoginAt).IsRequired(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Configuration.Hash;
|
|
||||||
|
|
||||||
public sealed class HasherSettings
|
|
||||||
{
|
|
||||||
public string? Salt { get; set; }
|
|
||||||
|
|
||||||
public int MinHashLength { get; set; }
|
|
||||||
|
|
||||||
public string? PayfastPassphrase { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Configuration.S3;
|
|
||||||
|
|
||||||
public sealed class S3Settings
|
|
||||||
{
|
|
||||||
public string? ServiceUrl { get; set; }
|
|
||||||
|
|
||||||
public string? AccessKey { get; set; }
|
|
||||||
|
|
||||||
public string? SecretKey { get; set; }
|
|
||||||
|
|
||||||
public string? BucketName { get; set; }
|
|
||||||
|
|
||||||
public string? Region { get; set; }
|
|
||||||
|
|
||||||
public string? CdnBaseUrl { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<Configuration.Entities.Activity, Activity>]
|
|
||||||
public class Activity : Models.Activity
|
|
||||||
{
|
|
||||||
public virtual User? Creator { get; set; }
|
|
||||||
|
|
||||||
public virtual User? Updator { get; set; }
|
|
||||||
|
|
||||||
public virtual User? Approver { get; set; }
|
|
||||||
|
|
||||||
public virtual Project? Project { get; set; }
|
|
||||||
|
|
||||||
public virtual Milestone? Milestone { get; set; }
|
|
||||||
|
|
||||||
public virtual ICollection<Evidence> Evidences { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace PostFundManagement.Domain.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<Configuration.Entities.AuditLog, AuditLog>]
|
|
||||||
public class AuditLog : Models.AuditLog
|
|
||||||
{
|
|
||||||
public virtual User? Performer { get; set; }
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user