commit abc7acbcedad7bd0a4c047786207b57a3f5d3872 Author: Khwezi Mngoma Date: Mon Jun 1 17:38:55 2026 +0200 Add project files. diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..2977f94 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,115 @@ +--- +kind: pipeline +type: docker +name: build + +steps: + - name: dotnet-build + image: mcr.microsoft.com/dotnet/sdk:10.0 + commands: + - dotnet restore MidrandBooksApi.slnx + - dotnet build MidrandBooksApi.slnx -c Release + + - name: dotnet-test + image: mcr.microsoft.com/dotnet/sdk:10.0 + commands: + - dotnet restore MidrandBooksApi.slnx + - dotnet test MidrandBooksApi.slnx -c Release --no-restore + +trigger: + event: [ pull_request ] + +--- +kind: pipeline +type: docker +name: package + +steps: + - name: docker-build + image: plugins/docker + settings: + registry: nexus.khongisa.co.za + repo: nexus.khongisa.co.za/midrandbooks-api + tags: [ latest, "1.${DRONE_BUILD_NUMBER}" ] + custom_labels: + - org.opencontainers.image.source=https://gitea.khongisa.co.za/litecharms/midrandbooks-api + - org.opencontainers.image.version=1.${DRONE_BUILD_NUMBER} + - org.opencontainers.image.revision=${DRONE_COMMIT_SHA} + username: { from_secret: docker_username } + password: { from_secret: docker_password } + + - name: gitea-tag-release + image: alpine/git + environment: + GITEA_TOKEN: { from_secret: git_token } + GITEA_USER: { from_secret: git_username } + GITEA_PASS: { from_secret: git_password } + commands: + - echo "169.255.58.144 gitea.khongisa.co.za" >> /etc/hosts + - apk add --no-cache curl + - git remote set-url origin https://$${GITEA_USER}:$${GITEA_PASS}@gitea.khongisa.co.za/litecharms/midrandbooks-api.git + - git tag 1.${DRONE_BUILD_NUMBER} + - git push origin 1.${DRONE_BUILD_NUMBER} + - | + curl -X POST "https://gitea.khongisa.co.za/api/v1/repos/litecharms/midrandbooks-api/releases" \ + -H "Authorization: token $${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"tag_name\": \"1.${DRONE_BUILD_NUMBER}\", + \"target_commitish\": \"${DRONE_COMMIT_SHA}\", + \"name\": \"Release 1.${DRONE_BUILD_NUMBER}\", + \"body\": \"### Artifacts\n* **Docker Image:** nexus.khongisa.co.za/midrandbooks-api:1.${DRONE_BUILD_NUMBER}\n* **NuGet:** [View on Nexus](https://nexus.khongisa.co.za/repository/nuget-group/)\", + \"draft\": false, + \"prerelease\": false + }" + +depends_on: + - build + +trigger: + event: [ pull_request ] + +--- +kind: pipeline +type: docker +name: uat + +steps: + - name: deploy + image: bitnami/kubectl:latest + environment: + KUBE_CONFIG: { from_secret: kube_config } + commands: + - mkdir -p $HOME/.kube + - echo "$KUBE_CONFIG" > $HOME/.kube/config + - kubectl apply -f midrandbooks-uat.yml + - sleep 10 + - kubectl rollout restart deployment/midrandbooks -n midrandbooks-uat + +depends_on: + - package + +trigger: + event: [ pull_request ] + +--- +kind: pipeline +type: docker +name: prod + +steps: + - name: deploy + image: bitnami/kubectl:latest + environment: + KUBE_CONFIG: { from_secret: kube_config } + commands: + - mkdir -p $HOME/.kube + - echo "$KUBE_CONFIG" > $HOME/.kube/config + - kubectl apply -f midrandbooks.yml + +depends_on: + - uat + +trigger: + event: [ promote ] + target: [ production ] \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4a5bdbf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,248 @@ +# 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 = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_non_hidden_explicit_cast_in_source = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = true +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_prefer_system_threading_lock = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_simple_property_accessors = true +csharp_style_prefer_top_level_statements = true + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_implicitly_typed_lambda_expression = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_unbound_generic_type_in_nameof = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/.filenesting.json b/.filenesting.json new file mode 100644 index 0000000..483aefc --- /dev/null +++ b/.filenesting.json @@ -0,0 +1,14 @@ +{ + "root": true, + "dependentFileProviders": { + "add": { + "addedExtension": {}, + "extensionToExtension": { + "add": { + ".css": [ ".razor" ], + ".cs": [ ".razor" ] + } + } + } + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2e02d76 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /app + +COPY ["nuget.config", "./"] +COPY ["MidrandBooksApi/MidrandBooksApi.csproj", "MidrandBooksApi/"] +RUN dotnet restore "MidrandBooksApi/MidrandBooksApi.csproj" --configfile nuget.config +COPY . . +RUN dotnet publish "MidrandBooksApi/MidrandBooksApi.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final +WORKDIR /app + +EXPOSE 8080 +EXPOSE 8081 + +COPY --from=build /app/publish . + +ENTRYPOINT ["dotnet", "MidrandBooksApi.dll"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d3f8705 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +PROPRIETARY LICENSE + +Copyright (c) 2026 Lite Charms (PTY) Ltd. All rights reserved. + +This software and its associated documentation (the "Software") are the +proprietary property of Lite Charms (PTY) Ltd. + +The Software is provided for internal use only. Unauthorized copying, +distribution, modification, or use of this file via any medium is +strictly prohibited. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/MidrandBooks.snk b/MidrandBooks.snk new file mode 100644 index 0000000..b3f5dae Binary files /dev/null and b/MidrandBooks.snk differ diff --git a/MidrandBooksApi.slnx b/MidrandBooksApi.slnx new file mode 100644 index 0000000..526a34e --- /dev/null +++ b/MidrandBooksApi.slnx @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/MidrandBooksApi/ApiVersionTargetAttribute.cs b/MidrandBooksApi/ApiVersionTargetAttribute.cs new file mode 100644 index 0000000..d02fb28 --- /dev/null +++ b/MidrandBooksApi/ApiVersionTargetAttribute.cs @@ -0,0 +1,7 @@ +namespace MidrandBooksApi; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public sealed class ApiVersionTargetAttribute(int majorVersion) : Attribute +{ + public int MajorVersion { get; } = majorVersion; +} diff --git a/MidrandBooksApi/EndpointTags.cs b/MidrandBooksApi/EndpointTags.cs new file mode 100644 index 0000000..af64953 --- /dev/null +++ b/MidrandBooksApi/EndpointTags.cs @@ -0,0 +1,7 @@ +namespace MidrandBooksApi; + +public static class EndpointTags +{ + public const string Books = nameof(Books); + public const string Payments = nameof(Payments); +} diff --git a/MidrandBooksApi/IEndpoint.cs b/MidrandBooksApi/IEndpoint.cs new file mode 100644 index 0000000..e2a2c05 --- /dev/null +++ b/MidrandBooksApi/IEndpoint.cs @@ -0,0 +1,6 @@ +namespace MidrandBooksApi; + +public interface IEndpoint +{ + void Map(IEndpointRouteBuilder builder); +} diff --git a/MidrandBooksApi/MidrandBooksApi.csproj b/MidrandBooksApi/MidrandBooksApi.csproj new file mode 100644 index 0000000..057e90a --- /dev/null +++ b/MidrandBooksApi/MidrandBooksApi.csproj @@ -0,0 +1,92 @@ + + + + net10.0 + enable + enable + a257fd9f-4f39-471a-b333-d5d6b51bd058 + ..\MidrandBooks.snk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + diff --git a/MidrandBooksApi/OpenApiBearerSecuritySchemeTransformer.cs b/MidrandBooksApi/OpenApiBearerSecuritySchemeTransformer.cs new file mode 100644 index 0000000..de99e93 --- /dev/null +++ b/MidrandBooksApi/OpenApiBearerSecuritySchemeTransformer.cs @@ -0,0 +1,16 @@ +namespace MidrandBooksApi; + +public sealed class OpenApiBearerSecuritySchemeTransformer : IOpenApiDocumentTransformer +{ + public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) + { + var bearerScheme = new OpenApiSecurityScheme + { + Type = SecuritySchemeType.Http, + Scheme = "bearer", + Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"" + }; + + document.AddComponent("Bearer", bearerScheme); + } +} diff --git a/MidrandBooksApi/Payments/Endpoints/ConfirmationEndpoint.cs b/MidrandBooksApi/Payments/Endpoints/ConfirmationEndpoint.cs new file mode 100644 index 0000000..62a0d5c --- /dev/null +++ b/MidrandBooksApi/Payments/Endpoints/ConfirmationEndpoint.cs @@ -0,0 +1,43 @@ +using LiteCharms.Features.Extensions; +using LiteCharms.Features.Hasher; +using LiteCharms.Features.Models; + +namespace MidrandBooksApi.Payments.Endpoints; + +[ApiVersionTarget(1)] +public sealed class ConfirmationEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder builder) + { + builder.MapPost("payments/confirm", async (HttpRequest request, HashService hashService, + CancellationToken cancellationToken) => + { + var formCollection = await request.ReadFormAsync(cancellationToken); + + if (!formCollection.TryGetValue("signature", out var signatureValues) || string.IsNullOrWhiteSpace(signatureValues.ToString())) + return Results.BadRequest("Missing Payfast validation signature."); + + string incomingSignature = signatureValues.ToString(); + + var payload = new PayfastWebhookPayload + { + Amount = formCollection.TryGetValue("amount", out var amountValues) ? amountValues.ToString() : null, + ItemName = formCollection.TryGetValue("item_name", out var itemValues) ? itemValues.ToString() : null, + MPaymentId = formCollection.TryGetValue("m_payment_id", out var paymentIdValues) ? paymentIdValues.ToString() : null + }; + + var validationResult = hashService.VerifyPayfastWebhookSignature(payload, incomingSignature); + + return validationResult.IsFailed || !validationResult.Value + ? Results.Unauthorized() + : Results.Ok(); + }) + .WithDescription("Securely confirm and process an incoming Payfast merchant payment callback.") + .WithName(typeof(ConfirmationEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Payments); + } +} diff --git a/MidrandBooksApi/Program.cs b/MidrandBooksApi/Program.cs new file mode 100644 index 0000000..5e287bf --- /dev/null +++ b/MidrandBooksApi/Program.cs @@ -0,0 +1,91 @@ +using Asp.Versioning.Builder; +using LiteCharms.Features.Extensions; +using LiteCharms.Features.Mediator; +using LiteCharms.Features.MidrandBooks.Extensions; +using MidrandBooksApi; +using static LiteCharms.Features.Extensions.Quartz; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddMonitoring(); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddEndpoints(Assembly.GetExecutingAssembly()); +builder.Services.AddApiServices(builder.Configuration); + +builder.Services.AddAuthorization(); +builder.Services.AddAuthentication(); + +builder.Services.AddMediator(); + +builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TelemetryPipelineBehavior<,>)); +builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingPipelineBehavior<,>)); + +builder.Services.AddQuartzSchedulerClient(MidrandShopSchedulerName, builder.Configuration); + +builder.Services.AddEmailServices(builder.Configuration); +builder.Services.AddEmailServiceBus(); + +builder.Services.AddShopServices(); +builder.Services.AddHashServices(builder.Configuration); +builder.Services.AddMidrandShopDatabase(builder.Configuration); + +builder.Services.AddMidrandShopPostgresHealthCheck(); +builder.Services.AddMidrandShopQuartzHealthCheck(); +builder.Services.AddHealthChecksSupport(builder.Configuration); + +var app = builder.Build(); + +var schedulerFactory = app.Services.GetRequiredService(); +var scheduler = await schedulerFactory.GetScheduler(MidrandShopSchedulerName); + +if (!scheduler!.IsStarted) + await scheduler.Start(); + +app.UseHsts(); +app.UseHttpsRedirection(); + +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); + +ApiVersionSet versionSet = app.NewApiVersionSet("v1") + .HasApiVersion(new ApiVersion(1)) + .HasApiVersion(new ApiVersion(2)) + .ReportApiVersions() + .Build(); + +var versionGroups = new Dictionary +{ + { 1, app.MapGroup("v{version:apiVersion}").WithApiVersionSet(versionSet) } +}; + +app.MapEndpoints(versionGroups); + +app.UseHealthChecks("/health", new HealthCheckOptions +{ + Predicate = _ => true, + AllowCachingResponses = true, + ResponseWriter = HealthChecks.UI.Client.UIResponseWriter.WriteHealthCheckUIResponse +}); + +app.MapHealthChecksUI(options => { options.UIPath = "/healthui"; }); +app.UseHealthChecks("/ready"); + +app.MapOpenApi(); + +foreach (var description in app.DescribeApiVersions().OrderByDescending(o => o.ApiVersion.MajorVersion)) + app.MapScalarApiReference($"/openapi/{description.GroupName}", (options, context) => + { + options.AddServer(new ScalarServer($"https://{context.Request.Host}")); + options.WithOpenApiRoutePattern($"/openapi/{description.GroupName}.json"); + options.WithTheme(ScalarTheme.DeepSpace); + options.Agent = new ScalarAgentOptions { Disabled = true }; + options.Authentication = new ScalarAuthenticationOptions { PreferredSecuritySchemes = ["Bearer"] }; + }); + + +if (!app.Environment.IsDevelopment()) + app.UseExceptionHandler("/Error", createScopeForErrors: true); + +app.Run(); diff --git a/MidrandBooksApi/Properties/launchSettings.json b/MidrandBooksApi/Properties/launchSettings.json new file mode 100644 index 0000000..e2d88db --- /dev/null +++ b/MidrandBooksApi/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5159", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7196;http://localhost:5159", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/MidrandBooksApi/Setup.cs b/MidrandBooksApi/Setup.cs new file mode 100644 index 0000000..9a5d63d --- /dev/null +++ b/MidrandBooksApi/Setup.cs @@ -0,0 +1,88 @@ +namespace MidrandBooksApi; + +public static class Setup +{ + public static IApplicationBuilder MapEndpoints(this WebApplication app, Dictionary versionGroups) + { + var endpoints = app.Services.GetRequiredService>(); + + foreach (var endpoint in endpoints) + { + var versionAttributes = endpoint.GetType().GetCustomAttributes().ToList(); + + if (versionAttributes.Count != 0) + { + foreach (var attr in versionAttributes) + if (versionGroups.TryGetValue(attr.MajorVersion, out var targetGroup)) + endpoint.Map(targetGroup); + } + else + endpoint.Map(app); + } + + return app; + } + + public static IServiceCollection AddEndpoints(this IServiceCollection services, Assembly assembly) + { + ServiceDescriptor[] discriptors = [.. assembly.DefinedTypes + .Where(t => t is { IsInterface: false, IsAbstract: false }) + .Where(t => t.IsAssignableTo(typeof(IEndpoint))) + .Select(t => ServiceDescriptor.Transient(typeof(IEndpoint), t))]; + + services.TryAddEnumerable(discriptors); + + return services; + } + + public static string ToEndpointName(this Type target, string? annotation = "") => + $"{target.Name.Replace("Endpoint", string.Empty)}{annotation}".ToLower(); + + public static IServiceCollection AddApiServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddApiVersioning(options => + { + options.DefaultApiVersion = new ApiVersion(1); + options.ReportApiVersions = true; + options.AssumeDefaultVersionWhenUnspecified = true; + options.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), + new QueryStringApiVersionReader("version"), + new QueryStringApiVersionReader("version"), + new MediaTypeApiVersionReader("version")); + }) + .AddApiExplorer(options => + { + options.GroupNameFormat = "'v'VVV"; + options.SubstituteApiVersionInUrl = true; + }); + + var urls = configuration["ASPNETCORE_URLS"] ?? configuration["Urls"]; + var healthUrl = "http://localhost:8080/health"; + + if (!string.IsNullOrWhiteSpace(urls)) + { + string firstUrl = urls.Split(';').FirstOrDefault(s => s.Contains("http://"))! + .Replace("*", "localhost").Replace("+", "localhost"); + + healthUrl = $"{firstUrl.TrimEnd('/')}/health"; + } + + services.AddHealthChecksUI(setup => + { + setup.SetNotifyUnHealthyOneTimeUntilChange(); + setup.AddHealthCheckEndpoint("primary, heal", healthUrl); + setup.SetHeaderText("Midrand Books"); + }) + .AddInMemoryStorage(); + + services.AddOutputCache(options => + { + options.AddBasePolicy(builder => builder.Cache()); + options.DefaultExpirationTimeSpan = TimeSpan.FromSeconds(10); + }); + + services.AddOpenApi(options => options.AddDocumentTransformer()); + + return services; + } +} diff --git a/MidrandBooksApi/appsettings.json b/MidrandBooksApi/appsettings.json new file mode 100644 index 0000000..085e7d6 --- /dev/null +++ b/MidrandBooksApi/appsettings.json @@ -0,0 +1,24 @@ +{ + "HasherSettings": { + "MinHashLength": 11 + }, + "BookshopS3Settings": { + "ServiceUrl": "http://192.168.1.177:30900", + "Region": "garage", + "BucketName": "bookshop", + "CdnBaseUrl": "https://bookshop.cdn.khongisa.co.za" + }, + "Monitoring": { + "ApiKey": "", + "Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889", + "ServiceName": "MidrandBooks.DEV" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Error" + } + }, + "AllowedHosts": "*" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b81ea2 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +# Midrand Books Ecommerce Platform + +An online bookstore web application designed for Midrand Books, allowing local and national customers to browse, search, and purchase books seamlessly. + +## 🚀 Features + +* **User Authentication**: Secure customer registration, login, and profile management. +* **Product Catalog**: Advanced search, filtering by genre, and dynamic book categorization. +* **Shopping Cart**: Real-time cart updates, item persistence, and coupon code validation. +* **Secure Checkout**: Integrated payment gateways supporting local South African methods (e.g., PayFast, Ozow, or Yoco). +* **Order Tracking**: Automated email confirmations and live delivery status tracking. +* **Admin Dashboard**: Content management system (CMS) to manage inventory, track sales, and process orders. + +## 🛠️ Tech Stack + +* **Frontend**: Blazor / HTML5 / Bootstrap CSS +* **Backend**: Blazor Server +* **Database**: PostgreSQL +* **Payment Gateway**: PayFast API / PayPal SDK + +## 📋 Prerequisites + +Before setting up the project locally, ensure you have the following installed: + +* .NET SDK (v6.0 or higher) +* Git + +## 🔧 Installation & Setup + +Follow these steps to run the project locally. + +1. **Clone the repository** + ```bash + git clone https://gitea.khongisa.co.za/litecharms/midrandbooks.git + cd midrandbooks + ``` + +2. **Install dependencies** + ```bash + # restore NuGet dependencies + dotnet restore + ``` + +3. **Environment Variables** + Create a `.env` file in both the frontend and backend roots. Use the `.env.example` files provided as a reference. + ```env + DATABASE_URL=your_database_url + Email = mailbox settings + Monitoring = Aspire dashboard settings + PAYMENT_GATEWAY_KEY=your_api_key + ``` + +4. **Run the application** + ```bash + # Start app + dotnet run + ``` + +5. **Access the app** + Open your browser and navigate to `http://localhost:5000`. + +## 📦 Deployment + +* **Frontend**: Hosted on Lite Charms Cloud. +* **Backend**: Hosted on Lite Charms Cloud. +* **Database**: Managed on Lite Charms PVC LXC container settings + +## 🤝 Contributing + +1. Fork the repository. +2. Create a feature branch: `git checkout -b feature/NewFeature`. +3. Commit your changes: `git commit -m 'Add NewFeature'`. +4. Push to the branch: `git push origin feature/NewFeature`. +5. Open a Pull Request. + +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/midrandbooksapi-uat.yml b/midrandbooksapi-uat.yml new file mode 100644 index 0000000..434a28a --- /dev/null +++ b/midrandbooksapi-uat.yml @@ -0,0 +1,179 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: midrandbooksapi-uat +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: midrandbooksapi-config + namespace: midrandbooksapi-uat +data: + ASPNETCORE_ENVIRONMENT: "Development" + ASPNETCORE_URLS: "http://0.0.0.0:8080" + Monitoring__Address: "http://aspire-dashboard-service.aspire.svc.cluster.local:18889" + Monitoring__ServiceName: "MidrandBooksApi.Uat" + HasherSettings__MinHashLength: "11" + BookshopS3Settings__ServiceUrl: "http://garage.garage.svc.cluster.local:3900" + BookshopS3Settings__Region: "garage" + BookshopS3Settings__BucketName: "bookshop" + BookshopS3Settings__CdnBaseUrl: "https://bookshop.cdn.khongisa.co.za" +--- +apiVersion: v1 +kind: Secret +metadata: + name: midrandbooksapi-secrets + namespace: midrandbooksapi-uat +type: Opaque +data: + connection-string: SG9zdD0xOTIuMTY4LjEuMTcwO0RhdGFiYXNlPW1pZHJhbmRzaG9wLWRldjtVc2VybmFtZT1taWRyYW5kc2hvcC1kZXYtdXNlcjtQYXNzd29yZD1hUFh5a0tnM3RTOWNtRDtQZXJzaXN0IFNlY3VyaXR5IEluZm89VHJ1ZQ== + connection-string-quartz: SG9zdD0xOTIuMTY4LjEuMTcwO0RhdGFiYXNlPXNjaGVkdWxlci1kZXY7VXNlcm5hbWU9c2NoZWR1bGVyLWRldi11c2VyO1Bhc3N3b3JkPWtWVm1vV0tKM3h6Z1FYO1BlcnNpc3QgU2VjdXJpdHkgSW5mbz1UcnVl + aspire-apikey: bWMzRzYzSzJqNVpPRXNpMEFqTW9qTFRYbTFLRVpGY3R6SUlqU3dEaVRHdXQ4cUdTa1B1V3d4R1AxUmJzY0pVbw== + hasher-salt: VEdsbmFIUWdRMmhoY20xekxDQk5hV1J5WVc1a1FtOXZhM01nYldGclpTQnNiM1J6SUc5bUlHMXZibVY1SUdGdVpDQmhjbVVnWVNCemRXTmpaWE56Wm5Wc0lIWnBjbUZzSUhOMGIzSjVJR2x1SUZOdmRYUm9JRUZtY21sallRPT0= + hasher-payfastpassphrase: OUdBSVIwdFdwaFgwcU8= + bookshop-s3-accesskey: R0s1MTRkMmNlOGRjNjkyMzdhMDVjMDFlZWY= + bookshop-s3-secretkey: ZWFhZmVkYTFhZWQ0MDllY2ZlNjA3MTRlY2RhNTQ5YjgyYmRmNWEzZGFmOWYxOGRkNjFmNjZiNDk3M2E2NDgyZQ== +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: midrandbooksapi-pvc + namespace: midrandbooksapi-uat +spec: + accessModes: ["ReadWriteMany"] + storageClassName: nfs-storage + resources: + requests: + storage: 2Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: midrandbooks-api + namespace: midrandbooksapi-uat +spec: + replicas: 2 + selector: + matchLabels: + app: midrandbooks-api + template: + metadata: + labels: + app: midrandbooks-api + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: DoesNotExist + containers: + - name: midrandbooks-api + image: nexus.khongisa.co.za/midrandbooks-api:latest + imagePullPolicy: Always + resources: + limits: + memory: "512Mi" + cpu: "500m" + requests: + memory: "256Mi" + cpu: "100m" + ports: + - containerPort: 8080 + envFrom: + - configMapRef: + name: midrandbooksapi-config + env: + - name: BookshopS3Settings__AccessKey + valueFrom: + secretKeyRef: + name: midrandbooksapi-secrets + key: bookshop-s3-accesskey + - name: BookshopS3Settings__SecretKey + valueFrom: + secretKeyRef: + name: midrandbooksapi-secrets + key: bookshop-s3-secretkey + - name: HasherSettings__Salt + valueFrom: + secretKeyRef: + name: midrandbooksapi-secrets + key: hasher-salt + - name: HasherSettings__PayfastPassphrase + valueFrom: + secretKeyRef: + name: midrandbooksapi-secrets + key: hasher-payfastpassphrase + - name: ConnectionStrings__PostgresScheduler + valueFrom: + secretKeyRef: + name: midrandbooksapi-secrets + key: connection-string-quartz + - name: ConnectionStrings__PostgresMidrandBooks + valueFrom: + secretKeyRef: + name: midrandbooksapi-secrets + key: connection-string + - name: Monitoring__ApiKey + valueFrom: + secretKeyRef: + name: midrandbooksapi-secrets + key: aspire-apikey + volumeMounts: + - name: data + mountPath: /app/wwwroot/content + resources: + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 3 + periodSeconds: 5 + volumes: + - name: data + persistentVolumeClaim: + claimName: midrandbooksapi-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: midrandbooksapi-service + namespace: midrandbooksapi-uat +spec: + type: ClusterIP + selector: + app: midrandbooks-api + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 8080 +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: midrandbooksapi-web-secure + namespace: midrandbooksapi-uat +spec: + entryPoints: + - websecure + routes: + - match: Host(`api.uat.midrandbooks.co.za`) + kind: Rule + services: + - name: midrandbooksapi-service + port: 80 + sticky: + cookie: + name: "lp-sticky-session" + httpOnly: true + secure: true + tls: {} \ No newline at end of file diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..87c9c3d --- /dev/null +++ b/nuget.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file