main 6039159c0c63 cached
296 files
828.1 KB
243.0k tokens
343 symbols
1 requests
Download .txt
Showing preview only (916K chars total). Download the full file or copy to clipboard to get everything.
Repository: microsoft/contosotraders-cloudtesting
Branch: main
Commit: 6039159c0c63
Files: 296
Total size: 828.1 KB

Directory structure:
gitextract_0zyx3uia/

├── .azurepipelines/
│   ├── aks-cost-optimization.yml
│   └── contoso-traders-cloud-testing.yml
├── .devcontainer/
│   └── devcontainer.json
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── pull_request_template.md
│   └── workflows/
│       ├── aks-cost-optimization.yml
│       ├── codeql.yml
│       └── contoso-traders-cloud-testing.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── SUPPORT.md
├── demo-scripts/
│   ├── azure-chaos-studio/
│   │   └── walkthrough.md
│   ├── azure-load-testing/
│   │   ├── aks-cost-optimization.md
│   │   ├── private-endpoints.md
│   │   └── walkthrough.md
│   ├── dev-workflow/
│   │   └── walkthrough.md
│   └── testing-with-playwright/
│       └── walkthrough.md
├── docs/
│   ├── architecture/
│   │   ├── .$contoso-traders-enhancements.drawio.bkp
│   │   ├── .$contoso-traders-enhancements.drawio.dtmp
│   │   └── contoso-traders-enhancements.drawio
│   ├── deployment-instructions-azure-pipelines.md
│   ├── deployment-instructions.md
│   └── running-locally.md
├── iac/
│   ├── createPrivateDnsZone.bicep
│   ├── createResourceGroup.bicep
│   ├── createResources.bicep
│   ├── createResources.parameters.json
│   ├── dashboard.json
│   ├── rsa
│   ├── rsa.pub
│   └── scripts/
│       └── enable-static-website.ps1
├── loadtests/
│   ├── contoso-traders-carts-internal.yaml
│   ├── contoso-traders-carts.jmx
│   ├── contoso-traders-carts.yaml
│   ├── contoso-traders-products.jmx
│   └── contoso-traders-products.yaml
└── src/
    ├── .dockerignore
    ├── ContosoTraders.Api.Carts/
    │   ├── ContosoTraders.Api.Carts.csproj
    │   ├── Controllers/
    │   │   ├── ProfilesController.cs
    │   │   └── ShoppingCartController.cs
    │   ├── Dockerfile
    │   ├── Program.cs
    │   ├── Properties/
    │   │   ├── ServiceDependencies/
    │   │   │   └── tailwind-traders-cart - Zip Deploy/
    │   │   │       └── profile.arm.json
    │   │   └── launchSettings.json
    │   └── Usings.cs
    ├── ContosoTraders.Api.Core/
    │   ├── Constants/
    │   │   ├── AuthConstants.cs
    │   │   ├── CosmosConstants.cs
    │   │   ├── KeyVaultConstants.cs
    │   │   └── RequestHeaderConstants.cs
    │   ├── ContosoTraders.Api.Core.csproj
    │   ├── Controllers/
    │   │   └── ContosoTradersControllerBase.cs
    │   ├── DependencyInjection.cs
    │   ├── Exceptions/
    │   │   ├── CartNotFoundException.cs
    │   │   ├── ContosoTradersBaseException.cs
    │   │   ├── MatchingProductsNotFoundException.cs
    │   │   ├── ProductNotFoundException.cs
    │   │   ├── ProfileNotFoundException.cs
    │   │   └── StockNotFoundException.cs
    │   ├── Models/
    │   │   ├── AutoMapperProfile.cs
    │   │   ├── Implementations/
    │   │   │   ├── Dao/
    │   │   │   │   ├── Brand.cs
    │   │   │   │   ├── CartDao.cs
    │   │   │   │   ├── Feature.cs
    │   │   │   │   ├── Product.cs
    │   │   │   │   ├── Profile.cs
    │   │   │   │   ├── StockDao.cs
    │   │   │   │   ├── Tag.cs
    │   │   │   │   └── Type.cs
    │   │   │   └── Dto/
    │   │   │       ├── AccessToken.cs
    │   │   │       ├── CartDto.cs
    │   │   │       ├── ProductDto.cs
    │   │   │       ├── StockDto.cs
    │   │   │       └── TokenRequest.cs
    │   │   └── Interfaces/
    │   │       └── ICosmosDao.cs
    │   ├── Repositories/
    │   │   ├── Implementations/
    │   │   │   ├── CartRepository.cs
    │   │   │   ├── CosmosGenericRepositoryBase.cs
    │   │   │   └── StockRepository.cs
    │   │   ├── Interfaces/
    │   │   │   ├── ICartRepository.cs
    │   │   │   ├── ICosmosGenericRepository.cs
    │   │   │   └── IStockRepository.cs
    │   │   ├── ProductsDbContext.cs
    │   │   └── ProfilesDbContext.cs
    │   ├── Requests/
    │   │   ├── Definitions/
    │   │   │   ├── AddItemToCartRequest.cs
    │   │   │   ├── DecrementStockCountRequest.cs
    │   │   │   ├── GetCartRequest.cs
    │   │   │   ├── GetPopularProductsRequest.cs
    │   │   │   ├── GetProductRequest.cs
    │   │   │   ├── GetProductsRequest.cs
    │   │   │   ├── GetProfileRequest.cs
    │   │   │   ├── GetProfilesRequest.cs
    │   │   │   ├── GetStockRequest.cs
    │   │   │   ├── LoadTestRequest.cs
    │   │   │   ├── PostImageRequest.cs
    │   │   │   ├── RemoveItemFromCartRequest.cs
    │   │   │   ├── SearchTextRequest.cs
    │   │   │   └── UpdateCartItemQuantityRequest.cs
    │   │   ├── Handlers/
    │   │   │   ├── AddItemToCartRequestHandler.cs
    │   │   │   ├── DecrementStockCountRequestHandler.cs
    │   │   │   ├── GetCartRequestHandler.cs
    │   │   │   ├── GetPopularProductsRequestHandler.cs
    │   │   │   ├── GetProductRequestHandler.cs
    │   │   │   ├── GetProductsRequestHandler.cs
    │   │   │   ├── GetProfileRequestHandler.cs
    │   │   │   ├── GetProfilesRequestHandler.cs
    │   │   │   ├── GetStockRequestHandler.cs
    │   │   │   ├── LoadTestRequestHandler.cs
    │   │   │   ├── PostImageRequestHandler.cs
    │   │   │   ├── RemoveItemFromCartRequestHandler.cs
    │   │   │   ├── SearchTextRequestHandler.cs
    │   │   │   └── UpdateCartItemQuantityRequestHandler.cs
    │   │   └── Validators/
    │   │       ├── AddItemToCartRequestValidator.cs
    │   │       ├── DecrementStockCountRequestValidator.cs
    │   │       ├── GetCartRequestValidator.cs
    │   │       ├── GetPopularProductsRequestValidator.cs
    │   │       ├── GetProductRequestValidator.cs
    │   │       ├── GetProductsRequestValidator.cs
    │   │       ├── GetProfileRequestValidator.cs
    │   │       ├── GetProfilesRequestValidator.cs
    │   │       ├── GetStockRequestValidator.cs
    │   │       ├── PostImageRequestValidator.cs
    │   │       ├── RemoveItemFromCartRequestValidator.cs
    │   │       ├── SearchTextRequestValidator.cs
    │   │       └── UpdateCartItemQuantityRequestValidator.cs
    │   ├── Services/
    │   │   ├── ContosoTradersServiceBase.cs
    │   │   ├── Implementations/
    │   │   │   ├── CartService.cs
    │   │   │   ├── ImageAnalysisService.cs
    │   │   │   ├── ImageSearchService.cs
    │   │   │   ├── ProductService.cs
    │   │   │   ├── ProfileService.cs
    │   │   │   └── StockService.cs
    │   │   └── Interfaces/
    │   │       ├── ICartService.cs
    │   │       ├── IImageAnalysisService.cs
    │   │       ├── IImageSearchService.cs
    │   │       ├── IProductService.cs
    │   │       ├── IProfileService.cs
    │   │       └── IStockService.cs
    │   └── Usings.cs
    ├── ContosoTraders.Api.Products/
    │   ├── ContosoTraders.Api.Products.csproj
    │   ├── Controllers/
    │   │   ├── LoginController.cs
    │   │   ├── ProductsController.cs
    │   │   ├── ProfilesController.cs
    │   │   └── StocksController.cs
    │   ├── Dockerfile
    │   ├── Manifests/
    │   │   ├── Certificate.yaml
    │   │   ├── ClusterIssuer.yaml
    │   │   ├── ClusterRole.yaml
    │   │   ├── Deployment.yaml
    │   │   ├── Ingress.yaml
    │   │   ├── NamespaceCertManager.yaml
    │   │   ├── NamespaceChaosTesting.yaml
    │   │   └── Service.yaml
    │   ├── Migration/
    │   │   └── productsdb.sql
    │   ├── Program.cs
    │   ├── Properties/
    │   │   ├── ServiceDependencies/
    │   │   │   ├── tailwind-traders-product - Web Deploy/
    │   │   │   │   └── profile.arm.json
    │   │   │   └── tailwind-traders-product - Web Deploy1/
    │   │   │       └── profile.arm.json
    │   │   └── launchSettings.json
    │   └── Usings.cs
    ├── ContosoTraders.Api.Profiles/
    │   ├── .gitignore
    │   ├── ContosoTraders.Api.Profiles.csproj
    │   ├── Controllers/
    │   │   └── ProfilesController.cs
    │   ├── Properties/
    │   │   ├── serviceDependencies.json
    │   │   └── serviceDependencies.local.json
    │   ├── Usings.cs
    │   └── host.json
    ├── ContosoTraders.Ui.Website/
    │   ├── .gitignore
    │   ├── README.md
    │   ├── package.json
    │   ├── playwright.config.ts
    │   ├── public/
    │   │   ├── browserconfig.xml
    │   │   ├── index.html
    │   │   ├── manifest.json
    │   │   └── site.webmanifest
    │   ├── src/
    │   │   ├── App.css
    │   │   ├── App.js
    │   │   ├── App.test.js
    │   │   ├── actions/
    │   │   │   └── actions.js
    │   │   ├── components/
    │   │   │   ├── Input/
    │   │   │   │   └── checkbox.js
    │   │   │   ├── accordion/
    │   │   │   │   ├── accordion.js
    │   │   │   │   ├── accordion.scss
    │   │   │   │   └── sidebarAccordion.js
    │   │   │   ├── breadcrumb/
    │   │   │   │   ├── breadcrumb.js
    │   │   │   │   └── breadcrumb.scss
    │   │   │   ├── corousel/
    │   │   │   │   ├── corousel.js
    │   │   │   │   └── corousel.scss
    │   │   │   ├── dropdowns/
    │   │   │   │   ├── categories.js
    │   │   │   │   └── categories.scss
    │   │   │   ├── footer/
    │   │   │   │   ├── footer.js
    │   │   │   │   └── footer.scss
    │   │   │   ├── header/
    │   │   │   │   ├── appbar.js
    │   │   │   │   ├── header.js
    │   │   │   │   ├── header.scss
    │   │   │   │   └── headerMessage.js
    │   │   │   ├── imageSlider/
    │   │   │   │   ├── imageSlider.js
    │   │   │   │   └── imageSlider.scss
    │   │   │   ├── loadingSpinner/
    │   │   │   │   ├── loadingSpinner.js
    │   │   │   │   └── loadingSpinner.scss
    │   │   │   ├── minimalSelect/
    │   │   │   │   ├── minimalSelect.js
    │   │   │   │   ├── minimalSelect.scss
    │   │   │   │   └── minimalSelect.styles.js
    │   │   │   ├── productCard/
    │   │   │   │   ├── product.js
    │   │   │   │   └── product.scss
    │   │   │   ├── quantityCounter/
    │   │   │   │   ├── productCounter.js
    │   │   │   │   └── productCounter.scss
    │   │   │   ├── shared/
    │   │   │   │   └── index.js
    │   │   │   ├── slider/
    │   │   │   │   ├── slider.js
    │   │   │   │   └── slider.scss
    │   │   │   └── uploadFile/
    │   │   │       ├── uploadFile.js
    │   │   │       └── uploadFile.scss
    │   │   ├── helpers/
    │   │   │   ├── errorsHandler.js
    │   │   │   ├── localStorage.js
    │   │   │   ├── refreshJWTHelper.js
    │   │   │   ├── toast.js
    │   │   │   └── tokensHelper.js
    │   │   ├── index.css
    │   │   ├── index.js
    │   │   ├── main.scss
    │   │   ├── pages/
    │   │   │   ├── arrivals/
    │   │   │   │   ├── arrivals.js
    │   │   │   │   └── arrivals.scss
    │   │   │   ├── cart/
    │   │   │   │   ├── cart.js
    │   │   │   │   └── cart.scss
    │   │   │   ├── detail/
    │   │   │   │   ├── detail.scss
    │   │   │   │   ├── detailContainer.js
    │   │   │   │   └── productDetails.js
    │   │   │   ├── error/
    │   │   │   │   ├── errorPage.js
    │   │   │   │   └── errorPage.scss
    │   │   │   ├── home/
    │   │   │   │   ├── home.js
    │   │   │   │   ├── home.scss
    │   │   │   │   ├── homeContainer.js
    │   │   │   │   └── sections/
    │   │   │   │       ├── banner.js
    │   │   │   │       ├── finalSection.js
    │   │   │   │       ├── gridSection.js
    │   │   │   │       └── hero.js
    │   │   │   ├── index.js
    │   │   │   ├── legals/
    │   │   │   │   ├── aboutUs.js
    │   │   │   │   ├── legals.scss
    │   │   │   │   ├── refundPolicy.js
    │   │   │   │   └── termsOfService.js
    │   │   │   ├── list/
    │   │   │   │   ├── list.js
    │   │   │   │   ├── list.scss
    │   │   │   │   ├── listContainer.js
    │   │   │   │   └── sections/
    │   │   │   │       ├── banner/
    │   │   │   │       │   └── offerBanner.js
    │   │   │   │       ├── index.js
    │   │   │   │       ├── listAside/
    │   │   │   │       │   └── listAside.js
    │   │   │   │       └── listGrid/
    │   │   │   │           └── listGrid.js
    │   │   │   ├── profile/
    │   │   │   │   ├── myAddressBook.js
    │   │   │   │   ├── myOrders.js
    │   │   │   │   ├── myWishlist.js
    │   │   │   │   ├── personalInformation.js
    │   │   │   │   ├── profile.scss
    │   │   │   │   └── profileForm.js
    │   │   │   └── suggestedProductsList/
    │   │   │       ├── suggestedProductsList.js
    │   │   │       └── suggestedproductslist.scss
    │   │   ├── reducers/
    │   │   │   ├── login.reducer.js
    │   │   │   └── reducers.js
    │   │   ├── reportWebVitals.js
    │   │   ├── services/
    │   │   │   ├── authB2CService.js
    │   │   │   ├── cartService.js
    │   │   │   ├── configService.js
    │   │   │   ├── index.js
    │   │   │   ├── productsService.js
    │   │   │   ├── telemetryClient.js
    │   │   │   └── userService.js
    │   │   ├── setupTests.js
    │   │   ├── store.js
    │   │   ├── styles/
    │   │   │   ├── abstracts/
    │   │   │   │   ├── _variables.scss
    │   │   │   │   └── mixins/
    │   │   │   │       ├── _ellipsis.scss
    │   │   │   │       ├── _font-placeholders.scss
    │   │   │   │       ├── _font-scale.scss
    │   │   │   │       ├── _fonts.scss
    │   │   │   │       └── _loader.scss
    │   │   │   ├── base/
    │   │   │   │   ├── _base.scss
    │   │   │   │   ├── _typography.scss
    │   │   │   │   └── _utilities.scss
    │   │   │   └── vendor/
    │   │   │       └── _normalize.scss
    │   │   └── types/
    │   │       └── types.js
    │   ├── tests/
    │   │   ├── account.ts
    │   │   ├── api/
    │   │   │   ├── cart.spec.ts
    │   │   │   ├── data.spec.ts
    │   │   │   └── products.spec.ts
    │   │   ├── auth.setup.ts
    │   │   ├── cart.spec.ts
    │   │   ├── darkmode.spec.ts
    │   │   ├── discounts.spec.ts
    │   │   ├── fileupload.spec.ts
    │   │   ├── map.spec.ts
    │   │   ├── mocks.spec.ts
    │   │   ├── pages.spec.ts
    │   │   └── test-data.csv
    │   └── tsconfig.json
    ├── ContosoTraders.sln
    └── ContosoTraders.sln.DotSettings

================================================
FILE CONTENTS
================================================

================================================
FILE: .azurepipelines/aks-cost-optimization.yml
================================================
name: aks-cost-optimization

trigger: none

pr: none

variables:
  - group: contosotraders-cloudtesting-variable-group
  - name: ACR_NAME
    value: contosotradersacr
  - name: AKS_CLUSTER_NAME
    value: contoso-traders-aks
  - name: AKS_CPU_LIMIT
    value: 250m
  - name: AKS_MEMORY_LIMIT
    value: 256Mi
  - name: AKS_REPLICAS
    value: "1"
  - name: AKS_SECRET_NAME_ACR_PASSWORD
    value: contoso-traders-acr-password
  - name: KV_NAME
    value: contosotraderskv
  - name: LOAD_TEST_SERVICE_NAME
    value: contoso-traders-loadtest
  - name: PRODUCTS_ACR_REPOSITORY_NAME
    value: contosotradersapiproducts
  - name: RESOURCE_GROUP_NAME
    value: contoso-traders-rg

pool:
  vmImage: ubuntu-latest

stages:
  - stage: default
    jobs:
      - job: aks_cost_optimization
        strategy:
          matrix:
            Cpu250m_Mem256Mi:
              AKS_REPLICAS: "1"
              AKS_CPU_LIMIT: "250m"
              AKS_MEMORY_LIMIT: "256Mi"
            Cpu250m_Mem128Mi:
              AKS_REPLICAS: "1"
              AKS_CPU_LIMIT: "250m"
              AKS_MEMORY_LIMIT: "128Mi"
            Cpu100m_Mem256Mi:
              AKS_REPLICAS: "1"
              AKS_CPU_LIMIT: "100m"
              AKS_MEMORY_LIMIT: "256Mi"
            Cpu100m_Mem128Mi:
              AKS_REPLICAS: "1"
              AKS_CPU_LIMIT: "100m"
              AKS_MEMORY_LIMIT: "128Mi"
          maxParallel: 1
        steps:
          - task: AzureCLI@1
            displayName: get products api endpoint
            name: getProductsApiEndpoint
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=productsApiEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name productsApiEndpoint --query value -o tsv)"
          - task: replacetokens@5
            displayName: substitute tokens in deployment manifest
            inputs:
              targetFiles: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
              tokenPattern: custom
              tokenPrefix: "{"
              tokenSuffix: "}"
              inlineVariables: |
                SUFFIX: "$(SUFFIX)"
                AKS_REPLICAS: "$(AKS_REPLICAS)"
                AKS_CPU_LIMIT: "$(AKS_CPU_LIMIT)"
                AKS_MEMORY_LIMIT: "$(AKS_MEMORY_LIMIT)"
          - task: KubernetesManifest@1
            displayName: apply deployment manifest
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: deploy
              manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
              containers: $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME):latest
              imagePullSecrets: $(AKS_SECRET_NAME_ACR_PASSWORD)
          - task: AzureLoadTest@1
            displayName: load test (products API)
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              # Path of the YAML file. Should be fully qualified path or relative to the default working directory
              loadtestConfigFile: ./loadtests/contoso-traders-products.yaml
              resourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              loadtestResource: $(LOAD_TEST_SERVICE_NAME)$(SUFFIX)
              env: |
                [
                  {
                    "name": "domain",
                    "value": "$(getProductsApiEndpoint.productsApiEndpoint)"
                  },
                  {
                    "name": "protocol",
                    "value": "https"
                  },
                  {
                    "name": "path",
                    "value": "v1/Products/1"
                  },
                  {
                    "name": "threads_per_engine",
                    "value": "25"
                  },
                  {
                    "name": "ramp_up_time",
                    "value": "0"
                  },
                  {
                    "name": "duration_in_sec",
                    "value": "45"
                  }
                ]

      - job: reset_aks
        dependsOn: [aks_cost_optimization]
        condition: always()
        steps:
          - task: AzureCLI@1
            displayName: get products api endpoint
            name: getProductsApiEndpoint
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=productsApiEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name productsApiEndpoint --query value -o tsv)"
          - task: replacetokens@5
            displayName: substitute tokens in deployment manifest
            inputs:
              targetFiles: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
              tokenPattern: custom
              tokenPrefix: "{"
              tokenSuffix: "}"
              inlineVariables: |
                SUFFIX: "$(SUFFIX)"
                AKS_REPLICAS: "$(AKS_REPLICAS)"
                AKS_CPU_LIMIT: "$(AKS_CPU_LIMIT)"
                AKS_MEMORY_LIMIT: "$(AKS_MEMORY_LIMIT)"
          - task: KubernetesManifest@1
            displayName: apply deployment manifest
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: deploy
              manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
              containers: $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME):latest
              imagePullSecrets: $(AKS_SECRET_NAME_ACR_PASSWORD)


================================================
FILE: .azurepipelines/contoso-traders-cloud-testing.yml
================================================
name: contoso-traders-cloud-testing

trigger:
  - main

pr:
  branches:
    include:
      - main
  paths:
    exclude:
      - "docs/**"
      - "demo-scripts/**"

variables:
  - group: contosotraders-cloudtesting-variable-group
  - name: ACR_NAME
    value: contosotradersacr
  - name: AKS_CLUSTER_NAME
    value: contoso-traders-aks
  - name: AKS_CPU_LIMIT
    value: 250m
  - name: AKS_DNS_LABEL
    value: contoso-traders-products
  - name: AKS_MEMORY_LIMIT
    value: 256Mi
  - name: AKS_NODES_RESOURCE_GROUP_NAME
    value: contoso-traders-aks-nodes-rg
  - name: AKS_REPLICAS
    value: "1"
  - name: AKS_SECRET_NAME_ACR_PASSWORD
    value: contoso-traders-acr-password
  - name: AKS_SECRET_NAME_KV_ENDPOINT
    value: contoso-traders-kv-endpoint
  - name: AKS_SECRET_NAME_MI_CLIENTID
    value: contoso-traders-mi-clientid
  - name: AZURE_AD_APP_NAME
    value: contoso-traders-cloud-testing-app
  - name: CARTS_ACA_NAME
    value: contoso-traders-carts
  - name: CARTS_ACR_REPOSITORY_NAME
    value: contosotradersapicarts
  - name: CARTS_INTERNAL_ACA_NAME
    value: contoso-traders-intcarts
  - name: CDN_PROFILE_NAME
    value: contoso-traders-cdn
  - name: CHAOS_AKS_EXPERIMENT_NAME
    value: contoso-traders-chaos-aks-experiment
  - name: KV_NAME
    value: contosotraderskv
  - name: LOAD_TEST_SERVICE_NAME
    value: contoso-traders-loadtest
  - name: MSGRAPH_API_ID
    value: 00000003-0000-0000-c000-000000000000
  - name: MSGRAPH_API_PERMISSION_EMAIL
    value: 64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0=Scope
  - name: MSGRAPH_API_PERMISSION_USER_READ
    value: e1fe6dd8-ba31-4d61-89e7-88639da4683d=Scope
  - name: PRODUCTS_ACR_REPOSITORY_NAME
    value: contosotradersapiproducts
  - name: PRODUCTS_DB_NAME
    value: productsdb
  - name: PRODUCTS_DB_SERVER_NAME
    value: contoso-traders-products
  - name: PRODUCTS_DB_USER_NAME
    value: localadmin
  - name: PRODUCT_DETAILS_CONTAINER_NAME
    value: product-details
  - name: PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME
    value: contosotradersimg
  - name: PRODUCT_LIST_CONTAINER_NAME
    value: product-list
  - name: PRODUCTS_CDN_ENDPOINT_NAME
    value: contoso-traders-images
  - name: RESOURCE_GROUP_NAME
    value: contoso-traders-rg
  - name: STORAGE_ACCOUNT_NAME
    value: contosotradersimg
  - name: UI_CDN_ENDPOINT_NAME
    value: contoso-traders-ui2
  - name: UI_STORAGE_ACCOUNT_NAME
    value: contosotradersui2
  - name: USER_ASSIGNED_MANAGED_IDENTITY_NAME
    value: contoso-traders-mi-kv-access

pool:
  vmImage: ubuntu-latest

stages:
  - stage: default
    jobs:
      - job: provision
        steps:
          # section #0: optional configuration of the Azure AD app.
          # create the Azure AD application (and update it if it already exists).
          # note: This is an idempotent operation.
          - task: AzureCLI@1
            displayName: create/update azure active directory app
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az ad app create --display-name $(AZURE_AD_APP_NAME)$(SUFFIX) --sign-in-audience AzureADandPersonalMicrosoftAccount
          - task: AzureCLI@1
            displayName: get azure ad app's object id
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
            name: getAzureAdAppObjId
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=azureAdAppObjId;isOutput=true]$(az ad app list --display-name $(AZURE_AD_APP_NAME)$(SUFFIX) --query [].id -o tsv)"
          - task: AzureCLI@1
            displayName: get azure ad app's client id
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
            name: getAzureAdAppClientId
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=azureAdAppClientId;isOutput=true]$(az ad app list --display-name $(AZURE_AD_APP_NAME)$(SUFFIX) --query [].appId -o tsv)"
          - task: AzureCLI@1
            displayName: register app as a spa
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: |
                az rest \
                  --method PATCH \
                  --uri https://graph.microsoft.com/v1.0/applications/$(getAzureAdAppObjId.azureAdAppObjId) \
                  --headers 'Content-Type=application/json' \
                  --body '{"spa":{"redirectUris":["https://localhost:3000/authcallback","http://localhost:3000/authcallback","https://production.contosotraders.com/authcallback","https://cloudtesting.contosotraders.com/authcallback"]}}'
          - task: AzureCLI@1
            displayName: enable issuance of id, access tokens
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az ad app update --id $(getAzureAdAppObjId.azureAdAppObjId) --enable-access-token-issuance true --enable-id-token-issuance true
          - task: AzureCLI@1
            displayName: enable email claim in access token
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az ad app update --id $(getAzureAdAppObjId.azureAdAppObjId) --optional-claims "{\"accessToken\":[{\"name\":\"email\",\"essential\":false}]}"
          # note: requesting MS Graph permissions in Azure AD app unfortunately isn't idempotent.
          # Even, if you have already requested the permissions, it'll keep adding to the list of requested permissions until you hit limit on max permissions requested.
          # Details: https://github.com/Azure/azure-cli/issues/24512
          - task: AzureCLI@1
            displayName: delete any requested Microsoft Graph permissions
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: |
                az ad app permission delete \
                  --id $(getAzureAdAppObjId.azureAdAppObjId) \
                  --api $(MSGRAPH_API_ID)
          - task: AzureCLI@1
            displayName: request Microsoft Graph permissions
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: |
                az ad app permission add \
                  --id $(getAzureAdAppObjId.azureAdAppObjId) \
                  --api $(MSGRAPH_API_ID) \
                  --api-permissions $(MSGRAPH_API_PERMISSION_USER_READ) $(MSGRAPH_API_PERMISSION_EMAIL)

          #
          # section #1: provisioning the resources on Azure using bicep templates
          #
          # The first step is to create the resource group: `contoso-traders-rg`.
          # The below step can also be manually executed as follows:
          # az deployment sub create --location {LOCATION} --template-file .\createResourceGroup.bicep
          # Note: You can specify any location for `{LOCATION}`. It's the region where the deployment metadata will be stored, and not
          # where the resource groups will be deployed.
          - task: AzureResourceManagerTemplateDeployment@3
            displayName: create resource group
            inputs:
              deploymentScope: Subscription
              azureResourceManagerConnection: SERVICEPRINCIPAL
              location: $(DEPLOYMENTREGION)
              csmFile: ./iac/createResourceGroup.bicep
              overrideParameters: -rgName $(RESOURCE_GROUP_NAME) -suffix $(SUFFIX) -rgLocation $(DEPLOYMENTREGION)
          # Next step is to deploy the Azure resources to the resource group `contoso-traders-rg` created above. The deployed resources
          # include storage accounts, function apps, app services cosmos db, and service bus etc.
          # The below step can also be manually executed as follows:
          # az deployment group create -g contoso-traders-rg --template-file .\createResources.bicep --parameters .\createResources.parameters.json
          # Note: The `createResources.parameters.json` file contains the parameters for the deployment; specifically the environment name.
          # You can modify the parameters to customize the deployment.
          # Note: The bicep template outputs are not shown in the logs. You can extract the outputs as shown here:
          # https://github.com/Azure/arm-deploy#another-example-on-how-to-use-this-action-to-get-the-output-of-arm-template
          - task: AzureResourceManagerTemplateDeployment@3
            displayName: create resources
            inputs:
              deploymentScope: Resource Group
              azureResourceManagerConnection: SERVICEPRINCIPAL
              location: $(DEPLOYMENTREGION)
              resourceGroupName: "$(RESOURCE_GROUP_NAME)$(SUFFIX)"
              csmFile: ./iac/createResources.bicep
              csmParametersFile: ./iac/createResources.parameters.json
              overrideParameters: -suffix $(SUFFIX) -sqlPassword $(SQLPASSWORD) -deployPrivateEndpoints $(DEPLOYPRIVATEENDPOINTS)

          # Add the logged-in service principal to the key vault access policy
          - task: AzureCLI@1
            displayName: add service principal to kv access policy
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az keyvault set-policy -n $(KV_NAME)$(SUFFIX) -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --secret-permissions get list set --object-id $(az ad sp show --id $(az account show --query "user.name" -o tsv) --query "id" -o tsv)
          # The AKS agent pool needs to be assigned the user-assigned managed identity created (which has kv access)
          - task: AzureCLI@1
            displayName: assign user-assigned managed-identity to aks agentpool
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: |
                az vmss identity assign \
                  --identities $(az identity show -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(USER_ASSIGNED_MANAGED_IDENTITY_NAME)$(SUFFIX) --query "id" -o tsv) \
                  --ids $(az vmss list -g $(AKS_NODES_RESOURCE_GROUP_NAME)$(SUFFIX) --query "[0].id" -o tsv) \
          # Seed the DBs and storage accounts
          - script: sqlcmd -S $(PRODUCTS_DB_SERVER_NAME)$(SUFFIX).database.windows.net -U $(PRODUCTS_DB_USER_NAME) -P $(SQLPASSWORD) -d $(PRODUCTS_DB_NAME) -i ./src/ContosoTraders.Api.Products/Migration/productsdb.sql
            displayName: seed products db
          - task: AzureCLI@1
            displayName: seed product image (product details)
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az storage blob sync --account-name '$(PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME)$(SUFFIX)' -c '$(PRODUCT_DETAILS_CONTAINER_NAME)' -s 'src/ContosoTraders.Api.Images/product-details'
          - task: AzureCLI@1
            displayName: seed product image (product list)
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az storage blob sync --account-name '$(PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME)$(SUFFIX)' -c '$(PRODUCT_LIST_CONTAINER_NAME)' -s 'src/ContosoTraders.Api.Images/product-list'
          - task: AzureCLI@1
            displayName: purge product images cdn endpoint
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az cdn endpoint purge --no-wait --content-paths '/*' -n '$(PRODUCTS_CDN_ENDPOINT_NAME)$(SUFFIX)' -g '$(RESOURCE_GROUP_NAME)$(SUFFIX)' --profile-name '$(CDN_PROFILE_NAME)$(SUFFIX)'
          - task: AzureCLI@1
            displayName: extract acr password
            name: extractAcrPassword
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=acrPassword;isOutput=true;issecret=true]$(az acr credential show -n $(ACR_NAME)$(SUFFIX) -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --query "passwords[0].value" --output tsv)"
          - script: docker login $(ACR_NAME)$(SUFFIX).azurecr.io --username $(ACR_NAME)$(SUFFIX) --password $(extractAcrPassword.acrPassword)
            displayName: azure container registry login
          - task: AzureCLI@1
            displayName: set aks context
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az aks get-credentials --resource-group $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(AKS_CLUSTER_NAME)$(SUFFIX)

          #
          # section #2: deploy the carts api
          #
          - script: docker build src -f ./src/ContosoTraders.Api.Carts/Dockerfile -t $(ACR_NAME)$(SUFFIX).azurecr.io/$(CARTS_ACR_REPOSITORY_NAME):latest -t $(ACR_NAME)$(SUFFIX).azurecr.io/$(CARTS_ACR_REPOSITORY_NAME):$(Build.SourceVersion)
            displayName: docker build
          - script: docker push --all-tags $(ACR_NAME)$(SUFFIX).azurecr.io/$(CARTS_ACR_REPOSITORY_NAME)
            displayName: docker push (to acr)
          - task: AzureCLI@1
            displayName: deploy to aca
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: |
                az config set extension.use_dynamic_install=yes_without_prompt
                az containerapp update -n $(CARTS_ACA_NAME)$(SUFFIX) -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --image $(ACR_NAME)$(SUFFIX).azurecr.io/$(CARTS_ACR_REPOSITORY_NAME):$(Build.SourceVersion)
          - task: AzureCLI@1
            displayName: deploy to aca (internal)
            condition: eq(variables['DEPLOYPRIVATEENDPOINTS'], 'true')
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: |
                az config set extension.use_dynamic_install=yes_without_prompt
                az containerapp update -n $(CARTS_INTERNAL_ACA_NAME)$(SUFFIX) -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --image $(ACR_NAME)$(SUFFIX).azurecr.io/$(CARTS_ACR_REPOSITORY_NAME):$(Build.SourceVersion)
          - task: AzureCLI@1
            displayName: get carts api endpoint
            name: getCartsApiEndpoint
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=cartsApiEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name cartsApiEndpoint --query value -o tsv)"

          #
          # section #3: deploy the products api
          #
          - task: HelmInstaller@1
            displayName: install helm
            name: installHelm
            inputs:
              helmVersionToInstall: 3.9.0
          - script: docker build src -f ./src/ContosoTraders.Api.Products/Dockerfile -t $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME):latest -t $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME):$(Build.SourceVersion)
            displayName: docker build
          - script: docker push --all-tags $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME)
            displayName: docker push (to acr)
          # more details: https://stackoverflow.com/a/45881324
          - script: kubectl delete secret $(AKS_SECRET_NAME_ACR_PASSWORD) --ignore-not-found
            displayName: delete any existing kubernetes secret (acr password)
          - script: |
              kubectl create secret docker-registry $(AKS_SECRET_NAME_ACR_PASSWORD) \
                --docker-server=$(ACR_NAME)$(SUFFIX).azurecr.io \
                --docker-username=$(ACR_NAME)$(SUFFIX) \
                --docker-password=$(extractAcrPassword.acrPassword) \
                --save-config \
                -o yaml | kubectl apply -f -
            displayName: create kubernetes secret (acr password)
          - task: AzureCLI@1
            displayName: get managedIdentityClientId
            name: getManagedIdentityClientId
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=managedIdentityClientId;isOutput=true]$(az identity show -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(USER_ASSIGNED_MANAGED_IDENTITY_NAME)$(SUFFIX) --query "clientId" -o tsv)"
          - task: KubernetesManifest@1
            displayName: create kubernetes secret (kv endpoint)
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: createSecret
              secretName: $(AKS_SECRET_NAME_KV_ENDPOINT)
              secretType: generic
              secretArguments: --from-literal=$(AKS_SECRET_NAME_KV_ENDPOINT)="https://$(KV_NAME)$(SUFFIX).vault.azure.net/"
          - task: KubernetesManifest@1
            displayName: create kubernetes secret (managed identity client id)
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: createSecret
              secretName: $(AKS_SECRET_NAME_MI_CLIENTID)
              secretType: generic
              secretArguments: --from-literal=$(AKS_SECRET_NAME_MI_CLIENTID)=$(getManagedIdentityClientId.managedIdentityClientId)
          - task: replacetokens@5
            displayName: substitute tokens in deployment manifest
            inputs:
              targetFiles: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
              tokenPattern: custom
              tokenPrefix: "{"
              tokenSuffix: "}"
              inlineVariables: |
                SUFFIX: "$(SUFFIX)"
                AKS_REPLICAS: "$(AKS_REPLICAS)"
                AKS_CPU_LIMIT: "$(AKS_CPU_LIMIT)"
                AKS_MEMORY_LIMIT: "$(AKS_MEMORY_LIMIT)"
          - task: KubernetesManifest@1
            displayName: apply deployment manifest
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: deploy
              manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
              containers: $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME):$(Build.SourceVersion)
              imagePullSecrets: $(AKS_SECRET_NAME_ACR_PASSWORD)
          - task: KubernetesManifest@1
            displayName: apply service manifest
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: deploy
              manifests: ./src/ContosoTraders.Api.Products/Manifests/Service.yaml
          # setup chaos mesh
          - task: KubernetesManifest@1
            displayName: apply namespace manifest (chaos-testing)
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: deploy
              manifests: ./src/ContosoTraders.Api.Products/Manifests/NamespaceChaosTesting.yaml
          - script: |
              az aks get-credentials --resource-group $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(AKS_CLUSTER_NAME)$(SUFFIX)
              helm repo add chaos-mesh https://charts.chaos-mesh.org
              helm repo update
              helm upgrade --install chaos-mesh chaos-mesh/chaos-mesh --namespace=chaos-testing --set chaosDaemon.runtime=containerd --set chaosDaemon.socketPath=/run/containerd/containerd.sock
            displayName: setup chaos mesh
          # create the ingress controller
          - script: |
              az aks get-credentials --resource-group $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(AKS_CLUSTER_NAME)$(SUFFIX)
              helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
              helm repo update
              helm upgrade --install --wait --timeout=1h nginx-ingress ingress-nginx/ingress-nginx \
                --set controller.replicaCount=1 \
                --set controller.nodeSelector."kubernetes\.io/os"=linux \
                --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \
                --set controller.admissionWebhooks.patch.nodeSelector."kubernetes\.io/os"=linux \
                --set controller.service.externalTrafficPolicy=Local
            displayName: create ingress controller
          - task: AzureCLI@1
            displayName: set dns label on public ip
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az network public-ip update --dns-name $(AKS_DNS_LABEL)$(SUFFIX) -g $(AKS_NODES_RESOURCE_GROUP_NAME)$(SUFFIX) -n $(az network public-ip list --query "[?starts_with(name,'kubernetes-') ].name" -o tsv -g $(AKS_NODES_RESOURCE_GROUP_NAME)$(SUFFIX))
          # hack: extract the full fqdn / dns label of the aks app's public IP address
          - task: AzureCLI@1
            displayName: get aks-fqdn
            name: getAksFqdn
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              # note: There should be a whitespace between ')' and ']'. More details: https://stackoverflow.com/a/59154958
              inlineScript: echo "##vso[task.setvariable variable=aksFqdn;isOutput=true]$(az network public-ip list --query "[?starts_with(name,'kubernetes-') ].dnsSettings.fqdn" -o tsv -g $(AKS_NODES_RESOURCE_GROUP_NAME)$(SUFFIX))"
          # install cert-manager
          - task: KubernetesManifest@1
            displayName: apply namespace manifest (cert-manager)
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: deploy
              manifests: ./src/ContosoTraders.Api.Products/Manifests/NamespaceCertManager.yaml
          - script: |
              az aks get-credentials --resource-group $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(AKS_CLUSTER_NAME)$(SUFFIX)
              kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.yaml
            displayName: install cert-manager
          - bash: sleep 30s
            displayName: sleep for 30 seconds
          - task: KubernetesManifest@1
            displayName: apply clusterIssuer manifest
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: deploy
              manifests: ./src/ContosoTraders.Api.Products/Manifests/ClusterIssuer.yaml
          - task: replacetokens@5
            displayName: substitute tokens in certificate manifest
            inputs:
              targetFiles: ./src/ContosoTraders.Api.Products/Manifests/Certificate.yaml
              tokenPattern: custom
              tokenPrefix: "{"
              tokenSuffix: "}"
              inlineVariables: "AKS_FQDN: $(getAksFqdn.aksFqdn)"
          - task: KubernetesManifest@1
            displayName: apply certificate manifest
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: deploy
              manifests: ./src/ContosoTraders.Api.Products/Manifests/Certificate.yaml
          - task: replacetokens@5
            displayName: substitute tokens in ingress manifest
            inputs:
              targetFiles: ./src/ContosoTraders.Api.Products/Manifests/Ingress.yaml
              tokenPattern: custom
              tokenPrefix: "{"
              tokenSuffix: "}"
              inlineVariables: "AKS_FQDN: $(getAksFqdn.aksFqdn)"
          - task: KubernetesManifest@1
            displayName: apply ingress manifest
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: deploy
              manifests: ./src/ContosoTraders.Api.Products/Manifests/Ingress.yaml
          - task: KubernetesManifest@1
            displayName: apply clusterRole manifest
            inputs:
              connectionType: azureResourceManager
              azureSubscriptionConnection: SERVICEPRINCIPAL
              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)
              action: deploy
              manifests: ./src/ContosoTraders.Api.Products/Manifests/ClusterRole.yaml
          - task: AzureCLI@1
            displayName: set productsApiEndpoint in kv
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az keyvault secret set --vault-name $(KV_NAME)$(SUFFIX) --name productsApiEndpoint --value $(getAksFqdn.aksFqdn) --description "endpoint url (fqdn) of the products api"
          - task: AzureCLI@1
            displayName: get products api endpoint
            name: getProductsApiEndpoint
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=productsApiEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name productsApiEndpoint --query value -o tsv)"

          #
          # section #4: deploy the ui
          #
          - script: echo "##vso[task.setvariable variable=REACT_APP_APIURLSHOPPINGCART]https://$(getCartsApiEndpoint.cartsApiEndpoint)/v1"
            displayName: set REACT_APP_APIURLSHOPPINGCART
          - script: echo "##vso[task.setvariable variable=REACT_APP_APIURL]https://$(getProductsApiEndpoint.productsApiEndpoint)/v1"
            displayName: set REACT_APP_APIURL
          - script: echo "##vso[task.setvariable variable=REACT_APP_B2CCLIENTID]$(getAzureAdAppClientId.azureAdAppClientId)"
            displayName: set REACT_APP_B2CCLIENTID
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
          - task: NodeTool@0
            displayName: install nodejs
            inputs:
              versionSpec: 18.x
          - script: npm ci
            displayName: npm ci
            workingDirectory: src/ContosoTraders.Ui.Website
          - script: echo "##vso[task.setvariable variable=REACT_APP_BINGMAPSKEY]$(BINGMAPSKEY)"
            displayName: set REACT_APP_BINGMAPSKEY            
          - script: npm run build
            displayName: npm run build
            workingDirectory: src/ContosoTraders.Ui.Website
          - task: AzureCLI@1
            displayName: deploy ui to storage
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az storage blob sync --account-name '$(UI_STORAGE_ACCOUNT_NAME)$(SUFFIX)' -c '$web' -s 'src/ContosoTraders.Ui.Website/build'
          - task: AzureCLI@1
            displayName: purge ui cdn endpoint
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az cdn endpoint purge --no-wait --content-paths '/*' -n '$(UI_CDN_ENDPOINT_NAME)$(SUFFIX)' -g '$(RESOURCE_GROUP_NAME)$(SUFFIX)' --profile-name '$(CDN_PROFILE_NAME)$(SUFFIX)'
          - task: AzureCLI@1
            displayName: get ui cdn endpoint
            name: getUiCdnEndpoint
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=uiCdnEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name uiCdnEndpoint --query value -o tsv)"
          - task: AzureCLI@1
            displayName: register auth callback (UI CDN)
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: |
                az rest \
                  --method PATCH \
                  --uri https://graph.microsoft.com/v1.0/applications/$(getAzureAdAppObjId.azureAdAppObjId) \
                  --headers 'Content-Type=application/json' \
                  --body '{"spa":{"redirectUris":["https://localhost:3000/authcallback","http://localhost:3000/authcallback","https://staging.contosotraders.com/authcallback","https://production.contosotraders.com/authcallback","https://cloudtesting.contosotraders.com/authcallback","https://$(getUiCdnEndpoint.uiCdnEndpoint)/authcallback"]}}'
          - task: AzureCLI@1
            displayName: display ui cdn endpoint
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo UI CDN endpoint accessible at https://$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name uiCdnEndpoint --query value -o tsv)

      - job: load_tests_with_chaos_products_api
        dependsOn: [provision, playwright_tests_ui]
        variables:
          productsApiEndpoint: $[ dependencies.provision.outputs['getProductsApiEndpoint.productsApiEndpoint'] ]
        steps:
          - task: AzureCLI@1
            displayName: get chaos experiment resource id
            name: getChaosAksExperimentResourceId
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=chaosAksExperimentResourceId;isOutput=true]$(az resource show --resource-group $(RESOURCE_GROUP_NAME)$(SUFFIX) --namespace Microsoft.Chaos --resource-type Experiments --name $(CHAOS_AKS_EXPERIMENT_NAME)$(SUFFIX) --query "id" -o tsv)"
          - task: AzureCLI@1
            displayName: start chaos experiment (pod failure)
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: az rest --method post --uri https://management.azure.com$(getChaosAksExperimentResourceId.chaosAksExperimentResourceId)/start?api-version=2021-09-15-preview
          - bash: sleep 30s
            displayName: sleep for 30 seconds
          - task: AzureLoadTest@1
            displayName: load test (products API)
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              # Path of the YAML file. Should be fully qualified path or relative to the default working directory
              loadtestConfigFile: ./loadtests/contoso-traders-products.yaml
              resourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              loadtestResource: $(LOAD_TEST_SERVICE_NAME)$(SUFFIX)
              env: |
                [
                  {
                    "name": "domain",
                    "value": "$(productsApiEndpoint)"
                  },
                  {
                    "name": "protocol",
                    "value": "https"
                  },
                  {
                    "name": "path",
                    "value": "v1/Products/1"
                  },
                  {
                    "name": "threads_per_engine",
                    "value": "5"
                  },
                  {
                    "name": "ramp_up_time",
                    "value": "0"
                  },
                  {
                    "name": "duration_in_sec",
                    "value": "120"
                  }
                ]

      - job: load_tests_carts_internal_api
        condition: eq(variables['DEPLOYPRIVATEENDPOINTS'], 'true')
        dependsOn: [provision, playwright_tests_ui]
        steps:
          - task: AzureCLI@1
            displayName: get carts api endpoint (internal)
            name: getCartsInternalApiEndpoint
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=cartsInternalApiEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name cartsInternalApiEndpoint --query value -o tsv)"
          - task: AzureCLI@1
            displayName: get vnetAcaSubnetId
            name: getVnetAcaSubnetId
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              scriptLocation: inlineScript
              inlineScript: echo "##vso[task.setvariable variable=vnetAcaSubnetId;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) -n vnetAcaSubnetId --query "value" -o tsv)"
          - task: replacetokens@5
            displayName: substitute tokens in load test config file
            inputs:
              targetFiles: ./loadtests/contoso-traders-carts-internal.yaml
              tokenPattern: doublebraces
              inlineVariables: "LOAD_TEST_SUBNET_ID: $(getVnetAcaSubnetId.vnetAcaSubnetId)"
          - task: AzureLoadTest@1
            displayName: load test (carts API)
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              # Path of the YAML file. Should be fully qualified path or relative to the default working directory
              loadtestConfigFile: ./loadtests/contoso-traders-carts-internal.yaml
              resourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              loadtestResource: $(LOAD_TEST_SERVICE_NAME)$(SUFFIX)
              env: |
                [
                  {
                    "name": "domain",
                    "value": "$(getCartsInternalApiEndpoint.cartsInternalApiEndpoint)"
                  },
                  {
                    "name": "protocol",
                    "value": "https"
                  },
                  {
                    "name": "path",
                    "value": "v1/ShoppingCart/loadtest"
                  },
                  {
                    "name": "threads_per_engine",
                    "value": "5"
                  },
                  {
                    "name": "ramp_up_time",
                    "value": "0"
                  },
                  {
                    "name": "duration_in_sec",
                    "value": "120"
                  }
                ]

      - job: load_tests_carts_api
        dependsOn: [provision, playwright_tests_ui]
        variables:
          cartsApiEndpoint: $[ dependencies.provision.outputs['getCartsApiEndpoint.cartsApiEndpoint'] ]
        steps:
          - task: AzureLoadTest@1
            displayName: load test (carts API)
            inputs:
              azureSubscription: SERVICEPRINCIPAL
              # Path of the YAML file. Should be fully qualified path or relative to the default working directory
              loadtestConfigFile: ./loadtests/contoso-traders-carts.yaml
              resourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)
              loadtestResource: $(LOAD_TEST_SERVICE_NAME)$(SUFFIX)
              env: |
                [
                  {
                    "name": "domain",
                    "value": "$(cartsApiEndpoint)"
                  },
                  {
                    "name": "protocol",
                    "value": "https"
                  },
                  {
                    "name": "path",
                    "value": "v1/ShoppingCart/loadtest"
                  },
                  {
                    "name": "threads_per_engine",
                    "value": "5"
                  },
                  {
                    "name": "ramp_up_time",
                    "value": "0"
                  },
                  {
                    "name": "duration_in_sec",
                    "value": "120"
                  }
                ]

      - job: playwright_tests_ui
        dependsOn: [provision]
        container: mcr.microsoft.com/playwright:v1.43.1-jammy
        variables:
          APIURLSHOPPINGCART: $[ dependencies.provision.outputs['getCartsApiEndpoint.cartsApiEndpoint'] ]
          APIURL: $[ dependencies.provision.outputs['getProductsApiEndpoint.productsApiEndpoint'] ]
          BASEURLFORPLAYWRIGHTTESTING: $[ dependencies.provision.outputs['getUiCdnEndpoint.uiCdnEndpoint'] ]
          B2CCLIENTID: $[ dependencies.provision.outputs['getAzureAdAppClientId.azureAdAppClientId'] ]
        steps:
          - task: NodeTool@0
            displayName: install nodejs
            inputs:
              versionSpec: 18.x
          - script: echo "##vso[task.setvariable variable=REACT_APP_APIURLSHOPPINGCART]https://$(APIURLSHOPPINGCART)/v1"
            displayName: set REACT_APP_APIURLSHOPPINGCART
          - script: echo "##vso[task.setvariable variable=REACT_APP_APIURL]https://$(APIURL)/v1"
            displayName: set REACT_APP_APIURL
          - script: echo "##vso[task.setvariable variable=REACT_APP_BASEURLFORPLAYWRIGHTTESTING]https://$(BASEURLFORPLAYWRIGHTTESTING)"
            displayName: set REACT_APP_BASEURLFORPLAYWRIGHTTESTING
          - script: echo "##vso[task.setvariable variable=REACT_APP_B2CCLIENTID]$(B2CCLIENTID)"
            displayName: set REACT_APP_B2CCLIENTID
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
          - script: echo "##vso[task.setvariable variable=REACT_APP_AADUSERNAME]$(AADUSERNAME)"
            displayName: set REACT_APP_AADUSERNAME
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
          - script: echo "##vso[task.setvariable variable=REACT_APP_AADPASSWORD]$(AADPASSWORD)"
            displayName: set REACT_APP_AADPASSWORD
            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))
          - script: npm ci
            displayName: install dependencies
            workingDirectory: src/ContosoTraders.Ui.Website
          - script: |
              echo "##vso[task.setvariable variable=CI;]1"
              npx playwright test
            displayName: run playwright tests
            workingDirectory: src/ContosoTraders.Ui.Website
            timeoutInMinutes: 20
          - task: PublishTestResults@2
            displayName: publish test results
            condition: succeededOrFailed()
            inputs:
              searchFolder: src/ContosoTraders.Ui.Website/playwright-report-junit
              testResultsFormat: JUnit
              testResultsFiles: e2e-junit-results.xml
              mergeTestResults: true
              failTaskOnFailedTests: true
              testRunTitle: Playwright Tests
          - task: PublishBuildArtifacts@1
            displayName: upload playwright report
            condition: failed()
            inputs:
              PathtoPublish: 'src/ContosoTraders.Ui.Website/playwright-report'
              ArtifactName: 'playwright-report'
              publishLocation: 'Container'              


================================================
FILE: .devcontainer/devcontainer.json
================================================
{
	"name": "Playwright",
	"image": "mcr.microsoft.com/playwright:v1.43.1-jammy",
	"features": {
		"ghcr.io/devcontainers/features/node:1": {
			"nodeGypDependencies": true,
			"version": "18"
		},
		"ghcr.io/devcontainers-contrib/features/npm-package:1": {
			"package": "typescript",
			"version": "latest"
		}
	}
}


================================================
FILE: .gitattributes
================================================
*.sh eol=lf
mvnw eol=lf

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/pull_request_template.md
================================================
# Change Description

> Please include a summary of the changes.

## Linked GitHub Issue

> Link to any related github issue number (e.g. #56).

Fixes # (issue)

## Checklist

Please check all options that are relevant.

- [ ] I have made all necessary updates to the documentation.
- [ ] I have made all necessary updates to the provisioning scripts (bicep templates, github workflows).
- [ ] This is not a breaking change.


================================================
FILE: .github/workflows/aks-cost-optimization.yml
================================================
name: aks-cost-optimization

on:
  workflow_dispatch:
  
env:
  ACR_NAME: contosotradersacr
  AKS_CLUSTER_NAME: contoso-traders-aks
  AKS_CPU_LIMIT: 250m
  AKS_MEMORY_LIMIT: 256Mi
  AKS_REPLICAS: "1"
  AKS_SECRET_NAME_ACR_PASSWORD: contoso-traders-acr-password
  KV_NAME: contosotraderskv
  LOAD_TEST_SERVICE_NAME: contoso-traders-loadtest
  PRODUCTS_ACR_REPOSITORY_NAME: contosotradersapiproducts
  RESOURCE_GROUP_NAME: contoso-traders-rg

jobs:
  aks-cost-optimization:
    strategy:
      fail-fast: false
      matrix:
        AKS_CPU_LIMIT: ["250m", "100m"]
        AKS_MEMORY_LIMIT: ["256Mi", "128Mi"]
      max-parallel: 1
    runs-on: ubuntu-latest
    steps:
      - name: checkout code
        uses: actions/checkout@v4
      - name: azure login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.SERVICEPRINCIPAL }}
      - name: extract acr password
        uses: azure/CLI@v1
        id: extract-acr-password
        with:
          inlineScript: |
            acrPassword=$(az acr credential show -n ${{ env.ACR_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --query "passwords[0].value" --output tsv)
            echo "::add-mask::$acrPassword"
            echo acrPassword=$acrPassword >> $GITHUB_OUTPUT
      - name: azure container registry login
        uses: azure/docker-login@v1
        with:
          login-server: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io
          username: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}
          password: ${{ steps.extract-acr-password.outputs.acrPassword }}
      - name: set aks context
        uses: azure/aks-set-context@v3
        with:
          resource-group: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}
          cluster-name: ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}
      - name: get products api endpoint
        uses: azure/CLI@v1
        id: get-productsApiEndpoint
        with:
          inlineScript: echo "productsApiEndpoint"="$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name productsApiEndpoint --query value -o tsv)" >> $GITHUB_OUTPUT
      - name: substitute tokens in deployment manifest
        uses: cschleiden/replace-tokens@v1.2
        with:
          tokenPrefix: "{"
          tokenSuffix: "}"
          files: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
        env:
          SUFFIX: ${{ vars.SUFFIX }}
          AKS_REPLICAS: ${{ env.AKS_REPLICAS }}
          AKS_CPU_LIMIT: ${{ matrix.AKS_CPU_LIMIT }}
          AKS_MEMORY_LIMIT: ${{ matrix.AKS_MEMORY_LIMIT }}
      - name: apply deployment manifest
        uses: Azure/k8s-deploy@v4
        with:
          manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
          images: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}:latest
          imagepullsecrets: ${{ env.AKS_SECRET_NAME_ACR_PASSWORD }}
          force: true
      - name: sleep for 30 seconds
        run: sleep 30s
        shell: bash
      - name: load test (products API)
        uses: Azure/load-testing@v1.1.19
        with:
          # Path of the YAML file. Should be fully qualified path or relative to the default working directory
          loadtestConfigFile: ./loadtests/contoso-traders-products.yaml
          loadtestResource: ${{ env.LOAD_TEST_SERVICE_NAME }}${{ vars.SUFFIX }}
          resourceGroup: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}
          env: |
            [
              {
                "name": "domain",
                "value": "${{ steps.get-productsApiEndpoint.outputs.productsApiEndpoint }}"
              },
              {
                "name": "protocol",
                "value": "https"
              },
              {
                "name": "path",
                "value": "v1/Products/1"
              },
              {
                "name": "threads_per_engine",
                "value": "25"
              },
              {
                "name": "ramp_up_time",
                "value": "0"
              },
              {
                "name": "duration_in_sec",
                "value": "45"
              }
            ]

  reset-aks:
    runs-on: ubuntu-latest
    needs: [aks-cost-optimization]
    if: always()
    steps:
      - name: checkout code
        uses: actions/checkout@v4
      - name: azure login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.SERVICEPRINCIPAL }}
      - name: extract acr password
        uses: azure/CLI@v1
        id: extract-acr-password
        with:
          inlineScript: |
            acrPassword=$(az acr credential show -n ${{ env.ACR_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --query "passwords[0].value" --output tsv)
            echo "::add-mask::$acrPassword"
            echo acrPassword=$acrPassword >> $GITHUB_OUTPUT
      - name: azure container registry login
        uses: azure/docker-login@v1
        with:
          login-server: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io
          username: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}
          password: ${{ steps.extract-acr-password.outputs.acrPassword }}
      - name: set aks context
        uses: azure/aks-set-context@v3
        with:
          resource-group: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}
          cluster-name: ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}
      - name: get products api endpoint
        uses: azure/CLI@v1
        id: get-productsApiEndpoint
        with:
          inlineScript: echo "productsApiEndpoint"="$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name productsApiEndpoint --query value -o tsv)" >> $GITHUB_OUTPUT
      - name: substitute tokens in deployment manifest
        uses: cschleiden/replace-tokens@v1.2
        with:
          tokenPrefix: "{"
          tokenSuffix: "}"
          files: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
        env:
          SUFFIX: ${{ vars.SUFFIX }}
          AKS_REPLICAS: ${{ env.AKS_REPLICAS }}
          AKS_CPU_LIMIT: ${{ env.AKS_CPU_LIMIT }}
          AKS_MEMORY_LIMIT: ${{ env.AKS_MEMORY_LIMIT }}
      - name: apply deployment manifest
        uses: Azure/k8s-deploy@v4
        with:
          manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
          images: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}:latest
          imagepullsecrets: ${{ env.AKS_SECRET_NAME_ACR_PASSWORD }}
          force: true


================================================
FILE: .github/workflows/codeql.yml
================================================
name: "CodeQL"

on:
    workflow_dispatch:

jobs:
  analyze:
    name: Analyze (${{ matrix.language }})
    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
    timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
    permissions:
      security-events: write
      packages: read
      actions: read
      contents: read

    strategy:
      fail-fast: false
      matrix:
        include:
        - language: csharp
          build-mode: autobuild
        - language: javascript-typescript
          build-mode: none
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v3
      with:
        languages: ${{ matrix.language }}
        build-mode: ${{ matrix.build-mode }}

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v3
      with:
        category: "/language:${{matrix.language}}"


================================================
FILE: .github/workflows/contoso-traders-cloud-testing.yml
================================================
name: contoso-traders-cloud-testing

on:
  workflow_dispatch:
  push:
    branches: ["main"]
    paths-ignore: ["docs/**", "demo-scripts/**"]

env:
  ACR_NAME: contosotradersacr
  AKS_CLUSTER_NAME: contoso-traders-aks
  AKS_CPU_LIMIT: 250m
  AKS_DNS_LABEL: contoso-traders-products
  AKS_MEMORY_LIMIT: 256Mi
  AKS_NODES_RESOURCE_GROUP_NAME: contoso-traders-aks-nodes-rg
  AKS_REPLICAS: "1"
  AKS_SECRET_NAME_ACR_PASSWORD: contoso-traders-acr-password
  AKS_SECRET_NAME_KV_ENDPOINT: contoso-traders-kv-endpoint
  AKS_SECRET_NAME_MI_CLIENTID: contoso-traders-mi-clientid
  AZURE_AD_APP_NAME: contoso-traders-cloud-testing-app
  CARTS_ACA_NAME: contoso-traders-carts
  CARTS_ACR_REPOSITORY_NAME: contosotradersapicarts
  CARTS_INTERNAL_ACA_NAME: contoso-traders-intcarts
  CDN_PROFILE_NAME: contoso-traders-cdn
  CHAOS_AKS_EXPERIMENT_NAME: contoso-traders-chaos-aks-experiment
  KV_NAME: contosotraderskv
  LOAD_TEST_SERVICE_NAME: contoso-traders-loadtest
  MSGRAPH_API_ID: 00000003-0000-0000-c000-000000000000
  MSGRAPH_API_PERMISSION_EMAIL: 64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0=Scope
  MSGRAPH_API_PERMISSION_USER_READ: e1fe6dd8-ba31-4d61-89e7-88639da4683d=Scope
  PRODUCTS_ACR_REPOSITORY_NAME: contosotradersapiproducts
  PRODUCTS_DB_NAME: productsdb
  PRODUCTS_DB_SERVER_NAME: contoso-traders-products
  PRODUCTS_DB_USER_NAME: localadmin
  PRODUCT_DETAILS_CONTAINER_NAME: product-details
  PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME: contosotradersimg
  PRODUCT_LIST_CONTAINER_NAME: product-list
  PRODUCTS_CDN_ENDPOINT_NAME: contoso-traders-images
  RESOURCE_GROUP_NAME: contoso-traders-rg
  STORAGE_ACCOUNT_NAME: contosotradersimg
  UI_CDN_ENDPOINT_NAME: contoso-traders-ui2
  UI_STORAGE_ACCOUNT_NAME: contosotradersui2
  USER_ASSIGNED_MANAGED_IDENTITY_NAME: contoso-traders-mi-kv-access

jobs:
  provision:
    runs-on: ubuntu-22.04
    env:
      AADUSERNAME: ${{ secrets.AADUSERNAME }}
      AADPASSWORD: ${{ secrets.AADPASSWORD }}
    outputs:
      azureAdAppClientId: ${{ steps.get-azureAdAppClientId.outputs.azureAdAppClientId }}
      azureAdAppObjId: ${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }}
      cartsApiEndpoint: ${{ steps.get-cartsApiEndpoint.outputs.cartsApiEndpoint }}
      productsApiEndpoint: ${{ steps.get-productsApiEndpoint.outputs.productsApiEndpoint }}
      uiCdnEndpoint: ${{ steps.get-uiCdnEndpoint.outputs.uiCdnEndpoint }}
    concurrency:
      group: provision
      cancel-in-progress: true
    steps:
      - name: checkout code
        uses: actions/checkout@v4
      - name: azure login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.SERVICEPRINCIPAL }}
      # section #0: optional configuration of the Azure AD app.
      # create the Azure AD application (and update it if it already exists).
      # note: This is an idempotent operation.
      - name: create/update azure active directory app
        uses: azure/CLI@v1
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        with:
          inlineScript: az ad app create --display-name ${{ env.AZURE_AD_APP_NAME}}${{ vars.SUFFIX }} --sign-in-audience AzureADandPersonalMicrosoftAccount
      - name: get azure ad app's object id
        uses: azure/CLI@v1
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        id: get-azureAdAppObjId
        with:
          inlineScript: echo "azureAdAppObjId"="$(az ad app list --display-name ${{ env.AZURE_AD_APP_NAME }}${{ vars.SUFFIX }} --query [].id -o tsv)" >> $GITHUB_OUTPUT
      - name: get azure ad app's client id
        uses: azure/CLI@v1
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        id: get-azureAdAppClientId
        with:
          inlineScript: echo "azureAdAppClientId"="$(az ad app list --display-name ${{ env.AZURE_AD_APP_NAME }}${{ vars.SUFFIX }} --query [].appId -o tsv)" >> $GITHUB_OUTPUT
      - name: register app as a spa
        uses: azure/CLI@v1
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        with:
          inlineScript: |
            az rest \
              --method PATCH \
              --uri https://graph.microsoft.com/v1.0/applications/${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} \
              --headers 'Content-Type=application/json' \
              --body '{"spa":{"redirectUris":["https://localhost:3000/authcallback","http://localhost:3000/authcallback","https://production.contosotraders.com/authcallback","https://cloudtesting.contosotraders.com/authcallback"]}}'
      - name: enable issuance of id, access tokens
        uses: azure/CLI@v1
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        with:
          inlineScript: az ad app update --id ${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} --enable-access-token-issuance true --enable-id-token-issuance true
      - name: enable email claim in access token
        uses: azure/CLI@v1
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        with:
          inlineScript: az ad app update --id ${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} --optional-claims "{\"accessToken\":[{\"name\":\"email\",\"essential\":false}]}"
      # note: requesting MS Graph permissions in Azure AD app unfortunately isn't idempotent.
      # Even, if you have already requested the permissions, it'll keep adding to the list of requested permissions until you hit limit on max permissions requested.
      # Details: https://github.com/Azure/azure-cli/issues/24512
      - name: delete any requested Microsoft Graph permissions
        uses: azure/CLI@v1
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        with:
          inlineScript: |
            az ad app permission delete \
              --id ${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} \
              --api ${{ env.MSGRAPH_API_ID }}
      - name: request Microsoft Graph permissions
        uses: azure/CLI@v1
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        with:
          inlineScript: |
            az ad app permission add \
              --id ${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} \
              --api ${{ env.MSGRAPH_API_ID }} \
              --api-permissions ${{ env.MSGRAPH_API_PERMISSION_USER_READ }} ${{ env.MSGRAPH_API_PERMISSION_EMAIL }}
      #
      # section #1: provisioning the resources on Azure using bicep templates
      #
      # The first step is to create the resource group: `contoso-traders-rg`.
      # The below step can also be manually executed as follows:
      # az deployment sub create --location {LOCATION} --template-file .\createResourceGroup.bicep
      # Note: You can specify any location for `{LOCATION}`. It's the region where the deployment metadata will be stored, and not
      # where the resource groups will be deployed.
      - name: create resource group
        uses: Azure/arm-deploy@v1
        with:
          scope: subscription
          region: ${{ vars.DEPLOYMENTREGION }}
          template: ./iac/createResourceGroup.bicep
          parameters: rgName=${{ env.RESOURCE_GROUP_NAME }} suffix=${{ vars.SUFFIX }} rgLocation=${{ vars.DEPLOYMENTREGION }}
      # Next step is to deploy the Azure resources to the resource group `contoso-traders-rg` created above. The deployed resources
      # include storage accounts, function apps, app services cosmos db, and service bus etc.
      # The below step can also be manually executed as follows:
      # az deployment group create -g contoso-traders-rg --template-file .\createResources.bicep --parameters .\createResources.parameters.json
      # Note: The `createResources.parameters.json` file contains the parameters for the deployment; specifically the environment name.
      # You can modify the parameters to customize the deployment.
      # Note: The bicep template outputs are not shown in the logs. You can extract the outputs as shown here:
      # https://github.com/Azure/arm-deploy#another-example-on-how-to-use-this-action-to-get-the-output-of-arm-template
      - name: create resources
        uses: Azure/arm-deploy@v1
        with:
          scope: resourcegroup
          region: ${{ vars.DEPLOYMENTREGION }}
          resourceGroupName: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}
          template: ./iac/createResources.bicep
          parameters: ./iac/createResources.parameters.json suffix=${{ vars.SUFFIX }} sqlPassword=${{ secrets.SQLPASSWORD }} deployPrivateEndpoints=${{ vars.DEPLOYPRIVATEENDPOINTS }}
      # Add the logged-in service principal to the key vault access policy
      - name: add service principal to kv access policy
        uses: azure/CLI@v1
        with:
          inlineScript: az keyvault set-policy -n ${{ env.KV_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --secret-permissions get list set --object-id $(az ad sp show --id $(az account show --query "user.name" -o tsv) --query "id" -o tsv)
      # The AKS agent pool needs to be assigned the user-assigned managed identity created (which has kv access)
      - name: assign user-assigned managed-identity to aks agentpool
        uses: azure/CLI@v1
        with:
          inlineScript: |
            az vmss identity assign \
              --identities $(az identity show -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --name ${{ env.USER_ASSIGNED_MANAGED_IDENTITY_NAME }}${{ vars.SUFFIX }} --query "id" -o tsv) \
              --ids $(az vmss list -g ${{ env.AKS_NODES_RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --query "[0].id" -o tsv) \
      # Seed the DBs and storage accounts
      - name: seed products db
        uses: azure/sql-action@v2.2
        with:
          connection-string: Server=tcp:${{ env.PRODUCTS_DB_SERVER_NAME }}${{ vars.SUFFIX }}.database.windows.net,1433;Initial Catalog=${{ env.PRODUCTS_DB_NAME }};Persist Security Info=False;User ID=${{ env.PRODUCTS_DB_USER_NAME }};Password=${{ secrets.SQLPASSWORD }};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
          path: ./src/ContosoTraders.Api.Products/Migration/productsdb.sql
      - name: seed product image (product details)
        uses: azure/CLI@v1
        with:
          inlineScript: az storage blob sync --account-name '${{ env.PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME }}${{ vars.SUFFIX }}' -c '${{ env.PRODUCT_DETAILS_CONTAINER_NAME }}' -s 'src/ContosoTraders.Api.Images/product-details'
      - name: seed product image (product list)
        uses: azure/CLI@v1
        with:
          inlineScript: az storage blob sync --account-name '${{ env.PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME }}${{ vars.SUFFIX }}' -c '${{ env.PRODUCT_LIST_CONTAINER_NAME }}' -s 'src/ContosoTraders.Api.Images/product-list'
      - name: purge product images cdn endpoint
        uses: azure/CLI@v1
        with:
          inlineScript: az cdn endpoint purge --no-wait --content-paths '/*' -n '${{ env.PRODUCTS_CDN_ENDPOINT_NAME }}${{ vars.SUFFIX }}' -g '${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}' --profile-name '${{ env.CDN_PROFILE_NAME }}${{ vars.SUFFIX }}'
      - name: extract acr password
        uses: azure/CLI@v1
        id: extract-acr-password
        with:
          inlineScript: |
            acrPassword=$(az acr credential show -n ${{ env.ACR_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --query "passwords[0].value" --output tsv)
            echo "::add-mask::$acrPassword"
            echo acrPassword=$acrPassword >> $GITHUB_OUTPUT
      - name: azure container registry login
        uses: azure/docker-login@v1
        with:
          login-server: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io
          username: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}
          password: ${{ steps.extract-acr-password.outputs.acrPassword }}
      - name: set aks context
        uses: azure/aks-set-context@v3
        with:
          resource-group: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}
          cluster-name: ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}
      #
      # section #2: deploy the carts api
      #
      - name: docker build
        run: docker build src -f ./src/ContosoTraders.Api.Carts/Dockerfile -t ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.CARTS_ACR_REPOSITORY_NAME }}:latest -t ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.CARTS_ACR_REPOSITORY_NAME }}:${{ github.sha }}
      - name: docker push (to acr)
        run: docker push --all-tags ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.CARTS_ACR_REPOSITORY_NAME }}
      - name: deploy to aca
        uses: azure/CLI@v1
        with:
          inlineScript: |
            az config set extension.use_dynamic_install=yes_without_prompt
            az containerapp update -n ${{ env.CARTS_ACA_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --image ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.CARTS_ACR_REPOSITORY_NAME }}:${{ github.sha }}
      - name: deploy to aca (internal)
        if: ${{ vars.DEPLOYPRIVATEENDPOINTS == 'true' }}
        uses: azure/CLI@v1
        with:
          inlineScript: |
            az config set extension.use_dynamic_install=yes_without_prompt
            az containerapp update -n ${{ env.CARTS_INTERNAL_ACA_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --image ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.CARTS_ACR_REPOSITORY_NAME }}:${{ github.sha }}
      - name: get carts api endpoint
        uses: azure/CLI@v1
        id: get-cartsApiEndpoint
        with:
          inlineScript: echo "cartsApiEndpoint"="$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name cartsApiEndpoint --query value -o tsv)" >> $GITHUB_OUTPUT

      #
      # section #3: deploy the products api
      #
      - name: install helm
        uses: Azure/setup-helm@v3
        id: install-helm
        with:
          version: v3.9.0
      - name: docker build
        run: docker build src -f ./src/ContosoTraders.Api.Products/Dockerfile -t ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}:latest -t ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}:${{ github.sha }}
      - name: docker push (to acr)
        run: docker push --all-tags ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}
      - name: setup kubectl
        uses: azure/setup-kubectl@v3
      - name: create kubernetes secret (acr password)
        uses: Azure/k8s-create-secret@v4
        with:
          secret-name: ${{ env.AKS_SECRET_NAME_ACR_PASSWORD }}
          container-registry-url: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io
          container-registry-username: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}
          container-registry-password: ${{ steps.extract-acr-password.outputs.acrPassword }}
      - name: get managedIdentityClientId
        uses: azure/CLI@v1
        id: get-managedIdentityClientId
        with:
          inlineScript: echo "managedIdentityClientId"="$(az identity show -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --name ${{ env.USER_ASSIGNED_MANAGED_IDENTITY_NAME }}${{ vars.SUFFIX }} --query "clientId" -o tsv)" >> $GITHUB_OUTPUT
      - name: create kubernetes secret (kv endpoint)
        uses: Azure/k8s-create-secret@v4
        with:
          secret-type: "generic"
          secret-name: ${{ env.AKS_SECRET_NAME_KV_ENDPOINT }}
          string-data: '{ "${{ env.AKS_SECRET_NAME_KV_ENDPOINT }}" : "https://${{ env.KV_NAME }}${{ vars.SUFFIX }}.vault.azure.net/" }'
      - name: create kubernetes secret (managed identity client id)
        uses: Azure/k8s-create-secret@v4
        with:
          secret-type: "generic"
          secret-name: ${{ env.AKS_SECRET_NAME_MI_CLIENTID }}
          string-data: '{ "${{ env.AKS_SECRET_NAME_MI_CLIENTID }}" : "${{ steps.get-managedIdentityClientId.outputs.managedIdentityClientId }}" }'
      - name: substitute tokens in deployment manifest
        uses: cschleiden/replace-tokens@v1.2
        with:
          tokenPrefix: "{"
          tokenSuffix: "}"
          files: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
        env:
          SUFFIX: ${{ vars.SUFFIX }}
          AKS_REPLICAS: ${{ env.AKS_REPLICAS }}
          AKS_CPU_LIMIT: ${{ env.AKS_CPU_LIMIT }}
          AKS_MEMORY_LIMIT: ${{ env.AKS_MEMORY_LIMIT }}
      - name: lint deployment manifest
        uses: azure/k8s-lint@v2.0
        with:
          manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
      - name: apply deployment manifest
        uses: Azure/k8s-deploy@v4
        with:
          manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml
          images: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}:${{ github.sha }}
          imagepullsecrets: ${{ env.AKS_SECRET_NAME_ACR_PASSWORD }}
          force: true
      - name: apply service manifest
        uses: Azure/k8s-deploy@v4
        with:
          pull-images: false
          manifests: ./src/ContosoTraders.Api.Products/Manifests/Service.yaml
          force: true
      # setup chaos mesh
      - name: apply namespace manifest (chaos-testing)
        uses: Azure/k8s-deploy@v4
        with:
          pull-images: false
          manifests: ./src/ContosoTraders.Api.Products/Manifests/NamespaceChaosTesting.yaml
          force: true
      - name: setup chaos mesh
        run: |
          az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --name ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}
          ${{ steps.install-helm.outputs.helm-path }} repo add chaos-mesh https://charts.chaos-mesh.org
          ${{ steps.install-helm.outputs.helm-path }} repo update
          ${{ steps.install-helm.outputs.helm-path }} upgrade --install chaos-mesh chaos-mesh/chaos-mesh --namespace=chaos-testing --set chaosDaemon.runtime=containerd --set chaosDaemon.socketPath=/run/containerd/containerd.sock
      # create the ingress controller
      - name: create ingress controller
        run: |
          az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --name ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}
          ${{ steps.install-helm.outputs.helm-path }} repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
          ${{ steps.install-helm.outputs.helm-path }} repo update
          ${{ steps.install-helm.outputs.helm-path }} upgrade --install --wait --timeout=1h nginx-ingress ingress-nginx/ingress-nginx \
            --set controller.replicaCount=1 \
            --set controller.nodeSelector."kubernetes\.io/os"=linux \
            --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \
            --set controller.admissionWebhooks.patch.nodeSelector."kubernetes\.io/os"=linux \
            --set controller.service.externalTrafficPolicy=Local
      - name: set dns label on public ip
        uses: azure/CLI@v1
        with:
          inlineScript: az network public-ip update --dns-name ${{ env.AKS_DNS_LABEL }}${{ vars.SUFFIX }} -g ${{ env.AKS_NODES_RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} -n $(az network public-ip list --query "[?starts_with(name,'kubernetes-') ].name" -o tsv -g ${{ env.AKS_NODES_RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }})
      # hack: extract the full fqdn / dns label of the aks app's public IP address
      - name: get aks-fqdn
        uses: azure/CLI@v1
        id: get-aks-fqdn
        with:
          # note: There should be a whitespace between ')' and ']'. More details: https://stackoverflow.com/a/59154958
          inlineScript: echo "aksFqdn"="$(az network public-ip list --query "[?starts_with(name,'kubernetes-') ].dnsSettings.fqdn" -o tsv -g ${{ env.AKS_NODES_RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }})" >> $GITHUB_OUTPUT
      # install cert-manager
      - name: apply namespace manifest (cert-manager)
        uses: Azure/k8s-deploy@v4
        with:
          pull-images: false
          manifests: ./src/ContosoTraders.Api.Products/Manifests/NamespaceCertManager.yaml
          force: true
      - name: install cert-manager
        run: |
          az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --name ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}
          kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.yaml
      - name: sleep for 30 seconds
        run: sleep 30s
        shell: bash
      - name: apply clusterIssuer manifest
        uses: Azure/k8s-deploy@v4
        with:
          pull-images: false
          manifests: ./src/ContosoTraders.Api.Products/Manifests/ClusterIssuer.yaml
          force: true
      - name: substitute tokens in certificate manifest
        uses: cschleiden/replace-tokens@v1.2
        with:
          tokenPrefix: "{"
          tokenSuffix: "}"
          files: ./src/ContosoTraders.Api.Products/Manifests/Certificate.yaml
        env:
          AKS_FQDN: ${{ steps.get-aks-fqdn.outputs.aksFqdn }}
      - name: apply certificate manifest
        uses: Azure/k8s-deploy@v4
        with:
          pull-images: false
          manifests: ./src/ContosoTraders.Api.Products/Manifests/Certificate.yaml
          force: true
      - name: substitute tokens in ingress manifest
        uses: cschleiden/replace-tokens@v1.2
        with:
          tokenPrefix: "{"
          tokenSuffix: "}"
          files: ./src/ContosoTraders.Api.Products/Manifests/Ingress.yaml
        env:
          AKS_FQDN: ${{ steps.get-aks-fqdn.outputs.aksFqdn }}
      - name: apply ingress manifest
        uses: Azure/k8s-deploy@v4
        with:
          pull-images: false
          manifests: ./src/ContosoTraders.Api.Products/Manifests/Ingress.yaml
          force: true
      - name: apply clusterRole manifest
        uses: Azure/k8s-deploy@v4
        with:
          pull-images: false
          manifests: ./src/ContosoTraders.Api.Products/Manifests/ClusterRole.yaml
          force: true
      - name: set productsApiEndpoint in kv
        uses: azure/CLI@v1
        with:
          inlineScript: az keyvault secret set --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name productsApiEndpoint --value ${{ steps.get-aks-fqdn.outputs.aksFqdn }} --description "endpoint url (fqdn) of the products api"
      - name: get products api endpoint
        uses: azure/CLI@v1
        id: get-productsApiEndpoint
        with:
          inlineScript: echo "productsApiEndpoint"="$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name productsApiEndpoint --query value -o tsv)" >> $GITHUB_OUTPUT

      #
      # section #4: deploy the ui
      #
      - name: set REACT_APP_APIURLSHOPPINGCART
        run: echo "REACT_APP_APIURLSHOPPINGCART"="https://${{ steps.get-cartsApiEndpoint.outputs.cartsApiEndpoint }}/v1" >> $GITHUB_ENV
      - name: set REACT_APP_APIURL
        run: echo "REACT_APP_APIURL"="https://${{ steps.get-productsApiEndpoint.outputs.productsApiEndpoint }}/v1" >> $GITHUB_ENV
      - name: set REACT_APP_B2CCLIENTID
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        run: echo "REACT_APP_B2CCLIENTID"="${{ steps.get-azureAdAppClientId.outputs.azureAdAppClientId }}" >> $GITHUB_ENV
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: npm
          cache-dependency-path: src/ContosoTraders.Ui.Website/package-lock.json
      - name: npm ci
        run: npm ci
        working-directory: src/ContosoTraders.Ui.Website
      - name: npm run build
        run: npm run build
        env:
          REACT_APP_BINGMAPSKEY: ${{ secrets.BINGMAPSKEY }}
        working-directory: src/ContosoTraders.Ui.Website
      - name: deploy ui to storage
        uses: azure/CLI@v1
        with:
          inlineScript: az storage blob sync --account-name '${{ env.UI_STORAGE_ACCOUNT_NAME }}${{ vars.SUFFIX }}' -c '$web' -s 'src/ContosoTraders.Ui.Website/build'
      - name: purge ui cdn endpoint
        uses: azure/CLI@v1
        with:
          inlineScript: az cdn endpoint purge --no-wait --content-paths '/*' -n '${{ env.UI_CDN_ENDPOINT_NAME }}${{ vars.SUFFIX }}' -g '${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}' --profile-name '${{ env.CDN_PROFILE_NAME }}${{ vars.SUFFIX }}'
      - name: get ui cdn endpoint
        uses: azure/CLI@v1
        id: get-uiCdnEndpoint
        with:
          inlineScript: echo "uiCdnEndpoint"="$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name uiCdnEndpoint --query value -o tsv)" >> $GITHUB_OUTPUT
      - name: register auth callback (UI CDN)
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        uses: azure/CLI@v1
        with:
          inlineScript: |
            az rest \
              --method PATCH \
              --uri https://graph.microsoft.com/v1.0/applications/${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} \
              --headers 'Content-Type=application/json' \
              --body '{"spa":{"redirectUris":["https://localhost:3000/authcallback","http://localhost:3000/authcallback","https://staging.contosotraders.com/authcallback","https://production.contosotraders.com/authcallback","https://cloudtesting.contosotraders.com/authcallback","https://${{ steps.get-uiCdnEndpoint.outputs.uiCdnEndpoint }}/authcallback"]}}'
      - name: display ui cdn endpoint
        uses: azure/CLI@v1
        with:
          inlineScript: echo UI CDN endpoint accessible at https://$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name uiCdnEndpoint --query value -o tsv)

  load-tests-with-chaos-products-api:
    needs: [provision, playwright-tests-ui]
    runs-on: ubuntu-22.04
    concurrency:
      group: load-tests-with-chaos-products-api
      cancel-in-progress: true
    steps:
      - name: checkout code
        uses: actions/checkout@v4
      - name: azure login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.SERVICEPRINCIPAL }}
      - name: get chaos experiment resource id
        uses: azure/CLI@v1
        id: get-chaosAksExperimentResourceId
        with:
          inlineScript: echo "chaosAksExperimentResourceId"="$(az resource show --resource-group ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --namespace Microsoft.Chaos --resource-type Experiments --name ${{ env.CHAOS_AKS_EXPERIMENT_NAME }}${{ vars.SUFFIX }} --query "id" -o tsv)" >> $GITHUB_OUTPUT
      - name: start chaos experiment (pod failure)
        uses: azure/CLI@v1
        with:
          inlineScript: az rest --method post --uri https://management.azure.com${{ steps.get-chaosAksExperimentResourceId.outputs.chaosAksExperimentResourceId }}/start?api-version=2021-09-15-preview
      - name: sleep for 30 seconds
        run: sleep 30s
        shell: bash
      - name: load test (products API)
        uses: Azure/load-testing@v1.1.19
        with:
          # Path of the YAML file. Should be fully qualified path or relative to the default working directory
          loadtestConfigFile: ./loadtests/contoso-traders-products.yaml
          loadtestResource: ${{ env.LOAD_TEST_SERVICE_NAME }}${{ vars.SUFFIX }}
          resourceGroup: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}
          env: |
            [
              {
                "name": "domain",
                "value": "${{ needs.provision.outputs.productsApiEndpoint }}"
              },
              {
                "name": "protocol",
                "value": "https"
              },
              {
                "name": "path",
                "value": "v1/Products/1"
              },
              {
                "name": "threads_per_engine",
                "value": "5"
              },
              {
                "name": "ramp_up_time",
                "value": "0"
              },
              {
                "name": "duration_in_sec",
                "value": "120"
              }
            ]

  load-tests-carts-internal-api:
    if: ${{ vars.DEPLOYPRIVATEENDPOINTS == 'true' }}
    needs: [provision, playwright-tests-ui]
    runs-on: ubuntu-22.04
    concurrency:
      group: load-tests-carts-internal-api
      cancel-in-progress: true
    steps:
      - name: checkout code
        uses: actions/checkout@v4
      - name: azure login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.SERVICEPRINCIPAL }}
      - name: get carts api endpoint (internal)
        uses: azure/CLI@v1
        id: get-cartsInternalApiEndpoint
        with:
          inlineScript: echo "cartsInternalApiEndpoint"="$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name cartsInternalApiEndpoint --query value -o tsv)" >> $GITHUB_OUTPUT
      - name: get vnetAcaSubnetId
        uses: azure/CLI@v1
        id: get-vnetAcaSubnetId
        with:
          inlineScript: echo "vnetAcaSubnetId"="$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} -n vnetAcaSubnetId --query "value" -o tsv)" >> $GITHUB_OUTPUT
      - name: substitute tokens in load test config file
        uses: cschleiden/replace-tokens@v1.2
        with:
          tokenPrefix: "{{"
          tokenSuffix: "}}"
          files: ./loadtests/contoso-traders-carts-internal.yaml
        env:
          LOAD_TEST_SUBNET_ID: ${{ steps.get-vnetAcaSubnetId.outputs.vnetAcaSubnetId }}
      - name: load test (carts internal API)
        uses: Azure/load-testing@v1.1.19
        with:
          # Path of the YAML file. Should be fully qualified path or relative to the default working directory
          loadtestConfigFile: ./loadtests/contoso-traders-carts-internal.yaml
          loadtestResource: ${{ env.LOAD_TEST_SERVICE_NAME }}${{ vars.SUFFIX }}
          resourceGroup: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}
          env: |
            [
              {
                "name": "domain",
                "value": "${{ steps.get-cartsInternalApiEndpoint.outputs.cartsInternalApiEndpoint }}"
              },
              {
                "name": "protocol",
                "value": "https"
              },
              {
                "name": "path",
                "value": "v1/ShoppingCart/loadtest"
              },
              {
                "name": "threads_per_engine",
                "value": "5"
              },
              {
                "name": "ramp_up_time",
                "value": "0"
              },
              {
                "name": "duration_in_sec",
                "value": "120"
              }
            ]

  load-tests-carts-api:
    needs: [provision, playwright-tests-ui]
    runs-on: ubuntu-22.04
    concurrency:
      group: load-tests-carts-api
      cancel-in-progress: true
    steps:
      - name: checkout code
        uses: actions/checkout@v4
      - name: azure login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.SERVICEPRINCIPAL }}
      - name: load test (carts API)
        uses: Azure/load-testing@v1.1.19
        with:
          # Path of the YAML file. Should be fully qualified path or relative to the default working directory
          loadtestConfigFile: ./loadtests/contoso-traders-carts.yaml
          loadtestResource: ${{ env.LOAD_TEST_SERVICE_NAME }}${{ vars.SUFFIX }}
          resourceGroup: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}
          env: |
            [
              {
                "name": "domain",
                "value": "${{ needs.provision.outputs.cartsApiEndpoint }}"
              },
              {
                "name": "protocol",
                "value": "https"
              },
              {
                "name": "path",
                "value": "v1/ShoppingCart/loadtest"
              },
              {
                "name": "threads_per_engine",
                "value": "5"
              },
              {
                "name": "ramp_up_time",
                "value": "0"
              },
              {
                "name": "duration_in_sec",
                "value": "120"
              }
            ]

  playwright-tests-ui:
    needs: [provision]
    timeout-minutes: 20
    runs-on: ubuntu-22.04
    container:
      image: mcr.microsoft.com/playwright:v1.43.1-jammy
    defaults:
      run:
        working-directory: src/ContosoTraders.Ui.Website
    env:
      AADUSERNAME: ${{ secrets.AADUSERNAME }}
      AADPASSWORD: ${{ secrets.AADPASSWORD }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: npm
          cache-dependency-path: src/ContosoTraders.Ui.Website/package-lock.json
      - name: Set env variables for testing endpoints
        run: |
          echo "REACT_APP_APIURLSHOPPINGCART"="https://${{ needs.provision.outputs.cartsApiEndpoint }}/v1" >> $GITHUB_ENV
          echo "REACT_APP_APIURL"="https://${{ needs.provision.outputs.productsApiEndpoint }}/v1" >> $GITHUB_ENV
          echo "REACT_APP_BASEURLFORPLAYWRIGHTTESTING"="https://${{ needs.provision.outputs.uiCdnEndpoint }}" >> $GITHUB_ENV
      - name: Set env variables for testing login
        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}
        run: |
          echo "REACT_APP_B2CCLIENTID"="${{ needs.provision.outputs.azureAdAppClientId }}" >> $GITHUB_ENV
          echo "REACT_APP_AADUSERNAME"="${{ env.AADUSERNAME }}" >> $GITHUB_ENV
          echo "REACT_APP_AADPASSWORD"="${{ env.AADPASSWORD }}" >> $GITHUB_ENV
      - name: install dependencies
        run: npm ci
      - name: run playwright tests
        id: test
        run: HOME=/root npx playwright test
      - name: upload playwright report
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: playwright-report
          path: src/ContosoTraders.Ui.Website/playwright-report/
          retention-days: 30


================================================
FILE: .gitignore
================================================
## 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
auth.json

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015/2017 cache/options directory
.vs/
.vscode/
# 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

# 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/

# StyleCop
StyleCopReport.xml

# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.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

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json

# 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
# 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

# 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

# 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/

# JetBrains Rider
.idea/
*.sln.iml

# CodeRush
.cr/

# 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/

Deploy/helm/__values/*
!Deploy/helm/__values/readme.txt

Source\Services\Contoso.Traders.Rewards.Registration.Api\Properties\PublishProfiles\*.pubxml
Source/Services/Contoso.Traders.Cart.Api/.env

/Source/.env
_gvalues.dev.yaml



================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Microsoft Open Source Code of Conduct

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).

Resources:

- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns


================================================
FILE: CONTRIBUTING.md
================================================
# How to contribute to ContosoTraders

This repo is a reference and learning resource and everyone is invited to contribute, however not all PRs will be accepted into the main branch (**`main`**).

There's a general development strategy that's driven by @marcusfelling, who chooses, or defines criteria for choosing, the issues to include in the codebase, given a bunch of constraints and other guidelines.

## Coding Standards

There are no explicit coding standards so pay attention to the general coding style, that's (mostly) used everywhere.

## Forks and Branches

All contributions must be submitted as a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/) so you need to [fork this repo](https://help.github.com/articles/fork-a-repo/) on your GitHub account.

The main branche is **`main`**:

- **`main`**: Contains the latest code **and it is the branch actively developed**.
**All PRs must be against `main` branch to be considered**.

- Any other branch is considered temporary and could be deleted at any time. Do not submit any PR to them!

## DISCLAIMER - This is not a PRODUCTION-READY TEMPLATE for microservices

ContosoTraders is a reference application to **showcase architectural patterns** for developing microservices applications using various Azure Services. **IT IS NOT A PRODUCTION-READY TEMPLATE** to start real-world application. In fact, the application is in a **permanent beta state**, as it’s also used to test new potentially interesting technologies as they show up.

Since this is a learning resource, some design decisions have favored simplicity to convey a pattern, over production-grade robustness.

## Suggestions

We hope this helps us all to work better and avoid some of the problems/frustrations of working in such a large community.

We'd also appreciate any comments or ideas to improve this.


================================================
FILE: LICENSE
================================================
    MIT License

    Copyright (c) Microsoft Corporation.

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    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


================================================
FILE: README.md
================================================
# Contoso Traders - Cloud testing tools demo app

The Contoso Traders app is a sample application showcasing [Playwright](https://playwright.dev), [Azure Load Testing](https://aka.ms/malt-docs), [Azure Chaos Studio](https://aka.ms/CHAOS-docs) and more.

This repo contains the source code, deployment templates, and demo scripts for exploring these cloud testing tools.

## Overview Video
[![Watch the overview video](https://img.youtube.com/vi/7JletmiT3io/hq1.jpg)](https://youtu.be/7JletmiT3io)

## Documentation and Resources

* Application Links: [UI](https://cloudtesting.contosotraders.com/) | [Carts API](https://contoso-traders-cartsctprd.bluestone-748d2276.eastus.azurecontainerapps.io/swagger/index.html) | [Products API](https://contoso-traders-productsctprd.eastus.cloudapp.azure.com/swagger/index.html)
* [Deployment Instructions](./docs/deployment-instructions.md) | [Running Locally](./docs/running-locally.md)

## Continuous Integration

| Pipeline                                                                     | Status                                                                                                                                                                                                                                                                               | Details                                                        |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
| [GitHub Workflow](./.github/workflows/contoso-traders-cloud-testing.yml)     | [![contoso-traders-cloud-testing](https://github.com/microsoft/contosotraders-cloudtesting/actions/workflows/contoso-traders-cloud-testing.yml/badge.svg?branch=main)](https://github.com/microsoft/contosotraders-cloudtesting/actions/workflows/contoso-traders-cloud-testing.yml) | Deploys to [production](https://production.contosotraders.com) |
| [Azure DevOps Pipeline](./.azurepipelines/contoso-traders-cloud-testing.yml) | [![Build Status](https://dev.azure.com/MicrosoftTestDemos/ContosoTraders_Testing/_apis/build/status%2Fmicrosoft.contosotraders-cloudtesting?branchName=main)](https://dev.azure.com/MicrosoftTestDemos/ContosoTraders_Testing/_build/latest?definitionId=1&branchName=main)          | Deploys to [staging](https://staging.contosotraders.com)       |

## Demo Scripts

* [Developer Workflow](./demo-scripts/dev-workflow/walkthrough.md)

* Azure Load Testing - Generate high-scale load and identify performance bottlenecks.
  * [Create a load test for the shopping cart API.](./demo-scripts/azure-load-testing/walkthrough.md)
  * [Use GitHub Actions for regression testing.](./demo-scripts/azure-load-testing/walkthrough.md#walkthrough-regression-testing-with-github-workflows)
  * [Create a load test for a private endpoint that’s behind a VNet.](./demo-scripts/azure-load-testing/private-endpoints.md)
  * [Right-size your AKS cluster using load tests.](./demo-scripts/azure-load-testing/aks-cost-optimization.md)

* Azure Chaos Studio - Improve application resilience by introducing faults and simulating outages.
  * [Create an experiment using Key Vault Deny Access fault to test the products API (AKS).](./demo-scripts/azure-chaos-studio/walkthrough.md)
  * [Run experiment in GitHub Actions to inject faults (pod failures) into the AKS cluster.](./demo-scripts/azure-chaos-studio/walkthrough.md#walkthrough-running-chaos-experiments-via-github-workflows)

* Playwright - Reliable end-to-end testing for modern web apps.
  * [Use the VS Code extension to explore and run web tests](./demo-scripts/testing-with-playwright/walkthrough.md) for [API testing](src/ContosoTraders.Ui.Website/tests/api), [Authentication](src/ContosoTraders.Ui.Website/tests/auth.setup.ts), [Shopping cart](src/ContosoTraders.Ui.Website/tests/cart.spec.ts), [Uploading files](src/ContosoTraders.Ui.Website/tests/fileupload.spec.ts), [Visual Comparisons](src/ContosoTraders.Ui.Website/tests/pages.spec.ts#L63), [Emulation](src/ContosoTraders.Ui.Website/tests/map.spec.ts), [Mocking](src/ContosoTraders.Ui.Website/tests/mocks.spec.ts), and [using a CSV for data](src/ContosoTraders.Ui.Website/tests/account.ts)

## Architecture

![Architecture](./docs/architecture/contoso-traders-enhancements.drawio.png)

## Contributing

This project welcomes contributions and suggestions.  Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit <https://cla.opensource.microsoft.com>.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

## Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general).
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
Any use of third-party trademarks or logos are subject to those third-party's policies.


================================================
FILE: SECURITY.md
================================================
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->

# Security

Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).

If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.

## Reporting Security Issues

**Please do not report security vulnerabilities through public GitHub issues.**

Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).

If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com).  If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).

You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).

Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:

* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue

This information will help us triage your report more quickly.

If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.

## Preferred Languages

We prefer all communications to be in English.

## Policy

Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).

<!-- END MICROSOFT SECURITY.MD BLOCK -->


================================================
FILE: SUPPORT.md
================================================

# Support

## How to file issues and get help  

This project uses GitHub Issues to track bugs and feature requests. Please search the existing
issues before filing new issues to avoid duplicates.  For new issues, file your bug or
feature request as a new Issue.

## Microsoft Support Policy  

Support for this project is limited to the resources listed above.


================================================
FILE: demo-scripts/azure-chaos-studio/walkthrough.md
================================================
# Azure Chaos Studio: Overview

Azure Chaos Studio is an Azure service that allows you to create & run chaos experiments on your application's infrastructure. By deliberately introducing faults that simulate real-world outages, you can test your application's resiliency and identify potential issues before they impact your customers.

## Key Takeaways

In this demo, you'll get an overview of Azure's Chaos Studio service; a managed service that can be used to simulate faults on your application's infrastructure.

- Running Chaos Experiments to introduce faults in Azure Key Vault (deny access) and seeing its impact on the application.
- Running Chaos Experiments via GitHub Workflows.

## Before You Begin

Please execute the steps outlined in the [deployment instructions](../../docs/deployment-instructions.md) to provision the infrastructure in your own Azure subscription.

## Walkthrough: Identify the Chaos Studio target (Key Vault)

1. In the Azure portal, you can navigate to the Azure Chaos Studio service from the search bar as follows.

   ![chaos studio](./media/chaos1.png)

2. Next, click on the `Target` tab and filter down to the `contoso-traders-rg{SUFFIX}` resource group.

   ![chaos studio](./media/chaos2.png)

3. Next, go to the `contosotraderskv${SUFFIX}` key vault resource, and click on the `Manage actions` button.

   ![chaos studio](./media/chaos3.png)

4. You'll notice that the "Key Vault Deny Access" fault will be being injected in the key vault when the chaos experiment is run. This fault will prevent the application from accessing the key vault, leading the application to fail.

   ![chaos studio](./media/chaos4.png)

## Walkthrough: Review the Chaos Experiment

1. In the Chaos Studio, click on the `Experiment` tab and click on the `contoso-traders-chaos-kv-experiment{SUFFIX}` experiment.

   ![chaos studio](./media/chaos5.png)

2. Click on the experiment's `Edit` button to review the experiment's configuration.

   ![chaos studio](./media/chaos6.png)

3. Click on the action's `Edit` button to review the action's configuration.

   ![chaos studio](./media/chaos7.png)

4. You'll notice that the experiment is configured to run on the the `contosotraderskv${SUFFIX}` key vault resource for 5 minutes. For the duration of the experiment, the `Key Vault Deny Access` fault will be injected into the key vault (i.e. the key vault will not be accessible even to principals mentioned in its access policies).

   ![chaos studio](./media/chaos8.png)

   ![chaos studio](./media/chaos9.png)

## Walkthrough: Run the Chaos Experiment

1. Before starting the experiment, you can verify that the application is working as expected by navigating to the application's URL and clicking on any product category (e.g. `laptops`). The application should load the product category page successfully by fetching data from the API.

   ![chaos studio](./media/chaos10.png)

2. Next, navigate to the Chaos Studio and click on the `Experiment` tab. Click on the `contoso-traders-chaos-kv-experiment{SUFFIX}` experiment and click on the `Start` button.

   ![chaos studio](./media/chaos11.png)

   ![chaos studio](./media/chaos12.png)

3. The experiment is now underway and during the course of the experiment, the key vault will not be accessible.

   ![chaos studio](./media/chaos13.png)

   ![chaos studio](./media/chaos14.png)

## Walkthrough: Exposing resiliency issues in application

1. The application's Products API follows the externalized configuration pattern, wherein upon startup, the API fetches DB connection strings, passwords etc from Azure key vault. The API then uses the connection string to connect to its product catalog db. If the key vault is not accessible, the API will fail to fetch the connection string and will fail to start.

   ![chaos studio](./media/kv-config-provider.png)

2. Let us force the API to restart (note: upon restarting, it'll attempt to connect to the key vault to fetch the connection string). We can do by simply deleting the API's pod. The AKS deployment will then recreate the pod and the API will restart.

   ![chaos studio](./media/chaos15.png)

   ![chaos studio](./media/chaos16.png)

3. As soon as the new pod is created, the API will attempt to connect to the key vault to fetch the connection string. Since the key vault is not accessible, the API will fail to start.

   ![chaos studio](./media/chaos17.png)

4. You can verify that the application is not working as expected by navigating to the application's URL and clicking on any product category (e.g. `laptops`). The application should fail to load the product category page.

   ![chaos studio](./media/chaos18.png)

## Walkthrough: After the Chaos Experiment ends

1. After the 5 minutes are up, the experiment will end and the key vault will be accessible again.

   ![chaos studio](./media/chaos19.png)

2. AKS's deployment ensures that the API will automatically restarted on crashes (with exponential back-off applied). Once the chaos experiment ends, the key vault will be accessible again. When AKS restarts the pod after this, the API will be able to connect to the key vault and will start successfully.

   ![chaos studio](./media/chaos20.png)

   ![chaos studio](./media/chaos21.png)

## Walkthrough: Running Chaos Experiments via GitHub Workflows

1. We have a Chaos Experiment `contoso-traders-chaos-aks-experiment{SUFFIX}` that injects faults (pod failures) into the AKS cluster: `contoso-traders-aks{SUFFIX}` for a duration of 5 mins.

   ![chaos studio](./media/chaos22.png)

2. Internally, this experiment leverages [Chaos Mesh](https://chaos-mesh.org/), a CNCF project that orchestrates fault injection on Kubernetes environments (e.g. network latency, pod failures, and even node failures).

   ![chaos studio](./media/chaos23.png)

3. The github workflow `contoso-traders-cloud-testing.yml` triggers the AKS chaos experiment (pod failures), while simultaneously running a load test against the same AKS cluster.

   ![chaos studio](./media/chaos24.png)

## More Information

- [Azure Chaos Studio](https://learn.microsoft.com/azure/chaos-studio/)
- [Pricing](https://azure.microsoft.com/pricing/details/chaos-studio/)


================================================
FILE: demo-scripts/azure-load-testing/aks-cost-optimization.md
================================================
# Azure Load Testing: AKS Cost Optimization

## Key Takeaways

In this demo, we'll see how Azure Load Testing can be used to help "right-size" an AKS cluster. The demo will start with a representative load test against a large AKS cluster. We'll iteratively scale down the cluster until we find the smallest cluster size that can handle the load. We'll also use the load test's integrated server-side metrics to analyze the cluster's resource utilization.

## Before you begin

Please execute the steps outlined in the [deployment instructions](../../docs/deployment-instructions.md) to provision the infrastructure in your own Azure subscription.

## Walkthrough

1. We have a GitHub workflow (and a Azure DevOps pipeline too) that executes load tests on a AKS cluster using a matrix of cluster sizes. The workflow is defined in the [aks-cost-optimization.yml](../../.github/workflows/aks-cost-optimization.yml) file. The workflow can be triggered manually on-demand.

   ![aks cost optimization](./media/aks-cost-optimization-0.png)


2. The load test, defined in the [load-test-aks.yml](../../.github/workflows/load-test-aks.yml) file, targets the `Products API`.

3. [Optional Step] From the Azure Portal, we'll tweak the existing load test `contoso-traders-products` to incorporate server side AKS metrics using steps below:

   ![aks cost optimization](./media/aks-cost-optimization-1.png)

   ![aks cost optimization](./media/aks-cost-optimization-2.png)

   ![aks cost optimization](./media/aks-cost-optimization-3.png)

   ![aks cost optimization](./media/aks-cost-optimization-4.png)

4. We manually invoke the GitHub workflow. In the beginning, the AKS cluster's size is automatically increased (CPU Limit: 250m, Mem Limit: 256 Mi) and a load test is run against it with 25 virtual concurrent users (threads) for a short duration of 30 seconds.

5. In the next matrix step, the cluster is automatically rescaled a smaller size (CPU Limit: 250m, Mem Limit: 128 Mi) and the load test is run again.

6. In next iterations, the scale down continues until the cluster is unable handle the load any longer. The last passing iteration is the smallest cluster size that can handle the load. In below example, the "right-sized" cluster will have CPU Limit: 250m, Mem Limit: 128 Mi.

   ![aks cost optimization](./media/aks-cost-optimization-5.png)

## Cost Considerations

A quick note on costs considerations when you run the GitHub workflow (or Azure DevOps pipeline):

1. Github Actions ([pricing details](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#included-storage-and-minutes)): Each run will consume approximately 25 billable minutes on linux runners.

2. Azure Load Testing ([pricing details](https://azure.microsoft.com/pricing/details/load-testing/)): The number of virtual users and duration of the test are the key factors that determine the cost of the test. In this demo, the load tests are configured to use 25 virtual users and the test matrix sets up 4 test runs, each of 30 seconds duration.

## Summary

In this demo, we saw how Azure Load Testing was used to help "right-size" an AKS cluster to optimize costs. The demo started with a representative load test against a large AKS cluster. We iteratively scaled down the cluster until we found the smallest cluster size that can handle the load.


================================================
FILE: demo-scripts/azure-load-testing/private-endpoints.md
================================================
# Azure Load Testing: Private Endpoints

## Key Takeaways

In this demo, we'll attempt to load test an private API endpoint using the Azure Load Testing Preview. The private API endpoint in question is only accessible from within an Azure virtual network (VNET).
We'll demonstrate Azure Load Testing service's capability to generate load from within a virtual network (using VNET resource injection).

## Before You Begin

Please execute the steps outlined in the [deployment instructions](../../docs/deployment-instructions.md) to provision the infrastructure in your own Azure subscription.

> **Warning**
To deploy the additional resources for this walkthrough ensure you have set the GitHub Action's variable `DEPLOYPRIVATEENDPOINTS` to `true`. If you're using Azure DevOps Pipeline, then please ensure that the variable `DEPLOYPRIVATEENDPOINTS` is set to `true` in the `contosotraders-cloudtesting-variable-group` variable group.
 

Specifically, here's what happens behind the scenes:

* An Azure virtual network (VNET) is created with three subnets:
  * A subnet for Azure Container Apps to deploy its infrastructure as well as the application's API private endpoints.
  * A subnet for Azure Load Testing to inject its resources.
  * A subnet for Azure VMs (jumpboxes) to access the application's private endpoints (for visual verification purposes).

* An Azure Container Apps instance is deployed to host the application's API endpoints. The API endpoints are configured to be private endpoints, and the ingress controller only allows internal VNET traffic (i.e. endpoint is only accessible from within the VNET).

* A private DNS zone (e.g. `eastus.azurecontainerapps.io`) is created to resolve the private endpoints' DNS names. This private DNS zone is associated with the VNET's subnet ACA. We add the necessary A records to link the endpoint FQDN to the ACA's private IP address.

* A jumpbox VM is deployed to the VNET's VM subnet. This VM will be used in this demo to access the application's private endpoints.

  ![VNET configuration](./media/vnet-configuration.png)

## Walkthrough: Identify the Load Test Target

1. In the Azure portal, you can navigate to the `contoso-traders-intcarts{SUFFIX}` Azure Container App in the `contoso-traders-rg` resource group. This is the application that hosts the `Carts API`. Note that this application is only accessible from within the VNET.

   ![ACA](./media/private-endpoint-1.png)

2. You can get the URL of the `Carts API` by as shown below.

   ![ACA](./media/private-endpoint-2.png)

3. In a separate browser tab, enter the following url in the address bar to load the API's swagger page: `<ACA url>/swagger/index.html`. You'll notice that the API's endpoints are not reachable via the public internet.

   ![ACA](./media/private-endpoint-3.png)

4. To access the API's endpoints, you'll need to access the API from within the VNET. You can RDP into the `jumpboxvm` VM. This is a jumpbox VM located in the same resource group `contoso-traders-rg`. From this RDP session, you can access the API's swagger page: `<ACA url>/swagger/index.html`

   ![ACA](./media/private-endpoint-4.png)

5. The specific API that we'll be using for is the `Carts API`'s `GET <ACA url>/v1/ShoppingCart/loadtest` endpoint. Please note down this endpoint for later use.

   ![ACA](./media/private-endpoint-5.png)

## Walkthrough: Modify the previously created Load Test

1. In the Azure portal, navigate to the Azure Load Testing instance (in the `contoso-traders-rg` resource group) that we created in the [previous demo](./walkthrough.md).

2. Now click on `Configure` > `Tests`.

   ![Load Test Private Endpoint](./media/load-test-private-endpoint-1.png)

3. Navigate to the `Parameters` tab in the Edit Test blade. Modify the `domain` value to point to the `Carts API`'s private endpoint. You can use the `<ACA url>` you noted down earlier (note: please remove the `https://` prefix).

   ![Load Test Private Endpoint](./media/load-test-private-endpoint-2.png)

4. Navigate to the `Load` tab. Change `Configure test traffic mode` to `private`. Also, specify the VNET and subnet details: `contoso-traders-vnet{SUFFIX}` and `subnet-loadtest` respectively.

   ![Load Test Private Endpoint](./media/load-test-private-endpoint-3.png)

5. Click apply to save the changes.

6. Now, click `Run` to start the load test against the private endpoint.

   ![Load Test Private Endpoint](./media/load-test-private-endpoint-4.png)

7. Behind the scenes, the Azure load testing service will inject the testing infrastructure into the specified VNET. The load test will successfully run to completion after that.

   ![Load Test Private Endpoint](./media/load-test-private-endpoint-5.png)

## Summary

In this demo, we saw how Azure Load Testing can be used to generate load from within a virtual network to test private/restricted API endpoints.

## More Information

* [Troubleshooting private endpoints](https://docs.microsoft.com/azure/container-apps/troubleshoot-private-endpoints)
* [Test private endpoints by deploying Azure Load Testing in an Azure virtual network](https://learn.microsoft.com/azure/load-testing/how-to-test-private-endpoint)
* [Scenarios for deploying Azure Load Testing in a virtual network](https://learn.microsoft.com/azure/load-testing/concept-azure-load-testing-vnet-injection)
* [Blog Post: Load test endpoints with access restrictions using Azure Load Testing](https://techcommunity.microsoft.com/t5/apps-on-azure-blog/load-test-endpoints-with-access-restrictions-using-azure-load/ba-p/3610412)
* [Blog Post: Load test private endpoints deployed in another Azure region or subscription](https://techcommunity.microsoft.com/t5/apps-on-azure-blog/load-test-private-endpoints-deployed-in-another-azure-region-or/ba-p/3693277)


================================================
FILE: demo-scripts/azure-load-testing/walkthrough.md
================================================
# Azure Load Testing: Overview

## Key Takeaways

In this demo, you'll get an overview of Azure's Load Testing service; a managed service that can be used to simulate load on your application's UI and APIs endpoints.

You'll also get an insight into:

- how to identify an application's breaking point under incrementally increasing load.
- how to leverage server-side metrics and Azure AppInsights to identify the performance bottleneck.
- how to guard your application against performance regressions leveraging load testing in CI/CD pipelines.

All these are especially crucial for an e-commerce application like Contoso Traders, which is expected to instantly handle a large, sudden spike in number of users, with low latency and no downtime.

## Before You Begin

Please execute the steps outlined in the [deployment instructions](../../docs/deployment-instructions.md) to provision the infrastructure in your own Azure subscription.

## Walkthrough: Identify the Load Test Target

1. In the Azure portal, you can navigate to the Azure Container App in the `contoso-traders-rg{SUFFIX}` resource group. This is the application that hosts the `Carts API`.

   ![ACA](./media/aca-2.png)

2. You can get the URL of the `Carts API` by as shown below.

   ![ACA](./media/aca-endpoint.png)

3. In a separate browser tab, enter the following url in the address bar to load the API's swagger page: `<ACA url>/swagger/index.html`

   ![ACA](./media/aca-swagger.png)

4. You can now identify the API that you want to load test. In this case, we'll be load testing the `Carts API`'s `GET <ACA url>/v1/ShoppingCart/loadtest` endpoint. Please note down this endpoint for later use.

   ![ACA](./media/aca-swagger-2.png)

## Walkthrough: Creating a Load Test

1. In the Azure portal, you can navigate to the Azure Load Testing service in the `contoso-traders-rg{SUFFIX}` resource group.

   ![load testing](./media/load-test-browse.png)

2. You can create a new load test as follows: Navigate to the `Tests` section, and then click on `Create` > `Create a URL-based Test` button.

   ![load testing](./media/load-test-create-1.png)

3. In the `Basic` blade, you can specify the target URL. You can also specify the number of concurrent users, and the duration of the test. See example below:

   ![load testing](./media/load-test-create-2.png)

> **Note**: The target URL is the URL from the `Carts API` that you identified in the previous section.

## Walkthrough: Running the Load Test

1. Once you've entered the load test specifications above, you can run it by clicking on the `Run` button.

   ![load testing](./media/load-test-run.png)

2. The load test will take about 2 minutes to complete. Once done, it'll display the summary and client-side metrics.

   ![load testing](./media/load-test-in-progress.png)

   ![load testing](./media/load-test-completed.png)

## Walkthrough: Incorporate Server Side Metrics

1. Click on the `App Components` button. Then from the flyout, select the `contoso-traders-carts{SUFFIX}` CosmosDB component. This will add relevant metrics from the CosmosDB to the load test dashboard.

   ![load testing](./media/load-test-server-side-metrics.png)

2. Re-run the load test, and you'll see the impact of the synthetic load on the DB (in real-time).

   ![load testing](./media/load-test-run-2.png)

> **Note**: Unfortunately, ACA metrics are not yet supported in Azure Load Testing's server side metrics. This feature will be coming soon.

## Walkthrough: Review ACA Metrics & Dashboards

1. In the Azure portal, you can navigate to the Azure Container App in the `contoso-traders-rg{SUFFIX}` resource group.

   ![ACA](./media/aca.png)

2. For demo purposes, we have configured a `HTTP Scaling` rule that horizontally scales out additional replicas when the number of concurrent requests exceeds a threshold (`3` in this case). ACA also supports automatic scale-in to zero when traffic dips below threshold.

   ![ACA Scaling Rules](./media/aca-scaling-rules.png)

3. In the metrics tab, you can see the various metrics measured & published by the ACA infrastructure. You can create a metric chart that combines two metrics: `replica count` vs `requests`. It'll now have updated with the latest data after the load test. Of particular interest is the replica count chart of `Carts API`, which shows the instances auto-scaled out under increasing load. After load subsided, the instances auto-scaled back in to zero.

   ![load testing ACA](./media/aca-metrics2.png)

## Walkthrough: Export the JMX File, Results

1. Navigate back to the Load Testing service, and click on the recently concluded test run. From there you can click on `Download` > `Input File`. This will download the JMX file in a zip archive.

   ![load testing](./media/load-test-export-jmx.png)

2. You can review the JMX file by simply loading it up in notepad or VSCode.

   ![load testing](./media/load-test-jmx-view.png)

3. The load test results can also be downloaded via in the `Download` > `Results` button. This will download a CSV file (inside a zip archive).

   ![load testing](./media/load-test-export-results.png)

   ![load testing](./media/load-test-results-view.png)

## Walkthrough: Identify application's breakpoints

1. Let us now modify the existing load test. We'll use it to put the application under increasing load, ultimately leading to failure. The goal is to identify the application's breakpoints (performance bottlenecks).

   ![app breakpoint](./media/app-breakpoint-1.png)

2. Modify the existing test configuration as follows:

   1. Increase the number of concurrent users to `250` (from original `5`).
   2. Change the test duration to `300` seconds (from original `120` seconds)
   3. Change the ramp-up time to `300` seconds (from original `120` seconds).

   ![app breakpoint](./media/app-breakpoint-2.png)

3. Increase the number of engine instances to `2` (from original `1`).

   ![app breakpoint](./media/app-breakpoint-3.png)

4. Run the modified load test. You'll notice that the application starts to eventually fail under the increased load.

   ![app breakpoint](./media/app-breakpoint-4.png)

5. If you add the server-side metrics for the `contoso-traders-cartsct{SUFFIX}` CosmosDB, you'll notice that the DB's normalized RU consumption eventually starts to peg at 100% under load.

   ![app breakpoint](./media/app-breakpoint-4-2.png)

6. App Insights can help us narrow down the root cause of the error. Navigate to the `contoso-traders-rg{SUFFIX}` resource group, and click on the `contoso-traders-aict{SUFFIX}` resource.

   ![app breakpoint](./media/app-breakpoint-5.png)

7. In the App Insights blade, click on the `Failures` tab. Narrow down the time range to (say) the last 30 minutes. You'll see the listed failures (sampled by App Insights) that occurred during the load test.

   ![app breakpoint](./media/app-breakpoint-6.png)

8. Clicking on any one sample will give you a detailed view of the error (including stack trace in case of an exception). In this case, the error is a `500` error, caused by a `TaskCanceledException` (due to a gateway timeout in CosmosDB). This is a good indication that the application is failing due to a performance bottleneck.

   ![app breakpoint](./media/app-breakpoint-7.png)

## Walkthrough: Regression Testing with Github Workflows

1. We have a GitHub workflow that executes load tests on the application's APIs. This workflow is automatically triggered on every checkin to the `main` branch. Specifically the load tests are run on the `Product API` and `Carts API` immediately after they're deployed to the AKS cluster and ACA respectively. This will help identify if any code (or infra) change causes the application performance to degrade under (simulated) load.

   ![github workflow](./media/github-workflow-2.png)

2. The workflow uses a github action to invoke the [Azure Load Testing](https://learn.microsoft.com/en-us/azure/load-testing/) service and simulate load on the application's `Product API` and `Carts API`, which are hosted on AKS and ACA respectively.

   ![github action for load testing](./media/github-action.png)

3. The workflow file references a load test configuration file (yml), which specifies the following:
   1. The load test parameters.
   2. The JMX/JMeter script to be used.
   3. The pass/fail criteria for the test.

   See an example of a load test configuration file below.

   ```yaml
   testName: contoso-traders-carts
   testPlan: contoso-traders-carts.jmx
   engineInstances: 1
   failureCriteria:
      - avg(response_time_ms) > 5000
      - percentage(error) > 20
   ```

4. The load test takes about 3 minutes to execute. In this specific example, you can see that the load test failed since the average response time exceeded the specified threshold of 5000ms.

   ![workflow for load testing](./media/github-workflow-3.png)

5. Once done, you can navigate to the Azure Portal to get more in-depth details about the test.

   ![load testing portal](./media/portal-load-test.png)

## Summary

In this demo, you got an overview of Azure's Load Testing service; including how to create a load test, run it, and review the results. You also saw how to incorporate server-side metrics from Azure Services, and how to export the JMX file and results. Finally, you saw how to create a new load test from the JMX file, and how to use a GitHub workflow to execute load tests on the application's APIs.

## More Information

- [Azure Load Testing](https://learn.microsoft.com/azure/load-testing/)
- [Blogs on Azure Load Testing](https://techcommunity.microsoft.com/t5/apps-on-azure-blog/bg-p/AppsonAzureBlog/label-name/Azure%20Load%20Testing)
- [Pricing](https://azure.microsoft.com/pricing/details/load-testing/)


================================================
FILE: demo-scripts/dev-workflow/walkthrough.md
================================================

# DevOps with GitHub & Azure: Technical Walkthrough  

## Key Takeaways

The key takeaways from this demo are:

- GitHub Actions is used to automate the deployment of the application and infrastructure. Using GitHub Action workflows, the application and infrastructure can be deployed to Azure cloud with a single click, allowing you to implement continuous integration and continuous deployment process.  
- GitHub Actions integrates with Azure services to enable you to build, test, and deploy to Azure directly from your GitHub repository, along with tons of other integrations.
- Using GitHub Actions, you will be able to incorporate your application testing, performance testing to validate your application for production readiness as part of your DevOps process itself.  
- As part of this demo script, you will be adding a new feature to enable **dark mode** capability on Contoso Traders Website, along with adding playwright based testing in your DevOps cycle.
  
## Before you Begin

You must have Contoso Traders deployed in your environment and setup with GitHub Actions.  Please refer to the deployment instructions [here](../../docs/deployment-instructions.md)

## Walkthrough – GitHub Actions for CI/CD

GitHub Actions is a way to automate processes and workflows in your GitHub repository. Some of the benefits of using GitHub Actions include the ability to automate your software development lifecycle, integrate with other tools and services, including Azure services.

Let us take a look at the GitHub Actions used by Contoso Traders for CI/CD.

## Review Workflows used in Contoso Traders

1. Navigate to [ContosoTraders CloudTesting repository.](https://github.com/microsoft/ContosoTraders-CloudTesting)

2. Go to the **github/workflows** folder; inside, you'll find the workflow **YAML file** that are used to deploy and set up the resources.  

    ![image](media/actionlist.png)

3. Here is a quick overview of the workflow. If you are interested, you can review the workflow code to get into more details.  

    a. **contoso-traders-cloud-testing:**  This workflow provisions Azure resources used for hosting the application, deploys the application to the provisioned resources. It also runs load testing & playwright tests as part of CI/CD cycle ensure the app is production ready with each change.

     It includes everything needed to get the application up and running in an Azure Environment.

      ![image](media/provision.png)

## Monitor GitHub Actions Workflow

GitHub Actions workflows can be monitored from the Actions tab on a repository. This tab shows a list of all the active and past workflows, along with their status and any associated logs. Users can see at a glance whether their workflows are running successfully and can troubleshoot any issues that may arise. Additionally, users can set up notifications to be alerted when a workflow starts or completes, or if it encounters an error. This can help users stay on top of their workflows and ensure that their projects are running smoothly.

Let us take a look at the workflows status for Contoso Traders in this public repository.

1. Navigate to [ContosoTraders/Actions](https://github.com/microsoft/ContosoTraders-CloudTesting/actions). Select the workflow **contoso-traders-cloud-testing**. This will the history of workflows execution.  

    ![image](media/actions1.png)

2. Select the latest run from the list. In Summary, you will see 5 jobs listed.

    - `provision`: Used for provisioning Azure resources, configure access policies and permissions, seeding initial database.
    - `playwright-tests-ui`: Used to execute playwright tests to validate UI functionalities before the UI service is deployed.
    - `load-tests-carts-api`: Used to execute Azure Load testing to validate performance for Carts API.
    - `load-tests-carts-internal-api`: Same as above, but executes Load testing against an internal/private API endpoint.
    - `load-tests-with-chaos-products-api`: Used to execute Azure Load testing to validate performance for Products API with simultaneous chaos experiments (fault injection) enabled.

    ![image](media/actionmonitor.png)

3. Click on **provision** job. You can now see the detailed task of this job and expand to see the logs and steps.

   ![image](media/actions3.png)

  Similarly, you can review other jobs and workflows. Workflow are set to run on push to the main branch so that any new code change to the main branch is automatically built and deployed.
  
## Demo – Experience GitHub Actions in Action  

Now that we have reviewed the GitHub Actions workflows, let us take a step-by-step approach to test the end-to-end CI/CD process.  As part of enhancement to the Contoso Traders website, you are asked to add **dark mode** functionality to the website.

Let us add the dark mode functionality to the application by modifying the source code of our website.

  ![image](media/L300-1.png)

Your marketing team requires changing this to following

Contoso Traders is one stop shop for electronics items including smartphones, laptops, and other popular gadgets. Contoso Traders delivery premium quality electronics at affordable rates to resellers across the globe.  

Let us make the changes and experience magic of GitHub Actions.  

1. Login to your fork of Contoso Traders repository and navigate to Contoso Traders repository `https://github.com/**YOURGITHUBUSERNAME**/ContosoTraders-CloudTesting`.

2. Create a new branch **add-dark-mode**.

    ![image](media/addbranch.png)

3. Navigate to appbar.js file, located in  **src/ContosoTraders.Ui.Website/src/shared/header/appbar.js**

4. To make it easier for you to complete this change, source code already include code blocks for adding dark mode functionality. You will need to uncomment the code block, let's start by clicking on edit **symbol (2)**.

5. Uncomment line number 8, 414,415 & 416.

    ![image](media/uncommentcode1.png)

    ![image](media/uncommentcode2.png)

6. Commit the change to add-dark-mode branch. Click on Commit changes after updating commit message.  

     ![image](media/commit1.png)

7. Now, let's update playwright tests to include testing for dark mode functionality. Edit **darkmode.spec.ts** located in **src/ContosoTraders.Ui.Website/tests** and remove the `skip` annotation from line 7. 

8. Now, let us raise a pull request to merge this change to **main branch**. Click on **Pull Requests** and notice that changes are detected already. Click on **Review and raise Pull Request**.

    ![image](media/L300-5.png)

9. Change the base from microsoft/contosotraders to **YOURUSERNAME/contosotraders** and click **Create Pull Request**.  

    ![image](media/addpr.png)

10. Merge the pull request to main branch.  

    ![image](media/L300-7.png)

11. Merging the pull request should trigger your GitHub Action workflow, it will take few minutes for workflow to complete.  You can navigate inside the workflow to review progress, as documented in previous step.

    ![image](media/workflowrunning.png)

12. Once the workflow successfully completes, you can navigate to your Contoso Traders URL and see that the dark mode functionality is now added to the website.

    ![image](media/darkmode.png)

## Summary

In this scenario, we looked at how GitHub Actions can incorporate entire lifecycle of application deployment, performance testing & testing with playwright as part of CI/CD cycle.

## Additional Reading  

Reference Links  

- [DevSecops in GitHub](https://learn.microsoft.com/azure/architecture/solution-ideas/articles/devsecops-in-github)
- [GitHub features and Actions](https://github.com/features/actions)
- [GitHub Advnced Security](https://docs.github.com/get-started/learning-about-github/about-github-advanced-security)
- [Microsoft Defender for DevOps - the benefits and features | Microsoft Learn](https://learn.microsoft.com/azure/defender-for-cloud/defender-for-devops-introduction)  


================================================
FILE: demo-scripts/testing-with-playwright/walkthrough.md
================================================
# Testing with Playwright: Overview

## Key Takeaways

## Before You Begin

1. Please execute the steps outlined in the [deployment instructions](../../docs/deployment-instructions.md) to provision the infrastructure in your own Azure subscription.

2. Once done, please execute the steps mentioned in the [running locally](../../docs/running-locally.md) document. Basically this will ensure that Playwright is installed on your machine.

## Installing VSCode Extension

You can install the Playwright extension for VSCode from the marketplace ([LINK](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)). This extension will help you get started with Playwright quickly. It will also help you run and debug your tests from within VSCode.

  ![Playwright](./media/playwright-1.png)

The Playwright docs have more details here: [Using the Playwright VSCode Extension](https://playwright.dev/docs/getting-started-vscode)

## Running tests in VSCode

1. Restore project dependencies:
   * Open a cmd window and navigate to the `src/ContosoTraders.Ui.Website` folder.
   * Run `npm install`.

1. Set environment variables for the remote testing endpoints. The values can be grabbed from the GitHub Action logs that set them in the pipeline.
    1. Navigate to [ContosoTraders/Actions](https://github.com/microsoft/ContosoTraders-CloudTesting/actions)

    1. Drill into the latest run by clicking on the `playwright-tests-ui` job, then clicking on the step `Set env variables for testing endpoints`.
    ![image](media/actions1.png)
    1. This project uses a `.env.playwright.local` file to easily manage these variables. Open `src\ContosoTraders.Ui.Website\.env.playwright.local` and set the following environment variables using the values from the logs above (these will be unique to your environment). NOTE: Do NOT check in these changes to source control!:

    ```bash
    REACT_APP_APIURLSHOPPINGCART = ''
    REACT_APP_APIURL = ''
    REACT_APP_BASEURLFORPLAYWRIGHTTESTING = ''
    ```

1. Click on the `Testing` tab in VSCode's activity bar. This will show you all the tests in your project (in a tree structure). There are tests that demonstrate [API testing](../../src/ContosoTraders.Ui.Website/tests/api.cart.spec.ts), [Authentication](../../src/ContosoTraders.Ui.Website/tests/auth.setup.ts), [Visual Comparisons](../../src/ContosoTraders.Ui.Website/tests/pages.spec.ts:63), [Emulation](../../src/ContosoTraders.Ui.Website/tests/map.spec.ts), [Mocking](../../src/ContosoTraders.Ui.Website/tests/mocks.spec.ts), and [using a CSV for data](../../src/ContosoTraders.Ui.Website/tests/account.ts).

   ![Playwright](./media/playwright-2.png)

1. You can run a single test (or a group of tests) by clicking the triangle symbol next to it. When Playwright finishes executing the test(s), you will see a green tick next to your test block as well as the time it took to run the test.

   ![Playwright](./media/playwright-3.png)

1. You can navigate to the test code by right-clicking on the test name in the tree structure, selecting `Go To Test`.

   ![Playwright](./media/playwright-4.png)

## Debugging the tests

1. You can set breakpoints in your test code by clicking on the left "gutter" (the left-most column in the code editor). Right-clicking in the gutter will show you more options (like setting a conditional breakpoint).

   ![Playwright](./media/playwright-5.png)

1. Then you can run a test in debug mode by clicking on the `Debug` button next to the test name in the tree structure.

   ![Playwright](./media/playwright-6.png)

1. Once the breakpoint is hit, you can use the single-step through the code and inspect variables (Note: These debugging features are already built into VSCode, and aren't playwright specific. [More details](https://code.visualstudio.com/docs/editor/debugging)).

   ![Playwright](./media/playwright-7.png)

## Testing with Azure AD

In order to test authentication, we can configure AAD, then run tests to log in to a Contoso Traders account. The specific steps are:

1. Identify the Service Principal details created in the [deployment instructions](../../docs/deployment-instructions.md).

1. Add the above Service Principal into the the [Application Administrator](https://learn.microsoft.com/azure/active-directory/roles/permissions-reference#application-administrator) active directory role.

    1. Go to the Azure portal, and navigate to the Azure Active Directory blade. Then click on the `Roles and Administrators` tab on the left.
    1. Select the `Application Administrator` role, and click on the `Add assignments` button.
    1. Select the service principal that you created in the previous step. Click on the `Add` button.

    ![Application Administrator](../../docs/images/ad-application-administrator.png)

    >
    > Notes:
    >
    > * Unfortunately, there is no AZ CLI, AZ PowerShell or Bicep template support to add a service principal to the `Application Administrator` role. You'll have to do this manually through the Azure portal.
    >
    > * Note: In order for you to add the service principal to the `Application Administrator` role, you must yourself be a member of the `Global Administrator` role in Azure Active Directory.
    >

1. Create a test account (MFA disabled).

1. To run the [example test](../../src/ContosoTraders.Ui.Website/tests/account.ts) in GitHub Actions, add the test account credentials as github environment-level variables.

   | Variable Name | Variable Value               |
   | ------------- | ---------------------------- |
   | `AADUSERNAME` | username of the test account |
   | `AADPASSWORD` | password of the test account |

   > If you're using Azure Pipelines instead of GitHub Actions, you can set the above variables in the `contosotraders-cloudtesting-variable-group` variable group. See [this document](../../docs/deployment-instructions-azure-pipelines.md#prepare-your-azure-pipeline-for-deployment) for details.
   >
   > If you wish to run the [example test](../../src/ContosoTraders.Ui.Website/tests/account.ts) locally, set the credentials as 2 environment variables: REACT_APP_AADUSERNAME and REACT_APP_AADPASSWORD

1. Re-run the github workflow `contoso-traders-cloud-testing`. This will configure the Azure AD to enable login functionality in the app.

   This test has a beforeAll hook that will log in to the app, then the test case uses the logged in state to fill out the personal info form.

1. Read the [Playwright Authentication Documentation](https://playwright.dev/docs/auth)

> Tests written with Playwright execute in isolated clean-slate environments called browser contexts. This isolation model improves reproducibility and prevents cascading test failures. New browser contexts can load existing authentication state. This eliminates the need to login in every context and speeds up test execution.

## More Information

* [Playwright Documentation](https://playwright.dev/)

* [Using the Playwright VSCode Extension](https://playwright.dev/docs/getting-started-vscode)

* [VSCode Debugging](https://code.visualstudio.com/docs/editor/debugging)


================================================
FILE: docs/architecture/.$contoso-traders-enhancements.drawio.bkp
================================================
<mxfile host="Electron" modified="2022-11-24T11:44:25.430Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="0ivnrrHtWxYojl2-M1km" version="20.3.0" type="device"><diagram id="cPhL8d0vtsXr2UynWkRd" name="Page-1">7X1bV+JI9/enmbX+78W4ckS5RAI0DhUaCdLJzSwITiCAOIINyad/929XAoFExG617WfsNQwSQqVqn2ofa/+hV+ebxsPgfiwWo9vZH5oy2vyhW39ommpo2h/4TxlF8spFyZQXgofJKLlpd6E7iW+Ti0py9XEyul3u3bhaLGaryf3+RX9xd3frr/auDR4eFuv92/5ZzPafej8IbnMXuv5glr/an4xW42QVprK7/uV2EozTJ6tK8s18kN6cXFiOB6PFOnNJr/2hVx8Wi5X8a76p3s4AvBQu8nf1J77dTuzh9m51yg+uSvfii3r/9/lf3e/KtRGEs+XDn/q5HOb7YPaYrDiZ7SpKQRA8LB7v809LJvD99mF1uynCxWCYjrBbLtHJ7WJ+u3qI6L4tkaQwS2hEM5LP6x3E1RSM4yy09fTqIEFzsB19Bwn6IwFGMWDCzSKaL+szY/pvY9j/ejG3/h39qeYBU7up2Q5d6jrXtYrIgYmgdDe6xZjKH/rlejxZ3XbvBz6+XRNv0LXxak5zsFT6czYY3s4uB/404J9VF7PFA311t7ij+y+Xq4fF9Da9+Ieml0qKouvbb1JKxJV/FnerzJ0K/8P1yWyWHeG8clmuY4Tp7cofJ9MA8iZE7JXZJLija8PFarWYp/P7ulhOVpMFvvAJ7bcPmV+0Dm5YLbDCQTLO9vbRYDlmmKjJlLoJwJaLGeB/uSAQTVZAvKokq0mEgGqmn5OfFFD8cZo+pMw8Bf5KAlMKOK80owdfDvcIq/TvI0TE5d5fQfLO9z+eeH/39uH7hAhSUy4fl+mPafKPhwPSteHu2gGdkxy7x59+NJsQwT/oz1P7ULJGa7i9sKX89uOKhrlNri8zqH8hi+BfvX4iQ+wNknKTtkeOpnJIvofMIkn+xZyy5bEcs7w+fSffagfyVTkraWZOxF4UMYBWujgz9HPzrbhALeCCQ4JLJRYwMljey23+n8kGmMmS2f1icrfiGZqXf5hWEYgnc97v9+SMtrtuTeYBrWM2GWI1S39AzFJP2OZvYpqz5ffgadIkaiuX6/VyOUefo9t/Bo/MWnmSO6DXeh2UXEyvb0YgurlHIGXt7ELVTCX9p+ZoxSygFfPNRKX+SSQfj0i089IZCYYtkSilX0wlxieVfDwqMUrKWbl0saOTX00l5ieVfDwqKRnGmamrBUrJ+xJH6ZM4Ph5xXGgXZ0p5t9W8nzZSuB714pNIPh6RqIqqnWX3mV9NJeVPKvmAVKKWS2fvuMsUrsI8QYC8tc/VuHje5VrsEHg7j9h5XjVrVJxav+L+gWeWBnN2MfL/6UrzZ32wW4c87g1mg+Uy+XvP6/OMGyrrTX2p2/bQ0XmCG7der5nMhk96bvc9pq/nyT101Bb4ck/k4i39v8g/+4bUqPeXUWk0aamXDVW/+3I9W5ibP/UTDIUnuPRlq3457x5683Kcq5YKXXlvBapTbKoXcWaWGfc4LEt1g8fVIsdaahHLVKvMMgcs9g//K2LbLfPlUXucVF4WcHhnJOXdI5X48QHRgIq/mnzHH9bkgehwkTwti7531E4GmBUWOxnRzyFytDrP9G85z7+3s0yUln3Sel3auajWqlWM+jAYTW539JOI/6ywNV+fWlKGP3C8XeQY/ryAlErGG1FSKn8+nmRUjUN1zzDywrEw0Pd2+0iRtreN8+UCefLCX7fRDevVPxqk+xX8urz1Hx8kv9L8/+YFLF+JR4uk87Pce1GplSzzae49kWG39H56vK20z7B5n2fpooBhL36eBIvNphM8408w7MsAcjrD/kJ+PMEwf2Nl5YeIuVKuGrXLlxDzcVp4heSI18BR8SSLAn6fMvNjysyfJbNfLjOLZdwJMedfpOToBzpOqfyOOk4xtn/cVj4O/N9xgzG033ODqdfhof15zv8xY/idcZRPO7q+HU2WdKk68Me3H2JvGA2IzAfLW5pVnWf1N8/xV+4Ozykhz9jDP0sw6VZxsScB9byNl9q+e/587eeJqdBvnVp4GXK5HQW3KRwS0Ozjaw9Hi4fVeBEs7gaz1gLOV74Y3q5WUUIrCeIyNHe7may+YawzM/nkJiPjb2uT/RClH+5otZkf4aObjocPu5/xp/R3OXopl43qU/Ryolv79m5UQXY+mASkO/HlxfpktnXPrwYPq4ObnPHkLv0qc+sL9+Dl4vHBvz0iHvRE+aDHBLfHNqrU8wl8H6Xch9vZgJ1s2b3s9Smx/CspUcuSovpDpKi8lBQvLbP2k6T4Ajo7gWp/GSmmascHIcXUR/DRheJ7UdRxakrAsSOlAyY5f5HAfnUaLJ9Igymxvh4NJj/9ChUrYwKloZM0fez8QFGUM01+dUDJ22n8hL1zgor/a4zD833AqBf5NJhCVfuiXH4jVVs/IafuI5pDzzlC9rIHTlV1tSfw/HLb6A0Rlq98q3xt0gUxuCNrZY4lps64h9SZ9n+Nwep2PYj+34ewnAb3938vZXITjCea/t+7yf+dpD39UkOqyv9e2ZA6nbpSQ+ogV1stn+Vzowpji69gmPeuvt3OxsG/E33mNbRhO1jMG3/+uGB9tdQo86BaqqgYVSlMRnkNqBRitiDiKtp202lfN+3GH0XJUU6tVRM159r9OVl7SiZUJgXwKX5JE4SeTpA6Mf2pemHVLlk73M9Gej6/6s0rXZ+pVD1K7S/bDt6O+AoneUKF3hMs+aI1v5hPi/KelEI3ovFWfGm+duLTj7PYj2xJP5qCaJVLpUKl5yj9/Hi1agGelSI8v1l8wyhQiZLcKbG4m6wINIdoT6ukU/0lpwalCgy8vH/oFflRq0Mn0S43RBFa9esXW/Oiy3vPUiaecz2zY1dxtc5KzHu6iBTFjq/mrX7TaDdqK3fuGu7cC8VcRG1nqjQnl8awv3n0Y2Uy+HKt+Nbie0sf6aPIpN+a3/25/12ElbWoluPR3J80v4zuvS/Xi6/d5qZdbQaDxs29p42V9PNoPpuNlKvvtzQXUa2sm1bN4FdoXHiNm7kfGxf+vD4d9OvLa/1q7DVms+FdJxj0O+XmvB62G81YzK/HLUesXa22shtNU0wUw7Mu5y1nPBXzjur13ciOx2NBz3P1oNwMe4923Jw0G7ZJ89BbYUeldUX0t0F/R/T3nXzmzeOofh25fTP25uVo6BA6v1yNh3f20v12PfvLEo8iFIHoGqroTgNfs8fDRm9Bn9eiW9m0wsrmr24z8Lbzvr4ZzmcKYDf6Zs/8mb12+/bM0cwbX+tNvoabeNQor5uN8ty7s2cjhonBcPL16+9+VQ2H2ua7HyqTZnwzc53LmTdpBi3AiL+//O7Nvdj7RrAJK4+24xL8bWWoV1auVl4O9Wa5ORGqG8/mXr83aVbJPrz8Ku8Ihpo397UbYGbdCl2ToMCzafXt78NGGdAJbadnutrNtDmpfH9itnrxbK/HbWc0a3efnm3bKpxtTFS39pzO0dlO46LZEm4MW7ueHZmtiRnlZ1uh311N7SdhWyua6VpYPtHddqbVMlHO9b3b33zZUpN1jJprZtvpKW2riVXQyoOVbU0V4kizbXWiVt9du05Pazc6kUuUZXdTagbVEsdMLsNbolDivqgVNs2Eoulv4sLGiOfi9U3lRlLwfKhfrb52rxSaz6OvbcYjotyWUyNu8PVmVdm0rd6jsGr3tNKQ1kDUTjhyevf73HHzOPh2HdM4KmEj6tGuQ/TxdTi7ib1u80l81Qqpq90QhkfYPoKv2C7EV0BQcw1Q3pO8MCn6nUeyr6KLY1TpFOJacZ3rid0QT1PlppAitV7shfZcrnD9vYAWJOVMvb4XEwwUgu7Sc8wrevZjq6+Oh/P6nddVYuH4Fy39+n7U2Mzovtmwsbn3v9gzkmkGPclMqYHp+w5UEjwy7O6m8m8LVOHdDxvrcvPuJhpWA8KyMNxQRB7JfRFfhm5XUdxwGrEcbbgrUJ5n3cy8sKnT3rD+K4Gb198sB98uCeP0TEs+09XxnCnB7spIZCz+TuSt0Gl+8wH9DlAheCcQ2axJri6aDYL9VAHlRi1rSnO9HtsTI7Kra40oW2lZlaWortctq0ZSo7m0G2ua49VYRCSHo3XcCntxcs+GKHolZovdWicCEmzizd0t7i6J9oMAu6Hj1OjX9LI6JNlHl/zULj9V3T0VM+osxcTQ7bqr4mmuI+habUkzUEQX99C9XXp66NNrNMiuVsRXE69/PS1YsdIKAz15No2/jo6O2zX0m7BJO9n1E/PsEHT8pV2l79Q9CNCOeUWaVKcYAhjdaFku/foGMNVatM/Zk+3T6Yk0I3q66AfAkek67qNw3CXNYrO9jyQI4wErOB0CWEGUPHvZcporMa3RNXpZ8hlyZRWGPkGIJF9P65Kksp36ZcFcs+PtzcGmvdMuprsNQYxea93tGorNr+2a6Dn+ClB2mAp6y3aVMNNfP4q4Sc+oAdob0j9o7W7cwW+reKW/X2/H7pM88uaz5ZBg3tTctW1VlPbp/ETjN1N+kn8f8rIzNW2nY9BuQHKrGberitJ2OoSPqWFbzZUXXo5JP6D96mpCUl5Nebmlk24YG1A23+Fkmn131Al2yqvbBr+qWM/Ip4tW7u9nBD4GmqY075aYxvLNLQCX5K8975jtfo+ogmQDKMWqT1tOENtzd+X1a2q7YYdENZFwRvOPZQHUdNep09wESXrsrB2yAGqGN1EglUzsXC5Rva31VDe8DHc60zSj/YtU+9fy2v+P6DdFO79LWpXbd5Ujuk0kinQb4l2yXebimJ5viQIdxR7btH96/eOas6sWamJOUyOYHZutUazn+6rtzKZPa2KF2pQmwuu5Z1We0ZyHjdmj169HeSq4CUVDKCTTiGpvSI/skeYcGCSHTXtO+5kTmJ7lkkZbMbx+k+XsrVUDFZCGCzk63dCqaeefQmsjmQmZa6yxiluniVmTjO2Yb0MZXng1F3M3OkoZURFluBsvrDNlJN999xv1kGZlJlaReVSTLhzzhjTP63mGEg/HVI+MqRTShNbTPedmLqpPjRkcm6daPOY1SXvSYZ6cZ7A5MqZWvPZOTBicuU/C0z/KSb5WyPfxNek6PWP3XRk2//3oyzSx0Y+PGijF0uR67M576pOjWrXj9nJUOKpTMe1+b/PUqAJ6xrFRn7DqmrrXuA6fHNU6MmqhFdW2pro7f3LEnRRZDvrmzP12tSdFWqCdO8HPS+yn6YyowFgN+/VHtz+akc66Edb6+6gxU4aNXl5DprWStLBgKwtzSLJQdBXS/kZ94ZDuPg30EenlpOUuQYfQ6UkTjEljpddWEzTk728WpOGy9kivnXbrANqC3n26PtvTXIlGQ69vzwo0V7NF/NJxejSX6ZI0V6O91VxhFVXMlkXjWaOQ5k92RhNWIDRpjeZF37tRiz1aU2jyut2v0ffQcqfbeZEUxJqNAWwU7Ynvaa1kyZPW769II9+wRQa4hAHsmKjdhZbvPzVPXkN/qtD8akqxZjsai75QRFxb2XOheaSvuM543nJmtONNyUola9ESkDkT2hW2mi3xsjKoXk5JYtvCwdxdzEdv91m6w6LZWZYOcNLja2TjkYUx7tM8I6HwvRtYnmyXJesmeKm0hhjWEH8HyxNjpt932XpVE7qRv+1mbDq2bHsmLBU7HC8IF8oo9MYEa41f6byqbPWsRxNFGxD+hONptMPRXF28pMXLeIDlfKmRToSxyD7Nz4meH9tdWCodwkXB91kY3dzbdoR9aE2woDk6NzQ3X+FXaml3Da0NCyn26dXBK7XSaT6uAXuVxtDJAtK3FhGsSXgj4tFgmNJII1jh/puwR+ubjfm3eKW2r5XgpkbziwVe2+fwnB2ywrIWf0I3Xl+sSRrPRYN0xL4wvC70XLJgnalG8Fm1rdnUDmuGsMYzl3b7Hd2Ul7f90ffh5HI91GaPoy+CpAxZlI4bCNBwtbKBx9euwicnPb/bz0TL9qRCOJxqpNOqwGXTyv42YwXGU7N99JmgvYpC48Ai3tB7+pnGdfXduIQfKwiIJk169ga0KSYVHTwvfxtomd/KsTLype2kesP6u697d18DNgb/I/ZgWhp9GKx/v+PliiOCBZF6Gnjiv4d9eD1u95uqsHyyDysG7EOvQbKpX5/Z/ebKIx6C1kD73ViE3uRX2IdPWwa9jWf5sYd9yfFVMac9g/iF9iD41DfwqXtO06Q9Y9K2ehF0aWkZuFIzk5YB8U5FSy0D8BP0R2kZQP64+/71vnnP/HpHlsC3Kwc6pNcrP958oz07LLTgimwi3Y5tWstLPcz7dub6ewFkMFpjcz+c34z96W7ezcYMe+OM9m7Ntjprt6tEdlwh66nDuG83OiTT1NDuQ/fubNqNK7JdYD0FgBHLPIafU0tkVU1NYQa5nFhakYRnoL2NNUW7T0wrCp/U/ottbN0mrrQTq7Wl5aFDY+W1yWrKIZ21iAOV9tmV7TQjN4LffDQlDpmTTr1y4yCinXpOGotCeit7UAQiNQ52uF7G/vTNHcS230VSu+79L1HZmHZgsslJgkRKTDrcpAUvjSNWJG0mpNGpHmlwkCi209FE3EmpLCLKAVWZEn61JGoYRHvfwX6vSi79udhroYdDF1p9Svb6Czwc9oQ0nLEXdl7FNmk7B7bJ9+GX2R3dFZDecE8rjYE36FkiQhSa5mrBZvFpv4eu1QwSnUBhfxfrkX7ANodF+gPp7G3oMGTXSN2F8Aydz6oFCR/zvaQ7SR0Gv0H8IarsPzNrsxBl23M3tfMPo1XQffU0YmWzXpxGrUif20WONom/KhmXZMV8vWhqL5Xx0xQLjZslUe6a8J94BVkrinaxM4M9g7zzkHYL+SDjZ5hVL69hOuOQZqsIxM9CnzRBRbEd0rwQP7M6JAfYuzIVznjiam70VPxsy/vSQxklkvTkebxUHj05jwlRwDb2wFZv9KJ5wBfX72w8hyxB+JInClltvbjVb9JzoYUTz8cicp2eYodC2Wrc85uQZCsoQ2q2xNOIaSZ+C33Qv1YGcn7qgcZAOwv7WuQcq2Rt7mnYlbUdj6epnM88x0TMK3nOBuPinpE2m5LVjwg7aQMBrW88G/RHC8gGonSTY8pMpbDWg2yEbS2sm4nr7LwwVzNX7wQSuwJ2LyQ7/RL2fZDVgaJkBQuvP7sbfOkkkK9FR1fVJ90rDz1E4LRXXJXq9Wtrr+/upBg0OPrrP2UbqOoHsw3yyc2Nh8E/g7sBXbQGy/FwMXgYvaWd8IH0fZJomj0VDUFSxpt6jotIgG53FbLrL0nLEKRtXM9IQpPNYk8ykYBUC4t2EYCt9/8VdNUkQyevr/ZvxoJW9SJ99UCOFeur6+8ptHa5fhXTDptx26qtvEZNxb5ga7WILbkG6V796zntkGva5TUvvJowxHcSxUygs5O/B9JDOJ00Pp36ww926pdiZ1qYoRCQzKzp7eoa+RIbP1JIJ6nEiNjQrgk/E0nfwIB/Q4ScwWCSHaPCt9l2/HUmmo/I+QZZI6QFX2K3E+FUgV+0bTXNTNReQYYZyWn48VTkZAhrupH+vgppoln/Z08lvQg+1TU9K4aPU4QdU3TZ96e0ndqeT5D0LqPFOllgug5yOaaaHa05q8IO/fTeFfvqHKGz79DxI3o23TPVeTz4eeLpZj8rJjDYT2a5hOE14EP6GcMpEn34VTtr0tuQraAQrLL+Rc0OA6yXNAiBsWPbwhzhn+4A5ilMaKyOQnOFTqbZPB+CLfxyyMywfDU7Hzsme7GL7Amx4fwWZGwxXIVhA147nyTBDzkzzUiE7H81SYNhn2nb6cH/uM2koHs0EfLYpAXg+aSzOpyXAwpWsmuiuWrSp2svRGO9akEeWS7eIzsc7a45yTVkiyKbyBEGZ5Q4tTi79jZi4+w7JRpzBOap8Ton2fnVCIa9FWAt4GeHf9RCNgq0JdLAJntZJYAPxtNsZzvHDe3PK7mum901q8lzbSPjCTRrwecMmq3oOz+6wtYQ0Sb74AmGJnE7rhE+sUaaG3/uxcJpgg4MohnQqAE6l2uvIa9Qh58dPm+bxqJ3+p6jpjHjk/R8xD4IlqZdY5uf1si+9A3TUQoLovG2JeDvNqHfsD9awgQ0lPqCkbeoksUHet+4MdMxaB1wUYk3QT/wHSNSawJX7EMPA5N522rqEvfsL5f4DJsMX6L7TUL/a3uOHC+CM+g7MtbEZ5lsMYKjhNkakqkDXo/pXn6moHt3Pm2SNwatn54FG6gTt3sCPhCd1qniGmBDfAH5At+/LjOR8GzfoHUgc4hgWdvIHK/O2uYYDFkPlpvl+w3xnSLn1Uv4o7Z7T+7D7gL8JPdLWmMaTPzmaUwD+aRkq7WtNMetl3nfv4/hFVdie1oDb8XMj4BvvJ8Lh3gRy1DsJGSpsN8fmWCOv+NB8DHiWxwH2ct8ijj+PUl3Ml/qlXf/ae+zefHBNMz8oVW7nm9fbgczmtXbFyp4Y4+MQYhUMh5jj4xJUloQvtkIJHw0moZLLNZuuDHUiI/lhnZVz+mpQvPGcFHY7KKokBmnjt15TW1B/NP3dp/uI1GKdA+plgYQI6lqSiIMyYX4rpn9LpKmXiV+G5cqbSJGu9E5lqQRicL0aoJc4/qIq+zJEoXYjmeh5xwvUZgWFjfYjfrM+4kkAE+7efS+XY2HVUWhXycJ2Pvh4zbUpMla4mMuOP2+JV1hnBBqTxQTWymrdhaJZbjW4huLw/kTVgV1Dv9zKJPEvVQh1naPQ96aFMm7ZNfkedj2OJ1Xqk7NnWjn+/1kDCFTCaAykhhOVE89TaK141Eo1ROEBIl/6LcI4cNh2mI3oShwsb2UfosVdyQ80N1yFh8QGkgt73ByN6f1knKLRKkMNB6HmlmUeMGUIBOwa8YACQlQgHAt2il5gg2HkZBu6xqeEduNGtKaM6nNULYAj0sBBUPOt6K6SCDhV1ZppN/UoFggcaC25mdmU7ChmGFzrmGMwJDrrSOpYMOv3easwihykPwRT2XyybxoXqw4GV1OYCDlE3hyxHJn/Ex5gxfTgNefJH/sDAZHQCkzHBSWkJJA69u0+7nnLCUuL1mJkXgOtPz613BeYW3Eae4GiS1IV2f6YhpLFDpJH+uhlSQzILFFo2dGRpwxEFA2wPcQB8ukFa1oXrR+pAXWAsZtcQnDf1htOT/oimLmKyzfVW0pFeZY7xKr/3POMW/sksHixrNZyxnTd6w1beyqYth9mSbrOvbUs0Yh/X62c44FxAedzTbs1mXjKA27YffjgE3iLIOBw59fP/T2XMnjiWV5z2oEibPekcmigp31O7zYbEA1D1xfnPCWDT3oNuZzGCKa1yd26IVItm836lOPS6xon3MCNUlCGgvtempr9szt98ynQjMcwENgTIaIkFgUtTNBM5rPhuci8UVGYabca4LCF/fJINgUwZxtARlJOmUXEvi5kWEguuYupATDdy+kRKPVMsG+Gicp74xFwvTkiVBVxI6jFB5wMmzEj8GjOJzKLgd7UkGgyGxXKygT0+0In5tk0iJMKnBdkeFSuCo6nOplxz44lEsy6ff4HWnLFeyOa9KZEG7VUUTcRnpcTO8oELJqCNFqdhfjVqImuy6RQjaFi9A4oXhOY5dll3VU6Spil8x0uX1PdvXEVZO8c2oodkADqZkIGZFGsLLhBgybcFmSGd9JPl/FNtwd7LbpkDSDljHVZZGefcnjsXsQml3qBkK6mSvTWq3Zkmy3R3Zz1hdPY+OwNI1oAns+wptMP3riTFVRMEd4V9qsM7kG3cfQg47RtjgR19gV0SFhF8mV08iuBRJa8r7H9tYhCZ0nSJJ7x6F0JLFuAT0mTt4Veyq4zAyrJYgp0PqJjjU4R9pWTyYlxnDe4vPNKnH4wf0eMzSqwFYlcWL28DxN6r2chKSMJiRvY2/MzjLoPOn7zmmqsC7I79cLoiRNhN6Sx6T1cnKn1ZPOY37fOmbhJFYZlnD2kPxHuV7bgVUNZ3SFNHueA78L6FnJ573ytcOw6l6iLZy9cEIxB+iCnW9TOMTT94zDUhiydK6ysb+4G3Zg9vl3cKCl75kiyNqaKBHHFZxa2mcyvqqMp4h+Jx1SMcMuFpC9dF2E7MCS72liKWQGO06J0mfuhn/njFFyaLbhdE7fUxuF90j6THAkWyTah1dv7Tk7WbYPL6Jp6TSk8ewxO+CZw0RWp1XAjVKOeThuwHA5eTZxru/r9ibJxRXo0j4ogTxSholQO3OvnIeQNhcn2DbhvFbk85qJ87AZ7z+P9Ww8j+fWAQziXiSDCHhPpcEU5dW6dIoSLewXqaqi0dOelARIPdaFnCHthEiV7jzuQjZwXbrQ1iMRw6pBOEgY7ez3jou0vrjdWK/YKkisRLov5jSWMLEW4h6HX1DMuZ8ejRRnuI4x+160l3I8sWcItBEmtnuhfe/P7eUuxUPKhG3RPo3Wdtw4KUTjQps9R2l8PbcbrpLSzH+tTDP5tqwoH8rCuMgfYlSZ0WreISvXmc1tx5vYpMSTUFU51wiRM2eKrPYVKbwaEdWalMyZ3bj5aFm5iufQvKwpIhsbL8S5LT3TjdQxCe5xy6lA+M2g2ghrPN25Q6dZV2g+Uj/Bhto03iizlNRxYupjNZFPnNTikgoiIvvIKRM2zJOCukiPsEfYPV53Vnj2CVFK6Ibi2FkaRrHTNtA8i1Sr6MTcgtwG5j/KDeumzw6v2B5DqeHXrlJEhbsRjhRsFOy44ldWPCMH7rrPGYZwksEIDbOKWYeraVCHL6CwseJyvWQHDY3T5ej3+IlnQwEb96HeuulcshUpHHXtmV1WV6dRJ38P6E/lCqqbgkoT53om+j3U268EKjSJNz1nFrb6NcIcqdwWbXbaDdRc4s9OXFShhNxDu8rOrbjLCuX48qAaCFFVKBpRuwcnYCBPidC4OkZWyGwdV6Q8wAE6hxI8GvPnfYUCqnxsK6xYmxzVU/LjZOe0f35BRpnZZZ/BMRBvs89gSE+y2WeIgk/3jWhELbu77LM9A3rfLPivRwi3nQjefyPs/huMRtHUWRpf3H9bhID5eeukVp3pFvjP7HaTHFBdfPJ55uC6Iwd5546kw796PX+wHI6b4+t5ZCRwf/Zc6RPB+pPHTxv6wSnLyiG+5JHaufOncyOZ6nMjvd5J1sX0cEoz+096OE4P5wcdeUvKD5LDhfbMQG9MDVq+eu2TGn6WGi6M8lk5++91aOOZYd+aUk44FfaTUp6llMNWbmXzR4njuZHemh7yue0fjx72+nIMZwt/+hsQiarndhf9TP1BOlEP66WLBntrUjmhs+KvJBU+lv9/g1QuTOVMyfxTX4lqnhn3rQnohHbGn7LmVQiI9pGLV6KZ/FCvRyYT6+/+3fyv4d21MWrWzcHS6sR/nmDZvHUriQNo6gVHmRuFJ5m/WSeJgm48Vu2m/bWbg81v3SeiXi+XWY6/qE+EUeCaeu8+EUdJ+UXHZL4dZRXOMX9E/qns9qIl/ywPFrCgXtg04s2aCZRO0H9+j6YR2ukMuW3cciL6z59A/1GKf29E5m3gxmQ1fhzStf97uL1f/IFHoxvPwIdMWOabY23lJkC2eFzNJneEB6bv5GIGzzlR8uMO82IHfKo0zTcBEdn4bH1LikuwWJ4FclkvITXl15NaLnT7TFOtnyTE5NuD1oR6Xt68b25t3jAnAlsNiNAe6PL1bTBZps/51W3b/HRi3PE6/fB3MsfJtmPbu1PhS6juXYhMU5+nMvPsvKgL9lvt/+oJHqC31rcPy/HeU90uBEpe2+7Wqr3rpvM/1petTv+4J9T/hr69peWPrHCrP96Y7WWL/q1U7sKl/Rc17ppqmbXz0zVu9Skp/ItU7sJJ5guFurQq0hJmH0KBWd76jw8Qf1qd+8P9nc4uUVwOUKSz3PxBneEF+ErZ8nyfLQuK1Ut7bt3Ur7vX+fXijVCbajRZz9TtP7fElA8fE7fp7D4Ebg8Vwo+G3CIJLHtIAz572C39+7hIv/hzyeip0A26cr9haZZ+n7ae3jajTi/AclgsYX07D4MRWxNKvmN17W48uPO5I/Qy/ZbWNjwclq7JKaaXn9w41Oc3jqdkdHaLGA9469jfQ05Xmp+u+jzwxxsXRo5GtMLWsmbprcjiBZGcV0jIzhyI1VsfHN2nisJkaa7levEZFV8D5a9mtRI0rY1sEJd+lq9jCddx25mFwpmhsafWbuDgrabpVVWas4+jkQw3bm7Q3scL/dhNE64nykZYIuJjgNJzJ/haz5RHZ22P+0WjLBwllJ5HofExnuHB0cj7rQdX7rfraPAtWIguYFIJaB44WlS3u3z00QtbDVqdRZCBBkPkxGNwaeXX43ajOWnFhl2dVPbHsU4+lk2zw9mEoPf9EDOFadQnY68eijlShwkTfXtsW2gMKXCgF+p1w1a/o3gNO8TBPPa8x40hd9jjY4UjeS3FHh+laeyO0a3gsDRDbA+1rq250o8Pf/yo2BvyCDMF1TOtUJyMOZz08ER7m0ei8UPM8asg8R6nShQ3SpngZICLU1eE5huheGJVFfUFI2Wqn7OjkPx5ZWqs6d78Zoaj9Fr9mt620HLrijhCHXvheNpy3NizagZa/tja9e6IRUl5fPJGhhrNNgoGrCDaUSNajLiKPEoc1Ohyk7+DivKebFvqkYxUaE7qePTl6v62GmxAiSRjV9yGwfJVtEdoV6cvOTz7NAlQWOqBA8GucJD4qXRU3CY0DEzBB4XnsG89ddh2ete2eehur0gq+w6LFITZ7otNu++uXIIxzlMS4WxK+8PGjt2VcMY0j9qGdri5TeviRsZceye4Rq7NNc89xW4Ekc3Hgo3RsJFr6ppWsIZEsTXU6dWSo6b5nSWF6KJQZxqLCK1qA1Tg8ikCbT6aEHtJJX3P3h+1q4qKCkyHJRiOo0f9taD97KnnVPg5tjwOL7K/LbJyIi242FVE4gi9Rgctl7gIw45ROQ4actd83GAfkhfFKDNU4xK91oLtO+/r8qhG2h9R142K0qXgVgICx2yjZlGz5VxJBlXS93VCs5ijgpYdXCPeDyKM0f5ybM7p+Twv3SHAD1zpGfM8Y24XonKjVTSwdOhz8r5dF47RtETAh0LHM8FtRZ1m0ObadLTh6+CUiEA2uNyuCfBfE61wm9Q20Qq3iLZGY26MSeOl7yL9Dbc6Bb5mS26ngiIaWWuJ9iU6N2njwhx/bTO9ENdFlfQ9A0tX4xYo8VWzzTi7FoxPwsMOn+sVHysYozCmsraVtdrmIwkT2j4J9i+Vh1vYa9w2yOmh9p84TMLeJtpJ37OwF6jRx3GP0wA402xrnNzvBul7UogXJPczrsR8HaG4iuYVcFue8Oqp36XPCbjpaXy1ENwg76n7K8lz/FVyf2krr/brU1/ByDh0watGQcVLqcDIeA03aXGFw++QLfYk5D9IYthhxcvF+QG6frTgJTfQ6yWFzR71iwe9E00vHr4Z4T/X/1x9efzzhPKntw5SHWbgXuQZxCjyqp6/hlc13Cyi+bI+M6b/Nob9rxdz699RUUJl/bptO3SpZltHPB4/7iq/W9zdPss22yhUcRjpBCc4ssCYwQKwcMqr48GI+Vo5jKS9TzAq8TiaueBULnxWFKvLU+ZRQn9ZBOud6e7iGdraI4/Fw2q8CBZ3BOwFoMsXw9vVKkogmsRWMsR3u5msvmX+djHumZl8sjbJY/hDlH64o6V9y37I/Aofdz/jT+nvCkM6SSrwqTkFmT0nTRmmS/XJLF3Qy5AvhbK8T+8vo9Jo0lIvG6p+9+V6tjA3fyZ8L2XukftSof3sbnTyNvNTdKPnQwWnSvEX8crLRfuBbNcU/czMSXf1XDtLy3P2/Kx6ab+i6/zn4VeM9vwueH27HjyM4Cmv3N//nMTfE7UnxUH3WONQgCOVF6M+DEaT2x3bJBvIm1eS50noOEk+L29TzfgA2eV8xEZN4zF7VKIe1P2V34pK8gn0TBvK9kjpDxCV8xfz+8cV/P00t7+TmaV5Yvtkm6XLvV3/VYP1h3kx1eox8n0mOPizpJYq3cqZsRf6K+1JKcMoJKiswUYC61w1tAtT/r+UJ8snbnn1fEYtn2dOA+kc4qtDfHW/ntk1J0ecv+6gUO/e+zaqDvlgRXZ5bNjkd5p8Bs+ha6vdayrXIYec0g7IcKUFIq482spa4wNt0UWij0N6+fwTuLJMdO3FoXA4BM+OuGNKkBzW9ij4ALvao62t0cclEKHPv0cXBHYnTAwTRxfa7M6ZslthFPbouTh0md1cj2ICV00ncdXd4PBhdKhd2eFViI7C3I2AD4XrYTzu0CDdeaPE3bA9GGx7VJZriLCmd7YOS+59F7RxooyG2Rg4qQhPxqjswMPndn+ttKsGHHV6K5yFLRyw3sU9U1p5Ra6Ue9s1EwcTICW4Xx6gNLQ6hnSAzkLBUKvBAcNH6tE4gAA7Sm74FJc6HyeIHhqC+36gt4bLDp5hFY5y9xG998QdO6By2JSHPiYnxeA45aii4+jnDjuyauyUo2fqgrEK52ovsAnKON7QBobknNY0X4Uh66BjXYcxhZ7FfozjYPm7IHvdRe8PdljyUd4B1gWHpzzgEtgTK5yM40YKY5GPuqb1tRmTTXqeQM8XelZlhd4t7SrmIEx+DgIBRFHcdc/CtZtYXqtkrtF9NAdaA3oNauyEIqrDEbb0vcKHVzrTRz7UDT0zugrN0ys5zhT9kWlO6MlOeKgaMY7pps8aepPbGnqf1AVRH62tt0F/8zacg13Qg/so+gHhf8zHBfJRiH30yIHDm+kI+FzgJKNRyGPBkRYPGb9wTNoLOEvT7+CMG8reQ4E9DzAXc8j0qGg0pzg7rpxj+lxvwdT/xcVv9K411W/n+J44ryHnl1mb/My03XkUuqSj9z027gdOx3mlnUnVzreqcDarrMAM1V/BCi3eUbSPak2oe9t0gZ9I1Qp2YvXw0JBX0whPANSLLIW8+vQS26EgBZlL/p5QtfLYO04NL3KcvDMi8jqQZXfpgoel5jSfbDHEO+ro86U/gAZFU0tT5I5ruz+Lj+Rb/eKotquZZ/qekX2eYyujfKaWSztd9jyP2ydueX3hdELq8kcQTnpBBco7M8UJcZ6XSadD+XNQX8x23alUrD8B948kVfLJ1bnUyvTCY3qh/kA8bS0ILqkBVrXsTOrkY0E65fDJXMpf4UG4u12tFw/TyR2+o8n//fVhQdh+xomwRyDFroDXJ42U9cpnplHgJ9w6Hc9KWQl3kXcslUpnZSVjrReQmV4+082sQW/8PNUVBwHOn2HTzyDAxwgCpF7jDxIESGVjhm4ws1S/2BFKbXf1gJl/hJi2hLGjBXePFIoJ44eRnaebIzR2PGP8p6hEO5VKfhWRFAvXfAHmB1GfDiJFerlAfyp0/79ZNDZvVHTHk/t73hgVMbijPRTFEh8uRFQqJZU2HydEdJwWX6YDvhkRFE8yHxr8uljzUQFFWH+L4ow2Wk1mTpvmz3kvOjf5sTOnWw/n5UcPCcsNdTm8E+XmfKyMvlRKraisj3T/cRSLx6F+ddeKm2thVfhM6mZo/PW1kTaUqoYbeG4V71szbcR1PyS55oeX/3iTZnKH1/OWQ608bTbk+15SOSe+X818/WaJuXfm9XCgjaKhfvPo1WYrtz+aXc9nhvSIy9Gull7fpNHU716jR89EDEEdD3GWf98oN2f299G3q9DrjSL32+Vi0BcEndkjztN/evTLyPtmq0OUttzZC/rdLPvE0Xj0zY4H/fpjZ75RfU5svq5537x799v1/ZCM22bY2VuH1/fmXt9WxE0ZZQUzT7/6PqJZy98jiXpx0dJSaAGmyd+TpCks4WjUuInwewlzj2a6+T7UOoQ/fj9Mhg/8+U08Imx87V5VCb4P6TO7fXXsaTeNwbcAvlIei+YxG847wbBfnnoO00I07NeXBIn7IUGqp5VVf27PkIrtN8b3KJwh6EbHxs6u39fG9Lve9nnXWl31dfve7ZvV4ZebCAn9Xu16NprPxqPG7DueiZaU7f11oFMCWpZtvnFC/M2dPy+rw/m1HCPB0Pp7Ci1A9cn0/rvL8aBvKqKetklDSU+Angu6bPLGfRg02UG9mTaBk0UnsgeDQjM02870hX0YqmmXgnx5jo42T3bckXA6MYm+M23GaCG6W39x4jw9N98Qrirpy2/U7+kugmtSMDFJO1GIXcFC1QDfR7Ylmw6Dg+w4SAuhTE6Op+9fCxp23bbGVif2XwSNa6uj2UrTfAVoRHlouDtocMow0ca2TbGv8XcMleZLyjC2a8uVWSg9s+PU0TODJcRpjQAP4FbcBpC40EZjuY4smZghwhaiiR1JDgmdCPIq0x2F94pMd5S9vWOkje9HKX9XyzxSD7G8Y0/qvtuTJj/zpOaXa8KkGXuQiYT5kSwPW9JTZn9xEj7T/UaEzbUdTYtnti1WWVzQXs3ftb5xPHdbqOIStflW5V7Kdz/gz1WVi1p2SfhYYWXzVwq7uyavubLiyClm/e1q+VfVTtY9nbAM7NfjQdJPh/+OaZdtqGNfW65GX2ZrSHy3v1kPGrXk+upoI0uShVzmlPa8oPeM9uEfaB8dnsNe7xRu4OZvUP5iV9FAbGa1HDTJbkYiFHqTeBiNe1Fukrlu0HX05EDLviAzhorItu3UhYjdx7YTmGhXiJ4ntjPVOrHgvigCLQe5SRtKpzqbVtjZ2Bwt315D01+UgdK1Jj2zZ9rTtWKjtCB2dRldrhl26AkbUUurx2U5aJ3VtkTcQcOv0I1tJ+Ax6Z4I5SDcbyRsxk30AEKzRpQdOJhnR4cGlv7e4e+bJFV79H0T4ytCEZCqcdvqxXLtviasDo3vYs6qiCqYXyRiLrPd/Z5LVToqyjy4NMQi2YSmxyhJC0eWjQyB2I0ER/k7BskrfqYdCrVdzV7brhP9hgheru4wboIYsLMjgo1Ds+ytFbR5FA5KotCssLNGG3CbG/HRM/k57qbtVBT6fosP9EERzpTHxzwJr3rm96KATjKN3a70QWOm0C6SaE9Mz1uu4E9b3kqoXvLWXoO1DhcpO1UlIggbtDr6fCnaFq3eGgub+LvteNmnSj7Uc9rD/VZDvGum2lu45cdUL15/09yCWfS4MVy7W4mIDvTkbxUNrWVjuinNYkz0jabdPuCJLAgdBa5E36BbHXkpNvrYOH6M9pvAB2kSxDO+LAnjHJjd7xFpJ17dOFYP2SERlzTJZohRuydUZHQI7P78fU0neuHviTdilJ+10VeJW4Bmfm/5aMStEz3EXO7EfIP+OP5ahDOLW9RZPv2WtAg0DY8F6HI7f2RgEOQ1pofd72NuKRrXie+4PajZtLKNEK8mQ5S2pg1U90td0eY9qxnGUrZuNcStXE0xVidLZZqTWLsno8F9QJTe1DOYAjSISyuAZorJhHNxDfkaxO3gGGDICuJ9rAlIGxR1ZbGGvIm1zXkgW6xpyC+xYz/ax1otGRMSaErSALQ7TcfMYBIFp3JM/IawiQI2FLQnY+7miVyTw3mSZCmYJ2PncJ6R2M0zhcea215atcjOtj+cXy1hP6TYPLAFH9H6N9lP757GCfGr4yd/g4czOJhmKdFXCiiRc4cOKZG4oIASXXNHiVv4qSj0k5y4D/+EE7Pw11JOPIB/wolZ+LspJ2ZhraacmOUkLn5jTsz+vpZy4laC7DWfTeRiXu9P9Jc96eQDLipojekCu4hCtgo6XUkOVbc7RuxzQ0oUvCIHrE3Sh+BOEgDd+tzM79fcRLhdW+vEO7RDBWgiDElBO4NtZcbRBdrWI9uP8efKnQfZfHKH1Rk+Tk3J/J7w4xP8UIDN8KfvO+APtMtFS2TJH7GLnQc7NO1CqdbRjGl3DTK/V2RBaI/4vQAO6pMwjXIwTWi4pflZaZRelRlLf72Hw+0Vunv9rIduW852dm7uR9/Mgoz/knFWkO+/u/r6wZHy7xRUK50/Ez3hT19vHyYEHVDA/1Ks7Zgv/v2jKIWryx80+PVhMXr0EQtowqObb6P6apkWpfPKZbn+a9z4P1lY90SoxywVhHrOCzIeX8XLX7iGfCJfd7V4YNe8UvF9wt0q7+r/xWlkl7PF8BI8Wnze2pZOnkkyeyWcnpf3cVpQNFFUjm1ob4RR43eS9/+NJIpjovT1BHtxmb1q7ucEGWmW0Uvr9bWDwn9TUc+UbHbRQZ3P65Xvl7zmpVUzyv3OnWnWvlfvnXLjlINcM+H8k8P0pxLCyTJevSiq+ywM5Rrqa2iBR8t2D85+f/5sxP8Tg8kdfdO/HdLGeZvvW/Drov31i2rtWJr3L1ATjlLqyfWgmnoyybxXBaiaTwm5vh34q6t8t6DDRIAM+osx8izgnzDqiAT+4X+pulBJVZFCvSTVKMar1f2SDxKto2p0dKeeTUgW/DPhE1x9eqJW5xwFrY7raEDweMd//YmeHH9+X8wgAuo4KqP+ACD8qWoXZ/d3v0ed6asTbbZ+x8iTYraW5zCRNE/QT9zyM/RcuLy8HfNZ0PzKhFagfRfswqcR4tNeF+3MVPJEt1VRzpSCbz9mObORp0nRrF63u7Xrm2a19j/Wlg3YYEX+t2sT8eKD0C6UfY1w2xxi70TuAhPx4uIVrP6H8nxqXtf7nqO5DXHVW3Zuyh/h7KuLC/XsfL9yqHxqewjNfBV/aTEbnpAn/HmCwOcJAp8nCHyeIPAbniCQl+hHN4jTO4oUiO43Kt0vDnPlHdlNKXVJpx88kDLzU+rTz5xd+PMd5VDVwKr2m+s+r04e28NfC1waxXu78VYkYua1nt8v1nHgy8+eX/mkEfi8un8iyb6oDVxy3OYzQZjXkT6aou1pkqpRKqC3twrDFFJbqtA/UZSYSI5XDs3sShJx1sF+qEUzngm2nBZar9d1nWXR8fZk5hFSWa4GD6s0JpOKUFxLgjIHUZvdmcvPxW2OEVM2blMsHhLF/9mIvPmxql/NvMFSXQR3E35yxqOV8+xXpX+JLig3kyVtC3nn/q9wew0mf88H/nhyd/v3jHbuu6QcP13ScUfYawnEfD3/bywdD5owqkbRZlwUXjBeoQlYsZF9QvTuoxrZNN7WJEM6vB2SmbEzvmVCJ4xcZ0rmyyXMANN27JBNE6cGsyyGCYVkvbbFXRsMMhmQNr7mrgqcvo40dzJprAqp8kjQC0xpfnV0pLrb6LgQy+RIO24mqcnTtS0Na4XMNZQdGNKEdZH8uYFZiERF7htEZjCZV2bbudJgpginDsNb4YRAhxMuYzZhORmzGSFBt+Ug0ZDmFyOlP2CTCEnBNC8ytQOTzMWNzQmiZCrLe8ncC9L5wWSJ2rUanhcjiZNM00cy60w255Ba3zXWSFYHnAds7NcUuyHYnKSx1BY6IrDJirR67nyg2RPuhoGk05VcT5Pvp+eTiUdwc2oxm6DoikLmF8FNJ9N5hQRgAZg5Qn6niA3WQHAzBJ6DdH2rxt0XhMWmv8blE440HdtOT3fJ5BWW7HRB6wi4/5KTrDkE3tjZspGmbse0pyLiBF2mJUG/qXHyq824QlJsBzACziLugBJxQu4GyagwBfFZJji7jzy3HY3ZvB4HXTaEzsnUGtwUlcREr6kiUmgdtY3Eg1jb3fQz3BId1ZalErJshN0WgZb8DfPalCURtRUnFROeaV4KJ+46lY2Eb6DxM+DIcQBL/g1cK6awfKZ7drdYHVnuApOe0/5HlkCCrCVoHT1FdgrBPNDPqgJaUWW5iFgTj+hEDzS2H7ermH8zKdHwtRa6joQCOIgYHxbNG9cswS4B4bgr7hYSsUsjFlzgR7hjWI7HIuZ+WjqXG1gBEpKlm8GqrLhrioRX5HICM9wWSJ5v0pg1JEajQ4rG3VfYVVFByYoJ/rCdClxCSBhX4dAC/gVoN6wQjXRU+g5lQOCdWHDy8A3NxQc84fpAwrCRdNRZtxwkrde4jNPWlBhlCzatgfHEDrxa1EYHpQnRzERhGCT4jgALglMg14u14d01GUZOL5CyCeUsJJeqLBvQ0YU7icCthETt9B2dRATDuyM7zDhXIeYG+Mjx0Imo8ig7RzHMNzY/n2lSziXuMFzlMwhXDvGM1WQ8gk8FemwhuZllRY/pjmQXkqJNXu8klWf4fUeXz62x68aOfQkTdkD2iLYZd+ySa6MzniyTgisLa8SYuuQPoj/Ii5Bl1JrpBmtiekJxAM3dAW2w7JB0ZfngW8KllGPoCtN2Al4n0TtkbcI7Taa/NruAxFpINxzkBPMZ5JiU33ChoYsSzxmO0Ai8JGkZrjXAScixGkrEcLJkiRVoU0j+ikhup/hHcr4hn429wJfOWbgHGdZ4/lQHzQiUoqFEDa4vi+Ut8xSutS03ZlxW2fWnC7lGyIeYy9PihK/TPQz7AOYT9yQOeT1TplHQCsl/yCXgbwNXLO2D+E6l9UNGm5w0b7m6hNGU1090RPJUmLLLEcl7whHBG25TdhIzngmXjC+8d/n5K5oT7Ws+XLaqgBuP6IX3VKwRsAfPkJyRnYtuxjbvXT7KgVCmpiedkTaJ7ItRMibmAs8nOcZrWm/3Pkl7JpzV9o4W1u0a874KmdbGPudch3D30b7F3ZFaLKMqGrvtQGtddO3hPYVw4AcMc9lliumNZAzwE9u0hzAfhT5goGDvEth3U/k2ob2R5+wabXYrgo/AF+6GZAh9Z3DpHBduWLMwhZ2kEZIP/LlJ86rh2SSnII+5g5PKtEXjQq4THFR2mTpM9zS/DsEvQIlVxOV23ZQH3EjKDp95FW5dYXHQwSDcY17orrVhPHfBC52NHD8AvGnvgyxwGV5tx1U5SBEKphMUiUBHESRP5W+w92PeXJ7I85Bud/A39rUpaArFMYqkh0AWTUiZBPrRUjnKrtmYgyBw2xv8XBRNccHSlGU9r5U/d6CLKCi+4DWGVyu4tEnm6wyzuC5xj8IauK5JJ0GpJd87UbgwRHZZm+L52EtV+Zl0CP5cw16oYs9F2SIX4YBneI2VWIYdmigdVVGsIj+jaIdkF+QX9FXMA8UugBvtfRKXch22LD5R0YlMwoqfqUjZ5Sb01ITMBA8bgvmypkm6qqgkl8DDRhv7DfZ4BHWgw4Uud7dqW0J2XEORTYS9lUMyKKKRHUp5vhiXwy4oNsLeJPluUtGZD8Hr2LO7ks6ZnqOE5pmuIKNJV2A4I0TDNKQzbVq+7JoVM18ZNu1XCA+x3h37EqdxhYNgjB+8J7xkO0y3Jnc4i9AHspd0V+M9KpZ48RVJNwHDnGwENd3zUv2JdWGsAfYA70tTpjGbeIG7slrcOYz03ankaw6tuIrEhQvcGEnXPC5ptPsKyyOe1wSw9TS5P1UMF3qywzrshkuRudMey55EprOcT57H61DkHokyXJL3/OyeKfdEV+6xkKVS5qgJ7Sroosad4KAfQ16iGMniUBvjHDTDwUb5XUR0JvcQgpWN8BhwKeldT/YeCWtL4oBoAKE3ohOFuxmSvMW6pH4GHX0i94VEx9AgB+VaYM+4UreQOoJB+tmK4ch4aib8XYsz95iko0q949v9irun7QceZbErcwo0E5akZA9yuSBLumAteyIyBKXGzhoeQySWlJhChymRJT3vlgytWix72qVSq4cgJSxBTWqCTaZmO8ZnaJq8a+DYjZVItQWm2CDhDkWWtjEVoBAZWPURTEKZsG2zJO7IgnGS8KzpWoztdHff2BLDXHYmOdtdpVo++k8mkjXiHZC5Ud7HZaHxNGbpjt8xhdRiOX/sJtNEAnXkTj6Ru4XU1JWEkyGZAt695ZwrMrjFJWhfpe1Of71hiEvVzs6VC0Mxyrp5cWGULvLHxWrnZ1rZRENt5bxkGopeEN8ovuXVm93lz1J8T/ezeeB9Vi701/U+/7jztxBaH9/3WzjtfNesN0fyx0NJ6UOhJO+NP8gsVf4PmfeDyR3Renw7+hhO96O5ps+6y9/Fi62mvQnTcxILuokaeXlbeoXTagsRXXRG8gcLHlsLf3r7cCoW3yw4W9JPC8W+AksWphDnaxdymPr5UqFnckAPmuEW9cLVtV1G5GEzuDfLdnxBS9yPFoj5zHb8zHb8zHb8zHZ8Ubbj0Q3iI2Y7Fopt/RTl44OK7c/4+Wf8/DN+/hk//4yff8bPP+Pnn/Hzz/j5Z/z8M37+GT//jJ9/xs8/4+cfMn7+Ok4T9VeXiObPwOqOF2lbq+rgIV/99xvViFarNbNedDrmq9eI7tXYvDqxpEUqRQV8T0ZI3qwT2imFKjJ29EMBthfB6sVHbKjl/aCTphXA9LwgZpoeafn64NQKwPlcY9nqYjlfLK3L36qXLJyqw8ES5+HWK7j4t1zG39bl71y4tpUyeVo+zj+nM37Rmdl5En2zsL7+zCbwk2W56kFiVEl77gzUl5blHu/8eUziZFNygjDq3/S0oXPzZ+dyVGm2o06tqPPnUTZ/87NN1X0Bd146O9fLJbVUKitmSTG1/QGfOOn0B04kPQadgyM2OdHnj6RnovJ/lWoln/Hz60I12/5RG/Sny/SP4s+j+Ww2Uq6+31romcZmj8EvhHEaN3OfFMgnu9LNm5GtuRu331m3nNEU7kVSrhWYxV4DroXpxp3bMxsKep9M9m1nMoQ8uBMddydrc08KsetaV0XYR6S92mRHFnRM2T7/+mjHp69pl7ZGee7d2bORlfaDK+pAJkj5tmlcel5Vmi+n9W6znV7kwak5qXz/+mTntq+NpK/CdNdhLen2NRFz1xRhDead4mpwv15PYJ65DtzLatjuu5E9FxvXuR63G27a8Q/mMxkRadc/wSb5tscdDBzu58iwg0tNB55/DnbNfLc3jQwZYiHMg2P7uW5vFdnjpAhysR8TLMJnIJd2IeMeRRJi7UZtje4bblchoxqBlespAlpu2FNEVR2TiaW5MUFRa6p0H1Zzl3mtZa81mt00dUY2lxw8c3zDZcdxx2hZ6XtlKarrTYu7tfSWcOy1u2t2KrTYUdTD9+uWzBGJ2UEeiyUb2dl3vqeyhMOmBSeKBsdYDddhtuFvmGz0qo85aBk2l+l7+lsei0ziluXDBNR275XkHgQae2zotxsYlwNRm36mQ1yTZujOb6ZeirFsj7OI1mYBFpiHS7CAY4QozUrfk+d0OYij2hPAwV3C4biDExxI0slqN9YbdiyEzXgEE3tO302M3XzhGMYz+zRXfqY9thle7jJ938HNjRDQOnkt7LDj7jYq0QscfUZLugXSMdnR0gqDjctBDQ48bNix0d1+lg4WTQaUCe/pu8QHO1gqMTtRInznKpn39B44F5dJNsyj7MHojVvoljPZ3sMmu+2M4cjVZEYFO/fMDtNjT5H0iPcEzuw0DrAurV0twAMHNGVPLBo303uKZEV8NfH611PJeTl+QFcc0LmezAMOvphgCMfacg9/7GyXjuIMDOMkOMUOazg6+bOG+VQMgkv6nqx9jcD3CsHVDniMeIJ7tKUw7q735nQ6/hFY86MWO+Sna+IHBLmQBaQ57Gzk7xRkL0mHGq9nzcEYxn+PeZGDLQguO50tnzPvw81zg2BPRyHYQA4o8t7tvHFN3gsY4pXiBn3h8Kz+Gt2PCJ6QPewiwnWVYIXPKuZqd7M0gh5910gAUDp0HwLCtiP0Hf8jgFohnKw30iG4j3eZacU4v8vI0xmCzh7xhz1RVCEDfybcY7SjR7QDzVynpnuONxd9O2w7lckhzdAalxxwT7J7GH6hjUQA1U4yj1guhnA6cpAqEnPAjIMlmx2ud8Gv0QR8cz3mQBheGdhx4MG52lub67i623eVdDfxiB74lfSQdJ2O7tJO5DU6K7vRgXaiuAggO+OQ1xrfjN24qdsN0lSsqcaazydNPUNTaSe6l2qA062WlXUSQwIGNHNkYwKyAeksWI3QJTQQAqzp9k6yyl3D8dUbphLasQBBhERx726X2Qh5TWEsOcEmwdKYx4MjdW+naW4YE/F1H05bOx5f8n18rbe9D9JWOCTZI2NvV0q6J75Ur7vPUm7irP1y+X34ZXY31IzAb5TvSWuLpeY3VWSC0pR4rZJ+RjKAQhozB7kEXNEI/lk1Dtq1HaFwAN7h/odIvDBkwIj12h0fhb5p08zp2kFPwt4OZ0WzIqrtJeE2gfSK9PNGdjrtyJSTmNPDEELQMQvZIVWGoDiU6iAdCJ0+0Q35RbNCT7JD6GVoi3Yv1mI6pNXAmd5TmUP4Xe4yrLnE7MjX7Sk43IeGk75ndiPewZR2tjfdhHS8+dWMpEs6n4Z978/tpde9DGFVwZpBH25XR5dA8O9UhzUDLZqf6EyPjcar25e6xBsah6u/QWIEpG0hfMNh5EhMMhKVQ4JXSw56IN0hRroE9+JbyhSINEVhvQb/oI8f4WinSXB6QCA1Vfo+c51fNyHjUcFvsRMkUmOJnV9qAYExTHZ6Ti+xLsfQEjj4Eu/dv2HNCpKM02R6ala6CU4xq5AWxSks6XgkcS8Fh42tHrSTiL43uKvsFPCocRfYrbZB+NvC7WYPfzn7b496YtYju2sEcJUh632wXmsqQwzBYgQod3rRug0I0745QgdKq2IW3oe9jJPwatoQ/W/RnzRmqYREgM3OtmC7IbZZx0KXRsh+BLe3OpTcZ0KvDy6zHU+OEXGgL6PvYlwZTO3IJEKZoLiT9Xvr3NO1Diy9bTiJuc7XvbuvAafiWgWRiVdvyPGzhUqvE+cyD0o9irp5FB33XXqFIEbxEfonZAu/dwFMqSiF+n3PA92eZZ5rZidb4Vyiam+18Kfw4TfvvtNKF8ljf9MY4fbYuV9wjuxxyjw9PIAi7Mw/49RjZbXzs1cotS6uQC0ILHda8Pki8PPhwlE0t7+t5PPvHItCyGPbcuKlUv0FlJeWMao4VdUoKxfmRdkoaWm/s1TM62cX2W5oWo4u6Y7SuaYbeqlMH0pGQVTLvDjTtPJ5WaVHlEpq+RUotrjs74QGEB+1fuSz7O+z7O+z7O+z7O/ny/5evgfopTP9IiPkL0p57eONUtoKkxXMnNT+UOd/HIV6Ntug8MaLE7MNUvXr7bMNNKQXbP+dX+wnH2hnZVNV9NKFZurnJf3g7OTXSz4oPJXhoiD5IE2b+mD6Z3E61PsocWpp3wxVC8zQt0o3KsSbqv7HWPgY9f5iDlYV9czQL0gNPz8/N8yLH+2UXDbPVF01jfI5dgwarpjk3qlRcl4yPJ9yWfmr+8PZlu9yWlGSVwVx8tfj8Pbh7nZ1u/yfaA1QUy2zdl5kbz0h607m1xdrNamrrkBEvpH7sthYLUoa/k2M1c/DDj4PO/g87ODzsIPPww4+Dzv4POzg87CDz8MOPg87+Dzs4POwg8/DDj4PO/g87OD3Oezg5Z7m8plSzjQLOM+fgfwuzQLo48Niscq6FB8G92OxGN3ijv8P</diagram></mxfile>

================================================
FILE: docs/architecture/.$contoso-traders-enhancements.drawio.dtmp
================================================
<mxfile host="Electron" modified="2023-03-19T09:38:10.917Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.8.16 Chrome/106.0.5249.199 Electron/21.4.0 Safari/537.36" etag="jRXm6rDv2sSY2WuEMFRG" version="20.8.16" type="device"><diagram id="cPhL8d0vtsXr2UynWkRd" name="Page-1">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
Download .txt
gitextract_0zyx3uia/

├── .azurepipelines/
│   ├── aks-cost-optimization.yml
│   └── contoso-traders-cloud-testing.yml
├── .devcontainer/
│   └── devcontainer.json
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── pull_request_template.md
│   └── workflows/
│       ├── aks-cost-optimization.yml
│       ├── codeql.yml
│       └── contoso-traders-cloud-testing.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── SUPPORT.md
├── demo-scripts/
│   ├── azure-chaos-studio/
│   │   └── walkthrough.md
│   ├── azure-load-testing/
│   │   ├── aks-cost-optimization.md
│   │   ├── private-endpoints.md
│   │   └── walkthrough.md
│   ├── dev-workflow/
│   │   └── walkthrough.md
│   └── testing-with-playwright/
│       └── walkthrough.md
├── docs/
│   ├── architecture/
│   │   ├── .$contoso-traders-enhancements.drawio.bkp
│   │   ├── .$contoso-traders-enhancements.drawio.dtmp
│   │   └── contoso-traders-enhancements.drawio
│   ├── deployment-instructions-azure-pipelines.md
│   ├── deployment-instructions.md
│   └── running-locally.md
├── iac/
│   ├── createPrivateDnsZone.bicep
│   ├── createResourceGroup.bicep
│   ├── createResources.bicep
│   ├── createResources.parameters.json
│   ├── dashboard.json
│   ├── rsa
│   ├── rsa.pub
│   └── scripts/
│       └── enable-static-website.ps1
├── loadtests/
│   ├── contoso-traders-carts-internal.yaml
│   ├── contoso-traders-carts.jmx
│   ├── contoso-traders-carts.yaml
│   ├── contoso-traders-products.jmx
│   └── contoso-traders-products.yaml
└── src/
    ├── .dockerignore
    ├── ContosoTraders.Api.Carts/
    │   ├── ContosoTraders.Api.Carts.csproj
    │   ├── Controllers/
    │   │   ├── ProfilesController.cs
    │   │   └── ShoppingCartController.cs
    │   ├── Dockerfile
    │   ├── Program.cs
    │   ├── Properties/
    │   │   ├── ServiceDependencies/
    │   │   │   └── tailwind-traders-cart - Zip Deploy/
    │   │   │       └── profile.arm.json
    │   │   └── launchSettings.json
    │   └── Usings.cs
    ├── ContosoTraders.Api.Core/
    │   ├── Constants/
    │   │   ├── AuthConstants.cs
    │   │   ├── CosmosConstants.cs
    │   │   ├── KeyVaultConstants.cs
    │   │   └── RequestHeaderConstants.cs
    │   ├── ContosoTraders.Api.Core.csproj
    │   ├── Controllers/
    │   │   └── ContosoTradersControllerBase.cs
    │   ├── DependencyInjection.cs
    │   ├── Exceptions/
    │   │   ├── CartNotFoundException.cs
    │   │   ├── ContosoTradersBaseException.cs
    │   │   ├── MatchingProductsNotFoundException.cs
    │   │   ├── ProductNotFoundException.cs
    │   │   ├── ProfileNotFoundException.cs
    │   │   └── StockNotFoundException.cs
    │   ├── Models/
    │   │   ├── AutoMapperProfile.cs
    │   │   ├── Implementations/
    │   │   │   ├── Dao/
    │   │   │   │   ├── Brand.cs
    │   │   │   │   ├── CartDao.cs
    │   │   │   │   ├── Feature.cs
    │   │   │   │   ├── Product.cs
    │   │   │   │   ├── Profile.cs
    │   │   │   │   ├── StockDao.cs
    │   │   │   │   ├── Tag.cs
    │   │   │   │   └── Type.cs
    │   │   │   └── Dto/
    │   │   │       ├── AccessToken.cs
    │   │   │       ├── CartDto.cs
    │   │   │       ├── ProductDto.cs
    │   │   │       ├── StockDto.cs
    │   │   │       └── TokenRequest.cs
    │   │   └── Interfaces/
    │   │       └── ICosmosDao.cs
    │   ├── Repositories/
    │   │   ├── Implementations/
    │   │   │   ├── CartRepository.cs
    │   │   │   ├── CosmosGenericRepositoryBase.cs
    │   │   │   └── StockRepository.cs
    │   │   ├── Interfaces/
    │   │   │   ├── ICartRepository.cs
    │   │   │   ├── ICosmosGenericRepository.cs
    │   │   │   └── IStockRepository.cs
    │   │   ├── ProductsDbContext.cs
    │   │   └── ProfilesDbContext.cs
    │   ├── Requests/
    │   │   ├── Definitions/
    │   │   │   ├── AddItemToCartRequest.cs
    │   │   │   ├── DecrementStockCountRequest.cs
    │   │   │   ├── GetCartRequest.cs
    │   │   │   ├── GetPopularProductsRequest.cs
    │   │   │   ├── GetProductRequest.cs
    │   │   │   ├── GetProductsRequest.cs
    │   │   │   ├── GetProfileRequest.cs
    │   │   │   ├── GetProfilesRequest.cs
    │   │   │   ├── GetStockRequest.cs
    │   │   │   ├── LoadTestRequest.cs
    │   │   │   ├── PostImageRequest.cs
    │   │   │   ├── RemoveItemFromCartRequest.cs
    │   │   │   ├── SearchTextRequest.cs
    │   │   │   └── UpdateCartItemQuantityRequest.cs
    │   │   ├── Handlers/
    │   │   │   ├── AddItemToCartRequestHandler.cs
    │   │   │   ├── DecrementStockCountRequestHandler.cs
    │   │   │   ├── GetCartRequestHandler.cs
    │   │   │   ├── GetPopularProductsRequestHandler.cs
    │   │   │   ├── GetProductRequestHandler.cs
    │   │   │   ├── GetProductsRequestHandler.cs
    │   │   │   ├── GetProfileRequestHandler.cs
    │   │   │   ├── GetProfilesRequestHandler.cs
    │   │   │   ├── GetStockRequestHandler.cs
    │   │   │   ├── LoadTestRequestHandler.cs
    │   │   │   ├── PostImageRequestHandler.cs
    │   │   │   ├── RemoveItemFromCartRequestHandler.cs
    │   │   │   ├── SearchTextRequestHandler.cs
    │   │   │   └── UpdateCartItemQuantityRequestHandler.cs
    │   │   └── Validators/
    │   │       ├── AddItemToCartRequestValidator.cs
    │   │       ├── DecrementStockCountRequestValidator.cs
    │   │       ├── GetCartRequestValidator.cs
    │   │       ├── GetPopularProductsRequestValidator.cs
    │   │       ├── GetProductRequestValidator.cs
    │   │       ├── GetProductsRequestValidator.cs
    │   │       ├── GetProfileRequestValidator.cs
    │   │       ├── GetProfilesRequestValidator.cs
    │   │       ├── GetStockRequestValidator.cs
    │   │       ├── PostImageRequestValidator.cs
    │   │       ├── RemoveItemFromCartRequestValidator.cs
    │   │       ├── SearchTextRequestValidator.cs
    │   │       └── UpdateCartItemQuantityRequestValidator.cs
    │   ├── Services/
    │   │   ├── ContosoTradersServiceBase.cs
    │   │   ├── Implementations/
    │   │   │   ├── CartService.cs
    │   │   │   ├── ImageAnalysisService.cs
    │   │   │   ├── ImageSearchService.cs
    │   │   │   ├── ProductService.cs
    │   │   │   ├── ProfileService.cs
    │   │   │   └── StockService.cs
    │   │   └── Interfaces/
    │   │       ├── ICartService.cs
    │   │       ├── IImageAnalysisService.cs
    │   │       ├── IImageSearchService.cs
    │   │       ├── IProductService.cs
    │   │       ├── IProfileService.cs
    │   │       └── IStockService.cs
    │   └── Usings.cs
    ├── ContosoTraders.Api.Products/
    │   ├── ContosoTraders.Api.Products.csproj
    │   ├── Controllers/
    │   │   ├── LoginController.cs
    │   │   ├── ProductsController.cs
    │   │   ├── ProfilesController.cs
    │   │   └── StocksController.cs
    │   ├── Dockerfile
    │   ├── Manifests/
    │   │   ├── Certificate.yaml
    │   │   ├── ClusterIssuer.yaml
    │   │   ├── ClusterRole.yaml
    │   │   ├── Deployment.yaml
    │   │   ├── Ingress.yaml
    │   │   ├── NamespaceCertManager.yaml
    │   │   ├── NamespaceChaosTesting.yaml
    │   │   └── Service.yaml
    │   ├── Migration/
    │   │   └── productsdb.sql
    │   ├── Program.cs
    │   ├── Properties/
    │   │   ├── ServiceDependencies/
    │   │   │   ├── tailwind-traders-product - Web Deploy/
    │   │   │   │   └── profile.arm.json
    │   │   │   └── tailwind-traders-product - Web Deploy1/
    │   │   │       └── profile.arm.json
    │   │   └── launchSettings.json
    │   └── Usings.cs
    ├── ContosoTraders.Api.Profiles/
    │   ├── .gitignore
    │   ├── ContosoTraders.Api.Profiles.csproj
    │   ├── Controllers/
    │   │   └── ProfilesController.cs
    │   ├── Properties/
    │   │   ├── serviceDependencies.json
    │   │   └── serviceDependencies.local.json
    │   ├── Usings.cs
    │   └── host.json
    ├── ContosoTraders.Ui.Website/
    │   ├── .gitignore
    │   ├── README.md
    │   ├── package.json
    │   ├── playwright.config.ts
    │   ├── public/
    │   │   ├── browserconfig.xml
    │   │   ├── index.html
    │   │   ├── manifest.json
    │   │   └── site.webmanifest
    │   ├── src/
    │   │   ├── App.css
    │   │   ├── App.js
    │   │   ├── App.test.js
    │   │   ├── actions/
    │   │   │   └── actions.js
    │   │   ├── components/
    │   │   │   ├── Input/
    │   │   │   │   └── checkbox.js
    │   │   │   ├── accordion/
    │   │   │   │   ├── accordion.js
    │   │   │   │   ├── accordion.scss
    │   │   │   │   └── sidebarAccordion.js
    │   │   │   ├── breadcrumb/
    │   │   │   │   ├── breadcrumb.js
    │   │   │   │   └── breadcrumb.scss
    │   │   │   ├── corousel/
    │   │   │   │   ├── corousel.js
    │   │   │   │   └── corousel.scss
    │   │   │   ├── dropdowns/
    │   │   │   │   ├── categories.js
    │   │   │   │   └── categories.scss
    │   │   │   ├── footer/
    │   │   │   │   ├── footer.js
    │   │   │   │   └── footer.scss
    │   │   │   ├── header/
    │   │   │   │   ├── appbar.js
    │   │   │   │   ├── header.js
    │   │   │   │   ├── header.scss
    │   │   │   │   └── headerMessage.js
    │   │   │   ├── imageSlider/
    │   │   │   │   ├── imageSlider.js
    │   │   │   │   └── imageSlider.scss
    │   │   │   ├── loadingSpinner/
    │   │   │   │   ├── loadingSpinner.js
    │   │   │   │   └── loadingSpinner.scss
    │   │   │   ├── minimalSelect/
    │   │   │   │   ├── minimalSelect.js
    │   │   │   │   ├── minimalSelect.scss
    │   │   │   │   └── minimalSelect.styles.js
    │   │   │   ├── productCard/
    │   │   │   │   ├── product.js
    │   │   │   │   └── product.scss
    │   │   │   ├── quantityCounter/
    │   │   │   │   ├── productCounter.js
    │   │   │   │   └── productCounter.scss
    │   │   │   ├── shared/
    │   │   │   │   └── index.js
    │   │   │   ├── slider/
    │   │   │   │   ├── slider.js
    │   │   │   │   └── slider.scss
    │   │   │   └── uploadFile/
    │   │   │       ├── uploadFile.js
    │   │   │       └── uploadFile.scss
    │   │   ├── helpers/
    │   │   │   ├── errorsHandler.js
    │   │   │   ├── localStorage.js
    │   │   │   ├── refreshJWTHelper.js
    │   │   │   ├── toast.js
    │   │   │   └── tokensHelper.js
    │   │   ├── index.css
    │   │   ├── index.js
    │   │   ├── main.scss
    │   │   ├── pages/
    │   │   │   ├── arrivals/
    │   │   │   │   ├── arrivals.js
    │   │   │   │   └── arrivals.scss
    │   │   │   ├── cart/
    │   │   │   │   ├── cart.js
    │   │   │   │   └── cart.scss
    │   │   │   ├── detail/
    │   │   │   │   ├── detail.scss
    │   │   │   │   ├── detailContainer.js
    │   │   │   │   └── productDetails.js
    │   │   │   ├── error/
    │   │   │   │   ├── errorPage.js
    │   │   │   │   └── errorPage.scss
    │   │   │   ├── home/
    │   │   │   │   ├── home.js
    │   │   │   │   ├── home.scss
    │   │   │   │   ├── homeContainer.js
    │   │   │   │   └── sections/
    │   │   │   │       ├── banner.js
    │   │   │   │       ├── finalSection.js
    │   │   │   │       ├── gridSection.js
    │   │   │   │       └── hero.js
    │   │   │   ├── index.js
    │   │   │   ├── legals/
    │   │   │   │   ├── aboutUs.js
    │   │   │   │   ├── legals.scss
    │   │   │   │   ├── refundPolicy.js
    │   │   │   │   └── termsOfService.js
    │   │   │   ├── list/
    │   │   │   │   ├── list.js
    │   │   │   │   ├── list.scss
    │   │   │   │   ├── listContainer.js
    │   │   │   │   └── sections/
    │   │   │   │       ├── banner/
    │   │   │   │       │   └── offerBanner.js
    │   │   │   │       ├── index.js
    │   │   │   │       ├── listAside/
    │   │   │   │       │   └── listAside.js
    │   │   │   │       └── listGrid/
    │   │   │   │           └── listGrid.js
    │   │   │   ├── profile/
    │   │   │   │   ├── myAddressBook.js
    │   │   │   │   ├── myOrders.js
    │   │   │   │   ├── myWishlist.js
    │   │   │   │   ├── personalInformation.js
    │   │   │   │   ├── profile.scss
    │   │   │   │   └── profileForm.js
    │   │   │   └── suggestedProductsList/
    │   │   │       ├── suggestedProductsList.js
    │   │   │       └── suggestedproductslist.scss
    │   │   ├── reducers/
    │   │   │   ├── login.reducer.js
    │   │   │   └── reducers.js
    │   │   ├── reportWebVitals.js
    │   │   ├── services/
    │   │   │   ├── authB2CService.js
    │   │   │   ├── cartService.js
    │   │   │   ├── configService.js
    │   │   │   ├── index.js
    │   │   │   ├── productsService.js
    │   │   │   ├── telemetryClient.js
    │   │   │   └── userService.js
    │   │   ├── setupTests.js
    │   │   ├── store.js
    │   │   ├── styles/
    │   │   │   ├── abstracts/
    │   │   │   │   ├── _variables.scss
    │   │   │   │   └── mixins/
    │   │   │   │       ├── _ellipsis.scss
    │   │   │   │       ├── _font-placeholders.scss
    │   │   │   │       ├── _font-scale.scss
    │   │   │   │       ├── _fonts.scss
    │   │   │   │       └── _loader.scss
    │   │   │   ├── base/
    │   │   │   │   ├── _base.scss
    │   │   │   │   ├── _typography.scss
    │   │   │   │   └── _utilities.scss
    │   │   │   └── vendor/
    │   │   │       └── _normalize.scss
    │   │   └── types/
    │   │       └── types.js
    │   ├── tests/
    │   │   ├── account.ts
    │   │   ├── api/
    │   │   │   ├── cart.spec.ts
    │   │   │   ├── data.spec.ts
    │   │   │   └── products.spec.ts
    │   │   ├── auth.setup.ts
    │   │   ├── cart.spec.ts
    │   │   ├── darkmode.spec.ts
    │   │   ├── discounts.spec.ts
    │   │   ├── fileupload.spec.ts
    │   │   ├── map.spec.ts
    │   │   ├── mocks.spec.ts
    │   │   ├── pages.spec.ts
    │   │   └── test-data.csv
    │   └── tsconfig.json
    ├── ContosoTraders.sln
    └── ContosoTraders.sln.DotSettings
Download .txt
SYMBOL INDEX (343 symbols across 133 files)

FILE: src/ContosoTraders.Api.Carts/Controllers/ProfilesController.cs
  class ProfilesController (line 3) | [Route("v1/[controller]")]
    method ProfilesController (line 6) | public ProfilesController(IMediator mediator) : base(mediator)
    method GetProfiles (line 10) | [HttpGet]
    method GetProfile (line 19) | [HttpGet("me")]

FILE: src/ContosoTraders.Api.Carts/Controllers/ShoppingCartController.cs
  class ShoppingCartController (line 3) | [Route("v1/[controller]")]
    method ShoppingCartController (line 6) | public ShoppingCartController(IMediator mediator) : base(mediator)
    method GetCart (line 13) | [HttpGet]
    method AddItemToCart (line 25) | [HttpPost]
    method UpdateCartItemQuantity (line 37) | [HttpPut("product")]
    method RemoveItemFromCart (line 49) | [HttpDelete("product")]
    method LoadTest (line 63) | [HttpGet("loadtest")]

FILE: src/ContosoTraders.Api.Core/Constants/AuthConstants.cs
  class AuthConstants (line 3) | public class AuthConstants

FILE: src/ContosoTraders.Api.Core/Constants/CosmosConstants.cs
  class CosmosConstants (line 3) | public class CosmosConstants

FILE: src/ContosoTraders.Api.Core/Constants/KeyVaultConstants.cs
  class KeyVaultConstants (line 3) | internal class KeyVaultConstants

FILE: src/ContosoTraders.Api.Core/Constants/RequestHeaderConstants.cs
  class RequestHeaderConstants (line 3) | public class RequestHeaderConstants

FILE: src/ContosoTraders.Api.Core/Controllers/ContosoTradersControllerBase.cs
  class ContosoTradersControllerBase (line 3) | [ApiController]
    method ContosoTradersControllerBase (line 8) | protected ContosoTradersControllerBase(IMediator mediator)
    method ProcessHttpRequestAsync (line 13) | protected async Task<IActionResult> ProcessHttpRequestAsync(IRequest<I...

FILE: src/ContosoTraders.Api.Core/DependencyInjection.cs
  class DependencyInjection (line 15) | public class DependencyInjection : FunctionsStartup
    method ConfigureServices (line 19) | public static void ConfigureServices(HostBuilderContext context, IServ...
    method Configure (line 28) | public override void Configure(IFunctionsHostBuilder builder)
    method ConfigureApp (line 37) | public static void ConfigureApp()
    method ConfigureServicesInternal (line 69) | private static void ConfigureServicesInternal(IServiceCollection servi...
    method ConfigureAspNetCoreServices (line 107) | private static void ConfigureAspNetCoreServices(IServiceCollection ser...
    method ConfigureAspNetCoreMiddleware (line 129) | private static void ConfigureAspNetCoreMiddleware(WebApplication app)

FILE: src/ContosoTraders.Api.Core/Exceptions/CartNotFoundException.cs
  class CartNotFoundException (line 3) | public class CartNotFoundException : ContosoTradersBaseException
    method CartNotFoundException (line 5) | public CartNotFoundException(string email)
    method ToActionResult (line 10) | public override IActionResult ToActionResult()

FILE: src/ContosoTraders.Api.Core/Exceptions/ContosoTradersBaseException.cs
  class ContosoTradersBaseException (line 3) | public abstract class ContosoTradersBaseException : Exception
    method ContosoTradersBaseException (line 5) | protected ContosoTradersBaseException(string message) : base(message)
    method ToActionResult (line 9) | public abstract IActionResult ToActionResult();

FILE: src/ContosoTraders.Api.Core/Exceptions/MatchingProductsNotFoundException.cs
  class MatchingProductsNotFoundException (line 3) | public class MatchingProductsNotFoundException : ContosoTradersBaseExcep...
    method MatchingProductsNotFoundException (line 5) | public MatchingProductsNotFoundException(string tags)
    method ToActionResult (line 10) | public override IActionResult ToActionResult()

FILE: src/ContosoTraders.Api.Core/Exceptions/ProductNotFoundException.cs
  class ProductNotFoundException (line 3) | public class ProductNotFoundException : ContosoTradersBaseException
    method ProductNotFoundException (line 5) | public ProductNotFoundException(int productId)
    method ToActionResult (line 10) | public override IActionResult ToActionResult()

FILE: src/ContosoTraders.Api.Core/Exceptions/ProfileNotFoundException.cs
  class ProfileNotFoundException (line 3) | public class ProfileNotFoundException : ContosoTradersBaseException
    method ProfileNotFoundException (line 5) | public ProfileNotFoundException(string email)
    method ToActionResult (line 10) | public override IActionResult ToActionResult()

FILE: src/ContosoTraders.Api.Core/Exceptions/StockNotFoundException.cs
  class StockNotFoundException (line 3) | public class StockNotFoundException : ContosoTradersBaseException
    method StockNotFoundException (line 5) | public StockNotFoundException(int productId)
    method ToActionResult (line 10) | public override IActionResult ToActionResult()

FILE: src/ContosoTraders.Api.Core/Models/AutoMapperProfile.cs
  class AutoMapperProfile (line 6) | public class AutoMapperProfile : Profile
    method AutoMapperProfile (line 8) | public AutoMapperProfile()

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dao/Brand.cs
  class Brand (line 3) | public class Brand

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dao/CartDao.cs
  class CartDao (line 3) | public class CartDao : ICosmosDao<string>

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dao/Feature.cs
  class Feature (line 3) | public class Feature

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dao/Product.cs
  class Product (line 3) | public class Product

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dao/Profile.cs
  class Profile (line 3) | public class Profile

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dao/StockDao.cs
  class StockDao (line 3) | public class StockDao : ICosmosDao<string>

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dao/Tag.cs
  class Tag (line 3) | public class Tag

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dao/Type.cs
  class Type (line 3) | public class Type

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dto/AccessToken.cs
  class AccessToken (line 5) | public class AccessToken

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dto/CartDto.cs
  class CartDto (line 3) | public class CartDto

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dto/ProductDto.cs
  class ProductDto (line 5) | public class ProductDto

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dto/StockDto.cs
  class StockDto (line 3) | public class StockDto

FILE: src/ContosoTraders.Api.Core/Models/Implementations/Dto/TokenRequest.cs
  class TokenRequest (line 5) | public class TokenRequest

FILE: src/ContosoTraders.Api.Core/Models/Interfaces/ICosmosDao.cs
  type ICosmosDao (line 3) | public interface ICosmosDao<T>

FILE: src/ContosoTraders.Api.Core/Repositories/Implementations/CartRepository.cs
  class CartRepository (line 5) | public class CartRepository : CosmosGenericRepositoryBase<CartDao>, ICar...
    method CartRepository (line 7) | public CartRepository(IEnumerable<Database> cosmosDatabases)

FILE: src/ContosoTraders.Api.Core/Repositories/Implementations/CosmosGenericRepositoryBase.cs
  class CosmosGenericRepositoryBase (line 6) | public abstract class CosmosGenericRepositoryBase<TEntity> : ICosmosGene...
    method CosmosGenericRepositoryBase (line 12) | protected CosmosGenericRepositoryBase(Database cosmosDatabase, string ...
    method QueryAsync (line 18) | public async Task<IEnumerable<TEntity>> QueryAsync(string querySpec, C...
    method ListAsync (line 25) | public async Task<IEnumerable<TEntity>> ListAsync(string filterClause ...
    method GetAsync (line 36) | public async Task<TEntity> GetAsync(string partitionKey, string id, Ca...
    method AddAsync (line 54) | public async Task AddAsync(string partitionKey, TEntity entity, Cancel...
    method UpsertAsync (line 63) | public async Task UpsertAsync(string partitionKey, TEntity entity, Can...
    method DeleteAsync (line 72) | public async Task DeleteAsync(string partitionKey, string id, Cancella...
    method ExecuteQueryAsync (line 81) | private async Task<IEnumerable<TEntity>> ExecuteQueryAsync(string quer...

FILE: src/ContosoTraders.Api.Core/Repositories/Implementations/StockRepository.cs
  class StockRepository (line 5) | public class StockRepository : CosmosGenericRepositoryBase<StockDao>, IS...
    method StockRepository (line 7) | public StockRepository(IEnumerable<Database> cosmosDatabases)

FILE: src/ContosoTraders.Api.Core/Repositories/Interfaces/ICartRepository.cs
  type ICartRepository (line 3) | public interface ICartRepository : ICosmosGenericRepository<CartDao>

FILE: src/ContosoTraders.Api.Core/Repositories/Interfaces/ICosmosGenericRepository.cs
  type ICosmosGenericRepository (line 8) | public interface ICosmosGenericRepository<TEntity> where TEntity : class
    method QueryAsync (line 10) | Task<IEnumerable<TEntity>> QueryAsync(string querySpec, CancellationTo...
    method ListAsync (line 12) | Task<IEnumerable<TEntity>> ListAsync(string filterClause = default, Ca...
    method GetAsync (line 14) | Task<TEntity> GetAsync(string partitionKey, string id, CancellationTok...
    method AddAsync (line 16) | Task AddAsync(string partitionKey, TEntity entity, CancellationToken c...
    method UpsertAsync (line 18) | Task UpsertAsync(string partitionKey, TEntity entity, CancellationToke...
    method DeleteAsync (line 20) | Task DeleteAsync(string partitionKey, string id, CancellationToken can...

FILE: src/ContosoTraders.Api.Core/Repositories/Interfaces/IStockRepository.cs
  type IStockRepository (line 3) | public interface IStockRepository : ICosmosGenericRepository<StockDao>

FILE: src/ContosoTraders.Api.Core/Repositories/ProductsDbContext.cs
  class ProductsDbContext (line 6) | public class ProductsDbContext : DbContext
    method ProductsDbContext (line 8) | public ProductsDbContext()
    method ProductsDbContext (line 12) | public ProductsDbContext(DbContextOptions<ProductsDbContext> options)
    method OnModelCreating (line 23) | protected override void OnModelCreating(ModelBuilder modelBuilder)

FILE: src/ContosoTraders.Api.Core/Repositories/ProfilesDbContext.cs
  class ProfilesDbContext (line 6) | public class ProfilesDbContext : DbContext
    method ProfilesDbContext (line 8) | public ProfilesDbContext()
    method ProfilesDbContext (line 12) | public ProfilesDbContext(DbContextOptions<ProfilesDbContext> options)
    method OnModelCreating (line 19) | protected override void OnModelCreating(ModelBuilder modelBuilder)

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/AddItemToCartRequest.cs
  class AddItemToCartRequest (line 3) | public class AddItemToCartRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/DecrementStockCountRequest.cs
  class DecrementStockCountRequest (line 3) | public class DecrementStockCountRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/GetCartRequest.cs
  class GetCartRequest (line 3) | public class GetCartRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/GetPopularProductsRequest.cs
  class GetPopularProductsRequest (line 3) | public class GetPopularProductsRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/GetProductRequest.cs
  class GetProductRequest (line 3) | public class GetProductRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/GetProductsRequest.cs
  class GetProductsRequest (line 3) | public class GetProductsRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/GetProfileRequest.cs
  class GetProfileRequest (line 3) | public class GetProfileRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/GetProfilesRequest.cs
  class GetProfilesRequest (line 3) | public class GetProfilesRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/GetStockRequest.cs
  class GetStockRequest (line 3) | public class GetStockRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/LoadTestRequest.cs
  class LoadTestRequest (line 3) | public class LoadTestRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/PostImageRequest.cs
  class PostImageRequest (line 3) | public class PostImageRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/RemoveItemFromCartRequest.cs
  class RemoveItemFromCartRequest (line 3) | public class RemoveItemFromCartRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/SearchTextRequest.cs
  class SearchTextRequest (line 3) | public class SearchTextRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Definitions/UpdateCartItemQuantityRequest.cs
  class UpdateCartItemQuantityRequest (line 3) | public class UpdateCartItemQuantityRequest : IRequest<IActionResult>

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/AddItemToCartRequestHandler.cs
  class AddItemToCartRequestHandler (line 5) | internal class AddItemToCartRequestHandler : IRequestPreProcessor<AddIte...
    method AddItemToCartRequestHandler (line 9) | public AddItemToCartRequestHandler(ICartService cartService)
    method Handle (line 14) | public async Task<IActionResult> Handle(AddItemToCartRequest request, ...
    method Process (line 23) | public async Task Process(AddItemToCartRequest request, CancellationTo...

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/DecrementStockCountRequestHandler.cs
  class DecrementStockCountRequestHandler (line 5) | internal class DecrementStockCountRequestHandler : IRequestPreProcessor<...
    method DecrementStockCountRequestHandler (line 9) | public DecrementStockCountRequestHandler(IStockService stockService)
    method Handle (line 14) | public async Task<IActionResult> Handle(DecrementStockCountRequest req...
    method Process (line 21) | public async Task Process(DecrementStockCountRequest request, Cancella...

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/GetCartRequestHandler.cs
  class GetCartRequestHandler (line 5) | internal class GetCartRequestHandler : IRequestPreProcessor<GetCartReque...
    method GetCartRequestHandler (line 9) | public GetCartRequestHandler(ICartService cartService)
    method Handle (line 14) | public async Task<IActionResult> Handle(GetCartRequest request, Cancel...
    method Process (line 21) | public async Task Process(GetCartRequest request, CancellationToken ca...

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/GetPopularProductsRequestHandler.cs
  class GetPopularProductsRequestHandler (line 5) | internal class GetPopularProductsRequestHandler : IRequestPreProcessor<G...
    method Handle (line 10) | public async Task<IActionResult> Handle(GetPopularProductsRequest requ...
    method Process (line 17) | public async Task Process(GetPopularProductsRequest request, Cancellat...

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/GetProductRequestHandler.cs
  class GetProductRequestHandler (line 5) | internal class GetProductRequestHandler : IRequestPreProcessor<GetProduc...
    method GetProductRequestHandler (line 11) | public GetProductRequestHandler(IProductService productService, IStock...
    method Handle (line 17) | public async Task<IActionResult> Handle(GetProductRequest request, Can...
    method Process (line 34) | public async Task Process(GetProductRequest request, CancellationToken...

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/GetProductsRequestHandler.cs
  class GetProductsRequestHandler (line 5) | internal class GetProductsRequestHandler : RequestHandler<GetProductsReq...
    method GetProductsRequestHandler (line 9) | public GetProductsRequestHandler(IProductService productService)
    method Process (line 14) | public async Task Process(GetProductsRequest request, CancellationToke...
    method Handle (line 21) | protected override IActionResult Handle(GetProductsRequest request)

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/GetProfileRequestHandler.cs
  class GetProfileRequestHandler (line 5) | internal class GetProfileRequestHandler : RequestHandler<GetProfileReque...
    method GetProfileRequestHandler (line 9) | public GetProfileRequestHandler(IProfileService profileService)
    method Process (line 14) | public async Task Process(GetProfileRequest request, CancellationToken...
    method Handle (line 21) | protected override IActionResult Handle(GetProfileRequest request)

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/GetProfilesRequestHandler.cs
  class GetProfilesRequestHandler (line 5) | internal class GetProfilesRequestHandler : RequestHandler<GetProfilesReq...
    method GetProfilesRequestHandler (line 9) | public GetProfilesRequestHandler(IProfileService profileService)
    method Process (line 14) | public async Task Process(GetProfilesRequest request, CancellationToke...
    method Handle (line 21) | protected override IActionResult Handle(GetProfilesRequest request)

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/GetStockRequestHandler.cs
  class GetStockRequestHandler (line 5) | internal class GetStockRequestHandler : IRequestPreProcessor<GetStockReq...
    method GetStockRequestHandler (line 9) | public GetStockRequestHandler(IStockService stockService)
    method Handle (line 14) | public async Task<IActionResult> Handle(GetStockRequest request, Cance...
    method Process (line 21) | public async Task Process(GetStockRequest request, CancellationToken c...

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/LoadTestRequestHandler.cs
  class LoadTestRequestHandler (line 3) | internal class LoadTestRequestHandler : IRequestHandler<LoadTestRequest,...
    method LoadTestRequestHandler (line 7) | public LoadTestRequestHandler(ICartService cartService)
    method Handle (line 12) | public async Task<IActionResult> Handle(LoadTestRequest request, Cance...

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/PostImageRequestHandler.cs
  class PostImageRequestHandler (line 5) | internal class PostImageRequestHandler : IRequestPreProcessor<PostImageR...
    method PostImageRequestHandler (line 9) | public PostImageRequestHandler(IImageSearchService imageSearchService)
    method Handle (line 14) | public async Task<IActionResult> Handle(PostImageRequest request, Canc...
    method Process (line 24) | public async Task Process(PostImageRequest request, CancellationToken ...

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/RemoveItemFromCartRequestHandler.cs
  class RemoveItemFromCartRequestHandler (line 5) | internal class RemoveItemFromCartRequestHandler : IRequestPreProcessor<R...
    method RemoveItemFromCartRequestHandler (line 9) | public RemoveItemFromCartRequestHandler(ICartService cartService)
    method Handle (line 14) | public async Task<IActionResult> Handle(RemoveItemFromCartRequest requ...
    method Process (line 21) | public async Task Process(RemoveItemFromCartRequest request, Cancellat...

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/SearchTextRequestHandler.cs
  class SearchTextRequestHandler (line 5) | internal class SearchTextRequestHandler : IRequestPreProcessor<SearchTex...
    method SearchTextRequestHandler (line 9) | public SearchTextRequestHandler(IProductService productService)
    method Handle (line 14) | public async Task<IActionResult> Handle(SearchTextRequest request, Can...
    method Process (line 21) | public async Task Process(SearchTextRequest request, CancellationToken...

FILE: src/ContosoTraders.Api.Core/Requests/Handlers/UpdateCartItemQuantityRequestHandler.cs
  class UpdateCartItemQuantityRequestHandler (line 5) | internal class UpdateCartItemQuantityRequestHandler : IRequestPreProcess...
    method UpdateCartItemQuantityRequestHandler (line 9) | public UpdateCartItemQuantityRequestHandler(ICartService cartService)
    method Handle (line 14) | public async Task<IActionResult> Handle(UpdateCartItemQuantityRequest ...
    method Process (line 23) | public async Task Process(UpdateCartItemQuantityRequest request, Cance...

FILE: src/ContosoTraders.Api.Core/Requests/Validators/AddItemToCartRequestValidator.cs
  class AddItemToCartRequestValidator (line 3) | public class AddItemToCartRequestValidator : AbstractValidator<AddItemTo...
    method AddItemToCartRequestValidator (line 5) | public AddItemToCartRequestValidator()

FILE: src/ContosoTraders.Api.Core/Requests/Validators/DecrementStockCountRequestValidator.cs
  class DecrementStockCountRequestValidator (line 3) | public class DecrementStockCountRequestValidator : AbstractValidator<Dec...
    method DecrementStockCountRequestValidator (line 5) | public DecrementStockCountRequestValidator()

FILE: src/ContosoTraders.Api.Core/Requests/Validators/GetCartRequestValidator.cs
  class GetCartRequestValidator (line 3) | public class GetCartRequestValidator : AbstractValidator<GetCartRequest>
    method GetCartRequestValidator (line 5) | public GetCartRequestValidator()

FILE: src/ContosoTraders.Api.Core/Requests/Validators/GetPopularProductsRequestValidator.cs
  class GetPopularProductsRequestValidator (line 3) | public class GetPopularProductsRequestValidator : AbstractValidator<GetP...

FILE: src/ContosoTraders.Api.Core/Requests/Validators/GetProductRequestValidator.cs
  class GetProductRequestValidator (line 3) | public class GetProductRequestValidator : AbstractValidator<GetProductRe...
    method GetProductRequestValidator (line 5) | public GetProductRequestValidator()

FILE: src/ContosoTraders.Api.Core/Requests/Validators/GetProductsRequestValidator.cs
  class GetProductsRequestValidator (line 3) | public class GetProductsRequestValidator : AbstractValidator<GetProducts...

FILE: src/ContosoTraders.Api.Core/Requests/Validators/GetProfileRequestValidator.cs
  class GetProfileRequestValidator (line 3) | public class GetProfileRequestValidator : AbstractValidator<GetProfileRe...

FILE: src/ContosoTraders.Api.Core/Requests/Validators/GetProfilesRequestValidator.cs
  class GetProfilesRequestValidator (line 3) | public class GetProfilesRequestValidator : AbstractValidator<GetProfiles...

FILE: src/ContosoTraders.Api.Core/Requests/Validators/GetStockRequestValidator.cs
  class GetStockRequestValidator (line 3) | public class GetStockRequestValidator : AbstractValidator<GetStockRequest>
    method GetStockRequestValidator (line 5) | public GetStockRequestValidator()

FILE: src/ContosoTraders.Api.Core/Requests/Validators/PostImageRequestValidator.cs
  class PostImageRequestValidator (line 3) | public class PostImageRequestValidator : AbstractValidator<PostImageRequ...
    method PostImageRequestValidator (line 5) | public PostImageRequestValidator()

FILE: src/ContosoTraders.Api.Core/Requests/Validators/RemoveItemFromCartRequestValidator.cs
  class RemoveItemFromCartRequestValidator (line 3) | public class RemoveItemFromCartRequestValidator : AbstractValidator<Remo...
    method RemoveItemFromCartRequestValidator (line 5) | public RemoveItemFromCartRequestValidator()

FILE: src/ContosoTraders.Api.Core/Requests/Validators/SearchTextRequestValidator.cs
  class SearchTextRequestValidator (line 3) | public class SearchTextRequestValidator : AbstractValidator<SearchTextRe...
    method SearchTextRequestValidator (line 5) | public SearchTextRequestValidator()

FILE: src/ContosoTraders.Api.Core/Requests/Validators/UpdateCartItemQuantityRequestValidator.cs
  class UpdateCartItemQuantityRequestValidator (line 3) | public class UpdateCartItemQuantityRequestValidator : AbstractValidator<...
    method UpdateCartItemQuantityRequestValidator (line 5) | public UpdateCartItemQuantityRequestValidator()

FILE: src/ContosoTraders.Api.Core/Services/ContosoTradersServiceBase.cs
  class ContosoTradersServiceBase (line 3) | internal abstract class ContosoTradersServiceBase
    method ContosoTradersServiceBase (line 11) | protected ContosoTradersServiceBase(IMapper mapper, IConfiguration con...

FILE: src/ContosoTraders.Api.Core/Services/Implementations/CartService.cs
  class CartService (line 3) | internal class CartService : ContosoTradersServiceBase, ICartService
    method CartService (line 7) | public CartService(ICartRepository cartRepository, IMapper mapper, ICo...
    method GetCartAsync (line 12) | public async Task<IEnumerable<CartDto>> GetCartAsync(string email, Can...
    method AddItemToCartAsync (line 25) | public async Task AddItemToCartAsync(CartDto cartItemDto, Cancellation...
    method UpdateCartItemQuantityAsync (line 33) | public async Task UpdateCartItemQuantityAsync(CartDto cartItemDto, Can...
    method RemoveItemFromCartAsync (line 40) | public async Task RemoveItemFromCartAsync(CartDto cartItemDto, Cancell...

FILE: src/ContosoTraders.Api.Core/Services/Implementations/ImageAnalysisService.cs
  class ImageAnalysisService (line 3) | internal class ImageAnalysisService : ContosoTradersServiceBase, IImageA...
    method ImageAnalysisService (line 5) | public ImageAnalysisService(IMapper mapper, IConfiguration configurati...
    method AnalyzeImageAsync (line 9) | public async Task<IEnumerable<string>> AnalyzeImageAsync(Stream imageS...
    method GetComputerVisionClient (line 27) | private ComputerVisionClient GetComputerVisionClient()

FILE: src/ContosoTraders.Api.Core/Services/Implementations/ImageSearchService.cs
  class ImageSearchService (line 3) | internal class ImageSearchService : ContosoTradersServiceBase, IImageSea...
    method ImageSearchService (line 9) | public ImageSearchService(
    method GetSimilarProductsAsync (line 21) | public async Task<IEnumerable<ProductDto>> GetSimilarProductsAsync(Str...

FILE: src/ContosoTraders.Api.Core/Services/Implementations/ProductService.cs
  class ProductService (line 5) | internal class ProductService : ContosoTradersServiceBase, IProductService
    method ProductService (line 9) | public ProductService(ProductsDbContext productDbContext, IMapper mapp...
    method GetProduct (line 17) | public ProductDto GetProduct(int id)
    method GetProducts (line 35) | public IEnumerable<ProductDto> GetProducts(int[] brands, int[] typeIds)
    method GetProducts (line 51) | public IEnumerable<ProductDto> GetProducts(string searchTerm)
    method GetBrands (line 73) | public IEnumerable<Brand> GetBrands()
    method GetTypes (line 78) | public IEnumerable<Type> GetTypes()
    method GetAllProducts (line 85) | private IEnumerable<Product> GetAllProducts()
    method GetProductsByFilter (line 90) | private IEnumerable<Product> GetProductsByFilter(int[] brands, int[] t...
    method CustomMapping (line 101) | private ProductDto CustomMapping(Product productDao, IEnumerable<Brand...

FILE: src/ContosoTraders.Api.Core/Services/Implementations/ProfileService.cs
  class ProfileService (line 5) | internal class ProfileService : ContosoTradersServiceBase, IProfileService
    method ProfileService (line 9) | public ProfileService(ProfilesDbContext profileRepository, IMapper map...
    method GetAllProfiles (line 14) | public IEnumerable<Profile> GetAllProfiles()
    method GetProfile (line 19) | public Profile GetProfile(string email)

FILE: src/ContosoTraders.Api.Core/Services/Implementations/StockService.cs
  class StockService (line 3) | internal class StockService : ContosoTradersServiceBase, IStockService
    method StockService (line 7) | public StockService(IStockRepository stockRepository, IMapper mapper, ...
    method GetStockAsync (line 12) | public async Task<StockDto> GetStockAsync(int productId, CancellationT...
    method DecrementStockCountAsync (line 27) | public async Task<StockDto> DecrementStockCountAsync(int productId, Ca...

FILE: src/ContosoTraders.Api.Core/Services/Interfaces/ICartService.cs
  type ICartService (line 3) | internal interface ICartService
    method GetCartAsync (line 11) | Task<IEnumerable<CartDto>> GetCartAsync(string email, CancellationToke...
    method AddItemToCartAsync (line 18) | Task AddItemToCartAsync(CartDto cartItemDto, CancellationToken cancell...
    method UpdateCartItemQuantityAsync (line 25) | Task UpdateCartItemQuantityAsync(CartDto cartItemDto, CancellationToke...
    method RemoveItemFromCartAsync (line 32) | Task RemoveItemFromCartAsync(CartDto cartItemDto, CancellationToken ca...

FILE: src/ContosoTraders.Api.Core/Services/Interfaces/IImageAnalysisService.cs
  type IImageAnalysisService (line 3) | internal interface IImageAnalysisService
    method AnalyzeImageAsync (line 10) | Task<IEnumerable<string>> AnalyzeImageAsync(Stream imageStream, Cancel...

FILE: src/ContosoTraders.Api.Core/Services/Interfaces/IImageSearchService.cs
  type IImageSearchService (line 3) | internal interface IImageSearchService
    method GetSimilarProductsAsync (line 10) | Task<IEnumerable<ProductDto>> GetSimilarProductsAsync(Stream imageStre...

FILE: src/ContosoTraders.Api.Core/Services/Interfaces/IProductService.cs
  type IProductService (line 5) | public interface IProductService
    method GetProduct (line 12) | ProductDto GetProduct(int id);
    method GetProducts (line 19) | IEnumerable<ProductDto> GetProducts(int[] brands, int[] typeIds);
    method GetProducts (line 25) | IEnumerable<ProductDto> GetProducts(string searchTerm);
    method GetBrands (line 30) | IEnumerable<Brand> GetBrands();
    method GetTypes (line 35) | IEnumerable<Type> GetTypes();

FILE: src/ContosoTraders.Api.Core/Services/Interfaces/IProfileService.cs
  type IProfileService (line 5) | internal interface IProfileService
    method GetAllProfiles (line 7) | IEnumerable<Profile> GetAllProfiles();
    method GetProfile (line 9) | Profile GetProfile(string email);

FILE: src/ContosoTraders.Api.Core/Services/Interfaces/IStockService.cs
  type IStockService (line 3) | internal interface IStockService
    method GetStockAsync (line 11) | Task<StockDto> GetStockAsync(int productId, CancellationToken cancella...
    method DecrementStockCountAsync (line 19) | Task<StockDto> DecrementStockCountAsync(int productId, CancellationTok...

FILE: src/ContosoTraders.Api.Products/Controllers/LoginController.cs
  class LoginController (line 9) | [Route("v1/[controller]")]
    method LoginController (line 15) | public LoginController(IConfiguration config, IMediator mediator) : ba...
    method Login (line 20) | [HttpPost]
    method CreateAccessToken (line 32) | private AccessToken CreateAccessToken(string username)

FILE: src/ContosoTraders.Api.Products/Controllers/ProductsController.cs
  class ProductsController (line 3) | [Route("v1/[controller]")]
    method ProductsController (line 7) | public ProductsController(IMediator mediator) : base(mediator)
    method GetProducts (line 12) | [HttpGet]
    method GetProduct (line 28) | [HttpGet("{id:int}")]
    method GetPopularProducts (line 42) | [HttpGet("landing")]
    method PostImage (line 52) | [HttpPost("imageclassifier")]
    method Search (line 66) | [HttpGet("search/{text}")]

FILE: src/ContosoTraders.Api.Products/Controllers/ProfilesController.cs
  class ProfilesController (line 3) | [Route("v1/[controller]")]
    method ProfilesController (line 7) | public ProfilesController(IMediator mediator) : base(mediator)
    method GetProfiles (line 11) | [HttpGet]
    method GetProfile (line 20) | [HttpGet("me")]

FILE: src/ContosoTraders.Api.Products/Controllers/StocksController.cs
  class StocksController (line 3) | [Route("v1/[controller]")]
    method StocksController (line 7) | public StocksController(IMediator mediator) : base(mediator)
    method GetStock (line 11) | [HttpGet("{id:int}")]
    method DecrementStockCount (line 23) | [HttpPost("{id:int}/consume")]

FILE: src/ContosoTraders.Api.Products/Migration/productsdb.sql
  type Features (line 27) | CREATE TABLE Features

FILE: src/ContosoTraders.Api.Profiles/Controllers/ProfilesController.cs
  class ProfilesController (line 17) | public class ProfilesController : ContosoTradersControllerBase
    method ProfilesController (line 21) | public ProfilesController(IMediator mediator, ILogger<ProfilesControll...
    method Run (line 26) | [FunctionName("Function1")]

FILE: src/ContosoTraders.Ui.Website/src/App.js
  function App (line 35) | function App(props) {

FILE: src/ContosoTraders.Ui.Website/src/components/accordion/accordion.js
  function CustomizedAccordions (line 16) | function CustomizedAccordions(props) {

FILE: src/ContosoTraders.Ui.Website/src/components/accordion/sidebarAccordion.js
  function SidebarAccordion (line 18) | function SidebarAccordion(props) {

FILE: src/ContosoTraders.Ui.Website/src/components/corousel/corousel.js
  function Corousel (line 6) | function Corousel(props)
  function Item (line 55) | function Item(props)

FILE: src/ContosoTraders.Ui.Website/src/components/dropdowns/categories.js
  function Categories (line 31) | function Categories(props) {

FILE: src/ContosoTraders.Ui.Website/src/components/footer/footer.js
  function showPosition (line 27) | function showPosition(position) {

FILE: src/ContosoTraders.Ui.Website/src/components/header/appbar.js
  function TopAppBar (line 50) | function TopAppBar(props) {

FILE: src/ContosoTraders.Ui.Website/src/components/header/header.js
  class Header (line 35) | class Header extends Component {
    method constructor (line 36) | constructor() {
    method componentDidMount (line 48) | async componentDidMount() {
    method setComponentVisibility (line 69) | setComponentVisibility(width) {
    method render (line 120) | render() {

FILE: src/ContosoTraders.Ui.Website/src/components/header/headerMessage.js
  function HeaderMessage (line 3) | function HeaderMessage(props) {

FILE: src/ContosoTraders.Ui.Website/src/components/imageSlider/imageSlider.js
  function ImageSlider (line 11) | function ImageSlider(props) {

FILE: src/ContosoTraders.Ui.Website/src/components/productCard/product.js
  function Product (line 11) | function Product(props) {

FILE: src/ContosoTraders.Ui.Website/src/components/quantityCounter/productCounter.js
  class QuantityPicker (line 5) | class QuantityPicker extends Component {
    method constructor (line 7) | constructor(props) {
    method componentDidMount (line 16) | componentDidMount() {
    method componentDidUpdate (line 25) | componentDidUpdate(prevProps, prevState) {
    method updateProductQty (line 34) | async updateProductQty() {
    method increment (line 49) | increment() {
    method decrement (line 63) | decrement() {
    method render (line 78) | render() {

FILE: src/ContosoTraders.Ui.Website/src/components/slider/slider.js
  function Slider (line 11) | function Slider(props) {
  function Item (line 60) | function Item(props) {

FILE: src/ContosoTraders.Ui.Website/src/components/uploadFile/uploadFile.js
  function UploadFile (line 23) | function UploadFile(props) {

FILE: src/ContosoTraders.Ui.Website/src/helpers/errorsHandler.js
  function errorResponseHandler (line 6) | function errorResponseHandler(error) {
  function isTokenExpiredError (line 23) | function isTokenExpiredError(error) {

FILE: src/ContosoTraders.Ui.Website/src/pages/cart/cart.js
  function Cart (line 12) | function Cart(props) {

FILE: src/ContosoTraders.Ui.Website/src/pages/detail/detailContainer.js
  function DetailContainer (line 19) | function DetailContainer(props) {

FILE: src/ContosoTraders.Ui.Website/src/pages/detail/productDetails.js
  function ProductDetails (line 9) | function ProductDetails(props) {

FILE: src/ContosoTraders.Ui.Website/src/pages/home/home.js
  function loadSettings (line 12) | async function loadSettings() {

FILE: src/ContosoTraders.Ui.Website/src/pages/home/homeContainer.js
  class HomeContainer (line 11) | class HomeContainer extends Component {
    method constructor (line 12) | constructor() {
    method componentDidMount (line 48) | async componentDidMount() {
    method shouldComponentUpdate (line 55) | async shouldComponentUpdate(nextProps) {
    method getRank (line 61) | async getRank() {
    method getRerankedProducts (line 83) | getRerankedProducts(data) {
    method renderPopularProducts (line 106) | async renderPopularProducts(token) {
    method render (line 117) | render() {

FILE: src/ContosoTraders.Ui.Website/src/pages/home/sections/banner.js
  function Banner (line 4) | function Banner(props) {

FILE: src/ContosoTraders.Ui.Website/src/pages/home/sections/gridSection.js
  function Gridsection (line 11) | function Gridsection() {

FILE: src/ContosoTraders.Ui.Website/src/pages/home/sections/hero.js
  function Hero (line 7) | function Hero() {

FILE: src/ContosoTraders.Ui.Website/src/pages/list/listContainer.js
  function ListContainer (line 31) | function ListContainer() {

FILE: src/ContosoTraders.Ui.Website/src/pages/list/sections/listAside/listAside.js
  class ListAside (line 4) | class ListAside extends Component {
    method constructor (line 5) | constructor() {
    method componentDidMount (line 16) | componentDidMount() {
    method setComponentVisibility (line 24) | setComponentVisibility(width) {
    method openFilterPanel (line 40) | openFilterPanel() {
    method render (line 56) | render() {

FILE: src/ContosoTraders.Ui.Website/src/pages/profile/myAddressBook.js
  function MyAddressBook (line 2) | function MyAddressBook(props) {

FILE: src/ContosoTraders.Ui.Website/src/pages/profile/myWishlist.js
  function Wishlist (line 4) | function Wishlist(props) {

FILE: src/ContosoTraders.Ui.Website/src/services/authB2CService.js
  class AuthB2CService (line 4) | class AuthB2CService {
    method constructor (line 5) | constructor() {

FILE: src/ContosoTraders.Ui.Website/src/services/cartService.js
  method getShoppingCart (line 6) | async getShoppingCart(token) {
  method postProductToCart (line 21) | async postProductToCart(token, detailProduct) {
  method addProduct (line 34) | async addProduct(token, detailProduct) {
  method getRelatedDetailProducts (line 74) | async getRelatedDetailProducts(token, typeid = {}) {
  method updateQuantity (line 80) | async updateQuantity(detailProduct, qty, token) {
  method deleteProduct (line 97) | async deleteProduct(detailProduct, token) {

FILE: src/ContosoTraders.Ui.Website/src/services/configService.js
  method loadSettings (line 43) | async loadSettings() {
  method HeadersConfig (line 94) | HeadersConfig(token = undefined) {

FILE: src/ContosoTraders.Ui.Website/src/services/productsService.js
  method getHomePageData (line 9) | async getHomePageData() {
  method getCouponsPageData (line 15) | async getCouponsPageData(token) {
  method getFilteredProducts (line 21) | async getFilteredProducts(filters = {}) {
  method getDetailProductData (line 34) | async getDetailProductData(productId) {
  method getRelatedProducts (line 40) | async getRelatedProducts(formData, token) {
  method getSearchResults (line 46) | async getSearchResults(term) {

FILE: src/ContosoTraders.Ui.Website/src/services/telemetryClient.js
  class TelemetryService (line 4) | class TelemetryService {
    method constructor (line 6) | constructor() {
    method initialize (line 10) | initialize(appInsightsInstrumentationKey, reactPluginConfig) {

FILE: src/ContosoTraders.Ui.Website/src/services/userService.js
  method postLoginForm (line 8) | async postLoginForm(formData) {
  method getUserInfoData (line 14) | async getUserInfoData(token) {
  method getProfileData (line 20) | async getProfileData(token) {

FILE: src/ContosoTraders.Ui.Website/src/types/types.js
  constant FORM_EMAIL (line 1) | const FORM_EMAIL = '@@form/EMAIL';
  constant SAVE_USER (line 2) | const SAVE_USER = '@@form/SUBMIT';
  constant REMOVE_USER (line 3) | const REMOVE_USER = '@@logout/CLICK';
  constant THEME_CHANGE (line 4) | const THEME_CHANGE = 'THEME_CHANGE';
  constant GET_QUANTITY (line 5) | const GET_QUANTITY = 'GET_QUANTITY';

FILE: src/ContosoTraders.Ui.Website/tests/api/cart.spec.ts
  constant USER (line 4) | const USER = 'fake@outlook.com';
  constant PRODUCT (line 5) | const PRODUCT = 'Xbox Wireless Controller Lunar Shift Special Edition';
  constant PRODUCTID (line 6) | const PRODUCTID = 1;
  constant PRODUCTIMAGE (line 7) | const PRODUCTIMAGE = 'PID1-1.jpg';
  constant PRODUCTPRICE (line 8) | const PRODUCTPRICE = 99;
  constant PRODUCTQUANTITY (line 9) | const PRODUCTQUANTITY = 1;

FILE: src/ContosoTraders.Ui.Website/tests/api/data.spec.ts
  type Product (line 3) | type Product = ({ type: { name: string } });
Condensed preview — 296 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (905K chars).
[
  {
    "path": ".azurepipelines/aks-cost-optimization.yml",
    "chars": 5998,
    "preview": "name: aks-cost-optimization\n\ntrigger: none\n\npr: none\n\nvariables:\n  - group: contosotraders-cloudtesting-variable-group\n "
  },
  {
    "path": ".azurepipelines/contoso-traders-cloud-testing.yml",
    "chars": 41073,
    "preview": "name: contoso-traders-cloud-testing\n\ntrigger:\n  - main\n\npr:\n  branches:\n    include:\n      - main\n  paths:\n    exclude:\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 317,
    "preview": "{\n\t\"name\": \"Playwright\",\n\t\"image\": \"mcr.microsoft.com/playwright:v1.43.1-jammy\",\n\t\"features\": {\n\t\t\"ghcr.io/devcontainers"
  },
  {
    "path": ".gitattributes",
    "chars": 23,
    "preview": "*.sh eol=lf\nmvnw eol=lf"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 834,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 425,
    "preview": "# Change Description\n\n> Please include a summary of the changes.\n\n## Linked GitHub Issue\n\n> Link to any related github i"
  },
  {
    "path": ".github/workflows/aks-cost-optimization.yml",
    "chars": 6549,
    "preview": "name: aks-cost-optimization\n\non:\n  workflow_dispatch:\n  \nenv:\n  ACR_NAME: contosotradersacr\n  AKS_CLUSTER_NAME: contoso-"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "chars": 1005,
    "preview": "name: \"CodeQL\"\n\non:\n    workflow_dispatch:\n\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n    runs-on: ${{"
  },
  {
    "path": ".github/workflows/contoso-traders-cloud-testing.yml",
    "chars": 33963,
    "preview": "name: contoso-traders-cloud-testing\n\non:\n  workflow_dispatch:\n  push:\n    branches: [\"main\"]\n    paths-ignore: [\"docs/**"
  },
  {
    "path": ".gitignore",
    "chars": 5797,
    "preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## G"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 444,
    "preview": "# Microsoft Open Source Code of Conduct\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://op"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1861,
    "preview": "# How to contribute to ContosoTraders\n\nThis repo is a reference and learning resource and everyone is invited to contrib"
  },
  {
    "path": "LICENSE",
    "chars": 1141,
    "preview": "    MIT License\n\n    Copyright (c) Microsoft Corporation.\n\n    Permission is hereby granted, free of charge, to any pers"
  },
  {
    "path": "README.md",
    "chars": 6042,
    "preview": "# Contoso Traders - Cloud testing tools demo app\n\nThe Contoso Traders app is a sample application showcasing [Playwright"
  },
  {
    "path": "SECURITY.md",
    "chars": 2741,
    "preview": "<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->\n\n# Security\n\nMicrosoft takes the security of our software products and"
  },
  {
    "path": "SUPPORT.md",
    "chars": 375,
    "preview": "\r\n# Support\r\n\r\n## How to file issues and get help  \r\n\r\nThis project uses GitHub Issues to track bugs and feature request"
  },
  {
    "path": "demo-scripts/azure-chaos-studio/walkthrough.md",
    "chars": 6186,
    "preview": "# Azure Chaos Studio: Overview\n\nAzure Chaos Studio is an Azure service that allows you to create & run chaos experiments"
  },
  {
    "path": "demo-scripts/azure-load-testing/aks-cost-optimization.md",
    "chars": 3377,
    "preview": "# Azure Load Testing: AKS Cost Optimization\n\n## Key Takeaways\n\nIn this demo, we'll see how Azure Load Testing can be use"
  },
  {
    "path": "demo-scripts/azure-load-testing/private-endpoints.md",
    "chars": 5785,
    "preview": "# Azure Load Testing: Private Endpoints\n\n## Key Takeaways\n\nIn this demo, we'll attempt to load test an private API endpo"
  },
  {
    "path": "demo-scripts/azure-load-testing/walkthrough.md",
    "chars": 9747,
    "preview": "# Azure Load Testing: Overview\n\n## Key Takeaways\n\nIn this demo, you'll get an overview of Azure's Load Testing service; "
  },
  {
    "path": "demo-scripts/dev-workflow/walkthrough.md",
    "chars": 7954,
    "preview": "\n# DevOps with GitHub & Azure: Technical Walkthrough  \n\n## Key Takeaways\n\nThe key takeaways from this demo are:\n\n- GitHu"
  },
  {
    "path": "demo-scripts/testing-with-playwright/walkthrough.md",
    "chars": 7114,
    "preview": "# Testing with Playwright: Overview\n\n## Key Takeaways\n\n## Before You Begin\n\n1. Please execute the steps outlined in the "
  },
  {
    "path": "docs/architecture/.$contoso-traders-enhancements.drawio.bkp",
    "chars": 25539,
    "preview": "<mxfile host=\"Electron\" modified=\"2022-11-24T11:44:25.430Z\" agent=\"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
  },
  {
    "path": "docs/architecture/.$contoso-traders-enhancements.drawio.dtmp",
    "chars": 27357,
    "preview": "<mxfile host=\"Electron\" modified=\"2023-03-19T09:38:10.917Z\" agent=\"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
  },
  {
    "path": "docs/architecture/contoso-traders-enhancements.drawio",
    "chars": 27357,
    "preview": "<mxfile host=\"Electron\" modified=\"2023-03-19T09:38:10.021Z\" agent=\"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
  },
  {
    "path": "docs/deployment-instructions-azure-pipelines.md",
    "chars": 5664,
    "preview": "# Contoso Traders - Deployment instructions using Azure Pipelines\n\nThis document will help you deploy the Contoso Trader"
  },
  {
    "path": "docs/deployment-instructions.md",
    "chars": 12054,
    "preview": "# Contoso Traders - Deployment instructions\n\nThis document will help you deploy the Contoso Traders application in your "
  },
  {
    "path": "docs/running-locally.md",
    "chars": 3258,
    "preview": "# ContosoTraders: Running Locally\n\n1. Follow the [deployment instructions](./deployment-instructions.md) to provision th"
  },
  {
    "path": "iac/createPrivateDnsZone.bicep",
    "chars": 1750,
    "preview": "// Note: There is a very specific reason the creation of the private DNS zone happens in this separate bicep module (as "
  },
  {
    "path": "iac/createResourceGroup.bicep",
    "chars": 1157,
    "preview": "// common\ntargetScope = 'subscription'\n\n// parameters\n//////////////////////////////////////////////////////////////////"
  },
  {
    "path": "iac/createResources.bicep",
    "chars": 47697,
    "preview": "// common\ntargetScope = 'resourceGroup'\n\n// parameters\n/////////////////////////////////////////////////////////////////"
  },
  {
    "path": "iac/createResources.parameters.json",
    "chars": 220,
    "preview": "{\n  \"$schema\": \"https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#\",\n  \"contentVersion\": "
  },
  {
    "path": "iac/dashboard.json",
    "chars": 81730,
    "preview": "{\n  \"properties\": {\n    \"lenses\": {\n      \"0\": {\n        \"order\": 0,\n        \"parts\": {\n          \"0\": {\n            \"po"
  },
  {
    "path": "iac/rsa",
    "chars": 3389,
    "preview": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn\nNhAAAAAwEAAQA"
  },
  {
    "path": "iac/rsa.pub",
    "chars": 748,
    "preview": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDmGzcTpLPWs7LSA8xCVDXgAa/9dLR+7OWAkcxjHcfZ++vpBhww2MbN3WErdTv/3ItheqRIxwhs1sZ2D2NK"
  },
  {
    "path": "iac/scripts/enable-static-website.ps1",
    "chars": 276,
    "preview": "$ErrorActionPreference = 'Stop'\n$storageAccount = Get-AzStorageAccount -ResourceGroupName $env:ResourceGroupName -Accoun"
  },
  {
    "path": "loadtests/contoso-traders-carts-internal.yaml",
    "chars": 219,
    "preview": "testName: contoso-traders-carts-internal\ntestPlan: contoso-traders-carts.jmx\ndescription:\nengineInstances: 1\nsubnetId: {"
  },
  {
    "path": "loadtests/contoso-traders-carts.jmx",
    "chars": 5472,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<jmeterTestPlan version=\"1.2\" properties=\"5.0\" jmeter=\"5.4.1\">\n  <hashTree>\n    <"
  },
  {
    "path": "loadtests/contoso-traders-carts.yaml",
    "chars": 176,
    "preview": "testName: contoso-traders-carts\ntestPlan: contoso-traders-carts.jmx\ndescription:\nengineInstances: 1\nfailureCriteria:\n  -"
  },
  {
    "path": "loadtests/contoso-traders-products.jmx",
    "chars": 5472,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<jmeterTestPlan version=\"1.2\" properties=\"5.0\" jmeter=\"5.4.1\">\n  <hashTree>\n    <"
  },
  {
    "path": "loadtests/contoso-traders-products.yaml",
    "chars": 182,
    "preview": "testName: contoso-traders-products\ntestPlan: contoso-traders-products.jmx\ndescription:\nengineInstances: 1\nfailureCriteri"
  },
  {
    "path": "src/.dockerignore",
    "chars": 316,
    "preview": "**/.classpath\n**/.dockerignore\n**/.env\n**/.git\n**/.gitignore\n**/.project\n**/.settings\n**/.toolstarget\n**/.vs\n**/.vscode\n"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/ContosoTraders.Api.Carts.csproj",
    "chars": 578,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n\t<PropertyGroup>\n\t\t<TargetFramework>net7.0</TargetFramework>\n\t\t<Nullable>enable<"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Controllers/ProfilesController.cs",
    "chars": 822,
    "preview": "namespace ContosoTraders.Api.Carts.Controllers;\n\n[Route(\"v1/[controller]\")]\npublic class ProfilesController : ContosoTr"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Controllers/ShoppingCartController.cs",
    "chars": 2072,
    "preview": "namespace ContosoTraders.Api.Carts.Controllers;\n\n[Route(\"v1/[controller]\")]\npublic class ShoppingCartController : Conto"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Dockerfile",
    "chars": 758,
    "preview": "FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base\nWORKDIR /app\nEXPOSE 80\nEXPOSE 443\n\nFROM mcr.microsoft.com/dotnet/sdk:7."
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Program.cs",
    "chars": 35,
    "preview": "DependencyInjection.ConfigureApp();"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Properties/ServiceDependencies/tailwind-traders-cart - Zip Deploy/profile.arm.json",
    "chars": 7227,
    "preview": "{\n  \"$schema\": \"https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#\",\n  \"content"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Properties/launchSettings.json",
    "chars": 707,
    "preview": "{\n  \"$schema\": \"https://json.schemastore.org/launchsettings.json\",\n  \"profiles\": {\n    \"ContosoTraders.Api.Carts\": {\n   "
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Usings.cs",
    "chars": 321,
    "preview": "global using MediatR;\nglobal using Microsoft.AspNetCore.Mvc;\nglobal using ContosoTraders.Api.Core;\nglobal using Contoso"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Constants/AuthConstants.cs",
    "chars": 160,
    "preview": "namespace ContosoTraders.Api.Core.Constants;\n\npublic class AuthConstants\n{\n    public static readonly string DefaultJwt"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Constants/CosmosConstants.cs",
    "chars": 363,
    "preview": "namespace ContosoTraders.Api.Core.Constants;\n\npublic class CosmosConstants\n{\n    public const string DatabaseNameCarts "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Constants/KeyVaultConstants.cs",
    "chars": 1069,
    "preview": "namespace ContosoTraders.Api.Core.Constants;\n\ninternal class KeyVaultConstants\n{\n    #region secrets\n\n    public static"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Constants/RequestHeaderConstants.cs",
    "chars": 146,
    "preview": "namespace ContosoTraders.Api.Core.Constants;\n\npublic class RequestHeaderConstants\n{\n    public const string HeaderNameU"
  },
  {
    "path": "src/ContosoTraders.Api.Core/ContosoTraders.Api.Core.csproj",
    "chars": 2774,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<TargetFramework>net7.0</TargetFramework>\n\t\t<ImplicitUsings>enabl"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Controllers/ContosoTradersControllerBase.cs",
    "chars": 776,
    "preview": "namespace ContosoTraders.Api.Core.Controllers;\n\n[ApiController]\npublic class ContosoTradersControllerBase : ControllerB"
  },
  {
    "path": "src/ContosoTraders.Api.Core/DependencyInjection.cs",
    "chars": 5489,
    "preview": "using System.Reflection;\nusing Azure.Identity;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.Azure.Cosmos;\nusing "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/CartNotFoundException.cs",
    "chars": 356,
    "preview": "namespace ContosoTraders.Api.Core.Exceptions;\n\npublic class CartNotFoundException : ContosoTradersBaseException\n{\n    p"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/ContosoTradersBaseException.cs",
    "chars": 252,
    "preview": "namespace ContosoTraders.Api.Core.Exceptions;\n\npublic abstract class ContosoTradersBaseException : Exception\n{\n    prot"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/MatchingProductsNotFoundException.cs",
    "chars": 377,
    "preview": "namespace ContosoTraders.Api.Core.Exceptions;\n\npublic class MatchingProductsNotFoundException : ContosoTradersBaseExcep"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/ProductNotFoundException.cs",
    "chars": 365,
    "preview": "namespace ContosoTraders.Api.Core.Exceptions;\n\npublic class ProductNotFoundException : ContosoTradersBaseException\n{\n  "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/ProfileNotFoundException.cs",
    "chars": 361,
    "preview": "namespace ContosoTraders.Api.Core.Exceptions;\n\npublic class ProfileNotFoundException : ContosoTradersBaseException\n{\n   "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/StockNotFoundException.cs",
    "chars": 356,
    "preview": "namespace ContosoTraders.Api.Core.Exceptions;\n\npublic class StockNotFoundException : ContosoTradersBaseException\n{\n    "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/AutoMapperProfile.cs",
    "chars": 1194,
    "preview": "using Profile = AutoMapper.Profile;\nusing ProfileDao = ContosoTraders.Api.Core.Models.Implementations.Dao.Profile;\n\nnam"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Brand.cs",
    "chars": 155,
    "preview": "namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Brand\n{\n    public int Id { get; set; }\n   "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/CartDao.cs",
    "chars": 399,
    "preview": "namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class CartDao : ICosmosDao<string>\n{\n    public s"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Feature.cs",
    "chars": 246,
    "preview": "namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Feature\n{\n    public int Id { get; set; }\n "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Product.cs",
    "chars": 350,
    "preview": "namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Product\n{\n    public int Id { get; set; }\n "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Profile.cs",
    "chars": 379,
    "preview": "namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Profile\n{\n    public int Id { get; set; }\n\n"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/StockDao.cs",
    "chars": 186,
    "preview": "namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class StockDao : ICosmosDao<string>\n{\n    public "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Tag.cs",
    "chars": 154,
    "preview": "namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Tag\n{\n    public int Id { get; set; }\n    p"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Type.cs",
    "chars": 191,
    "preview": "namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Type\n{\n    public int Id { get; set; }\n    "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dto/AccessToken.cs",
    "chars": 372,
    "preview": "using Newtonsoft.Json;\n\nnamespace ContosoTraders.Api.Core.Models.Implementations.Dto;\n\npublic class AccessToken\n{\n    ["
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dto/CartDto.cs",
    "chars": 365,
    "preview": "namespace ContosoTraders.Api.Core.Models.Implementations.Dto;\n\npublic class CartDto\n{\n    public string CartItemId { ge"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dto/ProductDto.cs",
    "chars": 486,
    "preview": "using Type = ContosoTraders.Api.Core.Models.Implementations.Dao.Type;\n\nnamespace ContosoTraders.Api.Core.Models.Impleme"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dto/StockDto.cs",
    "chars": 169,
    "preview": "namespace ContosoTraders.Api.Core.Models.Implementations.Dto;\n\npublic class StockDto\n{\n    public int ProductId { get; "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dto/TokenRequest.cs",
    "chars": 291,
    "preview": "using Newtonsoft.Json;\n\nnamespace ContosoTraders.Api.Core.Models.Implementations.Dto;\n\npublic class TokenRequest\n{\n    "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Interfaces/ICosmosDao.cs",
    "chars": 266,
    "preview": "namespace ContosoTraders.Api.Core.Models.Interfaces;\n\npublic interface ICosmosDao<T>\n{\n    // ReSharper disable once In"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Implementations/CartRepository.cs",
    "chars": 386,
    "preview": "using Microsoft.Azure.Cosmos;\n\nnamespace ContosoTraders.Api.Core.Repositories.Implementations;\n\npublic class CartReposi"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Implementations/CosmosGenericRepositoryBase.cs",
    "chars": 3603,
    "preview": "using System.Net;\nusing Microsoft.Azure.Cosmos;\n\nnamespace ContosoTraders.Api.Core.Repositories.Implementations;\n\npubli"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Implementations/StockRepository.cs",
    "chars": 392,
    "preview": "using Microsoft.Azure.Cosmos;\n\nnamespace ContosoTraders.Api.Core.Repositories.Implementations;\n\npublic class StockRepos"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Interfaces/ICartRepository.cs",
    "chars": 133,
    "preview": "namespace ContosoTraders.Api.Core.Repositories.Interfaces;\n\npublic interface ICartRepository : ICosmosGenericRepository"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Interfaces/ICosmosGenericRepository.cs",
    "chars": 1051,
    "preview": "namespace ContosoTraders.Api.Core.Repositories.Interfaces;\n\n/// <remarks>\n///     The type parameter constraint of 'cla"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Interfaces/IStockRepository.cs",
    "chars": 135,
    "preview": "namespace ContosoTraders.Api.Core.Repositories.Interfaces;\n\npublic interface IStockRepository : ICosmosGenericRepositor"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/ProductsDbContext.cs",
    "chars": 1869,
    "preview": "using Microsoft.EntityFrameworkCore;\nusing Type = ContosoTraders.Api.Core.Models.Implementations.Dao.Type;\n\nnamespace C"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/ProfilesDbContext.cs",
    "chars": 1061,
    "preview": "using Microsoft.EntityFrameworkCore;\nusing Profile = ContosoTraders.Api.Core.Models.Implementations.Dao.Profile;\n\nnames"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/AddItemToCartRequest.cs",
    "chars": 163,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class AddItemToCartRequest : IRequest<IActionResult>\n{\n"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/DecrementStockCountRequest.cs",
    "chars": 166,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class DecrementStockCountRequest : IRequest<IActionResu"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetCartRequest.cs",
    "chars": 153,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetCartRequest : IRequest<IActionResult>\n{\n    pu"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetPopularProductsRequest.cs",
    "chars": 126,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetPopularProductsRequest : IRequest<IActionResul"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetProductRequest.cs",
    "chars": 157,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetProductRequest : IRequest<IActionResult>\n{\n   "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetProductsRequest.cs",
    "chars": 198,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetProductsRequest : IRequest<IActionResult>\n{\n  "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetProfileRequest.cs",
    "chars": 155,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetProfileRequest : IRequest<IActionResult>\n{\n    "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetProfilesRequest.cs",
    "chars": 119,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetProfilesRequest : IRequest<IActionResult>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetStockRequest.cs",
    "chars": 155,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetStockRequest : IRequest<IActionResult>\n{\n    p"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/LoadTestRequest.cs",
    "chars": 116,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class LoadTestRequest : IRequest<IActionResult>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/PostImageRequest.cs",
    "chars": 157,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class PostImageRequest : IRequest<IActionResult>\n{\n    "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/RemoveItemFromCartRequest.cs",
    "chars": 168,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class RemoveItemFromCartRequest : IRequest<IActionResul"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/SearchTextRequest.cs",
    "chars": 155,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class SearchTextRequest : IRequest<IActionResult>\n{\n   "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/UpdateCartItemQuantityRequest.cs",
    "chars": 172,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class UpdateCartItemQuantityRequest : IRequest<IActionR"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/AddItemToCartRequestHandler.cs",
    "chars": 1030,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class AddItemToCartRequestHandl"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/DecrementStockCountRequestHandler.cs",
    "chars": 969,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class DecrementStockCountReques"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetCartRequestHandler.cs",
    "chars": 860,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetCartRequestHandler : I"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetPopularProductsRequestHandler.cs",
    "chars": 790,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetPopularProductsRequest"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetProductRequestHandler.cs",
    "chars": 1280,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetProductRequestHandler "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetProductsRequestHandler.cs",
    "chars": 1311,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetProductsRequestHandler"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetProfileRequestHandler.cs",
    "chars": 833,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetProfileRequestHandler "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetProfilesRequestHandler.cs",
    "chars": 833,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetProfilesRequestHandler"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetStockRequestHandler.cs",
    "chars": 871,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetStockRequestHandler : "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/LoadTestRequestHandler.cs",
    "chars": 1355,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class LoadTestRequestHandler : IRequestHandler<LoadTestR"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/PostImageRequestHandler.cs",
    "chars": 1079,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class PostImageRequestHandler :"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/RemoveItemFromCartRequestHandler.cs",
    "chars": 982,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class RemoveItemFromCartRequest"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/SearchTextRequestHandler.cs",
    "chars": 900,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class SearchTextRequestHandler "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/UpdateCartItemQuantityRequestHandler.cs",
    "chars": 1080,
    "preview": "using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class UpdateCartItemQuantityReq"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/AddItemToCartRequestValidator.cs",
    "chars": 472,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class AddItemToCartRequestValidator : AbstractValidator<"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/DecrementStockCountRequestValidator.cs",
    "chars": 354,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class DecrementStockCountRequestValidator : AbstractVali"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetCartRequestValidator.cs",
    "chars": 494,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetCartRequestValidator : AbstractValidator<GetCar"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetPopularProductsRequestValidator.cs",
    "chars": 155,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetPopularProductsRequestValidator : AbstractValid"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetProductRequestValidator.cs",
    "chars": 327,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetProductRequestValidator : AbstractValidator<Get"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetProductsRequestValidator.cs",
    "chars": 141,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetProductsRequestValidator : AbstractValidator<Ge"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetProfileRequestValidator.cs",
    "chars": 139,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetProfileRequestValidator : AbstractValidator<Get"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetProfilesRequestValidator.cs",
    "chars": 141,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetProfilesRequestValidator : AbstractValidator<Ge"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetStockRequestValidator.cs",
    "chars": 321,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetStockRequestValidator : AbstractValidator<GetSt"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/PostImageRequestValidator.cs",
    "chars": 314,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class PostImageRequestValidator : AbstractValidator<Post"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/RemoveItemFromCartRequestValidator.cs",
    "chars": 621,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class RemoveItemFromCartRequestValidator : AbstractValid"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/SearchTextRequestValidator.cs",
    "chars": 317,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class SearchTextRequestValidator : AbstractValidator<Sea"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/UpdateCartItemQuantityRequestValidator.cs",
    "chars": 637,
    "preview": "namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class UpdateCartItemQuantityRequestValidator : AbstractV"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/ContosoTradersServiceBase.cs",
    "chars": 490,
    "preview": "namespace ContosoTraders.Api.Core.Services;\n\ninternal abstract class ContosoTradersServiceBase\n{\n    protected readonly"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/CartService.cs",
    "chars": 1798,
    "preview": "namespace ContosoTraders.Api.Core.Services.Implementations;\n\ninternal class CartService : ContosoTradersServiceBase, IC"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/ImageAnalysisService.cs",
    "chars": 1346,
    "preview": "namespace ContosoTraders.Api.Core.Services.Implementations;\n\ninternal class ImageAnalysisService : ContosoTradersServic"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/ImageSearchService.cs",
    "chars": 1638,
    "preview": "namespace ContosoTraders.Api.Core.Services.Implementations;\n\ninternal class ImageSearchService : ContosoTradersServiceBa"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/ProductService.cs",
    "chars": 4393,
    "preview": "using Type = ContosoTraders.Api.Core.Models.Implementations.Dao.Type;\n\nnamespace ContosoTraders.Api.Core.Services.Imple"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/ProfileService.cs",
    "chars": 888,
    "preview": "using Profile = ContosoTraders.Api.Core.Models.Implementations.Dao.Profile;\n\nnamespace ContosoTraders.Api.Core.Services"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/StockService.cs",
    "chars": 1635,
    "preview": "namespace ContosoTraders.Api.Core.Services.Implementations;\n\ninternal class StockService : ContosoTradersServiceBase, I"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/ICartService.cs",
    "chars": 1189,
    "preview": "namespace ContosoTraders.Api.Core.Services.Interfaces;\n\ninternal interface ICartService\n{\n    /// <summary>\n    /// </s"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/IImageAnalysisService.cs",
    "chars": 374,
    "preview": "namespace ContosoTraders.Api.Core.Services.Interfaces;\n\ninternal interface IImageAnalysisService\n{\n    /// <summary>\n  "
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/IImageSearchService.cs",
    "chars": 381,
    "preview": "namespace ContosoTraders.Api.Core.Services.Interfaces;\n\ninternal interface IImageSearchService\n{\n    /// <summary>\n    /"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/IProductService.cs",
    "chars": 945,
    "preview": "using Type = ContosoTraders.Api.Core.Models.Implementations.Dao.Type;\n\nnamespace ContosoTraders.Api.Core.Services.Inter"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/IProfileService.cs",
    "chars": 254,
    "preview": "using Profile = ContosoTraders.Api.Core.Models.Implementations.Dao.Profile;\n\nnamespace ContosoTraders.Api.Core.Services"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/IStockService.cs",
    "chars": 731,
    "preview": "namespace ContosoTraders.Api.Core.Services.Interfaces;\n\ninternal interface IStockService\n{\n    /// <summary>\n    /// </"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Usings.cs",
    "chars": 1180,
    "preview": "global using AutoMapper;\nglobal using ContosoTraders.Api.Core;\nglobal using ContosoTraders.Api.Core.Constants;\nglobal u"
  },
  {
    "path": "src/ContosoTraders.Api.Products/ContosoTraders.Api.Products.csproj",
    "chars": 779,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n\t<PropertyGroup>\n\t\t<TargetFramework>net7.0</TargetFramework>\n\t\t<Nullable>disable"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Controllers/LoginController.cs",
    "chars": 1950,
    "preview": "using System.IdentityModel.Tokens.Jwt;\nusing System.Security.Claims;\nusing System.Text;\nusing ContosoTraders.Api.Core.C"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Controllers/ProductsController.cs",
    "chars": 2139,
    "preview": "namespace ContosoTraders.Api.Products.Controllers;\n\n[Route(\"v1/[controller]\")]\n[Produces(\"application/json\")]\npublic cl"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Controllers/ProfilesController.cs",
    "chars": 819,
    "preview": "namespace ContosoTraders.Api.Products.Controllers;\n\n[Route(\"v1/[controller]\")]\n[Produces(\"application/json\")]\npublic cl"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Controllers/StocksController.cs",
    "chars": 865,
    "preview": "namespace ContosoTraders.Api.Products.Controllers;\n\n[Route(\"v1/[controller]\")]\n[Produces(\"application/json\")]\npublic cl"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Dockerfile",
    "chars": 785,
    "preview": "FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base\nWORKDIR /app\nEXPOSE 80\nEXPOSE 443\n\nFROM mcr.microsoft.com/dotnet/sdk:7."
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/Certificate.yaml",
    "chars": 335,
    "preview": "apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: tls-secret\nspec:\n  secretName: tls-secret\n  dnsNames:"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/ClusterIssuer.yaml",
    "chars": 493,
    "preview": "apiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: letsencrypt\nspec:\n  acme:\n    # The ACME server URL"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/ClusterRole.yaml",
    "chars": 634,
    "preview": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: containerHealth-log-reader\nrules:\n  - apiGr"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/Deployment.yaml",
    "chars": 1389,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: contoso-traders-products\nspec:\n  #Note: The '{AKS_REPLICAS}' toke"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/Ingress.yaml",
    "chars": 974,
    "preview": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: contoso-traders-products\n  annotations:\n    kubernetes."
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/NamespaceCertManager.yaml",
    "chars": 119,
    "preview": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: cert-manager\n  labels:\n    cert-manager.io/disable-validation: \"true\"\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/NamespaceChaosTesting.yaml",
    "chars": 63,
    "preview": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: chaos-testing\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/Service.yaml",
    "chars": 166,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: contoso-traders-products\nspec:\n  type: ClusterIP\n  ports:\n    - port: 80\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Migration/productsdb.sql",
    "chars": 20101,
    "preview": "use productsdb\n\nDROP TABLE IF EXISTS Brands\n\nCREATE TABLE Brands\n(\n    Id int NOT NULL,\n    Name nvarchar(255) NULL,\n   "
  },
  {
    "path": "src/ContosoTraders.Api.Products/Program.cs",
    "chars": 35,
    "preview": "DependencyInjection.ConfigureApp();"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Properties/ServiceDependencies/tailwind-traders-product - Web Deploy/profile.arm.json",
    "chars": 4326,
    "preview": "{\n  \"$schema\": \"https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#\",\n  \"content"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Properties/ServiceDependencies/tailwind-traders-product - Web Deploy1/profile.arm.json",
    "chars": 4326,
    "preview": "{\n  \"$schema\": \"https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#\",\n  \"content"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Properties/launchSettings.json",
    "chars": 710,
    "preview": "{\n  \"$schema\": \"https://json.schemastore.org/launchsettings.json\",\n  \"profiles\": {\n    \"ContosoTraders.Api.Products\": {\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Usings.cs",
    "chars": 331,
    "preview": "global using MediatR;\nglobal using Microsoft.AspNetCore.Mvc;\nglobal using ContosoTraders.Api.Core;\nglobal using Contoso"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/.gitignore",
    "chars": 4363,
    "preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# Azur"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/ContosoTraders.Api.Profiles.csproj",
    "chars": 826,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>net7.0</TargetFramework>\n    <AzureFunctionsVer"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/Controllers/ProfilesController.cs",
    "chars": 2008,
    "preview": "using System.IO;\nusing System.Net;\nusing System.Threading.Tasks;\nusing MediatR;\nusing Microsoft.AspNetCore.Http;\nusing M"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/Properties/serviceDependencies.json",
    "chars": 177,
    "preview": "{\n  \"dependencies\": {\n    \"appInsights1\": {\n      \"type\": \"appInsights\"\n    },\n    \"storage1\": {\n      \"type\": \"storage\""
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/Properties/serviceDependencies.local.json",
    "chars": 190,
    "preview": "{\n  \"dependencies\": {\n    \"appInsights1\": {\n      \"type\": \"appInsights.sdk\"\n    },\n    \"storage1\": {\n      \"type\": \"stor"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/Usings.cs",
    "chars": 50,
    "preview": "global using ContosoTraders.Api.Core.Controllers;"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/host.json",
    "chars": 175,
    "preview": "{\n  \"version\": \"2.0\",\n  \"logging\": {\n    \"applicationInsights\": {\n      \"samplingSettings\": {\n        \"isEnabled\": true,"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/.gitignore",
    "chars": 511,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# t"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/README.md",
    "chars": 3359,
    "preview": "# Getting Started with Create React App\n\nThis project was bootstrapped with [Create React App](https://github.com/facebo"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/package.json",
    "chars": 1289,
    "preview": "{\n  \"name\": \"website-ui\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@mui/icons-material\": \"^5.13"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/playwright.config.ts",
    "chars": 2524,
    "preview": "import { defineConfig, devices } from '@playwright/test';\n\n/**\n * Read environment variables from file.\n * https://githu"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/public/browserconfig.xml",
    "chars": 246,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/public/index.html",
    "chars": 2750,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"shortcut icon\" href=\"%PUBLIC_URL%"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/public/manifest.json",
    "chars": 306,
    "preview": "{\n  \"short_name\": \"React App\",\n  \"name\": \"Create React App Sample\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n     "
  },
  {
    "path": "src/ContosoTraders.Ui.Website/public/site.webmanifest",
    "chars": 456,
    "preview": "{\n    \"name\": \"Contoso Traders\",\n    \"short_name\": \"Contoso Traders\",\n    \"icons\": [\n        {\n            \"src\": \"/andr"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/App.css",
    "chars": 564,
    "preview": ".App {\n  text-align: center;\n}\n\n.App-logo {\n  height: 40vmin;\n  pointer-events: none;\n}\n\n@media (prefers-reduced-motion:"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/App.js",
    "chars": 4178,
    "preview": "import React, { Fragment, useCallback, useEffect, useState } from \"react\";\nimport { Route, Routes } from \"react-router-d"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/App.test.js",
    "chars": 246,
    "preview": "import { render, screen } from '@testing-library/react';\nimport App from './App';\n\ntest('renders learn react link', () ="
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/actions/actions.js",
    "chars": 537,
    "preview": "import { FORM_EMAIL, SAVE_USER, REMOVE_USER, THEME_CHANGE, GET_QUANTITY } from '../types/types';\n\nexport const textActio"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/Input/checkbox.js",
    "chars": 455,
    "preview": "import React from \"react\";\n\nimport PropTypes from \"prop-types\";\n\nconst Checkbox = ({ type = \"checkbox\", name, checked = "
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/accordion/accordion.js",
    "chars": 1867,
    "preview": "import React from \"react\";\nimport MuiAccordion from \"@mui/material/Accordion\";\nimport MuiAccordionSummary from \"@mui/mat"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/accordion/accordion.scss",
    "chars": 1508,
    "preview": ".MuiAccordion-root{\n    .panelSummary {\n      flex-direction: row-reverse;\n      // padding-left: 10px;\n      border: 1p"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/accordion/sidebarAccordion.js",
    "chars": 4297,
    "preview": "\nimport React, { useRef, createRef } from \"react\";\nimport MuiAccordion from \"@mui/material/Accordion\";\nimport MuiAccordi"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/breadcrumb/breadcrumb.js",
    "chars": 497,
    "preview": "import React from \"react\";\nimport { Link } from \"react-router-dom\";\nimport './breadcrumb.scss'\n\nconst Breadcrumb = ({par"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/breadcrumb/breadcrumb.scss",
    "chars": 544,
    "preview": ".breadcrump {\n  padding: 24px 70px;\n\n  p {\n    margin: 0;\n    text-transform: capitalize;\n\n    span {\n      font-family:"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/corousel/corousel.js",
    "chars": 2978,
    "preview": "import React from 'react';\nimport Carousel from 'react-material-ui-carousel'\nimport { Button, Grid } from '@mui/material"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/corousel/corousel.scss",
    "chars": 2543,
    "preview": ".courousel-style {\n  padding: 70px;\n  /* background-image: url('./assets/images/original/Contoso_Assets/Slider_section/h"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/dropdowns/categories.js",
    "chars": 2257,
    "preview": "import React from 'react';\nimport Button from '@mui/material/Button';\nimport Menu from '@mui/material/Menu';\nimport Menu"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/dropdowns/categories.scss",
    "chars": 701,
    "preview": ".mainHeader .categories-btn {\n    min-width: 198px;\n    background-color: #F8F8F8 !important;\n    border: 1px solid #EFE"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/footer/footer.js",
    "chars": 5969,
    "preview": "import React, { useCallback } from 'react'\nimport { Link } from 'react-router-dom';\nimport { Grid } from '@mui/material'"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/footer/footer.scss",
    "chars": 3838,
    "preview": "// -----------------------------------------------------------------------------\n// This file contains all styles relate"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/header/appbar.js",
    "chars": 14696,
    "preview": "import React, { useCallback, useEffect, useRef } from 'react';\nimport { Link, useNavigate } from 'react-router-dom';\nimp"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/header/header.js",
    "chars": 9593,
    "preview": "import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { Link } from 'react-router-dom'"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/header/header.scss",
    "chars": 9708,
    "preview": "// -----------------------------------------------------------------------------\n// This file contains all styles relate"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/header/headerMessage.js",
    "chars": 331,
    "preview": "import React from 'react';\n\nfunction HeaderMessage(props) {\n    const { type, icon, message } = props\n    return ( \n    "
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/imageSlider/imageSlider.js",
    "chars": 2447,
    "preview": "import React from \"react\";\nimport productdetailimg1 from \"../../assets/images/original/Contoso_Assets/product_page_asset"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/imageSlider/imageSlider.scss",
    "chars": 677,
    "preview": ".slidesection {\n    margin-right: 16px;\n    display: flex;\n    justify-content: center;\n}\n\n.slideimagelist {\n    flex-di"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/loadingSpinner/loadingSpinner.js",
    "chars": 208,
    "preview": "import React from \"react\";\nimport './loadingSpinner.scss'\n\nconst LoadingSpinner = () => (\n    <div className=\"loader-wra"
  }
]

// ... and 96 more files (download for full content)

About this extraction

This page contains the full source code of the microsoft/contosotraders-cloudtesting GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 296 files (828.1 KB), approximately 243.0k tokens, and a symbol index with 343 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!