diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..36a40e0 --- /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 MidrandBooks.slnx + - dotnet build MidrandBooks.slnx -c Release + + - name: dotnet-test + image: mcr.microsoft.com/dotnet/sdk:10.0 + commands: + - dotnet restore MidrandBooks.slnx + - dotnet test MidrandBooks.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 + tags: [ latest, "1.${DRONE_BUILD_NUMBER}" ] + custom_labels: + - org.opencontainers.image.source=https://gitea.khongisa.co.za/litecharms/midrandbooks + - 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.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/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: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/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3d2a03e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /app + +COPY ["nuget.config", "./"] +COPY ["MidrandBookshop/MidrandBookshop.csproj", "MidrandBookshop/"] +RUN dotnet restore "MidrandBookshop/MidrandBookshop.csproj" --configfile nuget.config +COPY . . +RUN dotnet publish "MidrandBookshop/MidrandBookshop.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", "MidrandBookshop.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.slnx b/MidrandBooks.slnx index 5f4eee2..c9c4a0f 100644 --- a/MidrandBooks.slnx +++ b/MidrandBooks.slnx @@ -1,3 +1,9 @@ + + + + + + diff --git a/MidrandBookshop/Components/Layout/MainLayout.razor b/MidrandBookshop/Components/Layout/MainLayout.razor index 78624f3..b7d6acb 100644 --- a/MidrandBookshop/Components/Layout/MainLayout.razor +++ b/MidrandBookshop/Components/Layout/MainLayout.razor @@ -1,13 +1,9 @@ @inherits LayoutComponentBase
- -
- About +

Midrand Books

@@ -15,9 +11,3 @@
- -
- An unhandled error has occurred. - Reload - 🗙 -
diff --git a/MidrandBookshop/Components/Layout/NavMenu.razor b/MidrandBookshop/Components/Layout/NavMenu.razor deleted file mode 100644 index 97f8632..0000000 --- a/MidrandBookshop/Components/Layout/NavMenu.razor +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - diff --git a/MidrandBookshop/Components/Layout/NavMenu.razor.css b/MidrandBookshop/Components/Layout/NavMenu.razor.css deleted file mode 100644 index a2aeace..0000000 --- a/MidrandBookshop/Components/Layout/NavMenu.razor.css +++ /dev/null @@ -1,105 +0,0 @@ -.navbar-toggler { - appearance: none; - cursor: pointer; - width: 3.5rem; - height: 2.5rem; - color: white; - position: absolute; - top: 0.5rem; - right: 1rem; - border: 1px solid rgba(255, 255, 255, 0.1); - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); -} - -.navbar-toggler:checked { - background-color: rgba(255, 255, 255, 0.5); -} - -.top-row { - min-height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.bi { - display: inline-block; - position: relative; - width: 1.25rem; - height: 1.25rem; - margin-right: 0.75rem; - top: -1px; - background-size: cover; -} - -.bi-house-door-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); -} - -.bi-plus-square-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); -} - -.bi-list-nested-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep .nav-link { - color: #d7d7d7; - background: none; - border: none; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - width: 100%; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.37); - color: white; -} - -.nav-item ::deep .nav-link:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -.nav-scrollable { - display: none; -} - -.navbar-toggler:checked ~ .nav-scrollable { - display: block; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .nav-scrollable { - /* Never collapse the sidebar for wide screens */ - display: block; - - /* Allow sidebar to scroll for tall menus */ - height: calc(100vh - 3.5rem); - overflow-y: auto; - } -} diff --git a/MidrandBookshop/Components/Pages/Counter.razor b/MidrandBookshop/Components/Pages/Counter.razor deleted file mode 100644 index 1a4f8e7..0000000 --- a/MidrandBookshop/Components/Pages/Counter.razor +++ /dev/null @@ -1,19 +0,0 @@ -@page "/counter" -@rendermode InteractiveServer - -Counter - -

Counter

- -

Current count: @currentCount

- - - -@code { - private int currentCount = 0; - - private void IncrementCount() - { - currentCount++; - } -} diff --git a/MidrandBookshop/Components/Pages/Home.razor b/MidrandBookshop/Components/Pages/Home.razor index 9001e0b..a4ad784 100644 --- a/MidrandBookshop/Components/Pages/Home.razor +++ b/MidrandBookshop/Components/Pages/Home.razor @@ -1,7 +1,7 @@ @page "/" -Home +Midrand Books -

Hello, world!

+

midrandbooks.co.za

-Welcome to your new app. +Welcome to the Midrand Books online bookstore! We are passionate about providing a wide selection of books to readers of all ages and interests. Whether you're looking for the latest bestsellers, classic literature, or educational resources, we have something for everyone. diff --git a/MidrandBookshop/Components/Pages/Weather.razor b/MidrandBookshop/Components/Pages/Weather.razor deleted file mode 100644 index f437e5e..0000000 --- a/MidrandBookshop/Components/Pages/Weather.razor +++ /dev/null @@ -1,64 +0,0 @@ -@page "/weather" -@attribute [StreamRendering] - -Weather - -

Weather

- -

This component demonstrates showing data.

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ - - - - - - - - - - - @foreach (var forecast in forecasts) - { - - - - - - - } - -
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
-} - -@code { - private WeatherForecast[]? forecasts; - - protected override async Task OnInitializedAsync() - { - // Simulate asynchronous loading to demonstrate streaming rendering - await Task.Delay(500); - - var startDate = DateOnly.FromDateTime(DateTime.Now); - var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; - forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = startDate.AddDays(index), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = summaries[Random.Shared.Next(summaries.Length)] - }).ToArray(); - } - - private class WeatherForecast - { - public DateOnly Date { get; set; } - public int TemperatureC { get; set; } - public string? Summary { get; set; } - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - } -} diff --git a/MidrandBookshop/MidrandBookshop.csproj b/MidrandBookshop/MidrandBookshop.csproj index b5f6af0..0e65ae5 100644 --- a/MidrandBookshop/MidrandBookshop.csproj +++ b/MidrandBookshop/MidrandBookshop.csproj @@ -5,6 +5,7 @@ enable enable true + 63d55865-dbfb-40d6-b6df-9bf6d14d15ca diff --git a/MidrandBookshop/appsettings.json b/MidrandBookshop/appsettings.json index 10f68b8..bd579ab 100644 --- a/MidrandBookshop/appsettings.json +++ b/MidrandBookshop/appsettings.json @@ -1,8 +1,22 @@ { + "Email": { + "Credentials": { + "Username": "shop@litecharms.co.za" + }, + "Port": 465, + "Host": "mail.litecharms.co.za", + "UseSsl": true + }, + "Monitoring": { + "ApiKey": "", + "Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889", + "ServiceName": "LiteCharms.Shop" + }, "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Error" } }, "AllowedHosts": "*" diff --git a/README.md b/README.md index 944d078..6b81ea2 100644 --- a/README.md +++ b/README.md @@ -1 +1,78 @@ -# MidrandBooks \ No newline at end of file +# 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/midrandbooks-uat.yml b/midrandbooks-uat.yml new file mode 100644 index 0000000..4df8bc4 --- /dev/null +++ b/midrandbooks-uat.yml @@ -0,0 +1,160 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: midrandbooks-uat +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: midrandbooks-config + namespace: midrandbooks-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: "MidrandBooks.Uat" +--- +apiVersion: v1 +kind: Secret +metadata: + name: midrandbooks-secrets + namespace: midrandbooks-uat +type: Opaque +data: + connection-string: SG9zdD0xOTIuMTY4LjEuMTcwO0RhdGFiYXNlPW1pZHJhbmRib29rcy1kZXY7VXNlcm5hbWU9bWlkcmFuZGJvb2tzLWRldi11c2VyO1Bhc3N3b3JkPVBCd1pnbzZRS3dHN1ZGO1BlcnNpc3QgU2VjdXJpdHkgSW5mbz1UcnVl + connection-string-quartz: SG9zdD0xOTIuMTY4LjEuMTcwO0RhdGFiYXNlPXNjaGVkdWxlci1kZXY7VXNlcm5hbWU9c2NoZWR1bGVyLWRldi11c2VyO1Bhc3N3b3JkPWtWVm1vV0tKM3h6Z1FYO1BlcnNpc3QgU2VjdXJpdHkgSW5mbz1UcnVl + aspire-apikey: bWMzRzYzSzJqNVpPRXNpMEFqTW9qTFRYbTFLRVpGY3R6SUlqU3dEaVRHdXQ4cUdTa1B1V3d4R1AxUmJzY0pVbw== +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: midrandbooks-pvc + namespace: midrandbooks-uat +spec: + accessModes: ["ReadWriteMany"] + storageClassName: nfs-storage + resources: + requests: + storage: 2Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: midrandbooks + namespace: midrandbooks-uat +spec: + replicas: 1 + selector: + matchLabels: + app: midrandbooks + template: + metadata: + labels: + app: midrandbooks + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: DoesNotExist + containers: + - name: midrandbooks + image: nexus.khongisa.co.za/midrandbooks:latest + imagePullPolicy: Always + resources: + limits: + memory: "512Mi" + cpu: "500m" + requests: + memory: "256Mi" + cpu: "100m" + ports: + - containerPort: 8080 + envFrom: + - configMapRef: + name: midrandbooks-config + env: + - name: ConnectionStrings__PostgresScheduler + valueFrom: + secretKeyRef: + name: midrandbooks-secrets + key: connection-string-quartz + - name: ConnectionStrings__PostgresMidrandshop + valueFrom: + secretKeyRef: + name: midrandbooks-secrets + key: connection-string + - name: Monitoring__Address + valueFrom: + configMapKeyRef: + name: midrandbooks-config + key: Monitoring__Address + - name: Monitoring__ServiceName + valueFrom: + configMapKeyRef: + name: midrandbooks-config + key: Monitoring__ServiceName + - name: Monitoring__ApiKey + valueFrom: + secretKeyRef: + name: midrandbooks-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: midrandbooks-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: midrandbooks-service + namespace: midrandbooks-uat +spec: + type: ClusterIP + selector: + app: midrandbooks + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 8080 +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: midrandbooks-web-secure + namespace: midrandbooks-uat +spec: + entryPoints: + - websecure + routes: + - match: Host(`midrandbooks.co.za`) + kind: Rule + services: + - name: midrandbooks-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