diff --git a/src/.dockerignore b/src/.dockerignore
deleted file mode 100644
index 3aae53927b..0000000000
--- a/src/.dockerignore
+++ /dev/null
@@ -1,32 +0,0 @@
-# Include any files or directories that you don't want to be copied to your
-# container here (e.g., local build artifacts, temporary files, etc.).
-#
-# For more help, visit the .dockerignore file reference guide at
-# https://docs.docker.com/engine/reference/builder/#dockerignore-file
-
-**/.DS_Store
-**/.classpath
-**/.dockerignore
-**/.env
-**/.git
-**/.gitignore
-**/.project
-**/.settings
-**/.toolstarget
-**/.vs
-**/.vscode
-**/*.*proj.user
-**/*.dbmdl
-**/*.jfm
-**/bin
-**/charts
-**/docker-compose*
-**/compose*
-**/Dockerfile*
-**/node_modules
-**/npm-debug.log
-**/obj
-**/secrets.dev.yaml
-**/values.dev.yaml
-LICENSE
-README.md
diff --git a/src/.editorconfig b/src/.editorconfig
deleted file mode 100644
index b3fa9a701e..0000000000
--- a/src/.editorconfig
+++ /dev/null
@@ -1,397 +0,0 @@
-root = true
-
-# All files
-[*]
-indent_style = space
-
-# Xml files
-[*.{xml,csproj,props,targets,ruleset,nuspec,resx}]
-indent_size = 2
-
-# Json files
-[*.{json,config,nswag}]
-indent_size = 2
-
-# C# files
-[*.cs]
-
-#### Core EditorConfig Options ####
-
-# Indentation and spacing
-indent_size = 4
-tab_width = 4
-
-# New line preferences
-end_of_line = lf
-insert_final_newline = true
-
-#### .NET Coding Conventions ####
-[*.{cs,vb}]
-
-# Organize usings
-dotnet_separate_import_directive_groups = false
-dotnet_sort_system_directives_first = true
-file_header_template = unset
-
-# this. and Me. preferences
-dotnet_style_qualification_for_event = false:silent
-dotnet_style_qualification_for_field = false:silent
-dotnet_style_qualification_for_method = false:silent
-dotnet_style_qualification_for_property = false:silent
-
-# Language keywords vs BCL types preferences
-dotnet_style_predefined_type_for_locals_parameters_members = true:silent
-dotnet_style_predefined_type_for_member_access = true:silent
-
-# Parentheses preferences
-dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
-dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
-
-# Modifier preferences
-dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
-
-# Expression-level preferences
-dotnet_style_coalesce_expression = true:suggestion
-dotnet_style_collection_initializer = true:suggestion
-dotnet_style_explicit_tuple_names = true:suggestion
-dotnet_style_null_propagation = true:suggestion
-dotnet_style_object_initializer = true:suggestion
-dotnet_style_operator_placement_when_wrapping = beginning_of_line
-dotnet_style_prefer_auto_properties = true:suggestion
-dotnet_style_prefer_compound_assignment = true:suggestion
-dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
-dotnet_style_prefer_conditional_expression_over_return = true:suggestion
-dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
-dotnet_style_prefer_inferred_tuple_names = true:suggestion
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
-dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
-dotnet_style_prefer_simplified_interpolation = true:suggestion
-
-# Field preferences
-dotnet_style_readonly_field = true:warning
-
-# Parameter preferences
-dotnet_code_quality_unused_parameters = all:suggestion
-
-# Suppression preferences
-dotnet_remove_unnecessary_suppression_exclusions = none
-
-#### C# Coding Conventions ####
-[*.cs]
-
-# var preferences
-csharp_style_var_elsewhere = false:silent
-csharp_style_var_for_built_in_types = false:silent
-csharp_style_var_when_type_is_apparent = false:silent
-
-# Expression-bodied members
-csharp_style_expression_bodied_accessors = true:silent
-csharp_style_expression_bodied_constructors = false:silent
-csharp_style_expression_bodied_indexers = true:silent
-csharp_style_expression_bodied_lambdas = true:suggestion
-csharp_style_expression_bodied_local_functions = false:silent
-csharp_style_expression_bodied_methods = false:silent
-csharp_style_expression_bodied_operators = false:silent
-csharp_style_expression_bodied_properties = true:silent
-
-# Pattern matching preferences
-csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
-csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
-csharp_style_prefer_not_pattern = true:suggestion
-csharp_style_prefer_pattern_matching = true:silent
-csharp_style_prefer_switch_expression = true:suggestion
-
-# Null-checking preferences
-csharp_style_conditional_delegate_call = true:suggestion
-
-# Modifier preferences
-csharp_prefer_static_local_function = true:warning
-csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
-
-# Code-block preferences
-csharp_prefer_braces = true:silent
-csharp_prefer_simple_using_statement = true:suggestion
-
-# Expression-level preferences
-csharp_prefer_simple_default_expression = true:suggestion
-csharp_style_deconstructed_variable_declaration = true:suggestion
-csharp_style_inlined_variable_declaration = true:suggestion
-csharp_style_pattern_local_over_anonymous_function = true:suggestion
-csharp_style_prefer_index_operator = true:suggestion
-csharp_style_prefer_range_operator = true:suggestion
-csharp_style_throw_expression = true:suggestion
-csharp_style_unused_value_assignment_preference = discard_variable:suggestion
-csharp_style_unused_value_expression_statement_preference = discard_variable:silent
-
-# 'using' directive preferences
-csharp_using_directive_placement = outside_namespace:silent
-
-#### 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
-csharp_style_namespace_declarations = file_scoped:silent
-csharp_style_prefer_method_group_conversion = true:silent
-csharp_style_prefer_top_level_statements = true:silent
-csharp_style_prefer_primary_constructors = true:suggestion
-csharp_style_prefer_null_check_over_type_check = true:suggestion
-csharp_style_prefer_local_over_anonymous_function = true:suggestion
-csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
-csharp_style_prefer_tuple_swap = true:suggestion
-csharp_style_prefer_utf8_string_literals = true:suggestion
-dotnet_diagnostic.CA1032.severity = none
-dotnet_diagnostic.CA1812.severity = none
-dotnet_diagnostic.S6667.severity = none
-
-#### Naming styles ####
-[*.{cs,vb}]
-
-# Naming rules
-
-dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
-dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
-dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
-dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
-
-dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
-dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
-dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
-
-dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
-dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
-dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.events_should_be_pascalcase.symbols = events
-dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
-dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
-dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
-
-dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
-dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
-dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
-
-dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
-dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
-dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
-
-dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
-dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
-dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
-dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
-
-dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
-dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
-dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
-
-dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
-dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
-dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
-dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
-dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
-dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
-dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
-dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
-
-# Symbol specifications
-
-dotnet_naming_symbols.interfaces.applicable_kinds = interface
-dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.interfaces.required_modifiers =
-
-dotnet_naming_symbols.enums.applicable_kinds = enum
-dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.enums.required_modifiers =
-
-dotnet_naming_symbols.events.applicable_kinds = event
-dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.events.required_modifiers =
-
-dotnet_naming_symbols.methods.applicable_kinds = method
-dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.methods.required_modifiers =
-
-dotnet_naming_symbols.properties.applicable_kinds = property
-dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.properties.required_modifiers =
-
-dotnet_naming_symbols.public_fields.applicable_kinds = field
-dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
-dotnet_naming_symbols.public_fields.required_modifiers =
-
-dotnet_naming_symbols.private_fields.applicable_kinds = field
-dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
-dotnet_naming_symbols.private_fields.required_modifiers =
-
-dotnet_naming_symbols.private_static_fields.applicable_kinds = field
-dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
-dotnet_naming_symbols.private_static_fields.required_modifiers = static
-
-dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
-dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.types_and_namespaces.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 =
-
-dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
-dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
-dotnet_naming_symbols.type_parameters.required_modifiers =
-
-dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
-dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
-dotnet_naming_symbols.private_constant_fields.required_modifiers = const
-
-dotnet_naming_symbols.local_variables.applicable_kinds = local
-dotnet_naming_symbols.local_variables.applicable_accessibilities = local
-dotnet_naming_symbols.local_variables.required_modifiers =
-
-dotnet_naming_symbols.local_constants.applicable_kinds = local
-dotnet_naming_symbols.local_constants.applicable_accessibilities = local
-dotnet_naming_symbols.local_constants.required_modifiers = const
-
-dotnet_naming_symbols.parameters.applicable_kinds = parameter
-dotnet_naming_symbols.parameters.applicable_accessibilities = *
-dotnet_naming_symbols.parameters.required_modifiers =
-
-dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
-dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
-dotnet_naming_symbols.public_constant_fields.required_modifiers = const
-
-dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
-dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
-dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
-
-dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
-dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
-dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
-
-dotnet_naming_symbols.local_functions.applicable_kinds = local_function
-dotnet_naming_symbols.local_functions.applicable_accessibilities = *
-dotnet_naming_symbols.local_functions.required_modifiers =
-
-# Naming styles
-
-dotnet_naming_style.pascalcase.required_prefix =
-dotnet_naming_style.pascalcase.required_suffix =
-dotnet_naming_style.pascalcase.word_separator =
-dotnet_naming_style.pascalcase.capitalization = pascal_case
-
-dotnet_naming_style.ipascalcase.required_prefix = I
-dotnet_naming_style.ipascalcase.required_suffix =
-dotnet_naming_style.ipascalcase.word_separator =
-dotnet_naming_style.ipascalcase.capitalization = pascal_case
-
-dotnet_naming_style.tpascalcase.required_prefix = T
-dotnet_naming_style.tpascalcase.required_suffix =
-dotnet_naming_style.tpascalcase.word_separator =
-dotnet_naming_style.tpascalcase.capitalization = pascal_case
-
-dotnet_naming_style._camelcase.required_prefix = _
-dotnet_naming_style._camelcase.required_suffix =
-dotnet_naming_style._camelcase.word_separator =
-dotnet_naming_style._camelcase.capitalization = camel_case
-
-dotnet_naming_style.camelcase.required_prefix =
-dotnet_naming_style.camelcase.required_suffix =
-dotnet_naming_style.camelcase.word_separator =
-dotnet_naming_style.camelcase.capitalization = camel_case
-
-dotnet_naming_style.s_camelcase.required_prefix = s_
-dotnet_naming_style.s_camelcase.required_suffix =
-dotnet_naming_style.s_camelcase.word_separator =
-dotnet_naming_style.s_camelcase.capitalization = camel_case
-
-dotnet_style_namespace_match_folder = true:suggestion
-
-dotnet_diagnostic.CS1591.severity = none
-dotnet_diagnostic.CA1724.severity = none
-dotnet_diagnostic.CA1305.severity = none
-dotnet_diagnostic.CA1040.severity = none
-dotnet_diagnostic.CA1848.severity = none
-dotnet_diagnostic.CA1034.severity = none
-tab_width = 4
-indent_size = 4
-end_of_line = lf
-dotnet_diagnostic.CA1711.severity = none
-dotnet_diagnostic.CA1716.severity = none
-dotnet_diagnostic.CA1062.severity = none
-dotnet_diagnostic.CA1031.severity = none
-dotnet_diagnostic.CA1861.severity = none
-dotnet_diagnostic.CA2007.severity = none
\ No newline at end of file
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
deleted file mode 100644
index cf4b4bb3de..0000000000
--- a/src/Directory.Build.props
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
- net9.0
- false
- false
- true
- true
- enable
- enable
- true
- latest
- All
- 2.0.4-rc;latest
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Dockerfile.Blazor b/src/Dockerfile.Blazor
deleted file mode 100644
index 2438ffea64..0000000000
--- a/src/Dockerfile.Blazor
+++ /dev/null
@@ -1,13 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
-WORKDIR /app
-
-COPY . ./
-RUN dotnet publish ./apps/blazor/client/Client.csproj -c Release -o output
-
-FROM nginx:alpine
-WORKDIR /usr/share/nginx/html
-COPY --from=build-env /app/output/wwwroot .
-
-COPY ./apps/blazor/nginx.conf /etc/nginx/nginx.conf
-
-EXPOSE 80
\ No newline at end of file
diff --git a/src/FSH.Starter.sln b/src/FSH.Starter.sln
deleted file mode 100644
index 904c59f770..0000000000
--- a/src/FSH.Starter.sln
+++ /dev/null
@@ -1,287 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.31903.59
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{F3DF5AC5-8CDC-46D4-969D-1245A6880215}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A32CEFB3-4E50-401E-8835-787534414F41}"
- ProjectSection(SolutionItems) = preProject
- .editorconfig = .editorconfig
- Directory.Build.props = Directory.Build.props
- Directory.Packages.props = Directory.Packages.props
- README.md = README.md
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Catalog", "Catalog", "{93324D12-DE1B-4C1B-934A-92AA140FF6F6}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Todo", "Todo", "{79981A5A-207A-4A16-A21B-5E80394082F6}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Framework", "_Framework", "{05248A38-0F34-4E59-A3D1-B07097987AFB}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Migrations", "Migrations", "{12F8343D-20A6-4E24-B0F5-3A66F2228CF6}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebApi", "WebApi", "{CE64E92B-E088-46FB-9028-7FB6B67DEC55}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor", "Blazor", "{2B1F75CE-07A6-4C19-A2E3-F9E062CFDDFB}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "api\framework\Infrastructure\Infrastructure.csproj", "{294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "api\framework\Core\Core.csproj", "{A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "api\server\Server.csproj", "{86BD3DF6-A3E9-4839-8036-813A20DC8AD6}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSSQL", "api\migrations\MSSQL\MSSQL.csproj", "{ECCEA352-8953-49D6-8F87-8AB361499420}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PostgreSQL", "api\migrations\PostgreSQL\PostgreSQL.csproj", "{D64AD07C-A711-42D8-8653-EDCD7A825A44}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Todo", "api\modules\Todo\Todo.csproj", "{B3866EEF-8F46-4302-ABAC-A95EE2F27331}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Catalog.Application", "api\modules\Catalog\Catalog.Application\Catalog.Application.csproj", "{8C7DAF8E-F792-4092-8BBF-31A6B898B39A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Catalog.Domain", "api\modules\Catalog\Catalog.Domain\Catalog.Domain.csproj", "{B15705B5-041C-4F1E-8342-AD03182EDD42}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Catalog.Infrastructure", "api\modules\Catalog\Catalog.Infrastructure\Catalog.Infrastructure.csproj", "{89FE1C3B-29D3-48A8-8E7D-90C261D266C5}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "apps\blazor\client\Client.csproj", "{BCE4A428-8B97-4B56-AE45-496EE3906667}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "apps\blazor\infrastructure\Infrastructure.csproj", "{27BEF279-AE73-43DC-92A9-FD7021A999D0}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "apps\blazor\shared\Shared.csproj", "{34359707-CE66-4DF0-9EF4-D7544B615564}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{D36E77BC-4568-4BC8-9506-1EFB7B1CD335}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceDefaults", "aspire\service-defaults\ServiceDefaults.csproj", "{990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Host", "aspire\host\Host.csproj", "{2119CE89-308D-4932-BFCE-8CDC0A05EB9E}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Shared.csproj", "{49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FE1B1E84-F993-4840-9CAB-9082EB523FDD}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Auth", "Auth", "{F17769D7-0E41-4E80-BDD4-282EBE7B5199}"
- ProjectSection(SolutionItems) = preProject
- GetToken.http = GetToken.http
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Debug|x64 = Debug|x64
- Debug|x86 = Debug|x86
- Release|Any CPU = Release|Any CPU
- Release|x64 = Release|x64
- Release|x86 = Release|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Debug|x64.ActiveCfg = Debug|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Debug|x64.Build.0 = Debug|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Debug|x86.ActiveCfg = Debug|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Debug|x86.Build.0 = Debug|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Release|Any CPU.Build.0 = Release|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Release|x64.ActiveCfg = Release|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Release|x64.Build.0 = Release|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Release|x86.ActiveCfg = Release|Any CPU
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB}.Release|x86.Build.0 = Release|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Debug|x64.ActiveCfg = Debug|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Debug|x64.Build.0 = Debug|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Debug|x86.ActiveCfg = Debug|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Debug|x86.Build.0 = Debug|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Release|Any CPU.Build.0 = Release|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Release|x64.ActiveCfg = Release|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Release|x64.Build.0 = Release|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Release|x86.ActiveCfg = Release|Any CPU
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31}.Release|x86.Build.0 = Release|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Debug|x64.ActiveCfg = Debug|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Debug|x64.Build.0 = Debug|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Debug|x86.ActiveCfg = Debug|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Debug|x86.Build.0 = Debug|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Release|Any CPU.Build.0 = Release|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Release|x64.ActiveCfg = Release|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Release|x64.Build.0 = Release|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Release|x86.ActiveCfg = Release|Any CPU
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6}.Release|x86.Build.0 = Release|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Debug|x64.ActiveCfg = Debug|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Debug|x64.Build.0 = Debug|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Debug|x86.ActiveCfg = Debug|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Debug|x86.Build.0 = Debug|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Release|Any CPU.Build.0 = Release|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Release|x64.ActiveCfg = Release|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Release|x64.Build.0 = Release|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Release|x86.ActiveCfg = Release|Any CPU
- {ECCEA352-8953-49D6-8F87-8AB361499420}.Release|x86.Build.0 = Release|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Debug|x64.ActiveCfg = Debug|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Debug|x64.Build.0 = Debug|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Debug|x86.ActiveCfg = Debug|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Debug|x86.Build.0 = Debug|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Release|Any CPU.Build.0 = Release|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Release|x64.ActiveCfg = Release|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Release|x64.Build.0 = Release|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Release|x86.ActiveCfg = Release|Any CPU
- {D64AD07C-A711-42D8-8653-EDCD7A825A44}.Release|x86.Build.0 = Release|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Debug|x64.ActiveCfg = Debug|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Debug|x64.Build.0 = Debug|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Debug|x86.ActiveCfg = Debug|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Debug|x86.Build.0 = Debug|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Release|Any CPU.Build.0 = Release|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Release|x64.ActiveCfg = Release|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Release|x64.Build.0 = Release|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Release|x86.ActiveCfg = Release|Any CPU
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331}.Release|x86.Build.0 = Release|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Debug|x64.ActiveCfg = Debug|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Debug|x64.Build.0 = Debug|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Debug|x86.ActiveCfg = Debug|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Debug|x86.Build.0 = Debug|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Release|Any CPU.Build.0 = Release|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Release|x64.ActiveCfg = Release|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Release|x64.Build.0 = Release|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Release|x86.ActiveCfg = Release|Any CPU
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A}.Release|x86.Build.0 = Release|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Debug|x64.ActiveCfg = Debug|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Debug|x64.Build.0 = Debug|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Debug|x86.ActiveCfg = Debug|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Debug|x86.Build.0 = Debug|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Release|Any CPU.Build.0 = Release|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Release|x64.ActiveCfg = Release|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Release|x64.Build.0 = Release|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Release|x86.ActiveCfg = Release|Any CPU
- {B15705B5-041C-4F1E-8342-AD03182EDD42}.Release|x86.Build.0 = Release|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Debug|x64.ActiveCfg = Debug|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Debug|x64.Build.0 = Debug|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Debug|x86.ActiveCfg = Debug|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Debug|x86.Build.0 = Debug|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Release|Any CPU.Build.0 = Release|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Release|x64.ActiveCfg = Release|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Release|x64.Build.0 = Release|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Release|x86.ActiveCfg = Release|Any CPU
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5}.Release|x86.Build.0 = Release|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Debug|x64.ActiveCfg = Debug|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Debug|x64.Build.0 = Debug|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Debug|x86.ActiveCfg = Debug|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Debug|x86.Build.0 = Debug|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Release|Any CPU.Build.0 = Release|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Release|x64.ActiveCfg = Release|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Release|x64.Build.0 = Release|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Release|x86.ActiveCfg = Release|Any CPU
- {BCE4A428-8B97-4B56-AE45-496EE3906667}.Release|x86.Build.0 = Release|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Debug|x64.ActiveCfg = Debug|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Debug|x64.Build.0 = Debug|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Debug|x86.ActiveCfg = Debug|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Debug|x86.Build.0 = Debug|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Release|Any CPU.Build.0 = Release|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Release|x64.ActiveCfg = Release|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Release|x64.Build.0 = Release|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Release|x86.ActiveCfg = Release|Any CPU
- {27BEF279-AE73-43DC-92A9-FD7021A999D0}.Release|x86.Build.0 = Release|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Debug|x64.ActiveCfg = Debug|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Debug|x64.Build.0 = Debug|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Debug|x86.ActiveCfg = Debug|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Debug|x86.Build.0 = Debug|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Release|Any CPU.Build.0 = Release|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Release|x64.ActiveCfg = Release|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Release|x64.Build.0 = Release|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Release|x86.ActiveCfg = Release|Any CPU
- {34359707-CE66-4DF0-9EF4-D7544B615564}.Release|x86.Build.0 = Release|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Debug|x64.ActiveCfg = Debug|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Debug|x64.Build.0 = Debug|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Debug|x86.ActiveCfg = Debug|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Debug|x86.Build.0 = Debug|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Release|Any CPU.Build.0 = Release|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Release|x64.ActiveCfg = Release|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Release|x64.Build.0 = Release|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Release|x86.ActiveCfg = Release|Any CPU
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6}.Release|x86.Build.0 = Release|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Debug|x64.ActiveCfg = Debug|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Debug|x64.Build.0 = Debug|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Debug|x86.ActiveCfg = Debug|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Debug|x86.Build.0 = Debug|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Release|Any CPU.Build.0 = Release|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Release|x64.ActiveCfg = Release|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Release|x64.Build.0 = Release|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Release|x86.ActiveCfg = Release|Any CPU
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E}.Release|x86.Build.0 = Release|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Debug|x64.ActiveCfg = Debug|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Debug|x64.Build.0 = Debug|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Debug|x86.ActiveCfg = Debug|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Debug|x86.Build.0 = Debug|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Release|Any CPU.Build.0 = Release|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Release|x64.ActiveCfg = Release|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Release|x64.Build.0 = Release|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Release|x86.ActiveCfg = Release|Any CPU
- {49AA63BF-3DBA-4490-9470-5AE0EB7F49F0}.Release|x86.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {F3DF5AC5-8CDC-46D4-969D-1245A6880215} = {CE64E92B-E088-46FB-9028-7FB6B67DEC55}
- {93324D12-DE1B-4C1B-934A-92AA140FF6F6} = {F3DF5AC5-8CDC-46D4-969D-1245A6880215}
- {79981A5A-207A-4A16-A21B-5E80394082F6} = {F3DF5AC5-8CDC-46D4-969D-1245A6880215}
- {05248A38-0F34-4E59-A3D1-B07097987AFB} = {CE64E92B-E088-46FB-9028-7FB6B67DEC55}
- {12F8343D-20A6-4E24-B0F5-3A66F2228CF6} = {CE64E92B-E088-46FB-9028-7FB6B67DEC55}
- {294D6FF8-9379-4AB8-A203-8D0CC85FBFBB} = {05248A38-0F34-4E59-A3D1-B07097987AFB}
- {A1D828E4-6B83-4BA2-B8E9-B21CE3BE8A31} = {05248A38-0F34-4E59-A3D1-B07097987AFB}
- {86BD3DF6-A3E9-4839-8036-813A20DC8AD6} = {CE64E92B-E088-46FB-9028-7FB6B67DEC55}
- {ECCEA352-8953-49D6-8F87-8AB361499420} = {12F8343D-20A6-4E24-B0F5-3A66F2228CF6}
- {D64AD07C-A711-42D8-8653-EDCD7A825A44} = {12F8343D-20A6-4E24-B0F5-3A66F2228CF6}
- {B3866EEF-8F46-4302-ABAC-A95EE2F27331} = {79981A5A-207A-4A16-A21B-5E80394082F6}
- {8C7DAF8E-F792-4092-8BBF-31A6B898B39A} = {93324D12-DE1B-4C1B-934A-92AA140FF6F6}
- {B15705B5-041C-4F1E-8342-AD03182EDD42} = {93324D12-DE1B-4C1B-934A-92AA140FF6F6}
- {89FE1C3B-29D3-48A8-8E7D-90C261D266C5} = {93324D12-DE1B-4C1B-934A-92AA140FF6F6}
- {BCE4A428-8B97-4B56-AE45-496EE3906667} = {2B1F75CE-07A6-4C19-A2E3-F9E062CFDDFB}
- {27BEF279-AE73-43DC-92A9-FD7021A999D0} = {2B1F75CE-07A6-4C19-A2E3-F9E062CFDDFB}
- {34359707-CE66-4DF0-9EF4-D7544B615564} = {2B1F75CE-07A6-4C19-A2E3-F9E062CFDDFB}
- {990CA37A-86D3-4FE6-B777-3CF0DDAC6BF6} = {D36E77BC-4568-4BC8-9506-1EFB7B1CD335}
- {2119CE89-308D-4932-BFCE-8CDC0A05EB9E} = {D36E77BC-4568-4BC8-9506-1EFB7B1CD335}
- {FE1B1E84-F993-4840-9CAB-9082EB523FDD} = {CE64E92B-E088-46FB-9028-7FB6B67DEC55}
- {F17769D7-0E41-4E80-BDD4-282EBE7B5199} = {FE1B1E84-F993-4840-9CAB-9082EB523FDD}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {EA8248C2-3877-4AF7-8777-A17E7881E030}
- EndGlobalSection
-EndGlobal
diff --git a/src/GetToken.http b/src/GetToken.http
deleted file mode 100644
index 0de6481c71..0000000000
--- a/src/GetToken.http
+++ /dev/null
@@ -1,10 +0,0 @@
-@Host = https://localhost:7000
-
-POST {{Host}}/api/token/
-Accept: application/json
-Content-Type: application/json
-tenant: root
-{
- "email":"admin@root.com",
- "password":"123Pa$$word!"
-}
diff --git a/src/api/framework/Core/Audit/AuditTrail.cs b/src/api/framework/Core/Audit/AuditTrail.cs
deleted file mode 100644
index 97448ac39f..0000000000
--- a/src/api/framework/Core/Audit/AuditTrail.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace FSH.Framework.Core.Audit;
-public class AuditTrail
-{
- public Guid Id { get; set; }
- public Guid UserId { get; set; }
- public string? Operation { get; set; }
- public string? Entity { get; set; }
- public DateTimeOffset DateTime { get; set; }
- public string? PreviousValues { get; set; }
- public string? NewValues { get; set; }
- public string? ModifiedProperties { get; set; }
- public string? PrimaryKey { get; set; }
-}
diff --git a/src/api/framework/Core/Audit/IAuditService.cs b/src/api/framework/Core/Audit/IAuditService.cs
deleted file mode 100644
index 9c62f4d0db..0000000000
--- a/src/api/framework/Core/Audit/IAuditService.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace FSH.Framework.Core.Audit;
-public interface IAuditService
-{
- Task> GetUserTrailsAsync(Guid userId);
-}
diff --git a/src/api/framework/Core/Audit/TrailDto.cs b/src/api/framework/Core/Audit/TrailDto.cs
deleted file mode 100644
index 8268e4b172..0000000000
--- a/src/api/framework/Core/Audit/TrailDto.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System.Collections.ObjectModel;
-using System.Text.Json;
-
-namespace FSH.Framework.Core.Audit;
-public class TrailDto()
-{
- public Guid Id { get; set; }
- public DateTimeOffset DateTime { get; set; }
- public Guid UserId { get; set; }
- public Dictionary KeyValues { get; } = [];
- public Dictionary OldValues { get; } = [];
- public Dictionary NewValues { get; } = [];
- public Collection ModifiedProperties { get; } = [];
- public TrailType Type { get; set; }
- public string? TableName { get; set; }
-
- private static readonly JsonSerializerOptions SerializerOptions = new()
- {
- WriteIndented = false,
- };
-
- public AuditTrail ToAuditTrail()
- {
- return new()
- {
- Id = Guid.NewGuid(),
- UserId = UserId,
- Operation = Type.ToString(),
- Entity = TableName,
- DateTime = DateTime,
- PrimaryKey = JsonSerializer.Serialize(KeyValues, SerializerOptions),
- PreviousValues = OldValues.Count == 0 ? null : JsonSerializer.Serialize(OldValues, SerializerOptions),
- NewValues = NewValues.Count == 0 ? null : JsonSerializer.Serialize(NewValues, SerializerOptions),
- ModifiedProperties = ModifiedProperties.Count == 0 ? null : JsonSerializer.Serialize(ModifiedProperties, SerializerOptions)
- };
- }
-}
diff --git a/src/api/framework/Core/Audit/TrailType.cs b/src/api/framework/Core/Audit/TrailType.cs
deleted file mode 100644
index a98bfa29b6..0000000000
--- a/src/api/framework/Core/Audit/TrailType.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace FSH.Framework.Core.Audit;
-public enum TrailType
-{
- None = 0,
- Create = 1,
- Update = 2,
- Delete = 3
-}
diff --git a/src/api/framework/Core/Auth/Jwt/JwtOptions.cs b/src/api/framework/Core/Auth/Jwt/JwtOptions.cs
deleted file mode 100644
index 5d99d6702f..0000000000
--- a/src/api/framework/Core/Auth/Jwt/JwtOptions.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-
-namespace FSH.Framework.Core.Auth.Jwt;
-public class JwtOptions : IValidatableObject
-{
- public string Key { get; set; } = string.Empty;
-
- public int TokenExpirationInMinutes { get; set; } = 60;
-
- public int RefreshTokenExpirationInDays { get; set; } = 7;
-
- public IEnumerable Validate(ValidationContext validationContext)
- {
- if (string.IsNullOrEmpty(Key))
- {
- yield return new ValidationResult("No Key defined in JwtSettings config", [nameof(Key)]);
- }
- }
-}
diff --git a/src/api/framework/Core/Caching/ICacheService.cs b/src/api/framework/Core/Caching/ICacheService.cs
deleted file mode 100644
index 54f3c09048..0000000000
--- a/src/api/framework/Core/Caching/ICacheService.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace FSH.Framework.Core.Caching;
-
-public interface ICacheService
-{
- T? Get(string key);
- Task GetAsync(string key, CancellationToken token = default);
-
- void Refresh(string key);
- Task RefreshAsync(string key, CancellationToken token = default);
-
- void Remove(string key);
- Task RemoveAsync(string key, CancellationToken token = default);
-
- void Set(string key, T value, TimeSpan? slidingExpiration = null);
- Task SetAsync(string key, T value, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default);
-}
\ No newline at end of file
diff --git a/src/api/framework/Core/Domain/Contracts/IEntity.cs b/src/api/framework/Core/Domain/Contracts/IEntity.cs
deleted file mode 100644
index 1d48d306d6..0000000000
--- a/src/api/framework/Core/Domain/Contracts/IEntity.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Collections.ObjectModel;
-using FSH.Framework.Core.Domain.Events;
-
-namespace FSH.Framework.Core.Domain.Contracts;
-
-public interface IEntity
-{
- Collection DomainEvents { get; }
-}
-
-public interface IEntity : IEntity
-{
- TId Id { get; }
-}
diff --git a/src/api/framework/Core/Domain/Events/DomainEvent.cs b/src/api/framework/Core/Domain/Events/DomainEvent.cs
deleted file mode 100644
index 5350854602..0000000000
--- a/src/api/framework/Core/Domain/Events/DomainEvent.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using MediatR;
-
-namespace FSH.Framework.Core.Domain.Events;
-public abstract record DomainEvent : IDomainEvent, INotification
-{
- public DateTime RaisedOn { get; protected set; } = DateTime.UtcNow;
-}
diff --git a/src/api/framework/Core/Domain/Events/IDomainEvent.cs b/src/api/framework/Core/Domain/Events/IDomainEvent.cs
deleted file mode 100644
index 68d4c8f6c2..0000000000
--- a/src/api/framework/Core/Domain/Events/IDomainEvent.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace FSH.Framework.Core.Domain.Events;
-public interface IDomainEvent
-{
-}
diff --git a/src/api/framework/Core/Exceptions/CustomException.cs b/src/api/framework/Core/Exceptions/CustomException.cs
deleted file mode 100644
index 4d1af9af97..0000000000
--- a/src/api/framework/Core/Exceptions/CustomException.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Net;
-
-namespace FSH.Framework.Core.Exceptions;
-
-public class CustomException : Exception
-{
- public List? ErrorMessages { get; }
-
- public HttpStatusCode StatusCode { get; }
-
- public CustomException(string message, List? errors = default, HttpStatusCode statusCode = HttpStatusCode.InternalServerError)
- : base(message)
- {
- ErrorMessages = errors;
- StatusCode = statusCode;
- }
-}
diff --git a/src/api/framework/Core/Exceptions/ForbiddenException.cs b/src/api/framework/Core/Exceptions/ForbiddenException.cs
deleted file mode 100644
index fdafead902..0000000000
--- a/src/api/framework/Core/Exceptions/ForbiddenException.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Net;
-
-namespace FSH.Framework.Core.Exceptions;
-public class ForbiddenException : FshException
-{
- public ForbiddenException()
- : base("unauthorized", [], HttpStatusCode.Forbidden)
- {
- }
- public ForbiddenException(string message)
- : base(message, [], HttpStatusCode.Forbidden)
- {
- }
-}
diff --git a/src/api/framework/Core/Exceptions/FshException.cs b/src/api/framework/Core/Exceptions/FshException.cs
deleted file mode 100644
index 28597c5297..0000000000
--- a/src/api/framework/Core/Exceptions/FshException.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Net;
-
-namespace FSH.Framework.Core.Exceptions;
-public class FshException : Exception
-{
- public IEnumerable ErrorMessages { get; }
-
- public HttpStatusCode StatusCode { get; }
-
- public FshException(string message, IEnumerable errors, HttpStatusCode statusCode = HttpStatusCode.InternalServerError)
- : base(message)
- {
- ErrorMessages = errors;
- StatusCode = statusCode;
- }
-
- public FshException(string message) : base(message)
- {
- ErrorMessages = new List();
- }
-}
diff --git a/src/api/framework/Core/Exceptions/NotFoundException.cs b/src/api/framework/Core/Exceptions/NotFoundException.cs
deleted file mode 100644
index 351e25cfc7..0000000000
--- a/src/api/framework/Core/Exceptions/NotFoundException.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System.Collections.ObjectModel;
-using System.Net;
-
-namespace FSH.Framework.Core.Exceptions;
-public class NotFoundException : FshException
-{
- public NotFoundException(string message)
- : base(message, new Collection(), HttpStatusCode.NotFound)
- {
- }
-}
diff --git a/src/api/framework/Core/Exceptions/UnauthorizedException.cs b/src/api/framework/Core/Exceptions/UnauthorizedException.cs
deleted file mode 100644
index 559eb060c8..0000000000
--- a/src/api/framework/Core/Exceptions/UnauthorizedException.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System.Collections.ObjectModel;
-using System.Net;
-
-namespace FSH.Framework.Core.Exceptions;
-public class UnauthorizedException : FshException
-{
- public UnauthorizedException()
- : base("authentication failed", new Collection(), HttpStatusCode.Unauthorized)
- {
- }
- public UnauthorizedException(string message)
- : base(message, new Collection(), HttpStatusCode.Unauthorized)
- {
- }
-}
diff --git a/src/api/framework/Core/Identity/Roles/Features/CreateOrUpdateRole/CreateOrUpdateRoleValidator.cs b/src/api/framework/Core/Identity/Roles/Features/CreateOrUpdateRole/CreateOrUpdateRoleValidator.cs
deleted file mode 100644
index 68f4526661..0000000000
--- a/src/api/framework/Core/Identity/Roles/Features/CreateOrUpdateRole/CreateOrUpdateRoleValidator.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using FluentValidation;
-
-namespace FSH.Framework.Core.Identity.Roles.Features.CreateOrUpdateRole;
-
-public class CreateOrUpdateRoleValidator : AbstractValidator
-{
- public CreateOrUpdateRoleValidator()
- {
- RuleFor(x => x.Name).NotEmpty().WithMessage("Role name is required.");
- }
-}
diff --git a/src/api/framework/Core/Identity/Roles/Features/UpdatePermissions/UpdatePermissionsCommand.cs b/src/api/framework/Core/Identity/Roles/Features/UpdatePermissions/UpdatePermissionsCommand.cs
deleted file mode 100644
index 900c153956..0000000000
--- a/src/api/framework/Core/Identity/Roles/Features/UpdatePermissions/UpdatePermissionsCommand.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace FSH.Framework.Core.Identity.Roles.Features.UpdatePermissions;
-public class UpdatePermissionsCommand
-{
- public string RoleId { get; set; } = default!;
- public List Permissions { get; set; } = default!;
-}
diff --git a/src/api/framework/Core/Identity/Roles/Features/UpdatePermissions/UpdatePermissionsValidator.cs b/src/api/framework/Core/Identity/Roles/Features/UpdatePermissions/UpdatePermissionsValidator.cs
deleted file mode 100644
index 34b0b7f01c..0000000000
--- a/src/api/framework/Core/Identity/Roles/Features/UpdatePermissions/UpdatePermissionsValidator.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using FluentValidation;
-
-namespace FSH.Framework.Core.Identity.Roles.Features.UpdatePermissions;
-public class UpdatePermissionsValidator : AbstractValidator
-{
- public UpdatePermissionsValidator()
- {
- RuleFor(r => r.RoleId)
- .NotEmpty();
- RuleFor(r => r.Permissions)
- .NotNull();
- }
-}
diff --git a/src/api/framework/Core/Identity/Roles/IRoleService.cs b/src/api/framework/Core/Identity/Roles/IRoleService.cs
deleted file mode 100644
index dca61839af..0000000000
--- a/src/api/framework/Core/Identity/Roles/IRoleService.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using FSH.Framework.Core.Identity.Roles.Features.CreateOrUpdateRole;
-using FSH.Framework.Core.Identity.Roles.Features.UpdatePermissions;
-
-namespace FSH.Framework.Core.Identity.Roles;
-
-public interface IRoleService
-{
- Task> GetRolesAsync();
- Task GetRoleAsync(string id);
- Task CreateOrUpdateRoleAsync(CreateOrUpdateRoleCommand command);
- Task DeleteRoleAsync(string id);
- Task GetWithPermissionsAsync(string id, CancellationToken cancellationToken);
-
- Task UpdatePermissionsAsync(UpdatePermissionsCommand request);
-}
-
diff --git a/src/api/framework/Core/Identity/Tokens/Features/Generate/TokenGenerationCommand.cs b/src/api/framework/Core/Identity/Tokens/Features/Generate/TokenGenerationCommand.cs
deleted file mode 100644
index dccc1e15d7..0000000000
--- a/src/api/framework/Core/Identity/Tokens/Features/Generate/TokenGenerationCommand.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.ComponentModel;
-using FluentValidation;
-using FSH.Starter.Shared.Authorization;
-
-namespace FSH.Framework.Core.Identity.Tokens.Features.Generate;
-public record TokenGenerationCommand(
- [property: DefaultValue(TenantConstants.Root.EmailAddress)] string Email,
- [property: DefaultValue(TenantConstants.DefaultPassword)] string Password);
-
-public class GenerateTokenValidator : AbstractValidator
-{
- public GenerateTokenValidator()
- {
- RuleFor(p => p.Email).Cascade(CascadeMode.Stop).NotEmpty().EmailAddress();
-
- RuleFor(p => p.Password).Cascade(CascadeMode.Stop).NotEmpty();
- }
-}
diff --git a/src/api/framework/Core/Identity/Tokens/Features/Refresh/RefreshTokenCommand.cs b/src/api/framework/Core/Identity/Tokens/Features/Refresh/RefreshTokenCommand.cs
deleted file mode 100644
index 8fc45b8d24..0000000000
--- a/src/api/framework/Core/Identity/Tokens/Features/Refresh/RefreshTokenCommand.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using FluentValidation;
-
-namespace FSH.Framework.Core.Identity.Tokens.Features.Refresh;
-public record RefreshTokenCommand(string Token, string RefreshToken);
-
-public class RefreshTokenValidator : AbstractValidator
-{
- public RefreshTokenValidator()
- {
- RuleFor(p => p.Token).Cascade(CascadeMode.Stop).NotEmpty();
-
- RuleFor(p => p.RefreshToken).Cascade(CascadeMode.Stop).NotEmpty();
- }
-}
diff --git a/src/api/framework/Core/Identity/Tokens/ITokenService.cs b/src/api/framework/Core/Identity/Tokens/ITokenService.cs
deleted file mode 100644
index 86665ec818..0000000000
--- a/src/api/framework/Core/Identity/Tokens/ITokenService.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using FSH.Framework.Core.Identity.Tokens.Features.Generate;
-using FSH.Framework.Core.Identity.Tokens.Features.Refresh;
-using FSH.Framework.Core.Identity.Tokens.Models;
-
-namespace FSH.Framework.Core.Identity.Tokens;
-public interface ITokenService
-{
- Task GenerateTokenAsync(TokenGenerationCommand request, string ipAddress, CancellationToken cancellationToken);
- Task RefreshTokenAsync(RefreshTokenCommand request, string ipAddress, CancellationToken cancellationToken);
-
-}
diff --git a/src/api/framework/Core/Identity/Tokens/Models/TokenResponse.cs b/src/api/framework/Core/Identity/Tokens/Models/TokenResponse.cs
deleted file mode 100644
index fc56f00d89..0000000000
--- a/src/api/framework/Core/Identity/Tokens/Models/TokenResponse.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-namespace FSH.Framework.Core.Identity.Tokens.Models;
-public record TokenResponse(string Token, string RefreshToken, DateTime RefreshTokenExpiryTime);
diff --git a/src/api/framework/Core/Identity/Users/Abstractions/IUserService.cs b/src/api/framework/Core/Identity/Users/Abstractions/IUserService.cs
deleted file mode 100644
index 95fbf9f577..0000000000
--- a/src/api/framework/Core/Identity/Users/Abstractions/IUserService.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System.Security.Claims;
-using FSH.Framework.Core.Identity.Users.Dtos;
-using FSH.Framework.Core.Identity.Users.Features.AssignUserRole;
-using FSH.Framework.Core.Identity.Users.Features.ChangePassword;
-using FSH.Framework.Core.Identity.Users.Features.ForgotPassword;
-using FSH.Framework.Core.Identity.Users.Features.RegisterUser;
-using FSH.Framework.Core.Identity.Users.Features.ResetPassword;
-using FSH.Framework.Core.Identity.Users.Features.ToggleUserStatus;
-using FSH.Framework.Core.Identity.Users.Features.UpdateUser;
-
-namespace FSH.Framework.Core.Identity.Users.Abstractions;
-public interface IUserService
-{
- Task ExistsWithNameAsync(string name);
- Task ExistsWithEmailAsync(string email, string? exceptId = null);
- Task ExistsWithPhoneNumberAsync(string phoneNumber, string? exceptId = null);
- Task> GetListAsync(CancellationToken cancellationToken);
- Task GetCountAsync(CancellationToken cancellationToken);
- Task GetAsync(string userId, CancellationToken cancellationToken);
- Task ToggleStatusAsync(ToggleUserStatusCommand request, CancellationToken cancellationToken);
- Task GetOrCreateFromPrincipalAsync(ClaimsPrincipal principal);
- Task RegisterAsync(RegisterUserCommand request, string origin, CancellationToken cancellationToken);
- Task UpdateAsync(UpdateUserCommand request, string userId);
- Task DeleteAsync(string userId);
- Task ConfirmEmailAsync(string userId, string code, string tenant, CancellationToken cancellationToken);
- Task ConfirmPhoneNumberAsync(string userId, string code);
-
- // permisions
- Task HasPermissionAsync(string userId, string permission, CancellationToken cancellationToken = default);
-
- // passwords
- Task ForgotPasswordAsync(ForgotPasswordCommand request, string origin, CancellationToken cancellationToken);
- Task ResetPasswordAsync(ResetPasswordCommand request, CancellationToken cancellationToken);
- Task?> GetPermissionsAsync(string userId, CancellationToken cancellationToken);
-
- Task ChangePasswordAsync(ChangePasswordCommand request, string userId);
- Task AssignRolesAsync(string userId, AssignUserRoleCommand request, CancellationToken cancellationToken);
- Task> GetUserRolesAsync(string userId, CancellationToken cancellationToken);
-}
diff --git a/src/api/framework/Core/Identity/Users/Features/AssignUserRole/AssignUserRoleCommand.cs b/src/api/framework/Core/Identity/Users/Features/AssignUserRole/AssignUserRoleCommand.cs
deleted file mode 100644
index 34f3fadb89..0000000000
--- a/src/api/framework/Core/Identity/Users/Features/AssignUserRole/AssignUserRoleCommand.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using FSH.Framework.Core.Identity.Users.Dtos;
-
-namespace FSH.Framework.Core.Identity.Users.Features.AssignUserRole;
-public class AssignUserRoleCommand
-{
- public List UserRoles { get; set; } = new();
-}
diff --git a/src/api/framework/Core/Identity/Users/Features/ChangePassword/ChangePasswordCommand.cs b/src/api/framework/Core/Identity/Users/Features/ChangePassword/ChangePasswordCommand.cs
deleted file mode 100644
index 82abe1323c..0000000000
--- a/src/api/framework/Core/Identity/Users/Features/ChangePassword/ChangePasswordCommand.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace FSH.Framework.Core.Identity.Users.Features.ChangePassword;
-public class ChangePasswordCommand
-{
- public string Password { get; set; } = default!;
- public string NewPassword { get; set; } = default!;
- public string ConfirmNewPassword { get; set; } = default!;
-}
diff --git a/src/api/framework/Core/Identity/Users/Features/ChangePassword/ChangePasswordValidator.cs b/src/api/framework/Core/Identity/Users/Features/ChangePassword/ChangePasswordValidator.cs
deleted file mode 100644
index 9d52f78856..0000000000
--- a/src/api/framework/Core/Identity/Users/Features/ChangePassword/ChangePasswordValidator.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using FluentValidation;
-
-namespace FSH.Framework.Core.Identity.Users.Features.ChangePassword;
-public class ChangePasswordValidator : AbstractValidator
-{
- public ChangePasswordValidator()
- {
- RuleFor(p => p.Password)
- .NotEmpty();
-
- RuleFor(p => p.NewPassword)
- .NotEmpty();
-
- RuleFor(p => p.ConfirmNewPassword)
- .Equal(p => p.NewPassword)
- .WithMessage("passwords do not match.");
- }
-}
diff --git a/src/api/framework/Core/Identity/Users/Features/ForgotPassword/ForgotPasswordValidator.cs b/src/api/framework/Core/Identity/Users/Features/ForgotPassword/ForgotPasswordValidator.cs
deleted file mode 100644
index 2df57f5be4..0000000000
--- a/src/api/framework/Core/Identity/Users/Features/ForgotPassword/ForgotPasswordValidator.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using FluentValidation;
-
-namespace FSH.Framework.Core.Identity.Users.Features.ForgotPassword;
-public class ForgotPasswordValidator : AbstractValidator
-{
- public ForgotPasswordValidator()
- {
- RuleFor(p => p.Email).Cascade(CascadeMode.Stop)
- .NotEmpty()
- .EmailAddress();
- }
-}
diff --git a/src/api/framework/Core/Identity/Users/Features/RegisterUser/RegisterUserResponse.cs b/src/api/framework/Core/Identity/Users/Features/RegisterUser/RegisterUserResponse.cs
deleted file mode 100644
index 967539ae78..0000000000
--- a/src/api/framework/Core/Identity/Users/Features/RegisterUser/RegisterUserResponse.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-namespace FSH.Framework.Core.Identity.Users.Features.RegisterUser;
-public record RegisterUserResponse(string UserId);
diff --git a/src/api/framework/Core/Identity/Users/Features/ResetPassword/ResetPasswordValidator.cs b/src/api/framework/Core/Identity/Users/Features/ResetPassword/ResetPasswordValidator.cs
deleted file mode 100644
index 4141905651..0000000000
--- a/src/api/framework/Core/Identity/Users/Features/ResetPassword/ResetPasswordValidator.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using FluentValidation;
-
-namespace FSH.Framework.Core.Identity.Users.Features.ResetPassword;
-
-public class ResetPasswordValidator : AbstractValidator
-{
- public ResetPasswordValidator()
- {
- RuleFor(x => x.Email).NotEmpty().EmailAddress();
- RuleFor(x => x.Password).NotEmpty();
- RuleFor(x => x.Token).NotEmpty();
- }
-}
diff --git a/src/api/framework/Core/Paging/Extensions.cs b/src/api/framework/Core/Paging/Extensions.cs
deleted file mode 100644
index a9c5544eb9..0000000000
--- a/src/api/framework/Core/Paging/Extensions.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Ardalis.Specification;
-
-namespace FSH.Framework.Core.Paging;
-public static class Extensions
-{
- public static async Task> PaginatedListAsync(
- this IReadRepositoryBase repository, ISpecification spec, PaginationFilter filter, CancellationToken cancellationToken = default)
- where T : class
- where TDestination : class
- {
- ArgumentNullException.ThrowIfNull(repository);
-
- var items = await repository.ListAsync(spec, cancellationToken).ConfigureAwait(false);
- int totalCount = await repository.CountAsync(spec, cancellationToken).ConfigureAwait(false);
-
- return new PagedList(items, filter.PageNumber, filter.PageSize, totalCount);
- }
-}
diff --git a/src/api/framework/Core/Paging/Filter.cs b/src/api/framework/Core/Paging/Filter.cs
deleted file mode 100644
index fdbdc29387..0000000000
--- a/src/api/framework/Core/Paging/Filter.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-namespace FSH.Framework.Core.Paging;
-
-public static class FilterOperator
-{
- public const string EQ = "eq";
- public const string NEQ = "neq";
- public const string LT = "lt";
- public const string LTE = "lte";
- public const string GT = "gt";
- public const string GTE = "gte";
- public const string STARTSWITH = "startswith";
- public const string ENDSWITH = "endswith";
- public const string CONTAINS = "contains";
-}
-
-public static class FilterLogic
-{
- public const string AND = "and";
- public const string OR = "or";
- public const string XOR = "xor";
-}
-
-public class Filter
-{
- public string? Logic { get; set; }
-
- public IEnumerable? Filters { get; set; }
-
- public string? Field { get; set; }
-
- public string? Operator { get; set; }
-
- public object? Value { get; set; }
-}
diff --git a/src/api/framework/Core/Paging/IPageRequest.cs b/src/api/framework/Core/Paging/IPageRequest.cs
deleted file mode 100644
index c4a2a7f147..0000000000
--- a/src/api/framework/Core/Paging/IPageRequest.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace FSH.Framework.Core.Paging;
-
-public interface IPageRequest
-{
- int PageNumber { get; init; }
- int PageSize { get; init; }
- string? Filters { get; init; }
- string? SortOrder { get; init; }
-}
diff --git a/src/api/framework/Core/Paging/IPagedList.cs b/src/api/framework/Core/Paging/IPagedList.cs
deleted file mode 100644
index e2950b3984..0000000000
--- a/src/api/framework/Core/Paging/IPagedList.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace FSH.Framework.Core.Paging;
-
-public interface IPagedList
- where T : class
-{
- int TotalPages { get; }
- bool HasPrevious { get; }
- bool HasNext { get; }
- IReadOnlyList Items { get; init; }
- int TotalCount { get; init; }
- int PageNumber { get; init; }
- int PageSize { get; init; }
-
- IPagedList MapTo
(Func map)
- where TR : class;
- IPagedList MapTo
()
- where TR : class;
-}
diff --git a/src/api/framework/Core/Paging/PagedList.cs b/src/api/framework/Core/Paging/PagedList.cs
deleted file mode 100644
index 7f48292f7c..0000000000
--- a/src/api/framework/Core/Paging/PagedList.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Mapster;
-
-namespace FSH.Framework.Core.Paging;
-
-public record PagedList(IReadOnlyList Items, int PageNumber, int PageSize, int TotalCount) : IPagedList
- where T : class
-{
- public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
- public bool HasPrevious => PageNumber > 1;
- public bool HasNext => PageNumber < TotalPages;
- public IPagedList MapTo
(Func map)
- where TR : class
- {
- return new PagedList(Items.Select(map).ToList(), PageNumber, PageSize, TotalCount);
- }
- public IPagedList
MapTo
()
- where TR : class
- {
- return new PagedList
(Items.Adapt>(), PageNumber, PageSize, TotalCount);
- }
-}
diff --git a/src/api/framework/Core/Paging/PaginationFilter.cs b/src/api/framework/Core/Paging/PaginationFilter.cs
deleted file mode 100644
index 13be4026ee..0000000000
--- a/src/api/framework/Core/Paging/PaginationFilter.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace FSH.Framework.Core.Paging;
-
-public class PaginationFilter : BaseFilter
-{
- public int PageNumber { get; set; }
-
- public int PageSize { get; set; } = int.MaxValue;
- public string[]? OrderBy { get; set; }
-}
-
-public static class PaginationFilterExtensions
-{
- public static bool HasOrderBy(this PaginationFilter filter) =>
- filter.OrderBy?.Any() is true;
-}
diff --git a/src/api/framework/Core/Paging/Search.cs b/src/api/framework/Core/Paging/Search.cs
deleted file mode 100644
index a2f2624980..0000000000
--- a/src/api/framework/Core/Paging/Search.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace FSH.Framework.Core.Paging;
-
-public class Search
-{
- public List Fields { get; set; } = new();
- public string? Keyword { get; set; }
-}
diff --git a/src/api/framework/Core/Storage/File/Features/FileUploadCommand.cs b/src/api/framework/Core/Storage/File/Features/FileUploadCommand.cs
deleted file mode 100644
index c4e5cb0e53..0000000000
--- a/src/api/framework/Core/Storage/File/Features/FileUploadCommand.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using MediatR;
-
-namespace FSH.Framework.Core.Storage.File.Features;
-
-public class FileUploadCommand : IRequest
-{
- public string Name { get; set; } = default!;
- public string Extension { get; set; } = default!;
- public string Data { get; set; } = default!;
-}
-
diff --git a/src/api/framework/Core/Storage/File/Features/FileUploadResponse.cs b/src/api/framework/Core/Storage/File/Features/FileUploadResponse.cs
deleted file mode 100644
index f3af35debf..0000000000
--- a/src/api/framework/Core/Storage/File/Features/FileUploadResponse.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace FSH.Framework.Core.Storage.File.Features;
-
-public class FileUploadResponse
-{
- public Uri Url { get; set; } = default!;
-}
-
diff --git a/src/api/framework/Core/Storage/File/Features/FileUploadValidator.cs b/src/api/framework/Core/Storage/File/Features/FileUploadValidator.cs
deleted file mode 100644
index c064cf93f3..0000000000
--- a/src/api/framework/Core/Storage/File/Features/FileUploadValidator.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using FluentValidation;
-
-namespace FSH.Framework.Core.Storage.File.Features;
-
-public class FileUploadRequestValidator : AbstractValidator
-{
- public FileUploadRequestValidator()
- {
- RuleFor(p => p.Name)
- .NotEmpty()
- .MaximumLength(150);
-
- RuleFor(p => p.Extension)
- .NotEmpty()
- .MaximumLength(5);
-
- RuleFor(p => p.Data)
- .NotEmpty();
- }
-}
-
diff --git a/src/api/framework/Core/Storage/File/FileType.cs b/src/api/framework/Core/Storage/File/FileType.cs
deleted file mode 100644
index 267968aaa6..0000000000
--- a/src/api/framework/Core/Storage/File/FileType.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.ComponentModel;
-
-namespace FSH.Framework.Core.Storage.File;
-
-public enum FileType
-{
- [Description(".jpg,.png,.jpeg")]
- Image
-}
diff --git a/src/api/framework/Core/Storage/IStorageService.cs b/src/api/framework/Core/Storage/IStorageService.cs
deleted file mode 100644
index 5e13d6ddec..0000000000
--- a/src/api/framework/Core/Storage/IStorageService.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using FSH.Framework.Core.Storage.File;
-using FSH.Framework.Core.Storage.File.Features;
-
-namespace FSH.Framework.Core.Storage;
-
-public interface IStorageService
-{
- public Task UploadAsync(FileUploadCommand? request, FileType supportedFileType, CancellationToken cancellationToken = default)
- where T : class;
-
- public void Remove(Uri? path);
-}
diff --git a/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantCommand.cs b/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantCommand.cs
deleted file mode 100644
index 01f902dfc9..0000000000
--- a/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantCommand.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.ActivateTenant;
-public record ActivateTenantCommand(string TenantId) : IRequest;
diff --git a/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantHandler.cs b/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantHandler.cs
deleted file mode 100644
index ab018e532e..0000000000
--- a/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantHandler.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using FSH.Framework.Core.Tenant.Abstractions;
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.ActivateTenant;
-public sealed class ActivateTenantHandler(ITenantService service) : IRequestHandler
-{
- public async Task Handle(ActivateTenantCommand request, CancellationToken cancellationToken)
- {
- var status = await service.ActivateAsync(request.TenantId, cancellationToken);
- return new ActivateTenantResponse(status);
- }
-}
diff --git a/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantResponse.cs b/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantResponse.cs
deleted file mode 100644
index bd396891df..0000000000
--- a/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantResponse.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-namespace FSH.Framework.Core.Tenant.Features.ActivateTenant;
-public record ActivateTenantResponse(string Status);
diff --git a/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantValidator.cs b/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantValidator.cs
deleted file mode 100644
index de9bceb45e..0000000000
--- a/src/api/framework/Core/Tenant/Features/ActivateTenant/ActivateTenantValidator.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using FluentValidation;
-
-namespace FSH.Framework.Core.Tenant.Features.ActivateTenant;
-public sealed class ActivateTenantValidator : AbstractValidator
-{
- public ActivateTenantValidator() =>
- RuleFor(t => t.TenantId)
- .NotEmpty();
-}
diff --git a/src/api/framework/Core/Tenant/Features/CreateTenant/CreateTenantCommand.cs b/src/api/framework/Core/Tenant/Features/CreateTenant/CreateTenantCommand.cs
deleted file mode 100644
index a6bec85931..0000000000
--- a/src/api/framework/Core/Tenant/Features/CreateTenant/CreateTenantCommand.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.CreateTenant;
-public sealed record CreateTenantCommand(string Id,
- string Name,
- string? ConnectionString,
- string AdminEmail,
- string? Issuer) : IRequest;
diff --git a/src/api/framework/Core/Tenant/Features/CreateTenant/CreateTenantHandler.cs b/src/api/framework/Core/Tenant/Features/CreateTenant/CreateTenantHandler.cs
deleted file mode 100644
index d948367cb8..0000000000
--- a/src/api/framework/Core/Tenant/Features/CreateTenant/CreateTenantHandler.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using FSH.Framework.Core.Tenant.Abstractions;
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.CreateTenant;
-public sealed class CreateTenantHandler(ITenantService service) : IRequestHandler
-{
- public async Task Handle(CreateTenantCommand request, CancellationToken cancellationToken)
- {
- var tenantId = await service.CreateAsync(request, cancellationToken);
- return new CreateTenantResponse(tenantId);
- }
-}
diff --git a/src/api/framework/Core/Tenant/Features/CreateTenant/CreateTenantResponse.cs b/src/api/framework/Core/Tenant/Features/CreateTenant/CreateTenantResponse.cs
deleted file mode 100644
index 7a778e4f79..0000000000
--- a/src/api/framework/Core/Tenant/Features/CreateTenant/CreateTenantResponse.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-namespace FSH.Framework.Core.Tenant.Features.CreateTenant;
-public record CreateTenantResponse(string Id);
diff --git a/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantCommand.cs b/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantCommand.cs
deleted file mode 100644
index bc0dc1fa95..0000000000
--- a/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantCommand.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.DisableTenant;
-public record DisableTenantCommand(string TenantId) : IRequest;
diff --git a/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantHandler.cs b/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantHandler.cs
deleted file mode 100644
index d9cad8dcbd..0000000000
--- a/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantHandler.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using FSH.Framework.Core.Tenant.Abstractions;
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.DisableTenant;
-public sealed class DisableTenantHandler(ITenantService service) : IRequestHandler
-{
- public async Task Handle(DisableTenantCommand request, CancellationToken cancellationToken)
- {
- var status = await service.DeactivateAsync(request.TenantId);
- return new DisableTenantResponse(status);
- }
-}
diff --git a/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantResponse.cs b/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantResponse.cs
deleted file mode 100644
index 89ce0c0538..0000000000
--- a/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantResponse.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-namespace FSH.Framework.Core.Tenant.Features.DisableTenant;
-public record DisableTenantResponse(string Status);
diff --git a/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantValidator.cs b/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantValidator.cs
deleted file mode 100644
index 2c0831e209..0000000000
--- a/src/api/framework/Core/Tenant/Features/DisableTenant/DisableTenantValidator.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using FluentValidation;
-
-namespace FSH.Framework.Core.Tenant.Features.DisableTenant;
-public sealed class DisableTenantValidator : AbstractValidator
-{
- public DisableTenantValidator() =>
- RuleFor(t => t.TenantId)
- .NotEmpty();
-}
diff --git a/src/api/framework/Core/Tenant/Features/GetTenantById/GetTenantByIdHandler.cs b/src/api/framework/Core/Tenant/Features/GetTenantById/GetTenantByIdHandler.cs
deleted file mode 100644
index ec8e68737c..0000000000
--- a/src/api/framework/Core/Tenant/Features/GetTenantById/GetTenantByIdHandler.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using FSH.Framework.Core.Tenant.Abstractions;
-using FSH.Framework.Core.Tenant.Dtos;
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.GetTenantById;
-public sealed class GetTenantByIdHandler(ITenantService service) : IRequestHandler
-{
- public async Task Handle(GetTenantByIdQuery request, CancellationToken cancellationToken)
- {
- return await service.GetByIdAsync(request.TenantId);
- }
-}
diff --git a/src/api/framework/Core/Tenant/Features/GetTenantById/GetTenantByIdQuery.cs b/src/api/framework/Core/Tenant/Features/GetTenantById/GetTenantByIdQuery.cs
deleted file mode 100644
index 9f75bc68c4..0000000000
--- a/src/api/framework/Core/Tenant/Features/GetTenantById/GetTenantByIdQuery.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-using FSH.Framework.Core.Tenant.Dtos;
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.GetTenantById;
-public record GetTenantByIdQuery(string TenantId) : IRequest;
diff --git a/src/api/framework/Core/Tenant/Features/GetTenants/GetTenantsHandler.cs b/src/api/framework/Core/Tenant/Features/GetTenants/GetTenantsHandler.cs
deleted file mode 100644
index 1ccd5f90eb..0000000000
--- a/src/api/framework/Core/Tenant/Features/GetTenants/GetTenantsHandler.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using FSH.Framework.Core.Tenant.Abstractions;
-using FSH.Framework.Core.Tenant.Dtos;
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.GetTenants;
-public sealed class GetTenantsHandler(ITenantService service) : IRequestHandler>
-{
- public Task> Handle(GetTenantsQuery request, CancellationToken cancellationToken)
- {
- return service.GetAllAsync();
- }
-}
diff --git a/src/api/framework/Core/Tenant/Features/GetTenants/GetTenantsQuery.cs b/src/api/framework/Core/Tenant/Features/GetTenants/GetTenantsQuery.cs
deleted file mode 100644
index dba6bc1896..0000000000
--- a/src/api/framework/Core/Tenant/Features/GetTenants/GetTenantsQuery.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-using FSH.Framework.Core.Tenant.Dtos;
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.GetTenants;
-public sealed class GetTenantsQuery : IRequest>;
diff --git a/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionCommand.cs b/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionCommand.cs
deleted file mode 100644
index f132f455b7..0000000000
--- a/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionCommand.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.UpgradeSubscription;
-public class UpgradeSubscriptionCommand : IRequest
-{
- public string Tenant { get; set; } = default!;
- public DateTime ExtendedExpiryDate { get; set; }
-}
diff --git a/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionHandler.cs b/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionHandler.cs
deleted file mode 100644
index e4cbbb4e7a..0000000000
--- a/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionHandler.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using FSH.Framework.Core.Tenant.Abstractions;
-using MediatR;
-
-namespace FSH.Framework.Core.Tenant.Features.UpgradeSubscription;
-
-public class UpgradeSubscriptionHandler : IRequestHandler
-{
- private readonly ITenantService _tenantService;
-
- public UpgradeSubscriptionHandler(ITenantService tenantService) => _tenantService = tenantService;
-
- public async Task Handle(UpgradeSubscriptionCommand request, CancellationToken cancellationToken)
- {
- var validUpto = await _tenantService.UpgradeSubscription(request.Tenant, request.ExtendedExpiryDate);
- return new UpgradeSubscriptionResponse(validUpto, request.Tenant);
- }
-}
diff --git a/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionResponse.cs b/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionResponse.cs
deleted file mode 100644
index ef14487b74..0000000000
--- a/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionResponse.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-namespace FSH.Framework.Core.Tenant.Features.UpgradeSubscription;
-public record UpgradeSubscriptionResponse(DateTime NewValidity, string Tenant);
diff --git a/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionValidator.cs b/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionValidator.cs
deleted file mode 100644
index daddf1fbf1..0000000000
--- a/src/api/framework/Core/Tenant/Features/UpgradeSubscription/UpgradeSubscriptionValidator.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using FluentValidation;
-
-namespace FSH.Framework.Core.Tenant.Features.UpgradeSubscription;
-public class UpgradeSubscriptionValidator : AbstractValidator
-{
- public UpgradeSubscriptionValidator()
- {
- RuleFor(t => t.Tenant).NotEmpty();
- RuleFor(t => t.ExtendedExpiryDate).GreaterThan(DateTime.UtcNow);
- }
-}
diff --git a/src/api/framework/Infrastructure/Auth/Jwt/JwtAuthConstants.cs b/src/api/framework/Infrastructure/Auth/Jwt/JwtAuthConstants.cs
deleted file mode 100644
index b766fdf804..0000000000
--- a/src/api/framework/Infrastructure/Auth/Jwt/JwtAuthConstants.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace FSH.Framework.Infrastructure.Auth.Jwt;
-internal static class JwtAuthConstants
-{
- public const string Issuer = "https://fullstackhero.net";
- public const string Audience = "fullstackhero";
-}
diff --git a/src/api/framework/Infrastructure/Behaviours/ValidationBehavior.cs b/src/api/framework/Infrastructure/Behaviours/ValidationBehavior.cs
deleted file mode 100644
index 016652aeb7..0000000000
--- a/src/api/framework/Infrastructure/Behaviours/ValidationBehavior.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using FluentValidation;
-using MediatR;
-
-namespace FSH.Framework.Infrastructure.Behaviours;
-public class ValidationBehavior(IEnumerable> validators) : IPipelineBehavior
- where TRequest : IRequest
-{
- private readonly IEnumerable> _validators = validators;
-
- public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken)
- {
- if (_validators.Any())
- {
- var context = new ValidationContext(request);
- var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
- var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
-
- if (failures.Count > 0)
- throw new ValidationException(failures);
- }
- return await next();
- }
-}
diff --git a/src/api/framework/Infrastructure/Extensions.cs b/src/api/framework/Infrastructure/Extensions.cs
deleted file mode 100644
index 865bce172d..0000000000
--- a/src/api/framework/Infrastructure/Extensions.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-using System.Reflection;
-using Asp.Versioning.Conventions;
-using FluentValidation;
-using FSH.Framework.Core;
-using FSH.Framework.Core.Origin;
-using FSH.Framework.Infrastructure.Auth;
-using FSH.Framework.Infrastructure.Auth.Jwt;
-using FSH.Framework.Infrastructure.Behaviours;
-using FSH.Framework.Infrastructure.Caching;
-using FSH.Framework.Infrastructure.Cors;
-using FSH.Framework.Infrastructure.Exceptions;
-using FSH.Framework.Infrastructure.Identity;
-using FSH.Framework.Infrastructure.Jobs;
-using FSH.Framework.Infrastructure.Logging.Serilog;
-using FSH.Framework.Infrastructure.Mail;
-using FSH.Framework.Infrastructure.OpenApi;
-using FSH.Framework.Infrastructure.Persistence;
-using FSH.Framework.Infrastructure.RateLimit;
-using FSH.Framework.Infrastructure.SecurityHeaders;
-using FSH.Framework.Infrastructure.Storage.Files;
-using FSH.Framework.Infrastructure.Tenant;
-using FSH.Framework.Infrastructure.Tenant.Endpoints;
-using FSH.Starter.Aspire.ServiceDefaults;
-using MediatR;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.FileProviders;
-
-namespace FSH.Framework.Infrastructure;
-
-public static class Extensions
-{
- public static WebApplicationBuilder ConfigureFshFramework(this WebApplicationBuilder builder)
- {
- ArgumentNullException.ThrowIfNull(builder);
- builder.AddServiceDefaults();
- builder.ConfigureSerilog();
- builder.ConfigureDatabase();
- builder.Services.ConfigureMultitenancy();
- builder.Services.ConfigureIdentity();
- builder.Services.AddCorsPolicy(builder.Configuration);
- builder.Services.ConfigureFileStorage();
- builder.Services.ConfigureJwtAuth();
- builder.Services.ConfigureOpenApi();
- builder.Services.ConfigureJobs(builder.Configuration);
- builder.Services.ConfigureMailing();
- builder.Services.ConfigureCaching(builder.Configuration);
- builder.Services.AddExceptionHandler();
- builder.Services.AddProblemDetails();
- builder.Services.AddHealthChecks();
- builder.Services.AddOptions().BindConfiguration(nameof(OriginOptions));
-
- // Define module assemblies
- var assemblies = new Assembly[]
- {
- typeof(FshCore).Assembly,
- typeof(FshInfrastructure).Assembly
- };
-
- // Register validators
- builder.Services.AddValidatorsFromAssemblies(assemblies);
-
- // Register MediatR
- builder.Services.AddMediatR(cfg =>
- {
- cfg.RegisterServicesFromAssemblies(assemblies);
- cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
- });
-
- builder.Services.ConfigureRateLimit(builder.Configuration);
- builder.Services.ConfigureSecurityHeaders(builder.Configuration);
-
- return builder;
- }
-
- public static WebApplication UseFshFramework(this WebApplication app)
- {
- app.MapDefaultEndpoints();
- app.UseRateLimit();
- app.UseSecurityHeaders();
- app.UseMultitenancy();
- app.UseExceptionHandler();
- app.UseCorsPolicy();
- app.UseOpenApi();
- app.UseJobDashboard(app.Configuration);
- app.UseRouting();
- app.UseStaticFiles();
- app.UseStaticFiles(new StaticFileOptions()
- {
- FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "assets")),
- RequestPath = new PathString("/assets")
- });
- app.UseAuthentication();
- app.UseAuthorization();
- app.MapTenantEndpoints();
- app.MapIdentityEndpoints();
-
- // Current user middleware
- app.UseMiddleware();
-
- // Register API versions
- var versions = app.NewApiVersionSet()
- .HasApiVersion(1)
- .HasApiVersion(2)
- .ReportApiVersions()
- .Build();
-
- // Map versioned endpoint
- app.MapGroup("api/v{version:apiVersion}").WithApiVersionSet(versions);
-
- return app;
- }
-}
diff --git a/src/api/framework/Infrastructure/Identity/Audit/AuditPublishedEvent.cs b/src/api/framework/Infrastructure/Identity/Audit/AuditPublishedEvent.cs
deleted file mode 100644
index 46587882d7..0000000000
--- a/src/api/framework/Infrastructure/Identity/Audit/AuditPublishedEvent.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Collections.ObjectModel;
-using FSH.Framework.Core.Audit;
-using MediatR;
-
-namespace FSH.Framework.Infrastructure.Identity.Audit;
-public class AuditPublishedEvent : INotification
-{
- public AuditPublishedEvent(Collection? trails)
- {
- Trails = trails;
- }
- public Collection? Trails { get; }
-}
diff --git a/src/api/framework/Infrastructure/Identity/Audit/AuditPublishedEventHandler.cs b/src/api/framework/Infrastructure/Identity/Audit/AuditPublishedEventHandler.cs
deleted file mode 100644
index cb255f82af..0000000000
--- a/src/api/framework/Infrastructure/Identity/Audit/AuditPublishedEventHandler.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using FSH.Framework.Core.Audit;
-using FSH.Framework.Infrastructure.Identity.Persistence;
-using MediatR;
-using Microsoft.Extensions.Logging;
-
-namespace FSH.Framework.Infrastructure.Identity.Audit;
-public class AuditPublishedEventHandler(ILogger logger, IdentityDbContext context) : INotificationHandler
-{
- public async Task Handle(AuditPublishedEvent notification, CancellationToken cancellationToken)
- {
- if (context == null) return;
- logger.LogInformation("received audit trails");
- try
- {
- await context.Set().AddRangeAsync(notification.Trails!, default);
- await context.SaveChangesAsync(default);
- }
- catch
- {
- logger.LogError("error while saving audit trail");
- }
- return;
- }
-}
diff --git a/src/api/framework/Infrastructure/Identity/Audit/AuditService.cs b/src/api/framework/Infrastructure/Identity/Audit/AuditService.cs
deleted file mode 100644
index 823cb79576..0000000000
--- a/src/api/framework/Infrastructure/Identity/Audit/AuditService.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using FSH.Framework.Core.Audit;
-using FSH.Framework.Infrastructure.Identity.Persistence;
-using Microsoft.EntityFrameworkCore;
-
-namespace FSH.Framework.Infrastructure.Identity.Audit;
-public class AuditService(IdentityDbContext context) : IAuditService
-{
- public async Task> GetUserTrailsAsync(Guid userId)
- {
- var trails = await context.AuditTrails
- .Where(a => a.UserId == userId)
- .OrderByDescending(a => a.DateTime)
- .Take(250)
- .ToListAsync();
- return trails;
- }
-}
diff --git a/src/api/framework/Infrastructure/Identity/Audit/Endpoints/GetUserAuditTrailEndpoint.cs b/src/api/framework/Infrastructure/Identity/Audit/Endpoints/GetUserAuditTrailEndpoint.cs
deleted file mode 100644
index 78ca37942a..0000000000
--- a/src/api/framework/Infrastructure/Identity/Audit/Endpoints/GetUserAuditTrailEndpoint.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using FSH.Framework.Core.Audit;
-using FSH.Framework.Infrastructure.Auth.Policy;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Identity.Audit.Endpoints;
-
-public static class GetUserAuditTrailEndpoint
-{
- internal static RouteHandlerBuilder MapGetUserAuditTrailEndpoint(this IEndpointRouteBuilder endpoints)
- {
- return endpoints.MapGet("/{id:guid}/audit-trails", (Guid id, IAuditService service) =>
- {
- return service.GetUserTrailsAsync(id);
- })
- .WithName(nameof(GetUserAuditTrailEndpoint))
- .WithSummary("Get user's audit trail details")
- .RequirePermission("Permissions.AuditTrails.View")
- .WithDescription("Get user's audit trail details.");
- }
-}
diff --git a/src/api/framework/Infrastructure/Identity/Extensions.cs b/src/api/framework/Infrastructure/Identity/Extensions.cs
deleted file mode 100644
index 4d20559295..0000000000
--- a/src/api/framework/Infrastructure/Identity/Extensions.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using FSH.Framework.Core.Audit;
-using FSH.Framework.Core.Identity.Roles;
-using FSH.Framework.Core.Identity.Tokens;
-using FSH.Framework.Core.Identity.Users.Abstractions;
-using FSH.Framework.Core.Persistence;
-using FSH.Framework.Infrastructure.Auth;
-using FSH.Framework.Infrastructure.Identity.Audit;
-using FSH.Framework.Infrastructure.Identity.Persistence;
-using FSH.Framework.Infrastructure.Identity.Roles;
-using FSH.Framework.Infrastructure.Identity.Roles.Endpoints;
-using FSH.Framework.Infrastructure.Identity.Tokens;
-using FSH.Framework.Infrastructure.Identity.Tokens.Endpoints;
-using FSH.Framework.Infrastructure.Identity.Users;
-using FSH.Framework.Infrastructure.Identity.Users.Endpoints;
-using FSH.Framework.Infrastructure.Identity.Users.Services;
-using FSH.Framework.Infrastructure.Persistence;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Routing;
-using Microsoft.Extensions.DependencyInjection;
-using IdentityConstants = FSH.Starter.Shared.Authorization.IdentityConstants;
-
-namespace FSH.Framework.Infrastructure.Identity;
-internal static class Extensions
-{
- internal static IServiceCollection ConfigureIdentity(this IServiceCollection services)
- {
- ArgumentNullException.ThrowIfNull(services);
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped(sp => (ICurrentUserInitializer)sp.GetRequiredService());
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.BindDbContext();
- services.AddScoped();
- services.AddIdentity(options =>
- {
- options.Password.RequiredLength = IdentityConstants.PasswordLength;
- options.Password.RequireDigit = false;
- options.Password.RequireLowercase = false;
- options.Password.RequireNonAlphanumeric = false;
- options.Password.RequireUppercase = false;
- options.User.RequireUniqueEmail = true;
- })
- .AddEntityFrameworkStores()
- .AddDefaultTokenProviders();
- return services;
- }
-
- public static IEndpointRouteBuilder MapIdentityEndpoints(this IEndpointRouteBuilder app)
- {
- var users = app.MapGroup("api/users").WithTags("users");
- users.MapUserEndpoints();
-
- var tokens = app.MapGroup("api/token").WithTags("token");
- tokens.MapTokenEndpoints();
-
- var roles = app.MapGroup("api/roles").WithTags("roles");
- roles.MapRoleEndpoints();
-
- return app;
- }
-}
diff --git a/src/api/framework/Infrastructure/Identity/Roles/Endpoints/Extensions.cs b/src/api/framework/Infrastructure/Identity/Roles/Endpoints/Extensions.cs
deleted file mode 100644
index b899bb362c..0000000000
--- a/src/api/framework/Infrastructure/Identity/Roles/Endpoints/Extensions.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Identity.Roles.Endpoints;
-
-internal static class Extensions
-{
- public static IEndpointRouteBuilder MapRoleEndpoints(this IEndpointRouteBuilder app)
- {
- app.MapGetRoleEndpoint();
- app.MapGetRolesEndpoint();
- app.MapDeleteRoleEndpoint();
- app.MapCreateOrUpdateRoleEndpoint();
- app.MapGetRolePermissionsEndpoint();
- app.MapUpdateRolePermissionsEndpoint();
- return app;
- }
-}
-
diff --git a/src/api/framework/Infrastructure/Identity/Tokens/Endpoints/RefreshTokenEndpoint.cs b/src/api/framework/Infrastructure/Identity/Tokens/Endpoints/RefreshTokenEndpoint.cs
deleted file mode 100644
index a8f27128ba..0000000000
--- a/src/api/framework/Infrastructure/Identity/Tokens/Endpoints/RefreshTokenEndpoint.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using FSH.Framework.Core.Identity.Tokens;
-using FSH.Framework.Core.Identity.Tokens.Features.Refresh;
-using FSH.Starter.Shared.Authorization;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Identity.Tokens.Endpoints;
-public static class RefreshTokenEndpoint
-{
- internal static RouteHandlerBuilder MapRefreshTokenEndpoint(this IEndpointRouteBuilder endpoints)
- {
- return endpoints.MapPost("/refresh", (RefreshTokenCommand request,
- [FromHeader(Name = TenantConstants.Identifier)] string tenant,
- ITokenService service,
- HttpContext context,
- CancellationToken cancellationToken) =>
- {
- string ip = context.GetIpAddress();
- return service.RefreshTokenAsync(request, ip!, cancellationToken);
- })
- .WithName(nameof(RefreshTokenEndpoint))
- .WithSummary("refresh JWTs")
- .WithDescription("refresh JWTs")
- .AllowAnonymous();
- }
-}
diff --git a/src/api/framework/Infrastructure/Identity/Tokens/Endpoints/TokenGenerationEndpoint.cs b/src/api/framework/Infrastructure/Identity/Tokens/Endpoints/TokenGenerationEndpoint.cs
deleted file mode 100644
index e0bbc4796e..0000000000
--- a/src/api/framework/Infrastructure/Identity/Tokens/Endpoints/TokenGenerationEndpoint.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using FSH.Framework.Core.Identity.Tokens;
-using FSH.Framework.Core.Identity.Tokens.Features.Generate;
-using FSH.Starter.Shared.Authorization;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Identity.Tokens.Endpoints;
-public static class TokenGenerationEndpoint
-{
- internal static RouteHandlerBuilder MapTokenGenerationEndpoint(this IEndpointRouteBuilder endpoints)
- {
- return endpoints.MapPost("/", (TokenGenerationCommand request,
- [FromHeader(Name = TenantConstants.Identifier)] string tenant,
- ITokenService service,
- HttpContext context,
- CancellationToken cancellationToken) =>
- {
- string ip = context.GetIpAddress();
- return service.GenerateTokenAsync(request, ip!, cancellationToken);
- })
- .WithName(nameof(TokenGenerationEndpoint))
- .WithSummary("generate JWTs")
- .WithDescription("generate JWTs")
- .AllowAnonymous();
- }
-}
diff --git a/src/api/framework/Infrastructure/Identity/Users/Endpoints/AssignRolesToUserEndpoint.cs b/src/api/framework/Infrastructure/Identity/Users/Endpoints/AssignRolesToUserEndpoint.cs
deleted file mode 100644
index 161fe18e8f..0000000000
--- a/src/api/framework/Infrastructure/Identity/Users/Endpoints/AssignRolesToUserEndpoint.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using FluentValidation;
-using FSH.Framework.Core.Identity.Users.Abstractions;
-using FSH.Framework.Core.Identity.Users.Features.AssignUserRole;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Identity.Users.Endpoints;
-public static class AssignRolesToUserEndpoint
-{
- internal static RouteHandlerBuilder MapAssignRolesToUserEndpoint(this IEndpointRouteBuilder endpoints)
- {
- return endpoints.MapPost("/{id:guid}/roles", async (AssignUserRoleCommand command,
- HttpContext context,
- string id,
- IUserService userService,
- CancellationToken cancellationToken) =>
- {
-
- var message = await userService.AssignRolesAsync(id, command, cancellationToken);
- return Results.Ok(message);
- })
- .WithName(nameof(AssignRolesToUserEndpoint))
- .WithSummary("assign roles")
- .WithDescription("assign roles");
- }
-
-}
diff --git a/src/api/framework/Infrastructure/Identity/Users/Endpoints/Extensions.cs b/src/api/framework/Infrastructure/Identity/Users/Endpoints/Extensions.cs
deleted file mode 100644
index cbc311c6be..0000000000
--- a/src/api/framework/Infrastructure/Identity/Users/Endpoints/Extensions.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using FSH.Framework.Infrastructure.Identity.Audit.Endpoints;
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Identity.Users.Endpoints;
-internal static class Extensions
-{
- public static IEndpointRouteBuilder MapUserEndpoints(this IEndpointRouteBuilder app)
- {
- app.MapRegisterUserEndpoint();
- app.MapSelfRegisterUserEndpoint();
- app.MapUpdateUserEndpoint();
- app.MapGetUsersListEndpoint();
- app.MapDeleteUserEndpoint();
- app.MapForgotPasswordEndpoint();
- app.MapChangePasswordEndpoint();
- app.MapResetPasswordEndpoint();
- app.MapGetMeEndpoint();
- app.MapGetUserEndpoint();
- app.MapGetCurrentUserPermissionsEndpoint();
- app.ToggleUserStatusEndpointEndpoint();
- app.MapAssignRolesToUserEndpoint();
- app.MapGetUserRolesEndpoint();
- app.MapGetUserAuditTrailEndpoint();
- app.MapConfirmEmailEndpoint();
- return app;
- }
-}
diff --git a/src/api/framework/Infrastructure/Storage/Files/Extension.cs b/src/api/framework/Infrastructure/Storage/Files/Extension.cs
deleted file mode 100644
index 6699f126d2..0000000000
--- a/src/api/framework/Infrastructure/Storage/Files/Extension.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using FSH.Framework.Core.Storage;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.FileProviders;
-
-namespace FSH.Framework.Infrastructure.Storage.Files;
-
-internal static class Extension
-{
- internal static IServiceCollection ConfigureFileStorage(this IServiceCollection services)
- {
- ArgumentNullException.ThrowIfNull(services);
- services.AddTransient();
-
- return services;
- }
-
- internal static IApplicationBuilder UseFileStorage(this IApplicationBuilder app) =>
- app.UseStaticFiles(new StaticFileOptions()
- {
- FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "Files")),
- RequestPath = new PathString("/Files")
- });
-}
diff --git a/src/api/framework/Infrastructure/Storage/Files/LocalFileStorageService.cs b/src/api/framework/Infrastructure/Storage/Files/LocalFileStorageService.cs
deleted file mode 100644
index 16b786da6f..0000000000
--- a/src/api/framework/Infrastructure/Storage/Files/LocalFileStorageService.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
-using FSH.Framework.Core.Origin;
-using FSH.Framework.Core.Storage;
-using FSH.Framework.Core.Storage.File;
-using FSH.Framework.Core.Storage.File.Features;
-using FSH.Framework.Infrastructure.Common.Extensions;
-using Microsoft.Extensions.Options;
-namespace FSH.Framework.Infrastructure.Storage.Files
-{
- public class LocalFileStorageService(IOptions originSettings) : IStorageService
- {
- public async Task UploadAsync(FileUploadCommand? request, FileType supportedFileType, CancellationToken cancellationToken = default)
- where T : class
- {
- if (request == null || request.Data == null)
- {
- return null!;
- }
-
- if (request.Extension is null || !supportedFileType.GetDescriptionList().Contains(request.Extension.ToLower(System.Globalization.CultureInfo.CurrentCulture)))
- throw new InvalidOperationException("File Format Not Supported.");
- if (request.Name is null)
- throw new InvalidOperationException("Name is required.");
-
- string base64Data = Regex.Match(request.Data, "data:image/(?.+?),(?.+)").Groups["data"].Value;
-
- var streamData = new MemoryStream(Convert.FromBase64String(base64Data));
- if (streamData.Length > 0)
- {
- string folder = typeof(T).Name;
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- folder = folder.Replace(@"\", "/", StringComparison.Ordinal);
- }
-
- string folderName = supportedFileType switch
- {
- FileType.Image => Path.Combine("assets", "images", folder),
- _ => Path.Combine("assets", "others", folder),
- };
- string pathToSave = Path.Combine(Directory.GetCurrentDirectory(), folderName);
- Directory.CreateDirectory(pathToSave);
-
- string fileName = request.Name.Trim('"');
- fileName = RemoveSpecialCharacters(fileName);
- fileName = fileName.ReplaceWhitespace("-");
- fileName += request.Extension.Trim();
- string fullPath = Path.Combine(pathToSave, fileName);
- string dbPath = Path.Combine(folderName, fileName);
- if (File.Exists(dbPath))
- {
- dbPath = NextAvailableFilename(dbPath);
- fullPath = NextAvailableFilename(fullPath);
- }
-
- using var stream = new FileStream(fullPath, FileMode.Create);
- await streamData.CopyToAsync(stream, cancellationToken);
- var path = dbPath.Replace("\\", "/", StringComparison.Ordinal);
- var imageUri = new Uri(originSettings.Value.OriginUrl!, path);
- return imageUri;
- }
- else
- {
- return null!;
- }
- }
-
- public static string RemoveSpecialCharacters(string str)
- {
- return Regex.Replace(str, "[^a-zA-Z0-9_.]+", string.Empty, RegexOptions.Compiled);
- }
-
- public void Remove(Uri? path)
- {
- var pathString = path!.ToString();
- if (File.Exists(pathString))
- {
- File.Delete(pathString);
- }
- }
-
- private const string NumberPattern = "-{0}";
-
- private static string NextAvailableFilename(string path)
- {
- if (!File.Exists(path))
- {
- return path;
- }
-
- if (Path.HasExtension(path))
- {
- return GetNextFilename(path.Insert(path.LastIndexOf(Path.GetExtension(path), StringComparison.Ordinal), NumberPattern));
- }
-
- return GetNextFilename(path + NumberPattern);
- }
-
- private static string GetNextFilename(string pattern)
- {
- string tmp = string.Format(pattern, 1);
-
- if (!File.Exists(tmp))
- {
- return tmp;
- }
-
- int min = 1, max = 2;
-
- while (File.Exists(string.Format(pattern, max)))
- {
- min = max;
- max *= 2;
- }
-
- while (max != min + 1)
- {
- int pivot = (max + min) / 2;
- if (File.Exists(string.Format(pattern, pivot)))
- {
- min = pivot;
- }
- else
- {
- max = pivot;
- }
- }
-
- return string.Format(pattern, max);
- }
- }
-}
diff --git a/src/api/framework/Infrastructure/Tenant/Abstractions/IFshTenantInfo.cs b/src/api/framework/Infrastructure/Tenant/Abstractions/IFshTenantInfo.cs
deleted file mode 100644
index 843820200f..0000000000
--- a/src/api/framework/Infrastructure/Tenant/Abstractions/IFshTenantInfo.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Finbuckle.MultiTenant.Abstractions;
-
-namespace FSH.Framework.Infrastructure.Tenant.Abstractions;
-public interface IFshTenantInfo : ITenantInfo
-{
- string? ConnectionString { get; set; }
-}
diff --git a/src/api/framework/Infrastructure/Tenant/Endpoints/ActivateTenantEndpoint.cs b/src/api/framework/Infrastructure/Tenant/Endpoints/ActivateTenantEndpoint.cs
deleted file mode 100644
index 4f2f24f87b..0000000000
--- a/src/api/framework/Infrastructure/Tenant/Endpoints/ActivateTenantEndpoint.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using FSH.Framework.Core.Tenant.Features.ActivateTenant;
-using FSH.Framework.Infrastructure.Auth.Policy;
-using MediatR;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Tenant.Endpoints;
-public static class ActivateTenantEndpoint
-{
- internal static RouteHandlerBuilder MapActivateTenantEndpoint(this IEndpointRouteBuilder endpoints)
- {
- return endpoints.MapPost("/{id}/activate", (ISender mediator, string id) => mediator.Send(new ActivateTenantCommand(id)))
- .WithName(nameof(ActivateTenantEndpoint))
- .WithSummary("activate tenant")
- .RequirePermission("Permissions.Tenants.Update")
- .WithDescription("activate tenant");
- }
-}
diff --git a/src/api/framework/Infrastructure/Tenant/Endpoints/CreateTenantEndpoint.cs b/src/api/framework/Infrastructure/Tenant/Endpoints/CreateTenantEndpoint.cs
deleted file mode 100644
index 51d8a9f6fd..0000000000
--- a/src/api/framework/Infrastructure/Tenant/Endpoints/CreateTenantEndpoint.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using FSH.Framework.Core.Tenant.Features.CreateTenant;
-using FSH.Framework.Infrastructure.Auth.Policy;
-using MediatR;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Tenant.Endpoints;
-public static class CreateTenantEndpoint
-{
- internal static RouteHandlerBuilder MapRegisterTenantEndpoint(this IEndpointRouteBuilder endpoints)
- {
- return endpoints.MapPost("/", (CreateTenantCommand request, ISender mediator) => mediator.Send(request))
- .WithName(nameof(CreateTenantEndpoint))
- .WithSummary("creates a tenant")
- .RequirePermission("Permissions.Tenants.Create")
- .WithDescription("creates a tenant");
- }
-}
diff --git a/src/api/framework/Infrastructure/Tenant/Endpoints/Extensions.cs b/src/api/framework/Infrastructure/Tenant/Endpoints/Extensions.cs
deleted file mode 100644
index bc88511001..0000000000
--- a/src/api/framework/Infrastructure/Tenant/Endpoints/Extensions.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Tenant.Endpoints;
-public static class Extensions
-{
- public static IEndpointRouteBuilder MapTenantEndpoints(this IEndpointRouteBuilder app)
- {
- var tenantGroup = app.MapGroup("api/tenants").WithTags("tenants");
- tenantGroup.MapRegisterTenantEndpoint();
- tenantGroup.MapGetTenantsEndpoint();
- tenantGroup.MapGetTenantByIdEndpoint();
- tenantGroup.MapUpgradeTenantSubscriptionEndpoint();
- tenantGroup.MapActivateTenantEndpoint();
- tenantGroup.MapDisableTenantEndpoint();
- return app;
- }
-}
diff --git a/src/api/framework/Infrastructure/Tenant/Endpoints/GetTenantsEndpoint.cs b/src/api/framework/Infrastructure/Tenant/Endpoints/GetTenantsEndpoint.cs
deleted file mode 100644
index 1bf590deb4..0000000000
--- a/src/api/framework/Infrastructure/Tenant/Endpoints/GetTenantsEndpoint.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using FSH.Framework.Core.Tenant.Features.GetTenants;
-using FSH.Framework.Infrastructure.Auth.Policy;
-using MediatR;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Tenant.Endpoints;
-public static class GetTenantsEndpoint
-{
- internal static RouteHandlerBuilder MapGetTenantsEndpoint(this IEndpointRouteBuilder endpoints)
- {
- return endpoints.MapGet("/", (ISender mediator) => mediator.Send(new GetTenantsQuery()))
- .WithName(nameof(GetTenantsEndpoint))
- .WithSummary("get tenants")
- .RequirePermission("Permissions.Tenants.View")
- .WithDescription("get tenants");
- }
-}
diff --git a/src/api/framework/Infrastructure/Tenant/Endpoints/UpgradeSubscriptionEndpoint.cs b/src/api/framework/Infrastructure/Tenant/Endpoints/UpgradeSubscriptionEndpoint.cs
deleted file mode 100644
index 182330544f..0000000000
--- a/src/api/framework/Infrastructure/Tenant/Endpoints/UpgradeSubscriptionEndpoint.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using FSH.Framework.Core.Tenant.Features.UpgradeSubscription;
-using FSH.Framework.Infrastructure.Auth.Policy;
-using MediatR;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
-
-namespace FSH.Framework.Infrastructure.Tenant.Endpoints;
-
-public static class UpgradeSubscriptionEndpoint
-{
- internal static RouteHandlerBuilder MapUpgradeTenantSubscriptionEndpoint(this IEndpointRouteBuilder endpoints)
- {
- return endpoints.MapPost("/upgrade", (UpgradeSubscriptionCommand command, ISender mediator) => mediator.Send(command))
- .WithName(nameof(UpgradeSubscriptionEndpoint))
- .WithSummary("upgrade tenant subscription")
- .RequirePermission("Permissions.Tenants.Update")
- .WithDescription("upgrade tenant subscription");
- }
-}
diff --git a/src/api/framework/Infrastructure/Tenant/Extensions.cs b/src/api/framework/Infrastructure/Tenant/Extensions.cs
deleted file mode 100644
index f7fea460cd..0000000000
--- a/src/api/framework/Infrastructure/Tenant/Extensions.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-using Finbuckle.MultiTenant;
-using Finbuckle.MultiTenant.Abstractions;
-using Finbuckle.MultiTenant.Stores.DistributedCacheStore;
-using FSH.Framework.Core.Persistence;
-using FSH.Framework.Core.Tenant.Abstractions;
-using FSH.Framework.Infrastructure.Persistence;
-using FSH.Framework.Infrastructure.Persistence.Services;
-using FSH.Framework.Infrastructure.Tenant.Persistence;
-using FSH.Framework.Infrastructure.Tenant.Services;
-using FSH.Starter.Shared.Authorization;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using Serilog;
-
-namespace FSH.Framework.Infrastructure.Tenant;
-internal static class Extensions
-{
- public static IServiceCollection ConfigureMultitenancy(this IServiceCollection services)
- {
- ArgumentNullException.ThrowIfNull(services);
- services.AddTransient();
- services.BindDbContext();
- services
- .AddMultiTenant(config =>
- {
- // to save database calls to resolve tenant
- // this was happening for every request earlier, leading to ineffeciency
- config.Events.OnTenantResolveCompleted = async (context) =>
- {
- if (context.MultiTenantContext.StoreInfo is null) return;
- if (context.MultiTenantContext.StoreInfo.StoreType != typeof(DistributedCacheStore))
- {
- var sp = ((HttpContext)context.Context!).RequestServices;
- var distributedCacheStore = sp
- .GetService>>()!
- .FirstOrDefault(s => s.GetType() == typeof(DistributedCacheStore));
-
- await distributedCacheStore!.TryAddAsync(context.MultiTenantContext.TenantInfo!);
- }
- await Task.FromResult(0);
- };
- })
- .WithClaimStrategy(FshClaims.Tenant)
- .WithHeaderStrategy(TenantConstants.Identifier)
- .WithDelegateStrategy(async context =>
- {
- if (context is not HttpContext httpContext)
- return null;
- if (!httpContext.Request.Query.TryGetValue("tenant", out var tenantIdentifier) || string.IsNullOrEmpty(tenantIdentifier))
- return null;
- return await Task.FromResult(tenantIdentifier.ToString());
- })
- .WithDistributedCacheStore(TimeSpan.FromMinutes(60))
- .WithEFCoreStore();
- services.AddScoped();
- return services;
- }
-
- public static WebApplication UseMultitenancy(this WebApplication app)
- {
- ArgumentNullException.ThrowIfNull(app);
- app.UseMultiTenant();
-
- // set up tenant store
- var tenants = TenantStoreSetup(app);
-
- // set up tenant databases
- app.SetupTenantDatabases(tenants);
-
- return app;
- }
-
- private static IApplicationBuilder SetupTenantDatabases(this IApplicationBuilder app, IEnumerable tenants)
- {
- foreach (var tenant in tenants)
- {
- // create a scope for tenant
- using var tenantScope = app.ApplicationServices.CreateScope();
-
- //set current tenant so that the right connection string is used
- tenantScope.ServiceProvider.GetRequiredService()
- .MultiTenantContext = new MultiTenantContext()
- {
- TenantInfo = tenant
- };
-
- // using the scope, perform migrations / seeding
- var initializers = tenantScope.ServiceProvider.GetServices();
- foreach (var initializer in initializers)
- {
- initializer.MigrateAsync(CancellationToken.None).Wait();
- initializer.SeedAsync(CancellationToken.None).Wait();
- }
- }
- return app;
- }
-
- private static IEnumerable TenantStoreSetup(IApplicationBuilder app)
- {
- var scope = app.ApplicationServices.CreateScope();
-
- // tenant master schema migration
- var tenantDbContext = scope.ServiceProvider.GetRequiredService();
- if (tenantDbContext.Database.GetPendingMigrations().Any())
- {
- tenantDbContext.Database.Migrate();
- Log.Information("applied database migrations for tenant module");
- }
-
- // default tenant seeding
- if (tenantDbContext.TenantInfo.Find(TenantConstants.Root.Id) is null)
- {
- var rootTenant = new FshTenantInfo(
- TenantConstants.Root.Id,
- TenantConstants.Root.Name,
- string.Empty,
- TenantConstants.Root.EmailAddress);
-
- rootTenant.SetValidity(DateTime.UtcNow.AddYears(1));
- tenantDbContext.TenantInfo.Add(rootTenant);
- tenantDbContext.SaveChanges();
- Log.Information("configured default tenant data");
- }
-
- // get all tenants from store
- var tenantStore = scope.ServiceProvider.GetRequiredService>();
- var tenants = tenantStore.GetAllAsync().Result;
-
- //dispose scope
- scope.Dispose();
-
- return tenants;
- }
-}
diff --git a/src/framework/.editorconfig b/src/framework/.editorconfig
new file mode 100644
index 0000000000..6380dfb67b
--- /dev/null
+++ b/src/framework/.editorconfig
@@ -0,0 +1,297 @@
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+root = true
+
+# C# files
+[*.cs]
+
+#### 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 = never_if_unnecessary:suggestion
+dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:suggestion
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
+dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion
+
+# 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:suggestion
+dotnet_style_prefer_collection_expression = when_types_loosely_match
+dotnet_style_prefer_compound_assignment = true:error
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+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_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:error
+
+# 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:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:none
+csharp_style_expression_bodied_local_functions = true:error
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# 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:error
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
+csharp_style_prefer_readonly_struct = true
+csharp_style_prefer_readonly_struct_member = true
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:error
+csharp_prefer_system_threading_lock = true:suggestion
+csharp_style_namespace_declarations = file_scoped:error
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_style_prefer_top_level_statements = true:silent
+
+# 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_index_operator = true:silent
+csharp_style_prefer_local_over_anonymous_function = true
+csharp_style_prefer_null_check_over_type_check = true
+csharp_style_prefer_range_operator = true:silent
+csharp_style_prefer_tuple_swap = 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:error
+
+# 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
+
+# Static code analysis rule customizations
+
+# CA2007: Consider calling ConfigureAwait on the awaited task
+dotnet_diagnostic.CA2007.severity = none
+
+# CA1515: Consider making public types internal
+dotnet_diagnostic.CA1515.severity = none
+
+# CA1724: Type name conflicts with namespace
+dotnet_diagnostic.CA1724.severity = none
+
+# S2094: Classes should not be empty
+dotnet_diagnostic.S2094.severity = none
+
+# IDE0058: Expression value is never used
+dotnet_diagnostic.IDE0058.severity = none
+
+# IDE0005: Remove unnecessary usings/imports
+dotnet_diagnostic.IDE0005.severity = none
+
+# CA1062: Validate arguments of public methods
+dotnet_diagnostic.CA1062.severity = none
+
+# S125: Remove commented out code
+dotnet_diagnostic.S125.severity = none
+
+# IDE0053: Use expression body for lambda expression
+dotnet_diagnostic.IDE0053.severity = none
+
+# IDE0130: Namespace should match project structure
+dotnet_diagnostic.IDE0130.severity = none
+dotnet_diagnostic.CA1032.severity = none
+dotnet_diagnostic.S2326.severity = none
+
+[*.{cs,vb}]
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_diagnostic.CA1034.severity = none
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_diagnostic.CA1711.severity = none
+dotnet_diagnostic.CA1040.severity = none
+dotnet_diagnostic.CA1707.severity = none
\ No newline at end of file
diff --git a/src/framework/Directory.Build.props b/src/framework/Directory.Build.props
new file mode 100644
index 0000000000..80521c53b1
--- /dev/null
+++ b/src/framework/Directory.Build.props
@@ -0,0 +1,47 @@
+
+
+
+ net9.0
+
+
+ latest
+ enable
+ enable
+
+
+ false
+ false
+ true
+ latest
+ AllEnabledByDefault
+
+
+ true
+ 1591
+
+
+ 3.0.0-alpha;latest
+
+
+ true
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+ Mukesh Murugan
+ FullStackHero
+ 3.0.0
+ https://github.com/fullstackhero/dotnet-starter-kit
+ FSH;Modular;CQRS;VerticalSlice
+
+ true
+
+
diff --git a/src/Directory.Packages.props b/src/framework/Directory.Packages.props
similarity index 75%
rename from src/Directory.Packages.props
rename to src/framework/Directory.Packages.props
index 7015fadbab..ea37c17f10 100644
--- a/src/Directory.Packages.props
+++ b/src/framework/Directory.Packages.props
@@ -9,57 +9,62 @@
true
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
-
-
+
+
+
-
-
+
+
-
+
-
-
-
+
@@ -73,7 +78,7 @@
-
+
@@ -93,4 +98,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/framework/FSH.Framework.sln b/src/framework/FSH.Framework.sln
new file mode 100644
index 0000000000..c7d50b9650
--- /dev/null
+++ b/src/framework/FSH.Framework.sln
@@ -0,0 +1,131 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Common.Infrastructure", "Modules\Common\Modules.Common.Infrastructure\Modules.Common.Infrastructure.csproj", "{60DF219E-E1EB-428E-BB1F-F0342D42699F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Common.Shared", "Modules\Common\Modules.Common.Shared\Modules.Common.Shared.csproj", "{09380C15-F138-4305-A595-F4C7E16B6E78}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ Directory.Build.props = Directory.Build.props
+ Directory.Packages.props = Directory.Packages.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PlayGround", "PlayGround", "{C9FFFCC7-8CEA-4890-9B6D-91D815A0B04B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Migrations.PostgreSQL", "PlayGround\Migrations\PostgreSQL\Migrations.PostgreSQL.csproj", "{EEF3610C-BF3C-DA23-0AA8-FED171CE30A2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayGround.Api", "PlayGround\PlayGround.Api\PlayGround.Api.csproj", "{2527B9BA-2168-ED23-7E12-CF3C7C901279}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{83139AD6-D37E-4209-84A2-5E9023CEDA78}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tenant", "Tenant", "{297D1E34-BA90-4B75-80F4-646CF1FF59CC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Tenant", "Modules\Tenant\Modules.Tenant\Modules.Tenant.csproj", "{FE8B966D-94E8-EF2D-535B-DC58B1D93B2C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Tenant.Contracts", "Modules\Tenant\Modules.Tenant.Contracts\Modules.Tenant.Contracts.csproj", "{13FDC8C1-8926-DBD6-4915-885E6B5067C2}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{020A76FA-D28D-48AA-AFAF-D89E3FE55327}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Identity", "Modules\Identity\Modules.Identity\Modules.Identity.csproj", "{A3A85996-F456-91C2-FABA-DD56D9ECCFE0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Identity.Contracts", "Modules\Identity\Modules.Identity.Contracts\Modules.Identity.Contracts.csproj", "{14E87A21-A0F3-867D-9628-863DF0EFC8B0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Common.Core", "Modules\Common\Modules.Common.Core\Modules.Common.Core.csproj", "{0069E711-9C02-C4FC-1C3D-84B5BE0D0998}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Auditing", "Auditing", "{C39F3653-FC44-4E54-A13F-ECE30DDBE888}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Auditing", "Modules\Auditing\Modules.Auditing\Modules.Auditing.csproj", "{A47D120F-EA39-7479-5FF1-E8F0B9524F1B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Auditing.Contracts", "Modules\Auditing\Modules.Auditing.Contracts\Modules.Auditing.Contracts.csproj", "{84460CBC-D11D-D584-6F3C-EEDC6966689F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{F631F5FA-FCC7-4133-B7DE-0A7E2ED97169}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Architecture.Tests", "Tests\Architecture.Tests\Architecture.Tests.csproj", "{55291917-A598-4DC0-86B4-BD350D8F7C9A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {60DF219E-E1EB-428E-BB1F-F0342D42699F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {60DF219E-E1EB-428E-BB1F-F0342D42699F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {60DF219E-E1EB-428E-BB1F-F0342D42699F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {60DF219E-E1EB-428E-BB1F-F0342D42699F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {09380C15-F138-4305-A595-F4C7E16B6E78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {09380C15-F138-4305-A595-F4C7E16B6E78}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {09380C15-F138-4305-A595-F4C7E16B6E78}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {09380C15-F138-4305-A595-F4C7E16B6E78}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EEF3610C-BF3C-DA23-0AA8-FED171CE30A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EEF3610C-BF3C-DA23-0AA8-FED171CE30A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EEF3610C-BF3C-DA23-0AA8-FED171CE30A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EEF3610C-BF3C-DA23-0AA8-FED171CE30A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2527B9BA-2168-ED23-7E12-CF3C7C901279}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2527B9BA-2168-ED23-7E12-CF3C7C901279}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2527B9BA-2168-ED23-7E12-CF3C7C901279}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2527B9BA-2168-ED23-7E12-CF3C7C901279}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FE8B966D-94E8-EF2D-535B-DC58B1D93B2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FE8B966D-94E8-EF2D-535B-DC58B1D93B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FE8B966D-94E8-EF2D-535B-DC58B1D93B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FE8B966D-94E8-EF2D-535B-DC58B1D93B2C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {13FDC8C1-8926-DBD6-4915-885E6B5067C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {13FDC8C1-8926-DBD6-4915-885E6B5067C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {13FDC8C1-8926-DBD6-4915-885E6B5067C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {13FDC8C1-8926-DBD6-4915-885E6B5067C2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A3A85996-F456-91C2-FABA-DD56D9ECCFE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A3A85996-F456-91C2-FABA-DD56D9ECCFE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A3A85996-F456-91C2-FABA-DD56D9ECCFE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A3A85996-F456-91C2-FABA-DD56D9ECCFE0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {14E87A21-A0F3-867D-9628-863DF0EFC8B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {14E87A21-A0F3-867D-9628-863DF0EFC8B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {14E87A21-A0F3-867D-9628-863DF0EFC8B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {14E87A21-A0F3-867D-9628-863DF0EFC8B0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0069E711-9C02-C4FC-1C3D-84B5BE0D0998}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0069E711-9C02-C4FC-1C3D-84B5BE0D0998}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0069E711-9C02-C4FC-1C3D-84B5BE0D0998}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0069E711-9C02-C4FC-1C3D-84B5BE0D0998}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A47D120F-EA39-7479-5FF1-E8F0B9524F1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A47D120F-EA39-7479-5FF1-E8F0B9524F1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A47D120F-EA39-7479-5FF1-E8F0B9524F1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A47D120F-EA39-7479-5FF1-E8F0B9524F1B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {84460CBC-D11D-D584-6F3C-EEDC6966689F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {84460CBC-D11D-D584-6F3C-EEDC6966689F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {84460CBC-D11D-D584-6F3C-EEDC6966689F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {84460CBC-D11D-D584-6F3C-EEDC6966689F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {55291917-A598-4DC0-86B4-BD350D8F7C9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {55291917-A598-4DC0-86B4-BD350D8F7C9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {55291917-A598-4DC0-86B4-BD350D8F7C9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {55291917-A598-4DC0-86B4-BD350D8F7C9A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {60DF219E-E1EB-428E-BB1F-F0342D42699F} = {83139AD6-D37E-4209-84A2-5E9023CEDA78}
+ {09380C15-F138-4305-A595-F4C7E16B6E78} = {83139AD6-D37E-4209-84A2-5E9023CEDA78}
+ {EEF3610C-BF3C-DA23-0AA8-FED171CE30A2} = {C9FFFCC7-8CEA-4890-9B6D-91D815A0B04B}
+ {2527B9BA-2168-ED23-7E12-CF3C7C901279} = {C9FFFCC7-8CEA-4890-9B6D-91D815A0B04B}
+ {83139AD6-D37E-4209-84A2-5E9023CEDA78} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {297D1E34-BA90-4B75-80F4-646CF1FF59CC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {FE8B966D-94E8-EF2D-535B-DC58B1D93B2C} = {297D1E34-BA90-4B75-80F4-646CF1FF59CC}
+ {13FDC8C1-8926-DBD6-4915-885E6B5067C2} = {297D1E34-BA90-4B75-80F4-646CF1FF59CC}
+ {020A76FA-D28D-48AA-AFAF-D89E3FE55327} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {A3A85996-F456-91C2-FABA-DD56D9ECCFE0} = {020A76FA-D28D-48AA-AFAF-D89E3FE55327}
+ {14E87A21-A0F3-867D-9628-863DF0EFC8B0} = {020A76FA-D28D-48AA-AFAF-D89E3FE55327}
+ {0069E711-9C02-C4FC-1C3D-84B5BE0D0998} = {83139AD6-D37E-4209-84A2-5E9023CEDA78}
+ {C39F3653-FC44-4E54-A13F-ECE30DDBE888} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {A47D120F-EA39-7479-5FF1-E8F0B9524F1B} = {C39F3653-FC44-4E54-A13F-ECE30DDBE888}
+ {84460CBC-D11D-D584-6F3C-EEDC6966689F} = {C39F3653-FC44-4E54-A13F-ECE30DDBE888}
+ {55291917-A598-4DC0-86B4-BD350D8F7C9A} = {F631F5FA-FCC7-4133-B7DE-0A7E2ED97169}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {438A0F87-0F3E-43C4-A352-264E0C21F77B}
+ SolutionGuid = {A2A6BABD-325C-4482-8830-41058E5D509D}
+ EndGlobalSection
+EndGlobal
diff --git a/src/framework/Modules/Auditing/Modules.Auditing.Contracts/AuditingConstants.cs b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/AuditingConstants.cs
new file mode 100644
index 0000000000..4b21461fc0
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/AuditingConstants.cs
@@ -0,0 +1,18 @@
+namespace FSH.Framework.Auditing.Contracts;
+
+public static class AuditingConstants
+{
+ public const string SchemaName = "auditing";
+ public const string ModuleName = "Auditing";
+
+ public static class Permissions
+ {
+ public const string View = "Permissions.Auditing.View";
+ }
+
+ public static class Routes
+ {
+ public const string Base = "/v1/auditing";
+ public const string GetUserLogs = $"{Base}/logs/{{userId:guid}}";
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Dtos/TrailDto.cs b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Dtos/TrailDto.cs
new file mode 100644
index 0000000000..059826e875
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Dtos/TrailDto.cs
@@ -0,0 +1,19 @@
+using FSH.Framework.Auditing.Contracts.Enums;
+using System.Collections.ObjectModel;
+
+namespace FSH.Framework.Auditing.Contracts.Dtos;
+public class TrailDto
+{
+ public Guid Id { get; set; }
+ public DateTimeOffset DateTime { get; set; }
+ public Guid UserId { get; set; }
+ public AuditOperation Operation { get; set; }
+ public string Description { get; set; } = default!;
+ public string EntityName { get; set; } = default!;
+
+ // Uncomment if needed later
+ public Dictionary KeyValues { get; set; } = new();
+ public Dictionary OldValues { get; set; } = new();
+ public Dictionary NewValues { get; set; } = new();
+ public Collection ModifiedProperties { get; set; } = new();
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Enums/AuditOperation.cs b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Enums/AuditOperation.cs
new file mode 100644
index 0000000000..ff20b12d48
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Enums/AuditOperation.cs
@@ -0,0 +1,8 @@
+namespace FSH.Framework.Auditing.Contracts.Enums;
+public enum AuditOperation
+{
+ None = 0,
+ Create = 1,
+ Update = 2,
+ Delete = 3
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Events/IntegrationEvents/AuditPublishedEvent.cs b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Events/IntegrationEvents/AuditPublishedEvent.cs
new file mode 100644
index 0000000000..b42a68b331
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Events/IntegrationEvents/AuditPublishedEvent.cs
@@ -0,0 +1,14 @@
+using FSH.Framework.Auditing.Contracts.Dtos;
+using FSH.Framework.Core.Messaging.Events;
+
+namespace FSH.Framework.Auditing.Contracts.Events.IntegrationEvents;
+
+public class AuditPublishedEvent : IEvent
+{
+ public IReadOnlyCollection Trails { get; }
+
+ public AuditPublishedEvent(IReadOnlyCollection trails)
+ {
+ Trails = trails;
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Modules.Auditing.Contracts.csproj b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Modules.Auditing.Contracts.csproj
new file mode 100644
index 0000000000..ca796ab656
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/Modules.Auditing.Contracts.csproj
@@ -0,0 +1,12 @@
+
+
+ FSH.Modules.Auditing.Contracts
+ FSH.Modules.Auditing.Contracts
+
+
+
+
+
+
+
+
diff --git a/src/framework/Modules/Auditing/Modules.Auditing.Contracts/v1/GetUserTrails/GetUserTrailsQuery.cs b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/v1/GetUserTrails/GetUserTrailsQuery.cs
new file mode 100644
index 0000000000..1edab3eaaa
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/v1/GetUserTrails/GetUserTrailsQuery.cs
@@ -0,0 +1,4 @@
+using FSH.Framework.Core.Messaging.CQRS;
+
+namespace FSH.Framework.Auditing.Contracts.v1.GetUserTrails;
+public sealed record GetUserTrailsQuery(Guid UserId) : IQuery;
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing.Contracts/v1/GetUserTrails/GetUserTrailsQueryResponse.cs b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/v1/GetUserTrails/GetUserTrailsQueryResponse.cs
new file mode 100644
index 0000000000..8c9a426519
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing.Contracts/v1/GetUserTrails/GetUserTrailsQueryResponse.cs
@@ -0,0 +1,4 @@
+using FSH.Framework.Auditing.Contracts.Dtos;
+
+namespace FSH.Framework.Auditing.Contracts.v1.GetUserTrails;
+public sealed record GetUserTrailsQueryResponse(IReadOnlyList AuditTrails);
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/AuditingModule.cs b/src/framework/Modules/Auditing/Modules.Auditing/AuditingModule.cs
new file mode 100644
index 0000000000..e1380b67a8
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/AuditingModule.cs
@@ -0,0 +1,43 @@
+using Asp.Versioning;
+using FSH.Framework.Auditing.Data;
+using FSH.Framework.Auditing.Features.v1.GetUserTrails;
+using FSH.Framework.Auditing.Services;
+using FSH.Framework.Core.Persistence;
+using FSH.Framework.Infrastructure.Messaging.CQRS;
+using FSH.Framework.Infrastructure.Persistence;
+using FSH.Modules.Common.Infrastructure.Modules;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace FSH.Modules.Auditing;
+public class AuditingModule : IModule
+{
+ public void AddModule(IServiceCollection services, IConfiguration config)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+
+ services.AddScoped(provider => provider.GetRequiredService());
+ services.RegisterCommandAndQueryHandlers(typeof(AuditingModule).Assembly);
+ services.AddScoped();
+ services.BindDbContext();
+ services.AddScoped();
+ }
+
+ public void ConfigureModule(WebApplication app)
+ {
+ var apiVersionSet = app.NewApiVersionSet()
+ .HasApiVersion(new ApiVersion(1))
+ .ReportApiVersions()
+ .Build();
+
+ var group = app
+ .MapGroup("api/v{version:apiVersion}/auditing")
+ .WithTags("Auditing")
+ .WithOpenApi()
+ .WithApiVersionSet(apiVersionSet);
+
+ group.Map();
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Data/AuditingConfiguration.cs b/src/framework/Modules/Auditing/Modules.Auditing/Data/AuditingConfiguration.cs
new file mode 100644
index 0000000000..ac9eb07277
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Data/AuditingConfiguration.cs
@@ -0,0 +1,18 @@
+using Finbuckle.MultiTenant;
+using FSH.Framework.Auditing.Contracts;
+using FSH.Framework.Auditing.Core.Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace FSH.Framework.Auditing.Data;
+public class TrailConfig : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder
+ .ToTable("Trails", AuditingConstants.SchemaName)
+ .IsMultiTenant();
+
+ builder.HasKey(a => a.Id);
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Data/AuditingDbContext.cs b/src/framework/Modules/Auditing/Modules.Auditing/Data/AuditingDbContext.cs
new file mode 100644
index 0000000000..2f523e44cf
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Data/AuditingDbContext.cs
@@ -0,0 +1,26 @@
+using Finbuckle.MultiTenant.Abstractions;
+using FSH.Framework.Auditing.Core.Entities;
+using FSH.Framework.Core.Messaging.Events;
+using FSH.Framework.Core.Persistence;
+using FSH.Framework.Infrastructure.Persistence;
+using FSH.Framework.Shared.Multitenancy;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Options;
+
+namespace FSH.Framework.Auditing.Data;
+public class AuditingDbContext : FshDbContext, IAuditingDbContext
+{
+ public DbSet Trails { get; set; }
+
+ public AuditingDbContext(
+ IMultiTenantContextAccessor multiTenantContextAccessor,
+ DbContextOptions options,
+ IEventPublisher publisher,
+ IOptions settings) : base(multiTenantContextAccessor, options, publisher, settings) { }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.ApplyConfigurationsFromAssembly(typeof(AuditingDbContext).Assembly);
+ }
+
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Data/AuditingDbInitializer.cs b/src/framework/Modules/Auditing/Modules.Auditing/Data/AuditingDbInitializer.cs
new file mode 100644
index 0000000000..b248631bbc
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Data/AuditingDbInitializer.cs
@@ -0,0 +1,19 @@
+using FSH.Framework.Core.Persistence;
+using Microsoft.EntityFrameworkCore;
+
+namespace FSH.Framework.Auditing.Data;
+public class AuditingDbInitializer(AuditingDbContext context) : IDbInitializer
+{
+ public async Task MigrateAsync(CancellationToken cancellationToken)
+ {
+ if ((await context.Database.GetPendingMigrationsAsync(cancellationToken).ConfigureAwait(false)).Any())
+ {
+ await context.Database.MigrateAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public Task SeedAsync(CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Data/IAuditingDbContext.cs b/src/framework/Modules/Auditing/Modules.Auditing/Data/IAuditingDbContext.cs
new file mode 100644
index 0000000000..28e262da5d
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Data/IAuditingDbContext.cs
@@ -0,0 +1,9 @@
+using FSH.Framework.Auditing.Core.Entities;
+using Microsoft.EntityFrameworkCore;
+
+namespace FSH.Framework.Auditing.Data;
+public interface IAuditingDbContext
+{
+ DbSet Trails { get; }
+ Task SaveChangesAsync(CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/src/api/framework/Infrastructure/Persistence/Interceptors/AuditInterceptor.cs b/src/framework/Modules/Auditing/Modules.Auditing/Data/Interceptors/AuditInterceptor.cs
similarity index 71%
rename from src/api/framework/Infrastructure/Persistence/Interceptors/AuditInterceptor.cs
rename to src/framework/Modules/Auditing/Modules.Auditing/Data/Interceptors/AuditInterceptor.cs
index 6c2d819cac..f56d9de66d 100644
--- a/src/api/framework/Infrastructure/Persistence/Interceptors/AuditInterceptor.cs
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Data/Interceptors/AuditInterceptor.cs
@@ -1,18 +1,17 @@
-using System.Collections.ObjectModel;
-using FSH.Framework.Core.Audit;
+using FSH.Framework.Auditing.Contracts.Dtos;
+using FSH.Framework.Auditing.Contracts.Enums;
+using FSH.Framework.Auditing.Contracts.Events.IntegrationEvents;
using FSH.Framework.Core.Domain;
-using FSH.Framework.Core.Domain.Contracts;
-using FSH.Framework.Core.Identity.Users.Abstractions;
-using FSH.Framework.Infrastructure.Identity.Audit;
-using MediatR;
+using FSH.Framework.Core.ExecutionContext;
+using FSH.Framework.Core.Messaging.Events;
+using FSH.Modules.Common.Core.Domain.Contracts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Diagnostics;
-namespace FSH.Framework.Infrastructure.Persistence.Interceptors;
-public class AuditInterceptor(ICurrentUser currentUser, TimeProvider timeProvider, IPublisher publisher) : SaveChangesInterceptor
+namespace FSH.Framework.Auditing.Data.Interceptors;
+public class AuditInterceptor(ICurrentUser currentUser, TimeProvider timeProvider, IEventPublisher publisher) : SaveChangesInterceptor
{
-
public override ValueTask SavedChangesAsync(SaveChangesCompletedEventData eventData, int result, CancellationToken cancellationToken = default)
{
return base.SavedChangesAsync(eventData, result, cancellationToken);
@@ -26,11 +25,11 @@ public override Task SaveChangesFailedAsync(DbContextErrorEventData eventData, C
public override async ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default)
{
UpdateEntities(eventData.Context);
- await PublishAuditTrailsAsync(eventData);
+ await PublishAuditTrailsAsync(eventData, cancellationToken);
return await base.SavingChangesAsync(eventData, result, cancellationToken);
}
- private async Task PublishAuditTrailsAsync(DbContextEventData eventData)
+ private async Task PublishAuditTrailsAsync(DbContextEventData eventData, CancellationToken cancellationToken)
{
if (eventData.Context == null) return;
eventData.Context.ChangeTracker.DetectChanges();
@@ -39,10 +38,11 @@ private async Task PublishAuditTrailsAsync(DbContextEventData eventData)
foreach (var entry in eventData.Context.ChangeTracker.Entries().Where(x => x.State is EntityState.Added or EntityState.Deleted or EntityState.Modified).ToList())
{
var userId = currentUser.GetUserId();
- var trail = new TrailDto()
+ var audit = new TrailDto()
{
Id = Guid.NewGuid(),
- TableName = entry.Entity.GetType().Name,
+ EntityName = entry.Entity.GetType().Name,
+ Description = entry.Entity.GetType().Name,
UserId = userId,
DateTime = utcNow
};
@@ -56,20 +56,20 @@ private async Task PublishAuditTrailsAsync(DbContextEventData eventData)
string propertyName = property.Metadata.Name;
if (property.Metadata.IsPrimaryKey())
{
- trail.KeyValues[propertyName] = property.CurrentValue;
+ audit.KeyValues[propertyName] = property.CurrentValue;
continue;
}
switch (entry.State)
{
case EntityState.Added:
- trail.Type = TrailType.Create;
- trail.NewValues[propertyName] = property.CurrentValue;
+ audit.Operation = AuditOperation.Create;
+ audit.NewValues[propertyName] = property.CurrentValue;
break;
case EntityState.Deleted:
- trail.Type = TrailType.Delete;
- trail.OldValues[propertyName] = property.OriginalValue;
+ audit.Operation = AuditOperation.Delete;
+ audit.OldValues[propertyName] = property.OriginalValue;
break;
case EntityState.Modified:
@@ -77,17 +77,17 @@ private async Task PublishAuditTrailsAsync(DbContextEventData eventData)
{
if (entry.Entity is ISoftDeletable && property.OriginalValue == null && property.CurrentValue != null)
{
- trail.ModifiedProperties.Add(propertyName);
- trail.Type = TrailType.Delete;
- trail.OldValues[propertyName] = property.OriginalValue;
- trail.NewValues[propertyName] = property.CurrentValue;
+ audit.ModifiedProperties.Add(propertyName);
+ audit.Operation = AuditOperation.Delete;
+ audit.OldValues[propertyName] = property.OriginalValue;
+ audit.NewValues[propertyName] = property.CurrentValue;
}
else if (property.OriginalValue?.Equals(property.CurrentValue) == false)
{
- trail.ModifiedProperties.Add(propertyName);
- trail.Type = TrailType.Update;
- trail.OldValues[propertyName] = property.OriginalValue;
- trail.NewValues[propertyName] = property.CurrentValue;
+ audit.ModifiedProperties.Add(propertyName);
+ audit.Operation = AuditOperation.Update;
+ audit.OldValues[propertyName] = property.OriginalValue;
+ audit.NewValues[propertyName] = property.CurrentValue;
}
else
{
@@ -98,15 +98,10 @@ private async Task PublishAuditTrailsAsync(DbContextEventData eventData)
}
}
- trails.Add(trail);
+ trails.Add(audit);
}
if (trails.Count == 0) return;
- var auditTrails = new Collection();
- foreach (var trail in trails)
- {
- auditTrails.Add(trail.ToAuditTrail());
- }
- await publisher.Publish(new AuditPublishedEvent(auditTrails));
+ await publisher.PublishAsync(new AuditPublishedEvent(trails), cancellationToken);
}
public void UpdateEntities(DbContext? context)
@@ -125,7 +120,7 @@ public void UpdateEntities(DbContext? context)
entry.Entity.LastModifiedBy = currentUser.GetUserId();
entry.Entity.LastModified = utcNow;
}
- if(entry.State is EntityState.Deleted && entry.Entity is ISoftDeletable softDelete)
+ if (entry.State is EntityState.Deleted && entry.Entity is ISoftDeletable softDelete)
{
softDelete.DeletedBy = currentUser.GetUserId();
softDelete.Deleted = utcNow;
@@ -142,4 +137,4 @@ public static bool HasChangedOwnedEntities(this EntityEntry entry) =>
r.TargetEntry != null &&
r.TargetEntry.Metadata.IsOwned() &&
(r.TargetEntry.State == EntityState.Added || r.TargetEntry.State == EntityState.Modified));
-}
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/EventHandlers/AuditPublishedIntegrationEventHandler.cs b/src/framework/Modules/Auditing/Modules.Auditing/EventHandlers/AuditPublishedIntegrationEventHandler.cs
new file mode 100644
index 0000000000..877d22b408
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/EventHandlers/AuditPublishedIntegrationEventHandler.cs
@@ -0,0 +1,41 @@
+using FSH.Framework.Auditing.Contracts.Events.IntegrationEvents;
+using FSH.Framework.Auditing.Data;
+using FSH.Framework.Core.Messaging.Events;
+using FSH.Modules.Auditing.Core.Mappings;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using System.Diagnostics.CodeAnalysis;
+
+namespace FSH.Framework.Auditing.EventHandlers;
+
+[SuppressMessage("Performance", "CA1848")]
+[SuppressMessage("Design", "CA1031")]
+public class AuditPublishedIntegrationEventHandler(
+ ILogger logger,
+ IAuditingDbContext context)
+ : IEventHandler
+{
+ public async Task HandleAsync(AuditPublishedEvent notification, CancellationToken cancellationToken = default)
+ {
+ if (notification.Trails == null || notification.Trails.Count == 0)
+ {
+ logger.LogDebug("No audit trails to persist.");
+ return;
+ }
+ try
+ {
+ var trailEntities = notification.Trails.ToEntityList();
+ await context.Trails.AddRangeAsync(trailEntities, cancellationToken);
+ await context.SaveChangesAsync(cancellationToken);
+ logger.LogInformation("Persisted {Count} audit trail(s).", notification.Trails.Count);
+ }
+ catch (DbUpdateException ex)
+ {
+ logger.LogError(ex, "Database update error while saving audit trails.");
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Unexpected error while saving audit trails.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Features/Entities/Trail.cs b/src/framework/Modules/Auditing/Modules.Auditing/Features/Entities/Trail.cs
new file mode 100644
index 0000000000..2ced3612c9
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Features/Entities/Trail.cs
@@ -0,0 +1,69 @@
+using FSH.Framework.Auditing.Contracts.Enums;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json;
+
+namespace FSH.Framework.Auditing.Core.Entities;
+
+public class Trail
+{
+ public Guid Id { get; set; }
+ public Guid UserId { get; set; }
+ public DateTimeOffset DateTime { get; set; }
+ public AuditOperation Operation { get; set; }
+ public string Description { get; set; } = default!;
+ public string? EntityName { get; set; }
+
+ // Backing fields for JSON storage (persisted)
+ public string KeyValuesJson { get; private set; } = "{}";
+ public string OldValuesJson { get; private set; } = "{}";
+ public string NewValuesJson { get; private set; } = "{}";
+ public string ModifiedPropertiesJson { get; private set; } = "[]";
+
+ // Domain-facing properties (not mapped)
+ [NotMapped]
+ public IReadOnlyDictionary KeyValues =>
+ DeserializeDict(KeyValuesJson);
+
+ [NotMapped]
+ public IReadOnlyDictionary OldValues =>
+ DeserializeDict(OldValuesJson);
+
+ [NotMapped]
+ public IReadOnlyDictionary NewValues =>
+ DeserializeDict(NewValuesJson);
+
+ [NotMapped]
+ public IReadOnlyCollection ModifiedProperties =>
+ JsonSerializer.Deserialize>(ModifiedPropertiesJson)
+ ?? [];
+
+ // Setters for domain logic
+ public void SetKeyValues(Dictionary values) =>
+ KeyValuesJson = Serialize(values);
+
+ public void SetOldValues(Dictionary values) =>
+ OldValuesJson = Serialize(values);
+
+ public void SetNewValues(Dictionary values) =>
+ NewValuesJson = Serialize(values);
+
+ public void SetModifiedProperties(IEnumerable properties) =>
+ ModifiedPropertiesJson = JsonSerializer.Serialize(properties.Distinct().ToList());
+
+ public void AddModifiedProperty(string property)
+ {
+ var props = ModifiedProperties.ToList();
+ if (!props.Contains(property))
+ props.Add(property);
+ ModifiedPropertiesJson = JsonSerializer.Serialize(props);
+ }
+
+ // Helpers
+ private static Dictionary DeserializeDict(string json)
+ {
+ return JsonSerializer.Deserialize>(json) ?? new Dictionary();
+ }
+
+ private static string Serialize(object obj) =>
+ JsonSerializer.Serialize(obj);
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Features/v1/GetUserTrails/GetUserTrailsEndpoint.cs b/src/framework/Modules/Auditing/Modules.Auditing/Features/v1/GetUserTrails/GetUserTrailsEndpoint.cs
new file mode 100644
index 0000000000..7603ebafff
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Features/v1/GetUserTrails/GetUserTrailsEndpoint.cs
@@ -0,0 +1,28 @@
+using FSH.Framework.Auditing.Contracts.v1.GetUserTrails;
+using FSH.Framework.Core.Messaging.CQRS;
+using FSH.Framework.Shared.Authorization;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Routing;
+
+namespace FSH.Framework.Auditing.Features.v1.GetUserTrails;
+public static class GetUserTrailsEndpoint
+{
+ public static RouteHandlerBuilder Map(this IEndpointRouteBuilder endpoints)
+ {
+ return endpoints.MapGet("/users/{userId:guid}/trails", async (
+ Guid userId,
+ [FromServices] IQueryDispatcher dispatcher,
+ CancellationToken cancellationToken) =>
+ {
+ var query = new GetUserTrailsQuery(userId);
+ var result = await dispatcher.SendAsync(query, cancellationToken);
+ return TypedResults.Ok(result);
+ })
+ .WithName(nameof(GetUserTrailsEndpoint))
+ .WithSummary("Get user's audit trail details")
+ .WithDescription("Returns the audit trail details for a specific user.")
+ .RequirePermission("Permissions.AuditTrails.View");
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Features/v1/GetUserTrails/GetUserTrailsQueryHandler.cs b/src/framework/Modules/Auditing/Modules.Auditing/Features/v1/GetUserTrails/GetUserTrailsQueryHandler.cs
new file mode 100644
index 0000000000..86ebddd801
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Features/v1/GetUserTrails/GetUserTrailsQueryHandler.cs
@@ -0,0 +1,14 @@
+using FSH.Framework.Auditing.Contracts.v1.GetUserTrails;
+using FSH.Framework.Auditing.Services;
+using FSH.Framework.Core.Messaging.CQRS;
+
+namespace FSH.Framework.Auditing.Features.v1.GetUserTrails;
+internal sealed class GetUserTrailsQueryHandler(IAuditService auditService)
+ : IQueryHandler
+{
+ public async Task HandleAsync(GetUserTrailsQuery query, CancellationToken cancellationToken = default)
+ {
+ var trails = await auditService.GetUserTrailsAsync(query.UserId);
+ return new GetUserTrailsQueryResponse(trails);
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Features/v1/GetUserTrails/GetUserTrailsQueryValidator.cs b/src/framework/Modules/Auditing/Modules.Auditing/Features/v1/GetUserTrails/GetUserTrailsQueryValidator.cs
new file mode 100644
index 0000000000..cb2f5f2177
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Features/v1/GetUserTrails/GetUserTrailsQueryValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+using FSH.Framework.Auditing.Contracts.v1.GetUserTrails;
+
+namespace FSH.Framework.Auditing.Features.v1.GetUserTrails;
+internal sealed class GetUserTrailsQueryValidator : AbstractValidator
+{
+ public GetUserTrailsQueryValidator()
+ {
+ RuleFor(x => x.UserId).NotEmpty();
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Mappings/TrailMappings.cs b/src/framework/Modules/Auditing/Modules.Auditing/Mappings/TrailMappings.cs
new file mode 100644
index 0000000000..a50325f537
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Mappings/TrailMappings.cs
@@ -0,0 +1,56 @@
+using FSH.Framework.Auditing.Contracts.Dtos;
+using FSH.Framework.Auditing.Core.Entities;
+using System.Collections.ObjectModel;
+
+namespace FSH.Modules.Auditing.Core.Mappings;
+
+public static class TrailMappings
+{
+ public static TrailDto ToDto(this Trail trail)
+ {
+ return new TrailDto
+ {
+ Id = trail.Id,
+ DateTime = trail.DateTime,
+ UserId = trail.UserId,
+ Operation = trail.Operation,
+ Description = trail.Description,
+ EntityName = trail.EntityName ?? string.Empty,
+ KeyValues = new Dictionary(trail.KeyValues),
+ OldValues = new Dictionary(trail.OldValues),
+ NewValues = new Dictionary(trail.NewValues),
+ ModifiedProperties = new Collection(trail.ModifiedProperties.ToList())
+ };
+ }
+
+
+ public static IReadOnlyList ToDtoList(this IEnumerable trails)
+ {
+ return trails.Select(t => t.ToDto()).ToList();
+ }
+
+ public static Trail ToEntity(this TrailDto dto)
+ {
+ var entity = new Trail
+ {
+ Id = dto.Id,
+ DateTime = dto.DateTime,
+ UserId = dto.UserId,
+ Operation = dto.Operation,
+ Description = dto.Description,
+ EntityName = dto.EntityName
+ };
+
+ entity.SetKeyValues(dto.KeyValues);
+ entity.SetOldValues(dto.OldValues);
+ entity.SetNewValues(dto.NewValues);
+ entity.SetModifiedProperties(dto.ModifiedProperties);
+
+ return entity;
+ }
+
+ public static IReadOnlyList ToEntityList(this IEnumerable dtos)
+ {
+ return dtos.Select(dto => dto.ToEntity()).ToList();
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Modules.Auditing.csproj b/src/framework/Modules/Auditing/Modules.Auditing/Modules.Auditing.csproj
new file mode 100644
index 0000000000..ccc2253e80
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Modules.Auditing.csproj
@@ -0,0 +1,20 @@
+
+
+ FSH.Modules.Auditing
+ FSH.Modules.Auditing
+
+
+ FSH.Modules.Auditing
+ Auditing module for FullStackHero
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Services/AuditService.cs b/src/framework/Modules/Auditing/Modules.Auditing/Services/AuditService.cs
new file mode 100644
index 0000000000..82344479e6
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Services/AuditService.cs
@@ -0,0 +1,19 @@
+using FSH.Framework.Auditing.Contracts.Dtos;
+using FSH.Framework.Auditing.Data;
+using FSH.Modules.Auditing.Core.Mappings;
+using Microsoft.EntityFrameworkCore;
+
+namespace FSH.Framework.Auditing.Services;
+public class AuditService(IAuditingDbContext context) : IAuditService
+{
+ public async Task> GetUserTrailsAsync(Guid userId)
+ {
+ var trails = await context.Trails
+ .Where(a => a.UserId == userId)
+ .OrderByDescending(a => a.DateTime)
+ .Take(250)
+ .ToListAsync();
+
+ return trails.ToDtoList();
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Auditing/Modules.Auditing/Services/IAuditService.cs b/src/framework/Modules/Auditing/Modules.Auditing/Services/IAuditService.cs
new file mode 100644
index 0000000000..1ac37e58bb
--- /dev/null
+++ b/src/framework/Modules/Auditing/Modules.Auditing/Services/IAuditService.cs
@@ -0,0 +1,7 @@
+using FSH.Framework.Auditing.Contracts.Dtos;
+
+namespace FSH.Framework.Auditing.Services;
+public interface IAuditService
+{
+ Task> GetUserTrailsAsync(Guid userId);
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Caching/CacheOptions.cs b/src/framework/Modules/Common/Modules.Common.Core/Caching/CacheOptions.cs
similarity index 65%
rename from src/api/framework/Core/Caching/CacheOptions.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Caching/CacheOptions.cs
index b861c2e06a..ae06641a3b 100644
--- a/src/api/framework/Core/Caching/CacheOptions.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Caching/CacheOptions.cs
@@ -1,6 +1,6 @@
-namespace FSH.Framework.Core.Caching;
+namespace FSH.Modules.Common.Core.Caching;
public class CacheOptions
{
public string Redis { get; set; } = string.Empty;
-}
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Caching/CacheServiceExtensions.cs b/src/framework/Modules/Common/Modules.Common.Core/Caching/CacheServiceExtensions.cs
similarity index 71%
rename from src/api/framework/Core/Caching/CacheServiceExtensions.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Caching/CacheServiceExtensions.cs
index c03f94cc1f..df86cce679 100644
--- a/src/api/framework/Core/Caching/CacheServiceExtensions.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Caching/CacheServiceExtensions.cs
@@ -1,10 +1,10 @@
-namespace FSH.Framework.Core.Caching;
+namespace FSH.Modules.Common.Core.Caching;
public static class CacheServiceExtensions
{
public static T? GetOrSet(this ICacheService cache, string key, Func getItemCallback, TimeSpan? slidingExpiration = null)
{
- T? value = cache.Get(key);
+ T? value = cache.GetItem(key);
if (value is not null)
{
@@ -15,7 +15,7 @@ public static class CacheServiceExtensions
if (value is not null)
{
- cache.Set(key, value, slidingExpiration);
+ cache.SetItem(key, value, slidingExpiration);
}
return value;
@@ -23,7 +23,7 @@ public static class CacheServiceExtensions
public static async Task GetOrSetAsync(this ICacheService cache, string key, Func> task, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default)
{
- T? value = await cache.GetAsync(key, cancellationToken);
+ T? value = await cache.GetItemAsync(key, cancellationToken);
if (value is not null)
{
@@ -34,9 +34,9 @@ public static class CacheServiceExtensions
if (value is not null)
{
- await cache.SetAsync(key, value, slidingExpiration, cancellationToken);
+ await cache.SetItemAsync(key, value, slidingExpiration, cancellationToken);
}
return value;
}
-}
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Caching/ICacheService.cs b/src/framework/Modules/Common/Modules.Common.Core/Caching/ICacheService.cs
new file mode 100644
index 0000000000..b0c95755b5
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Caching/ICacheService.cs
@@ -0,0 +1,16 @@
+namespace FSH.Modules.Common.Core.Caching;
+
+public interface ICacheService
+{
+ T? GetItem(string key);
+ Task GetItemAsync(string key, CancellationToken token = default);
+
+ void RefreshItem(string key);
+ Task RefreshItemAsync(string key, CancellationToken token = default);
+
+ void RemoveItem(string key);
+ Task RemoveItemAsync(string key, CancellationToken token = default);
+
+ void SetItem(string key, T value, TimeSpan? slidingExpiration = null);
+ Task SetItemAsync(string key, T value, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Domain/AuditableEntity.cs b/src/framework/Modules/Common/Modules.Common.Core/Domain/AuditableEntity.cs
similarity index 90%
rename from src/api/framework/Core/Domain/AuditableEntity.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Domain/AuditableEntity.cs
index 6639a02156..4e1146e42b 100644
--- a/src/api/framework/Core/Domain/AuditableEntity.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Domain/AuditableEntity.cs
@@ -1,4 +1,4 @@
-using FSH.Framework.Core.Domain.Contracts;
+using FSH.Modules.Common.Core.Domain.Contracts;
namespace FSH.Framework.Core.Domain;
@@ -15,4 +15,4 @@ public class AuditableEntity : BaseEntity, IAuditable, ISoftDeletable
public abstract class AuditableEntity : AuditableEntity
{
protected AuditableEntity() => Id = Guid.NewGuid();
-}
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Domain/BaseEntity.cs b/src/framework/Modules/Common/Modules.Common.Core/Domain/BaseEntity.cs
similarity index 60%
rename from src/api/framework/Core/Domain/BaseEntity.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Domain/BaseEntity.cs
index 1c2e98daaf..5c96547a40 100644
--- a/src/api/framework/Core/Domain/BaseEntity.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Domain/BaseEntity.cs
@@ -1,7 +1,7 @@
-using System.Collections.ObjectModel;
+using FSH.Framework.Core.Messaging.Events;
+using FSH.Modules.Common.Core.Domain.Contracts;
+using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations.Schema;
-using FSH.Framework.Core.Domain.Contracts;
-using FSH.Framework.Core.Domain.Events;
namespace FSH.Framework.Core.Domain;
@@ -9,8 +9,8 @@ public abstract class BaseEntity : IEntity
{
public TId Id { get; protected init; } = default!;
[NotMapped]
- public Collection DomainEvents { get; } = new Collection();
- public void QueueDomainEvent(DomainEvent @event)
+ public Collection DomainEvents { get; } = new Collection();
+ public void QueueDomainEvent(AppEvent @event)
{
if (!DomainEvents.Contains(@event))
DomainEvents.Add(@event);
@@ -20,4 +20,4 @@ public void QueueDomainEvent(DomainEvent @event)
public abstract class BaseEntity : BaseEntity
{
protected BaseEntity() => Id = Guid.NewGuid();
-}
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Domain/Contracts/IAggregateRoot.cs b/src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/IAggregateRoot.cs
similarity index 76%
rename from src/api/framework/Core/Domain/Contracts/IAggregateRoot.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/IAggregateRoot.cs
index cc98c00dba..9d9d7a5f64 100644
--- a/src/api/framework/Core/Domain/Contracts/IAggregateRoot.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/IAggregateRoot.cs
@@ -1,7 +1,7 @@
-namespace FSH.Framework.Core.Domain.Contracts;
+namespace FSH.Modules.Common.Core.Domain.Contracts;
// Apply this marker interface only to aggregate root entities
// Repositories will only work with aggregate roots, not their children
public interface IAggregateRoot : IEntity
{
-}
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Domain/Contracts/IAuditable.cs b/src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/IAuditable.cs
similarity index 75%
rename from src/api/framework/Core/Domain/Contracts/IAuditable.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/IAuditable.cs
index edfa8ab9f3..8e6590cb82 100644
--- a/src/api/framework/Core/Domain/Contracts/IAuditable.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/IAuditable.cs
@@ -1,4 +1,4 @@
-namespace FSH.Framework.Core.Domain.Contracts;
+namespace FSH.Modules.Common.Core.Domain.Contracts;
public interface IAuditable
{
@@ -6,4 +6,4 @@ public interface IAuditable
Guid CreatedBy { get; }
DateTimeOffset LastModified { get; }
Guid? LastModifiedBy { get; }
-}
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/IEntity.cs b/src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/IEntity.cs
new file mode 100644
index 0000000000..f676924e9c
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/IEntity.cs
@@ -0,0 +1,14 @@
+using FSH.Framework.Core.Messaging.Events;
+using System.Collections.ObjectModel;
+
+namespace FSH.Modules.Common.Core.Domain.Contracts;
+
+public interface IEntity
+{
+ Collection DomainEvents { get; }
+}
+
+public interface IEntity : IEntity
+{
+ TId Id { get; }
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Domain/Contracts/ISoftDeletable.cs b/src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/ISoftDeletable.cs
similarity index 66%
rename from src/api/framework/Core/Domain/Contracts/ISoftDeletable.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/ISoftDeletable.cs
index d129d02e4a..3c4b210c0a 100644
--- a/src/api/framework/Core/Domain/Contracts/ISoftDeletable.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Domain/Contracts/ISoftDeletable.cs
@@ -1,7 +1,7 @@
-namespace FSH.Framework.Core.Domain.Contracts;
+namespace FSH.Modules.Common.Core.Domain.Contracts;
public interface ISoftDeletable
{
DateTimeOffset? Deleted { get; set; }
Guid? DeletedBy { get; set; }
-}
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Exceptions/CustomException.cs b/src/framework/Modules/Common/Modules.Common.Core/Exceptions/CustomException.cs
new file mode 100644
index 0000000000..3ed5a62ef3
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Exceptions/CustomException.cs
@@ -0,0 +1,41 @@
+using System.Net;
+
+namespace FSH.Modules.Common.Core.Exceptions;
+
+///
+/// FullStackHero exception used for consistent error handling across the stack.
+/// Includes HTTP status codes and optional detailed error messages.
+///
+public class CustomException : Exception
+{
+ ///
+ /// A list of error messages (e.g., validation errors, business rules).
+ ///
+ public IReadOnlyList ErrorMessages { get; }
+
+ ///
+ /// The HTTP status code associated with this exception.
+ ///
+ public HttpStatusCode StatusCode { get; }
+
+ public CustomException(
+ string message,
+ IEnumerable? errors = null,
+ HttpStatusCode statusCode = HttpStatusCode.InternalServerError)
+ : base(message)
+ {
+ ErrorMessages = errors?.ToList() ?? new List();
+ StatusCode = statusCode;
+ }
+
+ public CustomException(
+ string message,
+ Exception innerException,
+ IEnumerable? errors = null,
+ HttpStatusCode statusCode = HttpStatusCode.InternalServerError)
+ : base(message, innerException)
+ {
+ ErrorMessages = errors?.ToList() ?? new List();
+ StatusCode = statusCode;
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Exceptions/ForbiddenException.cs b/src/framework/Modules/Common/Modules.Common.Core/Exceptions/ForbiddenException.cs
new file mode 100644
index 0000000000..bb47fd4dc4
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Exceptions/ForbiddenException.cs
@@ -0,0 +1,25 @@
+using FSH.Modules.Common.Core.Exceptions;
+using System.Net;
+
+namespace FSH.Framework.Core.Exceptions;
+
+///
+/// Exception representing a 403 Forbidden error.
+///
+public class ForbiddenException : CustomException
+{
+ public ForbiddenException()
+ : base("Unauthorized access.", Array.Empty(), HttpStatusCode.Forbidden)
+ {
+ }
+
+ public ForbiddenException(string message)
+ : base(message, Array.Empty(), HttpStatusCode.Forbidden)
+ {
+ }
+
+ public ForbiddenException(string message, IEnumerable errors)
+ : base(message, errors.ToList(), HttpStatusCode.Forbidden)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Exceptions/NotFoundException.cs b/src/framework/Modules/Common/Modules.Common.Core/Exceptions/NotFoundException.cs
new file mode 100644
index 0000000000..3066beabaf
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Exceptions/NotFoundException.cs
@@ -0,0 +1,20 @@
+using FSH.Modules.Common.Core.Exceptions;
+using System.Net;
+
+namespace FSH.Framework.Core.Exceptions;
+
+///
+/// Exception representing a 404 Not Found error.
+///
+public class NotFoundException : CustomException
+{
+ public NotFoundException(string message)
+ : base(message, Array.Empty(), HttpStatusCode.NotFound)
+ {
+ }
+
+ public NotFoundException(string message, IEnumerable errors)
+ : base(message, errors.ToList(), HttpStatusCode.NotFound)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Exceptions/UnauthorizedException.cs b/src/framework/Modules/Common/Modules.Common.Core/Exceptions/UnauthorizedException.cs
new file mode 100644
index 0000000000..50b04e36ef
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Exceptions/UnauthorizedException.cs
@@ -0,0 +1,25 @@
+using FSH.Modules.Common.Core.Exceptions;
+using System.Net;
+
+namespace FSH.Framework.Core.Exceptions;
+
+///
+/// Exception representing a 401 Unauthorized error (authentication failure).
+///
+public class UnauthorizedException : CustomException
+{
+ public UnauthorizedException()
+ : base("Authentication failed.", Array.Empty(), HttpStatusCode.Unauthorized)
+ {
+ }
+
+ public UnauthorizedException(string message)
+ : base(message, Array.Empty(), HttpStatusCode.Unauthorized)
+ {
+ }
+
+ public UnauthorizedException(string message, IEnumerable errors)
+ : base(message, errors.ToList(), HttpStatusCode.Unauthorized)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Identity/Users/Abstractions/ICurrentUser.cs b/src/framework/Modules/Common/Modules.Common.Core/ExecutionContext/ICurrentUser.cs
similarity index 82%
rename from src/api/framework/Core/Identity/Users/Abstractions/ICurrentUser.cs
rename to src/framework/Modules/Common/Modules.Common.Core/ExecutionContext/ICurrentUser.cs
index aa5314b007..363df8f40d 100644
--- a/src/api/framework/Core/Identity/Users/Abstractions/ICurrentUser.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/ExecutionContext/ICurrentUser.cs
@@ -1,6 +1,6 @@
using System.Security.Claims;
-namespace FSH.Framework.Core.Identity.Users.Abstractions;
+namespace FSH.Framework.Core.ExecutionContext;
public interface ICurrentUser
{
string? Name { get; }
@@ -16,4 +16,4 @@ public interface ICurrentUser
bool IsInRole(string role);
IEnumerable? GetUserClaims();
-}
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Identity/Users/Abstractions/ICurrentUserInitializer.cs b/src/framework/Modules/Common/Modules.Common.Core/ExecutionContext/ICurrentUserInitializer.cs
similarity index 73%
rename from src/api/framework/Core/Identity/Users/Abstractions/ICurrentUserInitializer.cs
rename to src/framework/Modules/Common/Modules.Common.Core/ExecutionContext/ICurrentUserInitializer.cs
index 2342d75b8d..93bf622e69 100644
--- a/src/api/framework/Core/Identity/Users/Abstractions/ICurrentUserInitializer.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/ExecutionContext/ICurrentUserInitializer.cs
@@ -1,9 +1,9 @@
using System.Security.Claims;
-namespace FSH.Framework.Core.Identity.Users.Abstractions;
+namespace FSH.Framework.Core.ExecutionContext;
public interface ICurrentUserInitializer
{
void SetCurrentUser(ClaimsPrincipal user);
void SetCurrentUserId(string userId);
-}
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/FshCore.cs b/src/framework/Modules/Common/Modules.Common.Core/FshCore.cs
similarity index 98%
rename from src/api/framework/Core/FshCore.cs
rename to src/framework/Modules/Common/Modules.Common.Core/FshCore.cs
index 1891dc8d21..df778e0a8f 100644
--- a/src/api/framework/Core/FshCore.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/FshCore.cs
@@ -2,4 +2,4 @@
public static class FshCore
{
public static string Name { get; set; } = "FshCore";
-}
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Helpers/JsonHelpers.cs b/src/framework/Modules/Common/Modules.Common.Core/Helpers/JsonHelpers.cs
new file mode 100644
index 0000000000..4b99f4a6c7
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Helpers/JsonHelpers.cs
@@ -0,0 +1,13 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace FSH.Framework.Core.Helpers;
+public static class JsonHelpers
+{
+ public static readonly JsonSerializerOptions DefaultJsonOptions = new(JsonSerializerDefaults.Web)
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ WriteIndented = false,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+ };
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Jobs/IJobService.cs b/src/framework/Modules/Common/Modules.Common.Core/Jobs/IJobService.cs
similarity index 99%
rename from src/api/framework/Core/Jobs/IJobService.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Jobs/IJobService.cs
index 7016ae79d5..b7b7d15f0c 100644
--- a/src/api/framework/Core/Jobs/IJobService.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Jobs/IJobService.cs
@@ -37,4 +37,4 @@ public interface IJobService
string Schedule(Expression> methodCall, DateTimeOffset enqueueAt);
string Schedule(Expression> methodCall, DateTimeOffset enqueueAt);
-}
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Mail/IMailService.cs b/src/framework/Modules/Common/Modules.Common.Core/Mail/IMailService.cs
similarity index 98%
rename from src/api/framework/Core/Mail/IMailService.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Mail/IMailService.cs
index c5e000951b..f8ddc44c85 100644
--- a/src/api/framework/Core/Mail/IMailService.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Mail/IMailService.cs
@@ -2,4 +2,4 @@
public interface IMailService
{
Task SendAsync(MailRequest request, CancellationToken ct);
-}
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Mail/MailOptions.cs b/src/framework/Modules/Common/Modules.Common.Core/Mail/MailOptions.cs
similarity index 99%
rename from src/api/framework/Core/Mail/MailOptions.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Mail/MailOptions.cs
index 4b01169572..84bd4e9f31 100644
--- a/src/api/framework/Core/Mail/MailOptions.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Mail/MailOptions.cs
@@ -12,4 +12,4 @@ public class MailOptions
public string? Password { get; set; }
public string? DisplayName { get; set; }
-}
+}
\ No newline at end of file
diff --git a/src/api/framework/Core/Mail/MailRequest.cs b/src/framework/Modules/Common/Modules.Common.Core/Mail/MailRequest.cs
similarity index 99%
rename from src/api/framework/Core/Mail/MailRequest.cs
rename to src/framework/Modules/Common/Modules.Common.Core/Mail/MailRequest.cs
index 662dcd4010..e01723e63a 100644
--- a/src/api/framework/Core/Mail/MailRequest.cs
+++ b/src/framework/Modules/Common/Modules.Common.Core/Mail/MailRequest.cs
@@ -24,4 +24,4 @@ public class MailRequest(Collection to, string subject, string? body = n
public IDictionary AttachmentData { get; } = attachmentData ?? new Dictionary();
public IDictionary Headers { get; } = headers ?? new Dictionary();
-}
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/ICommand.cs b/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/ICommand.cs
new file mode 100644
index 0000000000..07f340c5be
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/ICommand.cs
@@ -0,0 +1,4 @@
+namespace FSH.Modules.Common.Core.Messaging.CQRS;
+
+// Marker for command requests (intended to modify system state)
+public interface ICommand : IRequest { }
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/ICommandDispatcher.cs b/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/ICommandDispatcher.cs
new file mode 100644
index 0000000000..7b30f63078
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/ICommandDispatcher.cs
@@ -0,0 +1,10 @@
+using FSH.Modules.Common.Core.Messaging.CQRS;
+
+namespace FSH.Framework.Core.Messaging.CQRS;
+public interface ICommandDispatcher
+{
+ ///
+ /// Sends a command to its handler.
+ ///
+ Task SendAsync(ICommand command, CancellationToken ct = default);
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/ICommandHandler.cs b/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/ICommandHandler.cs
new file mode 100644
index 0000000000..325cbf2405
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/ICommandHandler.cs
@@ -0,0 +1,12 @@
+namespace FSH.Modules.Common.Core.Messaging.CQRS;
+
+///
+/// Handles a command and returns a result.
+///
+/// Type of command
+/// Type of response
+public interface ICommandHandler
+ where TCommand : ICommand
+{
+ Task HandleAsync(TCommand command, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/IQuery.cs b/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/IQuery.cs
new file mode 100644
index 0000000000..c1947494fd
--- /dev/null
+++ b/src/framework/Modules/Common/Modules.Common.Core/Messaging/CQRS/IQuery.cs
@@ -0,0 +1,6 @@
+using FSH.Modules.Common.Core.Messaging.CQRS;
+
+namespace FSH.Framework.Core.Messaging.CQRS;
+
+// Marker for query requests (intended to return data without modifying state)
+public interface IQuery