[
  {
    "path": ".ado/eshoponweb-cd-aci.yml",
    "content": "#NAME THE PIPELINE SAME AS FILE (WITHOUT \".yml\")\n\n# trigger: none\nresources:\n  pipelines:\n  - pipeline: eshoponweb-ci-dockercompose\n    source: eshoponweb-ci-dockercompose # given pipeline name\n    trigger: true\n  repositories:\n    - repository: self\n      trigger: none\n\nvariables:\n  location: 'centralus'\n  templateFile: 'infra/aci.bicep'\n  subscriptionid: 'YOUR-SUBSCRIPTION-ID'\n  azureserviceconnection: 'azure subs'\n  webappname: 'az400eshop-NAME'\n  acr-login-server: 'YOUR-ACR.azurecr.io'\n  acr-username: 'ACR-USERNAME'\n  resource-group: 'AZ400-EWebShop-NAME' \n\n\nstages:\n- stage: Deploy\n  displayName: Docker Compose to ACI\n  #variable group referencing KV secret\n  variables:\n  - group: 'eshopweb-vg'\n  jobs:\n  - job: Deploy\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    # Deploy Azure Container Instance using Bicep\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: Deploy ACI Bicep\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: '$(azureserviceconnection)'\n        subscriptionId: '$(subscriptionid)'\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resource-group)'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: '$(templateFile)'\n        overrideParameters: ' -name $(webappname) -image $(acr-login-server)/eshopwebmvc:latest -server $(acr-login-server) -username $(acr-username) -password $(acr-secret)'\n        deploymentMode: 'Incremental'\n        # deploymentOutputs: 'asp-json'\n    "
  },
  {
    "path": ".ado/eshoponweb-cd-webapp-code.yml",
    "content": "#NAME THE PIPELINE SAME AS FILE (WITHOUT \".yml\")\n\n# Trigger CD when CI executed succesfully\nresources:\n  pipelines:\n    - pipeline: eshoponweb-ci\n      source: eshoponweb-ci # given pipeline name\n      trigger: true\n\n\nvariables:\n  resource-group: 'AZ400-EWebShop-NAME'\n  location: 'centralus'\n  templateFile: 'webapp.bicep'\n  subscriptionid: 'YOUR-SUBSCRIPTION-ID'\n  azureserviceconnection: 'azure subs'\n  webappname: 'az400-webapp-NAME'\n  # webappname: 'webapp-windows-eshop'\n  \n\nstages:\n- stage: Deploy\n  displayName: Deploy to WebApp\n  jobs:\n  - job: Deploy\n    pool:\n      vmImage: windows-latest\n    steps:\n    #download artifacts\n    - download: eshoponweb-ci\n  \n    # Deploy App Service Plan + App Service using Bicep\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: Deploy App Service Plan Bicep\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: '$(azureserviceconnection)'\n        subscriptionId: '$(subscriptionid)'\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resource-group)'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: '$(Pipeline.Workspace)/eshoponweb-ci/Bicep/$(templateFile)'\n        overrideParameters: '-webAppName $(webappname) -location $(location)'\n        deploymentMode: 'Incremental'\n        deploymentOutputs: 'asp-json'\n    \n    #Publish Website to Azure WebApp\n    - task: AzureRmWebAppDeployment@4\n      displayName: Publish Website to WebApp\n      inputs:\n        ConnectionType: 'AzureRM'\n        azureSubscription: '$(azureserviceconnection)'\n        appType: 'webApp'\n        WebAppName: '$(webappname)'\n        packageForLinux: '$(Pipeline.Workspace)/eshoponweb-ci/Website/Web.zip'\n      \n\n    \n"
  },
  {
    "path": ".ado/eshoponweb-cd-webapp-docker.yml",
    "content": "#NAME THE PIPELINE SAME AS FILE (WITHOUT \".yml\")\n\nresources:\n  repositories:\n    - repository: self\n      trigger: none\n\nvariables:\n  azureServiceConnection: 'azure subs'\n  resourceGroup: 'rg-az400-container-NAME'\n  location: 'centralus'\n  subscriptionId: 'YOUR-SUBSCRIPTION-ID'\n\nstages:\n- stage: Deploy\n  jobs:\n  - job: Deploy\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: Deploy App Service using Bicep\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: $(azureServiceConnection)\n        subscriptionId: $(subscriptionId)\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resourceGroup)'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: 'infra/webapp-docker.bicep'\n        deploymentMode: 'Incremental'\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: Add Role Assignment using Bicep\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: $(azureServiceConnection)\n        subscriptionId: $(subscriptionId)\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resourceGroup)'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: 'infra/webapp-to-acr-roleassignment.bicep'\n        deploymentMode: 'Incremental'\n"
  },
  {
    "path": ".ado/eshoponweb-cd-webapp-dockercompose.yml",
    "content": "#NOT WORKING YET\n\n#NAME THE PIPELINE SAME AS FILE (WITHOUT \".yml\")\n#locally docker compose works, deploying to Azure WebApp is succesful but something still fails\n\n# trigger: none\nresources:\n  repositories:\n    - repository: self\n      trigger: none\n\nvariables:\n  tag: '$(Build.BuildId)'\n  resource-group: 'AZ400-EWebShop-NAME'\n  location: 'centralus'\n  templateFile: '.ado/IaC/app-plan.bicep'\n  subscriptionid: 'YOUR-SUBSCRIPTION-ID'\n  azureserviceconnection: 'azure subs'\n  webappname: 'az400-webapp-compose-NAME'\n  composeFile: 'docker-compose-webapp.yml'\n  acr-login-server: 'YOUR-ACR.azurecr.io'\n  acr-username: 'YOUR-ACR'\n\n\nstages:\n- stage: Deploy\n  displayName: Docker Compose to WebApp\n  jobs:\n  - job: Deploy\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    # Deploy App Service Plan using Bicep\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: Deploy App Service Plan Bicep\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: $(azureserviceconnection)\n        subscriptionId: $(subscriptionid)\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resource-group)'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: '$(templateFile)'\n        deploymentMode: 'Incremental'\n        deploymentOutputs: 'asp-json'\n    \n    #Parse App Service Plan  variable\n    - task: PowerShell@2\n      displayName: Parse Bicep Output\n      inputs:\n        targetType: 'inline'\n        script: |\n          $var=ConvertFrom-Json '$(asp-json)'\n          $value=$var.asplan.value\n          Write-Host \"##vso[task.setvariable variable=appserviceplan;]$value\"\n          echo $appserviceplan\n\n    #Replace tokens in Docker Compose file\n    - task: qetza.replacetokens.replacetokens-task.replacetokens@3\n      inputs:\n        targetFiles: 'docker-compose-webapp.yml'\n        encoding: 'auto'\n        writeBOM: true\n        actionOnMissing: 'warn'\n        keepToken: false\n        actionOnNoFiles: 'continue'\n        enableTransforms: false\n        tokenPrefix: '__'\n        tokenSuffix: '__'\n        enableRecursion: false\n        useLegacyPattern: false\n        enableTelemetry: true\n    #Docker Login into private ACR (TODO alternative, using Docker task and docker ADO Service Connection)\n    #Deploy using Docker Compose\n    - task: AzureCLI@2\n      inputs:\n        azureSubscription: '$(azureserviceconnection)'\n        scriptType: 'bash'\n        scriptLocation: 'inlineScript'\n        inlineScript: |\n          az webapp create --resource-group $(resource-group) --plan $(appserviceplan) --name $(webappname) --multicontainer-config-type compose --multicontainer-config-file $(composeFile) \n          az webapp config container set --resource-group $(resource-group) --name $(webappname) --docker-registry-server-user $(acr-username)  --docker-registry-server-password $(acr-secret) --docker-registry-server-url https://$(acr-login-server)"
  },
  {
    "path": ".ado/eshoponweb-cd-windows-cm.yml",
    "content": "variables:\r\n  resource-group: 'AZ400-EWebShop-NAME'\r\n  location: 'centralus'\r\n  templateFile: 'infra/simple-windows-vm.bicep'\r\n  azureserviceconnection: 'azure subs'\r\n\r\nstages:\r\n- stage: Deploy\r\n  displayName: Deploy the Bicep template\r\n  jobs:\r\n  - job: Deploy\r\n    pool:\r\n      vmImage: ubuntu-latest\r\n    steps:\r\n    - checkout: self\r\n    - task: AzureCLI@2\r\n      inputs:\r\n        azureSubscription: $(azureserviceconnection)\r\n        scriptType: 'bash'\r\n        scriptLocation: 'inlineScript'\r\n        inlineScript: |\r\n          az group create -l $(location) -n $(resource-group)\r\n          \r\n          az deployment group create -f $(templateFile) -g $(resource-group)\r\n"
  },
  {
    "path": ".ado/eshoponweb-ci-docker.yml",
    "content": "# NAME THE PIPELINE SAME AS FILE (WITHOUT \".yml\")\n\nresources:\n  repositories:\n    - repository: self\n      trigger: none\n\nvariables:\n  azureServiceConnection: 'azure subs'\n  subscriptionId: 'YOUR-SUBSCRIPTION-ID'\n  resourceGroup: 'rg-az400-container-NAME'\n  location: 'centralus'\n\nstages:\n- stage: Build\n  jobs:\n  - job: Build\n    pool:\n      vmImage: 'ubuntu-latest'\n    steps:\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: Deploy ACR using Bicep\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: $(azureServiceConnection)\n        subscriptionId: $(subscriptionId)\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resourceGroup)'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: 'infra/acr.bicep'\n        deploymentMode: 'Incremental'\n        deploymentOutputs: 'outputJson'\n    - task: PowerShell@2\n      displayName: Parse Bicep Output\n      inputs:\n        targetType: 'inline'\n        script: |\n          $var=ConvertFrom-Json '$(outputJson)'\n          $value=$var.acrLoginServer.value\n          Write-Host \"##vso[task.setvariable variable=acrLoginServer;]$value\"\n    - task: Docker@0\n      displayName: 'Build the docker image'\n      inputs:\n        azureSubscription: $(azureServiceConnection)\n        azureContainerRegistry: $(acrLoginServer)\n        dockerFile: 'src/Web/Dockerfile'\n        defaultContext: false\n        context: $(Build.SourcesDirectory)\n        includeLatestTag: true\n        imageName: eshoponweb/web:$(Build.BuildId)\n    - task: Docker@0\n      displayName: 'Push the docker images'\n      inputs:\n        azureSubscription: $(azureServiceConnection)\n        azureContainerRegistry: $(acrLoginServer)\n        action: 'Push an image'\n        imageName: eshoponweb/web:$(Build.BuildId)\n        includeLatestTag: true\n    \n"
  },
  {
    "path": ".ado/eshoponweb-ci-dockercompose.yml",
    "content": "#NAME THE PIPELINE SAME AS FILE (WITHOUT \".yml\")\n\n# Docker\n# Build a Docker image\n# https://docs.microsoft.com/azure/devops/pipelines/languages/docker\n\n# trigger:\n# - main\n\nresources:\n  repositories:\n    - repository: self\n      trigger: none\n\nvariables:\n  tag: '$(Build.BuildId)'\n  resource-group: 'AZ400-EWebShop-NAME'\n  location: 'centralus'\n  templateFile: 'infra/acr.bicep'\n  azureserviceconnection: 'azure subs'\n  subscriptionId: 'YOUR-SUBSCRIPTION-ID'\n\nstages:\n- stage: Build\n  displayName: Create ACR for images\n  jobs:\n  - job: Build\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    #Create ACR to keep docker images\n    - task: AzureResourceManagerTemplateDeployment@3\n      displayName: Deploy ACR Bicep\n      inputs:\n        deploymentScope: 'Resource Group'\n        azureResourceManagerConnection: $(azureserviceconnection)\n        subscriptionId: $(subscriptionId)\n        action: 'Create Or Update Resource Group'\n        resourceGroupName: '$(resource-group)'\n        location: '$(location)'\n        templateLocation: 'Linked artifact'\n        csmFile: '$(templateFile)'\n        deploymentMode: 'Incremental'\n        deploymentOutputs: 'acr-json'\n    \n    #Parse ACR login server variable\n    - task: PowerShell@2\n      displayName: Parse Bicep Output\n      inputs:\n        targetType: 'inline'\n        script: |\n          $var=ConvertFrom-Json '$(acr-json)'\n          $value=$var.acrloginServer.value\n          Write-Host \"##vso[task.setvariable variable=acrloginserver;]$value\"\n          echo $acrloginserver\n    # docker compose build images\n    - task: DockerCompose@1\n      displayName: Build Docker Compose\n      inputs:\n        containerregistrytype: 'Azure Container Registry'\n        azureSubscription: '$(azureserviceconnection)'\n        azureContainerRegistry: '$(acrloginserver)'\n        dockerComposeFile: '**/docker-compose.yml'\n        projectName: \n        action: 'Build services'\n        additionalImageTags: '$(Build.BuildNumber)'\n        includeLatestTag: true\n    - task: DockerCompose@1\n      displayName: Push Docker Compose\n      inputs:\n        containerregistrytype: 'Azure Container Registry'\n        azureSubscription: $(azureserviceconnection)\n        azureContainerRegistry: '$(acrloginserver)'\n        dockerComposeFile: '**/docker-compose.yml'\n        projectName: \n        action: 'Push services'\n        additionalImageTags: '$(Build.BuildNumber)'\n        includeLatestTag: true\n"
  },
  {
    "path": ".ado/eshoponweb-ci-mend.yml",
    "content": "#NAME THE PIPELINE SAME AS FILE (WITHOUT \".yml\")\n# trigger:\n# - main\n\nresources:\n  repositories:\n    - repository: self\n      trigger: none\n  \nstages:\n- stage: Build\n  displayName: Build .Net Core Solution\n  jobs:\n  - job: Build\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    - task: DotNetCoreCLI@2\n      displayName: Restore\n      inputs:\n        command: 'restore'\n        projects: '**/*.sln'\n        feedsToUse: 'select'\n\n    - task: DotNetCoreCLI@2\n      displayName: Build\n      inputs:\n        command: 'build'\n        projects: '**/*.sln'\n    \n    - task: DotNetCoreCLI@2\n      displayName: Test\n      inputs:\n        command: 'test'\n        projects: 'tests/UnitTests/*.csproj'\n    \n    - task: WhiteSource@21\n      inputs:\n        cwd: '$(System.DefaultWorkingDirectory)'\n        projectName: 'eShopOnWeb'\n    - task: DotNetCoreCLI@2\n      displayName: Publish\n      inputs:\n        command: 'publish'\n        publishWebProjects: true\n        arguments: '-o $(Build.ArtifactStagingDirectory)'\n    \n    - task: PublishBuildArtifacts@1\n      displayName: Publish Artifacts ADO - Website\n      inputs:\n        pathToPublish: '$(Build.ArtifactStagingDirectory)'\n        artifactName: Website\n \n"
  },
  {
    "path": ".ado/eshoponweb-ci-pr.yml",
    "content": "\nresources:\n  repositories:\n    - repository: self\n      trigger: none\n\nstages:\n- stage: Build\n  displayName: Build .Net Core Solution\n  jobs:\n  - job: Build\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    - task: DotNetCoreCLI@2\n      displayName: Restore\n      inputs:\n        command: 'restore'\n        projects: '**/*.sln'\n        feedsToUse: 'select'\n\n    - task: DotNetCoreCLI@2\n      displayName: Build\n      inputs:\n        command: 'build'\n        projects: '**/*.sln'\n    \n    - task: DotNetCoreCLI@2\n      displayName: Test\n      inputs:\n        command: 'test'\n        projects: 'tests/UnitTests/*.csproj'\n    \n    - task: DotNetCoreCLI@2\n      displayName: Publish\n      inputs:\n        command: 'publish'\n        publishWebProjects: true\n        arguments: '-o $(Build.ArtifactStagingDirectory)'"
  },
  {
    "path": ".ado/eshoponweb-ci.yml",
    "content": "#NAME THE PIPELINE SAME AS FILE (WITHOUT \".yml\")\n# trigger:\n# - main\n\nresources:\n  repositories:\n    - repository: self\n      trigger: none\n\nstages:\n- stage: Build\n  displayName: Build .Net Core Solution\n  jobs:\n  - job: Build\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    - task: DotNetCoreCLI@2\n      displayName: Restore\n      inputs:\n        command: 'restore'\n        projects: '**/*.sln'\n        feedsToUse: 'select'\n\n    - task: DotNetCoreCLI@2\n      displayName: Build\n      inputs:\n        command: 'build'\n        projects: '**/*.sln'\n    \n    - task: DotNetCoreCLI@2\n      displayName: Test\n      inputs:\n        command: 'test'\n        projects: 'tests/UnitTests/*.csproj'\n    \n    - task: DotNetCoreCLI@2\n      displayName: Publish\n      inputs:\n        command: 'publish'\n        publishWebProjects: true\n        arguments: '-o $(Build.ArtifactStagingDirectory)'\n    \n    - task: PublishPipelineArtifact@1\n      displayName: Publish Artifacts ADO - Website\n      inputs:\n        targetPath: '$(Build.ArtifactStagingDirectory)'\n        artifact: 'Website'\n        publishLocation: 'pipeline'\n\n    - task: PublishPipelineArtifact@1\n      displayName: Publish Artifacts ADO - Bicep\n      inputs:\n        targetPath: '$(Build.SourcesDirectory)/infra/webapp.bicep'\n        artifact: 'Bicep'\n        publishLocation: 'pipeline'\n"
  },
  {
    "path": ".ado/eshoponweb-sonar-ci.yml",
    "content": "#NAME THE PIPELINE SAME AS FILE (WITHOUT \".yml\")\n# trigger:\n# - main\n\nresources:\n  repositories:\n    - repository: self\n      trigger: none\n\nstages:\n- stage: Build\n  displayName: Build .Net Core Solution\n  jobs:\n  - job: Build\n    pool:\n      vmImage: ubuntu-latest\n    steps:\n    \n    - checkout: self\n      fetchDepth: 0\n    - task: DotNetCoreCLI@2\n      displayName: Restore\n      inputs:\n        command: 'restore'\n        projects: '**/*.sln'\n        feedsToUse: 'select'\n\n    - task: SonarCloudPrepare@1\n      inputs:\n        SonarCloud: 'SonarSC'\n        organization: 'Your Sonarcloud org'\n        scannerMode: 'MSBuild'\n        projectKey: 'your sonarcloud project key'\n        projectName: 'your sonarcloud project name'\n    - task: DotNetCoreCLI@2\n      displayName: Build\n      inputs:\n        command: 'build'\n        projects: '**/*.sln'\n    \n    - task: DotNetCoreCLI@2\n      displayName: Test\n      inputs:\n        command: 'test'\n        projects: 'tests/UnitTests/*.csproj'\n    \n    - task: SonarCloudAnalyze@1\n    \n    - task: SonarCloudPublish@1\n      inputs:\n        pollingTimeoutSec: '300'\n    \n    - task: DotNetCoreCLI@2\n      displayName: Publish\n      inputs:\n        command: 'publish'\n        publishWebProjects: true\n        arguments: '-o $(Build.ArtifactStagingDirectory)'\n    \n    - task: PublishBuildArtifacts@1\n      displayName: Publish Artifacts ADO - Website\n      inputs:\n        pathToPublish: '$(Build.ArtifactStagingDirectory)'\n        artifactName: Website\n    \n"
  },
  {
    "path": ".dockerignore",
    "content": ".dockerignore\n.env\n.git\n.gitignore\n.vs\n.vscode\n*/bin\n*/obj\n**/.toolstarget"
  },
  {
    "path": ".editorconfig",
    "content": "###############################\n# Core EditorConfig Options   #\n###############################\nroot = true\n# All files\n[*]\nindent_style = space\n\n# XML project files\n[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]\nindent_size = 2\n\n# XML config files\n[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]\nindent_size = 2\n\n# Code files\n[*.{cs,csx,vb,vbx}]\nindent_size = 4\ninsert_final_newline = true\ncharset = utf-8-bom\n###############################\n# .NET Coding Conventions     #\n###############################\n[*.{cs,vb}]\n# Organize usings\ndotnet_sort_system_directives_first = true\n# this. preferences\ndotnet_style_qualification_for_field = false:silent\ndotnet_style_qualification_for_property = false:silent\ndotnet_style_qualification_for_method = false:silent\ndotnet_style_qualification_for_event = false:silent\n# Language keywords vs BCL types preferences\ndotnet_style_predefined_type_for_locals_parameters_members = true:silent\ndotnet_style_predefined_type_for_member_access = true:silent\n# Parentheses preferences\ndotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent\ndotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent\ndotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent\ndotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent\n# Modifier preferences\ndotnet_style_require_accessibility_modifiers = for_non_interface_members:silent\ndotnet_style_readonly_field = true:suggestion\n# Expression-level preferences\ndotnet_style_object_initializer = true:suggestion\ndotnet_style_collection_initializer = true:suggestion\ndotnet_style_explicit_tuple_names = true:suggestion\ndotnet_style_null_propagation = true:suggestion\ndotnet_style_coalesce_expression = true:suggestion\ndotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent\ndotnet_style_prefer_inferred_tuple_names = true:suggestion\ndotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion\ndotnet_style_prefer_auto_properties = true:silent\ndotnet_style_prefer_conditional_expression_over_assignment = true:silent\ndotnet_style_prefer_conditional_expression_over_return = true:silent\n###############################\n# Naming Conventions          #\n###############################\n# Style Definitions\ndotnet_naming_style.pascal_case_style.capitalization             = pascal_case\n# Use PascalCase for constant fields\ndotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion\ndotnet_naming_rule.constant_fields_should_be_pascal_case.symbols  = constant_fields\ndotnet_naming_rule.constant_fields_should_be_pascal_case.style    = pascal_case_style\ndotnet_naming_symbols.constant_fields.applicable_kinds            = field\ndotnet_naming_symbols.constant_fields.applicable_accessibilities  = *\ndotnet_naming_symbols.constant_fields.required_modifiers          = const\n\n# Instance fields are camelCase and start with _\ndotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion\ndotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields\ndotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style\n\ndotnet_naming_symbols.instance_fields.applicable_kinds = field\n\ndotnet_naming_style.instance_field_style.capitalization = camel_case\ndotnet_naming_style.instance_field_style.required_prefix = _\n###############################\n# C# Coding Conventions       #\n###############################\n[*.cs]\n# var preferences\ncsharp_style_var_for_built_in_types = true:silent\ncsharp_style_var_when_type_is_apparent = true:silent\ncsharp_style_var_elsewhere = true:silent\n# Expression-bodied members\ncsharp_style_expression_bodied_methods = false:silent\ncsharp_style_expression_bodied_constructors = false:silent\ncsharp_style_expression_bodied_operators = false:silent\ncsharp_style_expression_bodied_properties = true:silent\ncsharp_style_expression_bodied_indexers = true:silent\ncsharp_style_expression_bodied_accessors = true:silent\n# Pattern matching preferences\ncsharp_style_pattern_matching_over_is_with_cast_check = true:suggestion\ncsharp_style_pattern_matching_over_as_with_null_check = true:suggestion\n# Null-checking preferences\ncsharp_style_throw_expression = true:suggestion\ncsharp_style_conditional_delegate_call = true:suggestion\n# Modifier preferences\ncsharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion\n# Expression-level preferences\ncsharp_prefer_braces = true:silent\ncsharp_style_deconstructed_variable_declaration = true:suggestion\ncsharp_prefer_simple_default_expression = true:suggestion\ncsharp_style_pattern_local_over_anonymous_function = true:suggestion\ncsharp_style_inlined_variable_declaration = true:suggestion\n# Namespaces\ncsharp_style_namespace_declarations = file_scoped:warning\n###############################\n# C# Formatting Rules         #\n###############################\n# New line preferences\ncsharp_new_line_before_open_brace = all\ncsharp_new_line_before_else = true\ncsharp_new_line_before_catch = true\ncsharp_new_line_before_finally = true\ncsharp_new_line_before_members_in_object_initializers = true\ncsharp_new_line_before_members_in_anonymous_types = true\ncsharp_new_line_between_query_expression_clauses = true\n# Indentation preferences\ncsharp_indent_case_contents = true\ncsharp_indent_switch_labels = true\ncsharp_indent_labels = flush_left\n# Space preferences\ncsharp_space_after_cast = false\ncsharp_space_after_keywords_in_control_flow_statements = true\ncsharp_space_between_method_call_parameter_list_parentheses = false\ncsharp_space_between_method_declaration_parameter_list_parentheses = false\ncsharp_space_between_parentheses = false\ncsharp_space_before_colon_in_inheritance_clause = true\ncsharp_space_after_colon_in_inheritance_clause = true\ncsharp_space_around_binary_operators = before_and_after\ncsharp_space_between_method_declaration_empty_parameter_list_parentheses = false\ncsharp_space_between_method_call_name_and_opening_parenthesis = false\ncsharp_space_between_method_call_empty_parameter_list_parentheses = false\n# Wrapping preferences\ncsharp_preserve_single_line_statements = true\ncsharp_preserve_single_line_blocks = true\n###############################\n# VB Coding Conventions       #\n###############################\n[*.vb]\n# Modifier preferences\nvisual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion###############################\n######################################\n# Configure Nullable Reference Types #\n######################################\n[{**/*Dto.cs,**/*Request.cs,**/*Response.cs}]\n# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable.\ndotnet_diagnostic.CS8618.severity = none\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n*.{cmd,[cC][mM][dD]} text eol=crlf\n*.{bat,[bB][aA][tT]} text eol=crlf"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"nuget\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n"
  },
  {
    "path": ".github/workflows/dotnetcore.yml",
    "content": "name: eShopOnWeb Build and Test\n\n#Triggers (uncomment line below to use it)\n#on: [push, pull_request, workflow_dispatch]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v5\n      - name: Setup .NET\n        uses: actions/setup-dotnet@v5\n        with:\n          dotnet-version: \"8.0.x\"\n          dotnet-quality: \"preview\"\n\n      - name: Build with dotnet\n        run: dotnet build ./eShopOnWeb.sln --configuration Release\n\n      - name: Test with dotnet\n        run: dotnet test ./eShopOnWeb.sln --configuration Release\n"
  },
  {
    "path": ".github/workflows/eshoponweb-cicd.yml",
    "content": "name: eShopOnWeb Build and Test\n\n#Triggers (uncomment line below to use it)\n#on: [push, workflow_dispatch]\n\n#Environment variables https://docs.github.com/en/actions/learn-github-actions/environment-variables\nenv:\n  RESOURCE-GROUP: rg-eshoponweb-NAME\n  LOCATION: westeurope\n  TEMPLATE-FILE: infra/webapp.bicep\n  SUBSCRIPTION-ID: YOUR-SUBS-ID\n  WEBAPP-NAME: eshoponweb-webapp-NAME\n\njobs:\n  #Build, test and publish .net web project in repository\n  buildandtest:\n    runs-on: ubuntu-latest\n    steps:\n      #checkout the repository\n      - uses: actions/checkout@v5\n      #prepare runner for desired .net version SDK\n      - name: Setup .NET\n        uses: actions/setup-dotnet@v5\n        with:\n          dotnet-version: \"8.0.x\"\n          dotnet-quality: \"preview\"\n      #Build/Test/Publish the .net project\n      - name: Build with dotnet\n        run: dotnet build ./eShopOnWeb.sln --configuration Release\n      - name: Test with dotnet\n        run: dotnet test ./eShopOnWeb.sln --configuration Release\n      - name: dotnet publish\n        run: |\n          dotnet publish ./src/Web/Web.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp\n          cd ${{env.DOTNET_ROOT}}/myapp\n          zip -r ../app.zip .\n      # upload the published website code artifacts\n      - name: Upload artifact for deployment job\n        uses: actions/upload-artifact@v5\n        with:\n          name: .net-app\n          path: ${{env.DOTNET_ROOT}}/app.zip\n\n      # upload the bicep template as artifacts for next job\n      - name: Upload artifact for deployment job\n        uses: actions/upload-artifact@v5\n        with:\n          name: bicep-template\n          path: ${{ env.TEMPLATE-FILE }}\n\n  # Use Bicep to deploy infrastructure + Publish webapp\n  deploy:\n    runs-on: ubuntu-latest\n    needs: buildandtest\n    environment:\n      name: \"Development\"\n    steps:\n      #Download the publish files created in previous job\n      - name: Download artifact from build job\n        uses: actions/download-artifact@v6\n        with:\n          name: .net-app\n          path: .net-app\n\n      #Download the bicep templates from previous job\n      - name: Download artifact from build job\n        uses: actions/download-artifact@v6\n        with:\n          name: bicep-template\n          path: bicep-template\n\n      #Login in your azure subscription using a service principal (credentials stored as GitHub Secret in repo)\n      - name: Azure Login\n        uses: azure/login@v2\n        with:\n          creds: ${{ secrets.AZURE_CREDENTIALS }}\n\n      # Deploy Azure WebApp using Bicep file\n      - name: deploy\n        uses: azure/arm-deploy@v2\n        with:\n          subscriptionId: ${{ env.SUBSCRIPTION-ID }}\n          resourceGroupName: ${{ env.RESOURCE-GROUP }}\n          template: bicep-template/webapp.bicep\n          parameters: \"webAppName=${{ env.WEBAPP-NAME }} location=${{ env.LOCATION }}\"\n          failOnStdErr: false\n\n      # Publish website to Azure App Service (WebApp)\n      # Step disabled due to issue where the site sometimes can't be found: https://github.com/microsoft/pipelines-appservice-lib/issues/56. Instead deploy using CLI\n      - name: Publish Website to WebApp\n        if: false #Disable step due to comment above\n        uses: Azure/webapps-deploy@v3\n        with:\n          type: ZIP\n          app-name: ${{ env.WEBAPP-NAME  }}\n          package: .net-app/app.zip\n\n      # Publish website to Azure App Service using CLI (WebApp)\n      - name: Publish Website to WebApp\n        uses: Azure/cli@v2\n        with:\n          inlineScript: |\n            az webapp deploy --name ${{ env.WEBAPP-NAME }} --resource-group ${{ env.RESOURCE-GROUP }} --src-path .net-app/app.zip --type zip\n"
  },
  {
    "path": ".github/workflows/richnav.yml",
    "content": "name: eShopOnWeb - Code Index\n\non: workflow_dispatch\n\njobs:\n  build:\n    runs-on: windows-latest\n\n    steps:\n      - uses: actions/checkout@v5\n      - name: Setup .NET Core\n        uses: actions/setup-dotnet@v5\n        with:\n          dotnet-version: 8.0.x\n\n      - name: Build with dotnet\n        run: dotnet build ./Everything.sln --configuration Release /bl\n\n      - uses: microsoft/RichCodeNavIndexer@v0.1\n        with:\n          repo-token: ${{ github.token }}\n          languages: \"csharp\"\n          environment: \"internal\"\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n**/wwwroot/lib/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# DNX\nproject.lock.json\nartifacts/\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n# NuGet v3's project.json files produces more ignoreable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\nnode_modules/\norleans.codegen.cs\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\npub/\n/src/Web/WebMVC/Properties/PublishProfiles/eShopOnContainersWebMVC2016 - Web Deploy-publish.ps1\n/src/Web/WebMVC/Properties/PublishProfiles/publish-module.psm1\n/src/Services/Identity/eShopOnContainers.Identity/Properties/launchSettings.json\n\n#Ignore marker-file used to know which docker files we have.\n.eshopdocker_*\n.devcontainer\n\n.azure\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n    \"recommendations\": [\n      \"ms-dotnettools.csharp\",\n      \"formulahendry.dotnet-test-explorer\",\n      \"ms-vscode.vscode-node-azure-pack\",\n      \"ms-kubernetes-tools.vscode-kubernetes-tools\",\n      \"redhat.vscode-yaml\",\n      \"ms-azuretools.azure-dev\"\n    ]\n}"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n   // Use IntelliSense to find out which attributes exist for C# debugging\n   // Use hover for the description of the existing attributes\n   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md\n   \"version\": \"0.2.0\",\n   \"configurations\": [\n        {\n            \"name\": \".NET Core Launch (web)\",\n            \"type\": \"coreclr\",\n            \"request\": \"launch\",\n            \"preLaunchTask\": \"build\",\n            // If you have changed target frameworks, make sure to update the program path.\n            \"program\": \"${workspaceFolder}/src/Web/bin/Debug/net8.0/Web.dll\",\n            \"args\": [],\n            \"cwd\": \"${workspaceFolder}/src/Web\",\n            \"stopAtEntry\": false,\n            // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser\n            \"serverReadyAction\": {\n                \"action\": \"openExternally\",\n                \"pattern\": \"^\\\\s*Now listening on:\\\\s+(https?://\\\\S+)\"\n            },\n            \"env\": {\n                \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n            },\n            \"sourceFileMap\": {\n                \"/Views\": \"${workspaceFolder}/Views\"\n            }\n        },\n        {\n            \"name\": \".NET Core Attach\",\n            \"type\": \"coreclr\",\n            \"request\": \"attach\",\n            \"processId\": \"${command:pickProcess}\"\n        }\n    ]\n}"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n    \"version\": \"2.0.0\",\n    \"tasks\": [\n        {\n            \"label\": \"build\",\n            \"command\": \"dotnet\",\n            \"type\": \"process\",\n            \"args\": [\n                \"build\",\n                \"${workspaceFolder}/src/Web/Web.csproj\",\n                \"/property:GenerateFullPaths=true\",\n                \"/consoleloggerparameters:NoSummary\"\n            ],\n            \"problemMatcher\": \"$msCompile\"\n        },\n        {\n            \"label\": \"publish\",\n            \"command\": \"dotnet\",\n            \"type\": \"process\",\n            \"args\": [\n                \"publish\",\n                \"${workspaceFolder}/src/Web/Web.csproj\",\n                \"/property:GenerateFullPaths=true\",\n                \"/consoleloggerparameters:NoSummary\"\n            ],\n            \"problemMatcher\": \"$msCompile\"\n        },\n        {\n            \"label\": \"watch\",\n            \"command\": \"dotnet\",\n            \"type\": \"process\",\n            \"args\": [\n                \"watch\",\n                \"run\",\n                \"${workspaceFolder}/src/Web/Web.csproj\",\n                \"/property:GenerateFullPaths=true\",\n                \"/consoleloggerparameters:NoSummary\"\n            ],\n            \"problemMatcher\": \"$msCompile\"\n        }\n    ]\n}"
  },
  {
    "path": "CodeCoverage.runsettings",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- File name extension must be .runsettings -->\n<RunSettings>\n  <DataCollectionRunSettings>\n    <DataCollectors>\n      <DataCollector friendlyName=\"Code Coverage\" uri=\"datacollector://Microsoft/CodeCoverage/2.0\" assemblyQualifiedName=\"Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n        <Configuration>\n          <CodeCoverage>\n            <!--\nAdditional paths to search for .pdb (symbol) files. Symbols must be found for modules to be instrumented.\nIf .pdb files are in the same folder as the .dll or .exe files, they are automatically found. Otherwise, specify them here.\nNote that searching for symbols increases code coverage runtime. So keep this small and local.\n-->\n            <!--\n            <SymbolSearchPaths>\n                   <Path>C:\\Users\\User\\Documents\\Visual Studio 2012\\Projects\\ProjectX\\bin\\Debug</Path>\n                   <Path>\\\\mybuildshare\\builds\\ProjectX</Path>\n            </SymbolSearchPaths>\n-->\n\n            <!--\nAbout include/exclude lists:\nEmpty \"Include\" clauses imply all; empty \"Exclude\" clauses imply none.\nEach element in the list is a regular expression (ECMAScript syntax). See https://docs.microsoft.com/visualstudio/ide/using-regular-expressions-in-visual-studio.\nAn item must first match at least one entry in the include list to be included.\nIncluded items must then not match any entries in the exclude list to remain included.\n-->\n\n            <!-- Match assembly file paths: -->\n            <ModulePaths>\n              <Exclude>\n                <ModulePath>.*guardclauses\\.dll$</ModulePath>\n                <ModulePath>.*moq\\.dll$</ModulePath>\n                <ModulePath>.*unittests\\.dll$</ModulePath>\n              </Exclude>\n            </ModulePaths>\n\n            <!-- Match fully qualified names of functions: -->\n            <!-- (Use \"\\.\" to delimit namespaces in C# or Visual Basic, \"::\" in C++.) \n            <Functions>\n              <Exclude>\n                <Function>^Fabrikam\\.UnitTest\\..*</Function>\n                <Function>^std::.*</Function>\n                <Function>^ATL::.*</Function>\n                <Function>.*::__GetTestMethodInfo.*</Function>\n                <Function>^Microsoft::VisualStudio::CppCodeCoverageFramework::.*</Function>\n                <Function>^Microsoft::VisualStudio::CppUnitTestFramework::.*</Function>\n              </Exclude>\n            </Functions> -->\n\n            <!-- Match attributes on any code element: -->\n            <Attributes>\n              <Exclude>\n                <!-- Don't forget \"Attribute\" at the end of the name \n                <Attribute>^System\\.Diagnostics\\.DebuggerHiddenAttribute$</Attribute>\n                <Attribute>^System\\.Diagnostics\\.DebuggerNonUserCodeAttribute$</Attribute>\n                <Attribute>^System\\.Runtime\\.CompilerServices.CompilerGeneratedAttribute$</Attribute>\n                <Attribute>^System\\.CodeDom\\.Compiler.GeneratedCodeAttribute$</Attribute>\n                <Attribute>^System\\.Diagnostics\\.CodeAnalysis.ExcludeFromCodeCoverageAttribute$</Attribute>-->\n              </Exclude>\n            </Attributes>\n\n            <!-- Match the path of the source files in which each method is defined:\n            <Sources>\n              <Exclude>\n                <Source>.*\\\\atlmfc\\\\.*</Source>\n                <Source>.*\\\\vctools\\\\.*</Source>\n                <Source>.*\\\\public\\\\sdk\\\\.*</Source>\n                <Source>.*\\\\microsoft sdks\\\\.*</Source>\n                <Source>.*\\\\vc\\\\include\\\\.*</Source>\n              </Exclude>\n            </Sources> -->\n\n            <!-- Match the company name property in the assembly: -->\n            <CompanyNames>\n              <Exclude>\n                <CompanyName>.*microsoft.*</CompanyName>\n              </Exclude>\n            </CompanyNames>\n\n            <!-- Match the public key token of a signed assembly: -->\n            <PublicKeyTokens>\n              <!-- Exclude Visual Studio extensions: \n              <Exclude>\n                <PublicKeyToken>^B77A5C561934E089$</PublicKeyToken>\n                <PublicKeyToken>^B03F5F7F11D50A3A$</PublicKeyToken>\n                <PublicKeyToken>^31BF3856AD364E35$</PublicKeyToken>\n                <PublicKeyToken>^89845DCD8080CC91$</PublicKeyToken>\n                <PublicKeyToken>^71E9BCE111E9429C$</PublicKeyToken>\n                <PublicKeyToken>^8F50407C4E9E73B6$</PublicKeyToken>\n                <PublicKeyToken>^E361AF139669C375$</PublicKeyToken>\n              </Exclude>-->\n            </PublicKeyTokens>\n\n            <!-- We recommend you do not change the following values: -->\n            <UseVerifiableInstrumentation>True</UseVerifiableInstrumentation>\n            <AllowLowIntegrityProcesses>True</AllowLowIntegrityProcesses>\n            <CollectFromChildProcesses>True</CollectFromChildProcesses>\n            <CollectAspDotNet>False</CollectAspDotNet>\n\n          </CodeCoverage>\n        </Configuration>\n      </DataCollector>\n    </DataCollectors>\n  </DataCollectionRunSettings>\n</RunSettings>"
  },
  {
    "path": "Directory.Packages.props",
    "content": "<Project>\n  <PropertyGroup>\n    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>\n    <TargetFramework>net8.0</TargetFramework>\n    <AspNetVersion>8.0.0</AspNetVersion>\n    <SystemExtensionVersion>8.0.1</SystemExtensionVersion>\n    <EntityFramworkCoreVersion>8.0.0</EntityFramworkCoreVersion>\n    <VSCodeGeneratorVersion>9.0.0</VSCodeGeneratorVersion>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageVersion Include=\"Ardalis.ApiEndpoints\" Version=\"4.1.0\" />\n    <PackageVersion Include=\"Ardalis.GuardClauses\" Version=\"5.0.0\" />\n    <PackageVersion Include=\"Ardalis.Specification.EntityFrameworkCore\" Version=\"9.3.1\" />\n    <PackageVersion Include=\"Ardalis.Result\" Version=\"10.1.0\" />\n    <PackageVersion Include=\"Ardalis.Specification\" Version=\"9.3.1\" />\n    <PackageVersion Include=\"Ardalis.ListStartupServices\" Version=\"1.1.4\" />\n    <PackageVersion Include=\"Azure.Extensions.AspNetCore.Configuration.Secrets\" Version=\"1.4.0\" />\n    <PackageVersion Include=\"Azure.Identity\" Version=\"1.16.0\" />\n    <PackageVersion Include=\"AutoMapper.Extensions.Microsoft.DependencyInjection\" Version=\"12.0.1\" />\n    <PackageVersion Include=\"BlazorInputFile\" Version=\"0.2.0\" />\n    <PackageVersion Include=\"Blazored.LocalStorage\" Version=\"4.5.0\" />\n    <PackageVersion Include=\"BuildBundlerMinifier\" Version=\"3.2.449\" PrivateAssets=\"All\" />\n    <PackageVersion Include=\"FluentValidation\" Version=\"11.10.0\" />\n    <PackageVersion Include=\"MediatR\" Version=\"12.5.0\" />\n    <PackageVersion Include=\"Microsoft.AspNetCore.Components.Authorization\" Version=\"8.0.11\" />\n    <PackageVersion Include=\"Microsoft.AspNetCore.Components.WebAssembly\" Version=\"8.0.13\" />\n    <PackageVersion Include=\"Microsoft.AspNetCore.Components.WebAssembly.DevServer\" Version=\"8.0.10\" PrivateAssets=\"all\" />\n    <PackageVersion Include=\"Microsoft.AspNetCore.Components.WebAssembly.Authentication\" Version=\"8.0.11\" />\n    <PackageVersion Include=\"Microsoft.AspNetCore.Components.WebAssembly.Server\" Version=\"8.0.15\" />    \n    <PackageVersion Include=\"Microsoft.AspNetCore.Identity.EntityFrameworkCore\" Version=\"8.0.10\" />\n    <PackageVersion Include=\"Microsoft.AspNetCore.Authentication.JwtBearer\" Version=\"8.0.15\" />\n\n    <PackageVersion Include=\"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore\" Version=\"8.0.12\" />\n    <PackageVersion Include=\"Microsoft.AspNetCore.Identity.UI\" Version=\"8.0.10\" />\n    <PackageVersion Include=\"Microsoft.AspNetCore.Mvc\" Version=\"2.2.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Identity.Core\" Version=\"8.0.10\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Logging.Configuration\" Version=\"$(SystemExtensionVersion)\" />\n    <PackageVersion Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" Version=\"$(VSCodeGeneratorVersion)\" />\n    <PackageVersion Include=\"Microsoft.Web.LibraryManager.Build\" Version=\"3.0.71\" />\n    <PackageVersion Include=\"Microsoft.EntityFrameworkCore.InMemory\" Version=\"8.0.0\" />\n    <PackageVersion Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"8.0.8\" />\n    <PackageVersion Include=\"Microsoft.FeatureManagement.AspNetCore\" Version=\"4.0.0\" />\n    <PackageVersion Include=\"Microsoft.Azure.AppConfiguration.AspNetCore\" Version=\"8.0.0\" />\n    <PackageVersion Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"8.0.8\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageVersion>\n    <PackageVersion Include=\"Microsoft.VisualStudio.Azure.Containers.Tools.Targets\" Version=\"1.20.1\" />\n    <PackageVersion Include=\"MinimalApi.Endpoint\" Version=\"1.3.0\" />\n    <PackageVersion Include=\"Moq\" Version=\"4.20.70\" />\n    <PackageVersion Include=\"NSubstitute\" Version=\"5.1.0\" />\n    <PackageVersion Include=\"NSubstitute.Analyzers.CSharp\" Version=\"1.0.17\" />\n    <PackageVersion Include=\"System.Net.Http.Json\" Version=\"$(SystemExtensionVersion)\" />\n    <PackageVersion Include=\"System.Security.Claims\" Version=\"4.3.0\" />\n    <PackageVersion Include=\"System.Text.Json\" Version=\"8.0.5\" />\n    <PackageVersion Include=\"Swashbuckle.AspNetCore\" Version=\"6.8.1\" />\n    <PackageVersion Include=\"System.IdentityModel.Tokens.Jwt\" Version=\"8.1.1\" />\n    <PackageVersion Include=\"Swashbuckle.AspNetCore.SwaggerUI\" Version=\"7.3.1\" />\n    <PackageVersion Include=\"Swashbuckle.AspNetCore.Annotations\" Version=\"7.2.0\" />\n    <!-- Test -->\n    <PackageVersion Include=\"Microsoft.AspNetCore.Mvc.Testing\" Version=\"8.0.8\" />\n    <PackageVersion Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.11.1\" />\n    <PackageVersion Include=\"xunit\" Version=\"2.9.2\" />\n    <PackageVersion Include=\"xunit.runner.visualstudio\" Version=\"3.0.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>\n    </PackageVersion>\n    <PackageVersion Include=\"xunit.runner.console\" Version=\"2.9.0\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>\n    </PackageVersion>\n    <PackageVersion Include=\"MSTest.TestAdapter\" Version=\"3.5.2\" />\n    <PackageVersion Include=\"MSTest.TestFramework\" Version=\"3.6.2\" />\n    <PackageVersion Include=\"coverlet.collector\" Version=\"6.0.2\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\n\n\nCopyright (c) .NET Foundation and Contributors\n\n\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n\nof this software and associated documentation files (the \"Software\"), to deal\n\nin the Software without restriction, including without limitation the rights\n\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n\ncopies of the Software, and to permit persons to whom the Software is\n\nfurnished to do so, subject to the following conditions:\n\n\n\nThe above copyright notice and this permission notice shall be included in all\n\ncopies or substantial portions of the Software.\n\n\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n\nSOFTWARE.\n"
  },
  {
    "path": "MTT-Notes.md",
    "content": "# App modified\nFor architecture simplicity (and problems related to deploy SQL on free subscriptions), inmemory database option is selected.\n\n\nBy default, the project uses a real database. If you want an in memory database, /src/Infrastructure/Dependencies.cs line 13:\n\n```\nvar useOnlyInMemoryDatabase = true;\n```\n\n# ADO Pipelines\nLocated under the folder \".ado\", you can find the following YAML pipelines:\n\n- **main-ci.yml** : Dotnet  CI pipeline. Build + Test + Publish. Upload artifacts for website and webapp bicep file.\n- **main-ci-containers-compose.yml** : It first creates an ACR and build/push docker containers based on docker compose. WARNING: you need to have an existing RG.\n- **main-cd-web-aci.yml** : deploys container image on ACI using Bicep template. WARNING: you need to provide ACR username and password (using variable group / key vault).Contributor role is for management plane operations to manage key vaults. It does not allow access to keys, secrets and certificates. **READ/LIST access needed for used Service Principal in ADO VG**. TODO: using managed identity. \n- **main-cd-web-webapp.yml** : triggered by **main-ci.yml** and deploys app artifacts created by **main-ci.yml** to Azure Web App (linux). It uses bicep to create App Service Plan and WebApp, and publishes code to the webapp.\n\n# Deployment options\n\n## Azure App Service single container for only WEB (TODO)\nDeployment of the Web solution works with the following settings:\n![webapp container web](.images/webapp-container-web.png)\n"
  },
  {
    "path": "README.md",
    "content": "[![Build Status](https://github.com/dotnet-architecture/eShopOnWeb/workflows/eShopOnWeb%20Build%20and%20Test/badge.svg)](https://github.com/dotnet-architecture/eShopOnWeb/actions)\n\n# Microsoft eShopOnWeb ASP.NET Core Reference Application\n\nSample ASP.NET Core reference application, powered by Microsoft, demonstrating a single-process (monolithic) application architecture and deployment model. If you're new to .NET development, read the [Getting Started for Beginners](https://github.com/dotnet-architecture/eShopOnWeb/wiki/Getting-Started-for-Beginners) guide.\n\nA list of Frequently Asked Questions about this repository can be found [here](https://github.com/dotnet-architecture/eShopOnWeb/wiki/Frequently-Asked-Questions).\n\n## Overview Video\n\n[Steve \"ardalis\" Smith](https://twitter.com/ardalis) recorded [a live stream providing an overview of the eShopOnWeb reference app](https://www.youtube.com/watch?v=vRZ8ucGac8M&ab_channel=Ardalis) in October 2020. \n\n## eBook\n\nThis reference application is meant to support the free .PDF download ebook: [Architecting Modern Web Applications with ASP.NET Core and Azure](https://aka.ms/webappebook), updated to **ASP.NET Core 8.0**. [Also available in ePub/mobi formats](https://dotnet.microsoft.com/learn/web/aspnet-architecture).\n\nYou can also read the book in online pages at the .NET docs here: \nhttps://docs.microsoft.com/dotnet/architecture/modern-web-apps-azure/\n\n[<img src=\"https://dotnet.microsoft.com/blob-assets/images/e-books/aspnet.png\" height=\"300\" />](https://dotnet.microsoft.com/learn/web/aspnet-architecture)\n\nThe **eShopOnWeb** sample is related to the [eShopOnContainers](https://github.com/dotnet/eShopOnContainers) sample application which, in that case, focuses on a microservices/containers-based application architecture. However, **eShopOnWeb** is much simpler in regards to its current functionality and focuses on traditional Web Application Development with a single deployment.\n\nThe goal for this sample is to demonstrate some of the principles and patterns described in the [eBook](https://aka.ms/webappebook). It is not meant to be an eCommerce reference application, and as such it does not implement many features that would be obvious and/or essential to a real eCommerce application.\n\n> ### VERSIONS\n> #### The `main` branch is currently running ASP.NET Core 8.0.\n> #### Older versions are tagged.\n\n## Topics (eBook TOC)\n\n- Introduction\n- Characteristics of Modern Web Applications\n- Choosing Between Traditional Web Apps and SPAs\n- Architectural Principles\n- Common Web Application Architectures\n- Common Client Side Technologies\n- Developing ASP.NET Core MVC Apps\n- Working with Data in ASP.NET Core Apps\n- Testing ASP.NET Core MVC Apps\n- Development Process for Azure-Hosted ASP.NET Core Apps\n- Azure Hosting Recommendations for ASP.NET Core Web Apps\n\n## Running the sample using Azd template\n\nThe store's home page should look like this:\n\n![eShopOnWeb home page screenshot](https://user-images.githubusercontent.com/782127/88414268-92d83a00-cdaa-11ea-9b4c-db67d95be039.png)\n\nThe Azure Developer CLI (`azd`) is a developer-centric command-line interface (CLI) tool for creating Azure applications.\n\nYou need to install it before running and deploying with Azure Developer CLI.\n\n### Windows\n\n```powershell\npowershell -ex AllSigned -c \"Invoke-RestMethod 'https://aka.ms/install-azd.ps1' | Invoke-Expression\"\n```\n\n### Linux/MacOS\n\n```\ncurl -fsSL https://aka.ms/install-azd.sh | bash\n```\n\nAnd you can also install with package managers, like winget, choco, and brew. For more details, you can follow the documentation: https://aka.ms/azure-dev/install.\n\nAfter logging in with the following command, you will be able to use the azd cli to quickly provision and deploy the application.\n\n```\nazd auth login\n```\n\nThen, execute the `azd init` command to initialize the environment.\n```\nazd init -t dotnet-architecture/eShopOnWeb \n```\n\nRun `azd up` to provision all the resources to Azure and deploy the code to those resources.\n```\nazd up \n```\n\nAccording to the prompt, enter an `env name`, and select `subscription` and `location`, these are the necessary parameters when you create resources. Wait a moment for the resource deployment to complete, click the web endpoint and you will see the home page.\n\n**Notes:**\n1. Considering security, we store its related data (id, password) in the **Azure Key Vault** when we create the database, and obtain it from the Key Vault when we use it. This is different from directly deploying applications locally.\n2. The resource group name created in azure portal will be **rg-{env name}**.\n\nYou can also run the sample directly locally (See below).\n\n## Running the sample locally\nMost of the site's functionality works with just the web application running. However, the site's Admin page relies on Blazor WebAssembly running in the browser, and it must communicate with the server using the site's PublicApi web application. You'll need to also run this project. You can configure Visual Studio to start multiple projects, or just go to the PublicApi folder in a terminal window and run `dotnet run` from there. After that from the Web folder you should run `dotnet run --launch-profile Web`. Now you should be able to browse to `https://localhost:5001/`. The admin part in Blazor is accessible to `https://localhost:5001/admin`  \n\nNote that if you use this approach, you'll need to stop the application manually in order to build the solution (otherwise you'll get file locking errors).\n\nAfter cloning or downloading the sample you must setup your database. \nTo use the sample with a persistent database, you will need to run its Entity Framework Core migrations before you will be able to run the app.\n\nYou can also run the samples in Docker (see below).\n\n### Configuring the sample to use SQL Server\n\n1. By default, the project uses a real database. If you want an in memory database, you can add in the `appsettings.json` file in the Web folder\n\n    ```json\n   {\n       \"UseOnlyInMemoryDatabase\": true\n   }\n    ```\n\n1. Ensure your connection strings in `appsettings.json` point to a local SQL Server instance.\n1. Ensure the tool EF was already installed. You can find some help [here](https://docs.microsoft.com/ef/core/miscellaneous/cli/dotnet)\n\n    ```\n    dotnet tool update --global dotnet-ef\n    ```\n\n1. Open a command prompt in the Web folder and execute the following commands:\n\n    ```\n    dotnet restore\n    dotnet tool restore\n    dotnet ef database update -c catalogcontext -p ../Infrastructure/Infrastructure.csproj -s Web.csproj\n    dotnet ef database update -c appidentitydbcontext -p ../Infrastructure/Infrastructure.csproj -s Web.csproj\n    ```\n\n    These commands will create two separate databases, one for the store's catalog data and shopping cart information, and one for the app's user credentials and identity data.\n\n1. Run the application.\n\n    The first time you run the application, it will seed both databases with data such that you should see products in the store, and you should be able to log in using the demouser@microsoft.com account.\n\n    Note: If you need to create migrations, you can use these commands:\n\n    ```\n    -- create migration (from Web folder CLI)\n    dotnet ef migrations add InitialModel --context catalogcontext -p ../Infrastructure/Infrastructure.csproj -s Web.csproj -o Data/Migrations\n\n    dotnet ef migrations add InitialIdentityModel --context appidentitydbcontext -p ../Infrastructure/Infrastructure.csproj -s Web.csproj -o Identity/Migrations\n    ```\n\n## Running the sample in the dev container\n\nThis project includes a `.devcontainer` folder with a [dev container configuration](https://containers.dev/), which lets you use a container as a full-featured dev environment.\n\nYou can use the dev container to build and run the app without needing to install any of its tools locally! You can work in GitHub Codespaces or the VS Code Dev Containers extension.\n\nLearn more about using the dev container in its [readme](/.devcontainer/devcontainerreadme.md).\n\n## Running the sample using Docker\n\nYou can run the Web sample by running these commands from the root folder (where the .sln file is located):\n\n```\ndocker-compose build\ndocker-compose up\n```\n\nYou should be able to make requests to localhost:5106 for the Web project, and localhost:5200 for the Public API project once these commands complete. If you have any problems, especially with login, try from a new guest or incognito browser instance.\n\nYou can also run the applications by using the instructions located in their `Dockerfile` file in the root of each project. Again, run these commands from the root of the solution (where the .sln file is located).\n\n## Community Extensions\n\nWe have some great contributions from the community, and while these aren't maintained by Microsoft we still want to highlight them.\n\n[eShopOnWeb VB.NET](https://github.com/VBAndCs/eShopOnWeb_VB.NET) by Mohammad Hamdy Ghanem\n"
  },
  {
    "path": "azure.yaml",
    "content": "name: eShopOnWeb\nservices:\n  web:\n    project: ./src/Web\n    language: csharp\n    host: appservice"
  },
  {
    "path": "docker-compose-webapp.yml",
    "content": "version: '3.4'\n\nservices:\n  eshopwebmvc:\n    image: __acr-login-server__/eshopwebmvc\n    build:\n      context: .\n      dockerfile: src/Web/Dockerfile\n    depends_on:\n      - \"sqlserver\"\n  eshoppublicapi:\n    image: __acr-login-server__/eshoppublicapi\n    build:\n      context: .\n      dockerfile: src/PublicApi/Dockerfile\n    depends_on:\n      - \"sqlserver\"\n  sqlserver:\n    image: mcr.microsoft.com/azure-sql-edge\n    ports:\n      - \"1433:1433\"\n    environment:\n      - SA_PASSWORD=@someThingComplicated1234\n      - ACCEPT_EULA=Y"
  },
  {
    "path": "docker-compose.dcproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" Sdk=\"Microsoft.Docker.Sdk\" DefaultTargets=\"Build\">\n  <PropertyGroup Label=\"Globals\">\n    <ProjectVersion>2.1</ProjectVersion>\n    <DockerTargetOS>Linux</DockerTargetOS>\n    <ProjectGuid>{1FCBE191-34FE-4B2E-8915-CA81553958AD}</ProjectGuid>\n    <DockerLaunchBrowser>True</DockerLaunchBrowser>\n    <DockerServiceUrl>{Scheme}://localhost:{ServicePort}</DockerServiceUrl>\n    <DockerServiceName>eshopwebmvc</DockerServiceName>\n  </PropertyGroup>\n  <ItemGroup>\n    <None Include=\"docker-compose.override.yml\">\n      <DependentUpon>docker-compose.yml</DependentUpon>\n    </None>\n    <None Include=\"docker-compose.yml\" />\n    <None Include=\".dockerignore\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "docker-compose.override.yml",
    "content": "version: '3.4'\nservices:\n eshopwebmvc:\n   environment:\n     - ASPNETCORE_ENVIRONMENT=Docker\n     - ASPNETCORE_URLS=http://+:8080\n   ports:\n     - \"5106:8080\"\n   volumes:\n     - ~/.aspnet/https:/root/.aspnet/https:ro\n     - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro\n eshoppublicapi:\n   environment:\n     - ASPNETCORE_ENVIRONMENT=Docker\n     - ASPNETCORE_URLS=http://+:8080\n   ports:\n     - \"5200:8080\"\n   volumes:\n     - ~/.aspnet/https:/root/.aspnet/https:ro\n     - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  eshopwebmvc:\n    image: ${DOCKER_REGISTRY-}eshopwebmvc\n    build:\n      context: .\n      dockerfile: src/Web/Dockerfile\n    depends_on:\n      - \"sqlserver\"\n  eshoppublicapi:\n    image: ${DOCKER_REGISTRY-}eshoppublicapi\n    build:\n      context: .\n      dockerfile: src/PublicApi/Dockerfile\n    depends_on:\n      - \"sqlserver\"\n  sqlserver:\n    image: mcr.microsoft.com/azure-sql-edge\n    ports:\n      - \"1433:1433\"\n    environment:\n      - SA_PASSWORD=@someThingComplicated1234\n      - ACCEPT_EULA=Y\n\n"
  },
  {
    "path": "eShopOnWeb.sln",
    "content": "Microsoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.0.31903.59\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"src\", \"src\", \"{419A6ACE-0419-4315-A6FB-B0E63D39432E}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Web\", \"src\\Web\\Web.csproj\", \"{227CF035-29B0-448D-97E4-944F9EA850E5}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Infrastructure\", \"src\\Infrastructure\\Infrastructure.csproj\", \"{7C461394-ABDC-43CD-A798-71249C58BA67}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"ApplicationCore\", \"src\\ApplicationCore\\ApplicationCore.csproj\", \"{7FED7440-2311-4D1E-958B-3E887C585CD2}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"tests\", \"tests\", \"{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}\"\n\tProjectSection(SolutionItems) = preProject\n\t\tCodeCoverage.runsettings = CodeCoverage.runsettings\n\tEndProjectSection\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"UnitTests\", \"tests\\UnitTests\\UnitTests.csproj\", \"{EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"IntegrationTests\", \"tests\\IntegrationTests\\IntegrationTests.csproj\", \"{0F576306-7E2D-49B7-87B1-EB5D94CFD5FC}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"FunctionalTests\", \"tests\\FunctionalTests\\FunctionalTests.csproj\", \"{7EFB5482-F942-4C3D-94B0-9B70596E6D0A}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Solution Items\", \"Solution Items\", \"{0BD72BEA-EF42-4B72-8B69-12A39EC76FBA}\"\n\tProjectSection(SolutionItems) = preProject\n\t\t.editorconfig = .editorconfig\n\t\tDirectory.Packages.props = Directory.Packages.props\n\t\tdocker-compose.override.yml = docker-compose.override.yml\n\t\tdocker-compose.yml = docker-compose.yml\n\t\t.github\\workflows\\dotnetcore.yml = .github\\workflows\\dotnetcore.yml\n\t\tREADME.md = README.md\n\tEndProjectSection\nEndProject\nProject(\"{E53339B2-1760-4266-BCC7-CA923CBCF16C}\") = \"docker-compose\", \"docker-compose.dcproj\", \"{1FCBE191-34FE-4B2E-8915-CA81553958AD}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"PublicApi\", \"src\\PublicApi\\PublicApi.csproj\", \"{B5E4F33C-4667-4A55-AF6A-740F84C4CF3A}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"BlazorAdmin\", \"src\\BlazorAdmin\\BlazorAdmin.csproj\", \"{71368733-80A4-4869-B215-3A7001878577}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"BlazorShared\", \"src\\BlazorShared\\BlazorShared.csproj\", \"{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"PublicApiIntegrationTests\", \"tests\\PublicApiIntegrationTests\\PublicApiIntegrationTests.csproj\", \"{D53EF010-8F8C-4337-A059-456E19D8AE63}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{0F576306-7E2D-49B7-87B1-EB5D94CFD5FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{0F576306-7E2D-49B7-87B1-EB5D94CFD5FC}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{0F576306-7E2D-49B7-87B1-EB5D94CFD5FC}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{0F576306-7E2D-49B7-87B1-EB5D94CFD5FC}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{7EFB5482-F942-4C3D-94B0-9B70596E6D0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{7EFB5482-F942-4C3D-94B0-9B70596E6D0A}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{7EFB5482-F942-4C3D-94B0-9B70596E6D0A}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{7EFB5482-F942-4C3D-94B0-9B70596E6D0A}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{1FCBE191-34FE-4B2E-8915-CA81553958AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{1FCBE191-34FE-4B2E-8915-CA81553958AD}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{1FCBE191-34FE-4B2E-8915-CA81553958AD}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{1FCBE191-34FE-4B2E-8915-CA81553958AD}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{B5E4F33C-4667-4A55-AF6A-740F84C4CF3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{B5E4F33C-4667-4A55-AF6A-740F84C4CF3A}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{B5E4F33C-4667-4A55-AF6A-740F84C4CF3A}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{B5E4F33C-4667-4A55-AF6A-740F84C4CF3A}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{71368733-80A4-4869-B215-3A7001878577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{71368733-80A4-4869-B215-3A7001878577}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{71368733-80A4-4869-B215-3A7001878577}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{71368733-80A4-4869-B215-3A7001878577}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{D53EF010-8F8C-4337-A059-456E19D8AE63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{D53EF010-8F8C-4337-A059-456E19D8AE63}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{D53EF010-8F8C-4337-A059-456E19D8AE63}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{D53EF010-8F8C-4337-A059-456E19D8AE63}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}\n\t\t{7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}\n\t\t{7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}\n\t\t{EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF}\n\t\t{0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF}\n\t\t{7EFB5482-F942-4C3D-94B0-9B70596E6D0A} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF}\n\t\t{B5E4F33C-4667-4A55-AF6A-740F84C4CF3A} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}\n\t\t{71368733-80A4-4869-B215-3A7001878577} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}\n\t\t{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}\n\t\t{D53EF010-8F8C-4337-A059-456E19D8AE63} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF}\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {49813262-5DA3-4D61-ABD3-493C74CE8C2B}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "global.json",
    "content": "{\n  \"sdk\": {\n    \"version\": \"8.0.x\",\n    \"rollForward\": \"latestFeature\"\n  }\n}\n"
  },
  {
    "path": "infra/abbreviations.json",
    "content": "{\n    \"analysisServicesServers\": \"as\",\n    \"apiManagementService\": \"apim-\",\n    \"appConfigurationConfigurationStores\": \"appcs-\",\n    \"appManagedEnvironments\": \"cae-\",\n    \"appContainerApps\": \"ca-\",\n    \"authorizationPolicyDefinitions\": \"policy-\",\n    \"automationAutomationAccounts\": \"aa-\",\n    \"blueprintBlueprints\": \"bp-\",\n    \"blueprintBlueprintsArtifacts\": \"bpa-\",\n    \"cacheRedis\": \"redis-\",\n    \"cdnProfiles\": \"cdnp-\",\n    \"cdnProfilesEndpoints\": \"cdne-\",\n    \"cognitiveServicesAccounts\": \"cog-\",\n    \"cognitiveServicesFormRecognizer\": \"cog-fr-\",\n    \"cognitiveServicesTextAnalytics\": \"cog-ta-\",\n    \"computeAvailabilitySets\": \"avail-\",\n    \"computeCloudServices\": \"cld-\",\n    \"computeDiskEncryptionSets\": \"des\",\n    \"computeDisks\": \"disk\",\n    \"computeDisksOs\": \"osdisk\",\n    \"computeGalleries\": \"gal\",\n    \"computeSnapshots\": \"snap-\",\n    \"computeVirtualMachines\": \"vm\",\n    \"computeVirtualMachineScaleSets\": \"vmss-\",\n    \"containerInstanceContainerGroups\": \"ci\",\n    \"containerRegistryRegistries\": \"cr\",\n    \"containerServiceManagedClusters\": \"aks-\",\n    \"databricksWorkspaces\": \"dbw-\",\n    \"dataFactoryFactories\": \"adf-\",\n    \"dataLakeAnalyticsAccounts\": \"dla\",\n    \"dataLakeStoreAccounts\": \"dls\",\n    \"dataMigrationServices\": \"dms-\",\n    \"dBforMySQLServers\": \"mysql-\",\n    \"dBforPostgreSQLServers\": \"psql-\",\n    \"devicesIotHubs\": \"iot-\",\n    \"devicesProvisioningServices\": \"provs-\",\n    \"devicesProvisioningServicesCertificates\": \"pcert-\",\n    \"documentDBDatabaseAccounts\": \"cosmos-\",\n    \"eventGridDomains\": \"evgd-\",\n    \"eventGridDomainsTopics\": \"evgt-\",\n    \"eventGridEventSubscriptions\": \"evgs-\",\n    \"eventHubNamespaces\": \"evhns-\",\n    \"eventHubNamespacesEventHubs\": \"evh-\",\n    \"hdInsightClustersHadoop\": \"hadoop-\",\n    \"hdInsightClustersHbase\": \"hbase-\",\n    \"hdInsightClustersKafka\": \"kafka-\",\n    \"hdInsightClustersMl\": \"mls-\",\n    \"hdInsightClustersSpark\": \"spark-\",\n    \"hdInsightClustersStorm\": \"storm-\",\n    \"hybridComputeMachines\": \"arcs-\",\n    \"insightsActionGroups\": \"ag-\",\n    \"insightsComponents\": \"appi-\",\n    \"keyVaultVaults\": \"kv-\",\n    \"kubernetesConnectedClusters\": \"arck\",\n    \"kustoClusters\": \"dec\",\n    \"kustoClustersDatabases\": \"dedb\",\n    \"logicIntegrationAccounts\": \"ia-\",\n    \"logicWorkflows\": \"logic-\",\n    \"machineLearningServicesWorkspaces\": \"mlw-\",\n    \"managedIdentityUserAssignedIdentities\": \"id-\",\n    \"managementManagementGroups\": \"mg-\",\n    \"migrateAssessmentProjects\": \"migr-\",\n    \"networkApplicationGateways\": \"agw-\",\n    \"networkApplicationSecurityGroups\": \"asg-\",\n    \"networkAzureFirewalls\": \"afw-\",\n    \"networkBastionHosts\": \"bas-\",\n    \"networkConnections\": \"con-\",\n    \"networkDnsZones\": \"dnsz-\",\n    \"networkExpressRouteCircuits\": \"erc-\",\n    \"networkFirewallPolicies\": \"afwp-\",\n    \"networkFirewallPoliciesWebApplication\": \"waf\",\n    \"networkFirewallPoliciesRuleGroups\": \"wafrg\",\n    \"networkFrontDoors\": \"fd-\",\n    \"networkFrontdoorWebApplicationFirewallPolicies\": \"fdfp-\",\n    \"networkLoadBalancersExternal\": \"lbe-\",\n    \"networkLoadBalancersInternal\": \"lbi-\",\n    \"networkLoadBalancersInboundNatRules\": \"rule-\",\n    \"networkLocalNetworkGateways\": \"lgw-\",\n    \"networkNatGateways\": \"ng-\",\n    \"networkNetworkInterfaces\": \"nic-\",\n    \"networkNetworkSecurityGroups\": \"nsg-\",\n    \"networkNetworkSecurityGroupsSecurityRules\": \"nsgsr-\",\n    \"networkNetworkWatchers\": \"nw-\",\n    \"networkPrivateDnsZones\": \"pdnsz-\",\n    \"networkPrivateLinkServices\": \"pl-\",\n    \"networkPublicIPAddresses\": \"pip-\",\n    \"networkPublicIPPrefixes\": \"ippre-\",\n    \"networkRouteFilters\": \"rf-\",\n    \"networkRouteTables\": \"rt-\",\n    \"networkRouteTablesRoutes\": \"udr-\",\n    \"networkTrafficManagerProfiles\": \"traf-\",\n    \"networkVirtualNetworkGateways\": \"vgw-\",\n    \"networkVirtualNetworks\": \"vnet-\",\n    \"networkVirtualNetworksSubnets\": \"snet-\",\n    \"networkVirtualNetworksVirtualNetworkPeerings\": \"peer-\",\n    \"networkVirtualWans\": \"vwan-\",\n    \"networkVpnGateways\": \"vpng-\",\n    \"networkVpnGatewaysVpnConnections\": \"vcn-\",\n    \"networkVpnGatewaysVpnSites\": \"vst-\",\n    \"notificationHubsNamespaces\": \"ntfns-\",\n    \"notificationHubsNamespacesNotificationHubs\": \"ntf-\",\n    \"operationalInsightsWorkspaces\": \"log-\",\n    \"portalDashboards\": \"dash-\",\n    \"powerBIDedicatedCapacities\": \"pbi-\",\n    \"purviewAccounts\": \"pview-\",\n    \"recoveryServicesVaults\": \"rsv-\",\n    \"resourcesResourceGroups\": \"rg-\",\n    \"searchSearchServices\": \"srch-\",\n    \"serviceBusNamespaces\": \"sb-\",\n    \"serviceBusNamespacesQueues\": \"sbq-\",\n    \"serviceBusNamespacesTopics\": \"sbt-\",\n    \"serviceEndPointPolicies\": \"se-\",\n    \"serviceFabricClusters\": \"sf-\",\n    \"signalRServiceSignalR\": \"sigr\",\n    \"sqlManagedInstances\": \"sqlmi-\",\n    \"sqlServers\": \"sql-\",\n    \"sqlServersDataWarehouse\": \"sqldw-\",\n    \"sqlServersDatabases\": \"sqldb-\",\n    \"sqlServersDatabasesStretch\": \"sqlstrdb-\",\n    \"storageStorageAccounts\": \"st\",\n    \"storageStorageAccountsVm\": \"stvm\",\n    \"storSimpleManagers\": \"ssimp\",\n    \"streamAnalyticsCluster\": \"asa-\",\n    \"synapseWorkspaces\": \"syn\",\n    \"synapseWorkspacesAnalyticsWorkspaces\": \"synw\",\n    \"synapseWorkspacesSqlPoolsDedicated\": \"syndp\",\n    \"synapseWorkspacesSqlPoolsSpark\": \"synsp\",\n    \"timeSeriesInsightsEnvironments\": \"tsi-\",\n    \"webServerFarms\": \"plan-\",\n    \"webSitesAppService\": \"app-\",\n    \"webSitesAppServiceEnvironment\": \"ase-\",\n    \"webSitesFunctions\": \"func-\",\n    \"webStaticSites\": \"stapp-\"\n}"
  },
  {
    "path": "infra/aci.bicep",
    "content": "@description('Name for the container group')\nparam name string = 'eshopcontainer'\n\n@description('Location for all resources.')\nparam location string = resourceGroup().location\n\n@description('Container image to deploy. Should be of the form repoName/imagename:tag for images stored in public Docker Hub, or a fully qualified URI for other registries. Images from private registries require additional registry credentials.')\nparam image string = 'mcr.microsoft.com/azuredocs/aci-helloworld'\n\n@description('Port to open on the container and the public IP address.')\nparam port int = 5106\n\n@description('The number of CPU cores to allocate to the container.')\nparam cpuCores int = 1\n\n@description('The amount of memory to allocate to the container in gigabytes.')\nparam memoryInGb int = 2\n\n@description('The behavior of Azure runtime if container has stopped.')\n@allowed([\n  'Always'\n  'Never'\n  'OnFailure'\n])\nparam restartPolicy string = 'Always'\n\n@secure()\nparam password string\n\nparam username string\n\nparam server string\n\nresource containerGroup 'Microsoft.ContainerInstance/containerGroups@2021-09-01' = {\n  name: name\n  location: location\n  properties: {\n    containers: [\n      {\n        name: name\n        properties: {\n          image: image\n          ports: [\n            {\n              port: port\n              protocol: 'TCP'\n\n            }\n            {\n              port: 80\n              protocol: 'TCP'\n            }\n          ]\n          resources: {\n            requests: {\n              cpu: cpuCores\n              memoryInGB: memoryInGb\n            }\n          }\n          environmentVariables: [\n            {\n              name: 'ASPNETCORE_ENVIRONMENT'\n              value: 'Docker'\n            }\n            {\n              name: 'UseOnlyInMemoryDatabase'\n              value: 'true'\n            }\n            {\n              name: 'ASPNETCORE_HTTP_PORTS'\n              value: '80'\n            }\n          ]\n        }\n      }\n    ]\n    osType: 'Linux'\n    restartPolicy: restartPolicy\n    ipAddress: {\n      type: 'Public'\n      ports: [\n        {\n          port: port\n          protocol: 'TCP'\n        }\n        {\n          port: 80\n          protocol: 'TCP'\n        }\n      ]\n    }\n    imageRegistryCredentials: [\n      {\n        password: password\n        server: server\n        username: username\n      }\n    ]\n  }\n}\n\noutput containerIPv4Address string = containerGroup.properties.ipAddress.ip\n"
  },
  {
    "path": "infra/acr.bicep",
    "content": "@description('Generate a Suffix based on the Resource Group ID')\nparam suffix string = uniqueString(resourceGroup().id)\n\n@description('Use the Resource Group Location')\nparam location string = resourceGroup().location\n\nresource acr 'Microsoft.ContainerRegistry/registries@2021-09-01' = {\n  name: 'cr${suffix}'\n  location: location\n  sku: {\n    name: 'Basic'\n  }\n  properties: {\n    adminUserEnabled: false\n  }\n}\n\n@description('Output the login server property for later use')\noutput acrLoginServer string = acr.properties.loginServer\n"
  },
  {
    "path": "infra/core/database/sqlserver/sqlserver.bicep",
    "content": "param name string\nparam location string = resourceGroup().location\nparam tags object = {}\n\nparam appUser string = 'appUser'\nparam databaseName string\nparam keyVaultName string\nparam sqlAdmin string = 'sqlAdmin'\nparam connectionStringKey string = 'AZURE-SQL-CONNECTION-STRING'\n\n@secure()\nparam sqlAdminPassword string\n@secure()\nparam appUserPassword string\n\nresource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {\n  name: name\n  location: location\n  tags: tags\n  properties: {\n    version: '12.0'\n    minimalTlsVersion: '1.2'\n    publicNetworkAccess: 'Enabled'\n    administratorLogin: sqlAdmin\n    administratorLoginPassword: sqlAdminPassword\n  }\n\n  resource database 'databases' = {\n    name: databaseName\n    location: location\n  }\n\n  resource firewall 'firewallRules' = {\n    name: 'Azure Services'\n    properties: {\n      // Allow all clients\n      // Note: range [0.0.0.0-0.0.0.0] means \"allow all Azure-hosted clients only\".\n      // This is not sufficient, because we also want to allow direct access from developer machine, for debugging purposes.\n      startIpAddress: '0.0.0.1'\n      endIpAddress: '255.255.255.254'\n    }\n  }\n}\n\nresource sqlDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {\n  name: '${name}-deployment-script'\n  location: location\n  kind: 'AzureCLI'\n  properties: {\n    azCliVersion: '2.37.0'\n    retentionInterval: 'PT1H' // Retain the script resource for 1 hour after it ends running\n    timeout: 'PT5M' // Five minutes\n    cleanupPreference: 'OnSuccess'\n    environmentVariables: [\n      {\n        name: 'APPUSERNAME'\n        value: appUser\n      }\n      {\n        name: 'APPUSERPASSWORD'\n        secureValue: appUserPassword\n      }\n      {\n        name: 'DBNAME'\n        value: databaseName\n      }\n      {\n        name: 'DBSERVER'\n        value: sqlServer.properties.fullyQualifiedDomainName\n      }\n      {\n        name: 'SQLCMDPASSWORD'\n        secureValue: sqlAdminPassword\n      }\n      {\n        name: 'SQLADMIN'\n        value: sqlAdmin\n      }\n    ]\n\n    scriptContent: '''\nwget https://github.com/microsoft/go-sqlcmd/releases/download/v0.8.1/sqlcmd-v0.8.1-linux-x64.tar.bz2\ntar x -f sqlcmd-v0.8.1-linux-x64.tar.bz2 -C .\n\ncat <<SCRIPT_END > ./initDb.sql\ndrop user ${APPUSERNAME}\ngo\ncreate user ${APPUSERNAME} with password = '${APPUSERPASSWORD}'\ngo\nalter role db_owner add member ${APPUSERNAME}\ngo\nSCRIPT_END\n\n./sqlcmd -S ${DBSERVER} -d ${DBNAME} -U ${SQLADMIN} -i ./initDb.sql\n    '''\n  }\n}\n\nresource sqlAdminPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {\n  parent: keyVault\n  name: 'sqlAdminPassword'\n  properties: {\n    value: sqlAdminPassword\n  }\n}\n\nresource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {\n  parent: keyVault\n  name: 'appUserPassword'\n  properties: {\n    value: appUserPassword\n  }\n}\n\nresource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {\n  parent: keyVault\n  name: connectionStringKey\n  properties: {\n    value: '${connectionString}; Password=${appUserPassword}'\n  }\n}\n\nresource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {\n  name: keyVaultName\n}\n\nvar connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}'\noutput connectionStringKey string = connectionStringKey\noutput databaseName string = sqlServer::database.name\n"
  },
  {
    "path": "infra/core/host/appservice.bicep",
    "content": "param name string\nparam location string = resourceGroup().location\nparam tags object = {}\n\n// Reference Properties\nparam applicationInsightsName string = ''\nparam appServicePlanId string\nparam keyVaultName string = ''\nparam managedIdentity bool = !empty(keyVaultName)\n\n// Runtime Properties\n@allowed([\n  'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom'\n])\nparam runtimeName string\nparam runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}'\nparam runtimeVersion string\n\n// Microsoft.Web/sites Properties\nparam kind string = 'app,linux'\n\n// Microsoft.Web/sites/config\nparam allowedOrigins array = []\nparam alwaysOn bool = true\nparam appCommandLine string = ''\nparam appSettings object = {}\nparam clientAffinityEnabled bool = false\nparam enableOryxBuild bool = contains(kind, 'linux')\nparam functionAppScaleLimit int = -1\nparam linuxFxVersion string = runtimeNameAndVersion\nparam minimumElasticInstanceCount int = -1\nparam numberOfWorkers int = -1\nparam scmDoBuildDuringDeployment bool = false\nparam use32BitWorkerProcess bool = false\nparam ftpsState string = 'FtpsOnly'\nparam healthCheckPath string = ''\n\nresource appService 'Microsoft.Web/sites@2022-03-01' = {\n  name: name\n  location: location\n  tags: tags\n  kind: kind\n  properties: {\n    serverFarmId: appServicePlanId\n    siteConfig: {\n      linuxFxVersion: linuxFxVersion\n      alwaysOn: alwaysOn\n      ftpsState: ftpsState\n      minTlsVersion: '1.2'\n      appCommandLine: appCommandLine\n      numberOfWorkers: numberOfWorkers != -1 ? numberOfWorkers : null\n      minimumElasticInstanceCount: minimumElasticInstanceCount != -1 ? minimumElasticInstanceCount : null\n      use32BitWorkerProcess: use32BitWorkerProcess\n      functionAppScaleLimit: functionAppScaleLimit != -1 ? functionAppScaleLimit : null\n      healthCheckPath: healthCheckPath\n      cors: {\n        allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins)\n      }\n    }\n    clientAffinityEnabled: clientAffinityEnabled\n    httpsOnly: true\n  }\n\n  identity: { type: managedIdentity ? 'SystemAssigned' : 'None' }\n\n  resource configAppSettings 'config' = {\n    name: 'appsettings'\n    properties: union(appSettings,\n      {\n        SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment)\n        ENABLE_ORYX_BUILD: string(enableOryxBuild)\n      },\n      !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {},\n      !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {})\n  }\n\n  resource configLogs 'config' = {\n    name: 'logs'\n    properties: {\n      applicationLogs: { fileSystem: { level: 'Verbose' } }\n      detailedErrorMessages: { enabled: true }\n      failedRequestsTracing: { enabled: true }\n      httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } }\n    }\n    dependsOn: [\n      configAppSettings\n    ]\n  }\n}\n\nresource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) {\n  name: keyVaultName\n}\n\nresource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) {\n  name: applicationInsightsName\n}\n\noutput identityPrincipalId string = managedIdentity ? appService.identity.principalId : ''\noutput name string = appService.name\noutput uri string = 'https://${appService.properties.defaultHostName}'\n"
  },
  {
    "path": "infra/core/host/appserviceplan.bicep",
    "content": "param name string\nparam location string = resourceGroup().location\nparam tags object = {}\n\nparam kind string = ''\nparam reserved bool = true\nparam sku object\n\nresource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {\n  name: name\n  location: location\n  tags: tags\n  sku: sku\n  kind: kind\n  properties: {\n    reserved: reserved\n  }\n}\n\noutput id string = appServicePlan.id\n"
  },
  {
    "path": "infra/core/security/keyvault-access.bicep",
    "content": "param name string = 'add'\n\nparam keyVaultName string\nparam permissions object = { secrets: [ 'get', 'list' ] }\nparam principalId string\n\nresource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = {\n  parent: keyVault\n  name: name\n  properties: {\n    accessPolicies: [ {\n        objectId: principalId\n        tenantId: subscription().tenantId\n        permissions: permissions\n      } ]\n  }\n}\n\nresource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {\n  name: keyVaultName\n}\n"
  },
  {
    "path": "infra/core/security/keyvault.bicep",
    "content": "param name string\nparam location string = resourceGroup().location\nparam tags object = {}\n\nparam principalId string = ''\n\nresource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = {\n  name: name\n  location: location\n  tags: tags\n  properties: {\n    tenantId: subscription().tenantId\n    sku: { family: 'A', name: 'standard' }\n    accessPolicies: !empty(principalId) ? [\n      {\n        objectId: principalId\n        permissions: { secrets: [ 'get', 'list' ] }\n        tenantId: subscription().tenantId\n      }\n    ] : []\n  }\n}\n\noutput endpoint string = keyVault.properties.vaultUri\noutput name string = keyVault.name\n"
  },
  {
    "path": "infra/main.bicep",
    "content": "targetScope = 'subscription'\n\n@minLength(1)\n@maxLength(64)\n@description('Name of the the environment which is used to generate a short unique hash used in all resources.')\nparam environmentName string\n\n@minLength(1)\n@description('Primary location for all resources')\nparam location string\n\n// Optional parameters to override the default azd resource naming conventions. Update the main.parameters.json file to provide values. e.g.,:\n// \"resourceGroupName\": {\n//      \"value\": \"myGroupName\"\n// }\nparam resourceGroupName string = ''\nparam webServiceName string = ''\nparam catalogDatabaseName string = 'catalogDatabase'\nparam catalogDatabaseServerName string = ''\nparam identityDatabaseName string = 'identityDatabase'\nparam identityDatabaseServerName string = ''\nparam appServicePlanName string = ''\nparam keyVaultName string = ''\n\n@description('Id of the user or app to assign application roles')\nparam principalId string = ''\n\n@secure()\n@description('SQL Server administrator password')\nparam sqlAdminPassword string\n\n@secure()\n@description('Application user password')\nparam appUserPassword string\n\nvar abbrs = loadJsonContent('./abbreviations.json')\nvar resourceToken = toLower(uniqueString(subscription().id, environmentName, location))\nvar tags = { 'azd-env-name': environmentName }\n\n// Organize resources in a resource group\nresource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {\n  name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}'\n  location: location\n  tags: tags\n}\n\n// The application frontend\nmodule web './core/host/appservice.bicep' = {\n  name: 'web'\n  scope: rg\n  params: {\n    name: !empty(webServiceName) ? webServiceName : '${abbrs.webSitesAppService}web-${resourceToken}'\n    location: location\n    appServicePlanId: appServicePlan.outputs.id\n    keyVaultName: keyVault.outputs.name\n    runtimeName: 'dotnetcore'\n    runtimeVersion: '8.0'\n    tags: union(tags, { 'azd-service-name': 'web' })\n    appSettings: {\n      AZURE_SQL_CATALOG_CONNECTION_STRING_KEY: 'AZURE-SQL-CATALOG-CONNECTION-STRING'\n      AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY: 'AZURE-SQL-IDENTITY-CONNECTION-STRING'\n      AZURE_KEY_VAULT_ENDPOINT: keyVault.outputs.endpoint\n    }\n  }\n}\n\nmodule apiKeyVaultAccess './core/security/keyvault-access.bicep' = {\n  name: 'api-keyvault-access'\n  scope: rg\n  params: {\n    keyVaultName: keyVault.outputs.name\n    principalId: web.outputs.identityPrincipalId\n  }\n}\n\n// The application database: Catalog\nmodule catalogDb './core/database/sqlserver/sqlserver.bicep' = {\n  name: 'sql-catalog'\n  scope: rg\n  params: {\n    name: !empty(catalogDatabaseServerName) ? catalogDatabaseServerName : '${abbrs.sqlServers}catalog-${resourceToken}'\n    databaseName: catalogDatabaseName\n    location: location\n    tags: tags\n    sqlAdminPassword: sqlAdminPassword\n    appUserPassword: appUserPassword\n    keyVaultName: keyVault.outputs.name\n    connectionStringKey: 'AZURE-SQL-CATALOG-CONNECTION-STRING'\n  }\n}\n\n// The application database: Identity\nmodule identityDb './core/database/sqlserver/sqlserver.bicep' = {\n  name: 'sql-identity'\n  scope: rg\n  params: {\n    name: !empty(identityDatabaseServerName) ? identityDatabaseServerName : '${abbrs.sqlServers}identity-${resourceToken}'\n    databaseName: identityDatabaseName\n    location: location\n    tags: tags\n    sqlAdminPassword: sqlAdminPassword\n    appUserPassword: appUserPassword\n    keyVaultName: keyVault.outputs.name\n    connectionStringKey: 'AZURE-SQL-IDENTITY-CONNECTION-STRING'\n  }\n}\n\n// Store secrets in a keyvault\nmodule keyVault './core/security/keyvault.bicep' = {\n  name: 'keyvault'\n  scope: rg\n  params: {\n    name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}'\n    location: location\n    tags: tags\n    principalId: principalId\n  }\n}\n\n// Create an App Service Plan to group applications under the same payment plan and SKU\nmodule appServicePlan './core/host/appserviceplan.bicep' = {\n  name: 'appserviceplan'\n  scope: rg\n  params: {\n    name: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${resourceToken}'\n    location: location\n    tags: tags\n    sku: {\n      name: 'B1'\n    }\n  }\n}\n\n// Data outputs\noutput AZURE_SQL_CATALOG_CONNECTION_STRING_KEY string = catalogDb.outputs.connectionStringKey\noutput AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY string = identityDb.outputs.connectionStringKey\noutput AZURE_SQL_CATALOG_DATABASE_NAME string = catalogDb.outputs.databaseName\noutput AZURE_SQL_IDENTITY_DATABASE_NAME string = identityDb.outputs.databaseName\n\n// App outputs\noutput AZURE_LOCATION string = location\noutput AZURE_TENANT_ID string = tenant().tenantId\noutput AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.endpoint\noutput AZURE_KEY_VAULT_NAME string = keyVault.outputs.name\n"
  },
  {
    "path": "infra/main.parameters.json",
    "content": "{\n  \"$schema\": \"https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#\",\n  \"contentVersion\": \"1.0.0.0\",\n  \"parameters\": {\n    \"environmentName\": {\n      \"value\": \"${AZURE_ENV_NAME}\"\n    },\n    \"location\": {\n      \"value\": \"${AZURE_LOCATION}\"\n    },\n    \"principalId\": {\n      \"value\": \"${AZURE_PRINCIPAL_ID}\"\n    },\n    \"sqlAdminPassword\": {\n      \"value\": \"$(secretOrRandomPassword ${AZURE_KEY_VAULT_NAME} sqlAdminPassword)\"\n    },\n    \"appUserPassword\": {\n      \"value\": \"$(secretOrRandomPassword ${AZURE_KEY_VAULT_NAME} appUserPassword)\"\n    }\n  }\n}"
  },
  {
    "path": "infra/simple-windows-vm.bicep",
    "content": "@description('Username for the Virtual Machine.')\nparam adminUsername string = 'Student'\n\n@description('Password for the Virtual Machine.')\n@minLength(12)\n@secure()\nparam adminPassword string = 'P@s${uniqueString(newGuid())}!'\n\n@description('Unique DNS Name for the Public IP used to access the Virtual Machine.')\nparam dnsLabelPrefix string = toLower('${vmName}-${uniqueString(resourceGroup().id, vmName)}')\n\n@description('Name for the Public IP used to access the Virtual Machine.')\nparam publicIpName string = 'myPublicIP'\n\n@description('Allocation method for the Public IP used to access the Virtual Machine.')\n@allowed([\n  'Dynamic'\n  'Static'\n])\nparam publicIPAllocationMethod string = 'Dynamic'\n\n@description('SKU for the Public IP used to access the Virtual Machine.')\n@allowed([\n  'Basic'\n  'Standard'\n])\nparam publicIpSku string = 'Basic'\n\n@description('The Windows version for the VM. This will pick a fully patched image of this given Windows version.')\n@allowed([\n  '2016-datacenter-gensecond'\n  '2016-datacenter-server-core-g2'\n  '2016-datacenter-server-core-smalldisk-g2'\n  '2016-datacenter-smalldisk-g2'\n  '2016-datacenter-with-containers-g2'\n  '2016-datacenter-zhcn-g2'\n  '2019-datacenter-core-g2'\n  '2019-datacenter-core-smalldisk-g2'\n  '2019-datacenter-core-with-containers-g2'\n  '2019-datacenter-core-with-containers-smalldisk-g2'\n  '2019-datacenter-gensecond'\n  '2019-datacenter-smalldisk-g2'\n  '2019-datacenter-with-containers-g2'\n  '2019-datacenter-with-containers-smalldisk-g2'\n  '2019-datacenter-zhcn-g2'\n  '2022-datacenter-azure-edition'\n  '2022-datacenter-azure-edition-core'\n  '2022-datacenter-azure-edition-core-smalldisk'\n  '2022-datacenter-azure-edition-smalldisk'\n  '2022-datacenter-core-g2'\n  '2022-datacenter-core-smalldisk-g2'\n  '2022-datacenter-g2'\n  '2022-datacenter-smalldisk-g2'\n])\nparam OSVersion string = '2022-datacenter-azure-edition'\n\n@description('Size of the virtual machine.')\nparam vmSize string = 'Standard_B1ms'\n\n@description('Location for all resources.')\nparam location string = resourceGroup().location\n\n@description('Name of the virtual machine.')\nparam vmName string = 'simple-vm'\n\n@description('Security Type of the Virtual Machine.')\n@allowed([\n  'Standard'\n  'TrustedLaunch'\n])\nparam securityType string = 'TrustedLaunch'\n\nvar storageAccountName = 'bootdiags${uniqueString(resourceGroup().id)}'\nvar nicName = 'myVMNic'\nvar addressPrefix = '10.0.0.0/16'\nvar subnetName = 'Subnet'\nvar subnetPrefix = '10.0.0.0/24'\nvar virtualNetworkName = 'MyVNET'\nvar networkSecurityGroupName = 'default-NSG'\nvar securityProfileJson = {\n  uefiSettings: {\n    secureBootEnabled: true\n    vTpmEnabled: true\n  }\n  securityType: securityType\n}\nvar extensionName = 'GuestAttestation'\nvar extensionPublisher = 'Microsoft.Azure.Security.WindowsAttestation'\nvar extensionVersion = '1.0'\nvar maaTenantName = 'GuestAttestation'\nvar maaEndpoint = substring('emptyString', 0, 0)\n\nresource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = {\n  name: storageAccountName\n  location: location\n  sku: {\n    name: 'Standard_LRS'\n  }\n  kind: 'Storage'\n}\n\nresource publicIp 'Microsoft.Network/publicIPAddresses@2022-05-01' = {\n  name: publicIpName\n  location: location\n  sku: {\n    name: publicIpSku\n  }\n  properties: {\n    publicIPAllocationMethod: publicIPAllocationMethod\n    dnsSettings: {\n      domainNameLabel: dnsLabelPrefix\n    }\n  }\n}\n\nresource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2022-05-01' = {\n  name: networkSecurityGroupName\n  location: location\n  properties: {\n    securityRules: [\n      {\n        name: 'default-allow-3389'\n        properties: {\n          priority: 1000\n          access: 'Allow'\n          direction: 'Inbound'\n          destinationPortRange: '3389'\n          protocol: 'Tcp'\n          sourcePortRange: '*'\n          sourceAddressPrefix: '*'\n          destinationAddressPrefix: '*'\n        }\n      }\n    ]\n  }\n}\n\nresource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-05-01' = {\n  name: virtualNetworkName\n  location: location\n  properties: {\n    addressSpace: {\n      addressPrefixes: [\n        addressPrefix\n      ]\n    }\n    subnets: [\n      {\n        name: subnetName\n        properties: {\n          addressPrefix: subnetPrefix\n          networkSecurityGroup: {\n            id: networkSecurityGroup.id\n          }\n        }\n      }\n    ]\n  }\n}\n\nresource nic 'Microsoft.Network/networkInterfaces@2022-05-01' = {\n  name: nicName\n  location: location\n  properties: {\n    ipConfigurations: [\n      {\n        name: 'ipconfig1'\n        properties: {\n          privateIPAllocationMethod: 'Dynamic'\n          publicIPAddress: {\n            id: publicIp.id\n          }\n          subnet: {\n            id: resourceId('Microsoft.Network/virtualNetworks/subnets', virtualNetworkName, subnetName)\n          }\n        }\n      }\n    ]\n  }\n  dependsOn: [\n\n    virtualNetwork\n  ]\n}\n\nresource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = {\n  name: vmName\n  location: location\n  properties: {\n    hardwareProfile: {\n      vmSize: vmSize\n    }\n    osProfile: {\n      computerName: vmName\n      adminUsername: adminUsername\n      adminPassword: adminPassword\n    }\n    storageProfile: {\n      imageReference: {\n        publisher: 'MicrosoftWindowsServer'\n        offer: 'WindowsServer'\n        sku: OSVersion\n        version: 'latest'\n      }\n      osDisk: {\n        createOption: 'FromImage'\n        managedDisk: {\n          storageAccountType: 'StandardSSD_LRS'\n        }\n      }\n      dataDisks: [\n        {\n          diskSizeGB: 1023\n          lun: 0\n          createOption: 'Empty'\n        }\n      ]\n    }\n    networkProfile: {\n      networkInterfaces: [\n        {\n          id: nic.id\n        }\n      ]\n    }\n    diagnosticsProfile: {\n      bootDiagnostics: {\n        enabled: true\n        storageUri: storageAccount.properties.primaryEndpoints.blob\n      }\n    }\n    securityProfile: ((securityType == 'TrustedLaunch') ? securityProfileJson : null)\n  }\n}\n\nresource vmExtension 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = if ((securityType == 'TrustedLaunch') && ((securityProfileJson.uefiSettings.secureBootEnabled == true) && (securityProfileJson.uefiSettings.vTpmEnabled == true))) {\n  parent: vm\n  name: extensionName\n  location: location\n  properties: {\n    publisher: extensionPublisher\n    type: extensionName\n    typeHandlerVersion: extensionVersion\n    autoUpgradeMinorVersion: true\n    enableAutomaticUpgrade: true\n    settings: {\n      AttestationConfig: {\n        MaaSettings: {\n          maaEndpoint: maaEndpoint\n          maaTenantName: maaTenantName\n        }\n      }\n    }\n  }\n}\n\noutput hostname string = publicIp.properties.dnsSettings.fqdn\n"
  },
  {
    "path": "infra/webapp-docker.bicep",
    "content": "@description('Generate a Suffix based on the Resource Group ID')\nparam suffix string = uniqueString(resourceGroup().id)\n\n@description('Use the Resource Group Location')\nparam location string = resourceGroup().location\n\nresource acr 'Microsoft.ContainerRegistry/registries@2021-09-01' existing = {\n  name: 'cr${suffix}'\n}\n\nresource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {\n  name: 'asp-${suffix}'\n  location: location\n  kind: 'linux'\n  properties: {\n    reserved: true\n  }\n  sku: {\n    name: 'B1'\n  }\n}\n\nresource webApp 'Microsoft.Web/sites@2022-03-01' = {\n  name: 'app-${suffix}'\n  location: location\n  tags: {}\n  properties: {\n    siteConfig: {\n      acrUseManagedIdentityCreds: true\n      appSettings: [\n        {\n          name: 'UseOnlyInMemoryDatabase'\n          value: 'true'\n        }\n        {\n          name: 'ASPNETCORE_ENVIRONMENT'\n          value: 'Docker'\n        }\n        {\n          name: 'ASPNETCORE_HTTP_PORTS'\n          value: '80'\n        }\n      ]\n      linuxFxVersion: 'DOCKER|${acr.properties.loginServer}/eshoponweb/web:latest'\n    }\n    serverFarmId: appServicePlan.id\n  }\n  identity: {\n    type: 'SystemAssigned'\n  }\n}\n"
  },
  {
    "path": "infra/webapp-to-acr-roleassignment.bicep",
    "content": "@description('Generate a Suffix based on the Resource Group ID')\nparam suffix string = uniqueString(resourceGroup().id)\n\n@description('Set the ACR Pull Role Definition ID')\nparam acrPullRoleDefinitionID string = '7f951dda-4ed3-4680-a7ca-43fe172d538d'\n\n@description('Generate a unique GUID to use as name for the role assignment')\nvar webAppToAcrRoleAssignmentName = guid(webApp.id, acrPullRoleDefinitionID, acr.id)\n\n\nresource acr 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' existing = {\n  name: 'cr${suffix}'\n}\n\nresource webApp 'Microsoft.Web/sites@2022-03-01' existing = {\n  name: 'app-${suffix}'\n}\n\nresource webAppToAcrRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {\n  scope: acr\n  name: webAppToAcrRoleAssignmentName\n  properties: {\n    roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', acrPullRoleDefinitionID)\n    principalId: webApp.identity.principalId\n  }\n}\n"
  },
  {
    "path": "infra/webapp.bicep",
    "content": "param webAppName string // = uniqueString(resourceGroup().id) // unique String gets created from az cli instructions\nparam sku string = 'S1' // The SKU of App Service Plan\nparam location string = resourceGroup().location\n\nvar appServicePlanName = toLower('AppServicePlan-${webAppName}')\n\nresource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {\n  name: appServicePlanName\n  location: location\n  properties: {\n    reserved: true\n  }\n  sku: {\n    name: sku\n  }\n}\nresource appService 'Microsoft.Web/sites@2022-09-01' = {\n  name: webAppName\n  kind: 'app'\n  location: location\n  properties: {\n    serverFarmId: appServicePlan.id\n    siteConfig: {\n      linuxFxVersion: 'DOTNETCORE|8.0'\n      appSettings: [\n        {\n          name: 'ASPNETCORE_ENVIRONMENT'\n          value: 'Development'\n        }\n        {\n          name: 'UseOnlyInMemoryDatabase'\n          value: 'true'\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "src/ApplicationCore/ApplicationCore.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\t\n\t\t<RootNamespace>Microsoft.eShopWeb.ApplicationCore</RootNamespace>\n\t\t<Nullable>enable</Nullable>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t\t<PackageReference Include=\"Ardalis.GuardClauses\" />\n\t\t<PackageReference Include=\"Ardalis.Result\" />\n\t\t<PackageReference Include=\"Ardalis.Specification\" />\t\t\n\t\t<PackageReference Include=\"System.Security.Claims\" />\n\t\t<PackageReference Include=\"System.Text.Json\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t  <ProjectReference Include=\"..\\BlazorShared\\BlazorShared.csproj\" />\n\t</ItemGroup>\n\n</Project>"
  },
  {
    "path": "src/ApplicationCore/CatalogSettings.cs",
    "content": "﻿namespace Microsoft.eShopWeb;\n\npublic class CatalogSettings\n{\n    public string? CatalogBaseUrl { get; set; }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Constants/AuthorizationConstants.cs",
    "content": "﻿namespace Microsoft.eShopWeb.ApplicationCore.Constants;\n\npublic class AuthorizationConstants\n{\n    public const string AUTH_KEY = \"AuthKeyOfDoomThatMustBeAMinimumNumberOfBytes\";\n\n    // TODO: Don't use this in production\n    public const string DEFAULT_PASSWORD = \"Pass@word1\";\n\n    // TODO: Change this to an environment variable\n    public const string JWT_SECRET_KEY = \"SecretKeyOfDoomThatMustBeAMinimumNumberOfBytes\";\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/BaseEntity.cs",
    "content": "﻿namespace Microsoft.eShopWeb.ApplicationCore.Entities;\n\n// This can easily be modified to be BaseEntity<T> and public T Id to support different key types.\n// Using non-generic integer types for simplicity and to ease caching logic\npublic abstract class BaseEntity\n{\n    public virtual int Id { get; protected set; }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/BasketAggregate/Basket.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Ardalis.GuardClauses;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\n\npublic class Basket : BaseEntity, IAggregateRoot\n{\n    public string BuyerId { get; private set; }\n    private readonly List<BasketItem> _items = new List<BasketItem>();\n    public IReadOnlyCollection<BasketItem> Items => _items.AsReadOnly();\n\n    public int TotalItems => _items.Sum(i => i.Quantity);\n\n\n    public Basket(string buyerId)\n    {\n        BuyerId = buyerId;\n    }\n\n    public void AddItem(int catalogItemId, decimal unitPrice, int quantity = 1)\n    {\n        if (!Items.Any(i => i.CatalogItemId == catalogItemId))\n        {\n            _items.Add(new BasketItem(catalogItemId, quantity, unitPrice));\n            return;\n        }\n        var existingItem = Items.First(i => i.CatalogItemId == catalogItemId);\n        existingItem.AddQuantity(quantity);\n    }\n\n    public void RemoveEmptyItems()\n    {\n        _items.RemoveAll(i => i.Quantity == 0);\n    }\n\n    public void SetNewBuyerId(string buyerId)\n    {\n        BuyerId = buyerId;\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/BasketAggregate/BasketItem.cs",
    "content": "﻿using Ardalis.GuardClauses;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\n\npublic class BasketItem : BaseEntity\n{\n\n    public decimal UnitPrice { get; private set; }\n    public int Quantity { get; private set; }\n    public int CatalogItemId { get; private set; }\n    public int BasketId { get; private set; }\n\n    public BasketItem(int catalogItemId, int quantity, decimal unitPrice)\n    {\n        CatalogItemId = catalogItemId;\n        UnitPrice = unitPrice;\n        SetQuantity(quantity);\n    }\n\n    public void AddQuantity(int quantity)\n    {\n        Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue);\n\n        Quantity += quantity;\n    }\n\n    public void SetQuantity(int quantity)\n    {\n        Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue);\n\n        Quantity = quantity;\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs",
    "content": "﻿using System.Collections.Generic;\nusing Ardalis.GuardClauses;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Entities.BuyerAggregate;\n\npublic class Buyer : BaseEntity, IAggregateRoot\n{\n    public string IdentityGuid { get; private set; }\n\n    private List<PaymentMethod> _paymentMethods = new List<PaymentMethod>();\n\n    public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();\n\n    #pragma warning disable CS8618 // Required by Entity Framework\n    private Buyer() { }\n\n    public Buyer(string identity) : this()\n    {\n        Guard.Against.NullOrEmpty(identity, nameof(identity));\n        IdentityGuid = identity;\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs",
    "content": "﻿namespace Microsoft.eShopWeb.ApplicationCore.Entities.BuyerAggregate;\n\npublic class PaymentMethod : BaseEntity\n{\n    public string? Alias { get; private set; }\n    public string? CardId { get; private set; } // actual card data must be stored in a PCI compliant system, like Stripe\n    public string? Last4 { get; private set; }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/CatalogBrand.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Entities;\n\npublic class CatalogBrand : BaseEntity, IAggregateRoot\n{\n    public string Brand { get; private set; }\n    public CatalogBrand(string brand)\n    {\n        Brand = brand;\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/CatalogItem.cs",
    "content": "﻿using System;\nusing Ardalis.GuardClauses;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Entities;\n\npublic class CatalogItem : BaseEntity, IAggregateRoot\n{\n    public string Name { get; private set; }\n    public string Description { get; private set; }\n    public decimal Price { get; private set; }\n    public string PictureUri { get; private set; }\n    public int CatalogTypeId { get; private set; }\n    public CatalogType? CatalogType { get; private set; }\n    public int CatalogBrandId { get; private set; }\n    public CatalogBrand? CatalogBrand { get; private set; }\n\n    public CatalogItem(int catalogTypeId,\n        int catalogBrandId,\n        string description,\n        string name,\n        decimal price,\n        string pictureUri)\n    {\n        CatalogTypeId = catalogTypeId;\n        CatalogBrandId = catalogBrandId;\n        Description = description;\n        Name = name;\n        Price = price;\n        PictureUri = pictureUri;\n    }\n\n    public void UpdateDetails(CatalogItemDetails details)\n    {\n        Guard.Against.NullOrEmpty(details.Name, nameof(details.Name));\n        Guard.Against.NullOrEmpty(details.Description, nameof(details.Description));\n        Guard.Against.NegativeOrZero(details.Price, nameof(details.Price));\n\n        Name = details.Name;\n        Description = details.Description;\n        Price = details.Price;\n    }\n\n    public void UpdateBrand(int catalogBrandId)\n    {\n        Guard.Against.Zero(catalogBrandId, nameof(catalogBrandId));\n        CatalogBrandId = catalogBrandId;\n    }\n\n    public void UpdateType(int catalogTypeId)\n    {\n        Guard.Against.Zero(catalogTypeId, nameof(catalogTypeId));\n        CatalogTypeId = catalogTypeId;\n    }\n\n    public void UpdatePictureUri(string pictureName)\n    {\n        if (string.IsNullOrEmpty(pictureName))\n        {\n            PictureUri = string.Empty;\n            return;\n        }\n        PictureUri = $\"images\\\\products\\\\{pictureName}?{new DateTime().Ticks}\";\n    }\n\n    public readonly record struct CatalogItemDetails\n    {\n        public string? Name { get; }\n        public string? Description { get; }\n        public decimal Price { get; }\n\n        public CatalogItemDetails(string? name, string? description, decimal price)\n        {\n            Name = name;\n            Description = description;\n            Price = price;\n        }\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/CatalogType.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Entities;\n\npublic class CatalogType : BaseEntity, IAggregateRoot\n{\n    public string Type { get; private set; }\n    public CatalogType(string type)\n    {\n        Type = type;\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/EshopDiagram.cd",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ClassDiagram MajorVersion=\"1\" MinorVersion=\"1\">\n  <Class Name=\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand\" BaseTypeListCollapsed=\"true\">\n    <Position X=\"3\" Y=\"7.5\" Width=\"1.5\" />\n    <TypeIdentifier>\n      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAA=</HashCode>\n      <FileName>Entities\\CatalogBrand.cs</FileName>\n    </TypeIdentifier>\n    <Lollipop Position=\"0.377\" Collapsed=\"true\" />\n  </Class>\n  <Class Name=\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem\" BaseTypeListCollapsed=\"true\">\n    <Position X=\"4.75\" Y=\"3.5\" Width=\"1.5\" />\n    <TypeIdentifier>\n      <HashCode>AAgAAAAAA4AgAwAAAAAAAAQAAAEAAAAAAAAAAQAACQA=</HashCode>\n      <FileName>Entities\\CatalogItem.cs</FileName>\n    </TypeIdentifier>\n    <ShowAsAssociation>\n      <Property Name=\"CatalogBrand\" />\n      <Property Name=\"CatalogType\" />\n    </ShowAsAssociation>\n    <Lollipop Position=\"0.2\" Collapsed=\"true\" />\n  </Class>\n  <Class Name=\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType\" BaseTypeListCollapsed=\"true\">\n    <Position X=\"6.5\" Y=\"7.75\" Width=\"1.5\" />\n    <TypeIdentifier>\n      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAA=</HashCode>\n      <FileName>Entities\\CatalogType.cs</FileName>\n    </TypeIdentifier>\n    <Lollipop Position=\"0.2\" Collapsed=\"true\" />\n  </Class>\n  <Font Name=\"Segoe UI\" Size=\"9\" />\n</ClassDiagram>"
  },
  {
    "path": "src/ApplicationCore/Entities/OrderAggregate/Address.cs",
    "content": "﻿namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\npublic class Address // ValueObject\n{\n    public string Street { get; private set; }\n\n    public string City { get; private set; }\n\n    public string State { get; private set; }\n\n    public string Country { get; private set; }\n\n    public string ZipCode { get; private set; }\n\n    #pragma warning disable CS8618 // Required by Entity Framework\n    private Address() { }\n\n    public Address(string street, string city, string state, string country, string zipcode)\n    {\n        Street = street;\n        City = city;\n        State = state;\n        Country = country;\n        ZipCode = zipcode;\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/OrderAggregate/CatalogItemOrdered.cs",
    "content": "﻿using Ardalis.GuardClauses;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\n/// <summary>\n/// Represents a snapshot of the item that was ordered. If catalog item details change, details of\n/// the item that was part of a completed order should not change.\n/// </summary>\npublic class CatalogItemOrdered // ValueObject\n{\n    public CatalogItemOrdered(int catalogItemId, string productName, string pictureUri)\n    {\n        Guard.Against.OutOfRange(catalogItemId, nameof(catalogItemId), 1, int.MaxValue);\n        Guard.Against.NullOrEmpty(productName, nameof(productName));\n        Guard.Against.NullOrEmpty(pictureUri, nameof(pictureUri));\n\n        CatalogItemId = catalogItemId;\n        ProductName = productName;\n        PictureUri = pictureUri;\n    }\n\n    #pragma warning disable CS8618 // Required by Entity Framework\n    private CatalogItemOrdered() {}\n\n    public int CatalogItemId { get; private set; }\n    public string ProductName { get; private set; }\n    public string PictureUri { get; private set; }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/OrderAggregate/Order.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Ardalis.GuardClauses;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\npublic class Order : BaseEntity, IAggregateRoot\n{\n    #pragma warning disable CS8618 // Required by Entity Framework\n    private Order() {}\n\n    public Order(string buyerId, Address shipToAddress, List<OrderItem> items)\n    {\n        Guard.Against.NullOrEmpty(buyerId, nameof(buyerId));\n\n        BuyerId = buyerId;\n        ShipToAddress = shipToAddress;\n        _orderItems = items;\n    }\n\n    public string BuyerId { get; private set; }\n    public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now;\n    public Address ShipToAddress { get; private set; }\n\n    // DDD Patterns comment\n    // Using a private collection field, better for DDD Aggregate's encapsulation\n    // so OrderItems cannot be added from \"outside the AggregateRoot\" directly to the collection,\n    // but only through the method Order.AddOrderItem() which includes behavior.\n    private readonly List<OrderItem> _orderItems = new List<OrderItem>();\n\n    // Using List<>.AsReadOnly() \n    // This will create a read only wrapper around the private list so is protected against \"external updates\".\n    // It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)\n    //https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx \n    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();\n\n    public decimal Total()\n    {\n        var total = 0m;\n        foreach (var item in _orderItems)\n        {\n            total += item.UnitPrice * item.Units;\n        }\n        return total;\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs",
    "content": "﻿namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\npublic class OrderItem : BaseEntity\n{\n    public CatalogItemOrdered ItemOrdered { get; private set; }\n    public decimal UnitPrice { get; private set; }\n    public int Units { get; private set; }\n\n    #pragma warning disable CS8618 // Required by Entity Framework\n    private OrderItem() {}\n\n    public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)\n    {\n        ItemOrdered = itemOrdered;\n        UnitPrice = unitPrice;\n        Units = units;\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Exceptions/BasketNotFoundException.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Exceptions;\n\npublic class BasketNotFoundException : Exception\n{\n    public BasketNotFoundException(int basketId) : base($\"No basket found with id {basketId}\")\n    {\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Exceptions/DuplicateException.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Exceptions;\n\npublic class DuplicateException : Exception\n{\n    public DuplicateException(string message) : base(message)\n    {\n\n    }\n\n}\n"
  },
  {
    "path": "src/ApplicationCore/Exceptions/EmptyBasketOnCheckoutException.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Exceptions;\n\npublic class EmptyBasketOnCheckoutException : Exception\n{\n    public EmptyBasketOnCheckoutException()\n        : base($\"Basket cannot have 0 items on checkout\")\n    {\n    }\n\n    public EmptyBasketOnCheckoutException(string message) : base(message)\n    {\n    }\n\n    public EmptyBasketOnCheckoutException(string message, Exception innerException) : base(message, innerException)\n    {\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Extensions/GuardExtensions.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Exceptions;\n\nnamespace Ardalis.GuardClauses;\n\npublic static class BasketGuards\n{\n    public static void EmptyBasketOnCheckout(this IGuardClause guardClause, IReadOnlyCollection<BasketItem> basketItems)\n    {\n        if (!basketItems.Any())\n            throw new EmptyBasketOnCheckoutException();\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Extensions/JsonExtensions.cs",
    "content": "﻿using System.Text.Json;\n\nnamespace Microsoft.eShopWeb;\n\npublic static class JsonExtensions\n{\n    private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions\n    {\n        PropertyNameCaseInsensitive = true\n    };\n\n    public static T? FromJson<T>(this string json) =>\n        JsonSerializer.Deserialize<T>(json, _jsonOptions);\n\n    public static string ToJson<T>(this T obj) =>\n        JsonSerializer.Serialize<T>(obj, _jsonOptions);\n}\n"
  },
  {
    "path": "src/ApplicationCore/Interfaces/IAggregateRoot.cs",
    "content": "﻿namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\npublic interface IAggregateRoot\n{ }\n"
  },
  {
    "path": "src/ApplicationCore/Interfaces/IAppLogger.cs",
    "content": "﻿namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\n/// <summary>\n/// This type eliminates the need to depend directly on the ASP.NET Core logging types.\n/// </summary>\n/// <typeparam name=\"T\"></typeparam>\npublic interface IAppLogger<T>\n{\n    void LogInformation(string message, params object[] args);\n    void LogWarning(string message, params object[] args);\n}\n"
  },
  {
    "path": "src/ApplicationCore/Interfaces/IBasketQueryService.cs",
    "content": "﻿using System.Threading.Tasks;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\n/// <summary>\n/// Specific query used to fetch count without running in memory\n/// </summary>\npublic interface IBasketQueryService\n{\n    Task<int> CountTotalBasketItems(string username);\n}\n\n"
  },
  {
    "path": "src/ApplicationCore/Interfaces/IBasketService.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Ardalis.Result;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\npublic interface IBasketService\n{\n    Task TransferBasketAsync(string anonymousId, string userName);\n    Task<Basket> AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1);\n    Task<Result<Basket>> SetQuantities(int basketId, Dictionary<string, int> quantities);\n    Task DeleteBasketAsync(int basketId);\n}\n"
  },
  {
    "path": "src/ApplicationCore/Interfaces/IEmailSender.cs",
    "content": "﻿using System.Threading.Tasks;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\npublic interface IEmailSender\n{\n    Task SendEmailAsync(string email, string subject, string message);\n}\n"
  },
  {
    "path": "src/ApplicationCore/Interfaces/IOrderService.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\npublic interface IOrderService\n{\n    Task CreateOrderAsync(int basketId, Address shippingAddress);\n}\n"
  },
  {
    "path": "src/ApplicationCore/Interfaces/IReadRepository.cs",
    "content": "﻿using Ardalis.Specification;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\npublic interface IReadRepository<T> : IReadRepositoryBase<T> where T : class, IAggregateRoot\n{\n}\n"
  },
  {
    "path": "src/ApplicationCore/Interfaces/IRepository.cs",
    "content": "﻿using Ardalis.Specification;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\npublic interface IRepository<T> : IRepositoryBase<T> where T : class, IAggregateRoot\n{\n}\n"
  },
  {
    "path": "src/ApplicationCore/Interfaces/ITokenClaimsService.cs",
    "content": "﻿using System.Threading.Tasks;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\npublic interface ITokenClaimsService\n{\n    Task<string> GetTokenAsync(string userName);\n}\n"
  },
  {
    "path": "src/ApplicationCore/Interfaces/IUriComposer.cs",
    "content": "﻿namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\npublic interface IUriComposer\n{\n    string ComposePicUri(string uriTemplate);\n}\n"
  },
  {
    "path": "src/ApplicationCore/Services/BasketService.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Ardalis.GuardClauses;\nusing Ardalis.Result;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Services;\n\npublic class BasketService : IBasketService\n{\n    private readonly IRepository<Basket> _basketRepository;\n    private readonly IAppLogger<BasketService> _logger;\n\n    public BasketService(IRepository<Basket> basketRepository,\n        IAppLogger<BasketService> logger)\n    {\n        _basketRepository = basketRepository;\n        _logger = logger;\n    }\n\n    public async Task<Basket> AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1)\n    {\n        var basketSpec = new BasketWithItemsSpecification(username);\n        var basket = await _basketRepository.FirstOrDefaultAsync(basketSpec);\n\n        if (basket == null)\n        {\n            basket = new Basket(username);\n            await _basketRepository.AddAsync(basket);\n        }\n\n        basket.AddItem(catalogItemId, price, quantity);\n\n        await _basketRepository.UpdateAsync(basket);\n        return basket;\n    }\n\n    public async Task DeleteBasketAsync(int basketId)\n    {\n        var basket = await _basketRepository.GetByIdAsync(basketId);\n        Guard.Against.Null(basket, nameof(basket));\n        await _basketRepository.DeleteAsync(basket);\n    }\n\n    public async Task<Result<Basket>> SetQuantities(int basketId, Dictionary<string, int> quantities)\n    {\n        var basketSpec = new BasketWithItemsSpecification(basketId);\n        var basket = await _basketRepository.FirstOrDefaultAsync(basketSpec);\n        if (basket == null) return Result<Basket>.NotFound();\n\n        foreach (var item in basket.Items)\n        {\n            if (quantities.TryGetValue(item.Id.ToString(), out var quantity))\n            {\n                if (_logger != null) _logger.LogInformation($\"Updating quantity of item ID:{item.Id} to {quantity}.\");\n                item.SetQuantity(quantity);\n            }\n        }\n        basket.RemoveEmptyItems();\n        await _basketRepository.UpdateAsync(basket);\n        return basket;\n    }\n\n    public async Task TransferBasketAsync(string anonymousId, string userName)\n    {\n        var anonymousBasketSpec = new BasketWithItemsSpecification(anonymousId);\n        var anonymousBasket = await _basketRepository.FirstOrDefaultAsync(anonymousBasketSpec);\n        if (anonymousBasket == null) return;\n        var userBasketSpec = new BasketWithItemsSpecification(userName);\n        var userBasket = await _basketRepository.FirstOrDefaultAsync(userBasketSpec);\n        if (userBasket == null)\n        {\n            userBasket = new Basket(userName);\n            await _basketRepository.AddAsync(userBasket);\n        }\n        foreach (var item in anonymousBasket.Items)\n        {\n            userBasket.AddItem(item.CatalogItemId, item.UnitPrice, item.Quantity);\n        }\n        await _basketRepository.UpdateAsync(userBasket);\n        await _basketRepository.DeleteAsync(anonymousBasket);\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Services/OrderService.cs",
    "content": "﻿using System.Linq;\nusing System.Threading.Tasks;\nusing Ardalis.GuardClauses;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Services;\n\npublic class OrderService : IOrderService\n{\n    private readonly IRepository<Order> _orderRepository;\n    private readonly IUriComposer _uriComposer;\n    private readonly IRepository<Basket> _basketRepository;\n    private readonly IRepository<CatalogItem> _itemRepository;\n\n    public OrderService(IRepository<Basket> basketRepository,\n        IRepository<CatalogItem> itemRepository,\n        IRepository<Order> orderRepository,\n        IUriComposer uriComposer)\n    {\n        _orderRepository = orderRepository;\n        _uriComposer = uriComposer;\n        _basketRepository = basketRepository;\n        _itemRepository = itemRepository;\n    }\n\n    public async Task CreateOrderAsync(int basketId, Address shippingAddress)\n    {\n        var basketSpec = new BasketWithItemsSpecification(basketId);\n        var basket = await _basketRepository.FirstOrDefaultAsync(basketSpec);\n\n        Guard.Against.Null(basket, nameof(basket));\n        Guard.Against.EmptyBasketOnCheckout(basket.Items);\n\n        var catalogItemsSpecification = new CatalogItemsSpecification(basket.Items.Select(item => item.CatalogItemId).ToArray());\n        var catalogItems = await _itemRepository.ListAsync(catalogItemsSpecification);\n\n        var items = basket.Items.Select(basketItem =>\n        {\n            var catalogItem = catalogItems.First(c => c.Id == basketItem.CatalogItemId);\n            var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name, _uriComposer.ComposePicUri(catalogItem.PictureUri));\n            var orderItem = new OrderItem(itemOrdered, basketItem.UnitPrice, basketItem.Quantity);\n            return orderItem;\n        }).ToList();\n\n        var order = new Order(basket.BuyerId, shippingAddress, items);\n\n        await _orderRepository.AddAsync(order);\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Services/UriComposer.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Services;\n\npublic class UriComposer : IUriComposer\n{\n    private readonly CatalogSettings _catalogSettings;\n\n    public UriComposer(CatalogSettings catalogSettings) => _catalogSettings = catalogSettings;\n\n    public string ComposePicUri(string uriTemplate)\n    {\n        return uriTemplate.Replace(\"http://catalogbaseurltobereplaced\", _catalogSettings.CatalogBaseUrl);\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Specifications/BasketWithItemsSpecification.cs",
    "content": "﻿using Ardalis.Specification;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Specifications;\n\npublic sealed class BasketWithItemsSpecification : Specification<Basket>\n{\n    public BasketWithItemsSpecification(int basketId)\n    {\n        Query\n            .Where(b => b.Id == basketId)\n            .Include(b => b.Items);\n    }\n\n    public BasketWithItemsSpecification(string buyerId)\n    {\n        Query\n            .Where(b => b.BuyerId == buyerId)\n            .Include(b => b.Items);\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs",
    "content": "﻿using Ardalis.Specification;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Specifications;\n\npublic class CatalogFilterPaginatedSpecification : Specification<CatalogItem>\n{\n    public CatalogFilterPaginatedSpecification(int skip, int take, int? brandId, int? typeId)\n        : base()\n    {\n        if (take == 0)\n        {\n            take = int.MaxValue;\n        }\n        Query\n            .Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) &&\n            (!typeId.HasValue || i.CatalogTypeId == typeId))\n            .Skip(skip).Take(take);\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Specifications/CatalogFilterSpecification.cs",
    "content": "﻿using Ardalis.Specification;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Specifications;\n\npublic class CatalogFilterSpecification : Specification<CatalogItem>\n{\n    public CatalogFilterSpecification(int? brandId, int? typeId)\n    {\n        Query.Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) &&\n            (!typeId.HasValue || i.CatalogTypeId == typeId));\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Specifications/CatalogItemNameSpecification.cs",
    "content": "﻿using Ardalis.Specification;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Specifications;\n\npublic class CatalogItemNameSpecification : Specification<CatalogItem>\n{\n    public CatalogItemNameSpecification(string catalogItemName)\n    {\n        Query.Where(item => catalogItemName == item.Name);\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Specifications/CatalogItemsSpecification.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Ardalis.Specification;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Specifications;\n\npublic class CatalogItemsSpecification : Specification<CatalogItem>\n{\n    public CatalogItemsSpecification(params int[] ids)\n    {\n        Query.Where(c => ids.Contains(c.Id));\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Specifications/CustomerOrdersSpecification.cs",
    "content": "﻿using Ardalis.Specification;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Specifications;\n\npublic class CustomerOrdersSpecification : Specification<Order>\n{\n    public CustomerOrdersSpecification(string buyerId)\n    {\n        Query.Where(o => o.BuyerId == buyerId)\n            .Include(o => o.OrderItems);\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Specifications/CustomerOrdersWithItemsSpecification.cs",
    "content": "﻿using Ardalis.Specification;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Specifications;\n\npublic class CustomerOrdersWithItemsSpecification : Specification<Order>\n{\n    public CustomerOrdersWithItemsSpecification(string buyerId)\n    {\n        Query.Where(o => o.BuyerId == buyerId)\n            .Include(o => o.OrderItems)\n                .ThenInclude(i => i.ItemOrdered);\n    }\n}\n"
  },
  {
    "path": "src/ApplicationCore/Specifications/OrderWithItemsByIdSpec.cs",
    "content": "﻿using Ardalis.Specification;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\nnamespace Microsoft.eShopWeb.ApplicationCore.Specifications;\n\npublic class OrderWithItemsByIdSpec : Specification<Order>\n{\n    public OrderWithItemsByIdSpec(int orderId)\n    {\n        Query\n            .Where(order => order.Id == orderId)\n            .Include(o => o.OrderItems)\n            .ThenInclude(i => i.ItemOrdered);\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/App.razor",
    "content": "<CascadingAuthenticationState>\n    <Router AppAssembly=\"@typeof(Program).Assembly\">\n        <Found Context=\"routeData\">\n            <AuthorizeRouteView RouteData=\"@routeData\"\n                                DefaultLayout=\"@typeof(MainLayout)\">\n                <NotAuthorized>\n                    @if (!context.User.Identity.IsAuthenticated)\n                    {\n                        <RedirectToLogin />\n                    }\n                    else\n                    {\n                        <h2>Not Authorized</h2>\n                        <p>\n                            You are not authorized to access\n                            this resource.\n\n                            <a href=\"/\">Return to eShop</a>\n                        </p>\n                    }\n                </NotAuthorized>\n            </AuthorizeRouteView>\n        </Found>\n        <NotFound>\n            <LayoutView Layout=\"@typeof(MainLayout)\">\n                <p>Sorry, there's nothing at this address.</p>\n            </LayoutView>\n        </NotFound>\n    </Router>\n</CascadingAuthenticationState>"
  },
  {
    "path": "src/BlazorAdmin/BlazorAdmin.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.BlazorWebAssembly\">\t\n\t<ItemGroup>\n\t\t<PackageReference Include=\"Blazored.LocalStorage\" />\n\t\t<PackageReference Include=\"BlazorInputFile\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Components.Authorization\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Components.WebAssembly\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Components.WebAssembly.Authentication\" />\t\t\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Components.WebAssembly.DevServer\" />\n\t\t<PackageReference Include=\"Microsoft.Extensions.Identity.Core\" />\n\t\t<PackageReference Include=\"Microsoft.Extensions.Logging.Configuration\" />\n\t\t<PackageReference Include=\"System.Net.Http.Json\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<ProjectReference Include=\"..\\BlazorShared\\BlazorShared.csproj\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<Compile Update=\"Services\\CatalogItem\\Delete.EditCatalogItemResult.cs\">\n\t\t\t<DependentUpon>Delete.cs</DependentUpon>\n\t\t</Compile>\n\t\t<Compile Update=\"Services\\CatalogItem\\GetById.EditCatalogItemResult.cs\">\n\t\t\t<DependentUpon>GetById.cs</DependentUpon>\n\t\t</Compile>\n\t\t<Compile Update=\"Services\\CatalogItem\\Edit.CreateCatalogItemResult.cs\">\n\t\t\t<DependentUpon>Edit.cs</DependentUpon>\n\t\t</Compile>\n\t</ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "src/BlazorAdmin/CustomAuthStateProvider.cs",
    "content": "﻿using System;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Net.Http.Json;\nusing System.Security.Claims;\nusing System.Threading.Tasks;\nusing BlazorShared.Authorization;\nusing Microsoft.AspNetCore.Components.Authorization;\nusing Microsoft.Extensions.Logging;\n\nnamespace BlazorAdmin;\n\npublic class CustomAuthStateProvider : AuthenticationStateProvider\n{\n    // TODO: Get Default Cache Duration from Config\n    private static readonly TimeSpan UserCacheRefreshInterval = TimeSpan.FromSeconds(60);\n\n    private readonly HttpClient _httpClient;\n    private readonly ILogger<CustomAuthStateProvider> _logger;\n\n    private DateTimeOffset _userLastCheck = DateTimeOffset.FromUnixTimeSeconds(0);\n    private ClaimsPrincipal _cachedUser = new ClaimsPrincipal(new ClaimsIdentity());\n\n    public CustomAuthStateProvider(HttpClient httpClient,\n        ILogger<CustomAuthStateProvider> logger)\n    {\n        _httpClient = httpClient;\n        _logger = logger;\n    }\n\n    public override async Task<AuthenticationState> GetAuthenticationStateAsync()\n    {\n        return new AuthenticationState(await GetUser(useCache: true));\n    }\n\n    private async ValueTask<ClaimsPrincipal> GetUser(bool useCache = false)\n    {\n        var now = DateTimeOffset.Now;\n        if (useCache && now < _userLastCheck + UserCacheRefreshInterval)\n        {\n            return _cachedUser;\n        }\n\n        _cachedUser = await FetchUser();\n        _userLastCheck = now;\n\n        return _cachedUser;\n    }\n\n    private async Task<ClaimsPrincipal> FetchUser()\n    {\n        UserInfo user = null;\n\n        try\n        {\n            _logger.LogInformation(\"Fetching user details from web api.\");\n            user = await _httpClient.GetFromJsonAsync<UserInfo>(\"User\");\n        }\n        catch (Exception exc)\n        {\n            _logger.LogWarning(exc, \"Fetching user failed.\");\n        }\n\n        if (user == null || !user.IsAuthenticated)\n        {\n            return new ClaimsPrincipal(new ClaimsIdentity());\n        }\n\n        var identity = new ClaimsIdentity(\n            nameof(CustomAuthStateProvider),\n            user.NameClaimType,\n            user.RoleClaimType);\n\n        if (user.Claims != null)\n        {\n            foreach (var claim in user.Claims)\n            {\n                identity.AddClaim(new Claim(claim.Type, claim.Value));\n            }\n        }\n\n        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", user.Token);\n\n        return new ClaimsPrincipal(identity);\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Helpers/BlazorComponent.cs",
    "content": "﻿using Microsoft.AspNetCore.Components;\n\nnamespace BlazorAdmin.Helpers;\n\npublic class BlazorComponent : ComponentBase\n{\n    private readonly RefreshBroadcast _refresh = RefreshBroadcast.Instance;\n\n    protected override void OnInitialized()\n    {\n        _refresh.RefreshRequested += DoRefresh;\n        base.OnInitialized();\n    }\n\n    public void CallRequestRefresh()\n    {\n        _refresh.CallRequestRefresh();\n    }\n\n    private void DoRefresh()\n    {\n        StateHasChanged();\n    }\n\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Helpers/BlazorLayoutComponent.cs",
    "content": "﻿using Microsoft.AspNetCore.Components;\n\nnamespace BlazorAdmin.Helpers;\n\npublic class BlazorLayoutComponent : LayoutComponentBase\n{\n    private readonly RefreshBroadcast _refresh = RefreshBroadcast.Instance;\n\n    protected override void OnInitialized()\n    {\n        _refresh.RefreshRequested += DoRefresh;\n        base.OnInitialized();\n    }\n\n    public void CallRequestRefresh()\n    {\n        _refresh.CallRequestRefresh();\n    }\n\n    private void DoRefresh()\n    {\n        StateHasChanged();\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Helpers/RefreshBroadcast.cs",
    "content": "﻿using System;\n\nnamespace BlazorAdmin.Helpers;\n\ninternal sealed class RefreshBroadcast\n{\n    private static readonly Lazy<RefreshBroadcast>\n        Lazy =\n            new Lazy<RefreshBroadcast>\n                (() => new RefreshBroadcast());\n\n    public static RefreshBroadcast Instance => Lazy.Value;\n\n    private RefreshBroadcast()\n    {\n    }\n\n    public event Action RefreshRequested;\n    public void CallRequestRefresh()\n    {\n        RefreshRequested?.Invoke();\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Helpers/ToastComponent.cs",
    "content": "﻿using System;\nusing BlazorAdmin.Services;\nusing Microsoft.AspNetCore.Components;\n\nnamespace BlazorAdmin.Helpers;\n\npublic class ToastComponent : ComponentBase, IDisposable\n{\n    [Inject]\n    ToastService ToastService\n    {\n        get;\n        set;\n    }\n    protected string Heading\n    {\n        get;\n        set;\n    }\n    protected string Message\n    {\n        get;\n        set;\n    }\n    protected bool IsVisible\n    {\n        get;\n        set;\n    }\n    protected string BackgroundCssClass\n    {\n        get;\n        set;\n    }\n    protected string IconCssClass\n    {\n        get;\n        set;\n    }\n    protected override void OnInitialized()\n    {\n        ToastService.OnShow += ShowToast;\n        ToastService.OnHide += HideToast;\n    }\n    private void ShowToast(string message, ToastLevel level)\n    {\n        BuildToastSettings(level, message);\n        IsVisible = true;\n        StateHasChanged();\n    }\n    private void HideToast()\n    {\n        IsVisible = false;\n        StateHasChanged();\n    }\n    private void BuildToastSettings(ToastLevel level, string message)\n    {\n        switch (level)\n        {\n            case ToastLevel.Info:\n                BackgroundCssClass = \"bg-info\";\n                IconCssClass = \"info\";\n                Heading = \"Info\";\n                break;\n            case ToastLevel.Success:\n                BackgroundCssClass = \"bg-success\";\n                IconCssClass = \"check\";\n                Heading = \"Success\";\n                break;\n            case ToastLevel.Warning:\n                BackgroundCssClass = \"bg-warning\";\n                IconCssClass = \"exclamation\";\n                Heading = \"Warning\";\n                break;\n            case ToastLevel.Error:\n                BackgroundCssClass = \"bg-danger\";\n                IconCssClass = \"times\";\n                Heading = \"Error\";\n                break;\n        }\n        Message = message;\n    }\n    public void Dispose()\n    {\n        ToastService.OnShow -= ShowToast;\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/JavaScript/Cookies.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.JSInterop;\n\nnamespace BlazorAdmin.JavaScript;\n\npublic class Cookies\n{\n    private readonly IJSRuntime _jsRuntime;\n\n    public Cookies(IJSRuntime jsRuntime)\n    {\n        _jsRuntime = jsRuntime;\n    }\n\n    public async Task DeleteCookie(string name)\n    {\n        await _jsRuntime.InvokeAsync<string>(JSInteropConstants.DeleteCookie, name);\n    }\n\n    public async Task<string> GetCookie(string name)\n    {\n        return await _jsRuntime.InvokeAsync<string>(JSInteropConstants.GetCookie, name);\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/JavaScript/Css.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.JSInterop;\n\nnamespace BlazorAdmin.JavaScript;\n\npublic class Css\n{\n    private readonly IJSRuntime _jsRuntime;\n\n    public Css(IJSRuntime jsRuntime)\n    {\n        _jsRuntime = jsRuntime;\n    }\n\n    public async Task ShowBodyOverflow()\n    {\n        await _jsRuntime.InvokeAsync<string>(JSInteropConstants.ShowBodyOverflow);\n    }\n\n    public async Task<string> HideBodyOverflow()\n    {\n        return await _jsRuntime.InvokeAsync<string>(JSInteropConstants.HideBodyOverflow);\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/JavaScript/JSInteropConstants.cs",
    "content": "﻿namespace BlazorAdmin.JavaScript;\n\npublic static class JSInteropConstants\n{\n    public static string DeleteCookie => \"deleteCookie\";\n    public static string GetCookie => \"getCookie\";\n    public static string RouteOutside => \"routeOutside\";\n    public static string HideBodyOverflow => \"hideBodyOverflow\";\n    public static string ShowBodyOverflow => \"showBodyOverflow\";\n}\n"
  },
  {
    "path": "src/BlazorAdmin/JavaScript/Route.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.JSInterop;\n\nnamespace BlazorAdmin.JavaScript;\n\npublic class Route\n{\n    private readonly IJSRuntime _jsRuntime;\n\n    public Route(IJSRuntime jsRuntime)\n    {\n        _jsRuntime = jsRuntime;\n    }\n\n    public async Task RouteOutside(string path)\n    {\n        await _jsRuntime.InvokeAsync<string>(JSInteropConstants.RouteOutside, path);\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Pages/CatalogItemPage/Create.razor",
    "content": "﻿@inject ILogger<Create> Logger\n@inject IJSRuntime JSRuntime\n@inject ICatalogItemService CatalogItemService\n\n@inherits BlazorAdmin.Helpers.BlazorComponent\n\n@namespace BlazorAdmin.Pages.CatalogItemPage\n\n<div class=\"modal @_modalClass\" tabindex=\"-1\" role=\"dialog\" style=\"display:@_modalDisplay\">\n    <div class=\"modal-dialog\" role=\"document\">\n        <div class=\"modal-content\">\n            <EditForm Model=\"_item\" OnValidSubmit=\"@CreateClick\">\n                <DataAnnotationsValidator />\n                <div class=\"modal-header\">\n                    <h5 class=\"modal-title\" id=\"exampleModalLabel\">Create</h5>\n                    <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\" @onclick=\"Close\">\n                        <span aria-hidden=\"true\">&times;</span>\n                    </button>\n                </div>\n                <div class=\"modal-body\">\n                    @if (_item == null)\n                    {\n                        <Spinner></Spinner>\n                    }\n                    else\n                    {\n                        <div class=\"container\">\n                            <div class=\"row\">\n                                @if (HasPicture)\n                                {\n                                    <img class=\"col-md-6 esh-picture\" src=\"@LoadPicture\">\n                                }\n                                <div class=\"col-md-@(HasPicture?\"6\":\"12\")\">\n                                    <div class=\"form-group\">\n                                        <label class=\"control-label col-md-6\">Name</label>\n                                        <div class=\"col-md-12\">\n                                            <InputText class=\"form-control\" @bind-Value=\"_item.Name\" />\n                                            <ValidationMessage For=\"(() => _item.Name)\" />\n                                        </div>\n                                    </div>\n\n                                    <div class=\"form-group\">\n                                        <label class=\"control-label col-md-6\">Description</label>\n                                        <div class=\"col-md-12\">\n                                            <InputText class=\"form-control\" @bind-Value=\"_item.Description\" />\n                                            <ValidationMessage For=\"(() => _item.Description)\" />\n                                        </div>\n                                    </div>\n                                </div>\n                                <div class=\"col-md-12\">\n                                    <div class=\"form-group\">\n                                        <label class=\"control-label col-md-6\">Brand</label>\n                                        <div class=\"col-md-12\">\n                                            <InputSelect @bind-Value=\"_item.CatalogBrandId\" class=\"form-control\">\n                                                @foreach (var brand in Brands)\n                                                {\n                                                    <option value=\"@brand.Id\">@brand.Name</option>\n                                                }\n                                            </InputSelect>\n                                            <ValidationMessage For=\"(() => _item.CatalogBrandId)\" />\n                                        </div>\n                                    </div>\n\n                                    <div class=\"form-group\">\n                                        <label class=\"control-label col-md-6\">Type</label>\n                                        <div class=\"col-md-12\">\n                                            <InputSelect @bind-Value=\"_item.CatalogTypeId\" class=\"form-control\">\n                                                @foreach (var type in Types)\n                                                {\n                                                    <option value=\"@type.Id\">@type.Name</option>\n                                                }\n                                            </InputSelect>\n                                            <ValidationMessage For=\"(() => _item.CatalogTypeId)\" />\n                                        </div>\n                                    </div>\n\n                                    <div class=\"form-group\">\n                                        <label class=\"control-label col-md-6\">Price</label>\n                                        <div class=\"col-md-12\">\n                                            <InputNumber @bind-Value=\"_item.Price\" class=\"form-control\" />\n                                            <ValidationMessage For=\"(() => _item.Price)\" />\n                                        </div>\n                                    </div>                                   \n                                </div>\n                            </div>\n                        </div>\n                    }\n                </div>\n                <div class=\"modal-footer\">\n                    <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\" @onclick=\"Close\">Cancel</button>\n                    <button type=\"submit\" class=\"btn btn-primary\">\n                        Create\n                    </button>\n                </div>\n            </EditForm>\n        </div>\n    </div>\n</div>\n\n\n\n@if (_showCreateModal)\n{\n    <div class=\"modal-backdrop fade show\"></div>\n}\n\n@code {\n\n        [Parameter]\n        public IEnumerable<CatalogBrand> Brands { get; set; }\n        [Parameter]\n        public IEnumerable<CatalogType> Types { get; set; }\n\n        [Parameter]\n        public EventCallback<string> OnSaveClick { get; set; }\n\n    private string LoadPicture => string.IsNullOrEmpty(_item.PictureBase64) ? string.Empty : $\"data:image/png;base64, {_item.PictureBase64}\";\n    private bool HasPicture => !string.IsNullOrEmpty(_item.PictureBase64);\n    private string _badFileMessage = string.Empty;\n    private string _modalDisplay = \"none;\";\n    private string _modalClass = \"\";\n    private bool _showCreateModal = false;\n    private CreateCatalogItemRequest _item = new CreateCatalogItemRequest();\n\n    private async Task CreateClick()\n    {\n        var result = await CatalogItemService.Create(_item);\n        if (result != null)\n        {\n            await OnSaveClick.InvokeAsync(null);\n            await Close();\n        }\n    }\n\n    public async Task Open()\n    {\n    \n        Logger.LogInformation(\"Now loading... /Catalog/Create\");\n\n        await new Css(JSRuntime).HideBodyOverflow();\n\n        _item = new CreateCatalogItemRequest\n        {\n            CatalogTypeId = Types.First().Id,\n            CatalogBrandId = Brands.First().Id\n        };\n\n        _modalDisplay = \"block;\";\n        _modalClass = \"Show\";\n        _showCreateModal = true;\n\n        StateHasChanged();\n    }\n\n    private async Task Close()\n    {\n        await new Css(JSRuntime).ShowBodyOverflow();\n        _modalDisplay = \"none\";\n        _modalClass = \"\";\n        _showCreateModal = false;\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Pages/CatalogItemPage/Delete.razor",
    "content": "﻿@inject ILogger<Delete> Logger\n@inject IJSRuntime JSRuntime\n@inject ICatalogItemService CatalogItemService\n\n@inherits BlazorAdmin.Helpers.BlazorComponent\n\n@namespace BlazorAdmin.Pages.CatalogItemPage\n\n<div class=\"modal @_modalClass\" tabindex=\"-1\" role=\"dialog\" style=\"display:@_modalDisplay\">\n    <div class=\"modal-dialog\" role=\"document\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\" id=\"exampleModalLabel\">Delete @_item.Name</h5>\n                <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\" @onclick=\"Close\">\n                    <span aria-hidden=\"true\">&times;</span>\n                </button>\n            </div>\n            <div class=\"modal-body\">\n                @if (_item == null)\n                {\n                    <Spinner></Spinner>\n                }\n                else\n                {\n                    <div class=\"container\">\n                        <div class=\"row\">\n                            @if (HasPicture)\n                            {\n                                <img class=\"col-md-6 esh-picture\" src=\"@($\"{_item.PictureUri}\")\">\n                            }\n                            <dl class=\"col-md-@(HasPicture ? \"6\" : \"12\") dl-horizontal\">\n                                <dt>\n                                    Name\n                                </dt>\n\n                                <dd>\n                                    @_item.Name\n                                </dd>\n\n                                <dt>\n                                    Description\n                                </dt>\n\n                                <dd>\n                                    @_item.Description\n                                </dd>\n\n                                <dt>\n                                    Brand\n                                </dt>\n\n                                <dd>\n                                    @_item.CatalogBrand\n                                </dd>\n\n                                <dt>\n                                    Type\n                                </dt>\n\n                                <dd>\n                                    @_item.CatalogType\n                                </dd>\n                                <dt>\n                                    Price\n                                </dt>\n\n                                <dd>\n                                    @_item.Price\n                                </dd>\n                            </dl>\n                        </div>\n\n                    </div>\n                }\n\n            </div>\n            <div class=\"modal-footer\">\n                <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\" @onclick=\"Close\">Cancel</button>\n                <button class=\"btn btn-danger\" @onclick=\"() => DeleteClick(_item.Id)\">\n                    Delete\n                </button>\n            </div>\n        </div>\n    </div>\n</div>\n\n@if (_showDeleteModal)\n{\n    <div class=\"modal-backdrop fade show\"></div>\n}\n\n\n@code {\n    [Parameter]\n    public IEnumerable<CatalogBrand> Brands { get; set; }\n    [Parameter]\n    public IEnumerable<CatalogType> Types { get; set; }\n\n    [Parameter]\n    public EventCallback<string> OnSaveClick { get; set; }\n\n    private bool HasPicture => !string.IsNullOrEmpty(_item.PictureUri);\n    private string _modalDisplay = \"none;\";\n    private string _modalClass = \"\";\n    private bool _showDeleteModal = false;\n    private CatalogItem _item = new CatalogItem();\n\n    private async Task DeleteClick(int id)\n    {\n        // TODO: Add some kind of \"are you sure\" check before this\n\n        await CatalogItemService.Delete(id);\n\n        await OnSaveClick.InvokeAsync(null);\n        await Close();\n    }\n\n    public async Task Open(int id)\n    {\n        Logger.LogInformation(\"Now loading... /Catalog/Delete/{Id}\", id);\n\n        await new Css(JSRuntime).HideBodyOverflow();\n\n        _item = await CatalogItemService.GetById(id);\n\n        _modalDisplay = \"block;\";\n        _modalClass = \"Show\";\n        _showDeleteModal = true;\n\n        StateHasChanged();\n    }\n\n    private async Task Close()\n    {\n        await new Css(JSRuntime).ShowBodyOverflow();\n        _modalDisplay = \"none\";\n        _modalClass = \"\";\n        _showDeleteModal = false;\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Pages/CatalogItemPage/Details.razor",
    "content": "﻿@inject ILogger<Details> Logger\n@inject IJSRuntime JSRuntime\n@inject ICatalogItemService CatalogItemService\n\n@inherits BlazorAdmin.Helpers.BlazorComponent\n\n@namespace BlazorAdmin.Pages.CatalogItemPage\n\n<div class=\"modal @_modalClass\" tabindex=\"-1\" role=\"dialog\" style=\"display:@_modalDisplay\">\n    <div class=\"modal-dialog\" role=\"document\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\" id=\"exampleModalLabel\">Details @_item.Name</h5>\n                <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\" @onclick=\"Close\">\n                    <span aria-hidden=\"true\">&times;</span>\n                </button>\n            </div>\n            <div class=\"modal-body\">\n\n                @if (_item == null)\n                {\n                    <Spinner></Spinner>\n                }\n                else\n                {\n                    <div class=\"container\">\n                        <div class=\"row\">\n                            @if (HasPicture)\n                            {\n                                <img class=\"col-md-6 esh-picture\" src=\"@($\"{_item.PictureUri}\")\">\n                            }\n\n\n                            <dl class=\"col-md-@(HasPicture?\"6\":\"12\") dl-horizontal\">\n                                <dt>\n                                    Name\n                                </dt>\n\n                                <dd>\n                                    @_item.Name\n                                </dd>\n\n                                <dt>\n                                    Description\n                                </dt>\n\n                                <dd>\n                                    @_item.Description\n                                </dd>\n\n                                <dt>\n                                    Brand\n                                </dt>\n\n                                <dd>\n                                    @_item.CatalogBrand\n                                </dd>\n\n                                <dt>\n                                    Type\n                                </dt>\n\n                                <dd>\n                                    @_item.CatalogType\n                                </dd>\n                                <dt>\n                                    Price\n                                </dt>\n\n                                <dd>\n                                    @_item.Price\n                                </dd>\n\n                            </dl>\n                        </div>\n\n                    </div>\n                }\n\n            </div>\n            <div class=\"modal-footer\">\n                <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\" @onclick=\"Close\">Close</button>\n                <button class=\"btn btn-primary\" @onclick=\"EditClick\">\n                    Edit\n                </button>\n            </div>\n        </div>\n    </div>\n</div>\n\n@if (_showDetailsModal)\n{\n    <div class=\"modal-backdrop fade show\"></div>\n}\n\n@code {\n    [Parameter]\n    public IEnumerable<CatalogBrand> Brands { get; set; }\n    [Parameter]\n    public IEnumerable<CatalogType> Types { get; set; }\n\n    [Parameter]\n    public EventCallback<int> OnEditClick { get; set; }\n\n    private bool HasPicture => !string.IsNullOrEmpty(_item.PictureUri);\n    private string _modalDisplay = \"none;\";\n    private string _modalClass = \"\";\n    private bool _showDetailsModal = false;\n    private CatalogItem _item = new CatalogItem();\n\n    public async Task EditClick()\n    {\n        await OnEditClick.InvokeAsync(_item.Id);\n        await Close();\n    }\n\n    public async Task Open(int id)\n    {\n\n        Logger.LogInformation(\"Now loading... /Catalog/Details/{Id}\", id);\n\n        await new Css(JSRuntime).HideBodyOverflow();\n\n        _item = await CatalogItemService.GetById(id);\n\n        _modalDisplay = \"block;\";\n        _modalClass = \"Show\";\n        _showDetailsModal = true;\n\n        StateHasChanged();\n    }\n\n    public async Task Close()\n    {\n        await new Css(JSRuntime).ShowBodyOverflow();\n\n        _modalDisplay = \"none\";\n        _modalClass = \"\";\n        _showDetailsModal = false;\n    }\n\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Pages/CatalogItemPage/Edit.razor",
    "content": "﻿@inject ILogger<Edit> Logger\n@inject IJSRuntime JSRuntime\n@inject ICatalogItemService CatalogItemService\n\n@inherits BlazorAdmin.Helpers.BlazorComponent\n\n@namespace BlazorAdmin.Pages.CatalogItemPage\n\n<div class=\"modal @_modalClass\" tabindex=\"-1\" role=\"dialog\" style=\"display:@_modalDisplay\">\n    <div class=\"modal-dialog\" role=\"document\">\n        <div class=\"modal-content\">\n            <EditForm Model=\"_item\" OnValidSubmit=\"@SaveClick\">\n                <DataAnnotationsValidator />\n                <div class=\"modal-header\">\n                    <h5 class=\"modal-title\" id=\"exampleModalLabel\">Edit @_item.Name</h5>\n                    <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\" @onclick=\"Close\">\n                        <span aria-hidden=\"true\">&times;</span>\n                    </button>\n                </div>\n                <div class=\"modal-body\">\n\n                    @if (_item == null)\n                    {\n                        <Spinner></Spinner>\n                    }\n                    else\n                    {\n                        <div class=\"container\">\n                            <div class=\"row\">\n                                @if (HasPicture)\n                                {\n                                    <img class=\"col-md-6 esh-picture\" src=\"@LoadPicture\">\n                                }\n                                <div class=\"col-md-@(HasPicture?\"6\":\"12\") \">\n\n                                    <div class=\"form-group\">\n                                        <label class=\"control-label col-md-6\">Name</label>\n                                        <div class=\"col-md-12\">\n                                            <InputText class=\"form-control\" @bind-Value=\"_item.Name\" />\n                                            <ValidationMessage For=\"(() => _item.Name)\" />\n                                        </div>\n                                    </div>\n\n                                    <div class=\"form-group\">\n                                        <label class=\"control-label col-md-6\">Description</label>\n                                        <div class=\"col-md-12\">\n                                            <InputText class=\"form-control\" @bind-Value=\"_item.Description\" />\n                                            <ValidationMessage For=\"(() => _item.Description)\" />\n                                        </div>\n                                    </div>\n\n                                </div>\n                                <div class=\"col-md-12\">\n                                    <div class=\"form-group\">\n                                        <label class=\"control-label col-md-6\">Brand</label>\n                                        <div class=\"col-md-12\">\n                                            <CustomInputSelect @bind-Value=\"_item.CatalogBrandId\" class=\"form-control\">\n                                                @foreach (var brand in Brands)\n                                                    {\n                                                    <option value=\"@brand.Id.ToString()\">@brand.Name</option>\n                                                    }\n                                            </CustomInputSelect>\n                                            <ValidationMessage For=\"(() => _item.CatalogBrandId)\" />\n                                        </div>\n                                    </div>\n\n                                    <div class=\"form-group\">\n                                        <label class=\"control-label col-md-6\">Type</label>\n                                        <div class=\"col-md-12\">\n                                            <CustomInputSelect @bind-Value=\"_item.CatalogTypeId\" class=\"form-control\">\n                                                @foreach (var type in Types)\n                                                    {\n                                                    <option value=\"@type.Id\">@type.Name</option>\n                                                    }\n                                            </CustomInputSelect>\n                                            <ValidationMessage For=\"(() => _item.CatalogTypeId)\" />\n                                        </div>\n                                    </div>\n\n                                    <div class=\"form-group\">\n                                        <label class=\"control-label col-md-6\">Price</label>\n                                        <div class=\"col-md-12\">\n                                            <InputNumber @bind-Value=\"_item.Price\" class=\"form-control\" />\n                                            <ValidationMessage For=\"(() => _item.Price)\" />\n                                        </div>\n                                    </div>                                  \n                                </div>\n                            </div>\n                        </div>\n                    }\n                </div>\n                <div class=\"modal-footer\">\n                    <button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\" @onclick=\"Close\">Cancel</button>\n                    <button type=\"submit\" class=\"btn btn-primary\">\n                        Save\n                    </button>\n                </div>\n            </EditForm>\n        </div>\n    </div>\n</div>\n\n\n\n@if (_showEditModal)\n{\n    <div class=\"modal-backdrop fade show\"></div>\n}\n\n@code {\n    [Parameter]\n    public IEnumerable<CatalogBrand> Brands { get; set; }\n    [Parameter]\n    public IEnumerable<CatalogType> Types { get; set; }\n\n    [Parameter]\n    public EventCallback<string> OnSaveClick { get; set; }\n\n    private string LoadPicture => string.IsNullOrEmpty(_item.PictureBase64) ? string.IsNullOrEmpty(_item.PictureUri) ? string.Empty : $\"{_item.PictureUri}\" : $\"data:image/png;base64, {_item.PictureBase64}\";\n    private bool HasPicture => !(string.IsNullOrEmpty(_item.PictureBase64) && string.IsNullOrEmpty(_item.PictureUri));\n    private string _badFileMessage = string.Empty;\n    private string _modalDisplay = \"none;\";\n    private string _modalClass = \"\";\n    private bool _showEditModal = false;\n    private CatalogItem _item = new CatalogItem();\n\n    private async Task SaveClick()\n    {\n        await CatalogItemService.Edit(_item);\n        await OnSaveClick.InvokeAsync(null);\n        await Close();\n    }\n\n    public async Task Open(int id)\n    {\n        Logger.LogInformation(\"Now loading... /Catalog/Edit/{Id}\", id);\n\n        await new Css(JSRuntime).HideBodyOverflow();\n\n        _item = await CatalogItemService.GetById(id);\n\n        _modalDisplay = \"block;\";\n        _modalClass = \"Show\";\n        _showEditModal = true;\n\n        StateHasChanged();\n    }\n\n    private async Task Close()\n    {\n        await new Css(JSRuntime).ShowBodyOverflow();\n\n        _modalDisplay = \"none\";\n        _modalClass = \"\";\n        _showEditModal = false;\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Pages/CatalogItemPage/List.razor",
    "content": "﻿@page \"/admin\"\n@attribute [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)]\n@inherits BlazorAdmin.Helpers.BlazorComponent\n@namespace BlazorAdmin.Pages.CatalogItemPage\n\n<PageTitle>eShopOnWeb Admin: Manage Product Catalog</PageTitle>\n\n<h1>Manage Product Catalog</h1>\n\n@if (catalogItems == null)\n{\n    <Spinner></Spinner>\n}\nelse\n{\n\n    <p class=\"esh-link-wrapper\">\n        <button class=\"btn btn-primary\" @onclick=\"@(CreateClick)\">\n            Create New\n        </button>\n    </p>\n\n    <table class=\"table table-striped table-hover\">\n        <thead>\n            <tr>\n                <th></th>\n                <th>Item Type</th>\n                <th>Brand</th>\n                <th>Id</th>\n                <th>Name</th>\n                <th>@nameof(CatalogItem.Description)</th>\n                <th>@nameof(CatalogItem.Price)</th>\n                <th>Actions</th>\n            </tr>\n        </thead>\n        <tbody class=\"cursor-pointer\">\n            @foreach (var item in catalogItems)\n            {\n                <tr @onclick=\"@(() => DetailsClick(item.Id))\">\n                    <td>\n                        <img class=\"img-thumbnail\" src=\"@($\"{item.PictureUri}\")\">\n                    </td>\n                    <td>@item.CatalogType</td>\n                    <td>@item.CatalogBrand</td>\n                    <td>@item.Id</td>\n                    <td>@item.Name</td>\n                    <td>@item.Description</td>\n                    <td>@item.Price</td>\n                    <td>\n                        <button @onclick=\"@(() => EditClick(item.Id))\" @onclick:stopPropagation=\"true\" class=\"btn btn-primary\">\n                            Edit\n                        </button>\n\n                        <button @onclick=\"@(() => DeleteClick(item.Id))\" @onclick:stopPropagation=\"true\" class=\"btn btn-danger\">\n                            Delete\n                        </button>\n                    </td>\n                </tr>\n            }\n        </tbody>\n    </table>\n\n    <Details Brands=\"@catalogBrands\" Types=\"@catalogTypes\" OnEditClick=\"EditClick\" @ref=\"DetailsComponent\"></Details>\n    <Edit Brands=\"@catalogBrands\" Types=\"@catalogTypes\" OnSaveClick=\"ReloadCatalogItems\" @ref=\"EditComponent\"></Edit>\n    <Create Brands=\"@catalogBrands\" Types=\"@catalogTypes\" OnSaveClick=\"ReloadCatalogItems\" @ref=\"CreateComponent\"></Create>\n    <Delete Brands=\"@catalogBrands\" Types=\"@catalogTypes\" OnSaveClick=\"ReloadCatalogItems\" @ref=\"DeleteComponent\"></Delete>\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Pages/CatalogItemPage/List.razor.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading.Tasks;\nusing BlazorAdmin.Helpers;\nusing BlazorShared.Interfaces;\nusing BlazorShared.Models;\n\nnamespace BlazorAdmin.Pages.CatalogItemPage;\n\npublic partial class List : BlazorComponent\n{\n    [Microsoft.AspNetCore.Components.Inject]\n    public ICatalogItemService CatalogItemService { get; set; }\n\n    [Microsoft.AspNetCore.Components.Inject]\n    public ICatalogLookupDataService<CatalogBrand> CatalogBrandService { get; set; }\n\n    [Microsoft.AspNetCore.Components.Inject]\n    public ICatalogLookupDataService<CatalogType> CatalogTypeService { get; set; }\n\n    private List<CatalogItem> catalogItems = new List<CatalogItem>();\n    private List<CatalogType> catalogTypes = new List<CatalogType>();\n    private List<CatalogBrand> catalogBrands = new List<CatalogBrand>();\n\n    private Edit EditComponent { get; set; }\n    private Delete DeleteComponent { get; set; }\n    private Details DetailsComponent { get; set; }\n    private Create CreateComponent { get; set; }\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        if (firstRender)\n        {\n            catalogItems = await CatalogItemService.List();\n            catalogTypes = await CatalogTypeService.List();\n            catalogBrands = await CatalogBrandService.List();\n\n            CallRequestRefresh();\n        }\n\n        await base.OnAfterRenderAsync(firstRender);\n    }\n\n    private async void DetailsClick(int id)\n    {\n        await DetailsComponent.Open(id);\n    }\n\n    private async Task CreateClick()\n    {\n        await CreateComponent.Open();\n    }\n\n    private async Task EditClick(int id)\n    {\n        await EditComponent.Open(id);\n    }\n\n    private async Task DeleteClick(int id)\n    {\n        await DeleteComponent.Open(id);\n    }\n\n    private async Task ReloadCatalogItems()\n    {\n        catalogItems = await CatalogItemService.List();\n        StateHasChanged();\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Pages/Logout.razor",
    "content": "﻿@page \"/logout\"\n@inject IJSRuntime JSRuntime\n@inject HttpClient HttpClient\n@inherits BlazorAdmin.Helpers.BlazorComponent\n\n@code {\n\n    protected override async Task OnInitializedAsync()\n    {\n        await HttpClient.PostAsync(\"User/Logout\", null);\n        await new Route(JSRuntime).RouteOutside(\"/Identity/Account/Login\");\n    }\n\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Program.cs",
    "content": "﻿using System;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing BlazorAdmin;\nusing BlazorAdmin.Services;\nusing Blazored.LocalStorage;\nusing BlazorShared;\nusing BlazorShared.Models;\nusing Microsoft.AspNetCore.Components.Authorization;\nusing Microsoft.AspNetCore.Components.Web;\nusing Microsoft.AspNetCore.Components.WebAssembly.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\n\nvar builder = WebAssemblyHostBuilder.CreateDefault(args);\nbuilder.RootComponents.Add<App>(\"#admin\");\nbuilder.RootComponents.Add<HeadOutlet>(\"head::after\");\n\nvar configSection = builder.Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME);\nbuilder.Services.Configure<BaseUrlConfiguration>(configSection);\n\nbuilder.Services.AddScoped(sp => new HttpClient() { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });\n\nbuilder.Services.AddScoped<ToastService>();\nbuilder.Services.AddScoped<HttpService>();\n\nbuilder.Services.AddBlazoredLocalStorage();\n\nbuilder.Services.AddAuthorizationCore();\nbuilder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();\nbuilder.Services.AddScoped(sp => (CustomAuthStateProvider)sp.GetRequiredService<AuthenticationStateProvider>());\n\nbuilder.Services.AddBlazorServices();\n\nbuilder.Logging.AddConfiguration(builder.Configuration.GetRequiredSection(\"Logging\"));\n\nawait ClearLocalStorageCache(builder.Services);\n\nawait builder.Build().RunAsync();\n\nstatic async Task ClearLocalStorageCache(IServiceCollection services)\n{\n    var sp = services.BuildServiceProvider();\n    var localStorageService = sp.GetRequiredService<ILocalStorageService>();\n\n    await localStorageService.RemoveItemAsync(typeof(CatalogBrand).Name);\n    await localStorageService.RemoveItemAsync(typeof(CatalogType).Name);\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Properties/launchSettings.json",
    "content": "{\n  \"iisSettings\": {\n    \"windowsAuthentication\": false,\n    \"anonymousAuthentication\": true,\n    \"iisExpress\": {\n      \"applicationUrl\": \"http://localhost:58126\",\n      \"sslPort\": 44315\n    }\n  },\n  \"profiles\": {\n    \"IIS Express\": {\n      \"commandName\": \"IISExpress\",\n      \"launchBrowser\": true,\n      \"inspectUri\": \"{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}\",\n      \"applicationUrl\": \"https://localhost:5001;http://localhost:5000\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      }\n    },\n    \"BlazorAdmin\": {\n      \"commandName\": \"Project\",\n      \"launchBrowser\": true,\n      \"inspectUri\": \"{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}\",\n      \"applicationUrl\": \"https://localhost:5001;http://localhost:5000\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Services/CacheEntry.cs",
    "content": "﻿using System;\n\nnamespace BlazorAdmin.Services;\n\npublic class CacheEntry<T>\n{\n    public CacheEntry(T item)\n    {\n        Value = item;\n    }\n    public CacheEntry()\n    {\n\n    }\n\n    public T Value { get; set; }\n    public DateTime DateCreated { get; set; } = DateTime.UtcNow;\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Services/CachedCatalogItemServiceDecorator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Blazored.LocalStorage;\nusing BlazorShared.Interfaces;\nusing BlazorShared.Models;\nusing Microsoft.Extensions.Logging;\n\nnamespace BlazorAdmin.Services;\n\npublic class CachedCatalogItemServiceDecorator : ICatalogItemService\n{\n    private readonly ILocalStorageService _localStorageService;\n    private readonly CatalogItemService _catalogItemService;\n    private ILogger<CachedCatalogItemServiceDecorator> _logger;\n\n    public CachedCatalogItemServiceDecorator(ILocalStorageService localStorageService,\n        CatalogItemService catalogItemService,\n        ILogger<CachedCatalogItemServiceDecorator> logger)\n    {\n        _localStorageService = localStorageService;\n        _catalogItemService = catalogItemService;\n        _logger = logger;\n    }\n\n    public async Task<List<CatalogItem>> ListPaged(int pageSize)\n    {\n        string key = \"items\";\n        var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<CatalogItem>>>(key);\n        if (cacheEntry != null)\n        {\n            _logger.LogInformation(\"Loading items from local storage.\");\n            if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow)\n            {\n                return cacheEntry.Value;\n            }\n            else\n            {\n                _logger.LogInformation($\"Loading {key} from local storage.\");\n                await _localStorageService.RemoveItemAsync(key);\n            }\n        }\n\n        var items = await _catalogItemService.ListPaged(pageSize);\n        var entry = new CacheEntry<List<CatalogItem>>(items);\n        await _localStorageService.SetItemAsync(key, entry);\n        return items;\n    }\n\n    public async Task<List<CatalogItem>> List()\n    {\n        string key = \"items\";\n        var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<CatalogItem>>>(key);\n        if (cacheEntry != null)\n        {\n            _logger.LogInformation(\"Loading items from local storage.\");\n            if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow)\n            {\n                return cacheEntry.Value;\n            }\n            else\n            {\n                _logger.LogInformation($\"Loading {key} from local storage.\");\n                await _localStorageService.RemoveItemAsync(key);\n            }\n        }\n\n        var items = await _catalogItemService.List();\n        var entry = new CacheEntry<List<CatalogItem>>(items);\n        await _localStorageService.SetItemAsync(key, entry);\n        return items;\n    }\n\n    public async Task<CatalogItem> GetById(int id)\n    {\n        return (await List()).FirstOrDefault(x => x.Id == id);\n    }\n\n    public async Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem)\n    {\n        var result = await _catalogItemService.Create(catalogItem);\n        await RefreshLocalStorageList();\n\n        return result;\n    }\n\n    public async Task<CatalogItem> Edit(CatalogItem catalogItem)\n    {\n        var result = await _catalogItemService.Edit(catalogItem);\n        await RefreshLocalStorageList();\n\n        return result;\n    }\n\n    public async Task<string> Delete(int id)\n    {\n        var result = await _catalogItemService.Delete(id);\n        await RefreshLocalStorageList();\n\n        return result;\n    }\n\n    private async Task RefreshLocalStorageList()\n    {\n        string key = \"items\";\n\n        await _localStorageService.RemoveItemAsync(key);\n        var items = await _catalogItemService.List();\n        var entry = new CacheEntry<List<CatalogItem>>(items);\n        await _localStorageService.SetItemAsync(key, entry);\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Services/CachedCatalogLookupDataServiceDecorator .cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Blazored.LocalStorage;\nusing BlazorShared.Interfaces;\nusing BlazorShared.Models;\nusing Microsoft.Extensions.Logging;\n\nnamespace BlazorAdmin.Services;\n\npublic class CachedCatalogLookupDataServiceDecorator<TLookupData, TReponse>\n    : ICatalogLookupDataService<TLookupData>\n    where TLookupData : LookupData\n    where TReponse : ILookupDataResponse<TLookupData>\n{\n    private readonly ILocalStorageService _localStorageService;\n    private readonly CatalogLookupDataService<TLookupData, TReponse> _catalogTypeService;\n    private ILogger<CachedCatalogLookupDataServiceDecorator<TLookupData, TReponse>> _logger;\n\n    public CachedCatalogLookupDataServiceDecorator(ILocalStorageService localStorageService,\n        CatalogLookupDataService<TLookupData, TReponse> catalogTypeService,\n        ILogger<CachedCatalogLookupDataServiceDecorator<TLookupData, TReponse>> logger)\n    {\n        _localStorageService = localStorageService;\n        _catalogTypeService = catalogTypeService;\n        _logger = logger;\n    }\n\n    public async Task<List<TLookupData>> List()\n    {\n        string key = typeof(TLookupData).Name;\n        var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<TLookupData>>>(key);\n        if (cacheEntry != null)\n        {\n            _logger.LogInformation($\"Loading {key} from local storage.\");\n            if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow)\n            {\n                return cacheEntry.Value;\n            }\n            else\n            {\n                _logger.LogInformation($\"Cache expired; removing {key} from local storage.\");\n                await _localStorageService.RemoveItemAsync(key);\n            }\n        }\n\n        var types = await _catalogTypeService.List();\n        var entry = new CacheEntry<List<TLookupData>>(types);\n        await _localStorageService.SetItemAsync(key, entry);\n        return types;\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Services/CatalogItemService.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing BlazorShared.Interfaces;\nusing BlazorShared.Models;\nusing Microsoft.Extensions.Logging;\n\n\nnamespace BlazorAdmin.Services;\n\npublic class CatalogItemService : ICatalogItemService\n{\n    private readonly ICatalogLookupDataService<CatalogBrand> _brandService;\n    private readonly ICatalogLookupDataService<CatalogType> _typeService;\n    private readonly HttpService _httpService;\n    private readonly ILogger<CatalogItemService> _logger;\n\n    public CatalogItemService(ICatalogLookupDataService<CatalogBrand> brandService,\n        ICatalogLookupDataService<CatalogType> typeService,\n        HttpService httpService,\n        ILogger<CatalogItemService> logger)\n    {\n        _brandService = brandService;\n        _typeService = typeService;\n        _httpService = httpService;\n        _logger = logger;\n    }\n\n    public async Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem)\n    {\n        var response = await _httpService.HttpPost<CreateCatalogItemResponse>(\"catalog-items\", catalogItem);\n        return response?.CatalogItem;\n    }\n\n    public async Task<CatalogItem> Edit(CatalogItem catalogItem)\n    {\n        return (await _httpService.HttpPut<EditCatalogItemResult>(\"catalog-items\", catalogItem)).CatalogItem;\n    }\n\n    public async Task<string> Delete(int catalogItemId)\n    {\n        return (await _httpService.HttpDelete<DeleteCatalogItemResponse>(\"catalog-items\", catalogItemId)).Status;\n    }\n\n    public async Task<CatalogItem> GetById(int id)\n    {\n        var brandListTask = _brandService.List();\n        var typeListTask = _typeService.List();\n        var itemGetTask = _httpService.HttpGet<EditCatalogItemResult>($\"catalog-items/{id}\");\n        await Task.WhenAll(brandListTask, typeListTask, itemGetTask);\n        var brands = brandListTask.Result;\n        var types = typeListTask.Result;\n        var catalogItem = itemGetTask.Result.CatalogItem;\n        catalogItem.CatalogBrand = brands.FirstOrDefault(b => b.Id == catalogItem.CatalogBrandId)?.Name;\n        catalogItem.CatalogType = types.FirstOrDefault(t => t.Id == catalogItem.CatalogTypeId)?.Name;\n        return catalogItem;\n    }\n\n    public async Task<List<CatalogItem>> ListPaged(int pageSize)\n    {\n        _logger.LogInformation(\"Fetching catalog items from API.\");\n\n        var brandListTask = _brandService.List();\n        var typeListTask = _typeService.List();\n        var itemListTask = _httpService.HttpGet<PagedCatalogItemResponse>($\"catalog-items?PageSize=10\");\n        await Task.WhenAll(brandListTask, typeListTask, itemListTask);\n        var brands = brandListTask.Result;\n        var types = typeListTask.Result;\n        var items = itemListTask.Result.CatalogItems;\n        foreach (var item in items)\n        {\n            item.CatalogBrand = brands.FirstOrDefault(b => b.Id == item.CatalogBrandId)?.Name;\n            item.CatalogType = types.FirstOrDefault(t => t.Id == item.CatalogTypeId)?.Name;\n        }\n        return items;\n    }\n\n    public async Task<List<CatalogItem>> List()\n    {\n        _logger.LogInformation(\"Fetching catalog items from API.\");\n\n        var brandListTask = _brandService.List();\n        var typeListTask = _typeService.List();\n        var itemListTask = _httpService.HttpGet<PagedCatalogItemResponse>($\"catalog-items\");\n        await Task.WhenAll(brandListTask, typeListTask, itemListTask);\n        var brands = brandListTask.Result;\n        var types = typeListTask.Result;\n        var items = itemListTask.Result.CatalogItems;\n        foreach (var item in items)\n        {\n            item.CatalogBrand = brands.FirstOrDefault(b => b.Id == item.CatalogBrandId)?.Name;\n            item.CatalogType = types.FirstOrDefault(t => t.Id == item.CatalogTypeId)?.Name;\n        }\n        return items;\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Services/CatalogLookupDataService.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Net.Http.Json;\nusing System.Reflection;\nusing System.Threading.Tasks;\nusing BlazorShared;\nusing BlazorShared.Attributes;\nusing BlazorShared.Interfaces;\nusing BlazorShared.Models;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\n\nnamespace BlazorAdmin.Services;\n\npublic class CatalogLookupDataService<TLookupData, TReponse>\n    : ICatalogLookupDataService<TLookupData>\n    where TLookupData : LookupData\n    where TReponse : ILookupDataResponse<TLookupData>\n{\n\n    private readonly HttpClient _httpClient;\n    private readonly ILogger<CatalogLookupDataService<TLookupData, TReponse>> _logger;\n    private readonly string _apiUrl;\n\n    public CatalogLookupDataService(HttpClient httpClient,\n        IOptions<BaseUrlConfiguration> baseUrlConfiguration,\n        ILogger<CatalogLookupDataService<TLookupData, TReponse>> logger)\n    {\n        _httpClient = httpClient;\n        _logger = logger;\n        _apiUrl = baseUrlConfiguration.Value.ApiBase;\n    }\n\n    public async Task<List<TLookupData>> List()\n    {\n        var endpointName = typeof(TLookupData).GetCustomAttribute<EndpointAttribute>().Name;\n        _logger.LogInformation($\"Fetching {typeof(TLookupData).Name} from API. Enpoint : {endpointName}\");\n\n        var response = await _httpClient.GetFromJsonAsync<TReponse>($\"{_apiUrl}{endpointName}\");\n        return response.List;\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Services/HttpService.cs",
    "content": "﻿using System.Net.Http;\nusing System.Text;\nusing System.Text.Json;\nusing System.Threading.Tasks;\nusing BlazorShared;\nusing BlazorShared.Models;\nusing Microsoft.Extensions.Options;\n\nnamespace BlazorAdmin.Services;\n\npublic class HttpService\n{\n    private readonly HttpClient _httpClient;\n    private readonly ToastService _toastService;\n    private readonly string _apiUrl;\n\n\n    public HttpService(HttpClient httpClient, IOptions<BaseUrlConfiguration> baseUrlConfiguration, ToastService toastService)\n    {\n        _httpClient = httpClient;\n        _toastService = toastService;\n        _apiUrl = baseUrlConfiguration.Value.ApiBase;\n    }\n\n    public async Task<T> HttpGet<T>(string uri)\n        where T : class\n    {\n        var result = await _httpClient.GetAsync($\"{_apiUrl}{uri}\");\n        if (!result.IsSuccessStatusCode)\n        {\n            return null;\n        }\n\n        return await FromHttpResponseMessage<T>(result);\n    }\n\n    public async Task<T> HttpDelete<T>(string uri, int id)\n        where T : class\n    {\n        var result = await _httpClient.DeleteAsync($\"{_apiUrl}{uri}/{id}\");\n        if (!result.IsSuccessStatusCode)\n        {\n            return null;\n        }\n\n        return await FromHttpResponseMessage<T>(result);\n    }\n\n    public async Task<T> HttpPost<T>(string uri, object dataToSend)\n        where T : class\n    {\n        var content = ToJson(dataToSend);\n\n        var result = await _httpClient.PostAsync($\"{_apiUrl}{uri}\", content);\n        if (!result.IsSuccessStatusCode)\n        {\n            var exception = JsonSerializer.Deserialize<ErrorDetails>(await result.Content.ReadAsStringAsync(), new JsonSerializerOptions\n            {\n                PropertyNameCaseInsensitive = true\n            });\n            _toastService.ShowToast($\"Error : {exception.Message}\", ToastLevel.Error);\n\n            return null;\n        }\n\n        return await FromHttpResponseMessage<T>(result);\n    }\n\n    public async Task<T> HttpPut<T>(string uri, object dataToSend)\n        where T : class\n    {\n        var content = ToJson(dataToSend);\n\n        var result = await _httpClient.PutAsync($\"{_apiUrl}{uri}\", content);\n        if (!result.IsSuccessStatusCode)\n        {\n            _toastService.ShowToast(\"Error\", ToastLevel.Error);\n            return null;\n        }\n\n        return await FromHttpResponseMessage<T>(result);\n    }\n\n    private StringContent ToJson(object obj)\n    {\n        return new StringContent(JsonSerializer.Serialize(obj), Encoding.UTF8, \"application/json\");\n    }\n\n    private async Task<T> FromHttpResponseMessage<T>(HttpResponseMessage result)\n    {\n        return JsonSerializer.Deserialize<T>(await result.Content.ReadAsStringAsync(), new JsonSerializerOptions\n        {\n            PropertyNameCaseInsensitive = true\n        });\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Services/ToastService.cs",
    "content": "﻿using System;\nusing System.Timers;\n\nnamespace BlazorAdmin.Services;\n\npublic enum ToastLevel\n{\n    Info,\n    Success,\n    Warning,\n    Error\n}\n\npublic class ToastService : IDisposable\n{\n    public event Action<string, ToastLevel> OnShow;\n    public event Action OnHide;\n    private Timer Countdown;\n    public void ShowToast(string message, ToastLevel level)\n    {\n        OnShow?.Invoke(message, level);\n        StartCountdown();\n    }\n    private void StartCountdown()\n    {\n        SetCountdown();\n        if (Countdown.Enabled)\n        {\n            Countdown.Stop();\n            Countdown.Start();\n        }\n        else\n        {\n            Countdown.Start();\n        }\n    }\n    private void SetCountdown()\n    {\n        if (Countdown == null)\n        {\n            Countdown = new Timer(3000);\n            Countdown.Elapsed += HideToast;\n            Countdown.AutoReset = false;\n        }\n    }\n    private void HideToast(object source, ElapsedEventArgs args)\n    {\n        OnHide?.Invoke();\n    }\n    public void Dispose()\n    {\n        Countdown?.Dispose();\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/ServicesConfiguration.cs",
    "content": "﻿using BlazorAdmin.Services;\nusing BlazorShared.Interfaces;\nusing BlazorShared.Models;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace BlazorAdmin;\n\npublic static class ServicesConfiguration\n{\n    public static IServiceCollection AddBlazorServices(this IServiceCollection services)\n    {\n        services.AddScoped<ICatalogLookupDataService<CatalogBrand>, CachedCatalogLookupDataServiceDecorator<CatalogBrand, CatalogBrandResponse>>();\n        services.AddScoped<CatalogLookupDataService<CatalogBrand, CatalogBrandResponse>>();\n        services.AddScoped<ICatalogLookupDataService<CatalogType>, CachedCatalogLookupDataServiceDecorator<CatalogType, CatalogTypeResponse>>();\n        services.AddScoped<CatalogLookupDataService<CatalogType, CatalogTypeResponse>>();\n        services.AddScoped<ICatalogItemService, CachedCatalogItemServiceDecorator>();\n        services.AddScoped<CatalogItemService>();\n\n        return services;\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Shared/CustomInputSelect.cs",
    "content": "﻿using Microsoft.AspNetCore.Components.Forms;\n\nnamespace BlazorAdmin.Shared;\n\n/// <summary>\n/// This is needed until 5.0 ships with native support\n/// https://www.pragimtech.com/blog/blazor/inputselect-does-not-support-system.int32/\n/// </summary>\n/// <typeparam name=\"TValue\"></typeparam>\npublic class CustomInputSelect<TValue> : InputSelect<TValue>\n{\n    protected override bool TryParseValueFromString(string value, out TValue result,\n        out string validationErrorMessage)\n    {\n        if (typeof(TValue) == typeof(int))\n        {\n            if (int.TryParse(value, out var resultInt))\n            {\n                result = (TValue)(object)resultInt;\n                validationErrorMessage = null;\n                return true;\n            }\n            else\n            {\n                result = default;\n                validationErrorMessage =\n                    $\"The selected value {value} is not a valid number.\";\n                return false;\n            }\n        }\n        else\n        {\n            return base.TryParseValueFromString(value, out result,\n                out validationErrorMessage);\n        }\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Shared/MainLayout.razor",
    "content": "﻿@inject AuthenticationStateProvider AuthStateProvider\n@inject IJSRuntime JSRuntime\n\n@inherits BlazorAdmin.Helpers.BlazorLayoutComponent\n\n\n<AuthorizeView Roles=@BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS>\n    <div class=\"sidebar\">\n        <NavMenu />\n    </div>\n</AuthorizeView>\n\n<div class=\"main\">\n   \n    <div class=\"top-row px-4\">\n        <a href=\"https://github.com/dotnet-architecture/eShopOnWeb\" target=\"_blank\" class=\"ml-md-auto\">About eShopOnWeb</a>\n    </div>\n\n    <div class=\"content px-4\">\n        <Toast></Toast>\n        @Body\n    </div>\n</div>\n    @code\n{\n        protected override async Task OnAfterRenderAsync(bool firstRender)\n        {\n            if (firstRender)\n            {\n                var authState = await AuthStateProvider.GetAuthenticationStateAsync();\n\n                if (authState.User == null)\n                {\n                    await new Route(JSRuntime).RouteOutside(\"/Identity/Account/Login\");\n                }\n                CallRequestRefresh();\n            }\n\n            await base.OnAfterRenderAsync(firstRender);\n        }\n    }\n"
  },
  {
    "path": "src/BlazorAdmin/Shared/NavMenu.razor",
    "content": "﻿@inherits BlazorAdmin.Helpers.BlazorComponent\n<div class=\"top-row pl-4 navbar navbar-dark\">\n    <a class=\"navbar-brand\" href=\"\">eShopOnWeb Admin</a>\n    <button class=\"navbar-toggler\" @onclick=\"ToggleNavMenu\">\n        <span class=\"navbar-toggler-icon\"></span>\n    </button>\n</div>\n\n<div class=\"@NavMenuCssClass\" @onclick=\"ToggleNavMenu\">\n    <ul class=\"nav flex-column\">\n        <li class=\"nav-item px-3\">\n            <NavLink class=\"nav-link\" href=\"admin\" Match=\"NavLinkMatch.All\">\n                <span class=\"oi oi-home\" aria-hidden=\"true\"></span> Home\n            </NavLink>\n        </li>\n        <AuthorizeView>\n            <Authorized>\n                <li class=\"nav-item px-3\">\n                    <NavLink class=\"nav-link\" href=\"manage/my-account\" Match=\"NavLinkMatch.All\">\n                        <span class=\"oi oi-person\" aria-hidden=\"true\"></span>                         @context.User.Identity.Name\n\n                    </NavLink>\n                </li>\n                <li class=\"nav-item px-3\">\n                        <NavLink class=\"nav-link\" href=\"logout\">\n                            <span class=\"oi oi-account-logout\" aria-hidden=\"true\"></span> Logout\n                        </NavLink>\n                </li>\n            </Authorized>\n        </AuthorizeView>\n    </ul>\n</div>\n\n@code {\n\n    private bool collapseNavMenu = true;\n\n    private string NavMenuCssClass => collapseNavMenu ? \"collapse\" : null;\n\n    private void ToggleNavMenu()\n    {\n        collapseNavMenu = !collapseNavMenu;\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/Shared/RedirectToLogin.razor",
    "content": "﻿@using System.Web;\n\n@inject NavigationManager Navigation\n@inject IJSRuntime JsRuntime\n\n@code {\n    protected override void OnInitialized()\n    {       \n        var returnUrl = HttpUtility.UrlEncode($\"/{Uri.EscapeDataString(Navigation.ToBaseRelativePath(Navigation.Uri))}\");\n        JsRuntime.InvokeVoidAsync(\"location.replace\", $\"Identity/Account/Login?returnUrl={returnUrl}\");\n    }\n}"
  },
  {
    "path": "src/BlazorAdmin/Shared/Spinner.razor",
    "content": "﻿\n@inherits BlazorAdmin.Helpers.BlazorComponent\n\n@namespace  BlazorAdmin.Shared\n\n<div class=\"spinner-border\" role=\"status\">\n    <span class=\"sr-only\">Loading...</span>\n</div>\n\n"
  },
  {
    "path": "src/BlazorAdmin/Shared/Toast.razor",
    "content": "﻿@inherits BlazorAdmin.Helpers.ToastComponent\n\n@namespace BlazorAdmin.Shared\n\n<div class=\"toast @(IsVisible ? \"toast-visible\" : null) @BackgroundCssClass\">\n    <div class=\"toast-icon\">\n        <i class=\"fa fa-@IconCssClass\" aria-hidden=\"true\"></i>\n    </div>\n    <div class=\"toast-body\">\n        <h5>@Heading</h5>\n        <p>@Message</p>\n    </div>\n</div>\n"
  },
  {
    "path": "src/BlazorAdmin/_Imports.razor",
    "content": "﻿@using System.Net.Http\n@using System.Net.Http.Json\n@using Microsoft.AspNetCore.Authorization;\n@using Microsoft.AspNetCore.Components.Authorization\n@using Microsoft.AspNetCore.Components.Forms\n@using Microsoft.AspNetCore.Components.Routing\n@using Microsoft.AspNetCore.Components.Web\n@using Microsoft.AspNetCore.Components.WebAssembly.Http\n@using Microsoft.JSInterop\n@using Microsoft.Extensions.Logging\n@using BlazorAdmin\n@using BlazorAdmin.Shared\n@using BlazorAdmin.Services\n@using BlazorAdmin.JavaScript\n@using BlazorShared.Authorization\n@using BlazorShared.Interfaces\n@using BlazorInputFile\n@using BlazorShared.Models\n"
  },
  {
    "path": "src/BlazorAdmin/wwwroot/appsettings.Development.json",
    "content": "{\n  \"baseUrls\": {\n    \"apiBase\": \"https://localhost:5099/api/\",\n    \"webBase\": \"https://localhost:44315/\"\n  },\n  \"Logging\": {\n    \"IncludeScopes\": false,\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft\": \"Warning\",\n      \"System\": \"Warning\"\n    }\n  }\n}"
  },
  {
    "path": "src/BlazorAdmin/wwwroot/appsettings.Docker.json",
    "content": "{\n  \"baseUrls\": {\n    \"apiBase\": \"http://localhost:5200/api/\",\n    \"webBase\": \"http://host.docker.internal:5106/\"\n  }\n}"
  },
  {
    "path": "src/BlazorAdmin/wwwroot/appsettings.json",
    "content": "{\n  \"baseUrls\": {\n    \"apiBase\": \"https://localhost:5099/api/\",\n    \"webBase\": \"https://localhost:44315/\"\n  },\n  \"Logging\": {\n    \"IncludeScopes\": false,\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft\": \"Warning\",\n      \"System\": \"Warning\"\n    }\n  }\n}"
  },
  {
    "path": "src/BlazorAdmin/wwwroot/css/admin.css",
    "content": "﻿@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');\n\nhtml, body {\n    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;\n}\n\na, .btn-link {\n    color: #0366d6;\n}\n\n.btn-primary {\n    color: #fff;\n    background-color: #1b6ec2;\n    border-color: #1861ac;\n}\n\n#admin {\n    position: relative;\n    display: flex;    \n}\n\n.top-row {\n    height: 3.5rem;\n    display: flex;\n    align-items: center;\n}\n\n.main {\n    flex: 1;\n}\n\n    .main .top-row {\n        background-color: #f7f7f7;\n        border-bottom: 1px solid #d6d5d5;\n        justify-content: flex-end;\n    }\n\n        .main .top-row > a, .main .top-row .btn-link {\n            white-space: nowrap;\n            margin-left: 1.5rem;\n        }\n\n.main .top-row a:first-child {\n    overflow: hidden;\n    text-overflow: ellipsis;\n}\n\n.sidebar {\n    background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);\n}\n\n    .sidebar .top-row {\n        background-color: rgba(0,0,0,0.4);\n    }\n\n    .sidebar .navbar-brand {\n        font-size: 1.1rem;\n    }\n\n    .sidebar .oi {\n        width: 2rem;\n        font-size: 1.1rem;\n        vertical-align: text-top;\n        top: -2px;\n    }\n\n    .sidebar .nav-item {\n        font-size: 0.9rem;\n        padding-bottom: 0.5rem;\n    }\n\n        .sidebar .nav-item:first-of-type {\n            padding-top: 1rem;\n        }\n\n        .sidebar .nav-item:last-of-type {\n            padding-bottom: 1rem;\n        }\n\n        .sidebar .nav-item a {\n            color: #d7d7d7;\n            border-radius: 4px;\n            height: 3rem;\n            display: flex;\n            align-items: center;\n            line-height: 3rem;\n        }\n\n            .sidebar .nav-item a.active {\n                background-color: rgba(255,255,255,0.25);\n                color: white;\n            }\n\n            .sidebar .nav-item a:hover {\n                background-color: rgba(255,255,255,0.1);\n                color: white;\n            }\n\n.content {\n    padding-top: 1.1rem;\n}\n\n.navbar-toggler {\n    background-color: rgba(255, 255, 255, 0.1);\n}\n\n.valid.modified:not([type=checkbox]) {\n    outline: 1px solid #26b050;\n}\n\n.invalid {\n    outline: 1px solid red;\n}\n\n.validation-message {\n    color: red;\n}\n\n#blazor-error-ui {\n    background: lightyellow;\n    bottom: 0;\n    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);\n    display: none;\n    left: 0;\n    padding: 0.6rem 1.25rem 0.7rem 1.25rem;\n    position: fixed;\n    width: 100%;\n    z-index: 1000;\n}\n\n#blazor-error-ui .dismiss {\n    cursor: pointer;\n    position: absolute;\n    right: 0.75rem;\n    top: 0.5rem;\n}\n\n.cursor-pointer {\n    cursor: pointer;\n}\n\n.img-thumbnail {\n    max-width: 120px;\n    height: auto;\n}\n\n.esh-picture {\n    height: 100%;\n    width: 100%;\n}\n\n.body-no-overflow {\n    overflow: hidden !important;\n}\n\n.toast {\n    display: none;\n    padding: 1.5rem;\n    color: #fff;\n    z-index: 99999;\n    position: absolute;\n    width: 25rem;\n    top: 2rem;\n    border-radius: 1rem;\n    left: 50%;\n}\n\n.toast-icon {\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    padding: 01rem;\n    font-size: 2.5rem;\n}\n\n.toast-body {\n    display: flex;\n    flex-direction: column;\n    flex: 1;\n    padding-left: 1rem;\n}\n\n.toast-body p {\n    margin-bottom: 0;\n}\n\n.toast-visible {\n    display: flex;\n    flex-direction: row;\n    animation: fadein 1.5s;\n}\n\n@keyframes fadein {\n    from {\n        opacity: 0;\n    }\n\n    to {\n        opacity: 1;\n    }\n}\n\n@media (max-width: 767.98px) {\n    .main .top-row:not(.auth) {\n        display: none;\n    }\n\n    .main .top-row.auth {\n        justify-content: space-between;\n    }\n\n    .main .top-row a, .main .top-row .btn-link {\n        margin-left: 0;\n    }\n}\n\n@media (min-width: 768px) {    \n\n    .sidebar {\n        width: 250px;\n        height: 100vh;\n        position: sticky;\n        top: 0;\n    }\n\n    .main .top-row {\n        position: sticky;\n        top: 0;\n    }\n\n    .main > div {\n        padding-left: 2rem !important;\n        padding-right: 1.5rem !important;\n    }\n\n    .navbar-toggler {\n        display: none;\n    }\n\n    .sidebar .collapse {\n        /* Never collapse the sidebar for wide screens */\n        display: block;\n    }\n}\n"
  },
  {
    "path": "src/BlazorAdmin/wwwroot/css/open-iconic/FONT-LICENSE",
    "content": "SIL OPEN FONT LICENSE Version 1.1\n\nCopyright (c) 2014 Waybury\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded,\nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to, deleting,\nor substituting -- in part or in whole -- any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "src/BlazorAdmin/wwwroot/css/open-iconic/ICON-LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Waybury\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "src/BlazorAdmin/wwwroot/css/open-iconic/README.md",
    "content": "[Open Iconic v1.1.1](http://useiconic.com/open)\n===========\n\n### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint&mdash;ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons)\n\n\n\n## What's in Open Iconic?\n\n* 223 icons designed to be legible down to 8 pixels\n* Super-light SVG files - 61.8 for the entire set \n* SVG sprite&mdash;the modern replacement for icon fonts\n* Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats\n* Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats\n* PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px.\n\n\n## Getting Started\n\n#### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections.\n\n### General Usage\n\n#### Using Open Iconic's SVGs\n\nWe like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute).\n\n```\n<img src=\"/open-iconic/svg/icon-name.svg\" alt=\"icon name\">\n```\n\n#### Using Open Iconic's SVG Sprite\n\nOpen Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack.\n\nAdding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `<svg>` *tag and a unique class name for each different icon in the* `<use>` *tag.*  \n\n```\n<svg class=\"icon\">\n  <use xlink:href=\"open-iconic.svg#account-login\" class=\"icon-account-login\"></use>\n</svg>\n```\n\nSizing icons only needs basic CSS. All the icons are in a square format, so just set the `<svg>` tag with equal width and height dimensions.\n\n```\n.icon {\n  width: 16px;\n  height: 16px;\n}\n```\n\nColoring icons is even easier. All you need to do is set the `fill` rule on the `<use>` tag.\n\n```\n.icon-account-login {\n  fill: #f00;\n}\n```\n\nTo learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/).\n\n#### Using Open Iconic's Icon Font...\n\n\n##### …with Bootstrap\n\nYou can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}`\n\n\n```\n<link href=\"/open-iconic/font/css/open-iconic-bootstrap.css\" rel=\"stylesheet\">\n```\n\n\n```\n<span class=\"oi oi-icon-name\" title=\"icon name\" aria-hidden=\"true\"></span>\n```\n\n##### …with Foundation\n\nYou can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}`\n\n```\n<link href=\"/open-iconic/font/css/open-iconic-foundation.css\" rel=\"stylesheet\">\n```\n\n\n```\n<span class=\"fi-icon-name\" title=\"icon name\" aria-hidden=\"true\"></span>\n```\n\n##### …on its own\n\nYou can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}`\n\n```\n<link href=\"/open-iconic/font/css/open-iconic.css\" rel=\"stylesheet\">\n```\n\n```\n<span class=\"oi\" data-glyph=\"icon-name\" title=\"icon name\" aria-hidden=\"true\"></span>\n```\n\n\n## License\n\n### Icons\n\nAll code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT).\n\n### Fonts\n\nAll fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web).\n"
  },
  {
    "path": "src/BlazorShared/Attributes/EndpointAttribute.cs",
    "content": "﻿using System;\n\nnamespace BlazorShared.Attributes;\n\npublic class EndpointAttribute : Attribute\n{\n    public string Name { get; set; }\n}\n"
  },
  {
    "path": "src/BlazorShared/Authorization/ClaimValue.cs",
    "content": "﻿namespace BlazorShared.Authorization;\n\npublic class ClaimValue\n{\n    public ClaimValue()\n    {\n    }\n\n    public ClaimValue(string type, string value)\n    {\n        Type = type;\n        Value = value;\n    }\n\n    public string Type { get; set; }\n    public string Value { get; set; }\n}\n"
  },
  {
    "path": "src/BlazorShared/Authorization/Constants.cs",
    "content": "﻿namespace BlazorShared.Authorization;\n\npublic static class Constants\n{\n    public static class Roles\n    {\n        public const string ADMINISTRATORS = \"Administrators\";\n    }\n}\n"
  },
  {
    "path": "src/BlazorShared/Authorization/UserInfo.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace BlazorShared.Authorization;\n\npublic class UserInfo\n{\n    public static readonly UserInfo Anonymous = new UserInfo();\n    public bool IsAuthenticated { get; set; }\n    public string NameClaimType { get; set; }\n    public string RoleClaimType { get; set; }\n    public string Token { get; set; }\n    public IEnumerable<ClaimValue> Claims { get; set; }\n}\n"
  },
  {
    "path": "src/BlazorShared/BaseUrlConfiguration.cs",
    "content": "﻿namespace BlazorShared;\n\npublic class BaseUrlConfiguration\n{\n    public const string CONFIG_NAME = \"baseUrls\";\n\n    public string ApiBase { get; set; }\n    public string WebBase { get; set; }\n}\n"
  },
  {
    "path": "src/BlazorShared/BlazorShared.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>  \n    <RootNamespace>BlazorShared</RootNamespace>\n    <AssemblyName>BlazorShared</AssemblyName>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"BlazorInputFile\" />\n    <PackageReference Include=\"FluentValidation\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "src/BlazorShared/Interfaces/ICatalogItemService.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading.Tasks;\nusing BlazorShared.Models;\n\nnamespace BlazorShared.Interfaces;\n\npublic interface ICatalogItemService\n{\n    Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem);\n    Task<CatalogItem> Edit(CatalogItem catalogItem);\n    Task<string> Delete(int id);\n    Task<CatalogItem> GetById(int id);\n    Task<List<CatalogItem>> ListPaged(int pageSize);\n    Task<List<CatalogItem>> List();\n}\n"
  },
  {
    "path": "src/BlazorShared/Interfaces/ICatalogLookupDataService.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading.Tasks;\nusing BlazorShared.Models;\n\nnamespace BlazorShared.Interfaces;\n\npublic interface ICatalogLookupDataService<TLookupData> where TLookupData : LookupData\n{\n    Task<List<TLookupData>> List();\n}\n"
  },
  {
    "path": "src/BlazorShared/Interfaces/ILookupDataResponse.cs",
    "content": "﻿using System.Collections.Generic;\nusing BlazorShared.Models;\n\nnamespace BlazorShared.Interfaces;\n\npublic interface ILookupDataResponse<TLookupData> where TLookupData : LookupData\n{\n    List<TLookupData> List { get; set; }\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/CatalogBrand.cs",
    "content": "﻿using BlazorShared.Attributes;\n\nnamespace BlazorShared.Models;\n\n[Endpoint(Name = \"catalog-brands\")]\npublic class CatalogBrand : LookupData\n{\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/CatalogBrandResponse.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Text.Json.Serialization;\nusing BlazorShared.Interfaces;\n\nnamespace BlazorShared.Models;\n\npublic class CatalogBrandResponse : ILookupDataResponse<CatalogBrand>\n{\n    [JsonPropertyName(\"CatalogBrands\")]\n    public List<CatalogBrand> List { get; set; } = new List<CatalogBrand>();\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/CatalogItem.cs",
    "content": "﻿using System;\nusing System.ComponentModel.DataAnnotations;\nusing System.IO;\nusing System.Threading.Tasks;\nusing BlazorInputFile;\n\nnamespace BlazorShared.Models;\n\npublic class CatalogItem\n{\n    public int Id { get; set; }\n\n    public int CatalogTypeId { get; set; }\n    public string CatalogType { get; set; } = \"NotSet\";\n\n    public int CatalogBrandId { get; set; }\n    public string CatalogBrand { get; set; } = \"NotSet\";\n\n    [Required(ErrorMessage = \"The Name field is required\")]\n    public string Name { get; set; }\n\n    [Required(ErrorMessage = \"The Description field is required\")]\n    public string Description { get; set; }\n\n    // decimal(18,2)\n    [RegularExpression(@\"^\\d+(\\.\\d{0,2})*$\", ErrorMessage = \"The field Price must be a positive number with maximum two decimals.\")]\n    [Range(0.01, 1000)]\n    [DataType(DataType.Currency)]\n    public decimal Price { get; set; }\n\n    public string PictureUri { get; set; }\n    public string PictureBase64 { get; set; }\n    public string PictureName { get; set; }\n\n    private const int ImageMaximumBytes = 512000;\n\n    public static string IsValidImage(string pictureName, string pictureBase64)\n    {\n        if (string.IsNullOrEmpty(pictureBase64))\n        {\n            return \"File not found!\";\n        }\n        var fileData = Convert.FromBase64String(pictureBase64);\n\n        if (fileData.Length <= 0)\n        {\n            return \"File length is 0!\";\n        }\n\n        if (fileData.Length > ImageMaximumBytes)\n        {\n            return \"Maximum length is 512KB\";\n        }\n\n        if (!IsExtensionValid(pictureName))\n        {\n            return \"File is not image\";\n        }\n\n        return null;\n    }\n\n    public static async Task<string> DataToBase64(IFileListEntry fileItem)\n    {\n        using (var reader = new StreamReader(fileItem.Data))\n        {\n            using (var memStream = new MemoryStream())\n            {\n                await reader.BaseStream.CopyToAsync(memStream);\n                var fileData = memStream.ToArray();\n                var encodedBase64 = Convert.ToBase64String(fileData);\n\n                return encodedBase64;\n            }\n        }\n    }\n\n    private static bool IsExtensionValid(string fileName)\n    {\n        var extension = Path.GetExtension(fileName);\n\n        return string.Equals(extension, \".jpg\", StringComparison.OrdinalIgnoreCase) ||\n               string.Equals(extension, \".png\", StringComparison.OrdinalIgnoreCase) ||\n               string.Equals(extension, \".gif\", StringComparison.OrdinalIgnoreCase) ||\n               string.Equals(extension, \".jpeg\", StringComparison.OrdinalIgnoreCase);\n    }\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/CatalogType.cs",
    "content": "﻿using BlazorShared.Attributes;\n\nnamespace BlazorShared.Models;\n\n[Endpoint(Name = \"catalog-types\")]\npublic class CatalogType : LookupData\n{\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/CatalogTypeResponse.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Text.Json.Serialization;\nusing BlazorShared.Interfaces;\n\nnamespace BlazorShared.Models;\n\npublic class CatalogTypeResponse : ILookupDataResponse<CatalogType>\n{\n\n    [JsonPropertyName(\"CatalogTypes\")]\n    public List<CatalogType> List { get; set; } = new List<CatalogType>();\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/CreateCatalogItemRequest.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace BlazorShared.Models;\n\npublic class CreateCatalogItemRequest\n{\n    public int CatalogTypeId { get; set; }\n\n    public int CatalogBrandId { get; set; }\n\n    [Required(ErrorMessage = \"The Name field is required\")]\n    public string Name { get; set; } = string.Empty;\n\n    [Required(ErrorMessage = \"The Description field is required\")]\n    public string Description { get; set; } = string.Empty;\n\n    // decimal(18,2)\n    [RegularExpression(@\"^\\d+(\\.\\d{0,2})*$\", ErrorMessage = \"The field Price must be a positive number with maximum two decimals.\")]\n    [Range(0.01, 1000)]\n    [DataType(DataType.Currency)]\n    public decimal Price { get; set; } = 0;\n\n    public string PictureUri { get; set; } = string.Empty;\n    public string PictureBase64 { get; set; } = string.Empty;\n    public string PictureName { get; set; } = string.Empty;\n\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/CreateCatalogItemResponse.cs",
    "content": "﻿namespace BlazorShared.Models;\n\npublic class CreateCatalogItemResponse\n{\n    public CatalogItem CatalogItem { get; set; } = new CatalogItem();\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/DeleteCatalogItemResponse.cs",
    "content": "﻿namespace BlazorShared.Models;\n\npublic class DeleteCatalogItemResponse\n{\n    public string Status { get; set; } = \"Deleted\";\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/EditCatalogItemResponse.cs",
    "content": "﻿namespace BlazorShared.Models;\n\npublic class EditCatalogItemResult\n{\n    public CatalogItem CatalogItem { get; set; } = new CatalogItem();\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/ErrorDetails.cs",
    "content": "﻿using System.Text.Json;\n\nnamespace BlazorShared.Models;\n\npublic class ErrorDetails\n{\n    public int StatusCode { get; set; }\n    public string Message { get; set; }\n    public override string ToString()\n    {\n        return JsonSerializer.Serialize(this);\n    }\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/LookupData.cs",
    "content": "﻿namespace BlazorShared.Models;\n\npublic abstract class LookupData\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n}\n"
  },
  {
    "path": "src/BlazorShared/Models/PagedCatalogItemResponse.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace BlazorShared.Models;\n\npublic class PagedCatalogItemResponse\n{\n    public List<CatalogItem> CatalogItems { get; set; } = new List<CatalogItem>();\n    public int PageCount { get; set; } = 0;\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/CatalogContext.cs",
    "content": "﻿using System.Reflection;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data;\n\npublic class CatalogContext : DbContext\n{\n    #pragma warning disable CS8618 // Required by Entity Framework\n    public CatalogContext(DbContextOptions<CatalogContext> options) : base(options) {}\n\n    public DbSet<Basket> Baskets { get; set; }\n    public DbSet<CatalogItem> CatalogItems { get; set; }\n    public DbSet<CatalogBrand> CatalogBrands { get; set; }\n    public DbSet<CatalogType> CatalogTypes { get; set; }\n    public DbSet<Order> Orders { get; set; }\n    public DbSet<OrderItem> OrderItems { get; set; }\n    public DbSet<BasketItem> BasketItems { get; set; }\n\n    protected override void OnModelCreating(ModelBuilder builder)\n    {\n        base.OnModelCreating(builder);\n        builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/CatalogContextSeed.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.Extensions.Logging;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data;\n\npublic class CatalogContextSeed\n{\n    public static async Task SeedAsync(CatalogContext catalogContext,\n        ILogger logger,\n        int retry = 0)\n    {\n        var retryForAvailability = retry;\n        try\n        {\n            if (catalogContext.Database.IsSqlServer())\n            {\n                catalogContext.Database.Migrate();\n            }\n\n            if (!await catalogContext.CatalogBrands.AnyAsync())\n            {\n                await catalogContext.CatalogBrands.AddRangeAsync(\n                    GetPreconfiguredCatalogBrands());\n\n                await catalogContext.SaveChangesAsync();\n            }\n\n            if (!await catalogContext.CatalogTypes.AnyAsync())\n            {\n                await catalogContext.CatalogTypes.AddRangeAsync(\n                    GetPreconfiguredCatalogTypes());\n\n                await catalogContext.SaveChangesAsync();\n            }\n\n            if (!await catalogContext.CatalogItems.AnyAsync())\n            {\n                await catalogContext.CatalogItems.AddRangeAsync(\n                    GetPreconfiguredItems());\n\n                await catalogContext.SaveChangesAsync();\n            }\n        }\n        catch (Exception ex)\n        {\n            if (retryForAvailability >= 10) throw;\n\n            retryForAvailability++;\n            \n            logger.LogError(ex.Message);\n            await SeedAsync(catalogContext, logger, retryForAvailability);\n            throw;\n        }\n    }\n\n    static IEnumerable<CatalogBrand> GetPreconfiguredCatalogBrands()\n    {\n        return new List<CatalogBrand>\n            {\n                new(\"Azure\"),\n                new(\".NET\"),\n                new(\"Visual Studio\"),\n                new(\"SQL Server\"),\n                new(\"Other\")\n            };\n    }\n\n    static IEnumerable<CatalogType> GetPreconfiguredCatalogTypes()\n    {\n        return new List<CatalogType>\n            {\n                new(\"Mug\"),\n                new(\"T-Shirt\"),\n                new(\"Sheet\"),\n                new(\"USB Memory Stick\")\n            };\n    }\n\n    static IEnumerable<CatalogItem> GetPreconfiguredItems()\n    {\n        return new List<CatalogItem>\n            {\n                new(2,2, \".NET Bot Black Sweatshirt\", \".NET Bot Black Sweatshirt\", 19.5M,  \"http://catalogbaseurltobereplaced/images/products/1.png\"),\n                new(1,2, \".NET Black & White Mug\", \".NET Black & White Mug\", 8.50M, \"http://catalogbaseurltobereplaced/images/products/2.png\"),\n                new(2,5, \"Prism White T-Shirt\", \"Prism White T-Shirt\", 12,  \"http://catalogbaseurltobereplaced/images/products/3.png\"),\n                new(2,2, \".NET Foundation Sweatshirt\", \".NET Foundation Sweatshirt\", 12, \"http://catalogbaseurltobereplaced/images/products/4.png\"),\n                new(3,5, \"Roslyn Red Sheet\", \"Roslyn Red Sheet\", 8.5M, \"http://catalogbaseurltobereplaced/images/products/5.png\"),\n                new(2,2, \".NET Blue Sweatshirt\", \".NET Blue Sweatshirt\", 12, \"http://catalogbaseurltobereplaced/images/products/6.png\"),\n                new(2,5, \"Roslyn Red T-Shirt\", \"Roslyn Red T-Shirt\",  12, \"http://catalogbaseurltobereplaced/images/products/7.png\"),\n                new(2,5, \"Kudu Purple Sweatshirt\", \"Kudu Purple Sweatshirt\", 8.5M, \"http://catalogbaseurltobereplaced/images/products/8.png\"),\n                new(1,5, \"Cup<T> White Mug\", \"Cup<T> White Mug\", 12, \"http://catalogbaseurltobereplaced/images/products/9.png\"),\n                new(3,2, \".NET Foundation Sheet\", \".NET Foundation Sheet\", 12, \"http://catalogbaseurltobereplaced/images/products/10.png\"),\n                new(3,2, \"Cup<T> Sheet\", \"Cup<T> Sheet\", 8.5M, \"http://catalogbaseurltobereplaced/images/products/11.png\"),\n                new(2,5, \"Prism White TShirt\", \"Prism White TShirt\", 12, \"http://catalogbaseurltobereplaced/images/products/12.png\")\n            };\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Config/BasketConfiguration.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Config;\n\npublic class BasketConfiguration : IEntityTypeConfiguration<Basket>\n{\n    public void Configure(EntityTypeBuilder<Basket> builder)\n    {\n        var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items));\n        navigation?.SetPropertyAccessMode(PropertyAccessMode.Field);\n\n        builder.Property(b => b.BuyerId)\n            .IsRequired()\n            .HasMaxLength(256);\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Config/BasketItemConfiguration.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Config;\n\npublic class BasketItemConfiguration : IEntityTypeConfiguration<BasketItem>\n{\n    public void Configure(EntityTypeBuilder<BasketItem> builder)\n    {\n        builder.Property(bi => bi.UnitPrice)\n            .IsRequired(true)\n            .HasColumnType(\"decimal(18,2)\");\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Config;\n\npublic class CatalogBrandConfiguration : IEntityTypeConfiguration<CatalogBrand>\n{\n    public void Configure(EntityTypeBuilder<CatalogBrand> builder)\n    {\n        builder.HasKey(ci => ci.Id);\n\n        builder.Property(ci => ci.Id)\n           .UseHiLo(\"catalog_brand_hilo\")\n           .IsRequired();\n\n        builder.Property(cb => cb.Brand)\n            .IsRequired()\n            .HasMaxLength(100);\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Config/CatalogItemConfiguration.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Config;\n\npublic class CatalogItemConfiguration : IEntityTypeConfiguration<CatalogItem>\n{\n    public void Configure(EntityTypeBuilder<CatalogItem> builder)\n    {\n        builder.ToTable(\"Catalog\");\n\n        builder.Property(ci => ci.Id)\n            .UseHiLo(\"catalog_hilo\")\n            .IsRequired();\n\n        builder.Property(ci => ci.Name)\n            .IsRequired(true)\n            .HasMaxLength(50);\n\n        builder.Property(ci => ci.Price)\n            .IsRequired(true)\n            .HasColumnType(\"decimal(18,2)\");\n\n        builder.Property(ci => ci.PictureUri)\n            .IsRequired(false);\n\n        builder.HasOne(ci => ci.CatalogBrand)\n            .WithMany()\n            .HasForeignKey(ci => ci.CatalogBrandId);\n\n        builder.HasOne(ci => ci.CatalogType)\n            .WithMany()\n            .HasForeignKey(ci => ci.CatalogTypeId);\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Config;\n\npublic class CatalogTypeConfiguration : IEntityTypeConfiguration<CatalogType>\n{\n    public void Configure(EntityTypeBuilder<CatalogType> builder)\n    {\n        builder.HasKey(ci => ci.Id);\n\n        builder.Property(ci => ci.Id)\n           .UseHiLo(\"catalog_type_hilo\")\n           .IsRequired();\n\n        builder.Property(cb => cb.Type)\n            .IsRequired()\n            .HasMaxLength(100);\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Config/OrderConfiguration.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Config;\n\npublic class OrderConfiguration : IEntityTypeConfiguration<Order>\n{\n    public void Configure(EntityTypeBuilder<Order> builder)\n    {\n        var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems));\n\n        navigation?.SetPropertyAccessMode(PropertyAccessMode.Field);\n\n        builder.Property(b => b.BuyerId)\n            .IsRequired()\n            .HasMaxLength(256);\n\n        builder.OwnsOne(o => o.ShipToAddress, a =>\n        {\n            a.WithOwner();\n\n            a.Property(a => a.ZipCode)\n                .HasMaxLength(18)\n                .IsRequired();\n\n            a.Property(a => a.Street)\n                .HasMaxLength(180)\n                .IsRequired();\n\n            a.Property(a => a.State)\n                .HasMaxLength(60);\n\n            a.Property(a => a.Country)\n                .HasMaxLength(90)\n                .IsRequired();\n\n            a.Property(a => a.City)\n                .HasMaxLength(100)\n                .IsRequired();\n        });\n\n        builder.Navigation(x => x.ShipToAddress).IsRequired();\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Config/OrderItemConfiguration.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Config;\n\npublic class OrderItemConfiguration : IEntityTypeConfiguration<OrderItem>\n{\n    public void Configure(EntityTypeBuilder<OrderItem> builder)\n    {\n        builder.OwnsOne(i => i.ItemOrdered, io =>\n        {\n            io.WithOwner();\n\n            io.Property(cio => cio.ProductName)\n                .HasMaxLength(50)\n                .IsRequired();\n        });\n\n        builder.Property(oi => oi.UnitPrice)\n            .IsRequired(true)\n            .HasColumnType(\"decimal(18,2)\");\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/EfRepository.cs",
    "content": "﻿using Ardalis.Specification.EntityFrameworkCore;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data;\n\npublic class EfRepository<T> : RepositoryBase<T>, IReadRepository<T>, IRepository<T> where T : class, IAggregateRoot\n{\n    public EfRepository(CatalogContext dbContext) : base(dbContext)\n    {\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/FileItem.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Infrastructure.Data;\n\npublic class FileItem\n{\n    public string? FileName { get; set; }\n    public string? Url { get; set; }\n    public long Size { get; set; }\n    public string? Ext { get; set; }\n    public string? Type { get; set; }\n    public string? DataBase64 { get; set; }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing Microsoft.eShopWeb.Infrastructure.Data;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Migrations\n{\n    [DbContext(typeof(CatalogContext))]\n    [Migration(\"20201202111507_InitialModel\")]\n    partial class InitialModel\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .UseIdentityColumns()\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"ProductVersion\", \"5.0.0\");\n\n            modelBuilder.HasSequence(\"catalog_brand_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.HasSequence(\"catalog_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.HasSequence(\"catalog_type_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseIdentityColumn();\n\n                    b.Property<string>(\"BuyerId\")\n                        .IsRequired()\n                        .HasMaxLength(40)\n                        .HasColumnType(\"nvarchar(40)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Baskets\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseIdentityColumn();\n\n                    b.Property<int>(\"BasketId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"CatalogItemId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"Quantity\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"UnitPrice\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"BasketId\");\n\n                    b.ToTable(\"BasketItems\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseHiLo(\"catalog_brand_hilo\");\n\n                    b.Property<string>(\"Brand\")\n                        .IsRequired()\n                        .HasMaxLength(100)\n                        .HasColumnType(\"nvarchar(100)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"CatalogBrands\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseHiLo(\"catalog_hilo\");\n\n                    b.Property<int>(\"CatalogBrandId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"CatalogTypeId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"Description\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .IsRequired()\n                        .HasMaxLength(50)\n                        .HasColumnType(\"nvarchar(50)\");\n\n                    b.Property<string>(\"PictureUri\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<decimal>(\"Price\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CatalogBrandId\");\n\n                    b.HasIndex(\"CatalogTypeId\");\n\n                    b.ToTable(\"Catalog\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseHiLo(\"catalog_type_hilo\");\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasMaxLength(100)\n                        .HasColumnType(\"nvarchar(100)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"CatalogTypes\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseIdentityColumn();\n\n                    b.Property<string>(\"BuyerId\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<DateTimeOffset>(\"OrderDate\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Orders\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseIdentityColumn();\n\n                    b.Property<int?>(\"OrderId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"UnitPrice\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.Property<int>(\"Units\")\n                        .HasColumnType(\"int\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"OrderId\");\n\n                    b.ToTable(\"OrderItems\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", null)\n                        .WithMany(\"Items\")\n                        .HasForeignKey(\"BasketId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand\", \"CatalogBrand\")\n                        .WithMany()\n                        .HasForeignKey(\"CatalogBrandId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType\", \"CatalogType\")\n                        .WithMany()\n                        .HasForeignKey(\"CatalogTypeId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.Navigation(\"CatalogBrand\");\n\n                    b.Navigation(\"CatalogType\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.OwnsOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address\", \"ShipToAddress\", b1 =>\n                        {\n                            b1.Property<int>(\"OrderId\")\n                                .ValueGeneratedOnAdd()\n                                .HasColumnType(\"int\")\n                                .UseIdentityColumn();\n\n                            b1.Property<string>(\"City\")\n                                .IsRequired()\n                                .HasMaxLength(100)\n                                .HasColumnType(\"nvarchar(100)\");\n\n                            b1.Property<string>(\"Country\")\n                                .IsRequired()\n                                .HasMaxLength(90)\n                                .HasColumnType(\"nvarchar(90)\");\n\n                            b1.Property<string>(\"State\")\n                                .HasMaxLength(60)\n                                .HasColumnType(\"nvarchar(60)\");\n\n                            b1.Property<string>(\"Street\")\n                                .IsRequired()\n                                .HasMaxLength(180)\n                                .HasColumnType(\"nvarchar(180)\");\n\n                            b1.Property<string>(\"ZipCode\")\n                                .IsRequired()\n                                .HasMaxLength(18)\n                                .HasColumnType(\"nvarchar(18)\");\n\n                            b1.HasKey(\"OrderId\");\n\n                            b1.ToTable(\"Orders\");\n\n                            b1.WithOwner()\n                                .HasForeignKey(\"OrderId\");\n                        });\n\n                    b.Navigation(\"ShipToAddress\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", null)\n                        .WithMany(\"OrderItems\")\n                        .HasForeignKey(\"OrderId\");\n\n                    b.OwnsOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered\", \"ItemOrdered\", b1 =>\n                        {\n                            b1.Property<int>(\"OrderItemId\")\n                                .ValueGeneratedOnAdd()\n                                .HasColumnType(\"int\")\n                                .UseIdentityColumn();\n\n                            b1.Property<int>(\"CatalogItemId\")\n                                .HasColumnType(\"int\");\n\n                            b1.Property<string>(\"PictureUri\")\n                                .HasColumnType(\"nvarchar(max)\");\n\n                            b1.Property<string>(\"ProductName\")\n                                .IsRequired()\n                                .HasMaxLength(50)\n                                .HasColumnType(\"nvarchar(50)\");\n\n                            b1.HasKey(\"OrderItemId\");\n\n                            b1.ToTable(\"OrderItems\");\n\n                            b1.WithOwner()\n                                .HasForeignKey(\"OrderItemId\");\n                        });\n\n                    b.Navigation(\"ItemOrdered\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", b =>\n                {\n                    b.Navigation(\"Items\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.Navigation(\"OrderItems\");\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs",
    "content": "﻿using System;\nusing Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Migrations;\n\npublic partial class InitialModel : Migration\n{\n    protected override void Up(MigrationBuilder migrationBuilder)\n    {\n        migrationBuilder.CreateSequence(\n            name: \"catalog_brand_hilo\",\n            incrementBy: 10);\n\n        migrationBuilder.CreateSequence(\n            name: \"catalog_hilo\",\n            incrementBy: 10);\n\n        migrationBuilder.CreateSequence(\n            name: \"catalog_type_hilo\",\n            incrementBy: 10);\n\n        migrationBuilder.CreateTable(\n            name: \"Baskets\",\n            columns: table => new\n            {\n                Id = table.Column<int>(type: \"int\", nullable: false)\n                    .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                BuyerId = table.Column<string>(type: \"nvarchar(40)\", maxLength: 40, nullable: false)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_Baskets\", x => x.Id);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"CatalogBrands\",\n            columns: table => new\n            {\n                Id = table.Column<int>(type: \"int\", nullable: false),\n                Brand = table.Column<string>(type: \"nvarchar(100)\", maxLength: 100, nullable: false)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_CatalogBrands\", x => x.Id);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"CatalogTypes\",\n            columns: table => new\n            {\n                Id = table.Column<int>(type: \"int\", nullable: false),\n                Type = table.Column<string>(type: \"nvarchar(100)\", maxLength: 100, nullable: false)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_CatalogTypes\", x => x.Id);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"Orders\",\n            columns: table => new\n            {\n                Id = table.Column<int>(type: \"int\", nullable: false)\n                    .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                BuyerId = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                OrderDate = table.Column<DateTimeOffset>(type: \"datetimeoffset\", nullable: false),\n                ShipToAddress_Street = table.Column<string>(type: \"nvarchar(180)\", maxLength: 180, nullable: true),\n                ShipToAddress_City = table.Column<string>(type: \"nvarchar(100)\", maxLength: 100, nullable: true),\n                ShipToAddress_State = table.Column<string>(type: \"nvarchar(60)\", maxLength: 60, nullable: true),\n                ShipToAddress_Country = table.Column<string>(type: \"nvarchar(90)\", maxLength: 90, nullable: true),\n                ShipToAddress_ZipCode = table.Column<string>(type: \"nvarchar(18)\", maxLength: 18, nullable: true)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_Orders\", x => x.Id);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"BasketItems\",\n            columns: table => new\n            {\n                Id = table.Column<int>(type: \"int\", nullable: false)\n                    .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                UnitPrice = table.Column<decimal>(type: \"decimal(18,2)\", nullable: false),\n                Quantity = table.Column<int>(type: \"int\", nullable: false),\n                CatalogItemId = table.Column<int>(type: \"int\", nullable: false),\n                BasketId = table.Column<int>(type: \"int\", nullable: false)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_BasketItems\", x => x.Id);\n                table.ForeignKey(\n                    name: \"FK_BasketItems_Baskets_BasketId\",\n                    column: x => x.BasketId,\n                    principalTable: \"Baskets\",\n                    principalColumn: \"Id\",\n                    onDelete: ReferentialAction.Cascade);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"Catalog\",\n            columns: table => new\n            {\n                Id = table.Column<int>(type: \"int\", nullable: false),\n                Name = table.Column<string>(type: \"nvarchar(50)\", maxLength: 50, nullable: false),\n                Description = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                Price = table.Column<decimal>(type: \"decimal(18,2)\", nullable: false),\n                PictureUri = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                CatalogTypeId = table.Column<int>(type: \"int\", nullable: false),\n                CatalogBrandId = table.Column<int>(type: \"int\", nullable: false)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_Catalog\", x => x.Id);\n                table.ForeignKey(\n                    name: \"FK_Catalog_CatalogBrands_CatalogBrandId\",\n                    column: x => x.CatalogBrandId,\n                    principalTable: \"CatalogBrands\",\n                    principalColumn: \"Id\",\n                    onDelete: ReferentialAction.Cascade);\n                table.ForeignKey(\n                    name: \"FK_Catalog_CatalogTypes_CatalogTypeId\",\n                    column: x => x.CatalogTypeId,\n                    principalTable: \"CatalogTypes\",\n                    principalColumn: \"Id\",\n                    onDelete: ReferentialAction.Cascade);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"OrderItems\",\n            columns: table => new\n            {\n                Id = table.Column<int>(type: \"int\", nullable: false)\n                    .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                ItemOrdered_CatalogItemId = table.Column<int>(type: \"int\", nullable: true),\n                ItemOrdered_ProductName = table.Column<string>(type: \"nvarchar(50)\", maxLength: 50, nullable: true),\n                ItemOrdered_PictureUri = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                UnitPrice = table.Column<decimal>(type: \"decimal(18,2)\", nullable: false),\n                Units = table.Column<int>(type: \"int\", nullable: false),\n                OrderId = table.Column<int>(type: \"int\", nullable: true)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_OrderItems\", x => x.Id);\n                table.ForeignKey(\n                    name: \"FK_OrderItems_Orders_OrderId\",\n                    column: x => x.OrderId,\n                    principalTable: \"Orders\",\n                    principalColumn: \"Id\",\n                    onDelete: ReferentialAction.Restrict);\n            });\n\n        migrationBuilder.CreateIndex(\n            name: \"IX_BasketItems_BasketId\",\n            table: \"BasketItems\",\n            column: \"BasketId\");\n\n        migrationBuilder.CreateIndex(\n            name: \"IX_Catalog_CatalogBrandId\",\n            table: \"Catalog\",\n            column: \"CatalogBrandId\");\n\n        migrationBuilder.CreateIndex(\n            name: \"IX_Catalog_CatalogTypeId\",\n            table: \"Catalog\",\n            column: \"CatalogTypeId\");\n\n        migrationBuilder.CreateIndex(\n            name: \"IX_OrderItems_OrderId\",\n            table: \"OrderItems\",\n            column: \"OrderId\");\n    }\n\n    protected override void Down(MigrationBuilder migrationBuilder)\n    {\n        migrationBuilder.DropTable(\n            name: \"BasketItems\");\n\n        migrationBuilder.DropTable(\n            name: \"Catalog\");\n\n        migrationBuilder.DropTable(\n            name: \"OrderItems\");\n\n        migrationBuilder.DropTable(\n            name: \"Baskets\");\n\n        migrationBuilder.DropTable(\n            name: \"CatalogBrands\");\n\n        migrationBuilder.DropTable(\n            name: \"CatalogTypes\");\n\n        migrationBuilder.DropTable(\n            name: \"Orders\");\n\n        migrationBuilder.DropSequence(\n            name: \"catalog_brand_hilo\");\n\n        migrationBuilder.DropSequence(\n            name: \"catalog_hilo\");\n\n        migrationBuilder.DropSequence(\n            name: \"catalog_type_hilo\");\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Migrations/20211026175614_FixBuyerId.Designer.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing Microsoft.eShopWeb.Infrastructure.Data;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Migrations\n{\n    [DbContext(typeof(CatalogContext))]\n    [Migration(\"20211026175614_FixBuyerId\")]\n    partial class FixBuyerId\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"ProductVersion\", \"5.0.11\")\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.HasSequence(\"catalog_brand_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.HasSequence(\"catalog_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.HasSequence(\"catalog_type_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"BuyerId\")\n                        .IsRequired()\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Baskets\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"BasketId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"CatalogItemId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"Quantity\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"UnitPrice\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"BasketId\");\n\n                    b.ToTable(\"BasketItems\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:HiLoSequenceName\", \"catalog_brand_hilo\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.SequenceHiLo);\n\n                    b.Property<string>(\"Brand\")\n                        .IsRequired()\n                        .HasMaxLength(100)\n                        .HasColumnType(\"nvarchar(100)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"CatalogBrands\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:HiLoSequenceName\", \"catalog_hilo\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.SequenceHiLo);\n\n                    b.Property<int>(\"CatalogBrandId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"CatalogTypeId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"Description\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .IsRequired()\n                        .HasMaxLength(50)\n                        .HasColumnType(\"nvarchar(50)\");\n\n                    b.Property<string>(\"PictureUri\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<decimal>(\"Price\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CatalogBrandId\");\n\n                    b.HasIndex(\"CatalogTypeId\");\n\n                    b.ToTable(\"Catalog\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:HiLoSequenceName\", \"catalog_type_hilo\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.SequenceHiLo);\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasMaxLength(100)\n                        .HasColumnType(\"nvarchar(100)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"CatalogTypes\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"BuyerId\")\n                        .IsRequired()\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<DateTimeOffset>(\"OrderDate\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Orders\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int?>(\"OrderId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"UnitPrice\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.Property<int>(\"Units\")\n                        .HasColumnType(\"int\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"OrderId\");\n\n                    b.ToTable(\"OrderItems\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", null)\n                        .WithMany(\"Items\")\n                        .HasForeignKey(\"BasketId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand\", \"CatalogBrand\")\n                        .WithMany()\n                        .HasForeignKey(\"CatalogBrandId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType\", \"CatalogType\")\n                        .WithMany()\n                        .HasForeignKey(\"CatalogTypeId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.Navigation(\"CatalogBrand\");\n\n                    b.Navigation(\"CatalogType\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.OwnsOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address\", \"ShipToAddress\", b1 =>\n                        {\n                            b1.Property<int>(\"OrderId\")\n                                .ValueGeneratedOnAdd()\n                                .HasColumnType(\"int\")\n                                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                            b1.Property<string>(\"City\")\n                                .IsRequired()\n                                .HasMaxLength(100)\n                                .HasColumnType(\"nvarchar(100)\");\n\n                            b1.Property<string>(\"Country\")\n                                .IsRequired()\n                                .HasMaxLength(90)\n                                .HasColumnType(\"nvarchar(90)\");\n\n                            b1.Property<string>(\"State\")\n                                .HasMaxLength(60)\n                                .HasColumnType(\"nvarchar(60)\");\n\n                            b1.Property<string>(\"Street\")\n                                .IsRequired()\n                                .HasMaxLength(180)\n                                .HasColumnType(\"nvarchar(180)\");\n\n                            b1.Property<string>(\"ZipCode\")\n                                .IsRequired()\n                                .HasMaxLength(18)\n                                .HasColumnType(\"nvarchar(18)\");\n\n                            b1.HasKey(\"OrderId\");\n\n                            b1.ToTable(\"Orders\");\n\n                            b1.WithOwner()\n                                .HasForeignKey(\"OrderId\");\n                        });\n\n                    b.Navigation(\"ShipToAddress\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", null)\n                        .WithMany(\"OrderItems\")\n                        .HasForeignKey(\"OrderId\");\n\n                    b.OwnsOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered\", \"ItemOrdered\", b1 =>\n                        {\n                            b1.Property<int>(\"OrderItemId\")\n                                .ValueGeneratedOnAdd()\n                                .HasColumnType(\"int\")\n                                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                            b1.Property<int>(\"CatalogItemId\")\n                                .HasColumnType(\"int\");\n\n                            b1.Property<string>(\"PictureUri\")\n                                .HasColumnType(\"nvarchar(max)\");\n\n                            b1.Property<string>(\"ProductName\")\n                                .IsRequired()\n                                .HasMaxLength(50)\n                                .HasColumnType(\"nvarchar(50)\");\n\n                            b1.HasKey(\"OrderItemId\");\n\n                            b1.ToTable(\"OrderItems\");\n\n                            b1.WithOwner()\n                                .HasForeignKey(\"OrderItemId\");\n                        });\n\n                    b.Navigation(\"ItemOrdered\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", b =>\n                {\n                    b.Navigation(\"Items\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.Navigation(\"OrderItems\");\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Migrations/20211026175614_FixBuyerId.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Migrations;\n\npublic partial class FixBuyerId : Migration\n{\n    protected override void Up(MigrationBuilder migrationBuilder)\n    {\n        migrationBuilder.AlterColumn<string>(\n            name: \"BuyerId\",\n            table: \"Orders\",\n            type: \"nvarchar(256)\",\n            maxLength: 256,\n            nullable: false,\n            defaultValue: \"\",\n            oldClrType: typeof(string),\n            oldType: \"nvarchar(max)\",\n            oldNullable: true);\n\n        migrationBuilder.AlterColumn<string>(\n            name: \"BuyerId\",\n            table: \"Baskets\",\n            type: \"nvarchar(256)\",\n            maxLength: 256,\n            nullable: false,\n            oldClrType: typeof(string),\n            oldType: \"nvarchar(40)\",\n            oldMaxLength: 40);\n    }\n\n    protected override void Down(MigrationBuilder migrationBuilder)\n    {\n        migrationBuilder.AlterColumn<string>(\n            name: \"BuyerId\",\n            table: \"Orders\",\n            type: \"nvarchar(max)\",\n            nullable: true,\n            oldClrType: typeof(string),\n            oldType: \"nvarchar(256)\",\n            oldMaxLength: 256);\n\n        migrationBuilder.AlterColumn<string>(\n            name: \"BuyerId\",\n            table: \"Baskets\",\n            type: \"nvarchar(40)\",\n            maxLength: 40,\n            nullable: false,\n            oldClrType: typeof(string),\n            oldType: \"nvarchar(256)\",\n            oldMaxLength: 256);\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Migrations/20211231093753_FixShipToAddress.Designer.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing Microsoft.eShopWeb.Infrastructure.Data;\n\n#nullable disable\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Migrations\n{\n    [DbContext(typeof(CatalogContext))]\n    [Migration(\"20211231093753_FixShipToAddress\")]\n    partial class FixShipToAddress\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"6.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128);\n\n            SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);\n\n            modelBuilder.HasSequence(\"catalog_brand_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.HasSequence(\"catalog_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.HasSequence(\"catalog_type_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>(\"Id\"), 1L, 1);\n\n                    b.Property<string>(\"BuyerId\")\n                        .IsRequired()\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Baskets\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>(\"Id\"), 1L, 1);\n\n                    b.Property<int>(\"BasketId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"CatalogItemId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"Quantity\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"UnitPrice\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"BasketId\");\n\n                    b.ToTable(\"BasketItems\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseHiLo(b.Property<int>(\"Id\"), \"catalog_brand_hilo\");\n\n                    b.Property<string>(\"Brand\")\n                        .IsRequired()\n                        .HasMaxLength(100)\n                        .HasColumnType(\"nvarchar(100)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"CatalogBrands\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseHiLo(b.Property<int>(\"Id\"), \"catalog_hilo\");\n\n                    b.Property<int>(\"CatalogBrandId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"CatalogTypeId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"Description\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .IsRequired()\n                        .HasMaxLength(50)\n                        .HasColumnType(\"nvarchar(50)\");\n\n                    b.Property<string>(\"PictureUri\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<decimal>(\"Price\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CatalogBrandId\");\n\n                    b.HasIndex(\"CatalogTypeId\");\n\n                    b.ToTable(\"Catalog\", (string)null);\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseHiLo(b.Property<int>(\"Id\"), \"catalog_type_hilo\");\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasMaxLength(100)\n                        .HasColumnType(\"nvarchar(100)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"CatalogTypes\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>(\"Id\"), 1L, 1);\n\n                    b.Property<string>(\"BuyerId\")\n                        .IsRequired()\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<DateTimeOffset>(\"OrderDate\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Orders\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>(\"Id\"), 1L, 1);\n\n                    b.Property<int?>(\"OrderId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"UnitPrice\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.Property<int>(\"Units\")\n                        .HasColumnType(\"int\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"OrderId\");\n\n                    b.ToTable(\"OrderItems\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", null)\n                        .WithMany(\"Items\")\n                        .HasForeignKey(\"BasketId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand\", \"CatalogBrand\")\n                        .WithMany()\n                        .HasForeignKey(\"CatalogBrandId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType\", \"CatalogType\")\n                        .WithMany()\n                        .HasForeignKey(\"CatalogTypeId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.Navigation(\"CatalogBrand\");\n\n                    b.Navigation(\"CatalogType\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.OwnsOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address\", \"ShipToAddress\", b1 =>\n                        {\n                            b1.Property<int>(\"OrderId\")\n                                .HasColumnType(\"int\");\n\n                            b1.Property<string>(\"City\")\n                                .IsRequired()\n                                .HasMaxLength(100)\n                                .HasColumnType(\"nvarchar(100)\");\n\n                            b1.Property<string>(\"Country\")\n                                .IsRequired()\n                                .HasMaxLength(90)\n                                .HasColumnType(\"nvarchar(90)\");\n\n                            b1.Property<string>(\"State\")\n                                .HasMaxLength(60)\n                                .HasColumnType(\"nvarchar(60)\");\n\n                            b1.Property<string>(\"Street\")\n                                .IsRequired()\n                                .HasMaxLength(180)\n                                .HasColumnType(\"nvarchar(180)\");\n\n                            b1.Property<string>(\"ZipCode\")\n                                .IsRequired()\n                                .HasMaxLength(18)\n                                .HasColumnType(\"nvarchar(18)\");\n\n                            b1.HasKey(\"OrderId\");\n\n                            b1.ToTable(\"Orders\");\n\n                            b1.WithOwner()\n                                .HasForeignKey(\"OrderId\");\n                        });\n\n                    b.Navigation(\"ShipToAddress\")\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", null)\n                        .WithMany(\"OrderItems\")\n                        .HasForeignKey(\"OrderId\");\n\n                    b.OwnsOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered\", \"ItemOrdered\", b1 =>\n                        {\n                            b1.Property<int>(\"OrderItemId\")\n                                .HasColumnType(\"int\");\n\n                            b1.Property<int>(\"CatalogItemId\")\n                                .HasColumnType(\"int\");\n\n                            b1.Property<string>(\"PictureUri\")\n                                .HasColumnType(\"nvarchar(max)\");\n\n                            b1.Property<string>(\"ProductName\")\n                                .IsRequired()\n                                .HasMaxLength(50)\n                                .HasColumnType(\"nvarchar(50)\");\n\n                            b1.HasKey(\"OrderItemId\");\n\n                            b1.ToTable(\"OrderItems\");\n\n                            b1.WithOwner()\n                                .HasForeignKey(\"OrderItemId\");\n                        });\n\n                    b.Navigation(\"ItemOrdered\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", b =>\n                {\n                    b.Navigation(\"Items\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.Navigation(\"OrderItems\");\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Migrations/20211231093753_FixShipToAddress.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Migrations;\n\n#nullable disable\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Migrations\n{\n    public partial class FixShipToAddress : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.AlterColumn<string>(\n                name: \"ShipToAddress_ZipCode\",\n                table: \"Orders\",\n                type: \"nvarchar(18)\",\n                maxLength: 18,\n                nullable: false,\n                defaultValue: \"\",\n                oldClrType: typeof(string),\n                oldType: \"nvarchar(18)\",\n                oldMaxLength: 18,\n                oldNullable: true);\n\n            migrationBuilder.AlterColumn<string>(\n                name: \"ShipToAddress_Street\",\n                table: \"Orders\",\n                type: \"nvarchar(180)\",\n                maxLength: 180,\n                nullable: false,\n                defaultValue: \"\",\n                oldClrType: typeof(string),\n                oldType: \"nvarchar(180)\",\n                oldMaxLength: 180,\n                oldNullable: true);\n\n            migrationBuilder.AlterColumn<string>(\n                name: \"ShipToAddress_Country\",\n                table: \"Orders\",\n                type: \"nvarchar(90)\",\n                maxLength: 90,\n                nullable: false,\n                defaultValue: \"\",\n                oldClrType: typeof(string),\n                oldType: \"nvarchar(90)\",\n                oldMaxLength: 90,\n                oldNullable: true);\n\n            migrationBuilder.AlterColumn<string>(\n                name: \"ShipToAddress_City\",\n                table: \"Orders\",\n                type: \"nvarchar(100)\",\n                maxLength: 100,\n                nullable: false,\n                defaultValue: \"\",\n                oldClrType: typeof(string),\n                oldType: \"nvarchar(100)\",\n                oldMaxLength: 100,\n                oldNullable: true);\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.AlterColumn<string>(\n                name: \"ShipToAddress_ZipCode\",\n                table: \"Orders\",\n                type: \"nvarchar(18)\",\n                maxLength: 18,\n                nullable: true,\n                oldClrType: typeof(string),\n                oldType: \"nvarchar(18)\",\n                oldMaxLength: 18);\n\n            migrationBuilder.AlterColumn<string>(\n                name: \"ShipToAddress_Street\",\n                table: \"Orders\",\n                type: \"nvarchar(180)\",\n                maxLength: 180,\n                nullable: true,\n                oldClrType: typeof(string),\n                oldType: \"nvarchar(180)\",\n                oldMaxLength: 180);\n\n            migrationBuilder.AlterColumn<string>(\n                name: \"ShipToAddress_Country\",\n                table: \"Orders\",\n                type: \"nvarchar(90)\",\n                maxLength: 90,\n                nullable: true,\n                oldClrType: typeof(string),\n                oldType: \"nvarchar(90)\",\n                oldMaxLength: 90);\n\n            migrationBuilder.AlterColumn<string>(\n                name: \"ShipToAddress_City\",\n                table: \"Orders\",\n                type: \"nvarchar(100)\",\n                maxLength: 100,\n                nullable: true,\n                oldClrType: typeof(string),\n                oldType: \"nvarchar(100)\",\n                oldMaxLength: 100);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing Microsoft.eShopWeb.Infrastructure.Data;\n\n#nullable disable\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Migrations\n{\n    [DbContext(typeof(CatalogContext))]\n    partial class CatalogContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"6.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128);\n\n            SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);\n\n            modelBuilder.HasSequence(\"catalog_brand_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.HasSequence(\"catalog_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.HasSequence(\"catalog_type_hilo\")\n                .IncrementsBy(10);\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>(\"Id\"), 1L, 1);\n\n                    b.Property<string>(\"BuyerId\")\n                        .IsRequired()\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Baskets\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>(\"Id\"), 1L, 1);\n\n                    b.Property<int>(\"BasketId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"CatalogItemId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"Quantity\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"UnitPrice\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"BasketId\");\n\n                    b.ToTable(\"BasketItems\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseHiLo(b.Property<int>(\"Id\"), \"catalog_brand_hilo\");\n\n                    b.Property<string>(\"Brand\")\n                        .IsRequired()\n                        .HasMaxLength(100)\n                        .HasColumnType(\"nvarchar(100)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"CatalogBrands\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseHiLo(b.Property<int>(\"Id\"), \"catalog_hilo\");\n\n                    b.Property<int>(\"CatalogBrandId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<int>(\"CatalogTypeId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"Description\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .IsRequired()\n                        .HasMaxLength(50)\n                        .HasColumnType(\"nvarchar(50)\");\n\n                    b.Property<string>(\"PictureUri\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<decimal>(\"Price\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CatalogBrandId\");\n\n                    b.HasIndex(\"CatalogTypeId\");\n\n                    b.ToTable(\"Catalog\", (string)null);\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseHiLo(b.Property<int>(\"Id\"), \"catalog_type_hilo\");\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasMaxLength(100)\n                        .HasColumnType(\"nvarchar(100)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"CatalogTypes\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>(\"Id\"), 1L, 1);\n\n                    b.Property<string>(\"BuyerId\")\n                        .IsRequired()\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<DateTimeOffset>(\"OrderDate\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Orders\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\");\n\n                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>(\"Id\"), 1L, 1);\n\n                    b.Property<int?>(\"OrderId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"UnitPrice\")\n                        .HasColumnType(\"decimal(18,2)\");\n\n                    b.Property<int>(\"Units\")\n                        .HasColumnType(\"int\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"OrderId\");\n\n                    b.ToTable(\"OrderItems\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", null)\n                        .WithMany(\"Items\")\n                        .HasForeignKey(\"BasketId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand\", \"CatalogBrand\")\n                        .WithMany()\n                        .HasForeignKey(\"CatalogBrandId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType\", \"CatalogType\")\n                        .WithMany()\n                        .HasForeignKey(\"CatalogTypeId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.Navigation(\"CatalogBrand\");\n\n                    b.Navigation(\"CatalogType\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.OwnsOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address\", \"ShipToAddress\", b1 =>\n                        {\n                            b1.Property<int>(\"OrderId\")\n                                .HasColumnType(\"int\");\n\n                            b1.Property<string>(\"City\")\n                                .IsRequired()\n                                .HasMaxLength(100)\n                                .HasColumnType(\"nvarchar(100)\");\n\n                            b1.Property<string>(\"Country\")\n                                .IsRequired()\n                                .HasMaxLength(90)\n                                .HasColumnType(\"nvarchar(90)\");\n\n                            b1.Property<string>(\"State\")\n                                .HasMaxLength(60)\n                                .HasColumnType(\"nvarchar(60)\");\n\n                            b1.Property<string>(\"Street\")\n                                .IsRequired()\n                                .HasMaxLength(180)\n                                .HasColumnType(\"nvarchar(180)\");\n\n                            b1.Property<string>(\"ZipCode\")\n                                .IsRequired()\n                                .HasMaxLength(18)\n                                .HasColumnType(\"nvarchar(18)\");\n\n                            b1.HasKey(\"OrderId\");\n\n                            b1.ToTable(\"Orders\");\n\n                            b1.WithOwner()\n                                .HasForeignKey(\"OrderId\");\n                        });\n\n                    b.Navigation(\"ShipToAddress\")\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", null)\n                        .WithMany(\"OrderItems\")\n                        .HasForeignKey(\"OrderId\");\n\n                    b.OwnsOne(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered\", \"ItemOrdered\", b1 =>\n                        {\n                            b1.Property<int>(\"OrderItemId\")\n                                .HasColumnType(\"int\");\n\n                            b1.Property<int>(\"CatalogItemId\")\n                                .HasColumnType(\"int\");\n\n                            b1.Property<string>(\"PictureUri\")\n                                .HasColumnType(\"nvarchar(max)\");\n\n                            b1.Property<string>(\"ProductName\")\n                                .IsRequired()\n                                .HasMaxLength(50)\n                                .HasColumnType(\"nvarchar(50)\");\n\n                            b1.HasKey(\"OrderItemId\");\n\n                            b1.ToTable(\"OrderItems\");\n\n                            b1.WithOwner()\n                                .HasForeignKey(\"OrderItemId\");\n                        });\n\n                    b.Navigation(\"ItemOrdered\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket\", b =>\n                {\n                    b.Navigation(\"Items\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order\", b =>\n                {\n                    b.Navigation(\"OrderItems\");\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Data/Queries/BasketQueryService.cs",
    "content": "﻿using System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Data.Queries;\n\npublic class BasketQueryService : IBasketQueryService\n{\n    private readonly CatalogContext _dbContext;\n\n    public BasketQueryService(CatalogContext dbContext)\n    {\n        _dbContext = dbContext;\n    }\n\n    /// <summary>\n    /// This method performs the sum on the database rather than in memory\n    /// </summary>\n    /// <param name=\"username\"></param>\n    /// <returns></returns>\n    public async Task<int> CountTotalBasketItems(string username)\n    {\n        var totalItems = await _dbContext.Baskets\n            .Where(basket => basket.BuyerId == username)\n            .SelectMany(item => item.Items)\n            .SumAsync(sum => sum.Quantity);\n\n        return totalItems;\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Dependencies.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb.Infrastructure.Data;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace Microsoft.eShopWeb.Infrastructure;\n\npublic static class Dependencies\n{\n    public static void ConfigureServices(IConfiguration configuration, IServiceCollection services)\n    {\n        bool useOnlyInMemoryDatabase = false;\n        if (configuration[\"UseOnlyInMemoryDatabase\"] != null)\n        {\n            useOnlyInMemoryDatabase = bool.Parse(configuration[\"UseOnlyInMemoryDatabase\"]!);\n        }\n\n        if (useOnlyInMemoryDatabase)\n        {\n            services.AddDbContext<CatalogContext>(c =>\n               c.UseInMemoryDatabase(\"Catalog\"));\n         \n            services.AddDbContext<AppIdentityDbContext>(options =>\n                options.UseInMemoryDatabase(\"Identity\"));\n        }\n        else\n        {\n            // use real database\n            // Requires LocalDB which can be installed with SQL Server Express 2016\n            // https://www.microsoft.com/en-us/download/details.aspx?id=54284\n            services.AddDbContext<CatalogContext>(c =>\n                c.UseSqlServer(configuration.GetConnectionString(\"CatalogConnection\")));\n\n            // Add Identity DbContext\n            services.AddDbContext<AppIdentityDbContext>(options =>\n                options.UseSqlServer(configuration.GetConnectionString(\"IdentityConnection\")));\n        }\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Identity/AppIdentityDbContext.cs",
    "content": "﻿using Microsoft.AspNetCore.Identity.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore;\n\n\nnamespace Microsoft.eShopWeb.Infrastructure.Identity;\n\npublic class AppIdentityDbContext : IdentityDbContext<ApplicationUser>\n{\n    public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options)\n        : base(options)\n    {\n    }\n\n    protected override void OnModelCreating(ModelBuilder builder)\n    {\n        base.OnModelCreating(builder);\n        // Customize the ASP.NET Identity model and override the defaults if needed.\n        // For example, you can rename the ASP.NET Identity table names and more.\n        // Add your customizations after calling base.OnModelCreating(builder);\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Identity/AppIdentityDbContextSeed.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb.ApplicationCore.Constants;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Identity;\n\npublic class AppIdentityDbContextSeed\n{\n    public static async Task SeedAsync(AppIdentityDbContext identityDbContext, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)\n    {\n\n        if (identityDbContext.Database.IsSqlServer())\n        {\n            identityDbContext.Database.Migrate();\n        }\n\n        await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS));\n\n        var defaultUser = new ApplicationUser { UserName = \"demouser@microsoft.com\", Email = \"demouser@microsoft.com\" };\n        await userManager.CreateAsync(defaultUser, AuthorizationConstants.DEFAULT_PASSWORD);\n\n        string adminUserName = \"admin@microsoft.com\";\n        var adminUser = new ApplicationUser { UserName = adminUserName, Email = adminUserName };\n        await userManager.CreateAsync(adminUser, AuthorizationConstants.DEFAULT_PASSWORD);\n        adminUser = await userManager.FindByNameAsync(adminUserName);\n        if (adminUser != null)\n        {\n            await userManager.AddToRoleAsync(adminUser, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Identity/ApplicationUser.cs",
    "content": "﻿using Microsoft.AspNetCore.Identity;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Identity;\n\npublic class ApplicationUser : IdentityUser\n{\n}\n"
  },
  {
    "path": "src/Infrastructure/Identity/IdentityTokenClaimService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IdentityModel.Tokens.Jwt;\nusing System.Security.Claims;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.eShopWeb.ApplicationCore.Constants;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.IdentityModel.Tokens;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Identity;\n\npublic class IdentityTokenClaimService : ITokenClaimsService\n{\n    private readonly UserManager<ApplicationUser> _userManager;\n\n    public IdentityTokenClaimService(UserManager<ApplicationUser> userManager)\n    {\n        _userManager = userManager;\n    }\n\n    public async Task<string> GetTokenAsync(string userName)\n    {\n        var tokenHandler = new JwtSecurityTokenHandler();\n        var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY);\n        var user = await _userManager.FindByNameAsync(userName);\n        if (user == null) throw new UserNotFoundException(userName);\n        var roles = await _userManager.GetRolesAsync(user);\n        var claims = new List<Claim> { new Claim(ClaimTypes.Name, userName) };\n\n        foreach (var role in roles)\n        {\n            claims.Add(new Claim(ClaimTypes.Role, role));\n        }\n\n        var tokenDescriptor = new SecurityTokenDescriptor\n        {\n            Subject = new ClaimsIdentity(claims.ToArray()),\n            Expires = DateTime.UtcNow.AddDays(7),\n            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)\n        };\n        var token = tokenHandler.CreateToken(tokenDescriptor);\n        return tokenHandler.WriteToken(token);\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations\n{\n    [DbContext(typeof(AppIdentityDbContext))]\n    [Migration(\"20201202111612_InitialIdentityModel\")]\n    partial class InitialIdentityModel\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .UseIdentityColumns()\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"ProductVersion\", \"5.0.0\");\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRole\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<string>(\"NormalizedName\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedName\")\n                        .IsUnique()\n                        .HasDatabaseName(\"RoleNameIndex\")\n                        .HasFilter(\"[NormalizedName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseIdentityColumn();\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetRoleClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseIdentityColumn();\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ProviderKey\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ProviderDisplayName\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"LoginProvider\", \"ProviderKey\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserLogins\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"UserId\", \"RoleId\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetUserRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"Value\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"UserId\", \"LoginProvider\", \"Name\");\n\n                    b.ToTable(\"AspNetUserTokens\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<int>(\"AccessFailedCount\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Email\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<bool>(\"EmailConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<bool>(\"LockoutEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<DateTimeOffset?>(\"LockoutEnd\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.Property<string>(\"NormalizedEmail\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<string>(\"NormalizedUserName\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<string>(\"PasswordHash\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"PhoneNumber\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"PhoneNumberConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"SecurityStamp\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"TwoFactorEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"UserName\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedEmail\")\n                        .HasDatabaseName(\"EmailIndex\");\n\n                    b.HasIndex(\"NormalizedUserName\")\n                        .IsUnique()\n                        .HasDatabaseName(\"UserNameIndex\")\n                        .HasFilter(\"[NormalizedUserName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetUsers\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs",
    "content": "﻿using System;\nusing Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations;\n\npublic partial class InitialIdentityModel : Migration\n{\n    protected override void Up(MigrationBuilder migrationBuilder)\n    {\n        migrationBuilder.CreateTable(\n            name: \"AspNetRoles\",\n            columns: table => new\n            {\n                Id = table.Column<string>(type: \"nvarchar(450)\", nullable: false),\n                Name = table.Column<string>(type: \"nvarchar(256)\", maxLength: 256, nullable: true),\n                NormalizedName = table.Column<string>(type: \"nvarchar(256)\", maxLength: 256, nullable: true),\n                ConcurrencyStamp = table.Column<string>(type: \"nvarchar(max)\", nullable: true)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_AspNetRoles\", x => x.Id);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"AspNetUsers\",\n            columns: table => new\n            {\n                Id = table.Column<string>(type: \"nvarchar(450)\", nullable: false),\n                UserName = table.Column<string>(type: \"nvarchar(256)\", maxLength: 256, nullable: true),\n                NormalizedUserName = table.Column<string>(type: \"nvarchar(256)\", maxLength: 256, nullable: true),\n                Email = table.Column<string>(type: \"nvarchar(256)\", maxLength: 256, nullable: true),\n                NormalizedEmail = table.Column<string>(type: \"nvarchar(256)\", maxLength: 256, nullable: true),\n                EmailConfirmed = table.Column<bool>(type: \"bit\", nullable: false),\n                PasswordHash = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                SecurityStamp = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                ConcurrencyStamp = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                PhoneNumber = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                PhoneNumberConfirmed = table.Column<bool>(type: \"bit\", nullable: false),\n                TwoFactorEnabled = table.Column<bool>(type: \"bit\", nullable: false),\n                LockoutEnd = table.Column<DateTimeOffset>(type: \"datetimeoffset\", nullable: true),\n                LockoutEnabled = table.Column<bool>(type: \"bit\", nullable: false),\n                AccessFailedCount = table.Column<int>(type: \"int\", nullable: false)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_AspNetUsers\", x => x.Id);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"AspNetRoleClaims\",\n            columns: table => new\n            {\n                Id = table.Column<int>(type: \"int\", nullable: false)\n                    .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                RoleId = table.Column<string>(type: \"nvarchar(450)\", nullable: false),\n                ClaimType = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                ClaimValue = table.Column<string>(type: \"nvarchar(max)\", nullable: true)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_AspNetRoleClaims\", x => x.Id);\n                table.ForeignKey(\n                    name: \"FK_AspNetRoleClaims_AspNetRoles_RoleId\",\n                    column: x => x.RoleId,\n                    principalTable: \"AspNetRoles\",\n                    principalColumn: \"Id\",\n                    onDelete: ReferentialAction.Cascade);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"AspNetUserClaims\",\n            columns: table => new\n            {\n                Id = table.Column<int>(type: \"int\", nullable: false)\n                    .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                UserId = table.Column<string>(type: \"nvarchar(450)\", nullable: false),\n                ClaimType = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                ClaimValue = table.Column<string>(type: \"nvarchar(max)\", nullable: true)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_AspNetUserClaims\", x => x.Id);\n                table.ForeignKey(\n                    name: \"FK_AspNetUserClaims_AspNetUsers_UserId\",\n                    column: x => x.UserId,\n                    principalTable: \"AspNetUsers\",\n                    principalColumn: \"Id\",\n                    onDelete: ReferentialAction.Cascade);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"AspNetUserLogins\",\n            columns: table => new\n            {\n                LoginProvider = table.Column<string>(type: \"nvarchar(450)\", nullable: false),\n                ProviderKey = table.Column<string>(type: \"nvarchar(450)\", nullable: false),\n                ProviderDisplayName = table.Column<string>(type: \"nvarchar(max)\", nullable: true),\n                UserId = table.Column<string>(type: \"nvarchar(450)\", nullable: false)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_AspNetUserLogins\", x => new { x.LoginProvider, x.ProviderKey });\n                table.ForeignKey(\n                    name: \"FK_AspNetUserLogins_AspNetUsers_UserId\",\n                    column: x => x.UserId,\n                    principalTable: \"AspNetUsers\",\n                    principalColumn: \"Id\",\n                    onDelete: ReferentialAction.Cascade);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"AspNetUserRoles\",\n            columns: table => new\n            {\n                UserId = table.Column<string>(type: \"nvarchar(450)\", nullable: false),\n                RoleId = table.Column<string>(type: \"nvarchar(450)\", nullable: false)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_AspNetUserRoles\", x => new { x.UserId, x.RoleId });\n                table.ForeignKey(\n                    name: \"FK_AspNetUserRoles_AspNetRoles_RoleId\",\n                    column: x => x.RoleId,\n                    principalTable: \"AspNetRoles\",\n                    principalColumn: \"Id\",\n                    onDelete: ReferentialAction.Cascade);\n                table.ForeignKey(\n                    name: \"FK_AspNetUserRoles_AspNetUsers_UserId\",\n                    column: x => x.UserId,\n                    principalTable: \"AspNetUsers\",\n                    principalColumn: \"Id\",\n                    onDelete: ReferentialAction.Cascade);\n            });\n\n        migrationBuilder.CreateTable(\n            name: \"AspNetUserTokens\",\n            columns: table => new\n            {\n                UserId = table.Column<string>(type: \"nvarchar(450)\", nullable: false),\n                LoginProvider = table.Column<string>(type: \"nvarchar(450)\", nullable: false),\n                Name = table.Column<string>(type: \"nvarchar(450)\", nullable: false),\n                Value = table.Column<string>(type: \"nvarchar(max)\", nullable: true)\n            },\n            constraints: table =>\n            {\n                table.PrimaryKey(\"PK_AspNetUserTokens\", x => new { x.UserId, x.LoginProvider, x.Name });\n                table.ForeignKey(\n                    name: \"FK_AspNetUserTokens_AspNetUsers_UserId\",\n                    column: x => x.UserId,\n                    principalTable: \"AspNetUsers\",\n                    principalColumn: \"Id\",\n                    onDelete: ReferentialAction.Cascade);\n            });\n\n        migrationBuilder.CreateIndex(\n            name: \"IX_AspNetRoleClaims_RoleId\",\n            table: \"AspNetRoleClaims\",\n            column: \"RoleId\");\n\n        migrationBuilder.CreateIndex(\n            name: \"RoleNameIndex\",\n            table: \"AspNetRoles\",\n            column: \"NormalizedName\",\n            unique: true,\n            filter: \"[NormalizedName] IS NOT NULL\");\n\n        migrationBuilder.CreateIndex(\n            name: \"IX_AspNetUserClaims_UserId\",\n            table: \"AspNetUserClaims\",\n            column: \"UserId\");\n\n        migrationBuilder.CreateIndex(\n            name: \"IX_AspNetUserLogins_UserId\",\n            table: \"AspNetUserLogins\",\n            column: \"UserId\");\n\n        migrationBuilder.CreateIndex(\n            name: \"IX_AspNetUserRoles_RoleId\",\n            table: \"AspNetUserRoles\",\n            column: \"RoleId\");\n\n        migrationBuilder.CreateIndex(\n            name: \"EmailIndex\",\n            table: \"AspNetUsers\",\n            column: \"NormalizedEmail\");\n\n        migrationBuilder.CreateIndex(\n            name: \"UserNameIndex\",\n            table: \"AspNetUsers\",\n            column: \"NormalizedUserName\",\n            unique: true,\n            filter: \"[NormalizedUserName] IS NOT NULL\");\n    }\n\n    protected override void Down(MigrationBuilder migrationBuilder)\n    {\n        migrationBuilder.DropTable(\n            name: \"AspNetRoleClaims\");\n\n        migrationBuilder.DropTable(\n            name: \"AspNetUserClaims\");\n\n        migrationBuilder.DropTable(\n            name: \"AspNetUserLogins\");\n\n        migrationBuilder.DropTable(\n            name: \"AspNetUserRoles\");\n\n        migrationBuilder.DropTable(\n            name: \"AspNetUserTokens\");\n\n        migrationBuilder.DropTable(\n            name: \"AspNetRoles\");\n\n        migrationBuilder.DropTable(\n            name: \"AspNetUsers\");\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations\n{\n    [DbContext(typeof(AppIdentityDbContext))]\n    partial class AppIdentityDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .UseIdentityColumns()\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"ProductVersion\", \"5.0.0\");\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRole\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<string>(\"NormalizedName\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedName\")\n                        .IsUnique()\n                        .HasDatabaseName(\"RoleNameIndex\")\n                        .HasFilter(\"[NormalizedName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseIdentityColumn();\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetRoleClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .UseIdentityColumn();\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ProviderKey\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ProviderDisplayName\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"LoginProvider\", \"ProviderKey\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserLogins\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"UserId\", \"RoleId\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetUserRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"Value\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"UserId\", \"LoginProvider\", \"Name\");\n\n                    b.ToTable(\"AspNetUserTokens\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<int>(\"AccessFailedCount\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Email\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<bool>(\"EmailConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<bool>(\"LockoutEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<DateTimeOffset?>(\"LockoutEnd\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.Property<string>(\"NormalizedEmail\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<string>(\"NormalizedUserName\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.Property<string>(\"PasswordHash\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"PhoneNumber\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"PhoneNumberConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"SecurityStamp\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"TwoFactorEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"UserName\")\n                        .HasMaxLength(256)\n                        .HasColumnType(\"nvarchar(256)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedEmail\")\n                        .HasDatabaseName(\"EmailIndex\");\n\n                    b.HasIndex(\"NormalizedUserName\")\n                        .IsUnique()\n                        .HasDatabaseName(\"UserNameIndex\")\n                        .HasFilter(\"[NormalizedUserName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetUsers\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Identity/UserNotFoundException.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Identity;\n\npublic class UserNotFoundException : Exception\n{\n    public UserNotFoundException(string userName) : base($\"No user found with username: {userName}\")\n    {\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Infrastructure.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\t\t\n\t\t<RootNamespace>Microsoft.eShopWeb.Infrastructure</RootNamespace>\n\t\t<Nullable>enable</Nullable>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t\t<PackageReference Include=\"Ardalis.Specification.EntityFrameworkCore\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Identity.EntityFrameworkCore\" />\n\t\t<PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" />\n\t\t<PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" />\n\t\t<PackageReference Include=\"System.IdentityModel.Tokens.Jwt\" />\n\t</ItemGroup>\n\t<ItemGroup>\n\t\t<ProjectReference Include=\"..\\ApplicationCore\\ApplicationCore.csproj\" />\n\t</ItemGroup>\n\t<ItemGroup>\n\t\t<Folder Include=\"Data\\Migrations\\\" />\n\t</ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/Infrastructure/Logging/LoggerAdapter.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.Extensions.Logging;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Logging;\n\npublic class LoggerAdapter<T> : IAppLogger<T>\n{\n    private readonly ILogger<T> _logger;\n    public LoggerAdapter(ILoggerFactory loggerFactory)\n    {\n        _logger = loggerFactory.CreateLogger<T>();\n    }\n\n    public void LogWarning(string message, params object[] args)\n    {\n        _logger.LogWarning(message, args);\n    }\n\n    public void LogInformation(string message, params object[] args)\n    {\n        _logger.LogInformation(message, args);\n    }\n}\n"
  },
  {
    "path": "src/Infrastructure/Services/EmailSender.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.Infrastructure.Services;\n\n// This class is used by the application to send email for account confirmation and password reset.\n// For more details see https://go.microsoft.com/fwlink/?LinkID=532713\npublic class EmailSender : IEmailSender\n{\n    public Task SendEmailAsync(string email, string subject, string message)\n    {\n        // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc.\n        return Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/AuthEndpoints/AuthenticateEndpoint.AuthenticateRequest.cs",
    "content": "﻿namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints;\n\npublic class AuthenticateRequest : BaseRequest\n{\n    public string Username { get; set; }\n    public string Password { get; set; }\n}\n"
  },
  {
    "path": "src/PublicApi/AuthEndpoints/AuthenticateEndpoint.AuthenticateResponse.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.PublicApi.AuthEndpoints;\n\npublic class AuthenticateResponse : BaseResponse\n{\n    public AuthenticateResponse(Guid correlationId) : base(correlationId)\n    {\n    }\n\n    public AuthenticateResponse()\n    {\n    }\n    public bool Result { get; set; } = false;\n    public string Token { get; set; } = string.Empty;\n    public string Username { get; set; } = string.Empty;\n    public bool IsLockedOut { get; set; } = false;\n    public bool IsNotAllowed { get; set; } = false;\n    public bool RequiresTwoFactor { get; set; } = false;\n}\n"
  },
  {
    "path": "src/PublicApi/AuthEndpoints/AuthenticateEndpoint.ClaimValue.cs",
    "content": "﻿namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints;\n\npublic class ClaimValue\n{\n    public ClaimValue()\n    {\n    }\n\n    public ClaimValue(string type, string value)\n    {\n        Type = type;\n        Value = value;\n    }\n\n    public string Type { get; set; } = string.Empty;\n    public string Value { get; set; } = string.Empty;\n}\n"
  },
  {
    "path": "src/PublicApi/AuthEndpoints/AuthenticateEndpoint.UserInfo.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace Microsoft.eShopWeb.PublicApi.AuthEndpoints;\n\npublic class UserInfo\n{\n    public static readonly UserInfo Anonymous = new UserInfo();\n    public bool IsAuthenticated { get; set; }\n    public string NameClaimType { get; set; } = string.Empty;\n    public string RoleClaimType { get; set; } = string.Empty;\n    public IEnumerable<ClaimValue> Claims { get; set; } = new List<ClaimValue>();\n}\n"
  },
  {
    "path": "src/PublicApi/AuthEndpoints/AuthenticateEndpoint.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Ardalis.ApiEndpoints;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Swashbuckle.AspNetCore.Annotations;\n\nnamespace Microsoft.eShopWeb.PublicApi.AuthEndpoints;\n\n/// <summary>\n/// Authenticates a user\n/// </summary>\npublic class AuthenticateEndpoint : EndpointBaseAsync\n    .WithRequest<AuthenticateRequest>\n    .WithActionResult<AuthenticateResponse>\n{\n    private readonly SignInManager<ApplicationUser> _signInManager;\n    private readonly ITokenClaimsService _tokenClaimsService;\n\n    public AuthenticateEndpoint(SignInManager<ApplicationUser> signInManager,\n        ITokenClaimsService tokenClaimsService)\n    {\n        _signInManager = signInManager;\n        _tokenClaimsService = tokenClaimsService;\n    }\n\n    [HttpPost(\"api/authenticate\")]\n    [SwaggerOperation(\n        Summary = \"Authenticates a user\",\n        Description = \"Authenticates a user\",\n        OperationId = \"auth.authenticate\",\n        Tags = new[] { \"AuthEndpoints\" })\n    ]\n    public override async Task<ActionResult<AuthenticateResponse>> HandleAsync(AuthenticateRequest request,\n        CancellationToken cancellationToken = default)\n    {\n        var response = new AuthenticateResponse(request.CorrelationId());\n\n        // This doesn't count login failures towards account lockout\n        // To enable password failures to trigger account lockout, set lockoutOnFailure: true\n        //var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);\n        var result = await _signInManager.PasswordSignInAsync(request.Username, request.Password, false, true);\n\n        response.Result = result.Succeeded;\n        response.IsLockedOut = result.IsLockedOut;\n        response.IsNotAllowed = result.IsNotAllowed;\n        response.RequiresTwoFactor = result.RequiresTwoFactor;\n        response.Username = request.Username;\n\n        if (result.Succeeded)\n        {\n            response.Token = await _tokenClaimsService.GetTokenAsync(request.Username);\n        }\n\n        return response;\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/BaseMessage.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.PublicApi;\n\n/// <summary>\n/// Base class used by API requests\n/// </summary>\npublic abstract class BaseMessage\n{\n    /// <summary>\n    /// Unique Identifier used by logging\n    /// </summary>\n    protected Guid _correlationId = Guid.NewGuid();\n    public Guid CorrelationId() => _correlationId;\n}\n"
  },
  {
    "path": "src/PublicApi/BaseRequest.cs",
    "content": "﻿namespace Microsoft.eShopWeb.PublicApi;\n\n/// <summary>\n/// Base class used by API requests\n/// </summary>\npublic abstract class BaseRequest : BaseMessage\n{\n}\n"
  },
  {
    "path": "src/PublicApi/BaseResponse.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.PublicApi;\n\n/// <summary>\n/// Base class used by API responses\n/// </summary>\npublic abstract class BaseResponse : BaseMessage\n{\n    public BaseResponse(Guid correlationId) : base()\n    {\n        base._correlationId = correlationId;\n    }\n\n    public BaseResponse()\n    {\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogBrandEndpoints/CatalogBrandDto.cs",
    "content": "﻿namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints;\n\npublic class CatalogBrandDto\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogBrandEndpoints/CatalogBrandListEndpoint.ListCatalogBrandsResponse.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints;\n\npublic class ListCatalogBrandsResponse : BaseResponse\n{\n    public ListCatalogBrandsResponse(Guid correlationId) : base(correlationId)\n    {\n    }\n\n    public ListCatalogBrandsResponse()\n    {\n    }\n\n    public List<CatalogBrandDto> CatalogBrands { get; set; } = new List<CatalogBrandDto>();\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogBrandEndpoints/CatalogBrandListEndpoint.cs",
    "content": "﻿using System.Linq;\nusing System.Threading.Tasks;\nusing AutoMapper;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Routing;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing MinimalApi.Endpoint;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints;\n\n/// <summary>\n/// List Catalog Brands\n/// </summary>\npublic class CatalogBrandListEndpoint : IEndpoint<IResult, IRepository<CatalogBrand>>\n{\n    private readonly IMapper _mapper;\n\n    public CatalogBrandListEndpoint(IMapper mapper)\n    {\n        _mapper = mapper;\n    }\n\n    public void AddRoute(IEndpointRouteBuilder app)\n    {\n        app.MapGet(\"api/catalog-brands\",\n            async (IRepository<CatalogBrand> catalogBrandRepository) =>\n            {\n                return await HandleAsync(catalogBrandRepository);\n            })\n           .Produces<ListCatalogBrandsResponse>()\n           .WithTags(\"CatalogBrandEndpoints\");\n    }\n\n    public async Task<IResult> HandleAsync(IRepository<CatalogBrand> catalogBrandRepository)\n    {\n        var response = new ListCatalogBrandsResponse();\n\n        var items = await catalogBrandRepository.ListAsync();\n\n        response.CatalogBrands.AddRange(items.Select(_mapper.Map<CatalogBrandDto>));\n\n        return Results.Ok(response);\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/CatalogItemDto.cs",
    "content": "﻿namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class CatalogItemDto\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n    public string Description { get; set; }\n    public decimal Price { get; set; }\n    public string PictureUri { get; set; }\n    public int CatalogTypeId { get; set; }\n    public int CatalogBrandId { get; set; }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/CatalogItemGetByIdEndpoint.GetByIdCatalogItemRequest.cs",
    "content": "﻿namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class GetByIdCatalogItemRequest : BaseRequest\n{\n    public int CatalogItemId { get; init; }\n\n    public GetByIdCatalogItemRequest(int catalogItemId)\n    {\n        CatalogItemId = catalogItemId;\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/CatalogItemGetByIdEndpoint.GetByIdCatalogItemResponse.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class GetByIdCatalogItemResponse : BaseResponse\n{\n    public GetByIdCatalogItemResponse(Guid correlationId) : base(correlationId)\n    {\n    }\n\n    public GetByIdCatalogItemResponse()\n    {\n    }\n\n    public CatalogItemDto CatalogItem { get; set; }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/CatalogItemGetByIdEndpoint.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Routing;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing MinimalApi.Endpoint;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\n/// <summary>\n/// Get a Catalog Item by Id\n/// </summary>\npublic class CatalogItemGetByIdEndpoint : IEndpoint<IResult, GetByIdCatalogItemRequest, IRepository<CatalogItem>>\n{\n    private readonly IUriComposer _uriComposer;\n\n    public CatalogItemGetByIdEndpoint(IUriComposer uriComposer)\n    {\n        _uriComposer = uriComposer;\n    }\n\n    public void AddRoute(IEndpointRouteBuilder app)\n    {\n        app.MapGet(\"api/catalog-items/{catalogItemId}\",\n            async (int catalogItemId, IRepository<CatalogItem> itemRepository) =>\n            {\n                return await HandleAsync(new GetByIdCatalogItemRequest(catalogItemId), itemRepository);\n            })\n            .Produces<GetByIdCatalogItemResponse>()\n            .WithTags(\"CatalogItemEndpoints\");\n    }\n\n    public async Task<IResult> HandleAsync(GetByIdCatalogItemRequest request, IRepository<CatalogItem> itemRepository)\n    {\n        var response = new GetByIdCatalogItemResponse(request.CorrelationId());\n\n        var item = await itemRepository.GetByIdAsync(request.CatalogItemId);\n        if (item is null)\n            return Results.NotFound();\n\n        response.CatalogItem = new CatalogItemDto\n        {\n            Id = item.Id,\n            CatalogBrandId = item.CatalogBrandId,\n            CatalogTypeId = item.CatalogTypeId,\n            Description = item.Description,\n            Name = item.Name,\n            PictureUri = _uriComposer.ComposePicUri(item.PictureUri),\n            Price = item.Price\n        };\n        return Results.Ok(response);\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.ListPagedCatalogItemRequest.cs",
    "content": "﻿namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class ListPagedCatalogItemRequest : BaseRequest\n{\n    public int PageSize { get; init; }\n    public int PageIndex { get; init; }\n    public int? CatalogBrandId { get; init; }\n    public int? CatalogTypeId { get; init; }\n\n    public ListPagedCatalogItemRequest(int? pageSize, int? pageIndex, int? catalogBrandId, int? catalogTypeId)\n    {\n        PageSize = pageSize ?? 0;\n        PageIndex = pageIndex ?? 0;\n        CatalogBrandId = catalogBrandId;\n        CatalogTypeId = catalogTypeId;\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.ListPagedCatalogItemResponse.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class ListPagedCatalogItemResponse : BaseResponse\n{\n    public ListPagedCatalogItemResponse(Guid correlationId) : base(correlationId)\n    {\n    }\n\n    public ListPagedCatalogItemResponse()\n    {\n    }\n\n    public List<CatalogItemDto> CatalogItems { get; set; } = new List<CatalogItemDto>();\n    public int PageCount { get; set; }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing AutoMapper;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Routing;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing MinimalApi.Endpoint;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\n/// <summary>\n/// List Catalog Items (paged)\n/// </summary>\npublic class CatalogItemListPagedEndpoint : IEndpoint<IResult, ListPagedCatalogItemRequest, IRepository<CatalogItem>>\n{\n    private readonly IUriComposer _uriComposer;\n    private readonly IMapper _mapper;\n\n    public CatalogItemListPagedEndpoint(IUriComposer uriComposer, IMapper mapper)\n    {\n        _uriComposer = uriComposer;\n        _mapper = mapper;\n    }\n\n    public void AddRoute(IEndpointRouteBuilder app)\n    {\n        app.MapGet(\"api/catalog-items\",\n            async (int? pageSize, int? pageIndex, int? catalogBrandId, int? catalogTypeId, IRepository<CatalogItem> itemRepository) =>\n            {\n                return await HandleAsync(new ListPagedCatalogItemRequest(pageSize, pageIndex, catalogBrandId, catalogTypeId), itemRepository);\n            })\n            .Produces<ListPagedCatalogItemResponse>()\n            .WithTags(\"CatalogItemEndpoints\");\n    }\n\n    public async Task<IResult> HandleAsync(ListPagedCatalogItemRequest request, IRepository<CatalogItem> itemRepository)\n    {\n        await Task.Delay(1000);\n        var response = new ListPagedCatalogItemResponse(request.CorrelationId());\n\n        var filterSpec = new CatalogFilterSpecification(request.CatalogBrandId, request.CatalogTypeId);\n        int totalItems = await itemRepository.CountAsync(filterSpec);\n\n        var pagedSpec = new CatalogFilterPaginatedSpecification(\n            skip: request.PageIndex * request.PageSize,\n            take: request.PageSize,\n            brandId: request.CatalogBrandId,\n            typeId: request.CatalogTypeId);\n\n        var items = await itemRepository.ListAsync(pagedSpec);\n\n        response.CatalogItems.AddRange(items.Select(_mapper.Map<CatalogItemDto>));\n        foreach (CatalogItemDto item in response.CatalogItems)\n        {\n            item.PictureUri = _uriComposer.ComposePicUri(item.PictureUri);\n        }\n\n        if (request.PageSize > 0)\n        {\n            response.PageCount = int.Parse(Math.Ceiling((decimal)totalItems / request.PageSize).ToString());\n        }\n        else\n        {\n            response.PageCount = totalItems > 0 ? 1 : 0;\n        }\n\n        return Results.Ok(response);\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/CreateCatalogItemEndpoint.CreateCatalogItemRequest.cs",
    "content": "﻿namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class CreateCatalogItemRequest : BaseRequest\n{\n    public int CatalogBrandId { get; set; }\n    public int CatalogTypeId { get; set; }\n    public string Description { get; set; }\n    public string Name { get; set; }\n    public string PictureUri { get; set; }\n    public string PictureBase64 { get; set; }\n    public string PictureName { get; set; }\n    public decimal Price { get; set; }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/CreateCatalogItemEndpoint.CreateCatalogItemResponse.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class CreateCatalogItemResponse : BaseResponse\n{\n    public CreateCatalogItemResponse(Guid correlationId) : base(correlationId)\n    {\n    }\n\n    public CreateCatalogItemResponse()\n    {\n    }\n\n    public CatalogItemDto CatalogItem { get; set; }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/CreateCatalogItemEndpoint.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authentication.JwtBearer;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Routing;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Exceptions;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing MinimalApi.Endpoint;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\n/// <summary>\n/// Creates a new Catalog Item\n/// </summary>\npublic class CreateCatalogItemEndpoint : IEndpoint<IResult, CreateCatalogItemRequest, IRepository<CatalogItem>>\n{\n    private readonly IUriComposer _uriComposer;\n\n    public CreateCatalogItemEndpoint(IUriComposer uriComposer)\n    {\n        _uriComposer = uriComposer;\n    }\n\n    public void AddRoute(IEndpointRouteBuilder app)\n    {\n        app.MapPost(\"api/catalog-items\",\n            [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async\n            (CreateCatalogItemRequest request, IRepository<CatalogItem> itemRepository) =>\n            {\n                return await HandleAsync(request, itemRepository);\n            })\n            .Produces<CreateCatalogItemResponse>()\n            .WithTags(\"CatalogItemEndpoints\");\n    }\n\n    public async Task<IResult> HandleAsync(CreateCatalogItemRequest request, IRepository<CatalogItem> itemRepository)\n    {\n        var response = new CreateCatalogItemResponse(request.CorrelationId());\n\n        var catalogItemNameSpecification = new CatalogItemNameSpecification(request.Name);\n        var existingCataloogItem = await itemRepository.CountAsync(catalogItemNameSpecification);\n        if (existingCataloogItem > 0)\n        {\n            throw new DuplicateException($\"A catalogItem with name {request.Name} already exists\");\n        }\n\n        var newItem = new CatalogItem(request.CatalogTypeId, request.CatalogBrandId, request.Description, request.Name, request.Price, request.PictureUri);\n        newItem = await itemRepository.AddAsync(newItem);\n\n        if (newItem.Id != 0)\n        {\n            //We disabled the upload functionality and added a default/placeholder image to this sample due to a potential security risk \n            //  pointed out by the community. More info in this issue: https://github.com/dotnet-architecture/eShopOnWeb/issues/537 \n            //  In production, we recommend uploading to a blob storage and deliver the image via CDN after a verification process.\n\n            newItem.UpdatePictureUri(\"eCatalog-item-default.png\");\n            await itemRepository.UpdateAsync(newItem);\n        }\n\n        var dto = new CatalogItemDto\n        {\n            Id = newItem.Id,\n            CatalogBrandId = newItem.CatalogBrandId,\n            CatalogTypeId = newItem.CatalogTypeId,\n            Description = newItem.Description,\n            Name = newItem.Name,\n            PictureUri = _uriComposer.ComposePicUri(newItem.PictureUri),\n            Price = newItem.Price\n        };\n        response.CatalogItem = dto;\n        return Results.Created($\"api/catalog-items/{dto.Id}\", response);\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/DeleteCatalogItemEndpoint.DeleteCatalogItemRequest.cs",
    "content": "﻿namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class DeleteCatalogItemRequest : BaseRequest\n{\n    public int CatalogItemId { get; init; }\n\n    public DeleteCatalogItemRequest(int catalogItemId)\n    {\n        CatalogItemId = catalogItemId;\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/DeleteCatalogItemEndpoint.DeleteCatalogItemResponse.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class DeleteCatalogItemResponse : BaseResponse\n{\n    public DeleteCatalogItemResponse(Guid correlationId) : base(correlationId)\n    {\n    }\n\n    public DeleteCatalogItemResponse()\n    {\n    }\n\n    public string Status { get; set; } = \"Deleted\";\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/DeleteCatalogItemEndpoint.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authentication.JwtBearer;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Routing;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing MinimalApi.Endpoint;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\n/// <summary>\n/// Deletes a Catalog Item\n/// </summary>\npublic class DeleteCatalogItemEndpoint : IEndpoint<IResult, DeleteCatalogItemRequest, IRepository<CatalogItem>>\n{\n    public void AddRoute(IEndpointRouteBuilder app)\n    {\n        app.MapDelete(\"api/catalog-items/{catalogItemId}\",\n            [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async\n            (int catalogItemId, IRepository<CatalogItem> itemRepository) =>\n            {\n                return await HandleAsync(new DeleteCatalogItemRequest(catalogItemId), itemRepository);\n            })\n            .Produces<DeleteCatalogItemResponse>()\n            .WithTags(\"CatalogItemEndpoints\");\n    }\n\n    public async Task<IResult> HandleAsync(DeleteCatalogItemRequest request, IRepository<CatalogItem> itemRepository)\n    {\n        var response = new DeleteCatalogItemResponse(request.CorrelationId());\n\n        var itemToDelete = await itemRepository.GetByIdAsync(request.CatalogItemId);\n        if (itemToDelete is null)\n            return Results.NotFound();\n\n        await itemRepository.DeleteAsync(itemToDelete);\n\n        return Results.Ok(response);\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.UpdateCatalogItemRequest.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class UpdateCatalogItemRequest : BaseRequest\n{\n    [Range(1, 10000)]\n    public int Id { get; set; }\n    [Range(1, 10000)]\n    public int CatalogBrandId { get; set; }\n    [Range(1, 10000)]\n    public int CatalogTypeId { get; set; }\n    [Required]\n    public string Description { get; set; }\n    [Required]\n    public string Name { get; set; }\n    public string PictureBase64 { get; set; }\n    public string PictureUri { get; set; }\n    public string PictureName { get; set; }\n    [Range(0.01, 10000)]\n    public decimal Price { get; set; }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.UpdateCatalogItemResponse.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\npublic class UpdateCatalogItemResponse : BaseResponse\n{\n    public UpdateCatalogItemResponse(Guid correlationId) : base(correlationId)\n    {\n    }\n\n    public UpdateCatalogItemResponse()\n    {\n    }\n\n    public CatalogItemDto CatalogItem { get; set; }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authentication.JwtBearer;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Routing;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing MinimalApi.Endpoint;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\n\n/// <summary>\n/// Updates a Catalog Item\n/// </summary>\npublic class UpdateCatalogItemEndpoint : IEndpoint<IResult, UpdateCatalogItemRequest, IRepository<CatalogItem>>\n{ \n    private readonly IUriComposer _uriComposer;\n\n    public UpdateCatalogItemEndpoint(IUriComposer uriComposer)\n    {\n        _uriComposer = uriComposer;\n    }\n\n    public void AddRoute(IEndpointRouteBuilder app)\n    {\n        app.MapPut(\"api/catalog-items\",\n            [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async\n            (UpdateCatalogItemRequest request, IRepository<CatalogItem> itemRepository) =>\n            {\n                return await HandleAsync(request, itemRepository);\n            })\n            .Produces<UpdateCatalogItemResponse>()\n            .WithTags(\"CatalogItemEndpoints\");\n    }\n\n    public async Task<IResult> HandleAsync(UpdateCatalogItemRequest request, IRepository<CatalogItem> itemRepository)\n    {\n        var response = new UpdateCatalogItemResponse(request.CorrelationId());\n\n        var existingItem = await itemRepository.GetByIdAsync(request.Id);\n        if (existingItem == null)\n        {\n            return Results.NotFound();\n        }\n\n        CatalogItem.CatalogItemDetails details = new(request.Name, request.Description, request.Price);\n        existingItem.UpdateDetails(details);\n        existingItem.UpdateBrand(request.CatalogBrandId);\n        existingItem.UpdateType(request.CatalogTypeId);\n\n        await itemRepository.UpdateAsync(existingItem);\n\n        var dto = new CatalogItemDto\n        {\n            Id = existingItem.Id,\n            CatalogBrandId = existingItem.CatalogBrandId,\n            CatalogTypeId = existingItem.CatalogTypeId,\n            Description = existingItem.Description,\n            Name = existingItem.Name,\n            PictureUri = _uriComposer.ComposePicUri(existingItem.PictureUri),\n            Price = existingItem.Price\n        };\n        response.CatalogItem = dto;\n        return Results.Ok(response);\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogTypeEndpoints/CatalogTypeDto.cs",
    "content": "﻿namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints;\n\npublic class CatalogTypeDto\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogTypeEndpoints/CatalogTypeListEndpoint.ListCatalogTypesResponse.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints;\n\npublic class ListCatalogTypesResponse : BaseResponse\n{\n    public ListCatalogTypesResponse(Guid correlationId) : base(correlationId)\n    {\n    }\n\n    public ListCatalogTypesResponse()\n    {\n    }\n\n    public List<CatalogTypeDto> CatalogTypes { get; set; } = new List<CatalogTypeDto>();\n}\n"
  },
  {
    "path": "src/PublicApi/CatalogTypeEndpoints/CatalogTypeListEndpoint.cs",
    "content": "﻿using System.Linq;\nusing System.Threading.Tasks;\nusing AutoMapper;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Routing;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing MinimalApi.Endpoint;\n\nnamespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints;\n\n/// <summary>\n/// List Catalog Types\n/// </summary>\npublic class CatalogTypeListEndpoint : IEndpoint<IResult, IRepository<CatalogType>>\n{\n    private readonly IMapper _mapper;\n\n    public CatalogTypeListEndpoint(IMapper mapper)\n    {\n        _mapper = mapper;\n    }\n\n    public void AddRoute(IEndpointRouteBuilder app)\n    {\n        app.MapGet(\"api/catalog-types\",\n            async (IRepository<CatalogType> catalogTypeRepository) =>\n            {\n                return await HandleAsync(catalogTypeRepository);\n            })\n            .Produces<ListCatalogTypesResponse>()\n            .WithTags(\"CatalogTypeEndpoints\");\n    }\n\n    public async Task<IResult> HandleAsync(IRepository<CatalogType> catalogTypeRepository)\n    {\n        var response = new ListCatalogTypesResponse();\n\n        var items = await catalogTypeRepository.ListAsync();\n\n        response.CatalogTypes.AddRange(items.Select(_mapper.Map<CatalogTypeDto>));\n\n        return Results.Ok(response);\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/CustomSchemaFilters.cs",
    "content": "﻿using Microsoft.OpenApi.Models;\nusing Swashbuckle.AspNetCore.SwaggerGen;\n\nnamespace Microsoft.eShopWeb.PublicApi;\n\npublic class CustomSchemaFilters : ISchemaFilter\n{\n    public void Apply(OpenApiSchema schema, SchemaFilterContext context)\n    {\n        var excludeProperties = new[] { \"CorrelationId\" };\n\n        foreach (var prop in excludeProperties)\n            if (schema.Properties.ContainsKey(prop))\n                schema.Properties.Remove(prop);\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/Dockerfile",
    "content": "#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.\n\nFROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base\nWORKDIR /app\nEXPOSE 80\nEXPOSE 443\n\nFROM mcr.microsoft.com/dotnet/sdk:8.0 AS build\nWORKDIR /app\nCOPY . .\n#COPY [\"src/PublicApi/PublicApi.csproj\", \"./PublicApi/\"]\n#RUN dotnet restore \"./PublicApi/PublicApi.csproj\"\n#COPY . .\nWORKDIR \"/app/src/PublicApi\"\nRUN dotnet restore\n\nRUN dotnet build \"./PublicApi.csproj\" -c Release -o /app/build\n\nFROM build AS publish\nRUN dotnet publish \"./PublicApi.csproj\" -c Release -o /app/publish\n\nFROM base AS final\nWORKDIR /app\nCOPY --from=publish /app/publish .\nENTRYPOINT [\"dotnet\", \"PublicApi.dll\"]"
  },
  {
    "path": "src/PublicApi/ImageValidators.cs",
    "content": "﻿using System;\nusing System.IO;\n\nnamespace Microsoft.eShopWeb.PublicApi;\n\npublic static class ImageValidators\n{\n    private const int ImageMaximumBytes = 512000;\n\n    public static bool IsValidImage(this byte[] postedFile, string fileName)\n    {\n        return postedFile != null && postedFile.Length > 0 && postedFile.Length <= ImageMaximumBytes && IsExtensionValid(fileName);\n    }\n\n    private static bool IsExtensionValid(string fileName)\n    {\n        var extension = Path.GetExtension(fileName);\n\n        return string.Equals(extension, \".jpg\", StringComparison.OrdinalIgnoreCase) ||\n               string.Equals(extension, \".png\", StringComparison.OrdinalIgnoreCase) ||\n               string.Equals(extension, \".gif\", StringComparison.OrdinalIgnoreCase) ||\n               string.Equals(extension, \".jpeg\", StringComparison.OrdinalIgnoreCase);\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/MappingProfile.cs",
    "content": "﻿using AutoMapper;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints;\nusing Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\nusing Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints;\n\nnamespace Microsoft.eShopWeb.PublicApi;\n\npublic class MappingProfile : Profile\n{\n    public MappingProfile()\n    {\n        CreateMap<CatalogItem, CatalogItemDto>();\n        CreateMap<CatalogType, CatalogTypeDto>()\n            .ForMember(dto => dto.Name, options => options.MapFrom(src => src.Type));\n        CreateMap<CatalogBrand, CatalogBrandDto>()\n            .ForMember(dto => dto.Name, options => options.MapFrom(src => src.Brand));\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/Middleware/ExceptionMiddleware.cs",
    "content": "﻿using System;\nusing System.Net;\nusing System.Threading.Tasks;\nusing BlazorShared.Models;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.eShopWeb.ApplicationCore.Exceptions;\n\nnamespace Microsoft.eShopWeb.PublicApi.Middleware;\n\npublic class ExceptionMiddleware\n{\n    private readonly RequestDelegate _next;\n\n    public ExceptionMiddleware(RequestDelegate next)\n    {\n        _next = next;\n    }\n\n    public async Task InvokeAsync(HttpContext httpContext)\n    {\n        try\n        {\n            await _next(httpContext);\n        }\n        catch (Exception ex)\n        {\n            await HandleExceptionAsync(httpContext, ex);        \n        }\n    }\n\n    private async Task HandleExceptionAsync(HttpContext context, Exception exception)\n    {\n        context.Response.ContentType = \"application/json\";\n\n        if (exception is DuplicateException duplicationException)\n        {\n            context.Response.StatusCode = (int)HttpStatusCode.Conflict;\n            await context.Response.WriteAsync(new ErrorDetails()\n            {\n                StatusCode = context.Response.StatusCode,\n                Message = duplicationException.Message\n            }.ToString());\n        }\n        else\n        {\n            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;\n            await context.Response.WriteAsync(new ErrorDetails()\n            {\n                StatusCode = context.Response.StatusCode,\n                Message = exception.Message\n            }.ToString());\n        }\n    }\n}\n"
  },
  {
    "path": "src/PublicApi/Program.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing BlazorShared;\nusing Microsoft.AspNetCore.Authentication.JwtBearer;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.eShopWeb;\nusing Microsoft.eShopWeb.ApplicationCore.Constants;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Services;\nusing Microsoft.eShopWeb.Infrastructure.Data;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.eShopWeb.Infrastructure.Logging;\nusing Microsoft.eShopWeb.PublicApi;\nusing Microsoft.eShopWeb.PublicApi.Middleware;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.IdentityModel.Tokens;\nusing Microsoft.OpenApi.Models;\nusing MinimalApi.Endpoint.Configurations.Extensions;\nusing MinimalApi.Endpoint.Extensions;\n\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddEndpoints();\n\n// Use to force loading of appsettings.json of test project\nbuilder.Configuration.AddConfigurationFile(\"appsettings.test.json\");\nbuilder.Logging.AddConsole();\n\nMicrosoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services);\n\nbuilder.Services.AddIdentity<ApplicationUser, IdentityRole>()\n        .AddEntityFrameworkStores<AppIdentityDbContext>()\n        .AddDefaultTokenProviders();\n\nbuilder.Services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));\nbuilder.Services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>));\nbuilder.Services.Configure<CatalogSettings>(builder.Configuration);\nvar catalogSettings = builder.Configuration.Get<CatalogSettings>() ?? new CatalogSettings();\nbuilder.Services.AddSingleton<IUriComposer>(new UriComposer(catalogSettings));\nbuilder.Services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));\nbuilder.Services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>();\n\nvar configSection = builder.Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME);\nbuilder.Services.Configure<BaseUrlConfiguration>(configSection);\nvar baseUrlConfig = configSection.Get<BaseUrlConfiguration>();\n\nbuilder.Services.AddMemoryCache();\n\nvar key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY);\nbuilder.Services.AddAuthentication(config =>\n{\n    config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;\n})\n.AddJwtBearer(config =>\n{\n    config.RequireHttpsMetadata = false;\n    config.SaveToken = true;\n    config.TokenValidationParameters = new TokenValidationParameters\n    {\n        ValidateIssuerSigningKey = true,\n        IssuerSigningKey = new SymmetricSecurityKey(key),\n        ValidateIssuer = false,\n        ValidateAudience = false\n    };\n});\n\nconst string CORS_POLICY = \"CorsPolicy\";\nbuilder.Services.AddCors(options =>\n{\n    options.AddPolicy(name: CORS_POLICY,\n        corsPolicyBuilder =>\n        {\n            corsPolicyBuilder.WithOrigins(baseUrlConfig!.WebBase.Replace(\"host.docker.internal\", \"localhost\").TrimEnd('/'));\n            corsPolicyBuilder.AllowAnyMethod();\n            corsPolicyBuilder.AllowAnyHeader();\n        });\n});\n\nbuilder.Services.AddControllers();\nbuilder.Services.AddAutoMapper(typeof(MappingProfile).Assembly);\nbuilder.Configuration.AddEnvironmentVariables();\n\nbuilder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen(c =>\n{\n    c.SwaggerDoc(\"v1\", new OpenApiInfo { Title = \"My API\", Version = \"v1\" });\n    c.EnableAnnotations();\n    c.SchemaFilter<CustomSchemaFilters>();\n    c.AddSecurityDefinition(\"Bearer\", new OpenApiSecurityScheme\n    {\n        Description = @\"JWT Authorization header using the Bearer scheme. \\r\\n\\r\\n \n                      Enter 'Bearer' [space] and then your token in the text input below.\n                      \\r\\n\\r\\nExample: 'Bearer 12345abcdef'\",\n        Name = \"Authorization\",\n        In = ParameterLocation.Header,\n        Type = SecuritySchemeType.ApiKey,\n        Scheme = \"Bearer\"\n    });\n\n    c.AddSecurityRequirement(new OpenApiSecurityRequirement()\n            {\n                    {\n                        new OpenApiSecurityScheme\n                        {\n                            Reference = new OpenApiReference\n                            {\n                                Type = ReferenceType.SecurityScheme,\n                                Id = \"Bearer\"\n                            },\n                            Scheme = \"oauth2\",\n                            Name = \"Bearer\",\n                            In = ParameterLocation.Header,\n\n                        },\n                        new List<string>()\n                    }\n            });\n});\n\nvar app = builder.Build();\n\napp.Logger.LogInformation(\"PublicApi App created...\");\n\napp.Logger.LogInformation(\"Seeding Database...\");\n\nusing (var scope = app.Services.CreateScope())\n{\n    var scopedProvider = scope.ServiceProvider;\n    try\n    {\n        var catalogContext = scopedProvider.GetRequiredService<CatalogContext>();\n        await CatalogContextSeed.SeedAsync(catalogContext, app.Logger);\n\n        var userManager = scopedProvider.GetRequiredService<UserManager<ApplicationUser>>();\n        var roleManager = scopedProvider.GetRequiredService<RoleManager<IdentityRole>>();\n        var identityContext = scopedProvider.GetRequiredService<AppIdentityDbContext>();\n        await AppIdentityDbContextSeed.SeedAsync(identityContext, userManager, roleManager);\n    }\n    catch (Exception ex)\n    {\n        app.Logger.LogError(ex, \"An error occurred seeding the DB.\");\n    }\n}\n\nif (app.Environment.IsDevelopment())\n{\n    app.UseDeveloperExceptionPage();\n}\n\napp.UseMiddleware<ExceptionMiddleware>();\n\napp.UseHttpsRedirection();\n\napp.UseRouting();\n\napp.UseCors(CORS_POLICY);\n\napp.UseAuthorization();\n\n// Enable middleware to serve generated Swagger as a JSON endpoint.\napp.UseSwagger();\n\n// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), \n// specifying the Swagger JSON endpoint.\napp.UseSwaggerUI(c =>\n{\n    c.SwaggerEndpoint(\"/swagger/v1/swagger.json\", \"My API V1\");\n});\n\napp.MapControllers();\napp.MapEndpoints();\n\napp.Logger.LogInformation(\"LAUNCHING PublicApi\");\napp.Run();\n\npublic partial class Program { }\n"
  },
  {
    "path": "src/PublicApi/Properties/launchSettings.json",
    "content": "{\n  \"profiles\": {\n    \"IIS Express\": {\n      \"commandName\": \"IISExpress\",\n      \"launchBrowser\": true,\n      \"launchUrl\": \"swagger\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      }\n    },\n    \"PublicApi\": {\n      \"commandName\": \"Project\",\n      \"launchBrowser\": true,\n      \"launchUrl\": \"swagger\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      },\n      \"applicationUrl\": \"https://localhost:5099;http://localhost:5098\"\n    },\n    \"WSL\": {\n      \"commandName\": \"WSL2\",\n      \"launchBrowser\": true,\n      \"launchUrl\": \"https://localhost:5099/swagger\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\",\n        \"ASPNETCORE_URLS\": \"https://localhost:5099;http://localhost:5098\"\n      },\n      \"distributionName\": \"\"\n    },\n    \"Docker\": {\n      \"commandName\": \"Docker\",\n      \"launchBrowser\": true,\n      \"launchUrl\": \"{Scheme}://{ServiceHost}:{ServicePort}/swagger\",\n      \"publishAllPorts\": true,\n      \"useSSL\": true\n    }\n  },\n  \"$schema\": \"http://json.schemastore.org/launchsettings.json\",\n  \"iisSettings\": {\n    \"windowsAuthentication\": false,\n    \"anonymousAuthentication\": true,\n    \"iisExpress\": {\n      \"applicationUrl\": \"http://localhost:52023\",\n      \"sslPort\": 44339\n    }\n  }\n}"
  },
  {
    "path": "src/PublicApi/PublicApi.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup> \n    <RootNamespace>Microsoft.eShopWeb.PublicApi</RootNamespace>\n    <UserSecretsId>5b662463-1efd-4bae-bde4-befe0be3e8ff</UserSecretsId>\n    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>\n    <DockerfileContext>..\\..</DockerfileContext>\n    <Nullable>enable</Nullable>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Ardalis.ApiEndpoints\" />\n    <PackageReference Include=\"AutoMapper.Extensions.Microsoft.DependencyInjection\" />\n    <PackageReference Include=\"MinimalApi.Endpoint\" />\n    <PackageReference Include=\"Swashbuckle.AspNetCore\" />\n    <PackageReference Include=\"Swashbuckle.AspNetCore.SwaggerUI\" />\n    <PackageReference Include=\"Swashbuckle.AspNetCore.Annotations\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Authentication.JwtBearer\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.EntityFrameworkCore\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.UI\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" >\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.VisualStudio.Azure.Containers.Tools.Targets\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" />\n\n    <PackageReference Include=\"System.IdentityModel.Tokens.Jwt\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\ApplicationCore\\ApplicationCore.csproj\" />\n    <ProjectReference Include=\"..\\Infrastructure\\Infrastructure.csproj\" />\n  </ItemGroup>\n\n\n</Project>\n"
  },
  {
    "path": "src/PublicApi/README.md",
    "content": "﻿# API Endpoints\n\nThis folder demonstrates how to configure API endpoints as individual classes. You can compare it to the traditional controller-based approach found in /Web/Controllers/Api.\n\n"
  },
  {
    "path": "src/PublicApi/appsettings.Development.json",
    "content": "{\n  \"baseUrls\": {\n    \"apiBase\": \"https://localhost:5099/api/\",\n    \"webBase\": \"https://localhost:5001/\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft\": \"Warning\",\n      \"Microsoft.Hosting.Lifetime\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "src/PublicApi/appsettings.Docker.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"CatalogConnection\": \"Server=sqlserver,1433;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;User Id=sa;Password=@someThingComplicated1234;Trusted_Connection=false;TrustServerCertificate=true;\",\n    \"IdentityConnection\": \"Server=sqlserver,1433;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;User Id=sa;Password=@someThingComplicated1234;Trusted_Connection=false;TrustServerCertificate=true;\"\n  },\n  \"baseUrls\": {\n    \"apiBase\": \"http://localhost:5200/api/\",\n    \"webBase\": \"http://host.docker.internal:5106/\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft\": \"Warning\",\n      \"Microsoft.Hosting.Lifetime\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "src/PublicApi/appsettings.json",
    "content": "{\n  \"baseUrls\": {\n    \"apiBase\": \"https://localhost:5099/api/\",\n    \"webBase\": \"https://localhost:5001/\"\n  },\n  \"ConnectionStrings\": {\n    \"CatalogConnection\": \"Server=(localdb)\\\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;\",\n    \"IdentityConnection\": \"Server=(localdb)\\\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;\"\n  },\n  \"CatalogBaseUrl\": \"\",\n  \"Logging\": {\n    \"IncludeScopes\": false,\n    \"LogLevel\": {\n      \"Default\": \"Warning\",\n      \"Microsoft\": \"Warning\",\n      \"System\": \"Warning\"\n    },\n    \"AllowedHosts\": \"*\"\n  }\n}\n"
  },
  {
    "path": "src/Web/.config/dotnet-tools.json",
    "content": "{\n  \"version\": 1,\n  \"isRoot\": true,\n  \"tools\": {\n    \"dotnet-ef\": {\n      \"version\": \"8.0.0\",\n      \"commands\": [\n        \"dotnet-ef\"\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/Web/Areas/Identity/IdentityHostingStartup.cs",
    "content": "﻿using Microsoft.AspNetCore.Hosting;\n\n[assembly: HostingStartup(typeof(Microsoft.eShopWeb.Web.Areas.Identity.IdentityHostingStartup))]\nnamespace Microsoft.eShopWeb.Web.Areas.Identity;\n\npublic class IdentityHostingStartup : IHostingStartup\n{\n    public void Configure(IWebHostBuilder builder)\n    {\n        builder.ConfigureServices((context, services) =>\n        {\n        });\n    }\n}\n"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml",
    "content": "﻿@page\n@model ConfirmEmailModel\n@{\n    ViewData[\"Title\"] = \"Confirm email\";\n}\n\n<h2>@ViewData[\"Title\"]</h2>\n<div>\n    <p>\n        Thank you for confirming your email.\n    </p>\n</div>\n"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\n\nnamespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account;\n\n[AllowAnonymous]\npublic class ConfirmEmailModel : PageModel\n{\n    private readonly UserManager<ApplicationUser> _userManager;\n\n    public ConfirmEmailModel(UserManager<ApplicationUser> userManager)\n    {\n        _userManager = userManager;\n    }\n\n    public async Task<IActionResult> OnGetAsync(string userId, string code)\n    {\n        if (userId == null || code == null)\n        {\n            return RedirectToPage(\"/Index\");\n        }\n\n        var user = await _userManager.FindByIdAsync(userId);\n        if (user == null)\n        {\n            return NotFound($\"Unable to load user with ID '{userId}'.\");\n        }\n\n        var result = await _userManager.ConfirmEmailAsync(user, code);\n        if (!result.Succeeded)\n        {\n            throw new InvalidOperationException($\"Error confirming email for user with ID '{userId}':\");\n        }\n\n        return Page();\n    }\n}\n"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/Account/Login.cshtml",
    "content": "﻿@page\n@model LoginModel\n\n@{\n    ViewData[\"Title\"] = \"Log in\";\n}\n\n\n<div class=\"container account-login-container\">\n    <h2>@ViewData[\"Title\"]</h2>\n    <div class=\"row\">\n        <div class=\"col-md-12\">\n            <section>\n                <form method=\"post\">\n                    <hr />\n                    <div asp-validation-summary=\"All\" class=\"text-danger\"></div>\n                    <div class=\"form-group\">\n                        <label asp-for=\"Input.Email\"></label>\n                        <input asp-for=\"Input.Email\" class=\"form-control\" />\n                        <span asp-validation-for=\"Input.Email\" class=\"text-danger\"></span>\n                    </div>\n                    <div class=\"form-group\">\n                        <label asp-for=\"Input.Password\"></label>\n                        <input asp-for=\"Input.Password\" class=\"form-control\" />\n                        <span asp-validation-for=\"Input.Password\" class=\"text-danger\"></span>\n                    </div>\n                    <div class=\"form-group\">\n                        <div class=\"checkbox\">\n                            <label asp-for=\"Input.RememberMe\">\n                                <input asp-for=\"Input.RememberMe\" />\n                                @Html.DisplayNameFor(m => m.Input.RememberMe)\n                            </label>\n                        </div>\n                    </div>\n                    <div class=\"form-group\">\n                        <button type=\"submit\" class=\"btn btn-default\">Log in</button>\n                    </div>\n                    <div class=\"form-group\">\n                        <p>\n                            <a asp-page=\"./ForgotPassword\">Forgot your password?</a>\n                        </p>\n                        <p>\n                            <a asp-page=\"./Register\" asp-route-returnUrl=\"@Model.ReturnUrl\">Register as a new user</a>\n                        </p>\n                    </div>\n                    <p>\n                        Note that for demo purposes you don't need to register and can login with these credentials:\n                    </p>\n                    <p>\n                        User:     <b>demouser@microsoft.com</b> OR <b>admin@microsoft.com</b>\n                    </p>\n                    <p>\n                        Password: <b>Pass@word1</b>\n                    </p>\n                </form>\n            </section>\n        </div>\n    </div>\n</div>\n\n@section Scripts {\n    <partial name=\"_ValidationScriptsPartial\" />\n}\n"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing Ardalis.GuardClauses;\nusing Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\n\nnamespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account;\n\n[AllowAnonymous]\npublic class LoginModel : PageModel\n{\n    private readonly SignInManager<ApplicationUser> _signInManager;\n    private readonly ILogger<LoginModel> _logger;\n    private readonly IBasketService _basketService;\n\n    public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger, IBasketService basketService)\n    {\n        _signInManager = signInManager;\n        _logger = logger;\n        _basketService = basketService;\n    }\n\n    [BindProperty]\n    public required InputModel Input { get; set; }\n\n    public IList<AuthenticationScheme>? ExternalLogins { get; set; }\n\n    public string? ReturnUrl { get; set; }\n\n    [TempData]\n    public string? ErrorMessage { get; set; }\n\n    public class InputModel\n    {\n        [Required]\n        [EmailAddress]\n        public string? Email { get; set; }\n\n        [Required]\n        [DataType(DataType.Password)]\n        public string? Password { get; set; }\n\n        [Display(Name = \"Remember me?\")]\n        public bool RememberMe { get; set; }\n    }\n\n    public async Task OnGetAsync(string? returnUrl = null)\n    {\n        if (!string.IsNullOrEmpty(ErrorMessage))\n        {\n            ModelState.AddModelError(string.Empty, ErrorMessage);\n        }\n\n        returnUrl = returnUrl ?? Url.Content(\"~/\");\n\n        // Clear the existing external cookie to ensure a clean login process\n        await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);\n\n        ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();\n\n        ReturnUrl = returnUrl;\n    }\n\n    public async Task<IActionResult> OnPostAsync(string? returnUrl = null)\n    {\n        returnUrl = returnUrl ?? Url.Content(\"~/\");\n\n        if (ModelState.IsValid)\n        {\n            // This doesn't count login failures towards account lockout\n            // To enable password failures to trigger account lockout, set lockoutOnFailure: true\n            //var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);\n            var result = await _signInManager.PasswordSignInAsync(Input!.Email!, Input!.Password!, \n                false, true);\n\n            if (result.Succeeded)\n            {\n                _logger.LogInformation(\"User logged in.\");\n                await TransferAnonymousBasketToUserAsync(Input?.Email);\n                return LocalRedirect(returnUrl);\n            }\n            if (result.RequiresTwoFactor)\n            {\n                return RedirectToPage(\"./LoginWith2fa\", new { ReturnUrl = returnUrl, RememberMe = Input?.RememberMe });\n            }\n            if (result.IsLockedOut)\n            {\n                _logger.LogWarning(\"User account locked out.\");\n                return RedirectToPage(\"./Lockout\");\n            }\n            else\n            {\n                ModelState.AddModelError(string.Empty, \"Invalid login attempt.\");\n                return Page();\n            }\n        }\n\n        // If we got this far, something failed, redisplay form\n        return Page();\n    }\n\n    private async Task TransferAnonymousBasketToUserAsync(string? userName)\n    {\n        if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))\n        {\n            var anonymousId = Request.Cookies[Constants.BASKET_COOKIENAME];\n            if (Guid.TryParse(anonymousId, out var _))\n            {\n                Guard.Against.NullOrEmpty(userName, nameof(userName));\n                await _basketService.TransferBasketAsync(anonymousId, userName);\n            }\n            Response.Cookies.Delete(Constants.BASKET_COOKIENAME);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/Account/Logout.cshtml",
    "content": "﻿@page\n@model LogoutModel\n@{\n    ViewData[\"Title\"] = \"Log out\";\n}\n\n<header>\n    <h1>@ViewData[\"Title\"]</h1>\n    <p>You have successfully logged out of the application.</p>\n</header>"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs",
    "content": "﻿using System.Security.Claims;\nusing Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Authentication.Cookies;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.eShopWeb.Web.Configuration;\nusing Microsoft.Extensions.Caching.Memory;\n\nnamespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account;\n\n//TODO : replace IMemoryCache by distributed cache if you are in multi-host scenario\npublic class LogoutModel : PageModel\n{\n    private readonly SignInManager<ApplicationUser> _signInManager;\n    private readonly ILogger<LogoutModel> _logger;\n    private readonly IMemoryCache _cache;\n\n    public LogoutModel(SignInManager<ApplicationUser> signInManager, ILogger<LogoutModel> logger, IMemoryCache cache)\n    {\n        _signInManager = signInManager;\n        _logger = logger;\n        _cache = cache;\n    }\n\n    public void OnGet()\n    {\n    }\n\n    public async Task<IActionResult> OnPost(string? returnUrl = null)\n    {\n        await _signInManager.SignOutAsync();\n        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);\n        var userId = _signInManager.Context.User.Claims.First(c => c.Type == ClaimTypes.Name);\n        var identityKey = _signInManager.Context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName];\n        _cache.Set($\"{userId.Value}:{identityKey}\", identityKey, new MemoryCacheEntryOptions\n        {\n            AbsoluteExpiration = DateTime.Now.AddMinutes(ConfigureCookieSettings.ValidityMinutesPeriod)\n        });\n\n        _logger.LogInformation(\"User logged out.\");\n        if (returnUrl != null)\n        {\n            return LocalRedirect(returnUrl);\n        }\n        else\n        {\n            return RedirectToPage(\"/Index\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/Account/Register.cshtml",
    "content": "﻿@page\n@model RegisterModel\n@{\n    ViewData[\"Title\"] = \"Register\";\n}\n\n<div class=\"container\">\n\n    <h2>@ViewData[\"Title\"]</h2>\n\n    <div class=\"row\">\n        <div class=\"col-md-8\">\n            <form asp-route-returnUrl=\"@Model.ReturnUrl\" method=\"post\">\n                <h4>Create a new account.</h4>\n                <hr />\n                <div asp-validation-summary=\"All\" class=\"text-danger\"></div>\n                <div class=\"form-group\">\n                    <label asp-for=\"Input.Email\"></label>\n                    <input asp-for=\"Input.Email\" class=\"form-control\" />\n                    <span asp-validation-for=\"Input.Email\" class=\"text-danger\"></span>\n                </div>\n                <div class=\"form-group\">\n                    <label asp-for=\"Input.Password\"></label>\n                    <input asp-for=\"Input.Password\" class=\"form-control\" />\n                    <span asp-validation-for=\"Input.Password\" class=\"text-danger\"></span>\n                </div>\n                <div class=\"form-group\">\n                    <label asp-for=\"Input.ConfirmPassword\"></label>\n                    <input asp-for=\"Input.ConfirmPassword\" class=\"form-control\" />\n                    <span asp-validation-for=\"Input.ConfirmPassword\" class=\"text-danger\"></span>\n                </div>\n                <button type=\"submit\" class=\"btn btn-default\">Register</button>\n            </form>\n        </div>\n    </div>\n\n</div>\n\n@section Scripts {\n    <partial name=\"_ValidationScriptsPartial\" />\n}\n"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/Account/Register.cshtml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.Text.Encodings.Web;\nusing System.Threading.Tasks;\nusing Ardalis.GuardClauses;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Identity.UI.Services;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.Extensions.Logging;\n\nnamespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account;\n\n[AllowAnonymous]\npublic class RegisterModel : PageModel\n{\n    private readonly SignInManager<ApplicationUser> _signInManager;\n    private readonly UserManager<ApplicationUser> _userManager;\n    private readonly ILogger<RegisterModel> _logger;\n    private readonly IEmailSender _emailSender;\n\n    public RegisterModel(\n        UserManager<ApplicationUser> userManager,\n        SignInManager<ApplicationUser> signInManager,\n        ILogger<RegisterModel> logger,\n        IEmailSender emailSender)\n    {\n        _userManager = userManager;\n        _signInManager = signInManager;\n        _logger = logger;\n        _emailSender = emailSender;\n    }\n\n    [BindProperty]\n    public required InputModel Input { get; set; }\n\n    public string? ReturnUrl { get; set; }\n\n    public class InputModel\n    {\n        [Required]\n        [EmailAddress]\n        [Display(Name = \"Email\")]\n        public string? Email { get; set; }\n\n        [Required]\n        [StringLength(100, ErrorMessage = \"The {0} must be at least {2} and at max {1} characters long.\", MinimumLength = 6)]\n        [DataType(DataType.Password)]\n        [Display(Name = \"Password\")]\n        public string? Password { get; set; }\n\n        [DataType(DataType.Password)]\n        [Display(Name = \"Confirm password\")]\n        [Compare(\"Password\", ErrorMessage = \"The password and confirmation password do not match.\")]\n        public string? ConfirmPassword { get; set; }\n    }\n\n    public void OnGet(string? returnUrl = null)\n    {\n        ReturnUrl = returnUrl;\n    }\n\n    public async Task<IActionResult> OnPostAsync(string? returnUrl = null)\n    {\n        returnUrl = returnUrl ?? Url.Content(\"~/\");\n        if (ModelState.IsValid)\n        {\n            var user = new ApplicationUser { UserName = Input?.Email, Email = Input?.Email };\n            var result = await _userManager.CreateAsync(user, Input?.Password!);\n            if (result.Succeeded)\n            {\n                _logger.LogInformation(\"User created a new account with password.\");\n\n                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);\n                var callbackUrl = Url.Page(\n                    \"/Account/ConfirmEmail\",\n                    pageHandler: null,\n                    values: new { userId = user.Id, code = code },\n                    protocol: Request.Scheme);\n\n                Guard.Against.Null(callbackUrl, nameof(callbackUrl));\n                await _emailSender.SendEmailAsync(Input!.Email!, \"Confirm your email\",\n                    $\"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.\");\n\n                await _signInManager.SignInAsync(user, isPersistent: false);\n                return LocalRedirect(returnUrl);\n            }\n            foreach (var error in result.Errors)\n            {\n                ModelState.AddModelError(string.Empty, error.Description);\n            }\n        }\n\n        // If we got this far, something failed, redisplay form\n        return Page();\n    }\n}\n"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/Account/_ViewImports.cshtml",
    "content": "﻿@using Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml",
    "content": "﻿<environment include=\"Development,Docker\">\n    <script src=\"~/Identity/lib/jquery-validation/dist/jquery.validate.js\"></script>\n    <script src=\"~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js\"></script>\n</environment>\n<environment exclude=\"Development,Docker\">\n    <script src=\"https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js\"\n            asp-fallback-src=\"~/Identity/lib/jquery-validation/dist/jquery.validate.min.js\"\n            asp-fallback-test=\"window.jQuery && window.jQuery.validator\"\n            crossorigin=\"anonymous\"\n            integrity=\"sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp\">\n    </script>\n    <script src=\"https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.9/jquery.validate.unobtrusive.min.js\"\n            asp-fallback-src=\"~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js\"\n            asp-fallback-test=\"window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive\"\n            crossorigin=\"anonymous\"\n            integrity=\"sha384-ifv0TYDWxBHzvAk2Z0n8R434FL1Rlv/Av18DXE43N/1rvHyOG4izKst0f2iSLdds\">\n    </script>\n</environment>\n"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/_ViewImports.cshtml",
    "content": "﻿@using Microsoft.AspNetCore.Identity\n@using Microsoft.eShopWeb.Web.Areas.Identity\n@using Microsoft.eShopWeb.Infrastructure.Identity\n@namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "src/Web/Areas/Identity/Pages/_ViewStart.cshtml",
    "content": "﻿@{\n    Layout = \"/Views/Shared/_Layout.cshtml\";\n}\n"
  },
  {
    "path": "src/Web/Configuration/ConfigureCookieSettings.cs",
    "content": "﻿using System;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace Microsoft.eShopWeb.Web.Configuration;\n\npublic static class ConfigureCookieSettings\n{\n    public const int ValidityMinutesPeriod = 60;\n    public const string IdentifierCookieName = \"EshopIdentifier\";\n\n    public static IServiceCollection AddCookieSettings(this IServiceCollection services)\n    {\n        services.Configure<CookiePolicyOptions>(options =>\n        {\n                // This lambda determines whether user consent for non-essential cookies is needed for a given request.\n                //TODO need to check that.\n                //options.CheckConsentNeeded = context => true;\n                options.MinimumSameSitePolicy = SameSiteMode.Strict;\n        });\n        services.ConfigureApplicationCookie(options =>\n        {\n            options.EventsType = typeof(RevokeAuthenticationEvents);\n            options.Cookie.HttpOnly = true;\n            options.ExpireTimeSpan = TimeSpan.FromMinutes(ValidityMinutesPeriod);\n            options.LoginPath = \"/Account/Login\";\n            options.LogoutPath = \"/Account/Logout\";\n            options.Cookie = new CookieBuilder\n            {\n                Name = IdentifierCookieName,\n                IsEssential = true // required for auth to work without explicit user consent; adjust to suit your privacy policy\n                };\n        });\n\n        services.AddScoped<RevokeAuthenticationEvents>();\n\n        return services;\n    }\n}\n"
  },
  {
    "path": "src/Web/Configuration/ConfigureCoreServices.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Services;\nusing Microsoft.eShopWeb.Infrastructure.Data;\nusing Microsoft.eShopWeb.Infrastructure.Data.Queries;\nusing Microsoft.eShopWeb.Infrastructure.Logging;\nusing Microsoft.eShopWeb.Infrastructure.Services;\n\nnamespace Microsoft.eShopWeb.Web.Configuration;\n\npublic static class ConfigureCoreServices\n{\n    public static IServiceCollection AddCoreServices(this IServiceCollection services,\n        IConfiguration configuration)\n    {\n        services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>));\n        services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));\n\n        services.AddScoped<IBasketService, BasketService>();\n        services.AddScoped<IOrderService, OrderService>();\n        services.AddScoped<IBasketQueryService, BasketQueryService>();\n\n        var catalogSettings = configuration.Get<CatalogSettings>() ?? new CatalogSettings();\n        services.AddSingleton<IUriComposer>(new UriComposer(catalogSettings));\n\n        services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));\n        services.AddTransient<IEmailSender, EmailSender>();\n\n        return services;\n    }\n}\n"
  },
  {
    "path": "src/Web/Configuration/ConfigureWebServices.cs",
    "content": "﻿using MediatR;\nusing Microsoft.eShopWeb.Web.Interfaces;\nusing Microsoft.eShopWeb.Web.Services;\n\nnamespace Microsoft.eShopWeb.Web.Configuration;\n\npublic static class ConfigureWebServices\n{\n    public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration)\n    {\n        services.AddMediatR(cfg => \n            cfg.RegisterServicesFromAssembly(typeof(BasketViewModelService).Assembly));\n        services.AddScoped<IBasketViewModelService, BasketViewModelService>();\n        services.AddScoped<CatalogViewModelService>();\n        services.AddScoped<ICatalogItemViewModelService, CatalogItemViewModelService>();\n        services.Configure<CatalogSettings>(configuration);\n        services.AddScoped<ICatalogViewModelService, CachedCatalogViewModelService>();\n\n        return services;\n    }\n}\n"
  },
  {
    "path": "src/Web/Configuration/RevokeAuthenticationEvents.cs",
    "content": "﻿using System.Linq;\nusing System.Security.Claims;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Authentication.Cookies;\nusing Microsoft.Extensions.Caching.Memory;\nusing Microsoft.Extensions.Logging;\n\nnamespace Microsoft.eShopWeb.Web.Configuration;\n\n//TODO : replace IMemoryCache with a distributed cache if you are in multi-host scenario\npublic class RevokeAuthenticationEvents : CookieAuthenticationEvents\n{\n    private readonly IMemoryCache _cache;\n    private readonly ILogger _logger;\n\n    public RevokeAuthenticationEvents(IMemoryCache cache, ILogger<RevokeAuthenticationEvents> logger)\n    {\n        _cache = cache;\n        _logger = logger;\n    }\n\n    public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)\n    {\n        var userId = context.Principal?.Claims.First(c => c.Type == ClaimTypes.Name);\n        var identityKey = context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName];\n\n        if (_cache.TryGetValue($\"{userId?.Value}:{identityKey}\", out var revokeKeys))\n        {\n            _logger.LogDebug($\"Access has been revoked for: {userId?.Value}.\");\n            context.RejectPrincipal();\n            await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Web/Constants.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web;\n\npublic static class Constants\n{\n    public const string BASKET_COOKIENAME = \"eShop\";\n    public const int ITEMS_PER_PAGE = 10;\n    public const string DEFAULT_USERNAME = \"Guest\";\n    public const string BASKET_ID = \"BasketId\";\n}\n"
  },
  {
    "path": "src/Web/Controllers/Api/BaseApiController.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc;\n\nnamespace Microsoft.eShopWeb.Web.Controllers.Api;\n\n// No longer used - shown for reference only if using full controllers instead of Endpoints for APIs\n[Route(\"api/[controller]/[action]\")]\n[ApiController]\npublic class BaseApiController : ControllerBase\n{ }\n"
  },
  {
    "path": "src/Web/Controllers/ManageController.cs",
    "content": "﻿using System.Text;\nusing System.Text.Encodings.Web;\nusing Ardalis.GuardClauses;\nusing Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.eShopWeb.Web.Services;\nusing Microsoft.eShopWeb.Web.ViewModels.Manage;\n\nnamespace Microsoft.eShopWeb.Web.Controllers;\n\n[ApiExplorerSettings(IgnoreApi = true)]\n[Authorize] // Controllers that mainly require Authorization still use Controller/View; other pages use Pages\n[Route(\"[controller]/[action]\")]\npublic class ManageController : Controller\n{\n    private readonly UserManager<ApplicationUser> _userManager;\n    private readonly SignInManager<ApplicationUser> _signInManager;\n    private readonly IEmailSender _emailSender;\n    private readonly IAppLogger<ManageController> _logger;\n    private readonly UrlEncoder _urlEncoder;\n\n    private const string AuthenticatorUriFormat = \"otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6\";\n    private const string RecoveryCodesKey = nameof(RecoveryCodesKey);\n\n    public ManageController(\n      UserManager<ApplicationUser> userManager,\n      SignInManager<ApplicationUser> signInManager,\n      IEmailSender emailSender,\n      IAppLogger<ManageController> logger,\n      UrlEncoder urlEncoder)\n    {\n        _userManager = userManager;\n        _signInManager = signInManager;\n        _emailSender = emailSender;\n        _logger = logger;\n        _urlEncoder = urlEncoder;\n    }\n\n    [TempData]\n    public string? StatusMessage { get; set; }\n\n    [HttpGet]\n    public async Task<IActionResult> MyAccount()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var model = new IndexViewModel\n        {\n            Username = user.UserName,\n            Email = user.Email,\n            PhoneNumber = user.PhoneNumber,\n            IsEmailConfirmed = user.EmailConfirmed,\n            StatusMessage = StatusMessage\n        };\n\n        return View(model);\n    }\n\n    [HttpPost]\n    [ValidateAntiForgeryToken]\n    public async Task<IActionResult> MyAccount(IndexViewModel model)\n    {\n        if (!ModelState.IsValid)\n        {\n            return View(model);\n        }\n\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var email = user.Email;\n        if (model.Email != email)\n        {\n            var setEmailResult = await _userManager.SetEmailAsync(user, model.Email);\n            if (!setEmailResult.Succeeded)\n            {\n                throw new ApplicationException($\"Unexpected error occurred setting email for user with ID '{user.Id}'.\");\n            }\n        }\n\n        var phoneNumber = user.PhoneNumber;\n        if (model.PhoneNumber != phoneNumber)\n        {\n            var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, model.PhoneNumber);\n            if (!setPhoneResult.Succeeded)\n            {\n                throw new ApplicationException($\"Unexpected error occurred setting phone number for user with ID '{user.Id}'.\");\n            }\n        }\n\n        StatusMessage = \"Your profile has been updated\";\n        return RedirectToAction(nameof(MyAccount));\n    }\n\n    [HttpPost]\n    [ValidateAntiForgeryToken]\n    public async Task<IActionResult> SendVerificationEmail(IndexViewModel model)\n    {\n        if (!ModelState.IsValid)\n        {\n            return View(model);\n        }\n\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);\n        var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);\n        Guard.Against.Null(callbackUrl, nameof(callbackUrl));\n        var email = user.Email;\n        if (email == null)\n        {\n            throw new ApplicationException($\"No email associated with user {user.UserName}'.\");\n        }\n\n        await _emailSender.SendEmailConfirmationAsync(email, callbackUrl);\n\n        StatusMessage = \"Verification email sent. Please check your email.\";\n        return RedirectToAction(nameof(MyAccount));\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> ChangePassword()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var hasPassword = await _userManager.HasPasswordAsync(user);\n        if (!hasPassword)\n        {\n            return RedirectToAction(nameof(SetPassword));\n        }\n\n        var model = new ChangePasswordViewModel { StatusMessage = StatusMessage };\n        return View(model);\n    }\n\n    [HttpPost]\n    [ValidateAntiForgeryToken]\n    public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)\n    {\n        if (!ModelState.IsValid)\n        {\n            return View(model);\n        }\n\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var changePasswordResult = await _userManager\n            .ChangePasswordAsync(user, model.OldPassword!, model.NewPassword!);\n        if (!changePasswordResult.Succeeded)\n        {\n            AddErrors(changePasswordResult);\n            return View(model);\n        }\n\n        await _signInManager.SignInAsync(user, isPersistent: false);\n        _logger.LogInformation(\"User changed their password successfully.\");\n        StatusMessage = \"Your password has been changed.\";\n\n        return RedirectToAction(nameof(ChangePassword));\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> SetPassword()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var hasPassword = await _userManager.HasPasswordAsync(user);\n\n        if (hasPassword)\n        {\n            return RedirectToAction(nameof(ChangePassword));\n        }\n\n        var model = new SetPasswordViewModel { StatusMessage = StatusMessage };\n        return View(model);\n    }\n\n    [HttpPost]\n    [ValidateAntiForgeryToken]\n    public async Task<IActionResult> SetPassword(SetPasswordViewModel model)\n    {\n        if (!ModelState.IsValid)\n        {\n            return View(model);\n        }\n\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var addPasswordResult = await _userManager.AddPasswordAsync(user, model.NewPassword!);\n        if (!addPasswordResult.Succeeded)\n        {\n            AddErrors(addPasswordResult);\n            return View(model);\n        }\n\n        await _signInManager.SignInAsync(user, isPersistent: false);\n        StatusMessage = \"Your password has been set.\";\n\n        return RedirectToAction(nameof(SetPassword));\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> ExternalLogins()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var model = new ExternalLoginsViewModel { CurrentLogins = await _userManager.GetLoginsAsync(user) };\n        model.OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())\n            .Where(auth => model.CurrentLogins.All(ul => auth.Name != ul.LoginProvider))\n            .ToList();\n        model.ShowRemoveButton = await _userManager.HasPasswordAsync(user) || model.CurrentLogins.Count > 1;\n        model.StatusMessage = StatusMessage;\n\n        return View(model);\n    }\n\n    [HttpPost]\n    [ValidateAntiForgeryToken]\n    public async Task<IActionResult> LinkLogin(string provider)\n    {\n        // Clear the existing external cookie to ensure a clean login process\n        await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);\n\n        // Request a redirect to the external login provider to link a login for the current user\n        var redirectUrl = Url.Action(nameof(LinkLoginCallback));\n        var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));\n        return new ChallengeResult(provider, properties);\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> LinkLoginCallback()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var info = await _signInManager.GetExternalLoginInfoAsync(user.Id);\n        if (info == null)\n        {\n            throw new ApplicationException($\"Unexpected error occurred loading external login info for user with ID '{user.Id}'.\");\n        }\n\n        var result = await _userManager.AddLoginAsync(user, info);\n        if (!result.Succeeded)\n        {\n            throw new ApplicationException($\"Unexpected error occurred adding external login for user with ID '{user.Id}'.\");\n        }\n\n        // Clear the existing external cookie to ensure a clean login process\n        await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);\n\n        StatusMessage = \"The external login was added.\";\n        return RedirectToAction(nameof(ExternalLogins));\n    }\n\n    [HttpPost]\n    [ValidateAntiForgeryToken]\n    public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel model)\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n        if (!ModelState.IsValid)\n        {\n            return View(model);\n        }\n\n        var result = await _userManager.RemoveLoginAsync(user, model.LoginProvider, model.ProviderKey);\n        if (!result.Succeeded)\n        {\n            throw new ApplicationException($\"Unexpected error occurred removing external login for user with ID '{user.Id}'.\");\n        }\n\n        await _signInManager.SignInAsync(user, isPersistent: false);\n        StatusMessage = \"The external login was removed.\";\n        return RedirectToAction(nameof(ExternalLogins));\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> TwoFactorAuthentication()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var model = new TwoFactorAuthenticationViewModel\n        {\n            HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null,\n            Is2faEnabled = user.TwoFactorEnabled,\n            RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user),\n        };\n\n        return View(model);\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> Disable2faWarning()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        if (!user.TwoFactorEnabled)\n        {\n            throw new ApplicationException($\"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.\");\n        }\n\n        return View(nameof(Disable2fa));\n    }\n\n    [HttpPost]\n    [ValidateAntiForgeryToken]\n    public async Task<IActionResult> Disable2fa()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);\n        if (!disable2faResult.Succeeded)\n        {\n            throw new ApplicationException($\"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.\");\n        }\n\n        _logger.LogInformation(\"User with ID {UserId} has disabled 2fa.\", user.Id);\n        return RedirectToAction(nameof(TwoFactorAuthentication));\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> EnableAuthenticator()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        var model = new EnableAuthenticatorViewModel();\n        await LoadSharedKeyAndQrCodeUriAsync(user, model);\n\n        return View(model);\n    }\n\n    [HttpGet]\n    public IActionResult ShowRecoveryCodes()\n    {\n        var recoveryCodes = (string[]?)TempData[RecoveryCodesKey];\n        if (recoveryCodes == null)\n        {\n            return RedirectToAction(nameof(TwoFactorAuthentication));\n        }\n\n        var model = new ShowRecoveryCodesViewModel { RecoveryCodes = recoveryCodes };\n        return View(model);\n    }\n\n\n    [HttpPost]\n    [ValidateAntiForgeryToken]\n    public async Task<IActionResult> EnableAuthenticator(EnableAuthenticatorViewModel model)\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        if (!ModelState.IsValid)\n        {\n            await LoadSharedKeyAndQrCodeUriAsync(user, model);\n            return View(model);\n        }\n\n        // Strip spaces and hypens\n        string verificationCode = model.Code?.Replace(\" \", string.Empty).Replace(\"-\", string.Empty) ?? \"\";\n\n        var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(\n            user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);\n\n        if (!is2faTokenValid)\n        {\n            ModelState.AddModelError(\"Code\", \"Verification code is invalid.\");\n            await LoadSharedKeyAndQrCodeUriAsync(user, model);\n            return View(model);\n        }\n\n        await _userManager.SetTwoFactorEnabledAsync(user, true);\n        _logger.LogInformation(\"User with ID {UserId} has enabled 2FA with an authenticator app.\", user.Id);\n        var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10) ?? new List<string>();\n        TempData[RecoveryCodesKey] = recoveryCodes.ToArray();\n\n        return RedirectToAction(nameof(ShowRecoveryCodes));\n    }\n\n    [HttpGet]\n    public IActionResult ResetAuthenticatorWarning()\n    {\n        return View(nameof(ResetAuthenticator));\n    }\n\n    [HttpPost]\n    [ValidateAntiForgeryToken]\n    public async Task<IActionResult> ResetAuthenticator()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        await _userManager.SetTwoFactorEnabledAsync(user, false);\n        await _userManager.ResetAuthenticatorKeyAsync(user);\n        _logger.LogInformation(\"User with id '{UserId}' has reset their authentication app key.\", user.Id);\n\n        return RedirectToAction(nameof(EnableAuthenticator));\n    }\n\n    [HttpPost]\n    [ValidateAntiForgeryToken]\n    public async Task<IActionResult> GenerateRecoveryCodes()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        if (!user.TwoFactorEnabled)\n        {\n            throw new ApplicationException($\"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.\");\n        }\n\n        var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10) ?? new List<string>();\n        _logger.LogInformation(\"User with ID {UserId} has generated new 2FA recovery codes.\", user.Id);\n\n        var model = new ShowRecoveryCodesViewModel { RecoveryCodes = recoveryCodes.ToArray() };\n\n        return View(nameof(ShowRecoveryCodes), model);\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> GenerateRecoveryCodesWarning()\n    {\n        var user = await _userManager.GetUserAsync(User);\n        if (user == null)\n        {\n            throw new ApplicationException($\"Unable to load user with ID '{_userManager.GetUserId(User)}'.\");\n        }\n\n        if (!user.TwoFactorEnabled)\n        {\n            throw new ApplicationException($\"Cannot generate recovery codes for user with ID '{user.Id}' because they do not have 2FA enabled.\");\n        }\n\n        return View(nameof(GenerateRecoveryCodesWarning));\n    }\n\n    private void AddErrors(IdentityResult result)\n    {\n        foreach (var error in result.Errors)\n        {\n            ModelState.AddModelError(string.Empty, error.Description);\n        }\n    }\n\n    private string FormatKey(string unformattedKey)\n    {\n        var result = new StringBuilder();\n        int currentPosition = 0;\n        while (currentPosition + 4 < unformattedKey.Length)\n        {\n            result.Append(unformattedKey.Substring(currentPosition, 4)).Append(\" \");\n            currentPosition += 4;\n        }\n        if (currentPosition < unformattedKey.Length)\n        {\n            result.Append(unformattedKey.Substring(currentPosition));\n        }\n\n        return result.ToString().ToLowerInvariant();\n    }\n\n    private string GenerateQrCodeUri(string email, string unformattedKey)\n    {\n        return string.Format(\n            AuthenticatorUriFormat,\n            _urlEncoder.Encode(\"eShopOnWeb\"),\n            _urlEncoder.Encode(email),\n            unformattedKey);\n    }\n\n    private async Task LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user, EnableAuthenticatorViewModel model)\n    {\n        var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);\n        if (string.IsNullOrEmpty(unformattedKey))\n        {\n            await _userManager.ResetAuthenticatorKeyAsync(user);\n            unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);\n        }\n\n        model.SharedKey = FormatKey(unformattedKey!);\n        model.AuthenticatorUri = GenerateQrCodeUri(user.Email!, unformattedKey!);\n    }\n\n}\n"
  },
  {
    "path": "src/Web/Controllers/OrderController.cs",
    "content": "﻿using Ardalis.GuardClauses;\nusing MediatR;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.eShopWeb.Web.Features.MyOrders;\nusing Microsoft.eShopWeb.Web.Features.OrderDetails;\n\nnamespace Microsoft.eShopWeb.Web.Controllers;\n\n[ApiExplorerSettings(IgnoreApi = true)]\n[Authorize] // Controllers that mainly require Authorization still use Controller/View; other pages use Pages\n[Route(\"[controller]/[action]\")]\npublic class OrderController : Controller\n{\n    private readonly IMediator _mediator;\n\n    public OrderController(IMediator mediator)\n    {\n        _mediator = mediator;\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> MyOrders()\n    {   \n        Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name));\n        var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name));\n\n        return View(viewModel);\n    }\n\n    [HttpGet(\"{orderId}\")]\n    public async Task<IActionResult> Detail(int orderId)\n    {\n        Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name));\n        var viewModel = await _mediator.Send(new GetOrderDetails(User.Identity.Name, orderId));\n\n        if (viewModel == null)\n        {\n            return BadRequest(\"No such order found for this user.\");\n        }\n\n        return View(viewModel);\n    }\n}\n"
  },
  {
    "path": "src/Web/Controllers/UserController.cs",
    "content": "﻿using System.Security.Claims;\nusing BlazorShared.Authorization;\nusing Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Authentication.Cookies;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.eShopWeb.Web.Configuration;\nusing Microsoft.Extensions.Caching.Memory;\n\nnamespace Microsoft.eShopWeb.Web.Controllers;\n\n[Route(\"[controller]\")]\n[ApiController]\npublic class UserController : ControllerBase\n{\n    private readonly ITokenClaimsService _tokenClaimsService;\n    private readonly SignInManager<ApplicationUser> _signInManager;\n    private readonly ILogger<UserController> _logger;\n    private readonly IMemoryCache _cache;\n\n    public UserController(ITokenClaimsService tokenClaimsService,\n                          SignInManager<ApplicationUser> signInManager,\n                          ILogger<UserController> logger,\n                          IMemoryCache cache)\n    {\n        _tokenClaimsService = tokenClaimsService;\n        _signInManager = signInManager;\n        _logger = logger;\n        _cache = cache;\n    }\n\n    [HttpGet]\n    [Authorize]\n    [AllowAnonymous]\n    public async Task<IActionResult> GetCurrentUser() =>\n        Ok(await CreateUserInfo(User));\n\n    [Route(\"Logout\")]\n    [HttpPost]\n    [Authorize]\n    [AllowAnonymous]\n    public async Task<IActionResult> Logout()\n    {\n        await _signInManager.SignOutAsync();\n        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);\n        var userId = _signInManager.Context.User.Claims.First(c => c.Type == ClaimTypes.Name);\n        var identityKey = _signInManager.Context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName];\n        _cache.Set($\"{userId.Value}:{identityKey}\", identityKey, new MemoryCacheEntryOptions\n        {\n            AbsoluteExpiration = DateTime.Now.AddMinutes(ConfigureCookieSettings.ValidityMinutesPeriod)\n        });\n\n        _logger.LogInformation(\"User logged out.\");\n        return Ok();\n    }\n\n    private async Task<UserInfo> CreateUserInfo(ClaimsPrincipal claimsPrincipal)\n    {\n        if (claimsPrincipal.Identity == null || claimsPrincipal.Identity.Name == null || !claimsPrincipal.Identity.IsAuthenticated)\n        {\n            return UserInfo.Anonymous;\n        }\n\n        var userInfo = new UserInfo\n        {\n            IsAuthenticated = true\n        };\n\n        if (claimsPrincipal.Identity is ClaimsIdentity claimsIdentity)\n        {\n            userInfo.NameClaimType = claimsIdentity.NameClaimType;\n            userInfo.RoleClaimType = claimsIdentity.RoleClaimType;\n        }\n        else\n        {\n            userInfo.NameClaimType = \"name\";\n            userInfo.RoleClaimType = \"role\";\n        }\n\n        if (claimsPrincipal.Claims.Any())\n        {\n            var claims = new List<ClaimValue>();\n            var nameClaims = claimsPrincipal.FindAll(userInfo.NameClaimType);\n            foreach (var claim in nameClaims)\n            {\n                claims.Add(new ClaimValue(userInfo.NameClaimType, claim.Value));\n            }\n\n            foreach (var claim in claimsPrincipal.Claims.Except(nameClaims))\n            {\n                claims.Add(new ClaimValue(claim.Type, claim.Value));\n            }\n\n            userInfo.Claims = claims;\n        }\n\n        var token = await _tokenClaimsService.GetTokenAsync(claimsPrincipal.Identity.Name);\n        userInfo.Token = token;\n\n        return userInfo;\n    }\n}\n"
  },
  {
    "path": "src/Web/Dockerfile",
    "content": "# RUN ALL CONTAINERS FROM ROOT (folder with .sln file):\n# docker-compose build\n# docker-compose up\n#\n# RUN JUST THIS CONTAINER FROM ROOT (folder with .sln file):\n# docker build --pull -t web -f src/Web/Dockerfile .\n#\n# RUN COMMAND\n#  docker run --name eshopweb --rm -it -p 5106:5106 web\nFROM mcr.microsoft.com/dotnet/sdk:8.0 AS build\nWORKDIR /app\n\nCOPY *.sln .\nCOPY . .\nWORKDIR /app/src/Web\nRUN dotnet restore\n\nRUN dotnet publish -c Release -o out\n\nFROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime\nWORKDIR /app\nCOPY --from=build /app/src/Web/out ./\n\n# Optional: Set this here if not setting it from docker-compose.yml\n# ENV ASPNETCORE_ENVIRONMENT Development\n\nENTRYPOINT [\"dotnet\", \"Web.dll\"]\n"
  },
  {
    "path": "src/Web/Extensions/CacheHelpers.cs",
    "content": "﻿using System;\n\nnamespace Microsoft.eShopWeb.Web.Extensions;\n\npublic static class CacheHelpers\n{\n    public static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromSeconds(30);\n    private static readonly string _itemsKeyTemplate = \"items-{0}-{1}-{2}-{3}\";\n\n    public static string GenerateCatalogItemCacheKey(int pageIndex, int itemsPage, int? brandId, int? typeId)\n    {\n        return string.Format(_itemsKeyTemplate, pageIndex, itemsPage, brandId, typeId);\n    }\n\n    public static string GenerateBrandsCacheKey()\n    {\n        return \"brands\";\n    }\n\n    public static string GenerateTypesCacheKey()\n    {\n        return \"types\";\n    }\n}\n"
  },
  {
    "path": "src/Web/Extensions/EmailSenderExtensions.cs",
    "content": "﻿using System.Text.Encodings.Web;\nusing System.Threading.Tasks;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\n\nnamespace Microsoft.eShopWeb.Web.Services;\n\npublic static class EmailSenderExtensions\n{\n    public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link)\n    {\n        return emailSender.SendEmailAsync(email, \"Confirm your email\",\n            $\"Please confirm your account by clicking this link: <a href='{HtmlEncoder.Default.Encode(link)}'>link</a>\");\n    }\n}\n"
  },
  {
    "path": "src/Web/Extensions/UrlHelperExtensions.cs",
    "content": "﻿namespace Microsoft.AspNetCore.Mvc;\n\npublic static class UrlHelperExtensions\n{\n    public static string? EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme)\n    {\n        return urlHelper.Action(\n            action: \"GET\",\n            controller: \"ConfirmEmail\",\n            values: new { userId, code },\n            protocol: scheme);\n    }\n}\n"
  },
  {
    "path": "src/Web/Features/MyOrders/GetMyOrders.cs",
    "content": "﻿using MediatR;\nusing Microsoft.eShopWeb.Web.ViewModels;\n\nnamespace Microsoft.eShopWeb.Web.Features.MyOrders;\n\npublic class GetMyOrders : IRequest<IEnumerable<OrderViewModel>>\n{\n    public string UserName { get; set; }\n\n    public GetMyOrders(string userName)\n    {\n        UserName = userName;\n    }\n}\n"
  },
  {
    "path": "src/Web/Features/MyOrders/GetMyOrdersHandler.cs",
    "content": "﻿using MediatR;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing Microsoft.eShopWeb.Web.ViewModels;\n\nnamespace Microsoft.eShopWeb.Web.Features.MyOrders;\n\npublic class GetMyOrdersHandler : IRequestHandler<GetMyOrders, IEnumerable<OrderViewModel>>\n{\n    private readonly IReadRepository<Order> _orderRepository;\n\n    public GetMyOrdersHandler(IReadRepository<Order> orderRepository)\n    {\n        _orderRepository = orderRepository;\n    }\n\n    public async Task<IEnumerable<OrderViewModel>> Handle(GetMyOrders request,\n        CancellationToken cancellationToken)\n    {\n        var specification = new CustomerOrdersSpecification(request.UserName);\n        var orders = await _orderRepository.ListAsync(specification, cancellationToken);\n\n        return orders.Select(o => new OrderViewModel\n        {\n            OrderDate = o.OrderDate,\n            OrderNumber = o.Id,\n            ShippingAddress = o.ShipToAddress,\n            Total = o.Total()\n        });\n    }\n}\n"
  },
  {
    "path": "src/Web/Features/OrderDetails/GetOrderDetails.cs",
    "content": "﻿using MediatR;\nusing Microsoft.eShopWeb.Web.ViewModels;\n\nnamespace Microsoft.eShopWeb.Web.Features.OrderDetails;\n\npublic class GetOrderDetails : IRequest<OrderDetailViewModel>\n{\n    public string UserName { get; set; }\n    public int OrderId { get; set; }\n\n    public GetOrderDetails(string userName, int orderId)\n    {\n        UserName = userName;\n        OrderId = orderId;\n    }\n}\n"
  },
  {
    "path": "src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs",
    "content": "﻿using MediatR;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing Microsoft.eShopWeb.Web.ViewModels;\n\nnamespace Microsoft.eShopWeb.Web.Features.OrderDetails;\n\npublic class GetOrderDetailsHandler : IRequestHandler<GetOrderDetails, OrderDetailViewModel?>\n{\n    private readonly IReadRepository<Order> _orderRepository;\n\n    public GetOrderDetailsHandler(IReadRepository<Order> orderRepository)\n    {\n        _orderRepository = orderRepository;\n    }\n\n    public async Task<OrderDetailViewModel?> Handle(GetOrderDetails request,\n        CancellationToken cancellationToken)\n    {\n        var spec = new OrderWithItemsByIdSpec(request.OrderId);\n        var order = await _orderRepository.FirstOrDefaultAsync(spec, cancellationToken);\n\n        if (order == null)\n        {\n            return null;\n        }\n\n        return new OrderDetailViewModel\n        {\n            OrderDate = order.OrderDate,\n            OrderItems = order.OrderItems.Select(oi => new OrderItemViewModel\n            {\n                PictureUrl = oi.ItemOrdered.PictureUri,\n                ProductId = oi.ItemOrdered.CatalogItemId,\n                ProductName = oi.ItemOrdered.ProductName,\n                UnitPrice = oi.UnitPrice,\n                Units = oi.Units\n            }).ToList(),\n            OrderNumber = order.Id,\n            ShippingAddress = order.ShipToAddress,\n            Total = order.Total()\n        };\n    }\n}\n"
  },
  {
    "path": "src/Web/HealthChecks/ApiHealthCheck.cs",
    "content": "﻿using System.Net.Http;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing BlazorShared;\nusing Microsoft.Extensions.Diagnostics.HealthChecks;\nusing Microsoft.Extensions.Options;\n\nnamespace Microsoft.eShopWeb.Web.HealthChecks;\n\npublic class ApiHealthCheck : IHealthCheck\n{\n    private readonly BaseUrlConfiguration _baseUrlConfiguration;\n\n    public ApiHealthCheck(IOptions<BaseUrlConfiguration> baseUrlConfiguration)\n    {\n        _baseUrlConfiguration = baseUrlConfiguration.Value;\n    }\n\n    public async Task<HealthCheckResult> CheckHealthAsync(\n        HealthCheckContext context,\n        CancellationToken cancellationToken = default(CancellationToken))\n    {\n        string myUrl = _baseUrlConfiguration.ApiBase + \"catalog-items\";\n        var client = new HttpClient();\n        var response = await client.GetAsync(myUrl);\n        var pageContents = await response.Content.ReadAsStringAsync();\n        if (pageContents.Contains(\".NET Bot Black Sweatshirt\"))\n        {\n            return HealthCheckResult.Healthy(\"The check indicates a healthy result.\");\n        }\n\n        return HealthCheckResult.Unhealthy(\"The check indicates an unhealthy result.\");\n    }\n}\n"
  },
  {
    "path": "src/Web/HealthChecks/HomePageHealthCheck.cs",
    "content": "﻿using System.Net.Http;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.Extensions.Diagnostics.HealthChecks;\n\nnamespace Microsoft.eShopWeb.Web.HealthChecks;\n\npublic class HomePageHealthCheck : IHealthCheck\n{\n    private readonly IHttpContextAccessor _httpContextAccessor;\n\n    public HomePageHealthCheck(IHttpContextAccessor httpContextAccessor)\n    {\n        _httpContextAccessor = httpContextAccessor;\n    }\n\n    public async Task<HealthCheckResult> CheckHealthAsync(\n        HealthCheckContext context,\n        CancellationToken cancellationToken = default(CancellationToken))\n    {\n        var request = _httpContextAccessor.HttpContext?.Request;\n        string myUrl = request?.Scheme + \"://\" + request?.Host.ToString();\n\n        var client = new HttpClient();\n        var response = await client.GetAsync(myUrl);\n        var pageContents = await response.Content.ReadAsStringAsync();\n        if (pageContents.Contains(\".NET Bot Black Sweatshirt\"))\n        {\n            return HealthCheckResult.Healthy(\"The check indicates a healthy result.\");\n        }\n\n        return HealthCheckResult.Unhealthy(\"The check indicates an unhealthy result.\");\n    }\n}\n"
  },
  {
    "path": "src/Web/Interfaces/IBasketViewModelService.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.Web.Pages.Basket;\n\nnamespace Microsoft.eShopWeb.Web.Interfaces;\n\npublic interface IBasketViewModelService\n{\n    Task<BasketViewModel> GetOrCreateBasketForUser(string userName);\n    Task<int> CountTotalBasketItems(string username);\n    Task<BasketViewModel> Map(Basket basket);\n}\n"
  },
  {
    "path": "src/Web/Interfaces/ICatalogItemViewModelService.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.eShopWeb.Web.ViewModels;\n\nnamespace Microsoft.eShopWeb.Web.Interfaces;\n\npublic interface ICatalogItemViewModelService\n{\n    Task UpdateCatalogItem(CatalogItemViewModel viewModel);\n}\n"
  },
  {
    "path": "src/Web/Interfaces/ICatalogViewModelService.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.eShopWeb.Web.ViewModels;\n\nnamespace Microsoft.eShopWeb.Web.Services;\n\npublic interface ICatalogViewModelService\n{\n    Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId);\n    Task<IEnumerable<SelectListItem>> GetBrands();\n    Task<IEnumerable<SelectListItem>> GetTypes();\n}\n"
  },
  {
    "path": "src/Web/Pages/Admin/EditCatalogItem.cshtml",
    "content": "﻿@page\n@{\n    ViewData[\"Title\"] = \"Admin - Edit Catalog\";\n    @model EditCatalogItemModel\n}\n\n<div class=\"container\">\n    <div class=\"row\">\n        <div class=\"col-md-8\">\n            <img class=\"esh-catalog-thumbnail\" src=\"@Model.CatalogModel.PictureUri\" />\n            <form method=\"post\">\n                <div class=\"form-group\">\n                    <label asp-for=\"CatalogModel.Name\"></label>\n                    <input asp-for=\"CatalogModel.Name\" class=\"form-control\" />\n                    <span asp-validation-for=\"CatalogModel.Name\"></span>\n                </div>\n                <div class=\"form-group\">\n                    <label asp-for=\"CatalogModel.Price\"></label>\n                    <input asp-for=\"CatalogModel.Price\" class=\"form-control\" />\n                    <span asp-validation-for=\"CatalogModel.Price\"></span>\n                </div>\n                <div class=\"form-group\">\n                    <label asp-for=\"CatalogModel.Id\"></label>\n                    <input asp-for=\"CatalogModel.Id\" readonly class=\"form-control\" />\n                    <span asp-validation-for=\"CatalogModel.Id\"></span>\n                </div>\n                <div class=\"col-md-2\">\n                    <input type=\"submit\" value=\"Save\" class=\"esh-catalog-button\" />\n                </div>\n            </form>\n        </div>\n    </div>\n</div>\n\n\n@section Scripts {\n    <partial name=\"_ValidationScriptsPartial\" />\n}"
  },
  {
    "path": "src/Web/Pages/Admin/EditCatalogItem.cshtml.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.eShopWeb.ApplicationCore.Constants;\nusing Microsoft.eShopWeb.Web.Interfaces;\nusing Microsoft.eShopWeb.Web.ViewModels;\n\nnamespace Microsoft.eShopWeb.Web.Pages.Admin;\n\n[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)]\npublic class EditCatalogItemModel : PageModel\n{\n    private readonly ICatalogItemViewModelService _catalogItemViewModelService;\n\n    public EditCatalogItemModel(ICatalogItemViewModelService catalogItemViewModelService)\n    {\n        _catalogItemViewModelService = catalogItemViewModelService;\n    }\n\n    [BindProperty]\n    public CatalogItemViewModel CatalogModel { get; set; } = new CatalogItemViewModel();\n\n    public void OnGet(CatalogItemViewModel catalogModel)\n    {\n        CatalogModel = catalogModel;\n    }\n\n    public async Task<IActionResult> OnPostAsync()\n    {\n        if (ModelState.IsValid)\n        {\n            await _catalogItemViewModelService.UpdateCatalogItem(CatalogModel);\n        }\n\n        return RedirectToPage(\"/Admin/Index\");\n    }\n}\n"
  },
  {
    "path": "src/Web/Pages/Admin/Index.cshtml",
    "content": "﻿@page\n@using BlazorAdmin\n\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n\n@{\n    Layout = null;\n    ViewData[\"Title\"] = \"Admin - Catalog\";\n}\n\n\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />\n    <title>@ViewData[\"Title\"] - Microsoft.eShopOnWeb</title>\n    <base href=\"~/\" />\n    <environment include=\"Development,Docker\">\n        <link rel=\"stylesheet\" href=\"css/bootstrap/bootstrap.min.css\" />\n    </environment>\n    <environment exclude=\"Development,Docker\">\n        <link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\"\n              asp-fallback-href=\"css/bootstrap/bootstrap.min.css\"\n              asp-fallback-test-class=\"sr-only\" asp-fallback-test-property=\"position\" asp-fallback-test-value=\"absolute\"\n              crossorigin=\"anonymous\"\n              integrity=\"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T\" />\n    </environment>\n    <link href=\"css/admin.css\" rel=\"stylesheet\" />\n    <script src=\"_content/BlazorInputFile/inputfile.js\"></script>\n    <script>\n\n        window.getCookie = (cname) => {\n            var name = cname + \"=\";\n            var decodedCookie = decodeURIComponent(document.cookie);\n            var ca = decodedCookie.split(';');\n            for (var i = 0; i < ca.length; i++) {\n                var c = ca[i];\n                while (c.charAt(0) == ' ') {\n                    c = c.substring(1);\n                }\n                if (c.indexOf(name) == 0) {\n                    return c.substring(name.length, c.length);\n                }\n            }\n\n            return \"\";\n        };\n\n        window.deleteCookie = (cname) => {\n            document.cookie = cname + \"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;\";\n        };\n\n        window.routeOutside = (path) => {\n            window.location = path;\n        };\n\n        window.hideBodyOverflow = () => {\n            document.body.classList.add(\"body-no-overflow\");\n        };\n\n        window.showBodyOverflow = () => {\n            document.body.classList.remove(\"body-no-overflow\");\n        };\n\n    </script>\n</head>\n<body>\n    <div id=\"admin\"></div>    \n    <script src=\"_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js\"></script>\n    <script src=\"_framework/blazor.webassembly.js\"></script>\n\n</body>\n</html>\n\n"
  },
  {
    "path": "src/Web/Pages/Admin/Index.cshtml.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.eShopWeb.ApplicationCore.Constants;\nusing Microsoft.eShopWeb.Web.Extensions;\nusing Microsoft.eShopWeb.Web.Services;\nusing Microsoft.eShopWeb.Web.ViewModels;\nusing Microsoft.Extensions.Caching.Memory;\n\nnamespace Microsoft.eShopWeb.Web.Pages.Admin;\n\n[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)]\npublic class IndexModel : PageModel\n{\n    public IndexModel()\n    {\n\n    }\n}\n"
  },
  {
    "path": "src/Web/Pages/Basket/BasketItemViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Microsoft.eShopWeb.Web.Pages.Basket;\n\npublic class BasketItemViewModel\n{\n    public int Id { get; set; }\n    public int CatalogItemId { get; set; }\n    public string? ProductName { get; set; }\n    public decimal UnitPrice { get; set; }\n    public decimal OldUnitPrice { get; set; }\n\n    [Range(0, int.MaxValue, ErrorMessage = \"Quantity must be bigger than 0\")]\n    public int Quantity { get; set; }\n  \n    public string? PictureUrl { get; set; }\n}\n"
  },
  {
    "path": "src/Web/Pages/Basket/BasketViewModel.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web.Pages.Basket;\n\npublic class BasketViewModel\n{\n    public int Id { get; set; }\n    public List<BasketItemViewModel> Items { get; set; } = new List<BasketItemViewModel>();\n    public string? BuyerId { get; set; }\n\n    public decimal Total()\n    {\n        return Math.Round(Items.Sum(x => x.UnitPrice * x.Quantity), 2);\n    }\n}\n"
  },
  {
    "path": "src/Web/Pages/Basket/Checkout.cshtml",
    "content": "﻿@page\n@model CheckoutModel\n@{\n    ViewData[\"Title\"] = \"Checkout\";\n}\n<section class=\"esh-catalog-hero\">\n    <div class=\"container\">\n        <img class=\"esh-catalog-title\" src=\"~/images/main_banner_text.png\" />\n    </div>\n</section>\n\n<div class=\"container\">\n    <h1>Review</h1>\n    @if (Model.BasketModel.Items.Any())\n    {\n        <form asp-page=\"Checkout\" method=\"post\">\n            <article class=\"esh-basket-titles row\">\n                <br />\n                <section class=\"esh-basket-title col-xs-3\">Product</section>\n                <section class=\"esh-basket-title col-xs-3 hidden-lg-down\"></section>\n                <section class=\"esh-basket-title col-xs-2\">Price</section>\n                <section class=\"esh-basket-title col-xs-2\">Quantity</section>\n                <section class=\"esh-basket-title col-xs-2\">Cost</section>\n            </article>\n            <div class=\"esh-catalog-items row\">\n                <div asp-validation-summary=\"All\" class=\"text-danger\"></div>\n                @for (int i = 0; i < Model.BasketModel.Items.Count; i++)\n                {\n                    var item = Model.BasketModel.Items[i];\n                    <article class=\"esh-basket-items row\">\n                        <div>\n                            <section class=\"esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down\">\n                                <img class=\"esh-basket-image\" src=\"@item.PictureUrl\" />\n                            </section>\n                            <section class=\"esh-basket-item esh-basket-item--middle col-xs-3\">@item.ProductName</section>\n                            <section class=\"esh-basket-item esh-basket-item--middle col-xs-2\">$ @item.UnitPrice.ToString(\"N2\")</section>\n                            <section class=\"esh-basket-item esh-basket-item--middle col-xs-2\">\n                                <input type=\"hidden\" name=\"@(\"Items[\" + i + \"].Id\")\" value=\"@item.Id\" />\n                                <input type=\"hidden\" name=\"@(\"Items[\" + i + \"].Quantity\")\" value=\"@item.Quantity\" />\n                                @item.Quantity\n                            </section>\n                            <section class=\"esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2\">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString(\"N2\")</section>\n                        </div>\n                        <div class=\"row\">\n\n                        </div>\n                    </article>\n                }\n\n                <div class=\"container\">\n                    <article class=\"esh-basket-titles esh-basket-titles--clean row\">\n                        <section class=\"esh-basket-title col-xs-10\"></section>\n                        <section class=\"esh-basket-title col-xs-2\">Total</section>\n                    </article>\n\n                    <article class=\"esh-basket-items row\">\n                        <section class=\"esh-basket-item col-xs-10\"></section>\n                        <section class=\"esh-basket-item esh-basket-item--mark col-xs-2\">$ @Model.BasketModel.Total().ToString(\"N2\")</section>\n                    </article>\n\n                    <article class=\"esh-basket-items row\">\n                        <section class=\"esh-basket-item col-xs-7\"></section>\n                    </article>\n                </div>\n                <div class=\"row\">\n                    <section class=\"esh-basket-item col-xs-1\">\n                        <a asp-page=\"Index\" class=\"btn esh-basket-checkout text-white\">[ Back ]</a>\n                    </section>\n                    <section class=\"esh-basket-item col-xs-push-7 col-xs-4 text-right\">\n                        <input type=\"submit\" class=\"btn esh-basket-checkout\" value=\"[ Pay Now ]\" />\n                    </section>\n                </div>\n            </div>\n        </form>\n    }\n    else\n    {\n        <h3 class=\"esh-catalog-items row\">\n            Basket is empty.\n        </h3>\n\n        <section class=\"esh-basket-item\">\n            <a asp-page=\"/Index\" class=\"btn esh-basket-checkout text-white\">[ Continue Shopping ]</a>\n        </section>\n    }\n</div>"
  },
  {
    "path": "src/Web/Pages/Basket/Checkout.cshtml.cs",
    "content": "﻿using Ardalis.GuardClauses;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Exceptions;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.eShopWeb.Web.Interfaces;\n\nnamespace Microsoft.eShopWeb.Web.Pages.Basket;\n\n[Authorize]\npublic class CheckoutModel : PageModel\n{\n    private readonly IBasketService _basketService;\n    private readonly SignInManager<ApplicationUser> _signInManager;\n    private readonly IOrderService _orderService;\n    private string? _username = null;\n    private readonly IBasketViewModelService _basketViewModelService;\n    private readonly IAppLogger<CheckoutModel> _logger;\n\n    public CheckoutModel(IBasketService basketService,\n        IBasketViewModelService basketViewModelService,\n        SignInManager<ApplicationUser> signInManager,\n        IOrderService orderService,\n        IAppLogger<CheckoutModel> logger)\n    {\n        _basketService = basketService;\n        _signInManager = signInManager;\n        _orderService = orderService;\n        _basketViewModelService = basketViewModelService;\n        _logger = logger;\n    }\n\n    public BasketViewModel BasketModel { get; set; } = new BasketViewModel();\n\n    public async Task OnGet()\n    {\n        await SetBasketModelAsync();\n    }\n\n    public async Task<IActionResult> OnPost(IEnumerable<BasketItemViewModel> items)\n    {\n        try\n        {\n            await SetBasketModelAsync();\n\n            if (!ModelState.IsValid)\n            {\n                return BadRequest();\n            }\n\n            var updateModel = items.ToDictionary(b => b.Id.ToString(), b => b.Quantity);\n            await _basketService.SetQuantities(BasketModel.Id, updateModel);\n            await _orderService.CreateOrderAsync(BasketModel.Id, new Address(\"123 Main St.\", \"Kent\", \"OH\", \"United States\", \"44240\"));\n            await _basketService.DeleteBasketAsync(BasketModel.Id);\n        }\n        catch (EmptyBasketOnCheckoutException emptyBasketOnCheckoutException)\n        {\n            //Redirect to Empty Basket page\n            _logger.LogWarning(emptyBasketOnCheckoutException.Message);\n            return RedirectToPage(\"/Basket/Index\");\n        }\n\n        return RedirectToPage(\"Success\");\n    }\n\n    private async Task SetBasketModelAsync()\n    {\n        Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name));\n        if (_signInManager.IsSignedIn(HttpContext.User))\n        {\n            BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name);\n        }\n        else\n        {\n            GetOrSetBasketCookieAndUserName();\n            BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username!);\n        }\n    }\n\n    private void GetOrSetBasketCookieAndUserName()\n    {\n        if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))\n        {\n            _username = Request.Cookies[Constants.BASKET_COOKIENAME];\n        }\n        if (_username != null) return;\n\n        _username = Guid.NewGuid().ToString();\n        var cookieOptions = new CookieOptions();\n        cookieOptions.Expires = DateTime.Today.AddYears(10);\n        Response.Cookies.Append(Constants.BASKET_COOKIENAME, _username, cookieOptions);\n    }\n}\n"
  },
  {
    "path": "src/Web/Pages/Basket/Index.cshtml",
    "content": "﻿@page \"{handler?}\"\n@model IndexModel\n@{\n    ViewData[\"Title\"] = \"Basket\";\n}\n<section class=\"esh-catalog-hero\">\n    <div class=\"container\">\n        <img class=\"esh-catalog-title\" src=\"~/images/main_banner_text.png\" />\n    </div>\n</section>\n\n<div class=\"container\">\n\n    @if (Model.BasketModel.Items.Any())\n    {\n        <form method=\"post\">\n            <article class=\"esh-basket-titles row\">\n                <br />\n                <section class=\"esh-basket-title col-xs-3\">Product</section>\n                <section class=\"esh-basket-title col-xs-3 hidden-lg-down\"></section>\n                <section class=\"esh-basket-title col-xs-2\">Price</section>\n                <section class=\"esh-basket-title col-xs-2\">Quantity</section>\n                <section class=\"esh-basket-title col-xs-2\">Cost</section>\n            </article>\n            <div class=\"esh-catalog-items row\">\n                <div asp-validation-summary=\"All\" class=\"text-danger\"></div>\n                @for (int i = 0; i < Model.BasketModel.Items.Count; i++)\n                {\n                    var item = Model.BasketModel.Items[i];\n                    <article class=\"esh-basket-items row\">\n                        <div>\n                            <section class=\"esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down\">\n                                <img class=\"esh-basket-image\" src=\"@item.PictureUrl\" />\n                            </section>\n                            <section class=\"esh-basket-item esh-basket-item--middle col-xs-3\">@item.ProductName</section>\n                            <section class=\"esh-basket-item esh-basket-item--middle col-xs-2\">$ @item.UnitPrice.ToString(\"N2\")</section>\n                            <section class=\"esh-basket-item esh-basket-item--middle col-xs-2\">\n                                <input type=\"hidden\" name=\"@(\"Items[\" + i + \"].Id\")\" value=\"@item.Id\" />\n                                <input type=\"number\" class=\"esh-basket-input\" min=\"0\" name=\"@(\"Items[\" + i + \"].Quantity\")\" value=\"@item.Quantity\" />\n                            </section>\n                            <section class=\"esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2\">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString(\"N2\")</section>\n                        </div>\n                        <div class=\"row\">\n\n                        </div>\n                    </article>\n                }\n\n                <div class=\"container\">\n                    <article class=\"esh-basket-titles esh-basket-titles--clean row\">\n                        <section class=\"esh-basket-title col-xs-10\"></section>\n                        <section class=\"esh-basket-title col-xs-2\">Total</section>\n                    </article>\n\n                    <article class=\"esh-basket-items row\">\n                        <section class=\"esh-basket-item col-xs-10\"></section>\n                        <section class=\"esh-basket-item esh-basket-item--mark col-xs-2\">$ @Model.BasketModel.Total().ToString(\"N2\")</section>\n                    </article>\n\n                    <article class=\"esh-basket-items row\">\n                        <section class=\"esh-basket-item col-xs-7\"></section>\n                    </article>\n                </div>\n                <div class=\"row\">\n                    <section class=\"esh-basket-item col-xs-1\">\n                        <a asp-page=\"/Index\" class=\"btn esh-basket-checkout text-white\">[ Continue Shopping ]</a>\n                    </section>\n                    <section class=\"esh-basket-item col-xs-push-7 col-xs-4\">\n\n                        <button class=\"btn esh-basket-checkout\" name=\"updatebutton\" value=\"\" type=\"submit\"\n                                asp-page-handler=\"Update\">\n                            [ Update ]\n                        </button>\n                        <a asp-page=\"./Checkout\" class=\"btn esh-basket-checkout\">[ Checkout ]</a>\n                    </section>\n                </div>\n            </div>\n        </form>\n    }\n    else\n    {\n        <h3 class=\"esh-catalog-items row\">\n            Basket is empty.\n        </h3>\n\n        <section class=\"esh-basket-item\">\n            <a asp-page=\"/Index\" class=\"btn esh-basket-checkout text-white\">[ Continue Shopping ]</a>\n        </section>\n    }\n</div>\n"
  },
  {
    "path": "src/Web/Pages/Basket/Index.cshtml.cs",
    "content": "﻿using Ardalis.GuardClauses;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.Web.Interfaces;\nusing Microsoft.eShopWeb.Web.ViewModels;\n\nnamespace Microsoft.eShopWeb.Web.Pages.Basket;\n\npublic class IndexModel : PageModel\n{\n    private readonly IBasketService _basketService;\n    private readonly IBasketViewModelService _basketViewModelService;\n    private readonly IRepository<CatalogItem> _itemRepository;\n\n    public IndexModel(IBasketService basketService,\n        IBasketViewModelService basketViewModelService,\n        IRepository<CatalogItem> itemRepository)\n    {\n        _basketService = basketService;\n        _basketViewModelService = basketViewModelService;\n        _itemRepository = itemRepository;\n    }\n\n    public BasketViewModel BasketModel { get; set; } = new BasketViewModel();\n\n    public async Task OnGet()\n    {\n        BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(GetOrSetBasketCookieAndUserName());\n    }\n\n    public async Task<IActionResult> OnPost(CatalogItemViewModel productDetails)\n    {\n        if (productDetails?.Id == null)\n        {\n            return RedirectToPage(\"/Index\");\n        }\n\n        var item = await _itemRepository.GetByIdAsync(productDetails.Id);\n        if (item == null)\n        {\n            return RedirectToPage(\"/Index\");\n        }\n\n        var username = GetOrSetBasketCookieAndUserName();\n        var basket = await _basketService.AddItemToBasket(username,\n            productDetails.Id, item.Price);\n\n        BasketModel = await _basketViewModelService.Map(basket);\n\n        return RedirectToPage();\n    }\n\n    public async Task OnPostUpdate(IEnumerable<BasketItemViewModel> items)\n    {\n        if (!ModelState.IsValid)\n        {\n            return;\n        }\n\n        var basketView = await _basketViewModelService.GetOrCreateBasketForUser(GetOrSetBasketCookieAndUserName());\n        var updateModel = items.ToDictionary(b => b.Id.ToString(), b => b.Quantity);\n        var basket = await _basketService.SetQuantities(basketView.Id, updateModel);\n        BasketModel = await _basketViewModelService.Map(basket);\n    }\n\n    private string GetOrSetBasketCookieAndUserName()\n    {\n        Guard.Against.Null(Request.HttpContext.User.Identity, nameof(Request.HttpContext.User.Identity));\n        string? userName = null;\n\n        if (Request.HttpContext.User.Identity.IsAuthenticated)\n        {\n            Guard.Against.Null(Request.HttpContext.User.Identity.Name, nameof(Request.HttpContext.User.Identity.Name));\n            return Request.HttpContext.User.Identity.Name!;\n        }\n\n        if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))\n        {\n            userName = Request.Cookies[Constants.BASKET_COOKIENAME];\n\n            if (!Request.HttpContext.User.Identity.IsAuthenticated)\n            {\n                if (!Guid.TryParse(userName, out var _))\n                {\n                    userName = null;\n                }\n            }\n        }\n        if (userName != null) return userName;\n\n        userName = Guid.NewGuid().ToString();\n        var cookieOptions = new CookieOptions { IsEssential = true };\n        cookieOptions.Expires = DateTime.Today.AddYears(10);\n        Response.Cookies.Append(Constants.BASKET_COOKIENAME, userName, cookieOptions);\n\n        return userName;\n    }\n}\n"
  },
  {
    "path": "src/Web/Pages/Basket/Success.cshtml",
    "content": "﻿@page\n@model SuccessModel\n@{\n    ViewData[\"Title\"] = \"Checkout Complete\";\n}\n\n<section class=\"esh-catalog-hero\">\n    <div class=\"container\">\n        <img class=\"esh-catalog-title\" src=\"~/images/main_banner_text.png\" />\n    </div>\n</section>\n\n<div class=\"container\">\n    <h1>Thanks for your Order!</h1>\n\n    <a asp-page=\"/Index\">Continue Shopping...</a>\n</div>"
  },
  {
    "path": "src/Web/Pages/Basket/Success.cshtml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\n\nnamespace Microsoft.eShopWeb.Web.Pages.Basket;\n\n[Authorize]\npublic class SuccessModel : PageModel\n{\n    public void OnGet()\n    {\n\n    }\n}\n"
  },
  {
    "path": "src/Web/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "src/Web/Pages/Error.cshtml.cs",
    "content": "﻿using System.Diagnostics;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\n\nnamespace Microsoft.eShopWeb.Web.Pages;\n\n[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\npublic class ErrorModel : PageModel\n{\n    public string? RequestId { get; set; }\n\n    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n    public void OnGet()\n    {\n        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n    }\n}\n"
  },
  {
    "path": "src/Web/Pages/FeatureDiagnostics.cshtml",
    "content": "@page \"/feature-diagnostics\"\n@using Microsoft.FeatureManagement\n@inject IFeatureManager FeatureManager\n@inject IConfiguration Configuration\n@{\n    ViewData[\"Title\"] = \"Feature Diagnostics\";\n    var salesWeekendEnabled = await FeatureManager.IsEnabledAsync(\"SalesWeekend\");\n    var useAppConfig = Configuration[\"UseAppConfig\"];\n    var appConfigEndpoint = Configuration[\"AppConfigEndpoint\"];\n    var featureFlagConfig = Configuration[\"FeatureManagement:SalesWeekend\"];\n}\n\n<h1>Feature Flag Diagnostics</h1>\n\n<table class=\"table\">\n    <tr>\n        <td><strong>UseAppConfig</strong></td>\n        <td>@(useAppConfig ?? \"(not set)\")</td>\n    </tr>\n    <tr>\n        <td><strong>AppConfigEndpoint</strong></td>\n        <td>@(string.IsNullOrEmpty(appConfigEndpoint) ? \"(not set)\" : appConfigEndpoint)</td>\n    </tr>\n    <tr>\n        <td><strong>SalesWeekend Feature Flag Enabled</strong></td>\n        <td style=\"color: @(salesWeekendEnabled ? \"green\" : \"red\"); font-weight: bold;\">\n            @salesWeekendEnabled\n        </td>\n    </tr>\n    <tr>\n        <td><strong>FeatureManagement:SalesWeekend (from config)</strong></td>\n        <td>@(featureFlagConfig ?? \"(not found in config)\")</td>\n    </tr>\n    <tr>\n        <td><strong>ASPNETCORE_ENVIRONMENT</strong></td>\n        <td>@(Environment.GetEnvironmentVariable(\"ASPNETCORE_ENVIRONMENT\") ?? \"(not set)\")</td>\n    </tr>\n</table>\n\n<h2>What to check:</h2>\n<ul>\n    <li>If <strong>SalesWeekend Feature Flag Enabled</strong> is <span style=\"color:green\">True</span>, the banner should show.</li>\n    <li>If it's <span style=\"color:red\">False</span>, verify the feature flag in Azure App Configuration → Feature manager.</li>\n    <li>Ensure the feature flag name is exactly <code>SalesWeekend</code> (case-sensitive).</li>\n    <li>Ensure the feature flag has <strong>no label</strong>.</li>\n</ul>\n"
  },
  {
    "path": "src/Web/Pages/Index.cshtml",
    "content": "﻿@page\n@{\n    ViewData[\"Title\"] = \"Catalog\";\n    @model IndexModel\n}\n<section class=\"esh-catalog-hero\">\n    <div class=\"container\">\n        <feature name=\"SalesWeekend\">\n            <img class=\"esh-catalog-title\" src=\"~/images/main_banner_text.png\" />\n        </feature>\n    </div>\n</section>\n<section class=\"esh-catalog-filters\">\n    <div class=\"container\">\n        <form method=\"get\">\n            <label class=\"esh-catalog-label\" data-title=\"brand\">\n                <select asp-for=\"@Model.CatalogModel.BrandFilterApplied\" asp-items=\"@Model.CatalogModel.Brands\" class=\"esh-catalog-filter\"></select>\n            </label>\n            <label class=\"esh-catalog-label\" data-title=\"type\">\n                <select asp-for=\"@Model.CatalogModel.TypesFilterApplied\" asp-items=\"@Model.CatalogModel.Types\" class=\"esh-catalog-filter\"></select>\n            </label>\n            <input class=\"esh-catalog-send\" type=\"image\" src=\"~/images/arrow-right.svg\" />\n        </form>\n    </div>\n</section>\n<div class=\"container\">\n    @if (Model.CatalogModel.CatalogItems.Any())\n    {\n        <partial name=\"_pagination\" for=\"CatalogModel.PaginationInfo\" />\n\n        <div class=\"esh-catalog-items row\">\n            @foreach (var catalogItem in Model.CatalogModel.CatalogItems)\n            {\n                <div class=\"esh-catalog-item col-md-4\">\n                    <partial name=\"_product\" for=\"@catalogItem\" />\n                </div>\n            }\n        </div>\n        <partial name=\"_pagination\" for=\"CatalogModel.PaginationInfo\" />\n    }\n    else\n    {\n        <div class=\"esh-catalog-items row\">\n            @Model.SettingsModel.NoResultsMessage\n        </div>\n    }\n</div>\n"
  },
  {
    "path": "src/Web/Pages/Index.cshtml.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.eShopWeb.Web.Services;\nusing Microsoft.eShopWeb.Web.ViewModels;\nusing Microsoft.Extensions.Options;\n\nnamespace Microsoft.eShopWeb.Web.Pages;\n\npublic class IndexModel : PageModel\n{\n    private readonly ICatalogViewModelService _catalogViewModelService;\n    public SettingsViewModel SettingsModel { get; }\n\n    public IndexModel(ICatalogViewModelService catalogViewModelService, IOptionsSnapshot<SettingsViewModel> options)\n    {\n        _catalogViewModelService = catalogViewModelService;\n        SettingsModel = options.Value;\n    }\n\n    public required CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel();\n\n    public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId)\n    {\n        CatalogModel = await _catalogViewModelService.GetCatalogItems(pageId ?? 0, Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied);\n    }\n}\n"
  },
  {
    "path": "src/Web/Pages/Privacy.cshtml",
    "content": "﻿@page\n@model PrivacyModel\n@{\n    ViewData[\"Title\"] = \"Privacy Policy\";\n}\n<h1>@ViewData[\"Title\"]</h1>\n\n<p>Use this page to detail your site's privacy policy.</p>\n"
  },
  {
    "path": "src/Web/Pages/Privacy.cshtml.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc.RazorPages;\n\nnamespace Microsoft.eShopWeb.Web.Pages;\n\npublic class PrivacyModel : PageModel\n{\n    public void OnGet()\n    {\n    }\n}\n"
  },
  {
    "path": "src/Web/Pages/SettingsViewModel.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web.Pages;\n\npublic class SettingsViewModel\n{\n    public string? NoResultsMessage { get; set; }\n}\n"
  },
  {
    "path": "src/Web/Pages/Shared/Components/BasketComponent/Basket.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing Ardalis.GuardClauses;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.eShopWeb.Web.Interfaces;\nusing Microsoft.eShopWeb.Web.ViewModels;\n\nnamespace Microsoft.eShopWeb.Web.Pages.Shared.Components.BasketComponent;\n\npublic class Basket : ViewComponent\n{\n    private readonly IBasketViewModelService _basketService;\n    private readonly SignInManager<ApplicationUser> _signInManager;\n\n    public Basket(IBasketViewModelService basketService,\n                    SignInManager<ApplicationUser> signInManager)\n    {\n        _basketService = basketService;\n        _signInManager = signInManager;\n    }\n\n    public async Task<IViewComponentResult> InvokeAsync()\n    {\n        var vm = new BasketComponentViewModel\n        {\n            ItemsCount = await CountTotalBasketItems()\n        };\n        return View(vm);\n    }\n\n    private async Task<int> CountTotalBasketItems()\n    {\n        if (_signInManager.IsSignedIn(HttpContext.User))\n        {\n            Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name));\n            return await _basketService.CountTotalBasketItems(User.Identity.Name);\n        }\n\n        string? anonymousId = GetAnnonymousIdFromCookie();\n        if (anonymousId == null)\n            return 0;\n\n        return await _basketService.CountTotalBasketItems(anonymousId);\n    }\n\n    private string? GetAnnonymousIdFromCookie()\n    {\n        if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))\n        {\n            var id = Request.Cookies[Constants.BASKET_COOKIENAME];\n\n            if (Guid.TryParse(id, out var _))\n            {\n                return id;\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Web/Pages/Shared/Components/BasketComponent/Default.cshtml",
    "content": "﻿@model BasketComponentViewModel\n@{\n    ViewData[\"Title\"] = \"My Basket\";\n}\n<a class=\"esh-basketstatus \"\n   asp-page=\"/Basket/Index\">\n    <div class=\"esh-basketstatus-image\">\n        <img src=\"~/images/cart.png\" />\n    </div>\n    <div class=\"esh-basketstatus-badge\">\n        @Model.ItemsCount\n    </div>\n</a>\n"
  },
  {
    "path": "src/Web/Pages/Shared/_editCatalog.cshtml",
    "content": "﻿@model CatalogItemViewModel\n\n<form asp-page=\"/Admin/EditCatalogItem\" method=\"get\">\n    <div>\n        <img class=\"esh-catalog-thumbnail\" src=\"@Model.PictureUri\" />\n        <div class=\"esh-catalog-name\">\n            <span>@Model.Name</span>\n        </div>\n        <input class=\"esh-catalog-button\" type=\"submit\" value=\"[ Edit ]\" />\n        <input type=\"hidden\" asp-for=\"@Model.Id\" name=\"id\" />\n        <input type=\"hidden\" asp-for=\"@Model.Name\" name=\"name\" />\n        <input type=\"hidden\" asp-for=\"@Model.PictureUri\" name=\"pictureUri\" />\n        <input type=\"hidden\" asp-for=\"@Model.Price\" name=\"price\" />\n    </div>\n</form>\n"
  },
  {
    "path": "src/Web/Pages/Shared/_pagination.cshtml",
    "content": "﻿@model PaginationInfoViewModel\n@{\n    var prevRouteData = Context.Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString());\n    if (prevRouteData.ContainsKey(\"pageId\"))\n        prevRouteData.Remove(\"pageId\");\n    prevRouteData.Add(\"pageId\", (Model.ActualPage - 1).ToString());\n    var nextRouteData = Context.Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString());\n    if (nextRouteData.ContainsKey(\"pageId\"))\n        nextRouteData.Remove(\"pageId\");\n    nextRouteData.Add(\"pageId\", (Model.ActualPage + 1).ToString());\n}\n<div class=\"esh-pager\">\n    <div class=\"container-fluid\">\n        <article class=\"esh-pager-wrapper row\">\n            <nav>\n                <div class=\"col-md-2 col-xs-12\">\n                    <a class=\"esh-pager-item-left esh-pager-item--navigable esh-pager-item @Model.Previous\"\n                       id=\"Previous\"\n                       asp-all-route-data=\"prevRouteData\"\n                       aria-label=\"Previous\">\n                        Previous\n                    </a>\n                </div>\n\n                <div class=\"col-md-8 col-xs-12\">\n                    <span class=\"esh-pager-item\">\n                        Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages\n                    </span>\n                </div>\n\n                <div class=\"col-md-2 col-xs-12\">\n                    <a class=\"esh-pager-item-right esh-pager-item--navigable esh-pager-item @Model.Next\"\n                       id=\"Next\"\n                       asp-all-route-data=\"nextRouteData\"\n                       aria-label=\"Next\">\n                        Next\n                    </a>\n                </div>\n            </nav>\n        </article>\n    </div>\n</div>\n\n"
  },
  {
    "path": "src/Web/Pages/Shared/_product.cshtml",
    "content": "﻿@model CatalogItemViewModel\n\n<form asp-page=\"/Basket/Index\" method=\"post\">\n    <img class=\"esh-catalog-thumbnail\" src=\"@Model.PictureUri\" />\n    <input class=\"esh-catalog-button\" type=\"submit\" value=\"[ ADD TO BASKET ]\" />\n    <div class=\"esh-catalog-name\">\n        <span>@Model.Name</span>\n    </div>\n    <div class=\"esh-catalog-price\">\n        <span>@Model.Price.ToString(\"N2\")</span>\n    </div>\n    <input type=\"hidden\" asp-for=\"@Model.Id\" name=\"id\" />\n    <input type=\"hidden\" asp-for=\"@Model.Name\" name=\"name\" />\n    <input type=\"hidden\" asp-for=\"@Model.PictureUri\" name=\"pictureUri\" />\n    <input type=\"hidden\" asp-for=\"@Model.Price\" name=\"price\" />\n</form>\n"
  },
  {
    "path": "src/Web/Pages/_ViewImports.cshtml",
    "content": "﻿@using Microsoft.eShopWeb.Web\n@using Microsoft.eShopWeb.Web.ViewModels\n@using Microsoft.eShopWeb.Web.ViewModels.Account\n@using Microsoft.eShopWeb.Web.ViewModels.Manage\n@using Microsoft.eShopWeb.Web.Pages\n@using Microsoft.AspNetCore.Identity\n@using Microsoft.eShopWeb.Infrastructure.Identity\n@namespace Microsoft.eShopWeb.Web.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n@addTagHelper *, Microsoft.FeatureManagement.AspNetCore\n"
  },
  {
    "path": "src/Web/Pages/_ViewStart.cshtml",
    "content": "﻿@{\n    Layout = \"_Layout\";\n}\n"
  },
  {
    "path": "src/Web/Program.cs",
    "content": "using System.Net.Mime;\nusing Ardalis.ListStartupServices;\nusing Azure.Identity;\nusing BlazorAdmin;\nusing BlazorAdmin.Services;\nusing Blazored.LocalStorage;\nusing BlazorShared;\nusing Microsoft.AspNetCore.Authentication.Cookies;\nusing Microsoft.AspNetCore.Diagnostics.HealthChecks;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Mvc.ApplicationModels;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.Infrastructure.Data;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.eShopWeb.Web;\nusing Microsoft.eShopWeb.Web.Configuration;\nusing Microsoft.eShopWeb.Web.HealthChecks;\nusing Microsoft.Extensions.Diagnostics.HealthChecks;\nusing Microsoft.eShopWeb.Web.Pages;\nusing Microsoft.FeatureManagement;\nusing Microsoft.IdentityModel.Tokens;\n\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Logging.AddConsole();\n\nbuilder.Configuration.AddEnvironmentVariables();\n\nif (builder.Environment.IsDevelopment() || builder.Environment.EnvironmentName == \"Docker\")\n{\n    // Configure SQL Server (local)\n    Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services);\n}\nelse\n{\n    // Configure SQL Server (prod)\n    var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential());\n    builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration[\"AZURE_KEY_VAULT_ENDPOINT\"] ?? \"\"), credential);\n    builder.Services.AddDbContext<CatalogContext>(c =>\n    {\n        var connectionString = builder.Configuration[builder.Configuration[\"AZURE_SQL_CATALOG_CONNECTION_STRING_KEY\"] ?? \"\"];\n        c.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure());\n    });\n    builder.Services.AddDbContext<AppIdentityDbContext>(options =>\n    {\n        var connectionString = builder.Configuration[builder.Configuration[\"AZURE_SQL_IDENTITY_CONNECTION_STRING_KEY\"] ?? \"\"];\n        options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure());\n    });\n}\n\nbuilder.Services.AddCookieSettings();\n\nbuilder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)\n    .AddCookie(options =>\n    {\n        options.Cookie.HttpOnly = true;\n        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;\n        options.Cookie.SameSite = SameSiteMode.Lax;\n    });\n\nbuilder.Services.AddIdentity<ApplicationUser, IdentityRole>()\n           .AddDefaultUI()\n           .AddEntityFrameworkStores<AppIdentityDbContext>()\n                           .AddDefaultTokenProviders();\n\nbuilder.Services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>();\nbuilder.Services.AddCoreServices(builder.Configuration);\nbuilder.Services.AddWebServices(builder.Configuration);\n\n// Add memory cache services\nbuilder.Services.AddMemoryCache();\nbuilder.Services.AddRouting(options =>\n{\n    // Replace the type and the name used to refer to it with your own\n    // IOutboundParameterTransformer implementation\n    options.ConstraintMap[\"slugify\"] = typeof(SlugifyParameterTransformer);\n});\n\nbuilder.Services.AddMvc(options =>\n{\n    options.Conventions.Add(new RouteTokenTransformerConvention(\n             new SlugifyParameterTransformer()));\n\n});\nbuilder.Services.AddControllersWithViews();\nbuilder.Services.AddRazorPages(options =>\n{\n    options.Conventions.AuthorizePage(\"/Basket/Checkout\");\n});\nbuilder.Services.AddHttpContextAccessor();\nbuilder.Services\n    .AddHealthChecks()\n    .AddCheck<ApiHealthCheck>(\"api_health_check\", tags: new[] { \"apiHealthCheck\" })\n    .AddCheck<HomePageHealthCheck>(\"home_page_health_check\", tags: new[] { \"homePageHealthCheck\" });\nbuilder.Services.Configure<ServiceConfig>(config =>\n{\n    config.Services = new List<ServiceDescriptor>(builder.Services);\n    config.Path = \"/allservices\";\n});\n\n// Initialize useAppConfig parameter\nvar useAppConfig = false;\nBoolean.TryParse(builder.Configuration[\"UseAppConfig\"], out useAppConfig);\n// Add Azure App Configuration middleware to the container of services.\nbuilder.Services.AddAzureAppConfiguration();\n\n// Load configuration from Azure App Configuration\nif (useAppConfig)\n{\n    builder.Configuration.AddAzureAppConfiguration(options =>\n    {\n        var appConfigEndpoint = builder.Configuration[\"AppConfigEndpoint\"];\n\n        if (String.IsNullOrEmpty(appConfigEndpoint))\n        {\n            throw new Exception(\"AppConfigEndpoint is not set in the configuration. Please set AppConfigEndpoint in the configuration.\");\n        }\n\n        options.Connect(new Uri(appConfigEndpoint), new DefaultAzureCredential())\n        .ConfigureRefresh(refresh =>\n        {\n            // Default cache expiration is 30 seconds\n            refresh.Register(\"eShopWeb:Settings:NoResultsMessage\").SetRefreshInterval(TimeSpan.FromSeconds(10));\n        })\n        .UseFeatureFlags(featureFlagOptions =>\n        {\n            // Default cache expiration is 30 seconds\n            featureFlagOptions.SetRefreshInterval(TimeSpan.FromSeconds(10));\n        });\n    });\n}\n\n// Add Feature Management AFTER Azure App Configuration is loaded\nbuilder.Services.AddFeatureManagement();\n\n// Bind configuration \"eShopWeb:Settings\" section to the Settings object\n// This must be AFTER Azure App Configuration is added so it picks up remote values\nbuilder.Services.Configure<SettingsViewModel>(builder.Configuration.GetSection(\"eShopWeb:Settings\"));\n\n// blazor configuration\nvar configSection = builder.Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME);\nbuilder.Services.Configure<BaseUrlConfiguration>(configSection);\nvar baseUrlConfig = configSection.Get<BaseUrlConfiguration>();\n\n// Blazor Admin Required Services for Prerendering\nbuilder.Services.AddScoped<HttpClient>(s => new HttpClient\n{\n    BaseAddress = new Uri(baseUrlConfig!.WebBase)\n});\n\n// add blazor services\nbuilder.Services.AddBlazoredLocalStorage();\nbuilder.Services.AddServerSideBlazor();\nbuilder.Services.AddScoped<ToastService>();\nbuilder.Services.AddScoped<HttpService>();\nbuilder.Services.AddBlazorServices();\n\nbuilder.Services.AddDatabaseDeveloperPageExceptionFilter();\n\nvar app = builder.Build();\n\nif (useAppConfig)\n{\n    // Use Azure App Configuration middleware for dynamic configuration refresh.\n    app.UseAzureAppConfiguration();\n}\n\napp.Logger.LogInformation(\"App created...\");\n\napp.Logger.LogInformation(\"Seeding Database...\");\n\nusing (var scope = app.Services.CreateScope())\n{\n    var scopedProvider = scope.ServiceProvider;\n    try\n    {\n        var catalogContext = scopedProvider.GetRequiredService<CatalogContext>();\n        await CatalogContextSeed.SeedAsync(catalogContext, app.Logger);\n\n        var userManager = scopedProvider.GetRequiredService<UserManager<ApplicationUser>>();\n        var roleManager = scopedProvider.GetRequiredService<RoleManager<IdentityRole>>();\n        var identityContext = scopedProvider.GetRequiredService<AppIdentityDbContext>();\n        await AppIdentityDbContextSeed.SeedAsync(identityContext, userManager, roleManager);\n    }\n    catch (Exception ex)\n    {\n        app.Logger.LogError(ex, \"An error occurred seeding the DB.\");\n    }\n}\n\nvar catalogBaseUrl = builder.Configuration.GetValue(typeof(string), \"CatalogBaseUrl\") as string;\nif (!string.IsNullOrEmpty(catalogBaseUrl))\n{\n    app.Use((context, next) =>\n    {\n        context.Request.PathBase = new PathString(catalogBaseUrl);\n        return next();\n    });\n}\n\napp.UseHealthChecks(\"/health\",\n    new HealthCheckOptions\n    {\n        ResponseWriter = async (context, report) =>\n        {\n            var result = new\n            {\n                status = report.Status.ToString(),\n                errors = report.Entries.Select(e => new\n                {\n                    key = e.Key,\n                    value = Enum.GetName(typeof(HealthStatus), e.Value.Status)\n                })\n            }.ToJson();\n            context.Response.ContentType = MediaTypeNames.Application.Json;\n            await context.Response.WriteAsync(result);\n        }\n    });\nif (app.Environment.IsDevelopment() || app.Environment.EnvironmentName == \"Docker\")\n{\n    app.Logger.LogInformation(\"Adding Development middleware...\");\n    app.UseDeveloperExceptionPage();\n    app.UseShowAllServicesMiddleware();\n    app.UseMigrationsEndPoint();\n    app.UseWebAssemblyDebugging();\n}\nelse\n{\n    app.Logger.LogInformation(\"Adding non-Development middleware...\");\n    app.UseExceptionHandler(\"/Error\");\n    app.UseHsts();\n}\n\napp.UseHttpsRedirection();\napp.UseBlazorFrameworkFiles();\napp.UseStaticFiles();\napp.UseRouting();\n\napp.UseCookiePolicy();\napp.UseAuthentication();\napp.UseAuthorization();\n\n\napp.MapControllerRoute(\"default\", \"{controller:slugify=Home}/{action:slugify=Index}/{id?}\");\napp.MapRazorPages();\napp.MapHealthChecks(\"home_page_health_check\", new HealthCheckOptions { Predicate = check => check.Tags.Contains(\"homePageHealthCheck\") });\napp.MapHealthChecks(\"api_health_check\", new HealthCheckOptions { Predicate = check => check.Tags.Contains(\"apiHealthCheck\") });\n//endpoints.MapBlazorHub(\"/admin\");\napp.MapFallbackToFile(\"index.html\");\n\napp.Logger.LogInformation(\"LAUNCHING\");\napp.Run();\n"
  },
  {
    "path": "src/Web/Properties/launchSettings.json",
    "content": "{\n  \"iisSettings\": {\n    \"windowsAuthentication\": false,\n    \"anonymousAuthentication\": true,\n    \"iisExpress\": {\n      \"applicationUrl\": \"http://localhost:17469\",\n      \"sslPort\": 44315\n    }\n  },\n  \"profiles\": {\n    \"IIS Express\": {\n      \"commandName\": \"IISExpress\",\n      \"launchBrowser\": true,\n      \"inspectUri\": \"{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      },\n      \"applicationUrl\": \"https://localhost:5001;http://localhost:5000\"\n    },\n    \"Web\": {\n      \"commandName\": \"Project\",\n      \"launchBrowser\": true,\n      \"inspectUri\": \"{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\",\n        \"AZURE_TENANT_ID\": \"{azure-tenant-id}\",\n        \"AZURE_CLIENT_ID\": \"{azure-client-id}\",\n        \"AZURE_CLIENT_SECRET\": \"{azure-client-secret}\"\n      },\n      \"applicationUrl\": \"https://localhost:5001;http://localhost:5000\"\n    },\n    \"Web - PROD\": {\n      \"commandName\": \"Project\",\n      \"launchBrowser\": true,\n      \"inspectUri\": \"{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Production\"\n      },\n      \"applicationUrl\": \"https://localhost:5001;http://localhost:5000\"\n    }\n  }\n}"
  },
  {
    "path": "src/Web/Services/BasketViewModelService.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing Microsoft.eShopWeb.Web.Interfaces;\nusing Microsoft.eShopWeb.Web.Pages.Basket;\n\nnamespace Microsoft.eShopWeb.Web.Services;\n\npublic class BasketViewModelService : IBasketViewModelService\n{\n    private readonly IRepository<Basket> _basketRepository;\n    private readonly IUriComposer _uriComposer;\n    private readonly IBasketQueryService _basketQueryService;\n    private readonly IRepository<CatalogItem> _itemRepository;\n\n    public BasketViewModelService(IRepository<Basket> basketRepository,\n        IRepository<CatalogItem> itemRepository,\n        IUriComposer uriComposer,\n        IBasketQueryService basketQueryService)\n    {\n        _basketRepository = basketRepository;\n        _uriComposer = uriComposer;\n        _basketQueryService = basketQueryService;\n        _itemRepository = itemRepository;\n    }\n\n    public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)\n    {\n        var basketSpec = new BasketWithItemsSpecification(userName);\n        var basket = (await _basketRepository.FirstOrDefaultAsync(basketSpec));\n\n        if (basket == null)\n        {\n            return await CreateBasketForUser(userName);\n        }\n        var viewModel = await Map(basket);\n        return viewModel;\n    }\n\n    private async Task<BasketViewModel> CreateBasketForUser(string userId)\n    {\n        var basket = new Basket(userId);\n        await _basketRepository.AddAsync(basket);\n\n        return new BasketViewModel()\n        {\n            BuyerId = basket.BuyerId,\n            Id = basket.Id,\n        };\n    }\n\n    private async Task<List<BasketItemViewModel>> GetBasketItems(IReadOnlyCollection<BasketItem> basketItems)\n    {\n        var catalogItemsSpecification = new CatalogItemsSpecification(basketItems.Select(b => b.CatalogItemId).ToArray());\n        var catalogItems = await _itemRepository.ListAsync(catalogItemsSpecification);\n\n        var items = basketItems.Select(basketItem =>\n        {\n            var catalogItem = catalogItems.First(c => c.Id == basketItem.CatalogItemId);\n\n            var basketItemViewModel = new BasketItemViewModel\n            {\n                Id = basketItem.Id,\n                UnitPrice = basketItem.UnitPrice,\n                Quantity = basketItem.Quantity,\n                CatalogItemId = basketItem.CatalogItemId,\n                PictureUrl = _uriComposer.ComposePicUri(catalogItem.PictureUri),\n                ProductName = catalogItem.Name\n            };\n            return basketItemViewModel;\n        }).ToList();\n\n        return items;\n    }\n\n    public async Task<BasketViewModel> Map(Basket basket)\n    {\n        return new BasketViewModel()\n        {\n            BuyerId = basket.BuyerId,\n            Id = basket.Id,\n            Items = await GetBasketItems(basket.Items)\n        };\n    }\n\n    public async Task<int> CountTotalBasketItems(string username)\n    {\n        var counter = await _basketQueryService.CountTotalBasketItems(username);\n\n        return counter;\n    }\n}\n"
  },
  {
    "path": "src/Web/Services/CachedCatalogViewModelService.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.eShopWeb.Web.Extensions;\nusing Microsoft.eShopWeb.Web.ViewModels;\nusing Microsoft.Extensions.Caching.Memory;\n\nnamespace Microsoft.eShopWeb.Web.Services;\n\npublic class CachedCatalogViewModelService : ICatalogViewModelService\n{\n    private readonly IMemoryCache _cache;\n    private readonly CatalogViewModelService _catalogViewModelService;\n\n    public CachedCatalogViewModelService(IMemoryCache cache,\n        CatalogViewModelService catalogViewModelService)\n    {\n        _cache = cache;\n        _catalogViewModelService = catalogViewModelService;\n    }\n\n    public async Task<IEnumerable<SelectListItem>> GetBrands()\n    {\n        return (await _cache.GetOrCreateAsync(CacheHelpers.GenerateBrandsCacheKey(), async entry =>\n                {\n                    entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;\n                    return await _catalogViewModelService.GetBrands();\n                })) ?? new List<SelectListItem>();\n    }\n\n    public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId)\n    {\n        var cacheKey = CacheHelpers.GenerateCatalogItemCacheKey(pageIndex, Constants.ITEMS_PER_PAGE, brandId, typeId);\n\n        return (await _cache.GetOrCreateAsync(cacheKey, async entry =>\n        {\n            entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;\n            return await _catalogViewModelService.GetCatalogItems(pageIndex, itemsPage, brandId, typeId);\n        })) ?? new CatalogIndexViewModel();\n    }\n\n    public async Task<IEnumerable<SelectListItem>> GetTypes()\n    {\n        return (await _cache.GetOrCreateAsync(CacheHelpers.GenerateTypesCacheKey(), async entry =>\n        {\n            entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;\n            return await _catalogViewModelService.GetTypes();\n        })) ?? new List<SelectListItem>();\n    }\n}\n"
  },
  {
    "path": "src/Web/Services/CatalogItemViewModelService.cs",
    "content": "﻿using Ardalis.GuardClauses;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.Web.Interfaces;\nusing Microsoft.eShopWeb.Web.ViewModels;\n\nnamespace Microsoft.eShopWeb.Web.Services;\n\npublic class CatalogItemViewModelService : ICatalogItemViewModelService\n{\n    private readonly IRepository<CatalogItem> _catalogItemRepository;\n\n    public CatalogItemViewModelService(IRepository<CatalogItem> catalogItemRepository)\n    {\n        _catalogItemRepository = catalogItemRepository;\n    }\n\n    public async Task UpdateCatalogItem(CatalogItemViewModel viewModel)\n    {\n        var existingCatalogItem = await _catalogItemRepository.GetByIdAsync(viewModel.Id);\n\n        Guard.Against.Null(existingCatalogItem, nameof(existingCatalogItem));\n\n        CatalogItem.CatalogItemDetails details = new(viewModel.Name, existingCatalogItem.Description, viewModel.Price);\n        existingCatalogItem.UpdateDetails(details);\n        await _catalogItemRepository.UpdateAsync(existingCatalogItem);\n    }\n}\n"
  },
  {
    "path": "src/Web/Services/CatalogViewModelService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing Microsoft.eShopWeb.Web.ViewModels;\nusing Microsoft.Extensions.Logging;\n\nnamespace Microsoft.eShopWeb.Web.Services;\n\n/// <summary>\n/// This is a UI-specific service so belongs in UI project. It does not contain any business logic and works\n/// with UI-specific types (view models and SelectListItem types).\n/// </summary>\npublic class CatalogViewModelService : ICatalogViewModelService\n{\n    private readonly ILogger<CatalogViewModelService> _logger;\n    private readonly IRepository<CatalogItem> _itemRepository;\n    private readonly IRepository<CatalogBrand> _brandRepository;\n    private readonly IRepository<CatalogType> _typeRepository;\n    private readonly IUriComposer _uriComposer;\n\n    public CatalogViewModelService(\n        ILoggerFactory loggerFactory,\n        IRepository<CatalogItem> itemRepository,\n        IRepository<CatalogBrand> brandRepository,\n        IRepository<CatalogType> typeRepository,\n        IUriComposer uriComposer)\n    {\n        _logger = loggerFactory.CreateLogger<CatalogViewModelService>();\n        _itemRepository = itemRepository;\n        _brandRepository = brandRepository;\n        _typeRepository = typeRepository;\n        _uriComposer = uriComposer;\n    }\n\n    public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId)\n    {\n        _logger.LogInformation(\"GetCatalogItems called.\");\n\n        var filterSpecification = new CatalogFilterSpecification(brandId, typeId);\n        var filterPaginatedSpecification =\n            new CatalogFilterPaginatedSpecification(itemsPage * pageIndex, itemsPage, brandId, typeId);\n\n        // the implementation below using ForEach and Count. We need a List.\n        var itemsOnPage = await _itemRepository.ListAsync(filterPaginatedSpecification);\n        var totalItems = await _itemRepository.CountAsync(filterSpecification);\n\n        var vm = new CatalogIndexViewModel()\n        {\n            CatalogItems = itemsOnPage.Select(i => new CatalogItemViewModel()\n            {\n                Id = i.Id,\n                Name = i.Name,\n                PictureUri = _uriComposer.ComposePicUri(i.PictureUri),\n                Price = i.Price\n            }).ToList(),\n            Brands = (await GetBrands()).ToList(),\n            Types = (await GetTypes()).ToList(),\n            BrandFilterApplied = brandId ?? 0,\n            TypesFilterApplied = typeId ?? 0,\n            PaginationInfo = new PaginationInfoViewModel()\n            {\n                ActualPage = pageIndex,\n                ItemsPerPage = itemsOnPage.Count,\n                TotalItems = totalItems,\n                TotalPages = int.Parse(Math.Ceiling(((decimal)totalItems / itemsPage)).ToString())\n            }\n        };\n\n        vm.PaginationInfo.Next = (vm.PaginationInfo.ActualPage == vm.PaginationInfo.TotalPages - 1) ? \"is-disabled\" : \"\";\n        vm.PaginationInfo.Previous = (vm.PaginationInfo.ActualPage == 0) ? \"is-disabled\" : \"\";\n\n        return vm;\n    }\n\n    public async Task<IEnumerable<SelectListItem>> GetBrands()\n    {\n        _logger.LogInformation(\"GetBrands called.\");\n        var brands = await _brandRepository.ListAsync();\n\n        var items = brands\n            .Select(brand => new SelectListItem() { Value = brand.Id.ToString(), Text = brand.Brand })\n            .OrderBy(b => b.Text)\n            .ToList();\n\n        var allItem = new SelectListItem() { Value = null, Text = \"All\", Selected = true };\n        items.Insert(0, allItem);\n\n        return items;\n    }\n\n    public async Task<IEnumerable<SelectListItem>> GetTypes()\n    {\n        _logger.LogInformation(\"GetTypes called.\");\n        var types = await _typeRepository.ListAsync();\n\n        var items = types\n            .Select(type => new SelectListItem() { Value = type.Id.ToString(), Text = type.Type })\n            .OrderBy(t => t.Text)\n            .ToList();\n\n        var allItem = new SelectListItem() { Value = null, Text = \"All\", Selected = true };\n        items.Insert(0, allItem);\n\n        return items;\n    }\n}\n"
  },
  {
    "path": "src/Web/SlugifyParameterTransformer.cs",
    "content": "﻿using System.Text.RegularExpressions;\nusing Microsoft.AspNetCore.Routing;\n\nnamespace Microsoft.eShopWeb.Web;\n\npublic class SlugifyParameterTransformer : IOutboundParameterTransformer\n{\n    public string? TransformOutbound(object? value)\n    {\n        if (value == null) { return null; }\n        string? str = value.ToString();\n        if (string.IsNullOrEmpty(str)) { return null; }\n\n        // Slugify value\n        return Regex.Replace(str, \"([a-z])([A-Z])\", \"$1-$2\").ToLower();\n    }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Account/LoginViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels.Account;\n\npublic class LoginViewModel\n{\n    [Required]\n    [EmailAddress]\n    public string? Email { get; set; }\n\n    [Required]\n    [DataType(DataType.Password)]\n    public string? Password { get; set; }\n\n    [Display(Name = \"Remember me?\")]\n    public bool RememberMe { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Account/LoginWith2faViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels.Account;\n\npublic class LoginWith2faViewModel\n{\n    [Required]\n    [StringLength(7, ErrorMessage = \"The {0} must be at least {2} and at max {1} characters long.\", MinimumLength = 6)]\n    [DataType(DataType.Text)]\n    [Display(Name = \"Authenticator code\")]\n    public string? TwoFactorCode { get; set; }\n\n    [Display(Name = \"Remember this machine\")]\n    public bool RememberMachine { get; set; }\n\n    public bool RememberMe { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Account/RegisterViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels.Account;\n\npublic class RegisterViewModel\n{\n    [Required]\n    [EmailAddress]\n    [Display(Name = \"Email\")]\n    public string? Email { get; set; }\n\n    [Required]\n    [StringLength(100, ErrorMessage = \"The {0} must be at least {2} characters long.\", MinimumLength = 6)]\n    [DataType(DataType.Password)]\n    [Display(Name = \"Password\")]\n    public string? Password { get; set; }\n\n    [DataType(DataType.Password)]\n    [Display(Name = \"Confirm password\")]\n    [Compare(\"Password\", ErrorMessage = \"The password and confirmation password do not match.\")]\n    public string? ConfirmPassword { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Account/ResetPasswordViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels.Account;\n\npublic class ResetPasswordViewModel\n{\n    [Required]\n    [EmailAddress]\n    public string? Email { get; set; }\n\n    [Required]\n    [StringLength(100, ErrorMessage = \"The {0} must be at least {2} and at max {1} characters long.\", MinimumLength = 6)]\n    [DataType(DataType.Password)]\n    public string? Password { get; set; }\n\n    [DataType(DataType.Password)]\n    [Display(Name = \"Confirm password\")]\n    [Compare(\"Password\", ErrorMessage = \"The password and confirmation password do not match.\")]\n    public string? ConfirmPassword { get; set; }\n\n    public string? Code { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/BasketComponentViewModel.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web.ViewModels;\n\npublic class BasketComponentViewModel\n{\n    public int ItemsCount { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/CatalogIndexViewModel.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc.Rendering;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels;\n\npublic class CatalogIndexViewModel\n{\n    public List<CatalogItemViewModel> CatalogItems { get; set; } = new List<CatalogItemViewModel>();\n    public List<SelectListItem>? Brands { get; set; } = new List<SelectListItem>();\n    public List<SelectListItem>? Types { get; set; } = new List<SelectListItem>();\n    public int? BrandFilterApplied { get; set; }\n    public int? TypesFilterApplied { get; set; }\n    public PaginationInfoViewModel? PaginationInfo { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/CatalogItemViewModel.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web.ViewModels;\n\npublic class CatalogItemViewModel\n{\n    public int Id { get; set; }\n    public string? Name { get; set; }\n    public string? PictureUri { get; set; }\n    public decimal Price { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/File/FileViewModel.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web.ViewModels.File;\n\npublic class FileViewModel\n{\n    public string? FileName { get; set; }\n    public string? Url { get; set; }\n    public string? DataBase64 { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Manage/ChangePasswordViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels.Manage;\n\npublic class ChangePasswordViewModel\n{\n    [Required]\n    [DataType(DataType.Password)]\n    [Display(Name = \"Current password\")]\n    public string? OldPassword { get; set; }\n\n    [Required]\n    [StringLength(100, ErrorMessage = \"The {0} must be at least {2} and at max {1} characters long.\", MinimumLength = 6)]\n    [DataType(DataType.Password)]\n    [Display(Name = \"New password\")]\n    public string? NewPassword { get; set; }\n\n    [DataType(DataType.Password)]\n    [Display(Name = \"Confirm new password\")]\n    [Compare(\"NewPassword\", ErrorMessage = \"The new password and confirmation password do not match.\")]\n    public string? ConfirmPassword { get; set; }\n\n    public string? StatusMessage { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Manage/EnableAuthenticatorViewModel.cs",
    "content": "﻿using System.ComponentModel;\nusing System.ComponentModel.DataAnnotations;\nusing Microsoft.AspNetCore.Mvc.ModelBinding;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels.Manage;\n\npublic class EnableAuthenticatorViewModel\n{\n    [Required]\n    [StringLength(7, ErrorMessage = \"The {0} must be at least {2} and at max {1} characters long.\", MinimumLength = 6)]\n    [DataType(DataType.Text)]\n    [Display(Name = \"Verification Code\")]\n    public string? Code { get; set; }\n\n    [BindNever]\n    public string? SharedKey { get; set; }\n\n    [BindNever]\n    public string? AuthenticatorUri { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Manage/ExternalLoginsViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Identity;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels.Manage;\n\npublic class ExternalLoginsViewModel\n{\n    public IList<UserLoginInfo>? CurrentLogins { get; set; }\n    public IList<AuthenticationScheme>? OtherLogins { get; set; }\n    public bool ShowRemoveButton { get; set; }\n    public string? StatusMessage { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Manage/IndexViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels.Manage;\n\npublic class IndexViewModel\n{\n    public string? Username { get; set; }\n\n    public bool IsEmailConfirmed { get; set; }\n\n    [Required]\n    [EmailAddress]\n    public string? Email { get; set; }\n\n    [Phone]\n    [Display(Name = \"Phone number\")]\n    public string? PhoneNumber { get; set; }\n\n    public string? StatusMessage { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Manage/RemoveLoginViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels.Manage;\n\npublic class RemoveLoginViewModel\n{\n    [Required]\n    public string LoginProvider { get; set; } = string.Empty;\n    [Required]\n    public string ProviderKey { get; set; } = string.Empty;\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Manage/SetPasswordViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels.Manage;\n\npublic class SetPasswordViewModel\n{\n    [Required]\n    [StringLength(100, ErrorMessage = \"The {0} must be at least {2} and at max {1} characters long.\", MinimumLength = 6)]\n    [DataType(DataType.Password)]\n    [Display(Name = \"New password\")]\n    public string? NewPassword { get; set; }\n\n    [DataType(DataType.Password)]\n    [Display(Name = \"Confirm new password\")]\n    [Compare(\"NewPassword\", ErrorMessage = \"The new password and confirmation password do not match.\")]\n    public string? ConfirmPassword { get; set; }\n\n    public string? StatusMessage { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/Manage/ShowRecoveryCodesViewModel.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web.ViewModels.Manage;\n\npublic class ShowRecoveryCodesViewModel\n{\n    public string[]? RecoveryCodes { get; set; }\n}\n\n"
  },
  {
    "path": "src/Web/ViewModels/Manage/TwoFactorAuthenticationViewModel.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web.ViewModels.Manage;\n\npublic class TwoFactorAuthenticationViewModel\n{\n    public bool HasAuthenticator { get; set; }\n    public int RecoveryCodesLeft { get; set; }\n    public bool Is2faEnabled { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/OrderDetailViewModel.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web.ViewModels;\n\npublic class OrderDetailViewModel : OrderViewModel\n{\n    public List<OrderItemViewModel> OrderItems { get; set; } = new();\n}\n"
  },
  {
    "path": "src/Web/ViewModels/OrderItemViewModel.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web.ViewModels;\n\npublic class OrderItemViewModel\n{\n    public int ProductId { get; set; }\n    public string? ProductName { get; set; }\n    public decimal UnitPrice { get; set; }\n    public decimal Discount => 0;\n    public int Units { get; set; }\n    public string? PictureUrl { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/OrderViewModel.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\nnamespace Microsoft.eShopWeb.Web.ViewModels;\n\npublic class OrderViewModel\n{\n    private const string DEFAULT_STATUS = \"Pending\";\n\n    public int OrderNumber { get; set; }\n    public DateTimeOffset OrderDate { get; set; }\n    public decimal Total { get; set; }\n    public string Status => DEFAULT_STATUS;\n    public Address? ShippingAddress { get; set; }\n}\n"
  },
  {
    "path": "src/Web/ViewModels/PaginationInfoViewModel.cs",
    "content": "﻿namespace Microsoft.eShopWeb.Web.ViewModels;\n\npublic class PaginationInfoViewModel\n{\n    public int TotalItems { get; set; }\n    public int ItemsPerPage { get; set; }\n    public int ActualPage { get; set; }\n    public int TotalPages { get; set; }\n    public string? Previous { get; set; }\n    public string? Next { get; set; }\n}\n"
  },
  {
    "path": "src/Web/Views/Account/Lockout.cshtml",
    "content": "@{\n    ViewData[\"Title\"] = \"Locked out\";\n}\n\n<header>\n    <h2 class=\"text-danger\">@ViewData[\"Title\"]</h2>\n    <p class=\"text-danger\">This account has been locked out, please try again later.</p>\n</header>\n"
  },
  {
    "path": "src/Web/Views/Account/LoginWith2fa.cshtml",
    "content": "@model LoginWith2faViewModel\n@{\n    ViewData[\"Title\"] = \"Two-factor authentication\";\n}\n\n<h2>@ViewData[\"Title\"]</h2>\n<hr />\n<p>Your login is protected with an authenticator app. Enter your authenticator code below.</p>\n<div class=\"row\">\n    <div class=\"col-md-4\">\n        <form method=\"post\" asp-route-returnUrl=\"@ViewData[\"ReturnUrl\"]\">\n            <input asp-for=\"RememberMe\" type=\"hidden\" />\n            <div asp-validation-summary=\"All\" class=\"text-danger\"></div>\n            <div class=\"form-group\">\n                <label asp-for=\"TwoFactorCode\"></label>\n                <input asp-for=\"TwoFactorCode\" class=\"form-control\" autocomplete=\"off\" />\n                <span asp-validation-for=\"TwoFactorCode\" class=\"text-danger\"></span>\n            </div>\n            <div class=\"form-group\">\n                <div class=\"checkbox\">\n                    <label asp-for=\"RememberMachine\">\n                        <input asp-for=\"RememberMachine\" />\n                        @Html.DisplayNameFor(m => m.RememberMachine)\n                    </label>\n                </div>\n            </div>\n            <div class=\"form-group\">\n                <button type=\"submit\" class=\"btn btn-default\">Log in</button>\n            </div>\n        </form>\n    </div>\n</div>\n<p>\n    Don't have access to your authenticator device? You can\n    <a asp-action=\"LoginWithRecoveryCode\" asp-route-returnUrl=\"@ViewData[\"ReturnUrl\"]\">log in with a recovery code</a>.\n</p>\n\n@section Scripts {\n    <partial name=\"_ValidationScriptsPartial\" />\n}"
  },
  {
    "path": "src/Web/Views/Manage/ChangePassword.cshtml",
    "content": "﻿@model ChangePasswordViewModel\n@{\n    ViewData[\"Title\"] = \"Change password\";\n    ViewData.AddActivePage(ManageNavPages.ChangePassword);\n}\n\n<h4>@ViewData[\"Title\"]</h4>\n<partial name=\"_StatusMessage\" for=\"StatusMessage\" />\n<div class=\"row\">\n    <div class=\"col-md-6\">\n        <form method=\"post\">\n            <div asp-validation-summary=\"All\" class=\"text-danger\"></div>\n            <div class=\"form-group\">\n                <label asp-for=\"OldPassword\"></label>\n                <input asp-for=\"OldPassword\" class=\"form-control\" />\n                <span asp-validation-for=\"OldPassword\" class=\"text-danger\"></span>\n            </div>\n            <div class=\"form-group\">\n                <label asp-for=\"NewPassword\"></label>\n                <input asp-for=\"NewPassword\" class=\"form-control\" />\n                <span asp-validation-for=\"NewPassword\" class=\"text-danger\"></span>\n            </div>\n            <div class=\"form-group\">\n                <label asp-for=\"ConfirmPassword\"></label>\n                <input asp-for=\"ConfirmPassword\" class=\"form-control\" />\n                <span asp-validation-for=\"ConfirmPassword\" class=\"text-danger\"></span>\n            </div>\n            <button type=\"submit\" class=\"btn btn-default\">Update password</button>\n        </form>\n    </div>\n</div>\n\n@section Scripts {\n    <partial name=\"_ValidationScriptsPartial\" />\n}\n"
  },
  {
    "path": "src/Web/Views/Manage/Disable2fa.cshtml",
    "content": "﻿@{\n    ViewData[\"Title\"] = \"Disable two-factor authentication (2FA)\";\n    ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);\n}\n\n<h2>@ViewData[\"Title\"]</h2>\n\n<div class=\"alert alert-warning\" role=\"alert\">\n    <p>\n        <span class=\"glyphicon glyphicon-warning-sign\"></span>\n        <strong>This action only disables 2FA.</strong>\n    </p>\n    <p>\n        Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key\n        used in an authenticator app you should <a asp-action=\"ResetAuthenticatorWarning\">reset your\n        authenticator keys.</a>\n    </p>\n</div>\n\n<div>\n    <form asp-action=\"Disable2fa\" method=\"post\" class=\"form-group\">\n        <button class=\"btn btn-danger\" type=\"submit\">Disable 2FA</button>\n    </form>\n</div>\n"
  },
  {
    "path": "src/Web/Views/Manage/EnableAuthenticator.cshtml",
    "content": "﻿@model EnableAuthenticatorViewModel\n@{\n    ViewData[\"Title\"] = \"Enable authenticator\";\n    ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);\n}\n\n<h4>@ViewData[\"Title\"]</h4>\n<div>\n    <p>To use an authenticator app go through the following steps:</p>\n    <ol class=\"list\">\n        <li>\n            <p>\n                Download a two-factor authenticator app like Microsoft Authenticator for\n                <a href=\"https://go.microsoft.com/fwlink/?Linkid=825071\">Windows Phone</a>,\n                <a href=\"https://go.microsoft.com/fwlink/?Linkid=825072\">Android</a> and\n                <a href=\"https://go.microsoft.com/fwlink/?Linkid=825073\">iOS</a> or\n                Google Authenticator for\n                <a href=\"https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=en\">Android</a> and\n                <a href=\"https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8\">iOS</a>.\n            </p>\n        </li>\n        <li>\n            <p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>\n            <div class=\"alert alert-info\">To enable QR code generation please read our <a href=\"https://go.microsoft.com/fwlink/?Linkid=852423\">documentation</a>.</div>\n            <div id=\"qrCode\"></div>\n            <div id=\"qrCodeData\" data-url=\"@Model.AuthenticatorUri\"></div>\n        </li>\n        <li>\n            <p>\n                Once you have scanned the QR code or input the key above, your two factor authentication app will provide you\n                with a unique code. Enter the code in the confirmation box below.\n            </p>\n            <div class=\"row\">\n                <div class=\"col-md-6\">\n                    <form method=\"post\">\n                        <div class=\"form-group\">\n                            <label asp-for=\"Code\" class=\"control-label\">Verification Code</label>\n                            <input asp-for=\"Code\" class=\"form-control\" autocomplete=\"off\" />\n                            <span asp-validation-for=\"Code\" class=\"text-danger\"></span>\n                        </div>\n                        <button type=\"submit\" class=\"btn btn-default\">Verify</button>\n                        <div asp-validation-summary=\"ModelOnly\" class=\"text-danger\"></div>\n                    </form>\n                </div>\n            </div>\n        </li>\n    </ol>\n</div>\n\n@section Scripts {\n    <partial name=\"_ValidationScriptsPartial\" />\n}\n"
  },
  {
    "path": "src/Web/Views/Manage/ExternalLogins.cshtml",
    "content": "﻿@model ExternalLoginsViewModel\n@{\n    ViewData[\"Title\"] = \"Manage your external logins\";\n    ViewData.AddActivePage(ManageNavPages.ExternalLogins);\n}\n<partial name=\"_StatusMessage\" for=\"StatusMessage\" />\n@if (Model.CurrentLogins?.Count > 0)\n{\n    <h4>Registered Logins</h4>\n    <table class=\"table\">\n        <tbody>\n            @foreach (var login in Model.CurrentLogins)\n            {\n                <tr>\n                    <td>@login.LoginProvider</td>\n                    <td>\n                        @if (Model.ShowRemoveButton)\n                        {\n                            <form asp-action=\"RemoveLogin\" method=\"post\">\n                                <div>\n                                    <input asp-for=\"@login.LoginProvider\" name=\"LoginProvider\" type=\"hidden\" />\n                                    <input asp-for=\"@login.ProviderKey\" name=\"ProviderKey\" type=\"hidden\" />\n                                    <button type=\"submit\" class=\"btn btn-default\" title=\"Remove this @login.LoginProvider login from your account\">Remove</button>\n                                </div>\n                            </form>\n                        }\n                        else\n                        {\n                            @: &nbsp;\n                        }\n                    </td>\n                </tr>\n            }\n        </tbody>\n    </table>\n}\n@if (Model.OtherLogins?.Count > 0)\n{\n    <h4>Add another service to log in.</h4>\n    <hr />\n    <form asp-action=\"LinkLogin\" method=\"post\" class=\"form-horizontal\">\n        <div id=\"socialLoginList\">\n            <p>\n                @foreach (var provider in Model.OtherLogins)\n                {\n                    <button type=\"submit\" class=\"btn btn-default\" name=\"provider\" value=\"@provider.Name\" title=\"Log in using your @provider.DisplayName account\">@provider.DisplayName</button>\n                }\n            </p>\n        </div>\n    </form>\n}\n"
  },
  {
    "path": "src/Web/Views/Manage/GenerateRecoveryCodes.cshtml",
    "content": "﻿@{\n    ViewData[\"Title\"] = \"Generate two-factor authentication (2FA) recovery codes\";\n    ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);\n}\n\n<h2>@ViewData[\"Title\"]</h2>\n\n<div class=\"alert alert-warning\" role=\"alert\">\n    <p>\n        <span class=\"glyphicon glyphicon-warning-sign\"></span>\n        <strong>This action generates new recovery codes.</strong>\n    </p>\n    <p>\n        If you lose your device and don't have the recovery codes you will lose access to your account.\n    </p>\n    <p>\n        Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key\n        used in an authenticator app you should <a asp-action=\"ResetAuthenticatorWarning\">reset your authenticator keys.</a>\n    </p>\n</div>\n\n<div>\n    <form asp-action=\"GenerateRecoveryCodes\" method=\"post\" class=\"form-group\">\n        <button class=\"btn btn-danger\" type=\"submit\">Generate Recovery Codes</button>\n    </form>\n</div>"
  },
  {
    "path": "src/Web/Views/Manage/ManageNavPages.cs",
    "content": "﻿using System;\nusing Microsoft.AspNetCore.Mvc.Rendering;\nusing Microsoft.AspNetCore.Mvc.ViewFeatures;\n\nnamespace Microsoft.eShopWeb.Web.Views.Manage;\n\npublic static class ManageNavPages\n{\n    public static string ActivePageKey => \"ActivePage\";\n\n    public static string Index => \"Index\";\n\n    public static string ChangePassword => \"ChangePassword\";\n\n    public static string ExternalLogins => \"ExternalLogins\";\n\n    public static string TwoFactorAuthentication => \"TwoFactorAuthentication\";\n\n    public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);\n\n    public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);\n\n    public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);\n\n    public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);\n\n    public static string PageNavClass(ViewContext viewContext, string page)\n    {\n        var activePage = viewContext.ViewData[\"ActivePage\"] as string;\n        return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? \"active\" : string.Empty;\n    }\n\n    public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage;\n}\n"
  },
  {
    "path": "src/Web/Views/Manage/MyAccount.cshtml",
    "content": "﻿@model IndexViewModel\n@{\n    ViewData[\"Title\"] = \"Profile\";\n    ViewData.AddActivePage(ManageNavPages.Index);\n}\n\n<h4>@ViewData[\"Title\"]</h4>\n<partial name=\"_StatusMessage\" for=\"StatusMessage\" />\n<div class=\"row\">\n    <div class=\"col-md-6\">\n        <form method=\"post\">\n            <div asp-validation-summary=\"All\" class=\"text-danger\"></div>\n            <div class=\"form-group\">\n                <label asp-for=\"Username\"></label>\n                <input asp-for=\"Username\" class=\"form-control\" disabled />\n            </div>\n            <div class=\"form-group\">\n                <label asp-for=\"Email\"></label>\n                @if (Model.IsEmailConfirmed)\n                {\n                <div class=\"input-group\">\n                    <input asp-for=\"Email\" class=\"form-control\" />\n                    <span class=\"input-group-addon\" aria-hidden=\"true\"><span class=\"glyphicon glyphicon-ok text-success\"></span></span>\n                </div>\n                }\n                else\n                {\n                <input asp-for=\"Email\" class=\"form-control\" />\n                <button asp-action=\"SendVerificationEmail\" class=\"btn btn-link\">Send verification email</button>\n                }\n                <span asp-validation-for=\"Email\" class=\"text-danger\"></span>\n            </div>\n            <div class=\"form-group\">\n                <label asp-for=\"PhoneNumber\"></label>\n                <input asp-for=\"PhoneNumber\" class=\"form-control\" />\n                <span asp-validation-for=\"PhoneNumber\" class=\"text-danger\"></span>\n            </div>\n            <button type=\"submit\" class=\"btn btn-default\">Save</button>\n        </form>\n    </div>\n</div>\n\n@section Scripts {\n    <partial name=\"_ValidationScriptsPartial\" />\n}\n"
  },
  {
    "path": "src/Web/Views/Manage/ResetAuthenticator.cshtml",
    "content": "﻿@{\n    ViewData[\"Title\"] = \"Reset authenticator key\";\n    ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);\n}\n\n<h4>@ViewData[\"Title\"]</h4>\n<div class=\"alert alert-warning\" role=\"alert\">\n    <p>\n        <span class=\"glyphicon glyphicon-warning-sign\"></span>\n        <strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong>\n    </p>\n    <p>\n        This process disables 2FA until you verify your authenticator app and will also reset your 2FA recovery codes.\n        If you do not complete your authenticator app configuration you may lose access to your account.\n    </p>\n</div>\n<div>\n    <form asp-action=\"ResetAuthenticator\" method=\"post\" class=\"form-group\">\n        <button class=\"btn btn-danger\" type=\"submit\">Reset authenticator key</button>\n    </form>\n</div>"
  },
  {
    "path": "src/Web/Views/Manage/SetPassword.cshtml",
    "content": "﻿@model SetPasswordViewModel\n@{\n    ViewData[\"Title\"] = \"Set password\";\n    ViewData.AddActivePage(ManageNavPages.ChangePassword);\n}\n\n<h4>Set your password</h4>\n<partial name=\"_StatusMessage\" for=\"StatusMessage\" />\n<p class=\"text-info\">\n    You do not have a local username/password for this site. Add a local\n    account so you can log in without an external login.\n</p>\n<div class=\"row\">\n    <div class=\"col-md-6\">\n        <form method=\"post\">\n            <div asp-validation-summary=\"All\" class=\"text-danger\"></div>\n            <div class=\"form-group\">\n                <label asp-for=\"NewPassword\"></label>\n                <input asp-for=\"NewPassword\" class=\"form-control\" />\n                <span asp-validation-for=\"NewPassword\" class=\"text-danger\"></span>\n            </div>\n            <div class=\"form-group\">\n                <label asp-for=\"ConfirmPassword\"></label>\n                <input asp-for=\"ConfirmPassword\" class=\"form-control\" />\n                <span asp-validation-for=\"ConfirmPassword\" class=\"text-danger\"></span>\n            </div>\n            <button type=\"submit\" class=\"btn btn-default\">Set password</button>\n        </form>\n    </div>\n</div>\n\n@section Scripts {\n    <partial name=\"_ValidationScriptsPartial\" />\n}\n"
  },
  {
    "path": "src/Web/Views/Manage/ShowRecoverCodes.cshtml",
    "content": "﻿@model ShowRecoveryCodesViewModel\n@{\n    ViewData[\"Title\"] = \"Recovery codes\";\n    ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);\n}\n\n<h4>@ViewData[\"Title\"]</h4>\n<div class=\"alert alert-warning\" role=\"alert\">\n    <p>\n        <span class=\"glyphicon glyphicon-warning-sign\"></span>\n        <strong>Put these codes in a safe place.</strong>\n    </p>\n    <p>\n        If you lose your device and don't have the recovery codes you will lose access to your account.\n    </p>\n</div>\n<div class=\"row\">\n    <div class=\"col-md-12\">\n        @if (Model.RecoveryCodes != null)\n        {\n            @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)\n            {\n                <code>@Model.RecoveryCodes[row]</code><text>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />\n            }\n        }\n    </div>\n</div>\n© 2023 GitHub, Inc."
  },
  {
    "path": "src/Web/Views/Manage/TwoFactorAuthentication.cshtml",
    "content": "@model TwoFactorAuthenticationViewModel\n@{\n    ViewData[\"Title\"] = \"Two-factor authentication\";\n    ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);\n}\n\n<h4>@ViewData[\"Title\"]</h4>\n@if (Model.Is2faEnabled)\n{\n    if (Model.RecoveryCodesLeft == 0)\n    {\n        <div class=\"alert alert-danger\">\n            <strong>You have no recovery codes left.</strong>\n            <p>You must <a asp-action=\"GenerateRecoveryCodes\">generate a new set of recovery codes</a> before you can log in with a recovery code.</p>\n        </div>\n    }\n    else if (Model.RecoveryCodesLeft == 1)\n    {\n        <div class=\"alert alert-danger\">\n            <strong>You have 1 recovery code left.</strong>\n            <p>You can <a asp-action=\"GenerateRecoveryCodes\">generate a new set of recovery codes</a>.</p>\n        </div>\n    }\n    else if (Model.RecoveryCodesLeft <= 3)\n    {\n        <div class=\"alert alert-warning\">\n            <strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong>\n            <p>You should <a asp-action=\"GenerateRecoveryCodes\">generate a new set of recovery codes</a>.</p>\n        </div>\n    }\n\n    <a asp-action=\"Disable2faWarning\" class=\"btn btn-default\">Disable 2FA</a>\n    <a asp-action=\"GenerateRecoveryCodesWarning\" class=\"btn btn-default\">Reset recovery codes</a>\n}\n\n<h5>Authenticator app</h5>\n@if (!Model.HasAuthenticator)\n{\n    <a asp-action=\"EnableAuthenticator\" class=\"btn btn-default\">Add authenticator app</a>\n}\nelse\n{\n    <a asp-action=\"EnableAuthenticator\" class=\"btn btn-default\">Configure authenticator app</a>\n    <a asp-action=\"ResetAuthenticatorWarning\" class=\"btn btn-default\">Reset authenticator key</a>\n}\n\n@section Scripts {\n    <partial name=\"_ValidationScriptsPartial\" />\n}\n"
  },
  {
    "path": "src/Web/Views/Manage/_Layout.cshtml",
    "content": "﻿@{ \n    Layout = \"/Views/Shared/_Layout.cshtml\";\n}\n\n<div class=\"container\">\n    <h2>Manage your account</h2>\n    <h4>Change your account settings</h4>\n    <hr />\n    <div class=\"row\">\n        <div class=\"col-md-3\">\n            <partial name=\"_ManageNav\" />\n        </div>\n        <div class=\"col-md-9\">\n            @RenderBody()\n        </div>\n    </div>\n</div>\n\n@section Scripts {\n    @RenderSection(\"Scripts\", required: false)\n}\n        \n"
  },
  {
    "path": "src/Web/Views/Manage/_ManageNav.cshtml",
    "content": "﻿@inject SignInManager<ApplicationUser> SignInManager\n@{\n    var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();\n}\n\n<ul class=\"nav nav-pills nav-stacked\">\n    <li class=\"@ManageNavPages.IndexNavClass(ViewContext)\"><a asp-action=\"MyAccount\">Profile</a></li>\n    <li class=\"@ManageNavPages.ChangePasswordNavClass(ViewContext)\"><a asp-action=\"ChangePassword\">Password</a></li>\n    @if (hasExternalLogins)\n    {\n        <li class=\"@ManageNavPages.ExternalLoginsNavClass(ViewContext)\"><a asp-action=\"ExternalLogins\">External logins</a></li>\n    }\n    <li class=\"@ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)\"><a asp-action=\"TwoFactorAuthentication\">Two-factor authentication</a></li>\n</ul>\n\n"
  },
  {
    "path": "src/Web/Views/Manage/_StatusMessage.cshtml",
    "content": "﻿@model string\n\n@if (!String.IsNullOrEmpty(Model))\n{\n    var statusMessageClass = Model.StartsWith(\"Error\") ? \"danger\" : \"success\";\n    <div class=\"alert alert-@statusMessageClass alert-dismissible\" role=\"alert\">\n        <button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button>\n        @Model\n    </div>\n}\n"
  },
  {
    "path": "src/Web/Views/Manage/_ViewImports.cshtml",
    "content": "﻿@using Microsoft.eShopWeb.Web.Views.Manage"
  },
  {
    "path": "src/Web/Views/Order/Detail.cshtml",
    "content": "﻿@model OrderDetailViewModel\n@{\n    ViewData[\"Title\"] = \"My Order History\";\n}\n@{\n    ViewData[\"Title\"] = \"Order Detail\";\n}\n\n<div class=\"esh-orders-detail\">\n    <div class=\"container\">\n        <section class=\"esh-orders-detail-section\">\n            <article class=\"esh-orders-detail-titles row\">\n                <section class=\"esh-orders-detail-title col-xs-3\">Order number</section>\n                <section class=\"esh-orders-detail-title col-xs-3\">Date</section>\n                <section class=\"esh-orders-detail-title col-xs-2\">Total</section>\n                <section class=\"esh-orders-detail-title col-xs-3\">Status</section>\n            </article>\n\n            <article class=\"esh-orders-detail-items row\">\n                <section class=\"esh-orders-detail-item col-xs-3\">@Model.OrderNumber</section>\n                <section class=\"esh-orders-detail-item col-xs-3\">@Model.OrderDate</section>\n                <section class=\"esh-orders-detail-item col-xs-2\">$@Model.Total.ToString(\"N2\")</section>\n                <section class=\"esh-orders-detail-item col-xs-3\">@Model.Status</section>\n            </article>\n        </section>\n\n        <section class=\"esh-orders-detail-section\">\n            <article class=\"esh-orders-detail-titles row\">\n                <section class=\"esh-orders-detail-title col-xs-12\">Shipping Address</section>\n            </article>\n\n            <article class=\"esh-orders-detail-items row\">\n                <section class=\"esh-orders-detail-item col-xs-12\">@Model.ShippingAddress?.Street</section>\n            </article>\n\n            <article class=\"esh-orders-detail-items row\">\n                <section class=\"esh-orders-detail-item col-xs-12\">@Model.ShippingAddress?.City</section>\n            </article>\n\n            <article class=\"esh-orders-detail-items row\">\n                <section class=\"esh-orders-detail-item col-xs-12\">@Model.ShippingAddress?.Country</section>\n            </article>\n        </section>\n\n        <section class=\"esh-orders-detail-section\">\n            <article class=\"esh-orders-detail-titles row\">\n                <section class=\"esh-orders-detail-title col-xs-12\">ORDER DETAILS</section>\n            </article>\n\n            @for (int i = 0; i < Model.OrderItems.Count; i++)\n            {\n                var item = Model.OrderItems[i];\n                <article class=\"esh-orders-detail-items esh-orders-detail-items--border row\">\n                    <section class=\"esh-orders-detail-item col-md-4 hidden-md-down\">\n                        <img class=\"esh-orders-detail-image\" src=\"@item.PictureUrl\">\n                    </section>\n                    <section class=\"esh-orders-detail-item esh-orders-detail-item--middle col-xs-3\">@item.ProductName</section>\n                    <section class=\"esh-orders-detail-item esh-orders-detail-item--middle col-xs-1\">$ @item.UnitPrice.ToString(\"N2\")</section>\n                    <section class=\"esh-orders-detail-item esh-orders-detail-item--middle col-xs-1\">@item.Units</section>\n                    <section class=\"esh-orders-detail-item esh-orders-detail-item--middle col-xs-2\">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString(\"N2\")</section>\n                </article>\n            }\n        </section>\n\n        <section class=\"esh-orders-detail-section esh-orders-detail-section--right\">\n            <article class=\"esh-orders-detail-titles esh-basket-titles--clean row\">\n                <section class=\"esh-orders-detail-title col-xs-9\"></section>\n                <section class=\"esh-orders-detail-title col-xs-2\">TOTAL</section>\n            </article>\n\n            <article class=\"esh-orders-detail-items row\">\n                <section class=\"esh-orders-detail-item col-xs-9\"></section>\n                <section class=\"esh-orders-detail-item esh-orders-detail-item--mark col-xs-2\">$ @Model.Total.ToString(\"N2\")</section>\n            </article>\n        </section>\n    </div>\n</div>\n"
  },
  {
    "path": "src/Web/Views/Order/MyOrders.cshtml",
    "content": "﻿@model IEnumerable<OrderViewModel>\n@{\n    ViewData[\"Title\"] = \"My Order History\";\n}\n\n<div class=\"esh-orders\">\n    <div class=\"container\">\n        <h1>@ViewData[\"Title\"]</h1>\n        <article class=\"esh-orders-titles row\">\n            <section class=\"esh-orders-title col-xs-2\">Order number</section>\n            <section class=\"esh-orders-title col-xs-4\">Date</section>\n            <section class=\"esh-orders-title col-xs-2\">Total</section>\n            <section class=\"esh-orders-title col-xs-2\">Status</section>\n            <section class=\"esh-orders-title col-xs-2\"></section>\n        </article>\n        @if (Model != null && Model.Any())\n        {\n            @foreach (var item in Model)\n            {\n                <article class=\"esh-orders-items row\">\n                    <section class=\"esh-orders-item col-xs-2\">@Html.DisplayFor(modelItem => item.OrderNumber)</section>\n                    <section class=\"esh-orders-item col-xs-4\">@Html.DisplayFor(modelItem => item.OrderDate)</section>\n                    <section class=\"esh-orders-item col-xs-2\">$ @Html.DisplayFor(modelItem => item.Total)</section>\n                    <section class=\"esh-orders-item col-xs-2\">@Html.DisplayFor(modelItem => item.Status)</section>\n                    <section class=\"esh-orders-item col-xs-1\">\n                        <a class=\"esh-orders-link\" asp-controller=\"Order\" asp-action=\"Detail\" asp-route-orderId=\"@item.OrderNumber\">Detail</a>\n                    </section>\n                    <section class=\"esh-orders-item col-xs-1\">\n                        @if (item.Status.ToLower() == \"submitted\")\n                    {\n                            <a class=\"esh-orders-link\" asp-controller=\"Order\" asp-action=\"cancel\" asp-route-orderId=\"@item.OrderNumber\">Cancel</a>\n                        }\n                    </section>\n                </article>\n            }\n        }\n    </div>\n</div>\n"
  },
  {
    "path": "src/Web/Views/Shared/Components/Basket/Default.cshtml",
    "content": "﻿@model BasketComponentViewModel\n\n@{\n    ViewData[\"Title\"] = \"My Basket\";\n}\n\n<a class=\"esh-basketstatus\" asp-page=\"/Basket/Index\">\n        <div class=\"esh-basketstatus-image\">\n            <img src=\"~/images/cart.png\" />\n        </div>\n        <div class=\"esh-basketstatus-badge\">\n            @Model.ItemsCount\n        </div>\n</a>\n"
  },
  {
    "path": "src/Web/Views/Shared/Error.cshtml",
    "content": "﻿@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.\n</p>\n<p>\n    <strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.\n</p>\n"
  },
  {
    "path": "src/Web/Views/Shared/_CookieConsentPartial.cshtml",
    "content": "﻿@using Microsoft.AspNetCore.Http.Features\n\n@{\n    var consentFeature = Context.Features.Get<ITrackingConsentFeature>();\n    var showBanner = !consentFeature?.CanTrack ?? false;\n    var cookieString = consentFeature?.CreateConsentCookie();\n}\n\n@if (showBanner)\n{\n    <div id=\"cookieConsent\" class=\"alert alert-info alert-dismissible fade show\" role=\"alert\">\n        Use this space to summarize your privacy and cookie use policy. <a asp-area=\"\" asp-controller=\"Home\" asp-action=\"Privacy\">Learn More</a>.\n        <button type=\"button\" class=\"accept-policy close\" data-dismiss=\"alert\" aria-label=\"Close\" data-cookie-string=\"@cookieString\">\n            <span aria-hidden=\"true\">Accept</span>\n        </button>\n    </div>\n    <script>\n        (function () {\n            var button = document.querySelector(\"#cookieConsent button[data-cookie-string]\");\n            button.addEventListener(\"click\", function (event) {\n                document.cookie = button.dataset.cookieString;\n            }, false);\n        })();\n    </script>\n}\n"
  },
  {
    "path": "src/Web/Views/Shared/_Layout.cshtml",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>@ViewData[\"Title\"] - Microsoft.eShopOnWeb</title>\n    <environment names=\"Development,Docker\">\n        <link rel=\"stylesheet\" href=\"~/lib/bootstrap/dist/css/bootstrap.css\" />\n        <link rel=\"stylesheet\" href=\"~/css/app.css\" />\n        <link rel=\"stylesheet\" href=\"~/css/app.component.css\" />\n        <link rel=\"stylesheet\" href=\"~/css/shared/components/header/header.css\" />\n        <link rel=\"stylesheet\" href=\"~/css/shared/components/identity/identity.css\" />\n        <link rel=\"stylesheet\" href=\"~/css/shared/components/pager/pager.css\" />\n        <link rel=\"stylesheet\" href=\"~/css/basket/basket.component.css\" />\n        <link rel=\"stylesheet\" href=\"~/css/basket/basket-status/basket-status.component.css\" />\n        <link rel=\"stylesheet\" href=\"~/css/catalog/catalog.component.css\" />\n        <link rel=\"stylesheet\" href=\"~/css/orders/orders.component.css\" />\n    </environment>\n    <environment names=\"Staging,Production\">\n        <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.5/css/bootstrap.min.css\"\n              asp-fallback-href=\"~/lib/bootstrap/dist/css/bootstrap.min.css\"\n              asp-fallback-test-class=\"sr-only\" asp-fallback-test-property=\"position\" asp-fallback-test-value=\"absolute\" />\n        <link rel=\"stylesheet\" href=\"~/css/site.min.css\" asp-append-version=\"true\" />\n    </environment>\n</head>\n<body>\n    <div class=\"esh-app-wrapper\">\n        <header class=\"esh-app-header\">\n            <div class=\"container\">\n                <article class=\"row\">\n                    <section class=\"col-lg-7 col-md-6 col-xs-12\">\n                        <a asp-area=\"\" asp-page=\"/Index\">\n                            <img src=\"~/images/brand.png\" alt=\"eShop On Web\" />\n                        </a>\n                    </section>\n                    <partial name=\"_LoginPartial\" />\n                </article>\n            </div>\n        </header>\n        @RenderBody()\n        <footer class=\"esh-app-footer footer\">\n            <div class=\"container\">\n                <article class=\"row\">\n                    <section class=\"col-sm-6\"></section>\n                    <section class=\"col-sm-6\">\n                        <div class=\"esh-app-footer-text hidden-xs\"> e-ShopOnWeb. All rights reserved </div>\n                    </section>\n                </article>\n            </div>\n        </footer>\n    </div>\n    <environment names=\"Development,Docker\">\n        <script src=\"~/lib/jquery/jquery.js\"></script>\n        <script src=\"~/lib/bootstrap/dist/js/bootstrap.js\"></script>\n        <script src=\"~/js/site.js\" asp-append-version=\"true\"></script>\n    </environment>\n    <environment names=\"Staging,Production\">\n        <script src=\"https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js\"\n                asp-fallback-src=\"~/lib/jquery/dist/jquery.min.js\"\n                asp-fallback-test=\"window.jQuery\">\n        </script>\n        <script src=\"https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/bootstrap.min.js\"\n                asp-fallback-src=\"~/lib/bootstrap/dist/js/bootstrap.min.js\"\n                asp-fallback-test=\"window.jQuery && window.jQuery.fn && window.jQuery.fn.modal\">\n        </script>\n        <script src=\"~/js/site.min.js\" asp-append-version=\"true\"></script>\n    </environment>\n    @RenderSection(\"scripts\", required: false)\n</body>\n</html>\n"
  },
  {
    "path": "src/Web/Views/Shared/_LoginPartial.cshtml",
    "content": "﻿@if (Context!.User!.Identity!.IsAuthenticated)\n{\n    <section class=\"col-lg-4 col-md-5 col-xs-12\">\n        <div class=\"esh-identity\">\n            <form asp-area=\"Identity\" asp-page=\"/Account/Logout\" method=\"post\" id=\"logoutForm\" class=\"navbar-right\">\n                <section class=\"esh-identity-section\">\n                    <div class=\"esh-identity-name\">@Context.User.Identity.Name</div>\n                    <img class=\"esh-identity-image\" src=\"~/images/arrow-down.png\">\n                </section>\n                <section class=\"esh-identity-drop\">\n                    @if (User.IsInRole(\"Administrators\"))\n                    {\n                        <a class=\"esh-identity-item\"\n                           asp-page=\"/Admin/Index\">\n                            <div class=\"esh-identity-name esh-identity-name--upper\">Admin</div>\n                        </a>\n                    }\n                    <a class=\"esh-identity-item\"\n                       asp-controller=\"Order\"\n                       asp-action=\"MyOrders\">\n                        <div class=\"esh-identity-name esh-identity-name--upper\">My orders</div>\n                    </a>\n                    <a class=\"esh-identity-item\"\n                       asp-controller=\"Manage\"\n                       asp-action=\"MyAccount\">\n                        <div class=\"esh-identity-name esh-identity-name--upper\">My account</div>\n                    </a>\n                    <a class=\"esh-identity-item\" href=\"javascript:document.getElementById('logoutForm').submit()\">\n                        <div class=\"esh-identity-name esh-identity-name--upper\">Log Out</div>\n                        <img class=\"esh-identity-image\" src=\"~/images/logout.png\">\n                    </a>\n                </section>\n            </form>\n        </div>\n    </section>\n\n    <section class=\"col-lg-1 col-xs-12\">\n        @await Component.InvokeAsync(\"Basket\")\n    </section>\n\n}\nelse\n{\n    <section class=\"col-lg-4 col-md-5 col-xs-12\">\n        <div class=\"esh-identity\">\n            <section class=\"esh-identity-section\">\n                <div class=\"esh-identity-item\">\n\n                    <a asp-area=\"Identity\" asp-page=\"/Account/Login\" class=\"esh-identity-name esh-identity-name--upper\">\n                        Login\n                    </a>\n                </div>\n            </section>\n        </div>\n    </section>\n\n    <section class=\"col-lg-1 col-xs-12\">\n        @await Component.InvokeAsync(\"Basket\")\n    </section>\n}\n"
  },
  {
    "path": "src/Web/Views/Shared/_ValidationScriptsPartial.cshtml",
    "content": "﻿<environment names=\"Development,Docker\">\n    <script src=\"~/lib/jquery-validation/dist/jquery.validate.js\"></script>\n    <script src=\"~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js\"></script>\n</environment>\n<environment names=\"Staging,Production\">\n    <script src=\"https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js\"\n            asp-fallback-src=\"~/lib/jquery-validation/dist/jquery.validate.min.js\"\n            asp-fallback-test=\"window.jQuery && window.jQuery.validator\"\n            crossorigin=\"anonymous\"\n            integrity=\"sha384-Fnqn3nxp3506LP/7Y3j/25BlWeA3PXTyT1l78LjECcPaKCV12TsZP7yyMxOe/G/k\">\n    </script>\n    <script src=\"https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js\"\n            asp-fallback-src=\"~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js\"\n            asp-fallback-test=\"window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive\"\n            crossorigin=\"anonymous\"\n            integrity=\"sha384-JrXK+k53HACyavUKOsL+NkmSesD2P+73eDMrbTtTk0h4RmOF8hF8apPlkp26JlyH\">\n    </script>\n</environment>\n"
  },
  {
    "path": "src/Web/Views/_ViewImports.cshtml",
    "content": "﻿@using Microsoft.eShopWeb.Web\n@using Microsoft.eShopWeb.Web.ViewModels\n@using Microsoft.eShopWeb.Web.ViewModels.Account\n@using Microsoft.eShopWeb.Web.ViewModels.Manage\n@using Microsoft.eShopWeb.Web.Pages\n@using Microsoft.AspNetCore.Identity\n@using Microsoft.eShopWeb.Infrastructure.Identity\n@namespace Microsoft.eShopWeb.Web.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "src/Web/Views/_ViewStart.cshtml",
    "content": "﻿@{\n    Layout = \"_Layout\";\n}\n"
  },
  {
    "path": "src/Web/Web.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <Nullable>enable</Nullable>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <RootNamespace>Microsoft.eShopWeb.Web</RootNamespace>\n    <UserSecretsId>aspnet-Web2-1FA3F72E-E7E3-4360-9E49-1CCCD7FE85F7</UserSecretsId>\n    <LangVersion>latest</LangVersion>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <Content Remove=\"compilerconfig.json\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Ardalis.ListStartupServices\" />\n    <PackageReference Include=\"Ardalis.Specification\" />\n    <PackageReference Include=\"AutoMapper.Extensions.Microsoft.DependencyInjection\" />\n    <PackageReference Include=\"Azure.Extensions.AspNetCore.Configuration.Secrets\" />\n    <PackageReference Include=\"Azure.Identity\" />\n    <PackageReference Include=\"MediatR\" />\n    <PackageReference Include=\"BuildBundlerMinifier\" Condition=\"'$(Configuration)'=='Release'\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Components.WebAssembly.Server\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" />\n    <PackageReference Include=\"Microsoft.Web.LibraryManager.Build\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.EntityFrameworkCore\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.UI\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Authentication.JwtBearer\" />\n    <PackageReference Include=\"Microsoft.Azure.AppConfiguration.AspNetCore\" />\n    <PackageReference Include=\"Microsoft.FeatureManagement.AspNetCore\" />\n    <PackageReference Include=\"System.IdentityModel.Tokens.Jwt\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n  </ItemGroup>\n  <ItemGroup>\n    <Folder Include=\"wwwroot\\fonts\\\" />\n    <Folder Include=\"wwwroot\\lib\\\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\ApplicationCore\\ApplicationCore.csproj\" />\n    <ProjectReference Include=\"..\\BlazorAdmin\\BlazorAdmin.csproj\" />\n    <ProjectReference Include=\"..\\BlazorShared\\BlazorShared.csproj\" />\n    <ProjectReference Include=\"..\\Infrastructure\\Infrastructure.csproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"compilerconfig.json\" />\n    <None Include=\"wwwroot\\images\\products\\1.png\" />\n    <None Include=\"wwwroot\\images\\products\\10.png\" />\n    <None Include=\"wwwroot\\images\\products\\11.png\" />\n    <None Include=\"wwwroot\\images\\products\\12.png\" />\n    <None Include=\"wwwroot\\images\\products\\2.png\" />\n    <None Include=\"wwwroot\\images\\products\\3.png\" />\n    <None Include=\"wwwroot\\images\\products\\4.png\" />\n    <None Include=\"wwwroot\\images\\products\\5.png\" />\n    <None Include=\"wwwroot\\images\\products\\6.png\" />\n    <None Include=\"wwwroot\\images\\products\\7.png\" />\n    <None Include=\"wwwroot\\images\\products\\8.png\" />\n    <None Include=\"wwwroot\\images\\products\\9.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Content Update=\"appsettings.json\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Content>\n    <Content Update=\"Views\\Shared\\Error.cshtml\">\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n    <Content Update=\"Views\\Shared\\_Layout.cshtml\">\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n    <Content Update=\"Views\\Shared\\_LoginPartial.cshtml\">\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n    <Content Update=\"Views\\Shared\\_ValidationScriptsPartial.cshtml\">\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n    <Content Update=\"Views\\_ViewImports.cshtml\">\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n    <Content Update=\"Views\\_ViewStart.cshtml\">\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/Web/appsettings.Development.json",
    "content": "{\n  \"baseUrls\": {\n    \"apiBase\": \"https://localhost:5099/api/\",\n    \"webBase\": \"https://localhost:44315/\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "src/Web/appsettings.Docker.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"CatalogConnection\": \"Server=sqlserver,1433;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;User Id=sa;Password=@someThingComplicated1234;Trusted_Connection=false;TrustServerCertificate=true;\",\n    \"IdentityConnection\": \"Server=sqlserver,1433;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;User Id=sa;Password=@someThingComplicated1234;Trusted_Connection=false;TrustServerCertificate=true;\"\n  },\n  \"baseUrls\": {\n    \"apiBase\": \"http://localhost:5200/api/\",\n    \"webBase\": \"http://host.docker.internal:5106/\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "src/Web/appsettings.json",
    "content": "﻿{\n  \"baseUrls\": {\n    \"apiBase\": \"https://localhost:5099/api/\",\n    \"webBase\": \"https://localhost:44315/\"\n  },\n  \"ConnectionStrings\": {\n    \"CatalogConnection\": \"Server=(localdb)\\\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;\",\n    \"IdentityConnection\": \"Server=(localdb)\\\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;\"\n  },\n  \"CatalogBaseUrl\": \"\",\n  \"Logging\": {\n    \"IncludeScopes\": false,\n    \"LogLevel\": {\n      \"Default\": \"Warning\",\n      \"Microsoft\": \"Warning\",\n      \"System\": \"Warning\"\n    },\n    \"AllowedHosts\": \"*\"\n  },\n\n  \"eShopWeb\": {\n    \"Settings\": {\n      \"NoResultsMessage\": \"THERE ARE NO RESULTS THAT MATCH YOUR SEARCH\"\n    }\n  },\n  \"UseAppConfig\": false,\n  \"AppConfigEndpoint\": \"\"\n}\n"
  },
  {
    "path": "src/Web/bundleconfig.json",
    "content": "[\n  {\n    \"outputFileName\": \"wwwroot/css/site.min.css\",\n    \"inputFiles\": [\n      \"wwwroot/css/app.css\",\n      \"wwwroot/css/app.component.css\",\n      \"wwwroot/css/shared/components/header/header.css\",\n      \"wwwroot/css/shared/components/identity/identity.css\",\n      \"wwwroot/css/shared/components/pager/pager.css\",\n      \"wwwroot/css/basket/basket.component.css\",\n      \"wwwroot/css/basket/basket-status/basket-status.component.css\",\n      \"wwwroot/css/catalog/catalog.component.css\",\n      \"wwwroot/css/orders/orders.component.css\"\n    ]\n  },\n  {\n    \"outputFileName\": \"wwwroot/js/site.min.js\",\n    \"inputFiles\": [\n      \"wwwroot/js/site.js\"\n    ],\n    \"minify\": {\n      \"enabled\": true,\n      \"renameLocals\": true\n    },\n    \"sourceMap\": false\n  }\n]"
  },
  {
    "path": "src/Web/compilerconfig.json",
    "content": "﻿[\n  {\n    \"outputFile\": \"wwwroot/css/shared/components/header/header.css\",\n    \"inputFile\": \"wwwroot/css/shared/components/header/header.scss\"\n  },\n  {\n    \"outputFile\": \"wwwroot/css/orders/orders.component.css\",\n    \"inputFile\": \"wwwroot/css/orders/orders.component.scss\"\n  },\n  {\n    \"outputFile\": \"wwwroot/css/catalog/catalog.component.css\",\n    \"inputFile\": \"wwwroot/css/catalog/catalog.component.scss\"\n  },\n  {\n    \"outputFile\": \"wwwroot/css/basket/basket.component.css\",\n    \"inputFile\": \"wwwroot/css/basket/basket.component.scss\"\n  },\n  {\n    \"outputFile\": \"wwwroot/css/basket/basket-status/basket-status.component.css\",\n    \"inputFile\": \"wwwroot/css/basket/basket-status/basket-status.component.scss\"\n  },\n  {\n    \"outputFile\": \"wwwroot/css/app.component.css\",\n    \"inputFile\": \"wwwroot/css/app.component.scss\"\n  },\n  {\n    \"outputFile\": \"wwwroot/css/_variables.css\",\n    \"inputFile\": \"wwwroot/css/_variables.scss\"\n  },\n  {\n    \"outputFile\": \"wwwroot/css/shared/components/pager/pager.css\",\n    \"inputFile\": \"wwwroot/css/shared/components/pager/pager.scss\"\n  }\n]"
  },
  {
    "path": "src/Web/compilerconfig.json.defaults",
    "content": "{\n  \"compilers\": {\n    \"less\": {\n      \"autoPrefix\": \"\",\n      \"cssComb\": \"none\",\n      \"ieCompat\": true,\n      \"strictMath\": false,\n      \"strictUnits\": false,\n      \"relativeUrls\": true,\n      \"rootPath\": \"\",\n      \"sourceMapRoot\": \"\",\n      \"sourceMapBasePath\": \"\",\n      \"sourceMap\": false\n    },\n    \"sass\": {\n      \"autoPrefix\": \"\",\n      \"includePath\": \"\",\n      \"indentType\": \"space\",\n      \"indentWidth\": 2,\n      \"outputStyle\": \"nested\",\n      \"Precision\": 5,\n      \"relativeUrls\": true,\n      \"sourceMapRoot\": \"\",\n      \"lineFeed\": \"\",\n      \"sourceMap\": false\n    },\n    \"stylus\": {\n      \"sourceMap\": false\n    },\n    \"babel\": {\n      \"sourceMap\": false\n    },\n    \"coffeescript\": {\n      \"bare\": false,\n      \"runtimeMode\": \"node\",\n      \"sourceMap\": false\n    },\n    \"handlebars\": {\n      \"root\": \"\",\n      \"noBOM\": false,\n      \"name\": \"\",\n      \"namespace\": \"\",\n      \"knownHelpersOnly\": false,\n      \"forcePartial\": false,\n      \"knownHelpers\": [],\n      \"commonjs\": \"\",\n      \"amd\": false,\n      \"sourceMap\": false\n    }\n  },\n  \"minifiers\": {\n    \"css\": {\n      \"enabled\": true,\n      \"termSemicolons\": true,\n      \"gzip\": false\n    },\n    \"javascript\": {\n      \"enabled\": true,\n      \"termSemicolons\": true,\n      \"gzip\": false\n    }\n  }\n}"
  },
  {
    "path": "src/Web/key-768c1632-cf7b-41a9-bb7a-bff228ae8fba.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<key id=\"768c1632-cf7b-41a9-bb7a-bff228ae8fba\" version=\"1\">\n  <creationDate>2021-12-01T14:37:52.0438755Z</creationDate>\n  <activationDate>2021-12-01T14:37:52.0246578Z</activationDate>\n  <expirationDate>2022-03-01T14:37:52.0246578Z</expirationDate>\n  <descriptor deserializerType=\"Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60\">\n    <descriptor>\n      <encryption algorithm=\"AES_256_CBC\" />\n      <validation algorithm=\"HMACSHA256\" />\n      <masterKey p4:requiresEncryption=\"true\" xmlns:p4=\"http://schemas.asp.net/2015/03/dataProtection\">\n        <!-- Warning: the key below is in an unencrypted form. -->\n        <value>PF3GdfO7PnvHYvXyD5nxmoQ91pY9qfA0rjRsdXHdUQbE1Mg9Xok2gXLY2zn8XemsySH37UGrGknht8u/PlehWg==</value>\n      </masterKey>\n    </descriptor>\n  </descriptor>\n</key>"
  },
  {
    "path": "src/Web/libman.json",
    "content": "{\n  \"version\": \"1.0\",\n  \"defaultProvider\": \"cdnjs\",\n  \"libraries\": [\n    {\n      \"library\": \"jquery@3.6.3\",\n      \"destination\": \"wwwroot/lib/jquery/\"\n    },\n    {\n      \"library\": \"twitter-bootstrap@3.4.1\",\n      \"files\": [\n        \"css/bootstrap.css\",\n        \"css/bootstrap.css.map\",\n        \"css/bootstrap.min.css\",\n        \"css/bootstrap.min.css.map\",\n        \"js/bootstrap.js\",\n        \"js/bootstrap.min.js\"\n      ],\n      \"destination\": \"wwwroot/lib/bootstrap/dist/\"\n    },\n    {\n      \"library\": \"jquery-validation-unobtrusive@4.0.0\",\n      \"destination\": \"wwwroot/lib/jquery-validation-unobtrusive/\"\n    },\n    {\n      \"library\": \"jquery-validate@1.19.5\",\n      \"destination\": \"wwwroot/lib/jquery-validate/\",\n      \"files\": [\n        \"jquery.validate.min.js\",\n        \"jquery.validate.js\"\n      ]\n    },\n    {\n      \"library\": \"toastr.js@2.1.4\",\n      \"destination\": \"wwwroot/lib/toastr/\"\n    },\n    {\n      \"library\": \"aspnet-signalr@1.0.27\",\n      \"files\": [\n        \"signalr.js\",\n        \"signalr.min.js\"\n      ],\n      \"destination\": \"wwwroot/lib/@aspnet/signalr/dist/browser/\"\n    }\n  ]\n}\n"
  },
  {
    "path": "src/Web/wwwroot/css/_variables.css",
    "content": "﻿"
  },
  {
    "path": "src/Web/wwwroot/css/_variables.scss",
    "content": "// Colors\n$color-brand: #00A69C;\n$color-brand-dark: darken($color-brand, 10%);\n$color-brand-darker: darken($color-brand, 20%);\n$color-brand-bright: lighten($color-brand, 10%);\n$color-brand-brighter: lighten($color-brand, 20%);\n\n$color-secondary: #83D01B;\n$color-secondary-dark: darken($color-secondary, 5%);\n$color-secondary-darker: darken($color-secondary, 20%);\n$color-secondary-bright: lighten($color-secondary, 10%);\n$color-secondary-brighter: lighten($color-secondary, 20%);\n\n$color-warning: #ff0000;\n$color-warning-dark: darken($color-warning, 5%);\n$color-warning-darker: darken($color-warning, 20%);\n$color-warning-bright: lighten($color-warning, 10%);\n$color-warning-brighter: lighten($color-warning, 20%);\n\n\n$color-background-dark: #333333;\n$color-background-darker: #000000;\n$color-background-bright: #EEEEFF;\n$color-background-brighter: #FFFFFF;\n\n$color-foreground-dark: #333333;\n$color-foreground-darker: #000000;\n$color-foreground-bright: #EEEEEE;\n$color-foreground-brighter: #FFFFFF;\n\n// Animations\n$animation-speed-default: .35s;\n$animation-speed-slow: .5s;\n$animation-speed-fast: .15s;\n\n// Fonts\n$font-weight-light: 200;\n$font-weight-semilight: 300;\n$font-weight-normal: 400;\n$font-weight-semibold: 600;\n$font-weight-bold: 700;\n\n$font-size-xs: .65rem;    // 10.4px\n$font-size-s: .85rem;    // 13.6px\n$font-size-m: 1rem;      // 16px\n$font-size-l: 1.25rem;   // 20px\n$font-size-xl: 1.5rem;   // 24px\n\n// Medias\n$media-screen-xxs: 360px;\n$media-screen-xs: 640px;\n$media-screen-s: 768px;\n$media-screen-m: 1024px;\n$media-screen-l: 1280px;\n$media-screen-xl: 1440px;\n$media-screen-xxl: 1680px;\n$media-screen-xxxl: 1920px;\n\n// Borders\n$border-light: 1px;\n\n// Images\n$image_path: '../../images/';\n$image-main_banner: '#{$image_path}main_banner.png';\n$image-arrow_down: '#{$image_path}arrow-down.png';"
  },
  {
    "path": "src/Web/wwwroot/css/app.component.css",
    "content": "﻿.esh-app-footer {\n  background-color: #000000;\n  border-top: 1px solid #EEEEEE;\n  margin-top: 2.5rem;\n  padding-bottom: 2.5rem;\n  padding-top: 2.5rem;\n  width: 100%;\n  bottom: 0; }\n  .esh-app-footer-brand {\n    height: 50px;\n    width: 230px; }\n\n.esh-app-header {\n  margin: 15px; }\n\n.esh-app-wrapper {\n  display: flex;\n  min-height: 100vh;\n  flex-direction: column;\n  justify-content: space-between; }\n"
  },
  {
    "path": "src/Web/wwwroot/css/app.component.scss",
    "content": "@import './_variables.scss';\n\n.esh-app {\n    &-footer {\n        $margin: 2.5rem;\n        $padding: 2.5rem;\n\n        background-color: $color-background-darker;\n        border-top: $border-light solid $color-foreground-bright;\n        margin-top: $margin;\n        padding-bottom: $padding;\n        padding-top: $padding;\n        width: 100%;\n        bottom: 0;\n        $height: 50px;\n\n        &-brand {\n            height: $height;\n            width: 230px;\n        }\n    }\n\n    &-header {\n        margin: 15px;\n    }\n\n    &-wrapper {\n        display: flex;\n        min-height: 100vh;\n        flex-direction: column;\n        justify-content: space-between\n    }\n}"
  },
  {
    "path": "src/Web/wwwroot/css/app.css",
    "content": "@font-face {\n    font-family: Montserrat;\n    font-weight: 400;\n    src: url(\".../fonts/Montserrat-Regular.eot?\") format(\"eot\"), url(\"../fonts/Montserrat-Regular.woff\") format(\"woff\"), url(\"../fonts/Montserrat-Regular.ttf\") format(\"truetype\"), url(\"../fonts/Montserrat-Regular.svg#Montserrat\") format(\"svg\");\n}\n\n@font-face {\n    font-family: Montserrat;\n    font-weight: 700;\n    src: url(\"../fonts/Montserrat-Bold.eot?\") format(\"eot\"), url(\"../fonts/Montserrat-Bold.woff\") format(\"woff\"), url(\"../fonts/Montserrat-Bold.ttf\") format(\"truetype\"), url(\"../fonts/Montserrat-Bold.svg#Montserrat\") format(\"svg\");\n}\n\nhtml,\nbody {\n    font-family: Montserrat, sans-serif;\n    font-size: 16px;\n    font-weight: 400;\n    z-index: 10;\n}\n\n*,\n*::after,\n*::before {\n    box-sizing: border-box;\n}\n\n.preloading {\n    color: #00A69C;\n    display: block;\n    font-size: 1.5rem;\n    left: 50%;\n    position: fixed;\n    top: 50%;\n    transform: translate(-50%, -50%);\n}\n\nselect::-ms-expand {\n    display: none;\n}\n\n@media screen and (min-width: 992px) {\n    .form-input {\n        max-width: 360px;\n        width: 360px;\n    }\n}\n\n.form-input {\n    border-radius: 0;\n    height: 45px;\n    padding: 10px;\n}\n\n.form-input-small {\n    max-width: 100px !important;\n}\n\n.form-input-medium {\n    width: 150px !important;\n}\n\n.alert {\n    padding-left: 0;\n}\n\n.alert-danger {\n    background-color: transparent;\n    border: 0;\n    color: #FB0D0D;\n    font-size: 12px;\n}\n\na,\na:active,\na:hover,\na:visited {\n    color: #000;\n    text-decoration: none;\n    transition: color 0.35s;\n}\n\n    a:hover,\n    a:active {\n        color: #75B918;\n        transition: color 0.35s;\n    }\n"
  },
  {
    "path": "src/Web/wwwroot/css/basket/basket-status/basket-status.component.css",
    "content": "﻿.esh-basketstatus {\n  cursor: pointer;\n  display: inline-block;\n  float: right;\n  position: relative;\n  transition: all 0.35s; }\n  .esh-basketstatus.is-disabled {\n    opacity: .5;\n    pointer-events: none; }\n  .esh-basketstatus-image {\n    height: 36px;\n    margin-top: .5rem; }\n  .esh-basketstatus-badge {\n    background-color: #83D01B;\n    border-radius: 50%;\n    color: #FFFFFF;\n    display: block;\n    height: 1.5rem;\n    left: 50%;\n    position: absolute;\n    text-align: center;\n    top: 0;\n    transform: translateX(-38%);\n    transition: all 0.35s;\n    width: 1.5rem; }\n  .esh-basketstatus-badge-inoperative {\n    background-color: #ff0000;\n    border-radius: 50%;\n    color: #FFFFFF;\n    display: block;\n    height: 1.5rem;\n    left: 50%;\n    position: absolute;\n    text-align: center;\n    top: 0;\n    transform: translateX(-38%);\n    transition: all 0.35s;\n    width: 1.5rem; }\n  .esh-basketstatus:hover .esh-basketstatus-badge {\n    background-color: transparent;\n    color: #75b918;\n    transition: all 0.35s; }\n"
  },
  {
    "path": "src/Web/wwwroot/css/basket/basket-status/basket-status.component.scss",
    "content": "@import '../../_variables.scss';\n\n.esh-basketstatus {\n    cursor: pointer;\n    display: inline-block;\n    float: right;\n    position: relative;\n    transition: all $animation-speed-default;\n\n    &.is-disabled {\n        opacity: .5;\n        pointer-events: none;\n    }\n\n    &-image {\n        height: 36px;\n        margin-top: .5rem;\n    }\n\n    &-badge {\n        $size: 1.5rem;\n        background-color: $color-secondary;\n        border-radius: 50%;\n        color: $color-foreground-brighter;\n        display: block;\n        height: $size;\n        left: 50%;\n        position: absolute;\n        text-align: center;\n        top: 0;\n        transform: translateX(-38%);\n        transition: all $animation-speed-default;\n        width: $size;\n    }\n\n    &-badge-inoperative {\n        $size: 1.5rem;\n        background-color: $color-warning;\n        border-radius: 50%;\n        color: $color-foreground-brighter;\n        display: block;\n        height: $size;\n        left: 50%;\n        position: absolute;\n        text-align: center;\n        top: 0;\n        transform: translateX(-38%);\n        transition: all $animation-speed-default;\n        width: $size;\n    }\n\n    &:hover &-badge {\n        background-color: transparent;\n        color: $color-secondary-dark;\n        transition: all $animation-speed-default;\n    }\n}\n"
  },
  {
    "path": "src/Web/wwwroot/css/basket/basket.component.css",
    "content": "﻿.esh-basket {\n  min-height: 80vh; }\n  .esh-basket-titles {\n    padding-bottom: 1rem;\n    padding-top: 2rem; }\n    .esh-basket-titles--clean {\n      padding-bottom: 0;\n      padding-top: 0; }\n  .esh-basket-title {\n    text-transform: uppercase; }\n  .esh-basket-items--border {\n    border-bottom: 1px solid #EEEEEE;\n    padding: .5rem 0; }\n    .esh-basket-items--border:last-of-type {\n      border-color: transparent; }\n  .esh-basket-items-margin-left1 {\n    margin-left: 1px; }\n  .esh-basket-item {\n    font-size: 1rem;\n    font-weight: 300; }\n    .esh-basket-item--middle {\n      line-height: 8rem; }\n      @media screen and (max-width: 1024px) {\n        .esh-basket-item--middle {\n          line-height: 1rem; } }\n    .esh-basket-item--mark {\n      color: #00A69C; }\n  .esh-basket-image {\n    height: 8rem; }\n  .esh-basket-input {\n    line-height: 1rem;\n    width: 100%; }\n  .esh-basket-checkout {\n    background-color: #83D01B;\n    border: 0;\n    border-radius: 0;\n    color: #FFFFFF;\n    display: inline-block;\n    font-size: 1rem;\n    font-weight: 400;\n    margin-top: 1rem;\n    padding: 1rem 1.5rem;\n    text-align: center;\n    text-transform: uppercase;\n    transition: all 0.35s; }\n    .esh-basket-checkout:hover {\n      background-color: #4a760f;\n      transition: all 0.35s; }\n    .esh-basket-checkout:visited {\n      color: #FFFFFF; }\n"
  },
  {
    "path": "src/Web/wwwroot/css/basket/basket.component.scss",
    "content": "@import '../variables.scss';\n\n@mixin margin-left($distance) {\n    margin-left: $distance;\n}\n\n.esh-basket {\n    min-height: 80vh;\n\n    &-titles {\n        padding-bottom: 1rem;\n        padding-top: 2rem;\n\n        &--clean {\n            padding-bottom: 0;\n            padding-top: 0;\n        }\n    }\n\n    &-title {\n        text-transform: uppercase;\n    }\n\n    &-items {\n        &--border {\n            border-bottom: $border-light solid $color-foreground-bright;\n            padding: .5rem 0;\n\n            &:last-of-type {\n                border-color: transparent;\n            }\n        }\n\n        &-margin-left1 {\n            @include margin-left(1px);\n        }\n    }\n\n    $item-height: 8rem;\n\n    &-item {\n        font-size: $font-size-m;\n        font-weight: $font-weight-semilight;\n\n        &--middle {\n            line-height: $item-height;\n\n            @media screen and (max-width: $media-screen-m) {\n                line-height: $font-size-m;\n            }\n        }\n\n        &--mark {\n            color: $color-brand;\n        }\n    }\n\n    &-image {\n        height: $item-height;\n    }\n\n    &-input {\n        line-height: 1rem;\n        width: 100%;\n    }\n\n    &-checkout {\n        background-color: $color-secondary;\n        border: 0;\n        border-radius: 0;\n        color: $color-foreground-brighter;\n        display: inline-block;\n        font-size: 1rem;\n        font-weight: $font-weight-normal;\n        margin-top: 1rem;\n        padding: 1rem 1.5rem;\n        text-align: center;\n        text-transform: uppercase;\n        transition: all $animation-speed-default;\n\n        &:hover {\n            background-color: $color-secondary-darker;\n            transition: all $animation-speed-default;\n        }\n\n        &:visited {\n            color: $color-foreground-brighter;\n        }\n    }\n}\n\n\n\n"
  },
  {
    "path": "src/Web/wwwroot/css/catalog/catalog.component.css",
    "content": "﻿.esh-catalog-hero {\n  background-image: url(\"../../images/main_banner.png\");\n  background-size: cover;\n  height: 260px;\n  width: 100%; }\n\n.esh-catalog-title {\n  position: relative;\n  top: 74.28571px; }\n\n.esh-catalog-filters {\n  background-color: #00A69C;\n  height: 65px; }\n\n.esh-catalog-filter {\n  -webkit-appearance: none;\n  background-color: transparent;\n  border-color: #00d9cc;\n  color: #FFFFFF;\n  cursor: pointer;\n  margin-right: 1rem;\n  margin-top: .5rem;\n  min-width: 140px;\n  outline-color: #83D01B;\n  padding-bottom: 0;\n  padding-left: 0.5rem;\n  padding-right: 0.5rem;\n  padding-top: 1.5rem; }\n  .esh-catalog-filter option {\n    background-color: #00A69C; }\n\n.esh-catalog-label {\n  display: inline-block;\n  position: relative;\n  z-index: 0; }\n  .esh-catalog-label::before {\n    color: rgba(255, 255, 255, 0.5);\n    content: attr(data-title);\n    font-size: 0.65rem;\n    margin-left: 0.5rem;\n    margin-top: 0.65rem;\n    position: absolute;\n    text-transform: uppercase;\n    z-index: 1; }\n  .esh-catalog-label::after {\n    background-image: url(\"../../images/arrow-down.png\");\n    content: '';\n    height: 7px;\n    position: absolute;\n    right: 1.5rem;\n    top: 2.5rem;\n    width: 10px;\n    z-index: 1; }\n\n.esh-catalog-send {\n  background-color: #83D01B;\n  color: #FFFFFF;\n  cursor: pointer;\n  font-size: 1rem;\n  margin-top: -1.5rem;\n  transition: all 0.35s; }\n  .esh-catalog-send:hover {\n    background-color: #4a760f;\n    transition: all 0.35s; }\n\n.esh-catalog-items {\n  margin-top: 1rem; }\n\n.esh-catalog-item {\n  margin-bottom: 1.5rem;\n  text-align: center;\n  width: 33%;\n  display: inline-block;\n  float: none !important; }\n  @media screen and (max-width: 1024px) {\n    .esh-catalog-item {\n      width: 50%; } }\n  @media screen and (max-width: 768px) {\n    .esh-catalog-item {\n      width: 100%; } }\n\n.esh-catalog-thumbnail {\n  max-width: 370px;\n  width: 100%; }\n\n.esh-catalog-button {\n  background-color: #83D01B;\n  border: 0;\n  color: #FFFFFF;\n  cursor: pointer;\n  font-size: 1rem;\n  height: 3rem;\n  margin-top: 1rem;\n  transition: all 0.35s;\n  width: 80%; }\n  .esh-catalog-button.is-disabled {\n    opacity: .5;\n    pointer-events: none; }\n  .esh-catalog-button:hover {\n    background-color: #4a760f;\n    transition: all 0.35s; }\n\n.esh-catalog-name {\n  font-size: 1rem;\n  font-weight: 300;\n  margin-top: .5rem;\n  text-align: center;\n  text-transform: uppercase; }\n\n.esh-catalog-price {\n  font-size: 28px;\n  font-weight: 900;\n  text-align: center; }\n  .esh-catalog-price::before {\n    content: '$'; }\n"
  },
  {
    "path": "src/Web/wwwroot/css/catalog/catalog.component.scss",
    "content": "@import '../_variables.scss';\n\n.esh-catalog {\n    $banner-height: 260px;\n\n    &-hero {\n        background-image: url($image-main_banner);\n        background-size: cover;\n        height: $banner-height;\n        width: 100%;\n    }\n\n    &-title {\n        position: relative;\n        top: $banner-height / 3.5;\n    }\n\n    $filter-height: 65px;\n\n    &-filters {\n        background-color: $color-brand;\n        height: $filter-height;\n    }\n\n    $filter-padding: .5rem;\n\n    &-filter {\n        -webkit-appearance: none;\n        background-color: transparent;\n        border-color: $color-brand-bright;\n        color: $color-foreground-brighter;\n        cursor: pointer;\n        margin-right: 1rem;\n        margin-top: .5rem;\n        min-width: 140px;\n        outline-color: $color-secondary;\n        padding-bottom: 0;\n        padding-left: $filter-padding;\n        padding-right: $filter-padding;\n        padding-top: $filter-padding * 3;\n\n        option {\n            background-color: $color-brand;\n        }\n    }\n\n    &-label {\n        display: inline-block;\n        position: relative;\n        z-index: 0;\n\n        &::before {\n            color: rgba($color-foreground-brighter, .5);\n            content: attr(data-title);\n            font-size: $font-size-xs;\n            margin-left: $filter-padding;\n            margin-top: $font-size-xs;\n            position: absolute;\n            text-transform: uppercase;\n            z-index: 1;\n        }\n\n        &::after {\n            background-image: url($image-arrow_down);\n            content: '';\n            height: 7px; //png height\n            position: absolute;\n            right: $filter-padding * 3;\n            top: $filter-padding * 5;\n            width: 10px; //png width\n            z-index: 1;\n        }\n    }\n\n    &-send {\n        background-color: $color-secondary;\n        color: $color-foreground-brighter;\n        cursor: pointer;\n        font-size: $font-size-m;\n        margin-top: -$filter-padding * 3;\n        transition: all $animation-speed-default;\n\n        &:hover {\n            background-color: $color-secondary-darker;\n            transition: all $animation-speed-default;\n        }\n    }\n\n    &-items {\n        margin-top: 1rem;\n    }\n\n    &-item {\n        margin-bottom: 1.5rem;\n        text-align: center;\n        width: 33%;\n        display: inline-block;\n        float: none !important;\n\n        @media screen and (max-width: $media-screen-m) {\n            width: 50%;\n        }\n\n        @media screen and (max-width: $media-screen-s) {\n            width: 100%;\n        }\n    }\n\n    &-thumbnail {\n        max-width: 370px;\n        width: 100%;\n    }\n\n    &-button {\n        background-color: $color-secondary;\n        border: 0;\n        color: $color-foreground-brighter;\n        cursor: pointer;\n        font-size: $font-size-m;\n        height: 3rem;\n        margin-top: 1rem;\n        transition: all $animation-speed-default;\n        width: 80%;\n\n        &.is-disabled {\n            opacity: .5;\n            pointer-events: none;\n        }\n\n        &:hover {\n            background-color: $color-secondary-darker;\n            transition: all $animation-speed-default;\n        }\n    }\n\n    &-name {\n        font-size: $font-size-m;\n        font-weight: $font-weight-semilight;\n        margin-top: .5rem;\n        text-align: center;\n        text-transform: uppercase;\n    }\n\n    &-price {\n        font-size: 28px;\n        font-weight: 900;\n        text-align: center;\n\n        &::before {\n            content: '$';\n        }\n    }\n}"
  },
  {
    "path": "src/Web/wwwroot/css/catalog/pager.css",
    "content": ".esh-pager-wrapper {\n    padding-top: 1rem;\n    text-align: center;\n}\n\n.esh-pager-item-left {\n    float: left;\n}\n\n.esh-pager-item-right {\n    float: right;\n}\n\n.esh-pager-item--navigable {\n    display: inline-block;\n    cursor: pointer;\n}\n\n    .esh-pager-item--navigable.is-disabled {\n        opacity: 0;\n        pointer-events: none;\n    }\n\n    .esh-pager-item--navigable:hover {\n        color: #83D01B;\n    }\n\n@media screen and (max-width: 1280px) {\n    .esh-pager-item {\n        font-size: 0.85rem;\n    }\n}\n\n@media screen and (max-width: 1024px) {\n    .esh-pager-item {\n        margin: 0 4vw;\n    }\n}\n"
  },
  {
    "path": "src/Web/wwwroot/css/orders/orders.component.css",
    "content": "﻿.esh-orders {\n  min-height: 80vh;\n  overflow-x: hidden; }\n  .esh-orders-header {\n    background-color: #00A69C;\n    height: 4rem; }\n  .esh-orders-back {\n    color: rgba(255, 255, 255, 0.4);\n    line-height: 4rem;\n    text-decoration: none;\n    text-transform: uppercase;\n    transition: color 0.35s; }\n    .esh-orders-back:hover {\n      color: #FFFFFF;\n      transition: color 0.35s; }\n  .esh-orders-titles {\n    padding-bottom: 1rem;\n    padding-top: 2rem; }\n  .esh-orders-title {\n    text-transform: uppercase; }\n  .esh-orders-items {\n    height: 2rem;\n    line-height: 2rem;\n    position: relative; }\n    .esh-orders-items:nth-of-type(2n + 1):before {\n      background-color: #EEEEFF;\n      content: '';\n      height: 100%;\n      left: 0;\n      margin-left: -100vw;\n      position: absolute;\n      top: 0;\n      width: 200vw;\n      z-index: -1; }\n  .esh-orders-item {\n    font-weight: 300; }\n    .esh-orders-item--hover {\n      opacity: 0;\n      pointer-events: none; }\n  .esh-orders-items:hover .esh-orders-item--hover {\n    opacity: 1;\n    pointer-events: all; }\n  .esh-orders-link {\n    color: #83D01B;\n    text-decoration: none;\n    transition: color 0.35s; }\n    .esh-orders-link:hover {\n      color: #75b918;\n      transition: color 0.35s; }\n  .esh-orders-detail-section {\n    padding-bottom: 30px; }\n  .esh-orders-detail-title {\n    font-size: 25px; }\n"
  },
  {
    "path": "src/Web/wwwroot/css/orders/orders.component.scss",
    "content": "﻿@import '../variables.scss';\n\n.esh-orders {\n    min-height: 80vh;\n    overflow-x: hidden;\n    $header-height: 4rem;\n\n    &-header {\n        background-color: #00A69C;\n        height: $header-height;\n    }\n\n    &-back {\n        color: rgba($color-foreground-brighter, .4);\n        line-height: $header-height;\n        text-decoration: none;\n        text-transform: uppercase;\n        transition: color $animation-speed-default;\n\n        &:hover {\n            color: $color-foreground-brighter;\n            transition: color $animation-speed-default;\n        }\n    }\n\n    &-titles {\n        padding-bottom: 1rem;\n        padding-top: 2rem;\n    }\n\n    &-title {\n        text-transform: uppercase;\n    }\n\n    &-items {\n        $height: 2rem;\n        height: $height;\n        line-height: $height;\n        position: relative;\n\n        &:nth-of-type(2n + 1) {\n            &:before {\n                background-color: $color-background-bright;\n                content: '';\n                height: 100%;\n                left: 0;\n                margin-left: -100vw;\n                position: absolute;\n                top: 0;\n                width: 200vw;\n                z-index: -1;\n            }\n        }\n    }\n\n    &-item {\n        font-weight: $font-weight-semilight;\n\n        &--hover {\n            opacity: 0;\n            pointer-events: none;\n        }\n    }\n\n    &-items:hover &-item--hover {\n        opacity: 1;\n        pointer-events: all;\n    }\n\n    &-link {\n        color: $color-secondary;\n        text-decoration: none;\n        transition: color $animation-speed-default;\n\n        &:hover {\n            color: $color-secondary-dark;\n            transition: color $animation-speed-default;\n        }\n    }\n\n    &-detail {\n\n        &-section {\n            padding-bottom: 30px;\n        }\n\n        &-title {\n            font-size: 25px;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Web/wwwroot/css/shared/components/header/header.css",
    "content": "﻿.esh-header {\n  background-color: #00A69C;\n  height: 4rem; }\n  .esh-header-back {\n    color: rgba(255, 255, 255, 0.5);\n    line-height: 4rem;\n    text-decoration: none;\n    text-transform: uppercase;\n    transition: color 0.35s; }\n    .esh-header-back:hover {\n      color: #FFFFFF;\n      transition: color 0.35s; }\n"
  },
  {
    "path": "src/Web/wwwroot/css/shared/components/header/header.scss",
    "content": "@import '../../../variables.scss';\n\n.esh-header {\n    $header-height: 4rem;\n\n    background-color: $color-brand;\n    height: $header-height;\n\n    &-back {\n        color: rgba($color-foreground-brighter, .5);\n        line-height: $header-height;\n        text-decoration: none;\n        text-transform: uppercase;\n        transition: color $animation-speed-default;\n\n        &:hover {\n            color: $color-foreground-brighter;\n            transition: color $animation-speed-default;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Web/wwwroot/css/shared/components/identity/identity.css",
    "content": "﻿.esh-identity {\n  line-height: 3rem;\n  position: relative;\n  text-align: right;\n}\n\n.esh-identity-section {\n  display: inline-block;\n  width: 100%;\n}\n\n.esh-identity-name {\n  display: inline-block;\n}\n\n.esh-identity-name--upper {\n  text-transform: uppercase;\n}\n\n@media screen and (max-width: 768px) {\n  .esh-identity-name {\n    font-size: 0.85rem;\n  }\n}\n\n.esh-identity-image {\n  display: inline-block;\n}\n\n.esh-identity-drop {\n  background: #FFFFFF;\n  height: 10px;\n  width: 10rem;\n  overflow: hidden;\n  padding: .5rem;\n  position: absolute;\n  right: 0;\n  top: 2.5rem;\n  transition: height 0.35s;\n}\n\n.esh-identity:hover .esh-identity-drop {\n  border: 1px solid #EEEEEE;\n  height: 14rem;\n  transition: height 0.35s;\n  z-index: 10;\n}\n\n.esh-identity-item {\n  cursor: pointer;\n  transition: color 0.35s;\n}\n\n.esh-identity-item:hover {\n  color: #75b918;\n  transition: color 0.35s;\n}\n\n"
  },
  {
    "path": "src/Web/wwwroot/css/shared/components/identity/identity.scss",
    "content": "@import '../../../variables.scss';\n\n.esh-identity {\n    line-height: 3rem;\n    position: relative;\n    text-align: right;\n\n    &-section {\n        display: inline-block;\n        width: 100%;\n    }\n\n    &-name {\n        display: inline-block;\n\n        &--upper {\n            text-transform: uppercase;\n        }\n\n        @media screen and (max-width: $media-screen-s) {\n            font-size: $font-size-s;\n        }\n    }\n\n    &-image {\n        display: inline-block;\n    }\n\n    &-drop {\n        background: $color-background-brighter;\n        height: 10px;\n        width: 10rem;\n        overflow: hidden;\n        padding: .5rem;\n        position: absolute;\n        right: 0;\n        top: 2.5rem;\n        transition: height $animation-speed-default;\n    }\n\n    &:hover &-drop {\n        border: $border-light solid $color-foreground-bright;\n        height: 10rem;\n        transition: height $animation-speed-default;\n    }\n\n    &-item {\n        cursor: pointer;\n        transition: color $animation-speed-default;\n\n        &:hover {\n            color: $color-secondary-dark;\n            transition: color $animation-speed-default;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Web/wwwroot/css/shared/components/pager/pager.css",
    "content": "﻿.esh-pager-wrapper {\n  padding-top: 1rem;\n  text-align: center; }\n\n.esh-pager-item {\n  margin: 0 5vw; }\n  .esh-pager-item.is-disabled {\n    opacity: .5;\n    pointer-events: none; }\n  .esh-pager-item--navigable {\n    cursor: pointer;\n    display: inline-block; }\n    .esh-pager-item--navigable:hover {\n      color: #83D01B; }\n  @media screen and (max-width: 1280px) {\n    .esh-pager-item {\n      font-size: 0.85rem; } }\n  @media screen and (max-width: 1024px) {\n    .esh-pager-item {\n      margin: 0 2.5vw; } }\n"
  },
  {
    "path": "src/Web/wwwroot/css/shared/components/pager/pager.scss",
    "content": "@import '../../../variables.scss';\n\n.esh-pager {\n\n    &-wrapper {\n        padding-top: 1rem;\n        text-align: center;\n    }\n\n    &-item {\n        $margin: 5vw;\n        margin: 0 $margin;\n\n        &.is-disabled {\n            opacity: .5;\n            pointer-events: none;\n        }\n\n        &--navigable {\n            cursor: pointer;\n            display: inline-block;\n\n            &:hover {\n                color: $color-secondary;\n            }\n        }\n\n        @media screen and (max-width: $media-screen-l) {\n            font-size: $font-size-s;\n        }\n\n        @media screen and (max-width: $media-screen-m) {\n            margin: 0 $margin / 2;\n        }\n    }\n}"
  },
  {
    "path": "src/Web/wwwroot/js/site.js",
    "content": "﻿// Write your Javascript code.\n"
  },
  {
    "path": "tests/FunctionalTests/FunctionalTests.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>  \n    <RootNamespace>Microsoft.eShopWeb.FunctionalTests</RootNamespace>\n    <IsPackable>false</IsPackable>\n    <Nullable>enable</Nullable>\n    <ImplicitUsings>enable</ImplicitUsings>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <Compile Remove=\"WebRazorPages\\**\" />\n    <EmbeddedResource Remove=\"WebRazorPages\\**\" />\n    <None Remove=\"WebRazorPages\\**\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.AspNetCore.Mvc.Testing\" />\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" />\n    <PackageReference Include=\"xunit\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>     \n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" />\n    <DotNetCliToolReference Include=\"dotnet-xunit\" Version=\"2.3.1\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\..\\src\\ApplicationCore\\ApplicationCore.csproj\" />\n    <ProjectReference Include=\"..\\..\\src\\PublicApi\\PublicApi.csproj\" />\n    <ProjectReference Include=\"..\\..\\src\\Web\\Web.csproj\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Service Include=\"{82a7f48d-3b50-4b1e-b82e-3ada8210c358}\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "tests/FunctionalTests/PublicApi/ApiTestFixture.cs",
    "content": "﻿using Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.Mvc.Testing;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb.Infrastructure.Data;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.eShopWeb.PublicApi.AuthEndpoints;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.PublicApi;\n\npublic class TestApiApplication : WebApplicationFactory<AuthenticateEndpoint>\n{\n    private readonly string _environment = \"Testing\";\n\n    protected override IHost CreateHost(IHostBuilder builder)\n    {\n        builder.UseEnvironment(_environment);\n\n        // Add mock/test services to the builder here\n        builder.ConfigureServices(services =>\n        {\n            services.AddScoped(sp =>\n            {\n                // Replace SQLite with in-memory database for tests\n                return new DbContextOptionsBuilder<CatalogContext>()\n                .UseInMemoryDatabase(\"DbForPublicApi\")\n                .UseApplicationServiceProvider(sp)\n                .Options;\n            });\n            services.AddScoped(sp =>\n            {\n                // Replace SQLite with in-memory database for tests\n                return new DbContextOptionsBuilder<AppIdentityDbContext>()\n                .UseInMemoryDatabase(\"IdentityDbForPublicApi\")\n                .UseApplicationServiceProvider(sp)\n                .Options;\n            });\n        });\n\n        return base.CreateHost(builder);\n    }\n}\n"
  },
  {
    "path": "tests/FunctionalTests/PublicApi/ApiTokenHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IdentityModel.Tokens.Jwt;\nusing System.Security.Claims;\nusing System.Text;\nusing Microsoft.eShopWeb.ApplicationCore.Constants;\nusing Microsoft.IdentityModel.Tokens;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.Web.Api;\n\npublic class ApiTokenHelper\n{\n    public static string GetAdminUserToken()\n    {\n        string userName = \"admin@microsoft.com\";\n        string[] roles = { \"Administrators\" };\n\n        return CreateToken(userName, roles);\n    }\n\n    public static string GetNormalUserToken()\n    {\n        string userName = \"demouser@microsoft.com\";\n        string[] roles = { };\n\n        return CreateToken(userName, roles);\n    }\n\n    private static string CreateToken(string userName, string[] roles)\n    {\n        var claims = new List<Claim> { new Claim(ClaimTypes.Name, userName) };\n\n        foreach (var role in roles)\n        {\n            claims.Add(new Claim(ClaimTypes.Role, role));\n        }\n\n        var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY);\n        var tokenDescriptor = new SecurityTokenDescriptor\n        {\n            Subject = new ClaimsIdentity(claims.ToArray()),\n            Expires = DateTime.UtcNow.AddHours(1),\n            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)\n        };\n        var tokenHandler = new JwtSecurityTokenHandler();\n        var token = tokenHandler.CreateToken(tokenDescriptor);\n        return tokenHandler.WriteToken(token);\n    }\n}\n"
  },
  {
    "path": "tests/FunctionalTests/PublicApi/AuthEndpoints/AuthenticateEndpoint.cs",
    "content": "﻿//using System.Net.Http;\n//using System.Text;\n//using System.Text.Json;\n//using System.Threading.Tasks;\n//using Microsoft.eShopWeb.ApplicationCore.Constants;\n//using Microsoft.eShopWeb.FunctionalTests.PublicApi;\n//using Microsoft.eShopWeb.PublicApi.AuthEndpoints;\n//using Xunit;\n\n//namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;\n\n//[Collection(\"Sequential\")]\n//public class AuthenticateEndpoint : IClassFixture<TestApiApplication>\n//{\n//    JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };\n\n//    public AuthenticateEndpoint(TestApiApplication factory)\n//    {\n//        Client = factory.CreateClient();\n//    }\n\n//    public HttpClient Client { get; }\n\n//    [Theory]\n//    [InlineData(\"demouser@microsoft.com\", AuthorizationConstants.DEFAULT_PASSWORD, true)]\n//    [InlineData(\"demouser@microsoft.com\", \"badpassword\", false)]\n//    [InlineData(\"baduser@microsoft.com\", \"badpassword\", false)]\n//    public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult)\n//    {\n//        var request = new AuthenticateRequest()\n//        {\n//            Username = testUsername,\n//            Password = testPassword\n//        };\n//        var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, \"application/json\");\n//        var response = await Client.PostAsync(\"api/authenticate\", jsonContent);\n//        response.EnsureSuccessStatusCode();\n//        var stringResponse = await response.Content.ReadAsStringAsync();\n//        var model = stringResponse.FromJson<AuthenticateResponse>();\n\n//        Assert.Equal(expectedResult, model.Result);\n//    }\n//}\n"
  },
  {
    "path": "tests/FunctionalTests/Web/Controllers/AccountControllerSignIn.cs",
    "content": "﻿using System.Net;\nusing System.Text.RegularExpressions;\nusing Microsoft.AspNetCore.Mvc.Testing;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;\n\n[Collection(\"Sequential\")]\npublic class AccountControllerSignIn : IClassFixture<TestApplication>\n{\n    public AccountControllerSignIn(TestApplication factory)\n    {\n        Client = factory.CreateClient(new WebApplicationFactoryClientOptions\n        {\n            AllowAutoRedirect = false\n        });\n    }\n\n    public HttpClient Client { get; }\n\n    [Fact]\n    public async Task ReturnsSignInScreenOnGet()\n    {\n        var response = await Client.GetAsync(\"/identity/account/login\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n\n        Assert.Contains(\"demouser@microsoft.com\", stringResponse);\n    }\n\n    [Fact]\n    public void RegexMatchesValidRequestVerificationToken()\n    {\n        // TODO: Move to a unit test\n        // TODO: Move regex to a constant in test project\n        var input = @\"<input name=\"\"__RequestVerificationToken\"\" type=\"\"hidden\"\" value=\"\"CfDJ8Obhlq65OzlDkoBvsSX0tgxFUkIZ_qDDSt49D_StnYwphIyXO4zxfjopCWsygfOkngsL6P0tPmS2HTB1oYW-p_JzE0_MCFb7tF9Ol_qoOg_IC_yTjBNChF0qRgoZPmKYOIJigg7e2rsBsmMZDTdbnGo\"\" /><input name=\"\"RememberMe\"\" type=\"\"hidden\"\" value=\"\"false\"\" /></form>\";\n        string regexpression = @\"name=\"\"__RequestVerificationToken\"\" type=\"\"hidden\"\" value=\"\"([-A-Za-z0-9+=/\\\\_]+?)\"\"\";\n        var regex = new Regex(regexpression);\n        var match = regex.Match(input);\n        var group = match.Groups.Values.LastOrDefault();\n        Assert.NotNull(group);\n        Assert.True(group.Value.Length > 50);\n    }\n\n    [Fact]\n    public async Task ReturnsFormWithRequestVerificationToken()\n    {\n        var response = await Client.GetAsync(\"/identity/account/login\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n\n        string token = WebPageHelpers.GetRequestVerificationToken(stringResponse);\n        Assert.True(token.Length > 50);\n    }\n\n    [Fact]\n    public async Task ReturnsSuccessfulSignInOnPostWithValidCredentials()\n    {\n        var getResponse = await Client.GetAsync(\"/identity/account/login\");\n        getResponse.EnsureSuccessStatusCode();\n        var stringResponse1 = await getResponse.Content.ReadAsStringAsync();\n\n        var keyValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"Email\", \"demouser@microsoft.com\"),\n            new KeyValuePair<string, string>(\"Password\", \"Pass@word1\"),\n            new KeyValuePair<string, string>(WebPageHelpers.TokenTag, WebPageHelpers.GetRequestVerificationToken(stringResponse1))\n        };\n        var formContent = new FormUrlEncodedContent(keyValues);\n\n        var postResponse = await Client.PostAsync(\"/identity/account/login\", formContent);\n        Assert.Equal(HttpStatusCode.Redirect, postResponse.StatusCode);\n        Assert.Equal(new System.Uri(\"/\", UriKind.Relative), postResponse.Headers.Location);\n    }\n\n    [Fact]\n    public async Task UpdatePhoneNumberProfile()\n    {\n        //Login\n        var getResponse = await Client.GetAsync(\"/identity/account/login\");\n        getResponse.EnsureSuccessStatusCode();\n        var stringResponse1 = await getResponse.Content.ReadAsStringAsync();\n        var keyValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"Email\", \"demouser@microsoft.com\"),\n            new KeyValuePair<string, string>(\"Password\", \"Pass@word1\"),\n            new KeyValuePair<string, string>(WebPageHelpers.TokenTag, WebPageHelpers.GetRequestVerificationToken(stringResponse1))\n        };\n        var formContent = new FormUrlEncodedContent(keyValues);\n        await Client.PostAsync(\"/identity/account/login\", formContent);\n\n        //Profile page\n        var profileResponse = await Client.GetAsync(\"/manage/my-account\");\n        profileResponse.EnsureSuccessStatusCode();\n        var stringProfileResponse = await profileResponse.Content.ReadAsStringAsync();\n\n        //Update phone number\n        var updateProfileValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"Email\", \"demouser@microsoft.com\"),\n            new KeyValuePair<string, string>(\"PhoneNumber\", \"03656565\"),\n            new KeyValuePair<string, string>(WebPageHelpers.TokenTag, WebPageHelpers.GetRequestVerificationToken(stringProfileResponse))\n        };\n        var updateProfileContent = new FormUrlEncodedContent(updateProfileValues);\n        var postProfileResponse = await Client.PostAsync(\"/manage/my-account\", updateProfileContent);\n\n        Assert.Equal(HttpStatusCode.Redirect, postProfileResponse.StatusCode);\n        var profileResponse2 = await Client.GetAsync(\"/manage/my-account\");\n        var stringProfileResponse2 = await profileResponse2.Content.ReadAsStringAsync();\n        Assert.Contains(\"03656565\", stringProfileResponse2);\n\n    }\n}\n"
  },
  {
    "path": "tests/FunctionalTests/Web/Controllers/CatalogControllerIndex.cs",
    "content": "﻿using System.Net.Http;\nusing System.Threading.Tasks;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;\n\n[Collection(\"Sequential\")]\npublic class CatalogControllerIndex : IClassFixture<TestApplication>\n{\n    public CatalogControllerIndex(TestApplication factory)\n    {\n        Client = factory.CreateClient();\n    }\n\n    public HttpClient Client { get; }\n\n    [Fact]\n    public async Task ReturnsHomePageWithProductListing()\n    {\n        // Arrange & Act\n        var response = await Client.GetAsync(\"/\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n\n        // Assert\n        Assert.Contains(\".NET Bot Black Sweatshirt\", stringResponse);\n    }\n}\n"
  },
  {
    "path": "tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs",
    "content": "﻿using System.Net;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc.Testing;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;\n\n[Collection(\"Sequential\")]\npublic class OrderIndexOnGet : IClassFixture<TestApplication>\n{\n    public OrderIndexOnGet(TestApplication factory)\n    {\n        Client = factory.CreateClient(new WebApplicationFactoryClientOptions\n        {\n            AllowAutoRedirect = false\n        });\n    }\n\n    public HttpClient Client { get; }\n\n    [Fact]\n    public async Task ReturnsRedirectGivenAnonymousUser()\n    {\n        var response = await Client.GetAsync(\"/order/my-orders\");\n        var redirectLocation = response!.Headers.Location!.OriginalString;\n\n        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);\n        Assert.Contains(\"/Account/Login\", redirectLocation);\n    }\n}\n"
  },
  {
    "path": "tests/FunctionalTests/Web/Pages/Basket/BasketPageCheckout.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc.Testing;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.Web.Pages.Basket;\n\n[Collection(\"Sequential\")]\npublic class BasketPageCheckout : IClassFixture<TestApplication>\n{\n    public BasketPageCheckout(TestApplication factory)\n    {\n        Client = factory.CreateClient(new WebApplicationFactoryClientOptions\n        {\n            AllowAutoRedirect = true\n        });\n    }\n\n    public HttpClient Client { get; }\n\n    [Fact]\n    public async Task RedirectsToLoginIfNotAuthenticated()\n    {\n\n        // Load Home Page\n        var response = await Client.GetAsync(\"/\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse1 = await response.Content.ReadAsStringAsync();\n\n        string token = WebPageHelpers.GetRequestVerificationToken(stringResponse1);\n\n        // Add Item to Cart\n        var keyValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"id\", \"2\"),\n            new KeyValuePair<string, string>(\"name\", \"shirt\"),\n            new KeyValuePair<string, string>(\"price\", \"19.49\"),\n            new KeyValuePair<string, string>(\"__RequestVerificationToken\", token)\n        };\n        var formContent = new FormUrlEncodedContent(keyValues);\n        var postResponse = await Client.PostAsync(\"/basket/index\", formContent);\n        postResponse.EnsureSuccessStatusCode();\n        var stringResponse = await postResponse.Content.ReadAsStringAsync();\n        Assert.Contains(\".NET Black &amp; White Mug\", stringResponse);\n\n        keyValues.Clear();\n\n        formContent = new FormUrlEncodedContent(keyValues);\n        var postResponse2 = await Client.PostAsync(\"/Basket/Checkout\", formContent);\n        Assert.Contains(\"/Identity/Account/Login\", postResponse2!.RequestMessage!.RequestUri!.ToString()!);\n    }\n}\n"
  },
  {
    "path": "tests/FunctionalTests/Web/Pages/Basket/CheckoutTest.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc.Testing;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.Web.Pages.Basket;\n\n[Collection(\"Sequential\")]\npublic class CheckoutTest : IClassFixture<TestApplication>\n{\n    public CheckoutTest(TestApplication factory)\n    {\n        Client = factory.CreateClient(new WebApplicationFactoryClientOptions\n        {\n            AllowAutoRedirect = true\n        });\n    }\n\n    public HttpClient Client { get; }\n\n    [Fact]\n    public async Task SucessfullyPay()\n    {\n\n        // Load Home Page\n        var response = await Client.GetAsync(\"/\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n\n        // Add Item to Cart\n        var keyValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"id\", \"2\"),\n            new KeyValuePair<string, string>(\"name\", \"shirt\"),\n            new KeyValuePair<string, string>(\"price\", \"19.49\"),\n            new KeyValuePair<string, string>(WebPageHelpers.TokenTag, WebPageHelpers.GetRequestVerificationToken(stringResponse))\n        };\n        var formContent = new FormUrlEncodedContent(keyValues);\n        var postResponse = await Client.PostAsync(\"/basket/index\", formContent);\n        postResponse.EnsureSuccessStatusCode();\n        var stringPostResponse = await postResponse.Content.ReadAsStringAsync();\n        Assert.Contains(\".NET Black &amp; White Mug\", stringPostResponse);\n\n        //Load login page\n        var loginResponse = await Client.GetAsync(\"/Identity/Account/Login\");\n        var longinKeyValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"email\", \"demouser@microsoft.com\"),\n            new KeyValuePair<string, string>(\"password\", \"Pass@word1\"),\n            new KeyValuePair<string, string>(WebPageHelpers.TokenTag, WebPageHelpers.GetRequestVerificationToken(await loginResponse.Content.ReadAsStringAsync()))\n        };\n        var loginFormContent = new FormUrlEncodedContent(longinKeyValues);\n        var loginPostResponse = await Client.PostAsync(\"/Identity/Account/Login?ReturnUrl=%2FBasket%2FCheckout\", loginFormContent);\n        var loginStringResponse = await loginPostResponse.Content.ReadAsStringAsync();\n\n        //Basket checkout (Pay now)\n        var checkOutKeyValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"Items[0].Id\", \"2\"),\n            new KeyValuePair<string, string>(\"Items[0].Quantity\", \"1\"),\n            new KeyValuePair<string, string>(WebPageHelpers.TokenTag, WebPageHelpers.GetRequestVerificationToken(loginStringResponse))\n        };\n        var checkOutContent = new FormUrlEncodedContent(checkOutKeyValues);     \n        var checkOutResponse = await Client.PostAsync(\"/basket/checkout\", checkOutContent);\n        var stringCheckOutResponse = await checkOutResponse.Content.ReadAsStringAsync();\n\n        Assert.Contains(\"/Basket/Success\", checkOutResponse.RequestMessage!.RequestUri!.ToString());\n        Assert.Contains(\"Thanks for your Order!\", stringCheckOutResponse);\n    }\n}\n"
  },
  {
    "path": "tests/FunctionalTests/Web/Pages/Basket/IndexTest.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc.Testing;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.Web.Pages.Basket;\n\n[Collection(\"Sequential\")]\npublic class IndexTest : IClassFixture<TestApplication>\n{\n    public IndexTest(TestApplication factory)\n    {\n        Client = factory.CreateClient(new WebApplicationFactoryClientOptions\n        {\n            AllowAutoRedirect = true\n        });\n    }\n\n    public HttpClient Client { get; }\n\n\n    [Fact]\n    public async Task OnPostUpdateTo50Successfully()\n    {\n        // Load Home Page\n        var response = await Client.GetAsync(\"/\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse1 = await response.Content.ReadAsStringAsync();\n\n        string token = WebPageHelpers.GetRequestVerificationToken(stringResponse1);\n\n        // Add Item to Cart\n        var keyValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"id\", \"2\"),\n            new KeyValuePair<string, string>(\"name\", \"shirt\"),\n            new KeyValuePair<string, string>(\"__RequestVerificationToken\", token)\n        };\n        var formContent = new FormUrlEncodedContent(keyValues);\n        var postResponse = await Client.PostAsync(\"/basket/index\", formContent);\n        postResponse.EnsureSuccessStatusCode();\n        var stringResponse = await postResponse.Content.ReadAsStringAsync();\n        Assert.Contains(\".NET Black &amp; White Mug\", stringResponse);\n\n        //Update\n        var updateKeyValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"Items[0].Id\", WebPageHelpers.GetId(stringResponse)),\n            new KeyValuePair<string, string>(\"Items[0].Quantity\", \"49\"),\n            new KeyValuePair<string, string>(WebPageHelpers.TokenTag, WebPageHelpers.GetRequestVerificationToken(stringResponse))\n        };\n        var updateContent = new FormUrlEncodedContent(updateKeyValues);\n        var updateResponse = await Client.PostAsync(\"/basket/update\", updateContent);\n\n        var stringUpdateResponse = await updateResponse.Content.ReadAsStringAsync();\n\n        Assert.Contains(\"/basket/update\", updateResponse!.RequestMessage!.RequestUri!.ToString()!);\n        decimal expectedTotalAmount = 416.50M;\n        Assert.Contains(expectedTotalAmount.ToString(\"N2\"), stringUpdateResponse);\n    }\n\n    [Fact]\n    public async Task OnPostUpdateTo0EmptyBasket()\n    {\n        // Load Home Page\n        var response = await Client.GetAsync(\"/\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse1 = await response.Content.ReadAsStringAsync();\n\n        string token = WebPageHelpers.GetRequestVerificationToken(stringResponse1);\n\n        // Add Item to Cart\n        var keyValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"id\", \"2\"),\n            new KeyValuePair<string, string>(\"name\", \"shirt\"),\n            new KeyValuePair<string, string>(\"__RequestVerificationToken\", token)\n        };\n        var formContent = new FormUrlEncodedContent(keyValues);\n        var postResponse = await Client.PostAsync(\"/basket/index\", formContent);\n        postResponse.EnsureSuccessStatusCode();\n        var stringResponse = await postResponse.Content.ReadAsStringAsync();\n        Assert.Contains(\".NET Black &amp; White Mug\", stringResponse);\n\n        //Update\n        var updateKeyValues = new List<KeyValuePair<string, string>>\n        {\n            new KeyValuePair<string, string>(\"Items[0].Id\", WebPageHelpers.GetId(stringResponse)),\n            new KeyValuePair<string, string>(\"Items[0].Quantity\", \"0\"),\n            new KeyValuePair<string, string>(WebPageHelpers.TokenTag, WebPageHelpers.GetRequestVerificationToken(stringResponse))\n        };\n        var updateContent = new FormUrlEncodedContent(updateKeyValues);\n        var updateResponse = await Client.PostAsync(\"/basket/update\", updateContent);\n\n        var stringUpdateResponse = await updateResponse.Content.ReadAsStringAsync();\n\n        Assert.Contains(\"/basket/update\", updateResponse!.RequestMessage!.RequestUri!.ToString()!);\n        Assert.Contains(\"Basket is empty\", stringUpdateResponse);\n    }\n}\n"
  },
  {
    "path": "tests/FunctionalTests/Web/Pages/HomePageOnGet.cs",
    "content": "﻿using System.Net.Http;\nusing System.Threading.Tasks;\nusing Microsoft.eShopWeb.FunctionalTests.Web;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.WebRazorPages;\n\n[Collection(\"Sequential\")]\npublic class HomePageOnGet : IClassFixture<TestApplication>\n{\n    public HomePageOnGet(TestApplication factory)\n    {\n        Client = factory.CreateClient();\n    }\n\n    public HttpClient Client { get; }\n\n    [Fact]\n    public async Task ReturnsHomePageWithProductListing()\n    {\n        // Arrange & Act\n        var response = await Client.GetAsync(\"/\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n\n        // Assert\n        Assert.Contains(\".NET Bot Black Sweatshirt\", stringResponse);\n    }\n}\n"
  },
  {
    "path": "tests/FunctionalTests/Web/WebPageHelpers.cs",
    "content": "﻿using System.Text.RegularExpressions;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.Web;\n\npublic static class WebPageHelpers\n{\n    public static string TokenTag = \"__RequestVerificationToken\";\n\n    public static string GetRequestVerificationToken(string input)\n    {\n        string regexpression = @\"name=\"\"__RequestVerificationToken\"\" type=\"\"hidden\"\" value=\"\"([-A-Za-z0-9+=/\\\\_]+?)\"\"\";\n        return RegexSearch(regexpression, input);\n    }\n\n    public static string GetId(string input)\n    {\n        string regexpression = @\"name=\"\"Items\\[0\\].Id\"\" value=\"\"(\\d)\"\"\";\n        return RegexSearch(regexpression, input);\n    }\n\n    private static string RegexSearch(string regexpression, string input)\n    {\n        var regex = new Regex(regexpression);\n        var match = regex.Match(input);\n        return match!.Groups!.Values!.LastOrDefault()!.Value;\n    }\n}\n"
  },
  {
    "path": "tests/FunctionalTests/Web/WebTestFixture.cs",
    "content": "﻿using System;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Mvc.Testing;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb.Infrastructure.Data;\nusing Microsoft.eShopWeb.Infrastructure.Identity;\nusing Microsoft.eShopWeb.Web.Interfaces;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.Extensions.Logging;\n\nnamespace Microsoft.eShopWeb.FunctionalTests.Web;\n\npublic class TestApplication : WebApplicationFactory<IBasketViewModelService>\n{\n    private readonly string _environment = \"Development\";\n\n    protected override IHost CreateHost(IHostBuilder builder)\n    {\n        builder.UseEnvironment(_environment);\n\n        // Add mock/test services to the builder here\n        builder.ConfigureServices(services =>\n        {\n            var descriptors = services.Where(d =>\n                                                d.ServiceType == typeof(DbContextOptions<CatalogContext>) ||\n                                                d.ServiceType == typeof(DbContextOptions<AppIdentityDbContext>))\n                                            .ToList();\n\n            foreach (var descriptor in descriptors)\n            {\n                services.Remove(descriptor);\n            }\n\n            services.AddScoped(sp =>\n            {\n                // Replace SQLite with in-memory database for tests\n                return new DbContextOptionsBuilder<CatalogContext>()\n                .UseInMemoryDatabase(\"InMemoryDbForTesting\")\n                .UseApplicationServiceProvider(sp)\n                .Options;\n            });\n            services.AddScoped(sp =>\n            {\n                // Replace SQLite with in-memory database for tests\n                return new DbContextOptionsBuilder<AppIdentityDbContext>()\n                .UseInMemoryDatabase(\"Identity\")\n                .UseApplicationServiceProvider(sp)\n                .Options;\n            });\n        });\n\n        return base.CreateHost(builder);\n    }\n}\n"
  },
  {
    "path": "tests/IntegrationTests/IntegrationTests.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>  \n    <RootNamespace>Microsoft.eShopWeb.IntegrationTests</RootNamespace>\n    <IsPackable>false</IsPackable>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" />\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" />\n    <PackageReference Include=\"NSubstitute\" />\n    <PackageReference Include=\"NSubstitute.Analyzers.CSharp\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"xunit\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>\n    </PackageReference>\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\..\\src\\Infrastructure\\Infrastructure.csproj\" />\n    <ProjectReference Include=\"..\\UnitTests\\UnitTests.csproj\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Service Include=\"{82a7f48d-3b50-4b1e-b82e-3ada8210c358}\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "tests/IntegrationTests/Repositories/BasketRepositoryTests/SetQuantities.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Services;\nusing Microsoft.eShopWeb.Infrastructure.Data;\nusing Microsoft.eShopWeb.UnitTests.Builders;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.IntegrationTests.Repositories.BasketRepositoryTests;\n\npublic class SetQuantities\n{\n    private readonly CatalogContext _catalogContext;\n    private readonly EfRepository<Basket> _basketRepository;\n    private readonly BasketBuilder BasketBuilder = new BasketBuilder();\n\n    public SetQuantities()\n    {\n        var dbOptions = new DbContextOptionsBuilder<CatalogContext>()\n            .UseInMemoryDatabase(databaseName: \"TestCatalog\")\n            .Options;\n        _catalogContext = new CatalogContext(dbOptions);\n        _basketRepository = new EfRepository<Basket>(_catalogContext);\n    }\n\n    [Fact]\n    public async Task RemoveEmptyQuantities()\n    {\n        var basket = BasketBuilder.WithOneBasketItem();\n        var basketService = new BasketService(_basketRepository, null);\n        await _basketRepository.AddAsync(basket);\n        _catalogContext.SaveChanges();\n\n        await basketService.SetQuantities(BasketBuilder.BasketId, new Dictionary<string, int>() { { BasketBuilder.BasketId.ToString(), 0 } });\n\n        Assert.Empty(basket.Items);\n    }\n}\n"
  },
  {
    "path": "tests/IntegrationTests/Repositories/OrderRepositoryTests/GetById.cs",
    "content": "﻿using System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Microsoft.eShopWeb.Infrastructure.Data;\nusing Microsoft.eShopWeb.UnitTests.Builders;\nusing Xunit;\nusing Xunit.Abstractions;\n\nnamespace Microsoft.eShopWeb.IntegrationTests.Repositories.OrderRepositoryTests;\n\npublic class GetById\n{\n    private readonly CatalogContext _catalogContext;\n    private readonly EfRepository<Order> _orderRepository;\n    private OrderBuilder OrderBuilder { get; } = new OrderBuilder();\n    private readonly ITestOutputHelper _output;\n    public GetById(ITestOutputHelper output)\n    {\n        _output = output;\n        var dbOptions = new DbContextOptionsBuilder<CatalogContext>()\n            .UseInMemoryDatabase(databaseName: \"TestCatalog\")\n            .Options;\n        _catalogContext = new CatalogContext(dbOptions);\n        _orderRepository = new EfRepository<Order>(_catalogContext);\n    }\n\n    [Fact]\n    public async Task GetsExistingOrder()\n    {\n        var existingOrder = OrderBuilder.WithDefaultValues();\n        _catalogContext.Orders.Add(existingOrder);\n        _catalogContext.SaveChanges();\n        int orderId = existingOrder.Id;\n        _output.WriteLine($\"OrderId: {orderId}\");\n\n        var orderFromRepo = await _orderRepository.GetByIdAsync(orderId);\n        Assert.Equal(OrderBuilder.TestBuyerId, orderFromRepo.BuyerId);\n\n        // Note: Using InMemoryDatabase OrderItems is available. Will be null if using SQL DB.\n        // Use the OrderWithItemsByIdSpec instead of just GetById to get the full aggregate\n        var firstItem = orderFromRepo.OrderItems.FirstOrDefault();\n        Assert.Equal(OrderBuilder.TestUnits, firstItem.Units);\n    }\n}\n"
  },
  {
    "path": "tests/IntegrationTests/Repositories/OrderRepositoryTests/GetByIdWithItemsAsync.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing Microsoft.eShopWeb.Infrastructure.Data;\nusing Microsoft.eShopWeb.UnitTests.Builders;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.IntegrationTests.Repositories.OrderRepositoryTests;\n\npublic class GetByIdWithItemsAsync\n{\n    private readonly CatalogContext _catalogContext;\n    private readonly EfRepository<Order> _orderRepository;\n    private OrderBuilder OrderBuilder { get; } = new OrderBuilder();\n\n    public GetByIdWithItemsAsync()\n    {\n        var dbOptions = new DbContextOptionsBuilder<CatalogContext>()\n            .UseInMemoryDatabase(databaseName: \"TestCatalog\")\n            .Options;\n        _catalogContext = new CatalogContext(dbOptions);\n        _orderRepository = new EfRepository<Order>(_catalogContext);\n    }\n\n    [Fact]\n    public async Task GetOrderAndItemsByOrderIdWhenMultipleOrdersPresent()\n    {\n        //Arrange\n        var itemOneUnitPrice = 5.50m;\n        var itemOneUnits = 2;\n        var itemTwoUnitPrice = 7.50m;\n        var itemTwoUnits = 5;\n\n        var firstOrder = OrderBuilder.WithDefaultValues();\n        _catalogContext.Orders.Add(firstOrder);\n        int firstOrderId = firstOrder.Id;\n\n        var secondOrderItems = new List<OrderItem>();\n        secondOrderItems.Add(new OrderItem(OrderBuilder.TestCatalogItemOrdered, itemOneUnitPrice, itemOneUnits));\n        secondOrderItems.Add(new OrderItem(OrderBuilder.TestCatalogItemOrdered, itemTwoUnitPrice, itemTwoUnits));\n        var secondOrder = OrderBuilder.WithItems(secondOrderItems);\n        _catalogContext.Orders.Add(secondOrder);\n        int secondOrderId = secondOrder.Id;\n\n        _catalogContext.SaveChanges();\n\n        //Act\n        var spec = new OrderWithItemsByIdSpec(secondOrderId);\n        var orderFromRepo = await _orderRepository.FirstOrDefaultAsync(spec);\n\n        //Assert\n        Assert.Equal(secondOrderId, orderFromRepo.Id);\n        Assert.Equal(secondOrder.OrderItems.Count, orderFromRepo.OrderItems.Count);\n        Assert.Equal(1, orderFromRepo.OrderItems.Count(x => x.UnitPrice == itemOneUnitPrice));\n        Assert.Equal(1, orderFromRepo.OrderItems.Count(x => x.UnitPrice == itemTwoUnitPrice));\n        Assert.Equal(itemOneUnits, orderFromRepo.OrderItems.SingleOrDefault(x => x.UnitPrice == itemOneUnitPrice).Units);\n        Assert.Equal(itemTwoUnits, orderFromRepo.OrderItems.SingleOrDefault(x => x.UnitPrice == itemTwoUnitPrice).Units);\n    }\n}\n"
  },
  {
    "path": "tests/PublicApiIntegrationTests/ApiTokenHelper.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Constants;\nusing Microsoft.IdentityModel.Tokens;\nusing System;\nusing System.Collections.Generic;\nusing System.IdentityModel.Tokens.Jwt;\nusing System.Security.Claims;\nusing System.Text;\n\nnamespace PublicApiIntegrationTests\n{\n    public class ApiTokenHelper\n    {\n        public static string GetAdminUserToken()\n        {\n            string userName = \"admin@microsoft.com\";\n            string[] roles = { \"Administrators\" };\n\n            return CreateToken(userName, roles);\n        }\n\n        public static string GetNormalUserToken()\n        {\n            string userName = \"demouser@microsoft.com\";\n            string[] roles = { };\n\n            return CreateToken(userName, roles);\n        }\n\n        private static string CreateToken(string userName, string[] roles)\n        {\n            var claims = new List<Claim> { new Claim(ClaimTypes.Name, userName) };\n\n            foreach (var role in roles)\n            {\n                claims.Add(new Claim(ClaimTypes.Role, role));\n            }\n\n            var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY);\n            var tokenDescriptor = new SecurityTokenDescriptor\n            {\n                Subject = new ClaimsIdentity(claims.ToArray()),\n                Expires = DateTime.UtcNow.AddHours(1),\n                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)\n            };\n            var tokenHandler = new JwtSecurityTokenHandler();\n            var token = tokenHandler.CreateToken(tokenDescriptor);\n            return tokenHandler.WriteToken(token);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/PublicApiIntegrationTests/AuthEndpoints/AuthenticateEndpointTest.cs",
    "content": "﻿using System.Net.Http;\nusing System.Text;\nusing System.Text.Json;\nusing System.Threading.Tasks;\nusing Microsoft.eShopWeb;\nusing Microsoft.eShopWeb.ApplicationCore.Constants;\nusing Microsoft.eShopWeb.PublicApi.AuthEndpoints;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\n\nnamespace PublicApiIntegrationTests.AuthEndpoints;\n\n[TestClass]\npublic class AuthenticateEndpoint\n{\n    [TestMethod]\n    [DataRow(\"demouser@microsoft.com\", AuthorizationConstants.DEFAULT_PASSWORD, true)]\n    [DataRow(\"demouser@microsoft.com\", \"badpassword\", false)]\n    [DataRow(\"baduser@microsoft.com\", \"badpassword\", false)]\n    public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult)\n    {\n        var request = new AuthenticateRequest()\n        {\n            Username = testUsername,\n            Password = testPassword\n        };\n        var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, \"application/json\");\n        var response = await ProgramTest.NewClient.PostAsync(\"api/authenticate\", jsonContent);\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n        var model = stringResponse.FromJson<AuthenticateResponse>();\n\n        Assert.AreEqual(expectedResult, model!.Result);\n    }\n}\n"
  },
  {
    "path": "tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemGetByIdEndpointTest.cs",
    "content": "﻿using Microsoft.eShopWeb;\nusing Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Net;\nusing System.Threading.Tasks;\n\nnamespace PublicApiIntegrationTests.CatalogItemEndpoints;\n\n[TestClass]\npublic class CatalogItemGetByIdEndpointTest\n{\n    [TestMethod]\n    public async Task ReturnsItemGivenValidId()\n    {\n        var response = await ProgramTest.NewClient.GetAsync(\"api/catalog-items/5\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n        var model = stringResponse.FromJson<GetByIdCatalogItemResponse>();\n\n        Assert.AreEqual(5, model!.CatalogItem.Id);\n        Assert.AreEqual(\"Roslyn Red Sheet\", model.CatalogItem.Name);\n    }\n\n    [TestMethod]\n    public async Task ReturnsNotFoundGivenInvalidId()\n    {\n        var response = await ProgramTest.NewClient.GetAsync(\"api/catalog-items/0\");\n\n        Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);\n    }\n}\n"
  },
  {
    "path": "tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs",
    "content": "﻿using Microsoft.eShopWeb;\nusing Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;\nusing Microsoft.eShopWeb.Web.ViewModels;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Net;\nusing System.Threading.Tasks;\n\nnamespace PublicApiIntegrationTests.CatalogItemEndpoints;\n\n[TestClass]\npublic class CatalogItemListPagedEndpoint\n{\n    [TestMethod]\n    public async Task ReturnsFirst10CatalogItems()\n    {\n        var client = ProgramTest.NewClient;\n        var response = await client.GetAsync(\"/api/catalog-items?pageSize=10\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n        var model = stringResponse.FromJson<CatalogIndexViewModel>();\n\n        Assert.AreEqual(10, model!.CatalogItems.Count());\n    }\n\n    [TestMethod]\n    public async Task ReturnsCorrectCatalogItemsGivenPageIndex1()\n    {\n\n        var pageSize = 10;\n        var pageIndex = 1;\n\n        var client = ProgramTest.NewClient;\n        var response = await client.GetAsync($\"/api/catalog-items\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n        var model = stringResponse.FromJson<ListPagedCatalogItemResponse>();\n        var totalItem = model!.CatalogItems.Count();\n\n        var response2 = await client.GetAsync($\"/api/catalog-items?pageSize={pageSize}&pageIndex={pageIndex}\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse2 = await response2.Content.ReadAsStringAsync();\n        var model2 = stringResponse2.FromJson<ListPagedCatalogItemResponse>();\n\n        var totalExpected = totalItem - (pageSize * pageIndex);\n\n        Assert.AreEqual(totalExpected, model2!.CatalogItems.Count());\n    }\n\n    [DataTestMethod]\n    [DataRow(\"catalog-items\")]\n    [DataRow(\"catalog-brands\")]\n    [DataRow(\"catalog-types\")]\n    [DataRow(\"catalog-items/1\")]\n    public async Task SuccessFullMutipleParallelCall(string endpointName)\n    {\n        var client = ProgramTest.NewClient;\n        var tasks = new List<Task<HttpResponseMessage>>();\n\n        for (int i = 0; i < 100; i++)\n        {\n            var task = client.GetAsync($\"/api/{endpointName}\");\n            tasks.Add(task);\n        }\n        await Task.WhenAll(tasks.ToList());\n        var totalKO = tasks.Count(t => t.Result.StatusCode != HttpStatusCode.OK);\n\n        Assert.AreEqual(0, totalKO);\n    }\n}\n"
  },
  {
    "path": "tests/PublicApiIntegrationTests/CatalogItemEndpoints/CreateCatalogItemEndpointTest.cs",
    "content": "﻿using BlazorShared.Models;\nusing Microsoft.eShopWeb;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Net;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text;\nusing System.Text.Json;\nusing System.Threading.Tasks;\n\nnamespace PublicApiIntegrationTests.AuthEndpoints;\n\n[TestClass]\npublic class CreateCatalogItemEndpointTest\n{\n    private int _testBrandId = 1;\n    private int _testTypeId = 2;\n    private string _testDescription = \"test description\";\n    private string _testName = \"test name\";\n    private decimal _testPrice = 1.23m;\n\n\n    [TestMethod]\n    public async Task ReturnsNotAuthorizedGivenNormalUserToken()\n    {\n        var jsonContent = GetValidNewItemJson();\n        var token = ApiTokenHelper.GetNormalUserToken();\n        var client = ProgramTest.NewClient;\n        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", token);\n        var response = await client.PostAsync(\"api/catalog-items\", jsonContent);\n\n        Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);\n    }\n\n    [TestMethod]\n    public async Task ReturnsSuccessGivenValidNewItemAndAdminUserToken()\n    {\n        var jsonContent = GetValidNewItemJson();\n        var adminToken = ApiTokenHelper.GetAdminUserToken();\n        var client = ProgramTest.NewClient;\n        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", adminToken);\n        var response = await client.PostAsync(\"api/catalog-items\", jsonContent);\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n        var model = stringResponse.FromJson<CreateCatalogItemResponse>();\n\n        Assert.AreEqual(_testBrandId, model!.CatalogItem.CatalogBrandId);\n        Assert.AreEqual(_testTypeId, model.CatalogItem.CatalogTypeId);\n        Assert.AreEqual(_testDescription, model.CatalogItem.Description);\n        Assert.AreEqual(_testName, model.CatalogItem.Name);\n        Assert.AreEqual(_testPrice, model.CatalogItem.Price);\n    }\n\n    private StringContent GetValidNewItemJson()\n    {\n        var request = new CreateCatalogItemRequest()\n        {\n            CatalogBrandId = _testBrandId,\n            CatalogTypeId = _testTypeId,\n            Description = _testDescription,\n            Name = _testName,\n            Price = _testPrice\n        };\n        var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, \"application/json\");\n\n        return jsonContent;\n    }\n}\n"
  },
  {
    "path": "tests/PublicApiIntegrationTests/CatalogItemEndpoints/DeleteCatalogItemEndpointTest.cs",
    "content": "﻿using BlazorShared.Models;\nusing Microsoft.eShopWeb;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Net;\nusing System.Net.Http.Headers;\nusing System.Threading.Tasks;\n\nnamespace PublicApiIntegrationTests.CatalogItemEndpoints;\n\n[TestClass]\npublic class DeleteCatalogItemEndpointTest\n{\n    [TestMethod]\n    public async Task ReturnsSuccessGivenValidIdAndAdminUserToken()\n    {\n        var adminToken = ApiTokenHelper.GetAdminUserToken();\n        var client = ProgramTest.NewClient;\n        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", adminToken);\n        var response = await client.DeleteAsync(\"api/catalog-items/12\");\n        response.EnsureSuccessStatusCode();\n        var stringResponse = await response.Content.ReadAsStringAsync();\n        var model = stringResponse.FromJson<DeleteCatalogItemResponse>();\n\n        Assert.AreEqual(\"Deleted\", model!.Status);\n    }\n\n    [TestMethod]\n    public async Task ReturnsNotFoundGivenInvalidIdAndAdminUserToken()\n    {\n        var adminToken = ApiTokenHelper.GetAdminUserToken();\n        var client = ProgramTest.NewClient;\n        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", adminToken);\n        var response = await client.DeleteAsync(\"api/catalog-items/0\");\n\n        Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);\n    }\n}\n"
  },
  {
    "path": "tests/PublicApiIntegrationTests/ProgramTest.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc.Testing;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Net.Http;\n\nnamespace PublicApiIntegrationTests;\n\n[TestClass]\npublic class ProgramTest\n{\n    private static WebApplicationFactory<Program> _application = new();\n\n    public static HttpClient NewClient\n    {\n        get\n        {\n            return _application.CreateClient();\n        }\n    }\n\n    [AssemblyInitialize]\n    public static void AssemblyInitialize(TestContext _)\n    {\n        _application = new WebApplicationFactory<Program>();\n\n    }\n}\n"
  },
  {
    "path": "tests/PublicApiIntegrationTests/PublicApiIntegrationTests.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>    \n    <Nullable>enable</Nullable>\n\n    <IsPackable>false</IsPackable>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <None Remove=\"appsettings.test.json\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Content Include=\"appsettings.test.json\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.AspNetCore.Mvc.Testing\" />\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" />\n    <PackageReference Include=\"MSTest.TestAdapter\" />\n    <PackageReference Include=\"MSTest.TestFramework\" />\n    <PackageReference Include=\"coverlet.collector\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\..\\src\\PublicApi\\PublicApi.csproj\" />\n    <ProjectReference Include=\"..\\..\\src\\Web\\Web.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "tests/PublicApiIntegrationTests/appsettings.test.json",
    "content": "﻿{\n  \"UseOnlyInMemoryDatabase\": true\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Entities/BasketTests/BasketAddItem.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Entities.BasketTests;\n\npublic class BasketAddItem\n{\n    private readonly int _testCatalogItemId = 123;\n    private readonly decimal _testUnitPrice = 1.23m;\n    private readonly int _testQuantity = 2;\n    private readonly string _buyerId = \"Test buyerId\";\n\n    [Fact]\n    public void AddsBasketItemIfNotPresent()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice, _testQuantity);\n\n        var firstItem = basket.Items.Single();\n        Assert.Equal(_testCatalogItemId, firstItem.CatalogItemId);\n        Assert.Equal(_testUnitPrice, firstItem.UnitPrice);\n        Assert.Equal(_testQuantity, firstItem.Quantity);\n    }\n\n    [Fact]\n    public void IncrementsQuantityOfItemIfPresent()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice, _testQuantity);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice, _testQuantity);\n\n        var firstItem = basket.Items.Single();\n        Assert.Equal(_testQuantity * 2, firstItem.Quantity);\n    }\n\n    [Fact]\n    public void KeepsOriginalUnitPriceIfMoreItemsAdded()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice, _testQuantity);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice * 2, _testQuantity);\n\n        var firstItem = basket.Items.Single();\n        Assert.Equal(_testUnitPrice, firstItem.UnitPrice);\n    }\n\n    [Fact]\n    public void DefaultsToQuantityOfOne()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice);\n\n        var firstItem = basket.Items.Single();\n        Assert.Equal(1, firstItem.Quantity);\n    }\n\n    [Fact]\n    public void CantAddItemWithNegativeQuantity()\n    {\n        var basket = new Basket(_buyerId);\n\n        Assert.Throws<ArgumentOutOfRangeException>(() => basket.AddItem(_testCatalogItemId, _testUnitPrice, -1));\n    }\n\n    [Fact]\n    public void CantModifyQuantityToNegativeNumber()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice);\n\n        Assert.Throws<ArgumentOutOfRangeException>(() => basket.AddItem(_testCatalogItemId, _testUnitPrice, -2));\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Entities/BasketTests/BasketRemoveEmptyItems.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Entities.BasketTests;\n\npublic class BasketRemoveEmptyItems\n{\n    private readonly int _testCatalogItemId = 123;\n    private readonly decimal _testUnitPrice = 1.23m;\n    private readonly string _buyerId = \"Test buyerId\";\n\n    [Fact]\n    public void RemovesEmptyBasketItems()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice, 0);\n        basket.RemoveEmptyItems();\n\n        Assert.Empty(basket.Items);\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Entities/BasketTests/BasketTotalItems.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Entities.BasketTests;\n\npublic class BasketTotalItems\n{\n    private readonly int _testCatalogItemId = 123;\n    private readonly decimal _testUnitPrice = 1.23m;\n    private readonly int _testQuantity = 2;\n    private readonly string _buyerId = \"Test buyerId\";\n\n    [Fact]\n    public void ReturnsTotalQuantityWithOneItem()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice, _testQuantity);\n\n        var result = basket.TotalItems;\n\n        Assert.Equal(_testQuantity, result);\n    }\n\n    [Fact]\n    public void ReturnsTotalQuantityWithMultipleItems()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice, _testQuantity);\n        basket.AddItem(_testCatalogItemId, _testUnitPrice, _testQuantity*2);\n\n        var result = basket.TotalItems;\n\n        Assert.Equal(_testQuantity*3, result);\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Entities/OrderTests/OrderTotal.cs",
    "content": "﻿using System.Collections.Generic;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Microsoft.eShopWeb.UnitTests.Builders;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Entities.OrderTests;\n\npublic class OrderTotal\n{\n    private decimal _testUnitPrice = 42m;\n\n    [Fact]\n    public void IsZeroForNewOrder()\n    {\n        var order = new OrderBuilder().WithNoItems();\n\n        Assert.Equal(0, order.Total());\n    }\n\n    [Fact]\n    public void IsCorrectGiven1Item()\n    {\n        var builder = new OrderBuilder();\n        var items = new List<OrderItem>\n            {\n                new OrderItem(builder.TestCatalogItemOrdered, _testUnitPrice, 1)\n            };\n        var order = new OrderBuilder().WithItems(items);\n        Assert.Equal(_testUnitPrice, order.Total());\n    }\n\n    [Fact]\n    public void IsCorrectGiven3Items()\n    {\n        var builder = new OrderBuilder();\n        var order = builder.WithDefaultValues();\n\n        Assert.Equal(builder.TestUnitPrice * builder.TestUnits, order.Total());\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Extensions/JsonExtensions.cs",
    "content": "﻿using Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Extensions;\n\npublic class JsonExtensions\n{\n    [Fact]\n    public void CorrectlySerializesAndDeserializesObject()\n    {\n        var testParent = new TestParent\n        {\n            Id = 7,\n            Name = \"Test name\",\n            Children = new[]\n            {\n                    new TestChild(),\n                    new TestChild(),\n                    new TestChild()\n                }\n        };\n\n        var json = testParent.ToJson();\n        var result = json.FromJson<TestParent>();\n        Assert.Equal(testParent, result);\n    }\n\n    [\n        Theory,\n        InlineData(\"{ \\\"id\\\": 9, \\\"name\\\": \\\"Another test\\\" }\", 9, \"Another test\"),\n        InlineData(\"{ \\\"id\\\": 3124, \\\"name\\\": \\\"Test Value 1\\\" }\", 3124, \"Test Value 1\"),\n    ]\n    public void CorrectlyDeserializesJson(string json, int expectedId, string expectedName) =>\n        Assert.Equal(new TestParent { Id = expectedId, Name = expectedName }, json.FromJson<TestParent>());\n\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Extensions/TestChild.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Extensions;\n\n[DebuggerDisplay(\"Id={Id}, Date={Date}\")]\npublic class TestChild : IEquatable<TestChild>\n{\n    public Guid Id { get; set; } = Guid.NewGuid();\n\n    public DateTime Date { get; set; } = DateTime.UtcNow;\n\n    public bool Equals([AllowNull] TestChild other) =>\n        other?.Date == Date && other?.Id == Id;\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Extensions/TestParent.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Extensions;\n\npublic class TestParent : IEquatable<TestParent>\n{\n    public int Id { get; set; }\n\n    public string? Name { get; set; }\n\n    public IEnumerable<TestChild>? Children { get; set; }\n\n    public bool Equals([AllowNull] TestParent other) \n    {\n        if (other?.Id == Id && other?.Name == Name)\n        {\n            if (Children is null)\n            {\n                return other?.Children is null;\n            }\n\n            return other?.Children?.Zip(Children).All(t => t.First?.Equals(t.Second) ?? false) ?? false;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Services/BasketServiceTests/AddItemToBasket.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Services;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing NSubstitute;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTests;\n\npublic class AddItemToBasket\n{\n    private readonly string _buyerId = \"Test buyerId\";\n    private readonly IRepository<Basket> _mockBasketRepo = Substitute.For<IRepository<Basket>>();\n    private readonly IAppLogger<BasketService> _mockLogger = Substitute.For<IAppLogger<BasketService>>();\n\n    [Fact]\n    public async Task InvokesBasketRepositoryGetBySpecAsyncOnce()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(1, 1.5m);\n\n        _mockBasketRepo.FirstOrDefaultAsync(Arg.Any<BasketWithItemsSpecification>(), default).Returns(basket);\n\n        var basketService = new BasketService(_mockBasketRepo, _mockLogger);\n\n        await basketService.AddItemToBasket(basket.BuyerId, 1, 1.50m);\n\n        await _mockBasketRepo.Received().FirstOrDefaultAsync(Arg.Any<BasketWithItemsSpecification>(), default);\n    }\n\n    [Fact]\n    public async Task InvokesBasketRepositoryUpdateAsyncOnce()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(1, 1.1m, 1);\n        _mockBasketRepo.FirstOrDefaultAsync(Arg.Any<BasketWithItemsSpecification>(), default).Returns(basket);\n\n        var basketService = new BasketService(_mockBasketRepo, _mockLogger);\n\n        await basketService.AddItemToBasket(basket.BuyerId, 1, 1.50m);\n\n        await _mockBasketRepo.Received().UpdateAsync(basket, default);\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Services/BasketServiceTests/DeleteBasket.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Services;\n//using Moq;\nusing NSubstitute;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTests;\n\npublic class DeleteBasket\n{\n    private readonly string _buyerId = \"Test buyerId\";\n    private readonly IRepository<Basket> _mockBasketRepo = Substitute.For<IRepository<Basket>>();\n    private readonly IAppLogger<BasketService> _mockLogger = Substitute.For<IAppLogger<BasketService>>();\n\n    [Fact]\n    public async Task ShouldInvokeBasketRepositoryDeleteAsyncOnce()\n    {\n        var basket = new Basket(_buyerId);\n        basket.AddItem(1, 1.1m, 1);\n        basket.AddItem(2, 1.1m, 1);\n        _mockBasketRepo.GetByIdAsync(Arg.Any<int>(), default)\n            .Returns(basket);\n        var basketService = new BasketService(_mockBasketRepo, _mockLogger);\n\n        await basketService.DeleteBasketAsync(1);\n\n        await _mockBasketRepo.Received().DeleteAsync(Arg.Any<Basket>(), default);\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Services/BasketServiceTests/TransferBasket.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Services;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing NSubstitute;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTests;\n\npublic class TransferBasket\n{\n    private readonly string _nonexistentAnonymousBasketBuyerId = \"nonexistent-anonymous-basket-buyer-id\";\n    private readonly string _existentAnonymousBasketBuyerId = \"existent-anonymous-basket-buyer-id\";\n    private readonly string _nonexistentUserBasketBuyerId = \"newuser@microsoft.com\";\n    private readonly string _existentUserBasketBuyerId = \"testuser@microsoft.com\";\n    private readonly IRepository<Basket> _mockBasketRepo = Substitute.For<IRepository<Basket>>();\n    private readonly IAppLogger<BasketService> _mockLogger = Substitute.For<IAppLogger<BasketService>>();\n\n    public class Results<T>\n    {\n        private readonly Queue<Func<T>> values = new Queue<Func<T>>();\n        public Results(T result) { values.Enqueue(() => result); }\n        public Results<T> Then(T value) { return Then(() => value); }\n        public Results<T> Then(Func<T> value)\n        {\n            values.Enqueue(value);\n            return this;\n        }\n        public T Next() { return values.Dequeue()(); }\n    }\n\n        [Fact]\n    public async Task InvokesBasketRepositoryFirstOrDefaultAsyncOnceIfAnonymousBasketNotExists()\n    {\n            var anonymousBasket = null as Basket;\n            var userBasket = new Basket(_existentUserBasketBuyerId);\n            \n        var results = new Results<Basket?>(anonymousBasket)\n                        .Then(userBasket);\n\n\n        _mockBasketRepo.FirstOrDefaultAsync(Arg.Any<BasketWithItemsSpecification>(), default).Returns(x => results.Next());          \n        var basketService = new BasketService(_mockBasketRepo, _mockLogger);\n        await basketService.TransferBasketAsync(_nonexistentAnonymousBasketBuyerId, _existentUserBasketBuyerId);\n        await _mockBasketRepo.Received().FirstOrDefaultAsync(Arg.Any<BasketWithItemsSpecification>(), default);\n    }\n\n    [Fact]\n    public async Task TransferAnonymousBasketItemsWhilePreservingExistingUserBasketItems()\n    {\n        var anonymousBasket = new Basket(_existentAnonymousBasketBuyerId);\n        anonymousBasket.AddItem(1, 10, 1);\n        anonymousBasket.AddItem(3, 55, 7);\n        var userBasket = new Basket(_existentUserBasketBuyerId);\n        userBasket.AddItem(1, 10, 4);\n        userBasket.AddItem(2, 99, 3);\n\n        var results = new Results<Basket>(anonymousBasket)\n                        .Then(userBasket);\n\n        _mockBasketRepo.FirstOrDefaultAsync(Arg.Any<BasketWithItemsSpecification>(), default).Returns(x => results.Next());\n        var basketService = new BasketService(_mockBasketRepo, _mockLogger);\n        await basketService.TransferBasketAsync(_nonexistentAnonymousBasketBuyerId, _existentUserBasketBuyerId);\n        await _mockBasketRepo.Received().UpdateAsync(userBasket, default);\n\n        Assert.Equal(3, userBasket.Items.Count);\n        Assert.Contains(userBasket.Items, x => x.CatalogItemId == 1 && x.UnitPrice == 10 && x.Quantity == 5);\n        Assert.Contains(userBasket.Items, x => x.CatalogItemId == 2 && x.UnitPrice == 99 && x.Quantity == 3);\n        Assert.Contains(userBasket.Items, x => x.CatalogItemId == 3 && x.UnitPrice == 55 && x.Quantity == 7);\n    }\n\n    [Fact]\n    public async Task RemovesAnonymousBasketAfterUpdatingUserBasket()\n    {\n        var anonymousBasket = new Basket(_existentAnonymousBasketBuyerId);\n        var userBasket = new Basket(_existentUserBasketBuyerId);\n\n        var results = new Results<Basket>(anonymousBasket)\n                        .Then(userBasket);\n\n        _mockBasketRepo.FirstOrDefaultAsync(Arg.Any<BasketWithItemsSpecification>(), default).Returns(x => results.Next());\n        var basketService = new BasketService(_mockBasketRepo, _mockLogger);\n        await basketService.TransferBasketAsync(_nonexistentAnonymousBasketBuyerId, _existentUserBasketBuyerId);\n        await _mockBasketRepo.Received().UpdateAsync(userBasket, default);\n        await _mockBasketRepo.Received().DeleteAsync(anonymousBasket, default);\n    }\n\n    [Fact]\n    public async Task CreatesNewUserBasketIfNotExists()\n    {\n        var anonymousBasket = new Basket(_existentAnonymousBasketBuyerId);\n        var userBasket = null as Basket;\n\n        var results = new Results<Basket?>(anonymousBasket)\n                       .Then(userBasket);\n\n        _mockBasketRepo.FirstOrDefaultAsync(Arg.Any<BasketWithItemsSpecification>(), default).Returns(x => results.Next());\n        var basketService = new BasketService(_mockBasketRepo, _mockLogger);\n        await basketService.TransferBasketAsync(_existentAnonymousBasketBuyerId, _nonexistentUserBasketBuyerId);\n        await _mockBasketRepo.Received().AddAsync(Arg.Is<Basket>(x => x.BuyerId == _nonexistentUserBasketBuyerId), default);\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Specifications/BasketWithItemsSpecification.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing NSubstitute;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Specifications;\n\npublic class BasketWithItems\n{\n    private readonly int _testBasketId = 123;\n    private readonly string _buyerId = \"Test buyerId\";\n\n    [Fact]\n    public void MatchesBasketWithGivenBasketId()\n    {\n        var spec = new BasketWithItemsSpecification(_testBasketId);\n\n        var result = spec.Evaluate(GetTestBasketCollection()).FirstOrDefault();\n\n        Assert.NotNull(result);\n        Assert.Equal(_testBasketId, result.Id);\n    }\n\n    [Fact]\n    public void MatchesNoBasketsIfBasketIdNotPresent()\n    {\n        int badBasketId = -1;\n        var spec = new BasketWithItemsSpecification(badBasketId);\n\n        var result = spec.Evaluate(GetTestBasketCollection()).Any();\n\n        Assert.False(result);\n    }\n\n    [Fact]\n    public void MatchesBasketWithGivenBuyerId()\n    {\n        var spec = new BasketWithItemsSpecification(_buyerId);\n\n        var result = spec.Evaluate(GetTestBasketCollection()).FirstOrDefault();\n\n        Assert.NotNull(result);\n        Assert.Equal(_buyerId, result.BuyerId);\n    }\n\n    [Fact]\n    public void MatchesNoBasketsIfBuyerIdNotPresent()\n    {\n        string badBuyerId = \"badBuyerId\";\n        var spec = new BasketWithItemsSpecification(badBuyerId);\n\n        var result = spec.Evaluate(GetTestBasketCollection()).Any();\n\n        Assert.False(result);\n    }\n\n    public List<Basket> GetTestBasketCollection()\n    {\n        var basket1Mock = Substitute.For<Basket>(_buyerId);\n        basket1Mock.Id.Returns(1);\n        var basket2Mock = Substitute.For<Basket>(_buyerId);\n        basket2Mock.Id.Returns(2);\n        var basket3Mock = Substitute.For<Basket>(_buyerId);\n        basket3Mock.Id.Returns(_testBasketId);\n\n        return new List<Basket>()\n            {\n                basket1Mock,\n                basket2Mock,\n                basket3Mock\n            };\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Specifications;\n\npublic class CatalogFilterPaginatedSpecification\n{\n    [Fact]\n    public void ReturnsAllCatalogItems()\n    {\n        var spec = new eShopWeb.ApplicationCore.Specifications.CatalogFilterPaginatedSpecification(0, 10, null, null);\n\n        var result = spec.Evaluate(GetTestCollection());\n\n        Assert.NotNull(result);\n        Assert.Equal(4, result.ToList().Count);\n    }\n\n    [Fact]\n    public void Returns2CatalogItemsWithSameBrandAndTypeId()\n    {\n        var spec = new eShopWeb.ApplicationCore.Specifications.CatalogFilterPaginatedSpecification(0, 10, 1, 1);\n\n        var result = spec.Evaluate(GetTestCollection()).ToList();\n\n        Assert.NotNull(result);\n        Assert.Equal(2, result.ToList().Count);\n    }\n\n    private List<CatalogItem> GetTestCollection()\n    {\n        var catalogItemList = new List<CatalogItem>();\n\n        catalogItemList.Add(new CatalogItem(1, 1, \"Item 1\", \"Item 1\", 1.00m, \"TestUri1\"));\n        catalogItemList.Add(new CatalogItem(1, 1, \"Item 1.5\", \"Item 1.5\", 1.50m, \"TestUri1\"));\n        catalogItemList.Add(new CatalogItem(2, 2, \"Item 2\", \"Item 2\", 2.00m, \"TestUri2\"));\n        catalogItemList.Add(new CatalogItem(3, 3, \"Item 3\", \"Item 3\", 3.00m, \"TestUri3\"));\n\n        return catalogItemList;\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Specifications/CatalogFilterSpecification.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Specifications;\n\npublic class CatalogFilterSpecification\n{\n    [Theory]\n    [InlineData(null, null, 5)]\n    [InlineData(1, null, 3)]\n    [InlineData(2, null, 2)]\n    [InlineData(null, 1, 2)]\n    [InlineData(null, 3, 1)]\n    [InlineData(1, 3, 1)]\n    [InlineData(2, 3, 0)]\n    public void MatchesExpectedNumberOfItems(int? brandId, int? typeId, int expectedCount)\n    {\n        var spec = new eShopWeb.ApplicationCore.Specifications.CatalogFilterSpecification(brandId, typeId);\n\n        var result = spec.Evaluate(GetTestItemCollection()).ToList();\n\n        Assert.Equal(expectedCount, result.Count());\n    }\n\n    public List<CatalogItem> GetTestItemCollection()\n    {\n        return new List<CatalogItem>()\n            {\n                new CatalogItem(1, 1, \"Description\", \"Name\", 0, \"FakePath\"),\n                new CatalogItem(2, 1, \"Description\", \"Name\", 0, \"FakePath\"),\n                new CatalogItem(3, 1, \"Description\", \"Name\", 0, \"FakePath\"),\n                new CatalogItem(1, 2, \"Description\", \"Name\", 0, \"FakePath\"),\n                new CatalogItem(2, 2, \"Description\", \"Name\", 0, \"FakePath\"),\n            };\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Specifications/CatalogItemsSpecification.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.eShopWeb.ApplicationCore.Entities;\nusing NSubstitute;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Specifications;\n\npublic class CatalogItemsSpecification\n{\n    [Fact]\n    public void MatchesSpecificCatalogItem()\n    {\n        var catalogItemIds = new int[] { 1 };\n        var spec = new eShopWeb.ApplicationCore.Specifications.CatalogItemsSpecification(catalogItemIds);\n\n        var result = spec.Evaluate(GetTestCollection()).ToList();\n\n        Assert.NotNull(result);\n        Assert.Single(result.ToList());\n    }\n\n    [Fact]\n    public void MatchesAllCatalogItems()\n    {\n        var catalogItemIds = new int[] { 1, 3 };\n        var spec = new eShopWeb.ApplicationCore.Specifications.CatalogItemsSpecification(catalogItemIds);\n\n        var result = spec.Evaluate(GetTestCollection()).ToList();\n\n        Assert.NotNull(result);\n        Assert.Equal(2, result.ToList().Count);\n    }\n\n    private List<CatalogItem> GetTestCollection()\n    {\n        var catalogItems = new List<CatalogItem>();\n\n        var mockCatalogItem1 = Substitute.For<CatalogItem>(1, 1, \"Item 1 description\", \"Item 1\", 1.5m, \"Item1Uri\");\n        mockCatalogItem1.Id.Returns(1);\n\n        var mockCatalogItem3 = Substitute.For<CatalogItem>(3, 3, \"Item 3 description\", \"Item 3\", 3.5m, \"Item3Uri\");\n        mockCatalogItem3.Id.Returns(3);\n\n        catalogItems.Add(mockCatalogItem1);\n        catalogItems.Add(mockCatalogItem3);\n\n        return catalogItems;\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/ApplicationCore/Specifications/CustomerOrdersWithItemsSpecification.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Specifications;\n\npublic class CustomerOrdersWithItemsSpecification\n{\n    private readonly string _buyerId = \"TestBuyerId\";\n    private Address _shipToAddress = new Address(\"Street\", \"City\", \"OH\", \"US\", \"11111\");\n\n    [Fact]\n    public void ReturnsOrderWithOrderedItem()\n    {\n        var spec = new eShopWeb.ApplicationCore.Specifications.CustomerOrdersWithItemsSpecification(_buyerId);\n\n        var result = spec.Evaluate(GetTestCollection()).FirstOrDefault();\n\n        Assert.NotNull(result);\n        Assert.NotNull(result.OrderItems);\n        Assert.Single(result.OrderItems);\n        Assert.NotNull(result.OrderItems.FirstOrDefault()?.ItemOrdered);\n    }\n\n    [Fact]\n    public void ReturnsAllOrderWithAllOrderedItem()\n    {\n        var spec = new eShopWeb.ApplicationCore.Specifications.CustomerOrdersWithItemsSpecification(_buyerId);\n\n        var result = spec.Evaluate(GetTestCollection()).ToList();\n\n        Assert.NotNull(result);\n        Assert.Equal(2, result.Count);\n        Assert.Single(result[0].OrderItems);\n        Assert.NotNull(result[0].OrderItems.FirstOrDefault()?.ItemOrdered);\n        Assert.Equal(2, result[1].OrderItems.Count);\n        Assert.NotNull(result[1].OrderItems.ToList()[0].ItemOrdered);\n        Assert.NotNull(result[1].OrderItems.ToList()[1].ItemOrdered);\n    }\n\n    public List<Order> GetTestCollection()\n    {\n        var ordersList = new List<Order>();\n\n        ordersList.Add(new Order(_buyerId, _shipToAddress,\n            new List<OrderItem>\n            {\n                    new OrderItem(new CatalogItemOrdered(1, \"Product1\", \"testurl\"), 10.50m, 1)\n            }));\n        ordersList.Add(new Order(_buyerId, _shipToAddress,\n            new List<OrderItem>\n            {\n                    new OrderItem(new CatalogItemOrdered(2, \"Product2\", \"testurl\"), 15.50m, 2),\n                    new OrderItem(new CatalogItemOrdered(2, \"Product3\", \"testurl\"), 20.50m, 1)\n            }));\n\n        return ordersList;\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/Builders/AddressBuilder.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\nnamespace Microsoft.eShopWeb.UnitTests.Builders;\n\npublic class AddressBuilder\n{\n    private Address _address;\n    public string TestStreet => \"123 Main St.\";\n    public string TestCity => \"Kent\";\n    public string TestState => \"OH\";\n    public string TestCountry => \"USA\";\n    public string TestZipCode => \"44240\";\n\n    public AddressBuilder()\n    {\n        _address = WithDefaultValues();\n    }\n    public Address Build()\n    {\n        return _address;\n    }\n    public Address WithDefaultValues()\n    {\n        _address = new Address(TestStreet, TestCity, TestState, TestCountry, TestZipCode);\n        return _address;\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/Builders/BasketBuilder.cs",
    "content": "﻿using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;\nusing NSubstitute;\n\nnamespace Microsoft.eShopWeb.UnitTests.Builders;\n\npublic class BasketBuilder\n{\n    private Basket _basket;\n    public string BasketBuyerId => \"testbuyerId@test.com\";\n\n    public int BasketId => 1;\n\n    public BasketBuilder()\n    {\n        _basket = WithNoItems();\n    }\n\n    public Basket Build()\n    {\n        return _basket;\n    }\n\n    public Basket WithNoItems()\n    {\n        var basketMock = Substitute.For<Basket>(BasketBuyerId);\n        basketMock.Id.Returns(BasketId);\n\n        _basket = basketMock;\n        return _basket;\n    }\n\n    public Basket WithOneBasketItem()\n    {\n        var basketMock = Substitute.For<Basket>(BasketBuyerId);\n        _basket = basketMock;\n        _basket.AddItem(2, 3.40m, 4);\n        return _basket;\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/Builders/OrderBuilder.cs",
    "content": "﻿using System.Collections.Generic;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\n\nnamespace Microsoft.eShopWeb.UnitTests.Builders;\n\npublic class OrderBuilder\n{\n    private Order _order;\n    public string TestBuyerId => \"12345\";\n    public int TestCatalogItemId => 234;\n    public string TestProductName => \"Test Product Name\";\n    public string TestPictureUri => \"http://test.com/image.jpg\";\n    public decimal TestUnitPrice = 1.23m;\n    public int TestUnits = 3;\n    public CatalogItemOrdered TestCatalogItemOrdered { get; }\n\n    public OrderBuilder()\n    {\n        TestCatalogItemOrdered = new CatalogItemOrdered(TestCatalogItemId, TestProductName, TestPictureUri);\n        _order = WithDefaultValues();\n    }\n\n    public Order Build()\n    {\n        return _order;\n    }\n\n    public Order WithDefaultValues()\n    {\n        var orderItem = new OrderItem(TestCatalogItemOrdered, TestUnitPrice, TestUnits);\n        var itemList = new List<OrderItem>() { orderItem };\n        _order = new Order(TestBuyerId, new AddressBuilder().WithDefaultValues(), itemList);\n        return _order;\n    }\n\n    public Order WithNoItems()\n    {\n        _order = new Order(TestBuyerId, new AddressBuilder().WithDefaultValues(), new List<OrderItem>());\n        return _order;\n    }\n\n    public Order WithItems(List<OrderItem> items)\n    {\n        _order = new Order(TestBuyerId, new AddressBuilder().WithDefaultValues(), items);\n        return _order;\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/MediatorHandlers/OrdersTests/GetMyOrders.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Ardalis.Specification;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.Web.Features.MyOrders;\nusing NSubstitute;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.MediatorHandlers.OrdersTests;\n\npublic class GetMyOrders\n{\n    private readonly IReadRepository<Order> _mockOrderRepository = Substitute.For<IReadRepository<Order>>();\n\n    public GetMyOrders()\n    {\n        var item = new OrderItem(new CatalogItemOrdered(1, \"ProductName\", \"URI\"), 10.00m, 10);\n        var address = new Address(\"\", \"\", \"\", \"\", \"\");\n        Order order = new Order(\"buyerId\", address, new List<OrderItem> { item });\n              \n        _mockOrderRepository.ListAsync(Arg.Any<ISpecification<Order>>(), default).Returns(new List<Order> { order });\n    }\n\n    [Fact]\n    public async Task NotReturnNullIfOrdersArePresIent()\n    {\n        var request = new eShopWeb.Web.Features.MyOrders.GetMyOrders(\"SomeUserName\");\n\n        var handler = new GetMyOrdersHandler(_mockOrderRepository);\n\n        var result = await handler.Handle(request, CancellationToken.None);\n\n        Assert.NotNull(result);\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/MediatorHandlers/OrdersTests/GetOrderDetails.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Ardalis.Specification;\nusing Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;\nusing Microsoft.eShopWeb.ApplicationCore.Interfaces;\nusing Microsoft.eShopWeb.ApplicationCore.Specifications;\nusing Microsoft.eShopWeb.Web.Features.OrderDetails;\nusing NSubstitute;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.MediatorHandlers.OrdersTests;\n\npublic class GetOrderDetails\n{\n    private readonly IReadRepository<Order> _mockOrderRepository =  Substitute.For<IReadRepository<Order>>();\n    \n    public GetOrderDetails()\n    {\n        var item = new OrderItem(new CatalogItemOrdered(1, \"ProductName\", \"URI\"), 10.00m, 10);\n        var address = new Address(\"\", \"\", \"\", \"\", \"\");\n        Order order = new Order(\"buyerId\", address, new List<OrderItem> { item });\n                \n        _mockOrderRepository.FirstOrDefaultAsync(Arg.Any<OrderWithItemsByIdSpec>(), default)\n            .Returns(order);\n    }\n\n    [Fact]\n    public async Task NotBeNullIfOrderExists()\n    {\n        var request = new eShopWeb.Web.Features.OrderDetails.GetOrderDetails(\"SomeUserName\", 0);\n\n        var handler = new GetOrderDetailsHandler(_mockOrderRepository);\n\n        var result = await handler.Handle(request, CancellationToken.None);\n\n        Assert.NotNull(result);\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/UnitTests.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>   \n    <Nullable>enable</Nullable>\n    <RootNamespace>Microsoft.eShopWeb.UnitTests</RootNamespace>\n    <IsPackable>false</IsPackable>\n    <LangVersion>latest</LangVersion>\n    <ImplicitUsings>enable</ImplicitUsings>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" />\n    <PackageReference Include=\"NSubstitute\" />\n    <PackageReference Include=\"NSubstitute.Analyzers.CSharp\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"xunit\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>      \n    <PackageReference Include=\"xunit.runner.console\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>     \n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\..\\src\\ApplicationCore\\ApplicationCore.csproj\" />\n    <ProjectReference Include=\"..\\..\\src\\Web\\Web.csproj\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Service Include=\"{82a7f48d-3b50-4b1e-b82e-3ada8210c358}\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Folder Include=\"ApplicationCore\\Helpers\\\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "tests/UnitTests/Web/Extensions/CacheHelpersTests/GenerateBrandsCacheKey.cs",
    "content": "﻿using Microsoft.eShopWeb.Web.Extensions;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests;\n\npublic class GenerateBrandsCacheKey\n{\n    [Fact]\n    public void ReturnsBrandsCacheKey()\n    {\n        var result = CacheHelpers.GenerateBrandsCacheKey();\n\n        Assert.Equal(\"brands\", result);\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/Web/Extensions/CacheHelpersTests/GenerateCatalogItemCacheKey.cs",
    "content": "﻿using Microsoft.eShopWeb.Web;\nusing Microsoft.eShopWeb.Web.Extensions;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests;\n\npublic class GenerateCatalogItemCacheKey\n{\n    [Fact]\n    public void ReturnsCatalogItemCacheKey()\n    {\n        var pageIndex = 0;\n        int? brandId = null;\n        int? typeId = null;\n\n        var result = CacheHelpers.GenerateCatalogItemCacheKey(pageIndex, Constants.ITEMS_PER_PAGE, brandId, typeId);\n\n        Assert.Equal(\"items-0-10--\", result);\n    }\n}\n"
  },
  {
    "path": "tests/UnitTests/Web/Extensions/CacheHelpersTests/GenerateTypesCacheKey.cs",
    "content": "﻿using Microsoft.eShopWeb.Web.Extensions;\nusing Xunit;\n\nnamespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests;\n\npublic class GenerateTypesCacheKey\n{\n    [Fact]\n    public void ReturnsTypesCacheKey()\n    {\n        var result = CacheHelpers.GenerateTypesCacheKey();\n\n        Assert.Equal(\"types\", result);\n    }\n}\n"
  }
]