Compare commits
No commits in common. "dev" and "release/net-5" have entirely different histories.
dev
...
release/ne
132
.editorconfig
132
.editorconfig
@ -1,132 +0,0 @@
|
||||
###############################
|
||||
# Core EditorConfig Options #
|
||||
###############################
|
||||
root = true
|
||||
# All files
|
||||
[*]
|
||||
indent_style = space
|
||||
|
||||
# XML project files
|
||||
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
|
||||
indent_size = 2
|
||||
|
||||
# XML config files
|
||||
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
|
||||
indent_size = 2
|
||||
|
||||
# Code files
|
||||
[*.{cs,csx,vb,vbx}]
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
charset = utf-8-bom
|
||||
###############################
|
||||
# .NET Coding Conventions #
|
||||
###############################
|
||||
[*.{cs,vb}]
|
||||
# Organize usings
|
||||
dotnet_sort_system_directives_first = true
|
||||
# this. preferences
|
||||
dotnet_style_qualification_for_field = false:silent
|
||||
dotnet_style_qualification_for_property = false:silent
|
||||
dotnet_style_qualification_for_method = false:silent
|
||||
dotnet_style_qualification_for_event = false:silent
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||
dotnet_style_predefined_type_for_member_access = true:silent
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
# Expression-level preferences
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
###############################
|
||||
# Naming Conventions #
|
||||
###############################
|
||||
# Style Definitions
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
# Use PascalCase for constant fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
###############################
|
||||
# C# Coding Conventions #
|
||||
###############################
|
||||
[*.cs]
|
||||
# var preferences
|
||||
csharp_style_var_for_built_in_types = true:silent
|
||||
csharp_style_var_when_type_is_apparent = true:silent
|
||||
csharp_style_var_elsewhere = true:silent
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
# Null-checking preferences
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
# Modifier preferences
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
|
||||
# Expression-level preferences
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
###############################
|
||||
# C# Formatting Rules #
|
||||
###############################
|
||||
# New line preferences
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
# Indentation preferences
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_indent_labels = flush_left
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_statements = true
|
||||
csharp_preserve_single_line_blocks = true
|
||||
###############################
|
||||
# VB Coding Conventions #
|
||||
###############################
|
||||
[*.vb]
|
||||
# Modifier preferences
|
||||
visual_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
|
2
.github/workflows/catalog-api.yml
vendored
2
.github/workflows/catalog-api.yml
vendored
@ -22,7 +22,7 @@ on:
|
||||
env:
|
||||
SERVICE: catalog-api
|
||||
IMAGE: catalog.api
|
||||
DOTNET_VERSION: 7.0.x
|
||||
DOTNET_VERSION: 5.0.x
|
||||
PROJECT_PATH: Services/Catalog/Catalog.API
|
||||
TESTS_PATH: Services/Catalog/Catalog.UnitTests
|
||||
|
||||
|
2
.github/workflows/ordering-api.yml
vendored
2
.github/workflows/ordering-api.yml
vendored
@ -22,7 +22,7 @@ on:
|
||||
env:
|
||||
SERVICE: ordering-api
|
||||
IMAGE: ordering.api
|
||||
DOTNET_VERSION: 7.0.x
|
||||
DOTNET_VERSION: 5.0.x
|
||||
PROJECT_PATH: Services/Ordering/Ordering.API
|
||||
TESTS_PATH: Services/Ordering/Ordering.UnitTests
|
||||
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -281,5 +281,3 @@ pub/
|
||||
src/**/app.yaml
|
||||
src/**/inf.yaml
|
||||
|
||||
.angular/
|
||||
/src/Services/Identity/Identity.API/keys/*.json
|
||||
|
@ -47,7 +47,7 @@ All contributions must be submitted as a [Pull Request (PR)](https://help.github
|
||||
The main branches are **`dev`** and **`master`**:
|
||||
|
||||
- **`dev`**: Contains the latest code **and it is the branch actively developed**.
|
||||
**All PRs must be against `dev` branch to be considered**. This branch is developed using `.NET 7`
|
||||
**All PRs must be against `dev` branch to be considered**. This branch is developed using `.NET 5`
|
||||
|
||||
- **`main`**: Synced from time to time from **`dev`**. It contains "stable" code.This branch contains changes specific to `.NET Core 3.1` (**Keep in mind "stable" does not mean PRODUCTION-READY!**)
|
||||
|
||||
|
@ -72,9 +72,9 @@ In the future, more features will be implemented in the advanced scenario.
|
||||
**NEWS / ANNOUNCEMENTS**
|
||||
Do you want to be up-to-date on .NET Architecture guidance and reference apps like eShopOnContainers? --> Subscribe by "WATCHING" this new GitHub repo: https://github.com/dotnet-architecture/News
|
||||
|
||||
## Updated for .NET 7
|
||||
## Updated for .NET 5
|
||||
|
||||
eShopOnContainers is updated to .NET 7 "wave" of technologies. Not just compilation but also new recommended code in EF Core, ASP.NET Core, and other new related versions with several significant changes.
|
||||
eShopOnContainers is updated to .NET 5 "wave" of technologies. Not just compilation but also new recommended code in EF Core, ASP.NET Core, and other new related versions with several significant changes.
|
||||
|
||||
**See more details in the [Release notes](https://github.com/dotnet-architecture/eShopOnContainers/wiki/Release-notes) wiki page**.
|
||||
|
||||
@ -86,7 +86,7 @@ eShopOnContainers is updated to .NET 7 "wave" of technologies. Not just compilat
|
||||
|
||||
### Architecture overview
|
||||
|
||||
This reference application is cross-platform at the server and client-side, thanks to .NET 7 services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS, or Windows/UWP plus any browser for the client web apps.
|
||||
This reference application is cross-platform at the server and client-side, thanks to .NET 5 services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS, or Windows/UWP plus any browser for the client web apps.
|
||||
The architecture proposes a microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using HTTP as the communication protocol between the client apps and the microservices and supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus (a light message broker, to choose between RabbitMQ or Azure Service Bus, underneath) plus other features defined at the [roadmap](https://github.com/dotnet-architecture/eShopOnContainers/wiki/Roadmap).
|
||||
|
||||

|
||||
|
@ -2,12 +2,8 @@
|
||||
|
||||
Following are the most important branches:
|
||||
|
||||
- `dev`: Contains the latest code **and it is the branch actively developed**. Note that **all PRs must be against the `dev` branch to be considered**. This branch is developed using `.NET 7`
|
||||
- `release/net-6`: Contains the code changes specific to the `.NET 6`
|
||||
- `release/net-5`: Contains the code changes specific to the `.NET 5`
|
||||
- `release/net-3.1.1`: Contains the code changes specific to the `.NET 3.1`
|
||||
|
||||
> [!DISCLAIMER]: The `main` branch contains the old code base and will get obsolete in the future. So it's recommended to refer to different [tags](https://github.com/dotnet-architecture/eShopOnContainers/tags) to avoid any confusion.
|
||||
- `dev`: Contains the latest code **and it is the branch actively developed**. Note that **all PRs must be against the `dev` branch to be considered**. This branch is developed using `.NET 5`
|
||||
- `main`: Synced time to time from `dev`.It contains "stable" code, although not the latest one. Right now, this branch contains changes specific to `.NET Core 3.1`
|
||||
|
||||
Any other branch is considered temporary and could be deleted at any time. Do not submit any PR against them!
|
||||
|
||||
|
@ -31,3 +31,5 @@ $services |% {
|
||||
Write-Host "Setting ACR build $bname ($bimg)"
|
||||
az acr build-task create --registry $acrName --name $bname --image ${bimg}:$gitBranch --context $gitContext --branch $gitBranch --git-access-token $patToken --file $bfile
|
||||
}
|
||||
|
||||
# Basket.API
|
||||
|
@ -9,20 +9,19 @@ static_resources:
|
||||
- address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 8080
|
||||
port_value: 80
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.filters.network.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
- name: envoy.http_connection_manager
|
||||
config:
|
||||
codec_type: auto
|
||||
stat_prefix: ingress_http
|
||||
codec_type: AUTO
|
||||
route_config:
|
||||
name: eshop_backend_route
|
||||
virtual_hosts:
|
||||
- name: eshop_backend
|
||||
domains:
|
||||
- ["*"]
|
||||
- "*"
|
||||
routes:
|
||||
- name: "c-short"
|
||||
match:
|
||||
@ -78,70 +77,63 @@ static_resources:
|
||||
prefix_rewrite: "/"
|
||||
cluster: shoppingagg
|
||||
http_filters:
|
||||
- name: envoy.filters.http.router
|
||||
- name: envoy.router
|
||||
access_log:
|
||||
- name: envoy.file_access_log
|
||||
filter:
|
||||
not_health_check_filter: {}
|
||||
config:
|
||||
json_format:
|
||||
time: "%START_TIME%"
|
||||
protocol: "%PROTOCOL%"
|
||||
duration: "%DURATION%"
|
||||
request_method: "%REQ(:METHOD)%"
|
||||
request_host: "%REQ(HOST)%"
|
||||
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
|
||||
response_flags: "%RESPONSE_FLAGS%"
|
||||
route_name: "%ROUTE_NAME%"
|
||||
upstream_host: "%UPSTREAM_HOST%"
|
||||
upstream_cluster: "%UPSTREAM_CLUSTER%"
|
||||
upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%"
|
||||
path: "/tmp/access.log"
|
||||
clusters:
|
||||
- name: shoppingagg
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: shoppingagg
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: webshoppingagg
|
||||
port_value: 80
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: webshoppingagg
|
||||
port_value: 80
|
||||
- name: catalog
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: catalog
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: catalog-api
|
||||
port_value: 80
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: catalog-api
|
||||
port_value: 80
|
||||
- name: basket
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: basket
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: basket-api
|
||||
port_value: 80
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: basket-api
|
||||
port_value: 80
|
||||
- name: ordering
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: ordering
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: ordering-api
|
||||
port_value: 80
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: ordering-api
|
||||
port_value: 80
|
||||
- name: signalr-hub
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: signalr-hub
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: ordering-signalrhub
|
||||
port_value: 80
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: ordering-signalrhub
|
||||
port_value: 80
|
||||
|
@ -27,10 +27,6 @@ spec:
|
||||
linkerd.io/inject: enabled
|
||||
{{- end }}
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: 2000
|
||||
runAsGroup: 3000
|
||||
fsGroup: 2000
|
||||
{{ if .Values.inf.registry -}}
|
||||
imagePullSecrets:
|
||||
- name: {{ .Values.inf.registry.secretName }}
|
||||
@ -92,7 +88,7 @@ spec:
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
- name: admin
|
||||
containerPort: 8001
|
||||
|
@ -39,7 +39,7 @@ spec:
|
||||
- host: {{ . }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ $ingressPath }}(/|$)(.*)
|
||||
- path: {{ $ingressPath }}
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
|
@ -4,7 +4,7 @@ pathBase: /mobileshoppingapigw
|
||||
|
||||
image:
|
||||
repository: envoyproxy/envoy
|
||||
tag: v1.21.0
|
||||
tag: v1.11.1
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
@ -14,9 +14,8 @@ service:
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||
nginx.ingress.kubernetes.io/rewrite-target: "/"
|
||||
ingress.kubernetes.io/rewrite-target: "/"
|
||||
tls: []
|
||||
|
||||
resources: {}
|
||||
|
@ -9,20 +9,19 @@ static_resources:
|
||||
- address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 8080
|
||||
port_value: 80
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.filters.network.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
- name: envoy.http_connection_manager
|
||||
config:
|
||||
codec_type: auto
|
||||
stat_prefix: ingress_http
|
||||
codec_type: AUTO
|
||||
route_config:
|
||||
name: eshop_backend_route
|
||||
virtual_hosts:
|
||||
- name: eshop_backend
|
||||
domains:
|
||||
- ["*"]
|
||||
- "*"
|
||||
routes:
|
||||
- name: "c-short"
|
||||
match:
|
||||
@ -81,70 +80,63 @@ static_resources:
|
||||
prefix_rewrite: "/"
|
||||
cluster: shoppingagg
|
||||
http_filters:
|
||||
- name: envoy.filters.http.router
|
||||
- name: envoy.router
|
||||
access_log:
|
||||
- name: envoy.file_access_log
|
||||
filter:
|
||||
not_health_check_filter: {}
|
||||
config:
|
||||
json_format:
|
||||
time: "%START_TIME%"
|
||||
protocol: "%PROTOCOL%"
|
||||
duration: "%DURATION%"
|
||||
request_method: "%REQ(:METHOD)%"
|
||||
request_host: "%REQ(HOST)%"
|
||||
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
|
||||
response_flags: "%RESPONSE_FLAGS%"
|
||||
route_name: "%ROUTE_NAME%"
|
||||
upstream_host: "%UPSTREAM_HOST%"
|
||||
upstream_cluster: "%UPSTREAM_CLUSTER%"
|
||||
upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%"
|
||||
path: "/tmp/access.log"
|
||||
clusters:
|
||||
- name: shoppingagg
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: shoppingagg
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: webshoppingagg
|
||||
port_value: 80
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: webshoppingagg
|
||||
port_value: 80
|
||||
- name: catalog
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: catalog
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: catalog-api
|
||||
port_value: 80
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: catalog-api
|
||||
port_value: 80
|
||||
- name: basket
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: basket
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: basket-api
|
||||
port_value: 80
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: basket-api
|
||||
port_value: 80
|
||||
- name: ordering
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: ordering
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: ordering-api
|
||||
port_value: 80
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: ordering-api
|
||||
port_value: 80
|
||||
- name: signalr-hub
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: signalr-hub
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: ordering-signalrhub
|
||||
port_value: 80
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: ordering-signalrhub
|
||||
port_value: 80
|
||||
|
@ -26,10 +26,6 @@ spec:
|
||||
linkerd.io/inject: enabled
|
||||
{{- end }}
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: 2000
|
||||
runAsGroup: 3000
|
||||
fsGroup: 2000
|
||||
{{ if .Values.inf.registry -}}
|
||||
imagePullSecrets:
|
||||
- name: {{ .Values.inf.registry.secretName }}
|
||||
@ -91,7 +87,7 @@ spec:
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
- name: admin
|
||||
containerPort: 8001
|
||||
|
@ -38,7 +38,7 @@ spec:
|
||||
- host: {{ . }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ $ingressPath }}(/|$)(.*)
|
||||
- path: {{ $ingressPath }}
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
|
@ -4,7 +4,7 @@ pathBase: /webshoppingapigw
|
||||
|
||||
image:
|
||||
repository: envoyproxy/envoy
|
||||
tag: v1.21.0
|
||||
tag: v1.11.1
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
@ -14,9 +14,8 @@ service:
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||
nginx.ingress.kubernetes.io/rewrite-target: "/"
|
||||
ingress.kubernetes.io/rewrite-target: "/"
|
||||
tls: []
|
||||
|
||||
resources: {}
|
||||
|
@ -13,7 +13,7 @@ metadata:
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
data:
|
||||
catalog__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.catalog.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
|
||||
catalog__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.catalog.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};
|
||||
catalog__PicBaseUrl: {{ $protocol }}://{{ $webshoppingapigw }}/c/api/v1/catalog/items/[0]/pic/
|
||||
catalog__AzureStorageEnabled: "{{ .Values.inf.misc.useAzureStorage }}"
|
||||
all__EventBusConnection: {{ .Values.inf.eventbus.constr }}
|
||||
|
@ -20,7 +20,7 @@ metadata:
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
data:
|
||||
identity__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.identity.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
|
||||
identity__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.identity.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};
|
||||
identity__keystore: {{ .Values.inf.redis.keystore.constr }}
|
||||
all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}"
|
||||
mvc_e: http://{{ $mvc_url }}
|
||||
|
@ -13,7 +13,6 @@ inf:
|
||||
user: sa # SQL user
|
||||
pwd: Pass@word # SQL pwd
|
||||
pid: Developer
|
||||
TrustServerCertificate: true
|
||||
catalog: # inf.sql.catalog: settings for the catalog-api sql (user, pwd, db)
|
||||
db: CatalogDb # Catalog API SQL db name
|
||||
ordering: # inf.sql.ordering: settings for the ordering-api sql (user, pwd, db)
|
||||
|
@ -11,7 +11,7 @@ metadata:
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
data:
|
||||
ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
|
||||
ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};
|
||||
urls__IdentityUrl: http://{{ .Values.app.svc.identity }}
|
||||
all__EventBusConnection: {{ .Values.inf.eventbus.constr }}
|
||||
all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}"
|
||||
|
@ -12,7 +12,7 @@ metadata:
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
data:
|
||||
ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
|
||||
ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};
|
||||
ordering__EnableLoadTest: "{{ .Values.inf.misc.useLoadTest }}"
|
||||
all__EventBusConnection: {{ .Values.inf.eventbus.constr }}
|
||||
all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}"
|
||||
|
@ -13,7 +13,7 @@ metadata:
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
data:
|
||||
webhooks__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.webhooks.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
|
||||
webhooks__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.webhooks.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};
|
||||
urls__IdentityUrl: http://{{ $identity }}
|
||||
urls__IdentityUrlExternal: {{ $protocol }}://{{ $identity }}
|
||||
all__EventBusConnection: {{ .Values.inf.eventbus.constr }}
|
||||
|
@ -4,7 +4,7 @@ metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: ingress-nginx
|
||||
app.kubernetes.io/part-of: ingress-nginx
|
||||
name: ingress-nginx-controller
|
||||
name: nginx-configuration
|
||||
namespace: ingress-nginx
|
||||
data:
|
||||
proxy-buffer-size: "128k"
|
||||
|
@ -11,16 +11,15 @@ metadata:
|
||||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- host: localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /webmvc
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: webmvc
|
||||
port:
|
||||
number: 80
|
||||
http:
|
||||
paths:
|
||||
- path: /webmvc
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: webmvc
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
@ -35,13 +34,12 @@ metadata:
|
||||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- host: localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /identity
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: identity
|
||||
port:
|
||||
number: 80
|
||||
http:
|
||||
paths:
|
||||
- path: /identity
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: identity
|
||||
port:
|
||||
number: 80
|
||||
|
Binary file not shown.
6
src/.env
6
src/.env
@ -6,15 +6,15 @@
|
||||
|
||||
# Use this values to run the app locally in Windows
|
||||
ESHOP_EXTERNAL_DNS_NAME_OR_IP=host.docker.internal
|
||||
ESHOP_STORAGE_CATALOG_URL=http://host.docker.internal:5121/c/api/v1/catalog/items/[0]/pic/
|
||||
ESHOP_STORAGE_CATALOG_URL=http://host.docker.internal:5202/c/api/v1/catalog/items/[0]/pic/
|
||||
|
||||
# Use this values to run the app locally in Mac
|
||||
# ESHOP_EXTERNAL_DNS_NAME_OR_IP=docker.for.mac.localhost
|
||||
# ESHOP_STORAGE_CATALOG_URL=http://docker.for.mac.localhost:5121/c/api/v1/catalog/items/[0]/pic/
|
||||
# ESHOP_STORAGE_CATALOG_URL=http://docker.for.mac.localhost:5202/c/api/v1/catalog/items/[0]/pic/
|
||||
|
||||
# Use this values to run the app locally in Linux
|
||||
# ESHOP_EXTERNAL_DNS_NAME_OR_IP=docker.for.linux.localhost
|
||||
# ESHOP_STORAGE_CATALOG_URL=http://docker.for.linux.localhost:5121/c/api/v1/catalog/items/[0]/pic/
|
||||
# ESHOP_STORAGE_CATALOG_URL=http://docker.for.linux.localhost:5202/c/api/v1/catalog/items/[0]/pic/
|
||||
|
||||
# Configure this values to the cloud storage locations
|
||||
# ESHOP_STORAGE_CATALOG_URL=<YourAzureStorage_Catalog_BLOB_URL>
|
||||
|
139
src/ApiGateways/Envoy/config/mobileshopping/envoy.yaml
Normal file
139
src/ApiGateways/Envoy/config/mobileshopping/envoy.yaml
Normal file
@ -0,0 +1,139 @@
|
||||
admin:
|
||||
access_log_path: "/dev/null"
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 8001
|
||||
static_resources:
|
||||
listeners:
|
||||
- address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 80
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.http_connection_manager
|
||||
config:
|
||||
codec_type: auto
|
||||
stat_prefix: ingress_http
|
||||
route_config:
|
||||
name: eshop_backend_route
|
||||
virtual_hosts:
|
||||
- name: eshop_backend
|
||||
domains:
|
||||
- "*"
|
||||
routes:
|
||||
- name: "c-short"
|
||||
match:
|
||||
prefix: "/c/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
prefix_rewrite: "/catalog-api/"
|
||||
cluster: catalog
|
||||
- name: "c-long"
|
||||
match:
|
||||
prefix: "/catalog-api/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
cluster: catalog
|
||||
- name: "o-short"
|
||||
match:
|
||||
prefix: "/o/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
prefix_rewrite: "/ordering-api/"
|
||||
cluster: ordering
|
||||
- name: "o-long"
|
||||
match:
|
||||
prefix: "/ordering-api/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
cluster: ordering
|
||||
- name: "h-long"
|
||||
match:
|
||||
prefix: "/hub/notificationhub"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
cluster: signalr-hub
|
||||
timeout: 300s
|
||||
- name: "b-short"
|
||||
match:
|
||||
prefix: "/b/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
prefix_rewrite: "/basket-api/"
|
||||
cluster: basket
|
||||
- name: "b-long"
|
||||
match:
|
||||
prefix: "/basket-api/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
cluster: basket
|
||||
- name: "agg"
|
||||
match:
|
||||
prefix: "/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
prefix_rewrite: "/"
|
||||
cluster: shoppingagg
|
||||
http_filters:
|
||||
- name: envoy.router
|
||||
access_log:
|
||||
- name: envoy.file_access_log
|
||||
filter:
|
||||
not_health_check_filter: {}
|
||||
config:
|
||||
json_format:
|
||||
time: "%START_TIME%"
|
||||
protocol: "%PROTOCOL%"
|
||||
duration: "%DURATION%"
|
||||
request_method: "%REQ(:METHOD)%"
|
||||
request_host: "%REQ(HOST)%"
|
||||
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
|
||||
response_flags: "%RESPONSE_FLAGS%"
|
||||
route_name: "%ROUTE_NAME%"
|
||||
upstream_host: "%UPSTREAM_HOST%"
|
||||
upstream_cluster: "%UPSTREAM_CLUSTER%"
|
||||
upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%"
|
||||
path: "/tmp/access.log"
|
||||
clusters:
|
||||
- name: shoppingagg
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: mobileshoppingagg
|
||||
port_value: 80
|
||||
- name: catalog
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: catalog-api
|
||||
port_value: 80
|
||||
- name: basket
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: basket-api
|
||||
port_value: 80
|
||||
- name: ordering
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: ordering-api
|
||||
port_value: 80
|
||||
- name: signalr-hub
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: ordering-signalrhub
|
||||
port_value: 80
|
142
src/ApiGateways/Envoy/config/webshopping/envoy.yaml
Normal file
142
src/ApiGateways/Envoy/config/webshopping/envoy.yaml
Normal file
@ -0,0 +1,142 @@
|
||||
admin:
|
||||
access_log_path: "/dev/null"
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 8001
|
||||
static_resources:
|
||||
listeners:
|
||||
- address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 80
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.http_connection_manager
|
||||
config:
|
||||
codec_type: auto
|
||||
stat_prefix: ingress_http
|
||||
route_config:
|
||||
name: eshop_backend_route
|
||||
virtual_hosts:
|
||||
- name: eshop_backend
|
||||
domains:
|
||||
- "*"
|
||||
routes:
|
||||
- name: "c-short"
|
||||
match:
|
||||
prefix: "/c/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
prefix_rewrite: "/catalog-api/"
|
||||
cluster: catalog
|
||||
- name: "c-long"
|
||||
match:
|
||||
prefix: "/catalog-api/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
cluster: catalog
|
||||
- name: "o-short"
|
||||
match:
|
||||
prefix: "/o/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
prefix_rewrite: "/ordering-api/"
|
||||
cluster: ordering
|
||||
- name: "o-long"
|
||||
match:
|
||||
prefix: "/ordering-api/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
cluster: ordering
|
||||
- name: "h-long"
|
||||
match:
|
||||
prefix: "/hub/notificationhub"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
cluster: signalr-hub
|
||||
timeout: 300s
|
||||
upgrade_configs:
|
||||
upgrade_type: "websocket"
|
||||
enabled: true
|
||||
- name: "b-short"
|
||||
match:
|
||||
prefix: "/b/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
prefix_rewrite: "/basket-api/"
|
||||
cluster: basket
|
||||
- name: "b-long"
|
||||
match:
|
||||
prefix: "/basket-api/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
cluster: basket
|
||||
- name: "agg"
|
||||
match:
|
||||
prefix: "/"
|
||||
route:
|
||||
auto_host_rewrite: true
|
||||
prefix_rewrite: "/"
|
||||
cluster: shoppingagg
|
||||
http_filters:
|
||||
- name: envoy.router
|
||||
access_log:
|
||||
- name: envoy.file_access_log
|
||||
filter:
|
||||
not_health_check_filter: {}
|
||||
config:
|
||||
json_format:
|
||||
time: "%START_TIME%"
|
||||
protocol: "%PROTOCOL%"
|
||||
duration: "%DURATION%"
|
||||
request_method: "%REQ(:METHOD)%"
|
||||
request_host: "%REQ(HOST)%"
|
||||
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
|
||||
response_flags: "%RESPONSE_FLAGS%"
|
||||
route_name: "%ROUTE_NAME%"
|
||||
upstream_host: "%UPSTREAM_HOST%"
|
||||
upstream_cluster: "%UPSTREAM_CLUSTER%"
|
||||
upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%"
|
||||
path: "/tmp/access.log"
|
||||
clusters:
|
||||
- name: shoppingagg
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: webshoppingagg
|
||||
port_value: 80
|
||||
- name: catalog
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: catalog-api
|
||||
port_value: 80
|
||||
- name: basket
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: basket-api
|
||||
port_value: 80
|
||||
- name: ordering
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: ordering-api
|
||||
port_value: 80
|
||||
- name: signalr-hub
|
||||
connect_timeout: 0.25s
|
||||
type: strict_dns
|
||||
lb_policy: round_robin
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: ordering-signalrhub
|
||||
port_value: 80
|
@ -1,35 +1,38 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class UrlsConfig
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config
|
||||
{
|
||||
public class CatalogOperations
|
||||
public class UrlsConfig
|
||||
{
|
||||
public static string GetItemById(int id) => $"/api/v1/catalog/items/{id}";
|
||||
public class CatalogOperations
|
||||
{
|
||||
public static string GetItemById(int id) => $"/api/v1/catalog/items/{id}";
|
||||
|
||||
public static string GetItemsById(IEnumerable<int> ids) => $"/api/v1/catalog/items?ids={string.Join(',', ids)}";
|
||||
public static string GetItemsById(IEnumerable<int> ids) => $"/api/v1/catalog/items?ids={string.Join(',', ids)}";
|
||||
}
|
||||
|
||||
public class BasketOperations
|
||||
{
|
||||
public static string GetItemById(string id) => $"/api/v1/basket/{id}";
|
||||
|
||||
public static string UpdateBasket() => "/api/v1/basket";
|
||||
}
|
||||
|
||||
public class OrdersOperations
|
||||
{
|
||||
public static string GetOrderDraft() => "/api/v1/orders/draft";
|
||||
}
|
||||
|
||||
public string Basket { get; set; }
|
||||
|
||||
public string Catalog { get; set; }
|
||||
|
||||
public string Orders { get; set; }
|
||||
|
||||
public string GrpcBasket { get; set; }
|
||||
|
||||
public string GrpcCatalog { get; set; }
|
||||
|
||||
public string GrpcOrdering { get; set; }
|
||||
}
|
||||
|
||||
public class BasketOperations
|
||||
{
|
||||
public static string GetItemById(string id) => $"/api/v1/basket/{id}";
|
||||
|
||||
public static string UpdateBasket() => "/api/v1/basket";
|
||||
}
|
||||
|
||||
public class OrdersOperations
|
||||
{
|
||||
public static string GetOrderDraft() => "/api/v1/orders/draft";
|
||||
}
|
||||
|
||||
public string Basket { get; set; }
|
||||
|
||||
public string Catalog { get; set; }
|
||||
|
||||
public string Orders { get; set; }
|
||||
|
||||
public string GrpcBasket { get; set; }
|
||||
|
||||
public string GrpcCatalog { get; set; }
|
||||
|
||||
public string GrpcOrdering { get; set; }
|
||||
}
|
||||
|
@ -1,143 +1,156 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
public class BasketController : ControllerBase
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers
|
||||
{
|
||||
private readonly ICatalogService _catalog;
|
||||
private readonly IBasketService _basket;
|
||||
|
||||
public BasketController(ICatalogService catalogService, IBasketService basketService)
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
public class BasketController : ControllerBase
|
||||
{
|
||||
_catalog = catalogService;
|
||||
_basket = basketService;
|
||||
}
|
||||
private readonly ICatalogService _catalog;
|
||||
private readonly IBasketService _basket;
|
||||
|
||||
[HttpPost]
|
||||
[HttpPut]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<BasketData>> UpdateAllBasketAsync([FromBody] UpdateBasketRequest data)
|
||||
{
|
||||
if (data.Items == null || !data.Items.Any())
|
||||
public BasketController(ICatalogService catalogService, IBasketService basketService)
|
||||
{
|
||||
return BadRequest("Need to pass at least one basket line");
|
||||
_catalog = catalogService;
|
||||
_basket = basketService;
|
||||
}
|
||||
|
||||
// Retrieve the current basket
|
||||
var basket = await _basket.GetByIdAsync(data.BuyerId) ?? new BasketData(data.BuyerId);
|
||||
var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId));
|
||||
// group by product id to avoid duplicates
|
||||
var itemsCalculated = data
|
||||
.Items
|
||||
.GroupBy(x => x.ProductId, x => x, (k, i) => new { productId = k, items = i })
|
||||
.Select(groupedItem =>
|
||||
[HttpPost]
|
||||
[HttpPut]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)]
|
||||
public async Task<ActionResult<BasketData>> UpdateAllBasketAsync([FromBody] UpdateBasketRequest data)
|
||||
{
|
||||
if (data.Items == null || !data.Items.Any())
|
||||
{
|
||||
return BadRequest("Need to pass at least one basket line");
|
||||
}
|
||||
|
||||
// Retrieve the current basket
|
||||
var basket = await _basket.GetById(data.BuyerId) ?? new BasketData(data.BuyerId);
|
||||
|
||||
var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId));
|
||||
// group by product id to avoid duplicates
|
||||
var itemsCalculated = data
|
||||
.Items
|
||||
.GroupBy(x => x.ProductId, x => x, (k, i) => new { productId = k, items = i })
|
||||
.Select(groupedItem =>
|
||||
{
|
||||
var item = groupedItem.items.First();
|
||||
item.Quantity = groupedItem.items.Sum(i => i.Quantity);
|
||||
return item;
|
||||
});
|
||||
|
||||
foreach (var bitem in itemsCalculated)
|
||||
{
|
||||
var catalogItem = catalogItems.SingleOrDefault(ci => ci.Id == bitem.ProductId);
|
||||
if (catalogItem == null)
|
||||
{
|
||||
var item = groupedItem.items.First();
|
||||
item.Quantity = groupedItem.items.Sum(i => i.Quantity);
|
||||
return item;
|
||||
});
|
||||
return BadRequest($"Basket refers to a non-existing catalog item ({bitem.ProductId})");
|
||||
}
|
||||
|
||||
foreach (var bitem in itemsCalculated)
|
||||
{
|
||||
var catalogItem = catalogItems.SingleOrDefault(ci => ci.Id == bitem.ProductId);
|
||||
if (catalogItem == null)
|
||||
{
|
||||
return BadRequest($"Basket refers to a non-existing catalog item ({bitem.ProductId})");
|
||||
}
|
||||
|
||||
var itemInBasket = basket.Items.FirstOrDefault(x => x.ProductId == bitem.ProductId);
|
||||
if (itemInBasket == null)
|
||||
{
|
||||
basket.Items.Add(new BasketDataItem()
|
||||
var itemInBasket = basket.Items.FirstOrDefault(x => x.ProductId == bitem.ProductId);
|
||||
if (itemInBasket == null)
|
||||
{
|
||||
Id = bitem.Id,
|
||||
ProductId = catalogItem.Id,
|
||||
ProductName = catalogItem.Name,
|
||||
PictureUrl = catalogItem.PictureUri,
|
||||
UnitPrice = catalogItem.Price,
|
||||
Quantity = bitem.Quantity
|
||||
});
|
||||
basket.Items.Add(new BasketDataItem()
|
||||
{
|
||||
Id = bitem.Id,
|
||||
ProductId = catalogItem.Id,
|
||||
ProductName = catalogItem.Name,
|
||||
PictureUrl = catalogItem.PictureUri,
|
||||
UnitPrice = catalogItem.Price,
|
||||
Quantity = bitem.Quantity
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
itemInBasket.Quantity = bitem.Quantity;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
await _basket.UpdateAsync(basket);
|
||||
|
||||
return basket;
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[Route("items")]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)]
|
||||
public async Task<ActionResult<BasketData>> UpdateQuantitiesAsync([FromBody] UpdateBasketItemsRequest data)
|
||||
{
|
||||
if (!data.Updates.Any())
|
||||
{
|
||||
itemInBasket.Quantity = bitem.Quantity;
|
||||
return BadRequest("No updates sent");
|
||||
}
|
||||
}
|
||||
|
||||
await _basket.UpdateAsync(basket);
|
||||
|
||||
return basket;
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[Route("items")]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<BasketData>> UpdateQuantitiesAsync([FromBody] UpdateBasketItemsRequest data)
|
||||
{
|
||||
if (!data.Updates.Any())
|
||||
{
|
||||
return BadRequest("No updates sent");
|
||||
}
|
||||
|
||||
// Retrieve the current basket
|
||||
var currentBasket = await _basket.GetByIdAsync(data.BasketId);
|
||||
if (currentBasket == null)
|
||||
{
|
||||
return BadRequest($"Basket with id {data.BasketId} not found.");
|
||||
}
|
||||
|
||||
// Update with new quantities
|
||||
foreach (var update in data.Updates)
|
||||
{
|
||||
var basketItem = currentBasket.Items.SingleOrDefault(bitem => bitem.Id == update.BasketItemId);
|
||||
|
||||
if (basketItem == null)
|
||||
// Retrieve the current basket
|
||||
var currentBasket = await _basket.GetById(data.BasketId);
|
||||
if (currentBasket == null)
|
||||
{
|
||||
return BadRequest($"Basket item with id {update.BasketItemId} not found");
|
||||
return BadRequest($"Basket with id {data.BasketId} not found.");
|
||||
}
|
||||
|
||||
basketItem.Quantity = update.NewQty;
|
||||
// Update with new quantities
|
||||
foreach (var update in data.Updates)
|
||||
{
|
||||
var basketItem = currentBasket.Items.SingleOrDefault(bitem => bitem.Id == update.BasketItemId);
|
||||
|
||||
if (basketItem == null)
|
||||
{
|
||||
return BadRequest($"Basket item with id {update.BasketItemId} not found");
|
||||
}
|
||||
|
||||
basketItem.Quantity = update.NewQty;
|
||||
}
|
||||
|
||||
// Save the updated basket
|
||||
await _basket.UpdateAsync(currentBasket);
|
||||
|
||||
return currentBasket;
|
||||
}
|
||||
|
||||
// Save the updated basket
|
||||
await _basket.UpdateAsync(currentBasket);
|
||||
|
||||
return currentBasket;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("items")]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult> AddBasketItemAsync([FromBody] AddBasketItemRequest data)
|
||||
{
|
||||
if (data == null || data.Quantity == 0)
|
||||
[HttpPost]
|
||||
[Route("items")]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType((int)HttpStatusCode.OK)]
|
||||
public async Task<ActionResult> AddBasketItemAsync([FromBody] AddBasketItemRequest data)
|
||||
{
|
||||
return BadRequest("Invalid payload");
|
||||
if (data == null || data.Quantity == 0)
|
||||
{
|
||||
return BadRequest("Invalid payload");
|
||||
}
|
||||
|
||||
// Step 1: Get the item from catalog
|
||||
var item = await _catalog.GetCatalogItemAsync(data.CatalogItemId);
|
||||
|
||||
//item.PictureUri =
|
||||
|
||||
// Step 2: Get current basket status
|
||||
var currentBasket = (await _basket.GetById(data.BasketId)) ?? new BasketData(data.BasketId);
|
||||
// Step 3: Merge current status with new product
|
||||
currentBasket.Items.Add(new BasketDataItem()
|
||||
{
|
||||
UnitPrice = item.Price,
|
||||
PictureUrl = item.PictureUri,
|
||||
ProductId = item.Id,
|
||||
ProductName = item.Name,
|
||||
Quantity = data.Quantity,
|
||||
Id = Guid.NewGuid().ToString()
|
||||
});
|
||||
|
||||
// Step 4: Update basket
|
||||
await _basket.UpdateAsync(currentBasket);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// Step 1: Get the item from catalog
|
||||
var item = await _catalog.GetCatalogItemAsync(data.CatalogItemId);
|
||||
|
||||
//item.PictureUri =
|
||||
|
||||
// Step 2: Get current basket status
|
||||
var currentBasket = (await _basket.GetByIdAsync(data.BasketId)) ?? new BasketData(data.BasketId);
|
||||
// Step 3: Merge current status with new product
|
||||
currentBasket.Items.Add(new BasketDataItem()
|
||||
{
|
||||
UnitPrice = item.Price,
|
||||
PictureUrl = item.PictureUri,
|
||||
ProductId = item.Id,
|
||||
ProductName = item.Name,
|
||||
Quantity = data.Quantity,
|
||||
Id = Guid.NewGuid().ToString()
|
||||
});
|
||||
|
||||
// Step 4: Update basket
|
||||
await _basket.UpdateAsync(currentBasket);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers
|
||||
{
|
||||
[Route("")]
|
||||
public class HomeController : Controller
|
||||
{
|
||||
[HttpGet()]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return new RedirectResult("~/swagger");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +1,45 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
public class OrderController : ControllerBase
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers
|
||||
{
|
||||
private readonly IBasketService _basketService;
|
||||
private readonly IOrderingService _orderingService;
|
||||
|
||||
public OrderController(IBasketService basketService, IOrderingService orderingService)
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
public class OrderController : ControllerBase
|
||||
{
|
||||
_basketService = basketService;
|
||||
_orderingService = orderingService;
|
||||
}
|
||||
private readonly IBasketService _basketService;
|
||||
private readonly IOrderingService _orderingService;
|
||||
|
||||
[Route("draft/{basketId}")]
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<OrderData>> GetOrderDraftAsync(string basketId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(basketId))
|
||||
public OrderController(IBasketService basketService, IOrderingService orderingService)
|
||||
{
|
||||
return BadRequest("Need a valid basketid");
|
||||
}
|
||||
// Get the basket data and build a order draft based on it
|
||||
var basket = await _basketService.GetByIdAsync(basketId);
|
||||
|
||||
if (basket == null)
|
||||
{
|
||||
return BadRequest($"No basket found for id {basketId}");
|
||||
_basketService = basketService;
|
||||
_orderingService = orderingService;
|
||||
}
|
||||
|
||||
return await _orderingService.GetOrderDraftAsync(basket);
|
||||
[Route("draft/{basketId}")]
|
||||
[HttpGet]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(OrderData), (int)HttpStatusCode.OK)]
|
||||
public async Task<ActionResult<OrderData>> GetOrderDraftAsync(string basketId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(basketId))
|
||||
{
|
||||
return BadRequest("Need a valid basketid");
|
||||
}
|
||||
// Get the basket data and build a order draft based on it
|
||||
var basket = await _basketService.GetById(basketId);
|
||||
|
||||
if (basket == null)
|
||||
{
|
||||
return BadRequest($"No basket found for id {basketId}");
|
||||
}
|
||||
|
||||
return await _orderingService.GetOrderDraftAsync(basket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles
|
||||
@ -11,6 +11,7 @@ COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWe
|
||||
|
||||
COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj"
|
||||
COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj"
|
||||
COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj"
|
||||
COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj"
|
||||
COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj"
|
||||
COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj"
|
||||
@ -32,8 +33,6 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj"
|
||||
COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj"
|
||||
COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj"
|
||||
COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj"
|
||||
COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj"
|
||||
COPY "Services/Contact/Contact.API/Contact.API.csproj" "Services/Contact/Contact.API/Contact.API.csproj"
|
||||
COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj"
|
||||
COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj"
|
||||
COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj"
|
||||
@ -43,7 +42,6 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj"
|
||||
|
||||
COPY "docker-compose.dcproj" "docker-compose.dcproj"
|
||||
|
||||
COPY "Directory.Packages.props" "Directory.Packages.props"
|
||||
COPY "NuGet.config" "NuGet.config"
|
||||
|
||||
RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln"
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:7.0
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0
|
||||
ARG BUILD_CONFIGURATION=Debug
|
||||
ENV ASPNETCORE_ENVIRONMENT=Development
|
||||
ENV DOTNET_USE_POLLING_FILE_WATCHER=true
|
||||
@ -6,6 +6,7 @@ EXPOSE 80
|
||||
|
||||
WORKDIR /src
|
||||
COPY ["src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj", "src/ApiGateways/Mobile.Bff.Shopping/aggregator/"]
|
||||
COPY ["src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj", "src/BuildingBlocks/Devspaces.Support/"]
|
||||
COPY ["src/NuGet.config", "src/NuGet.config"]
|
||||
|
||||
RUN dotnet restore src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503
|
||||
|
@ -1,62 +0,0 @@
|
||||
internal static class Extensions
|
||||
{
|
||||
public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy"));
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" })
|
||||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" })
|
||||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" })
|
||||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" });
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
|
||||
{
|
||||
// Register delegating handlers
|
||||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
|
||||
|
||||
// Register http services
|
||||
services.AddHttpClient<IOrderApiClient, OrderApiClient>()
|
||||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddGrpcServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<IBasketService, BasketService>();
|
||||
|
||||
services.AddGrpcClient<Basket.BasketClient>((services, options) =>
|
||||
{
|
||||
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket;
|
||||
options.Address = new Uri(basketApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<ICatalogService, CatalogService>();
|
||||
|
||||
services.AddGrpcClient<Catalog.CatalogClient>((services, options) =>
|
||||
{
|
||||
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog;
|
||||
options.Address = new Uri(catalogApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<IOrderingService, OrderingService>();
|
||||
|
||||
services.AddGrpcClient<GrpcOrdering.OrderingGrpc.OrderingGrpcClient>((services, options) =>
|
||||
{
|
||||
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering;
|
||||
options.Address = new Uri(orderingApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters
|
||||
{
|
||||
|
||||
namespace Basket.API.Infrastructure.Filters
|
||||
{
|
||||
public class AuthorizeCheckOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
// Check for authorize attribute
|
||||
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
|
||||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
|
||||
|
||||
if (!hasAuthorize) return;
|
||||
|
||||
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
|
||||
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
|
||||
|
||||
var oAuthScheme = new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
|
||||
};
|
||||
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
[ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
global using System.Text.Json;
|
||||
global using CatalogApi;
|
||||
global using Grpc.Core;
|
||||
global using Grpc.Core.Interceptors;
|
||||
global using GrpcBasket;
|
||||
global using Microsoft.AspNetCore.Authorization;
|
||||
global using Microsoft.AspNetCore.Mvc;
|
||||
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
|
||||
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure;
|
||||
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
global using Microsoft.Extensions.Options;
|
||||
global using Services.Common;
|
@ -1,35 +1,41 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure;
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class GrpcExceptionInterceptor : Interceptor
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure
|
||||
{
|
||||
private readonly ILogger<GrpcExceptionInterceptor> _logger;
|
||||
|
||||
public GrpcExceptionInterceptor(ILogger<GrpcExceptionInterceptor> logger)
|
||||
public class GrpcExceptionInterceptor : Interceptor
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly ILogger<GrpcExceptionInterceptor> _logger;
|
||||
|
||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||
TRequest request,
|
||||
ClientInterceptorContext<TRequest, TResponse> context,
|
||||
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||
{
|
||||
var call = continuation(request, context);
|
||||
|
||||
return new AsyncUnaryCall<TResponse>(HandleResponse(call.ResponseAsync), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose);
|
||||
}
|
||||
|
||||
private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> t)
|
||||
{
|
||||
try
|
||||
public GrpcExceptionInterceptor(ILogger<GrpcExceptionInterceptor> logger)
|
||||
{
|
||||
var response = await t;
|
||||
return response;
|
||||
_logger = logger;
|
||||
}
|
||||
catch (RpcException e)
|
||||
|
||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||
TRequest request,
|
||||
ClientInterceptorContext<TRequest, TResponse> context,
|
||||
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||
{
|
||||
_logger.LogError(e, "Error calling via gRPC: {Status}", e.Status);
|
||||
return default;
|
||||
var call = continuation(request, context);
|
||||
|
||||
return new AsyncUnaryCall<TResponse>(HandleResponse(call.ResponseAsync), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose);
|
||||
}
|
||||
|
||||
private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> t)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await t;
|
||||
return response;
|
||||
}
|
||||
catch (RpcException e)
|
||||
{
|
||||
_logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure
|
||||
{
|
||||
public class HttpClientAuthorizationDelegatingHandler : DelegatingHandler
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ILogger<HttpClientAuthorizationDelegatingHandler> _logger;
|
||||
|
||||
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor, ILogger<HttpClientAuthorizationDelegatingHandler> logger)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
request.Version = new System.Version(2, 0);
|
||||
request.Method = HttpMethod.Get;
|
||||
|
||||
var authorizationHeader = _httpContextAccessor.HttpContext
|
||||
.Request.Headers["Authorization"];
|
||||
|
||||
if (!string.IsNullOrEmpty(authorizationHeader))
|
||||
{
|
||||
request.Headers.Add("Authorization", new List<string>() { authorizationHeader });
|
||||
}
|
||||
|
||||
var token = await GetToken();
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
async Task<string> GetToken()
|
||||
{
|
||||
const string ACCESS_TOKEN = "access_token";
|
||||
|
||||
return await _httpContextAccessor.HttpContext
|
||||
.GetTokenAsync(ACCESS_TOKEN);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AssemblyName>Mobile.Shopping.HttpAggregator</AssemblyName>
|
||||
<RootNamespace>Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator</RootNamespace>
|
||||
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
|
||||
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Yarp.ReverseProxy" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Uris" />
|
||||
<PackageReference Include="Google.Protobuf" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" />
|
||||
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Services\Services.Common\Services.Common.csproj" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="5.0.1" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="5.0.1" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.14.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" Version="2.34.0" />
|
||||
<PackageReference Include="Grpc.Core" Version="2.34.0" />
|
||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.34.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.34.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.2" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0-dev-00834" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\Devspaces.Support\Devspaces.Support.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,15 +1,16 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class AddBasketItemRequest
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public int CatalogItemId { get; set; }
|
||||
|
||||
public string BasketId { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public AddBasketItemRequest()
|
||||
public class AddBasketItemRequest
|
||||
{
|
||||
Quantity = 1;
|
||||
public int CatalogItemId { get; set; }
|
||||
|
||||
public string BasketId { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public AddBasketItemRequest()
|
||||
{
|
||||
Quantity = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,22 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class BasketData
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string BuyerId { get; set; }
|
||||
|
||||
public List<BasketDataItem> Items { get; set; } = new();
|
||||
|
||||
public BasketData()
|
||||
public class BasketData
|
||||
{
|
||||
public string BuyerId { get; set; }
|
||||
|
||||
public List<BasketDataItem> Items { get; set; } = new List<BasketDataItem>();
|
||||
|
||||
public BasketData()
|
||||
{
|
||||
}
|
||||
|
||||
public BasketData(string buyerId)
|
||||
{
|
||||
BuyerId = buyerId;
|
||||
}
|
||||
}
|
||||
|
||||
public BasketData(string buyerId)
|
||||
{
|
||||
BuyerId = buyerId;
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class BasketDataItem
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public int ProductId { get; set; }
|
||||
public class BasketDataItem
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string ProductName { get; set; }
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public decimal OldUnitPrice { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
public decimal OldUnitPrice { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class CatalogItem
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public class CatalogItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public decimal Price { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
|
||||
public string PictureUri { get; set; }
|
||||
public string PictureUri { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,48 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class OrderData
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string OrderNumber { get; set; }
|
||||
|
||||
public DateTime Date { get; set; }
|
||||
public class OrderData
|
||||
{
|
||||
public string OrderNumber { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public decimal Total { get; set; }
|
||||
public string Status { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
|
||||
public string City { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Street { get; set; }
|
||||
public string City { get; set; }
|
||||
|
||||
public string State { get; set; }
|
||||
public string Street { get; set; }
|
||||
|
||||
public string Country { get; set; }
|
||||
public string State { get; set; }
|
||||
|
||||
public string ZipCode { get; set; }
|
||||
public string Country { get; set; }
|
||||
|
||||
public string CardNumber { get; set; }
|
||||
public string ZipCode { get; set; }
|
||||
|
||||
public string CardHolderName { get; set; }
|
||||
public string CardNumber { get; set; }
|
||||
|
||||
public bool IsDraft { get; set; }
|
||||
public string CardHolderName { get; set; }
|
||||
|
||||
public DateTime CardExpiration { get; set; }
|
||||
public bool IsDraft { get; set; }
|
||||
|
||||
public string CardExpirationShort { get; set; }
|
||||
public DateTime CardExpiration { get; set; }
|
||||
|
||||
public string CardSecurityNumber { get; set; }
|
||||
public string CardExpirationShort { get; set; }
|
||||
|
||||
public int CardTypeId { get; set; }
|
||||
public string CardSecurityNumber { get; set; }
|
||||
|
||||
public string Buyer { get; set; }
|
||||
public int CardTypeId { get; set; }
|
||||
|
||||
public string Buyer { get; set; }
|
||||
|
||||
public List<OrderItemData> OrderItems { get; } = new List<OrderItemData>();
|
||||
}
|
||||
|
||||
public List<OrderItemData> OrderItems { get; } = new();
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class OrderItemData
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public string ProductName { get; set; }
|
||||
public class OrderItemData
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public decimal Discount { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
public int Units { get; set; }
|
||||
public decimal Discount { get; set; }
|
||||
|
||||
public int Units { get; set; }
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class UpdateBasketItemData
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string BasketItemId { get; set; }
|
||||
|
||||
public int NewQty { get; set; }
|
||||
|
||||
public UpdateBasketItemData()
|
||||
public class UpdateBasketItemData
|
||||
{
|
||||
NewQty = 0;
|
||||
public string BasketItemId { get; set; }
|
||||
|
||||
public int NewQty { get; set; }
|
||||
|
||||
public UpdateBasketItemData()
|
||||
{
|
||||
NewQty = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,19 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class UpdateBasketItemsRequest
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string BasketId { get; set; }
|
||||
|
||||
public ICollection<UpdateBasketItemData> Updates { get; set; }
|
||||
|
||||
public UpdateBasketItemsRequest()
|
||||
public class UpdateBasketItemsRequest
|
||||
{
|
||||
Updates = new List<UpdateBasketItemData>();
|
||||
|
||||
public string BasketId { get; set; }
|
||||
|
||||
public ICollection<UpdateBasketItemData> Updates { get; set; }
|
||||
|
||||
public UpdateBasketItemsRequest()
|
||||
{
|
||||
Updates = new List<UpdateBasketItemData>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class UpdateBasketRequest
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string BuyerId { get; set; }
|
||||
|
||||
public IEnumerable<UpdateBasketRequestItemData> Items { get; set; }
|
||||
public class UpdateBasketRequest
|
||||
{
|
||||
public string BuyerId { get; set; }
|
||||
|
||||
public IEnumerable<UpdateBasketRequestItemData> Items { get; set; }
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class UpdateBasketRequestItemData
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string Id { get; set; } // Basket id
|
||||
|
||||
public int ProductId { get; set; } // Catalog item id
|
||||
public class UpdateBasketRequestItemData
|
||||
{
|
||||
public string Id { get; set; } // Basket id
|
||||
|
||||
public int ProductId { get; set; } // Catalog item id
|
||||
|
||||
public int Quantity { get; set; } // Quantity
|
||||
}
|
||||
|
||||
public int Quantity { get; set; } // Quantity
|
||||
}
|
||||
|
@ -1,24 +1,29 @@
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator;
|
||||
using Serilog;
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
builder.Services.AddReverseProxy(builder.Configuration);
|
||||
builder.Services.AddControllers();
|
||||
|
||||
builder.Services.AddHealthChecks(builder.Configuration);
|
||||
|
||||
builder.Services.AddApplicationServices();
|
||||
builder.Services.AddGrpcServices();
|
||||
|
||||
builder.Services.Configure<UrlsConfig>(builder.Configuration.GetSection("urls"));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseServiceDefaults();
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.MapControllers();
|
||||
app.MapReverseProxy();
|
||||
|
||||
await app.RunAsync();
|
||||
BuildWebHost(args).Run();
|
||||
IWebHost BuildWebHost(string[] args) =>
|
||||
WebHost
|
||||
.CreateDefaultBuilder(args)
|
||||
.ConfigureAppConfiguration(cb =>
|
||||
{
|
||||
var sources = cb.Sources;
|
||||
sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource()
|
||||
{
|
||||
Optional = true,
|
||||
Path = "appsettings.localhost.json",
|
||||
ReloadOnChange = false
|
||||
});
|
||||
})
|
||||
.UseStartup<Startup>()
|
||||
.UseSerilog((builderContext, config) =>
|
||||
{
|
||||
config
|
||||
.MinimumLevel.Information()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console();
|
||||
})
|
||||
.Build();
|
@ -24,6 +24,13 @@
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:61632/"
|
||||
},
|
||||
"Azure Dev Spaces": {
|
||||
"commandName": "AzureDevSpaces",
|
||||
"launchBrowser": true,
|
||||
"resourceGroup": "eshoptestedu",
|
||||
"aksName": "eshoptestedu",
|
||||
"subscriptionId": "e3035ac1-c06c-4daf-8939-57b3c5f1f759"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,83 +1,90 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using GrpcBasket;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class BasketService : IBasketService
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
|
||||
{
|
||||
private readonly Basket.BasketClient _basketClient;
|
||||
private readonly ILogger<BasketService> _logger;
|
||||
|
||||
public BasketService(Basket.BasketClient basketClient, ILogger<BasketService> logger)
|
||||
public class BasketService : IBasketService
|
||||
{
|
||||
_basketClient = basketClient;
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly Basket.BasketClient _basketClient;
|
||||
private readonly ILogger<BasketService> _logger;
|
||||
|
||||
public async Task<BasketData> GetByIdAsync(string id)
|
||||
{
|
||||
_logger.LogDebug("grpc client created, request = {@id}", id);
|
||||
var response = await _basketClient.GetBasketByIdAsync(new BasketRequest { Id = id });
|
||||
_logger.LogDebug("grpc response {@response}", response);
|
||||
|
||||
return MapToBasketData(response);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(BasketData currentBasket)
|
||||
{
|
||||
_logger.LogDebug("Grpc update basket currentBasket {@currentBasket}", currentBasket);
|
||||
var request = MapToCustomerBasketRequest(currentBasket);
|
||||
_logger.LogDebug("Grpc update basket request {@request}", request);
|
||||
|
||||
await _basketClient.UpdateBasketAsync(request);
|
||||
}
|
||||
|
||||
private BasketData MapToBasketData(CustomerBasketResponse customerBasketRequest)
|
||||
{
|
||||
if (customerBasketRequest == null)
|
||||
public BasketService(Basket.BasketClient basketClient, ILogger<BasketService> logger)
|
||||
{
|
||||
return null;
|
||||
_basketClient = basketClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
var map = new BasketData
|
||||
public async Task<BasketData> GetById(string id)
|
||||
{
|
||||
BuyerId = customerBasketRequest.Buyerid
|
||||
};
|
||||
_logger.LogDebug("grpc client created, request = {@id}", id);
|
||||
var response = await _basketClient.GetBasketByIdAsync(new BasketRequest { Id = id });
|
||||
_logger.LogDebug("grpc response {@response}", response);
|
||||
|
||||
customerBasketRequest.Items.ToList().ForEach(item => map.Items.Add(new BasketDataItem
|
||||
{
|
||||
Id = item.Id,
|
||||
OldUnitPrice = (decimal)item.Oldunitprice,
|
||||
PictureUrl = item.Pictureurl,
|
||||
ProductId = item.Productid,
|
||||
ProductName = item.Productname,
|
||||
Quantity = item.Quantity,
|
||||
UnitPrice = (decimal)item.Unitprice
|
||||
}));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private CustomerBasketRequest MapToCustomerBasketRequest(BasketData basketData)
|
||||
{
|
||||
if (basketData == null)
|
||||
{
|
||||
return null;
|
||||
return MapToBasketData(response);
|
||||
}
|
||||
|
||||
var map = new CustomerBasketRequest
|
||||
public async Task UpdateAsync(BasketData currentBasket)
|
||||
{
|
||||
Buyerid = basketData.BuyerId
|
||||
};
|
||||
_logger.LogDebug("Grpc update basket currentBasket {@currentBasket}", currentBasket);
|
||||
var request = MapToCustomerBasketRequest(currentBasket);
|
||||
_logger.LogDebug("Grpc update basket request {@request}", request);
|
||||
|
||||
basketData.Items.ToList().ForEach(item => map.Items.Add(new BasketItemResponse
|
||||
await _basketClient.UpdateBasketAsync(request);
|
||||
}
|
||||
|
||||
private BasketData MapToBasketData(CustomerBasketResponse customerBasketRequest)
|
||||
{
|
||||
Id = item.Id,
|
||||
Oldunitprice = (double)item.OldUnitPrice,
|
||||
Pictureurl = item.PictureUrl,
|
||||
Productid = item.ProductId,
|
||||
Productname = item.ProductName,
|
||||
Quantity = item.Quantity,
|
||||
Unitprice = (double)item.UnitPrice
|
||||
}));
|
||||
if (customerBasketRequest == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return map;
|
||||
var map = new BasketData
|
||||
{
|
||||
BuyerId = customerBasketRequest.Buyerid
|
||||
};
|
||||
|
||||
customerBasketRequest.Items.ToList().ForEach(item => map.Items.Add(new BasketDataItem
|
||||
{
|
||||
Id = item.Id,
|
||||
OldUnitPrice = (decimal)item.Oldunitprice,
|
||||
PictureUrl = item.Pictureurl,
|
||||
ProductId = item.Productid,
|
||||
ProductName = item.Productname,
|
||||
Quantity = item.Quantity,
|
||||
UnitPrice = (decimal)item.Unitprice
|
||||
}));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private CustomerBasketRequest MapToCustomerBasketRequest(BasketData basketData)
|
||||
{
|
||||
if (basketData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var map = new CustomerBasketRequest
|
||||
{
|
||||
Buyerid = basketData.BuyerId
|
||||
};
|
||||
|
||||
basketData.Items.ToList().ForEach(item => map.Items.Add(new BasketItemResponse
|
||||
{
|
||||
Id = item.Id,
|
||||
Oldunitprice = (double)item.OldUnitPrice,
|
||||
Pictureurl = item.PictureUrl,
|
||||
Productid = item.ProductId,
|
||||
Productname = item.ProductName,
|
||||
Quantity = item.Quantity,
|
||||
Unitprice = (double)item.UnitPrice
|
||||
}));
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +1,43 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using CatalogApi;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class CatalogService : ICatalogService
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
|
||||
{
|
||||
private readonly Catalog.CatalogClient _client;
|
||||
|
||||
public CatalogService(Catalog.CatalogClient client)
|
||||
public class CatalogService : ICatalogService
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
private readonly Catalog.CatalogClient _client;
|
||||
|
||||
public async Task<CatalogItem> GetCatalogItemAsync(int id)
|
||||
{
|
||||
var request = new CatalogItemRequest { Id = id };
|
||||
var response = await _client.GetItemByIdAsync(request);
|
||||
return MapToCatalogItemResponse(response);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids)
|
||||
{
|
||||
var request = new CatalogItemsRequest { Ids = string.Join(",", ids), PageIndex = 1, PageSize = 10 };
|
||||
var response = await _client.GetItemsByIdsAsync(request);
|
||||
return response.Data.Select(MapToCatalogItemResponse);
|
||||
}
|
||||
|
||||
private CatalogItem MapToCatalogItemResponse(CatalogItemResponse catalogItemResponse)
|
||||
{
|
||||
return new CatalogItem
|
||||
public CatalogService(Catalog.CatalogClient client)
|
||||
{
|
||||
Id = catalogItemResponse.Id,
|
||||
Name = catalogItemResponse.Name,
|
||||
PictureUri = catalogItemResponse.PictureUri,
|
||||
Price = (decimal)catalogItemResponse.Price
|
||||
};
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public async Task<CatalogItem> GetCatalogItemAsync(int id)
|
||||
{
|
||||
var request = new CatalogItemRequest { Id = id };
|
||||
var response = await _client.GetItemByIdAsync(request);
|
||||
return MapToCatalogItemResponse(response);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids)
|
||||
{
|
||||
var request = new CatalogItemsRequest { Ids = string.Join(",", ids), PageIndex = 1, PageSize = 10 };
|
||||
var response = await _client.GetItemsByIdsAsync(request);
|
||||
return response.Data.Select(MapToCatalogItemResponse);
|
||||
}
|
||||
|
||||
private CatalogItem MapToCatalogItemResponse(CatalogItemResponse catalogItemResponse)
|
||||
{
|
||||
return new CatalogItem
|
||||
{
|
||||
Id = catalogItemResponse.Id,
|
||||
Name = catalogItemResponse.Name,
|
||||
PictureUri = catalogItemResponse.PictureUri,
|
||||
Price = (decimal)catalogItemResponse.Price
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IBasketService
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
|
||||
{
|
||||
Task<BasketData> GetByIdAsync(string id);
|
||||
public interface IBasketService
|
||||
{
|
||||
Task<BasketData> GetById(string id);
|
||||
|
||||
Task UpdateAsync(BasketData currentBasket);
|
||||
Task UpdateAsync(BasketData currentBasket);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface ICatalogService
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
|
||||
{
|
||||
Task<CatalogItem> GetCatalogItemAsync(int id);
|
||||
public interface ICatalogService
|
||||
{
|
||||
Task<CatalogItem> GetCatalogItemAsync(int id);
|
||||
|
||||
Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids);
|
||||
Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IOrderApiClient
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
|
||||
{
|
||||
Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket);
|
||||
public interface IOrderApiClient
|
||||
{
|
||||
Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IOrderingService
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
|
||||
{
|
||||
Task<OrderData> GetOrderDraftAsync(BasketData basketData);
|
||||
public interface IOrderingService
|
||||
{
|
||||
Task<OrderData> GetOrderDraftAsync(BasketData basketData);
|
||||
}
|
||||
}
|
@ -1,28 +1,40 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
|
||||
public class OrderApiClient : IOrderApiClient
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
|
||||
{
|
||||
private readonly HttpClient _apiClient;
|
||||
private readonly ILogger<OrderApiClient> _logger;
|
||||
private readonly UrlsConfig _urls;
|
||||
|
||||
public OrderApiClient(HttpClient httpClient, ILogger<OrderApiClient> logger, IOptions<UrlsConfig> config)
|
||||
public class OrderApiClient : IOrderApiClient
|
||||
{
|
||||
_apiClient = httpClient;
|
||||
_logger = logger;
|
||||
_urls = config.Value;
|
||||
}
|
||||
private readonly HttpClient _apiClient;
|
||||
private readonly ILogger<OrderApiClient> _logger;
|
||||
private readonly UrlsConfig _urls;
|
||||
|
||||
public async Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket)
|
||||
{
|
||||
var uri = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft();
|
||||
var content = new StringContent(JsonSerializer.Serialize(basket), System.Text.Encoding.UTF8, "application/json");
|
||||
var response = await _apiClient.PostAsync(uri, content);
|
||||
public OrderApiClient(HttpClient httpClient, ILogger<OrderApiClient> logger, IOptions<UrlsConfig> config)
|
||||
{
|
||||
_apiClient = httpClient;
|
||||
_logger = logger;
|
||||
_urls = config.Value;
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
public async Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket)
|
||||
{
|
||||
var uri = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft();
|
||||
var content = new StringContent(JsonSerializer.Serialize(basket), System.Text.Encoding.UTF8, "application/json");
|
||||
var response = await _apiClient.PostAsync(uri, content);
|
||||
|
||||
var ordersDraftResponse = await response.Content.ReadAsStringAsync();
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return JsonSerializer.Deserialize<OrderData>(ordersDraftResponse, JsonDefaults.CaseInsensitiveOptions);
|
||||
var ordersDraftResponse = await response.Content.ReadAsStringAsync();
|
||||
|
||||
return JsonSerializer.Deserialize<OrderData>(ordersDraftResponse, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,72 +1,79 @@
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using GrpcOrdering;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class OrderingService : IOrderingService
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
|
||||
{
|
||||
private readonly GrpcOrdering.OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
|
||||
private readonly ILogger<OrderingService> _logger;
|
||||
|
||||
public OrderingService(GrpcOrdering.OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
|
||||
public class OrderingService : IOrderingService
|
||||
{
|
||||
_orderingGrpcClient = orderingGrpcClient;
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
|
||||
private readonly ILogger<OrderingService> _logger;
|
||||
|
||||
public async Task<OrderData> GetOrderDraftAsync(BasketData basketData)
|
||||
{
|
||||
_logger.LogDebug(" grpc client created, basketData={@basketData}", basketData);
|
||||
|
||||
var command = MapToOrderDraftCommand(basketData);
|
||||
var response = await _orderingGrpcClient.CreateOrderDraftFromBasketDataAsync(command);
|
||||
_logger.LogDebug(" grpc response: {@response}", response);
|
||||
|
||||
return MapToResponse(response, basketData);
|
||||
}
|
||||
|
||||
private OrderData MapToResponse(GrpcOrdering.OrderDraftDTO orderDraft, BasketData basketData)
|
||||
{
|
||||
if (orderDraft == null)
|
||||
public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
|
||||
{
|
||||
return null;
|
||||
_orderingGrpcClient = orderingGrpcClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
var data = new OrderData
|
||||
public async Task<OrderData> GetOrderDraftAsync(BasketData basketData)
|
||||
{
|
||||
Buyer = basketData.BuyerId,
|
||||
Total = (decimal)orderDraft.Total,
|
||||
};
|
||||
_logger.LogDebug(" grpc client created, basketData={@basketData}", basketData);
|
||||
|
||||
orderDraft.OrderItems.ToList().ForEach(o => data.OrderItems.Add(new OrderItemData
|
||||
var command = MapToOrderDraftCommand(basketData);
|
||||
var response = await _orderingGrpcClient.CreateOrderDraftFromBasketDataAsync(command);
|
||||
_logger.LogDebug(" grpc response: {@response}", response);
|
||||
|
||||
return MapToResponse(response, basketData);
|
||||
}
|
||||
|
||||
private OrderData MapToResponse(GrpcOrdering.OrderDraftDTO orderDraft, BasketData basketData)
|
||||
{
|
||||
Discount = (decimal)o.Discount,
|
||||
PictureUrl = o.PictureUrl,
|
||||
ProductId = o.ProductId,
|
||||
ProductName = o.ProductName,
|
||||
UnitPrice = (decimal)o.UnitPrice,
|
||||
Units = o.Units,
|
||||
}));
|
||||
if (orderDraft == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = new OrderData
|
||||
{
|
||||
Buyer = basketData.BuyerId,
|
||||
Total = (decimal)orderDraft.Total,
|
||||
};
|
||||
|
||||
orderDraft.OrderItems.ToList().ForEach(o => data.OrderItems.Add(new OrderItemData
|
||||
{
|
||||
Discount = (decimal)o.Discount,
|
||||
PictureUrl = o.PictureUrl,
|
||||
ProductId = o.ProductId,
|
||||
ProductName = o.ProductName,
|
||||
UnitPrice = (decimal)o.UnitPrice,
|
||||
Units = o.Units,
|
||||
}));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
|
||||
{
|
||||
var command = new CreateOrderDraftCommand
|
||||
{
|
||||
BuyerId = basketData.BuyerId,
|
||||
};
|
||||
|
||||
basketData.Items.ForEach(i => command.Items.Add(new BasketItem
|
||||
{
|
||||
Id = i.Id,
|
||||
OldUnitPrice = (double)i.OldUnitPrice,
|
||||
PictureUrl = i.PictureUrl,
|
||||
ProductId = i.ProductId,
|
||||
ProductName = i.ProductName,
|
||||
Quantity = i.Quantity,
|
||||
UnitPrice = (double)i.UnitPrice,
|
||||
}));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private GrpcOrdering.CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
|
||||
{
|
||||
var command = new GrpcOrdering.CreateOrderDraftCommand
|
||||
{
|
||||
BuyerId = basketData.BuyerId,
|
||||
};
|
||||
|
||||
basketData.Items.ForEach(i => command.Items.Add(new GrpcOrdering.BasketItem
|
||||
{
|
||||
Id = i.Id,
|
||||
OldUnitPrice = (double)i.OldUnitPrice,
|
||||
PictureUrl = i.PictureUrl,
|
||||
ProductId = i.ProductId,
|
||||
ProductName = i.ProductName,
|
||||
Quantity = i.Quantity,
|
||||
UnitPrice = (double)i.UnitPrice,
|
||||
}));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
}
|
||||
|
222
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs
Normal file
222
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs
Normal file
@ -0,0 +1,222 @@
|
||||
using CatalogApi;
|
||||
using Devspaces.Support;
|
||||
using GrpcBasket;
|
||||
using GrpcOrdering;
|
||||
using HealthChecks.UI.Client;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure;
|
||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("self", () => HealthCheckResult.Healthy())
|
||||
.AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" })
|
||||
.AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" })
|
||||
.AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" })
|
||||
.AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" })
|
||||
.AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" });
|
||||
|
||||
services.AddCustomMvc(Configuration)
|
||||
.AddCustomAuthentication(Configuration)
|
||||
.AddDevspaces()
|
||||
.AddHttpServices()
|
||||
.AddGrpcServices();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
|
||||
{
|
||||
var pathBase = Configuration["PATH_BASE"];
|
||||
|
||||
if (!string.IsNullOrEmpty(pathBase))
|
||||
{
|
||||
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
|
||||
app.UsePathBase(pathBase);
|
||||
}
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseSwagger().UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1");
|
||||
|
||||
c.OAuthClientId("mobileshoppingaggswaggerui");
|
||||
c.OAuthClientSecret(string.Empty);
|
||||
c.OAuthRealm(string.Empty);
|
||||
c.OAuthAppName("Purchase BFF Swagger UI");
|
||||
});
|
||||
|
||||
app.UseRouting();
|
||||
app.UseCors("CorsPolicy");
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapDefaultControllerRoute();
|
||||
endpoints.MapControllers();
|
||||
endpoints.MapHealthChecks("/hc", new HealthCheckOptions()
|
||||
{
|
||||
Predicate = _ => true,
|
||||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
||||
});
|
||||
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
|
||||
{
|
||||
Predicate = r => r.Name.Contains("self")
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddOptions();
|
||||
services.Configure<UrlsConfig>(configuration.GetSection("urls"));
|
||||
|
||||
services.AddControllers()
|
||||
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
|
||||
|
||||
services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.DescribeAllEnumsAsStrings();
|
||||
options.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "Shopping Aggregator for Mobile Clients",
|
||||
Version = "v1",
|
||||
Description = "Shopping Aggregator for Mobile Clients"
|
||||
});
|
||||
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
|
||||
{
|
||||
Type = SecuritySchemeType.OAuth2,
|
||||
Flows = new OpenApiOAuthFlows()
|
||||
{
|
||||
Implicit = new OpenApiOAuthFlow()
|
||||
{
|
||||
AuthorizationUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"),
|
||||
TokenUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"),
|
||||
|
||||
Scopes = new Dictionary<string, string>()
|
||||
{
|
||||
{ "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
options.OperationFilter<AuthorizeCheckOperationFilter>();
|
||||
});
|
||||
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("CorsPolicy",
|
||||
builder => builder
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.SetIsOriginAllowed((host) => true)
|
||||
.AllowCredentials());
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
|
||||
|
||||
var identityUrl = configuration.GetValue<string>("urls:identity");
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
|
||||
})
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.Authority = identityUrl;
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.Audience = "mobileshoppingagg";
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddHttpServices(this IServiceCollection services)
|
||||
{
|
||||
//register delegating handlers
|
||||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
|
||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
|
||||
//register http services
|
||||
|
||||
services.AddHttpClient<IOrderApiClient, OrderApiClient>()
|
||||
.AddDevspacesSupport();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddGrpcServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<IBasketService, BasketService>();
|
||||
|
||||
services.AddGrpcClient<Basket.BasketClient>((services, options) =>
|
||||
{
|
||||
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket;
|
||||
options.Address = new Uri(basketApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<ICatalogService, CatalogService>();
|
||||
|
||||
services.AddGrpcClient<Catalog.CatalogClient>((services, options) =>
|
||||
{
|
||||
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog;
|
||||
options.Address = new Uri(catalogApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<IOrderingService, OrderingService>();
|
||||
|
||||
services.AddGrpcClient<OrderingGrpc.OrderingGrpcClient>((services, options) =>
|
||||
{
|
||||
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering;
|
||||
options.Address = new Uri(orderingApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,138 +1,15 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"System.Net.Http": "Warning"
|
||||
}
|
||||
},
|
||||
"OpenApi": {
|
||||
"Endpoint": {
|
||||
"Name": "Purchase BFF V1"
|
||||
},
|
||||
"Document": {
|
||||
"Description": "Shopping Aggregator for Mobile Clients",
|
||||
"Title": "Shopping Aggregator for Mobile Clients",
|
||||
"Version": "v1"
|
||||
},
|
||||
"Auth": {
|
||||
"ClientId": "mobileshoppingaggswaggerui",
|
||||
"AppName": "Mobile shopping BFF Swagger UI"
|
||||
}
|
||||
},
|
||||
"Identity": {
|
||||
"Url": "http://localhost:5223",
|
||||
"Audience": "mobileshoppingagg",
|
||||
"Scopes": {
|
||||
"webshoppingagg": "Shopping Aggregator for Mobile Clients"
|
||||
}
|
||||
},
|
||||
"ReverseProxy": {
|
||||
"Routes": {
|
||||
"c-short": {
|
||||
"ClusterId": "catalog",
|
||||
"Match": {
|
||||
"Path": "c/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/c" }
|
||||
]
|
||||
},
|
||||
"c-long": {
|
||||
"ClusterId": "catalog",
|
||||
"Match": {
|
||||
"Path": "catalog-api/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/catalog-api" }
|
||||
]
|
||||
},
|
||||
"b-short": {
|
||||
"ClusterId": "basket",
|
||||
"Match": {
|
||||
"Path": "b/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/b" }
|
||||
]
|
||||
},
|
||||
"b-long": {
|
||||
"ClusterId": "basket",
|
||||
"Match": {
|
||||
"Path": "basket-api/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/basket-api" }
|
||||
]
|
||||
},
|
||||
"o-short": {
|
||||
"ClusterId": "orders",
|
||||
"Match": {
|
||||
"Path": "o/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/o" }
|
||||
]
|
||||
},
|
||||
"o-long": {
|
||||
"ClusterId": "orders",
|
||||
"Match": {
|
||||
"Path": "ordering-api/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/ordering-api" }
|
||||
]
|
||||
},
|
||||
"h-long": {
|
||||
"ClusterId": "signalr",
|
||||
"Match": {
|
||||
"Path": "hub/notificationhub/{**catch-all}"
|
||||
}
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"Clusters": {
|
||||
"basket": {
|
||||
"Destinations": {
|
||||
"destination0": {
|
||||
"Address": "http://localhost:5221"
|
||||
}
|
||||
}
|
||||
},
|
||||
"catalog": {
|
||||
"Destinations": {
|
||||
"destination0": {
|
||||
"Address": "http://localhost:5222"
|
||||
}
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"Destinations": {
|
||||
"destination0": {
|
||||
"Address": "http://localhost:5224"
|
||||
}
|
||||
}
|
||||
},
|
||||
"signalr": {
|
||||
"Destinations": {
|
||||
"destination0": {
|
||||
"Address": "http://localhost:5225"
|
||||
}
|
||||
}
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Urls": {
|
||||
"Basket": "http://localhost:5221",
|
||||
"Catalog": "http://localhost:5222",
|
||||
"Orders": "http://localhost:5224",
|
||||
"Identity": "http://localhost:5223",
|
||||
"Signalr": "http://localhost:5225",
|
||||
"GrpcBasket": "http://localhost:6221",
|
||||
"GrpcCatalog": "http://localhost:6222",
|
||||
"GrpcOrdering": "http://localhost:6224"
|
||||
},
|
||||
"CatalogUrlHC": "http://localhost:5222/hc",
|
||||
"OrderingUrlHC": "http://localhost:5224/hc",
|
||||
"BasketUrlHC": "http://localhost:5221/hc",
|
||||
"IdentityUrlHC": "http://localhost:5223/hc"
|
||||
}
|
||||
}
|
||||
|
@ -8,19 +8,16 @@
|
||||
"grpcCatalog": "http://localhost:81",
|
||||
"grpcOrdering": "http://localhost:5581"
|
||||
},
|
||||
"Identity": {
|
||||
"ExternalUrl": "http://localhost:5105",
|
||||
"Url": "http://localhost:5105",
|
||||
},
|
||||
"IdentityUrlExternal": "http://localhost:5105",
|
||||
"IdentityUrl": "http://localhost:5105",
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Debug"
|
||||
}
|
||||
},
|
||||
"Console": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Debug"
|
||||
}
|
||||
|
55
src/ApiGateways/Mobile.Bff.Shopping/aggregator/azds.yaml
Normal file
55
src/ApiGateways/Mobile.Bff.Shopping/aggregator/azds.yaml
Normal file
@ -0,0 +1,55 @@
|
||||
kind: helm-release
|
||||
apiVersion: 1.1
|
||||
build:
|
||||
context: ..\..\..\..
|
||||
dockerfile: Dockerfile
|
||||
install:
|
||||
chart: ../../../../k8s/helm/mobileshoppingagg
|
||||
set:
|
||||
image:
|
||||
tag: $(tag)
|
||||
pullPolicy: Never
|
||||
ingress:
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: traefik-azds
|
||||
hosts:
|
||||
# This expands to [space.s.]apigwms.<guid>.<region>.aksapp.io
|
||||
- $(spacePrefix)eshop$(hostSuffix)
|
||||
inf:
|
||||
k8s:
|
||||
dns: $(spacePrefix)eshop$(hostSuffix)
|
||||
values:
|
||||
- values.dev.yaml?
|
||||
- secrets.dev.yaml?
|
||||
- app.yaml
|
||||
- inf.yaml
|
||||
configurations:
|
||||
develop:
|
||||
build:
|
||||
useGitIgnore: true
|
||||
dockerfile: Dockerfile.develop
|
||||
container:
|
||||
syncTarget: /src
|
||||
sync:
|
||||
- '**/Pages/**'
|
||||
- '**/Views/**'
|
||||
- '**/wwwroot/**'
|
||||
- '!**/*.{sln,csproj}'
|
||||
command:
|
||||
- dotnet
|
||||
- run
|
||||
- --no-restore
|
||||
- --no-build
|
||||
- --no-launch-profile
|
||||
- -c
|
||||
- ${Configuration:-Debug}
|
||||
iterate:
|
||||
processesToKill:
|
||||
- dotnet
|
||||
- vsdbg
|
||||
buildCommands:
|
||||
- - dotnet
|
||||
- build
|
||||
- --no-restore
|
||||
- -c
|
||||
- ${Configuration:-Debug}
|
@ -1,41 +1,45 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class UrlsConfig
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config
|
||||
{
|
||||
|
||||
public class CatalogOperations
|
||||
public class UrlsConfig
|
||||
{
|
||||
// grpc call under REST must go trough port 80
|
||||
public static string GetItemById(int id) => $"/api/v1/catalog/items/{id}";
|
||||
|
||||
public static string GetItemById(string ids) => $"/api/v1/catalog/items/ids/{string.Join(',', ids)}";
|
||||
public class CatalogOperations
|
||||
{
|
||||
// grpc call under REST must go trough port 80
|
||||
public static string GetItemById(int id) => $"/api/v1/catalog/items/{id}";
|
||||
|
||||
// REST call standard must go through port 5000
|
||||
public static string GetItemsById(IEnumerable<int> ids) => $":5000/api/v1/catalog/items?ids={string.Join(',', ids)}";
|
||||
public static string GetItemById(string ids) => $"/api/v1/catalog/items/ids/{string.Join(',', ids)}";
|
||||
|
||||
// REST call standard must go through port 5000
|
||||
public static string GetItemsById(IEnumerable<int> ids) => $":5000/api/v1/catalog/items?ids={string.Join(',', ids)}";
|
||||
}
|
||||
|
||||
public class BasketOperations
|
||||
{
|
||||
public static string GetItemById(string id) => $"/api/v1/basket/{id}";
|
||||
|
||||
public static string UpdateBasket() => "/api/v1/basket";
|
||||
}
|
||||
|
||||
public class OrdersOperations
|
||||
{
|
||||
public static string GetOrderDraft() => "/api/v1/orders/draft";
|
||||
}
|
||||
|
||||
public string Basket { get; set; }
|
||||
|
||||
public string Catalog { get; set; }
|
||||
|
||||
public string Orders { get; set; }
|
||||
|
||||
public string GrpcBasket { get; set; }
|
||||
|
||||
public string GrpcCatalog { get; set; }
|
||||
|
||||
public string GrpcOrdering { get; set; }
|
||||
}
|
||||
|
||||
public class BasketOperations
|
||||
{
|
||||
public static string GetItemById(string id) => $"/api/v1/basket/{id}";
|
||||
|
||||
public static string UpdateBasket() => "/api/v1/basket";
|
||||
}
|
||||
|
||||
public class OrdersOperations
|
||||
{
|
||||
public static string GetOrderDraft() => "/api/v1/orders/draft";
|
||||
}
|
||||
|
||||
public string Basket { get; set; }
|
||||
|
||||
public string Catalog { get; set; }
|
||||
|
||||
public string Orders { get; set; }
|
||||
|
||||
public string GrpcBasket { get; set; }
|
||||
|
||||
public string GrpcCatalog { get; set; }
|
||||
|
||||
public string GrpcOrdering { get; set; }
|
||||
}
|
||||
|
||||
|
@ -1,152 +1,164 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
public class BasketController : ControllerBase
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers
|
||||
{
|
||||
private readonly ICatalogService _catalog;
|
||||
private readonly IBasketService _basket;
|
||||
|
||||
public BasketController(ICatalogService catalogService, IBasketService basketService)
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
public class BasketController : ControllerBase
|
||||
{
|
||||
_catalog = catalogService;
|
||||
_basket = basketService;
|
||||
}
|
||||
private readonly ICatalogService _catalog;
|
||||
private readonly IBasketService _basket;
|
||||
|
||||
[HttpPost]
|
||||
[HttpPut]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<BasketData>> UpdateAllBasketAsync([FromBody] UpdateBasketRequest data)
|
||||
{
|
||||
if (data.Items == null || !data.Items.Any())
|
||||
public BasketController(ICatalogService catalogService, IBasketService basketService)
|
||||
{
|
||||
return BadRequest("Need to pass at least one basket line");
|
||||
_catalog = catalogService;
|
||||
_basket = basketService;
|
||||
}
|
||||
|
||||
// Retrieve the current basket
|
||||
var basket = await _basket.GetByIdAsync(data.BuyerId) ?? new BasketData(data.BuyerId);
|
||||
var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId));
|
||||
|
||||
// group by product id to avoid duplicates
|
||||
var itemsCalculated = data
|
||||
.Items
|
||||
.GroupBy(x => x.ProductId, x => x, (k, i) => new { productId = k, items = i })
|
||||
.Select(groupedItem =>
|
||||
{
|
||||
var item = groupedItem.items.First();
|
||||
item.Quantity = groupedItem.items.Sum(i => i.Quantity);
|
||||
return item;
|
||||
});
|
||||
|
||||
foreach (var bitem in itemsCalculated)
|
||||
[HttpPost]
|
||||
[HttpPut]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)]
|
||||
public async Task<ActionResult<BasketData>> UpdateAllBasketAsync([FromBody] UpdateBasketRequest data)
|
||||
{
|
||||
var catalogItem = catalogItems.SingleOrDefault(ci => ci.Id == bitem.ProductId);
|
||||
if (catalogItem == null)
|
||||
if (data.Items == null || !data.Items.Any())
|
||||
{
|
||||
return BadRequest($"Basket refers to a non-existing catalog item ({bitem.ProductId})");
|
||||
return BadRequest("Need to pass at least one basket line");
|
||||
}
|
||||
|
||||
var itemInBasket = basket.Items.FirstOrDefault(x => x.ProductId == bitem.ProductId);
|
||||
if (itemInBasket == null)
|
||||
// Retrieve the current basket
|
||||
var basket = await _basket.GetById(data.BuyerId) ?? new BasketData(data.BuyerId);
|
||||
var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId));
|
||||
|
||||
// group by product id to avoid duplicates
|
||||
var itemsCalculated = data
|
||||
.Items
|
||||
.GroupBy(x => x.ProductId, x => x, (k, i) => new { productId = k, items = i })
|
||||
.Select(groupedItem =>
|
||||
{
|
||||
var item = groupedItem.items.First();
|
||||
item.Quantity = groupedItem.items.Sum(i => i.Quantity);
|
||||
return item;
|
||||
});
|
||||
|
||||
foreach (var bitem in itemsCalculated)
|
||||
{
|
||||
basket.Items.Add(new BasketDataItem()
|
||||
var catalogItem = catalogItems.SingleOrDefault(ci => ci.Id == bitem.ProductId);
|
||||
if (catalogItem == null)
|
||||
{
|
||||
Id = bitem.Id,
|
||||
ProductId = catalogItem.Id,
|
||||
ProductName = catalogItem.Name,
|
||||
PictureUrl = catalogItem.PictureUri,
|
||||
UnitPrice = catalogItem.Price,
|
||||
Quantity = bitem.Quantity
|
||||
});
|
||||
return BadRequest($"Basket refers to a non-existing catalog item ({bitem.ProductId})");
|
||||
}
|
||||
|
||||
var itemInBasket = basket.Items.FirstOrDefault(x => x.ProductId == bitem.ProductId);
|
||||
if (itemInBasket == null)
|
||||
{
|
||||
basket.Items.Add(new BasketDataItem()
|
||||
{
|
||||
Id = bitem.Id,
|
||||
ProductId = catalogItem.Id,
|
||||
ProductName = catalogItem.Name,
|
||||
PictureUrl = catalogItem.PictureUri,
|
||||
UnitPrice = catalogItem.Price,
|
||||
Quantity = bitem.Quantity
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
itemInBasket.Quantity = bitem.Quantity;
|
||||
}
|
||||
}
|
||||
|
||||
await _basket.UpdateAsync(basket);
|
||||
|
||||
return basket;
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[Route("items")]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)]
|
||||
public async Task<ActionResult<BasketData>> UpdateQuantitiesAsync([FromBody] UpdateBasketItemsRequest data)
|
||||
{
|
||||
if (!data.Updates.Any())
|
||||
{
|
||||
return BadRequest("No updates sent");
|
||||
}
|
||||
|
||||
// Retrieve the current basket
|
||||
var currentBasket = await _basket.GetById(data.BasketId);
|
||||
if (currentBasket == null)
|
||||
{
|
||||
return BadRequest($"Basket with id {data.BasketId} not found.");
|
||||
}
|
||||
|
||||
// Update with new quantities
|
||||
foreach (var update in data.Updates)
|
||||
{
|
||||
var basketItem = currentBasket.Items.SingleOrDefault(bitem => bitem.Id == update.BasketItemId);
|
||||
if (basketItem == null)
|
||||
{
|
||||
return BadRequest($"Basket item with id {update.BasketItemId} not found");
|
||||
}
|
||||
basketItem.Quantity = update.NewQty;
|
||||
}
|
||||
|
||||
// Save the updated basket
|
||||
await _basket.UpdateAsync(currentBasket);
|
||||
|
||||
return currentBasket;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("items")]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType((int)HttpStatusCode.OK)]
|
||||
public async Task<ActionResult> AddBasketItemAsync([FromBody] AddBasketItemRequest data)
|
||||
{
|
||||
if (data == null || data.Quantity == 0)
|
||||
{
|
||||
return BadRequest("Invalid payload");
|
||||
}
|
||||
|
||||
// Step 1: Get the item from catalog
|
||||
var item = await _catalog.GetCatalogItemAsync(data.CatalogItemId);
|
||||
|
||||
//item.PictureUri =
|
||||
|
||||
// Step 2: Get current basket status
|
||||
var currentBasket = (await _basket.GetById(data.BasketId)) ?? new BasketData(data.BasketId);
|
||||
// Step 3: Search if exist product into basket
|
||||
var product = currentBasket.Items.SingleOrDefault(i => i.ProductId == item.Id);
|
||||
if (product != null)
|
||||
{
|
||||
// Step 4: Update quantity for product
|
||||
product.Quantity += data.Quantity;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemInBasket.Quantity = bitem.Quantity;
|
||||
// Step 4: Merge current status with new product
|
||||
currentBasket.Items.Add(new BasketDataItem()
|
||||
{
|
||||
UnitPrice = item.Price,
|
||||
PictureUrl = item.PictureUri,
|
||||
ProductId = item.Id,
|
||||
ProductName = item.Name,
|
||||
Quantity = data.Quantity,
|
||||
Id = Guid.NewGuid().ToString()
|
||||
});
|
||||
}
|
||||
|
||||
// Step 5: Update basket
|
||||
await _basket.UpdateAsync(currentBasket);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
await _basket.UpdateAsync(basket);
|
||||
|
||||
return basket;
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[Route("items")]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<BasketData>> UpdateQuantitiesAsync([FromBody] UpdateBasketItemsRequest data)
|
||||
{
|
||||
if (!data.Updates.Any())
|
||||
{
|
||||
return BadRequest("No updates sent");
|
||||
}
|
||||
|
||||
// Retrieve the current basket
|
||||
var currentBasket = await _basket.GetByIdAsync(data.BasketId);
|
||||
if (currentBasket == null)
|
||||
{
|
||||
return BadRequest($"Basket with id {data.BasketId} not found.");
|
||||
}
|
||||
|
||||
// Update with new quantities
|
||||
foreach (var update in data.Updates)
|
||||
{
|
||||
var basketItem = currentBasket.Items.SingleOrDefault(bitem => bitem.Id == update.BasketItemId);
|
||||
if (basketItem == null)
|
||||
{
|
||||
return BadRequest($"Basket item with id {update.BasketItemId} not found");
|
||||
}
|
||||
basketItem.Quantity = update.NewQty;
|
||||
}
|
||||
|
||||
// Save the updated basket
|
||||
await _basket.UpdateAsync(currentBasket);
|
||||
|
||||
return currentBasket;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("items")]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult> AddBasketItemAsync([FromBody] AddBasketItemRequest data)
|
||||
{
|
||||
if (data == null || data.Quantity == 0)
|
||||
{
|
||||
return BadRequest("Invalid payload");
|
||||
}
|
||||
|
||||
// Step 1: Get the item from catalog
|
||||
var item = await _catalog.GetCatalogItemAsync(data.CatalogItemId);
|
||||
|
||||
//item.PictureUri =
|
||||
|
||||
// Step 2: Get current basket status
|
||||
var currentBasket = (await _basket.GetByIdAsync(data.BasketId)) ?? new BasketData(data.BasketId);
|
||||
// Step 3: Search if exist product into basket
|
||||
var product = currentBasket.Items.SingleOrDefault(i => i.ProductId == item.Id);
|
||||
if (product != null)
|
||||
{
|
||||
// Step 4: Update quantity for product
|
||||
product.Quantity += data.Quantity;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Step 4: Merge current status with new product
|
||||
currentBasket.Items.Add(new BasketDataItem()
|
||||
{
|
||||
UnitPrice = item.Price,
|
||||
PictureUrl = item.PictureUri,
|
||||
ProductId = item.Id,
|
||||
ProductName = item.Name,
|
||||
Quantity = data.Quantity,
|
||||
Id = Guid.NewGuid().ToString()
|
||||
});
|
||||
}
|
||||
|
||||
// Step 5: Update basket
|
||||
await _basket.UpdateAsync(currentBasket);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers
|
||||
{
|
||||
[Route("")]
|
||||
public class HomeController : Controller
|
||||
{
|
||||
[HttpGet()]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return new RedirectResult("~/swagger");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +1,44 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
public class OrderController : ControllerBase
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers
|
||||
{
|
||||
private readonly IBasketService _basketService;
|
||||
private readonly IOrderingService _orderingService;
|
||||
|
||||
public OrderController(IBasketService basketService, IOrderingService orderingService)
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
public class OrderController : ControllerBase
|
||||
{
|
||||
_basketService = basketService;
|
||||
_orderingService = orderingService;
|
||||
}
|
||||
|
||||
[Route("draft/{basketId}")]
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<OrderData>> GetOrderDraftAsync(string basketId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(basketId))
|
||||
private readonly IBasketService _basketService;
|
||||
private readonly IOrderingService _orderingService;
|
||||
public OrderController(IBasketService basketService, IOrderingService orderingService)
|
||||
{
|
||||
return BadRequest("Need a valid basketid");
|
||||
}
|
||||
// Get the basket data and build a order draft based on it
|
||||
var basket = await _basketService.GetByIdAsync(basketId);
|
||||
|
||||
if (basket == null)
|
||||
{
|
||||
return BadRequest($"No basket found for id {basketId}");
|
||||
_basketService = basketService;
|
||||
_orderingService = orderingService;
|
||||
}
|
||||
|
||||
return await _orderingService.GetOrderDraftAsync(basket);
|
||||
[Route("draft/{basketId}")]
|
||||
[HttpGet]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(OrderData), (int)HttpStatusCode.OK)]
|
||||
public async Task<ActionResult<OrderData>> GetOrderDraftAsync(string basketId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(basketId))
|
||||
{
|
||||
return BadRequest("Need a valid basketid");
|
||||
}
|
||||
// Get the basket data and build a order draft based on it
|
||||
var basket = await _basketService.GetById(basketId);
|
||||
|
||||
if (basket == null)
|
||||
{
|
||||
return BadRequest($"No basket found for id {basketId}");
|
||||
}
|
||||
|
||||
return await _orderingService.GetOrderDraftAsync(basket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles
|
||||
@ -11,6 +11,7 @@ COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWe
|
||||
|
||||
COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj"
|
||||
COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj"
|
||||
COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj"
|
||||
COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj"
|
||||
COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj"
|
||||
COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj"
|
||||
@ -32,8 +33,6 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj"
|
||||
COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj"
|
||||
COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj"
|
||||
COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj"
|
||||
COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj"
|
||||
COPY "Services/Contact/Contact.API/Contact.API.csproj" "Services/Contact/Contact.API/Contact.API.csproj"
|
||||
COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj"
|
||||
COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj"
|
||||
COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj"
|
||||
@ -43,7 +42,6 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj"
|
||||
|
||||
COPY "docker-compose.dcproj" "docker-compose.dcproj"
|
||||
|
||||
COPY "Directory.Packages.props" "Directory.Packages.props"
|
||||
COPY "NuGet.config" "NuGet.config"
|
||||
|
||||
RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln"
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:7.0
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0
|
||||
ARG BUILD_CONFIGURATION=Debug
|
||||
ENV ASPNETCORE_ENVIRONMENT=Development
|
||||
ENV DOTNET_USE_POLLING_FILE_WATCHER=true
|
||||
@ -6,6 +6,7 @@ EXPOSE 80
|
||||
|
||||
WORKDIR /src
|
||||
COPY ["src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj", "src/ApiGateways/Web.Bff.Shopping/aggregator/"]
|
||||
COPY ["src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj", "src/BuildingBlocks/Devspaces.Support/"]
|
||||
COPY ["src/NuGet.config", "src/NuGet.config"]
|
||||
|
||||
RUN dotnet restore src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503
|
||||
|
@ -1,63 +0,0 @@
|
||||
internal static class Extensions
|
||||
{
|
||||
public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy"));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" })
|
||||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" })
|
||||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" })
|
||||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" });
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
|
||||
{
|
||||
// Register delegating handlers
|
||||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
|
||||
|
||||
// Register http services
|
||||
services.AddHttpClient<IOrderApiClient, OrderApiClient>()
|
||||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddGrpcServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<IBasketService, BasketService>();
|
||||
|
||||
services.AddGrpcClient<Basket.BasketClient>((services, options) =>
|
||||
{
|
||||
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket;
|
||||
options.Address = new Uri(basketApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<ICatalogService, CatalogService>();
|
||||
|
||||
services.AddGrpcClient<Catalog.CatalogClient>((services, options) =>
|
||||
{
|
||||
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog;
|
||||
options.Address = new Uri(catalogApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<IOrderingService, OrderingService>();
|
||||
|
||||
services.AddGrpcClient<GrpcOrdering.OrderingGrpc.OrderingGrpcClient>((services, options) =>
|
||||
{
|
||||
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering;
|
||||
options.Address = new Uri(orderingApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters
|
||||
{
|
||||
namespace Basket.API.Infrastructure.Filters
|
||||
{
|
||||
public class AuthorizeCheckOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
// Check for authorize attribute
|
||||
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
|
||||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
|
||||
|
||||
if (!hasAuthorize) return;
|
||||
|
||||
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
|
||||
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
|
||||
|
||||
var oAuthScheme = new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
|
||||
};
|
||||
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
[ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator" }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
global using System.Text.Json;
|
||||
global using CatalogApi;
|
||||
global using Grpc.Core;
|
||||
global using Grpc.Core.Interceptors;
|
||||
global using GrpcBasket;
|
||||
global using Microsoft.AspNetCore.Authorization;
|
||||
global using Microsoft.AspNetCore.Mvc;
|
||||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
|
||||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure;
|
||||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
global using Microsoft.Extensions.Options;
|
||||
global using Services.Common;
|
@ -1,35 +1,41 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure;
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class GrpcExceptionInterceptor : Interceptor
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure
|
||||
{
|
||||
private readonly ILogger<GrpcExceptionInterceptor> _logger;
|
||||
|
||||
public GrpcExceptionInterceptor(ILogger<GrpcExceptionInterceptor> logger)
|
||||
public class GrpcExceptionInterceptor : Interceptor
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly ILogger<GrpcExceptionInterceptor> _logger;
|
||||
|
||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||
TRequest request,
|
||||
ClientInterceptorContext<TRequest, TResponse> context,
|
||||
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||
{
|
||||
var call = continuation(request, context);
|
||||
|
||||
return new AsyncUnaryCall<TResponse>(HandleResponse(call.ResponseAsync), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose);
|
||||
}
|
||||
|
||||
private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> task)
|
||||
{
|
||||
try
|
||||
public GrpcExceptionInterceptor(ILogger<GrpcExceptionInterceptor> logger)
|
||||
{
|
||||
var response = await task;
|
||||
return response;
|
||||
_logger = logger;
|
||||
}
|
||||
catch (RpcException e)
|
||||
|
||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||
TRequest request,
|
||||
ClientInterceptorContext<TRequest, TResponse> context,
|
||||
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||
{
|
||||
_logger.LogError(e, "Error calling via gRPC: {Status}", e.Status);
|
||||
return default;
|
||||
var call = continuation(request, context);
|
||||
|
||||
return new AsyncUnaryCall<TResponse>(HandleResponse(call.ResponseAsync), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose);
|
||||
}
|
||||
|
||||
private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> t)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await t;
|
||||
return response;
|
||||
}
|
||||
catch (RpcException e)
|
||||
{
|
||||
_logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure
|
||||
{
|
||||
public class HttpClientAuthorizationDelegatingHandler
|
||||
: DelegatingHandler
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var authorizationHeader = _httpContextAccessor.HttpContext
|
||||
.Request.Headers["Authorization"];
|
||||
|
||||
if (!string.IsNullOrEmpty(authorizationHeader))
|
||||
{
|
||||
request.Headers.Add("Authorization", new List<string>() { authorizationHeader });
|
||||
}
|
||||
|
||||
var token = await GetToken();
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
async Task<string> GetToken()
|
||||
{
|
||||
const string ACCESS_TOKEN = "access_token";
|
||||
|
||||
return await _httpContextAccessor.HttpContext
|
||||
.GetTokenAsync(ACCESS_TOKEN);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,18 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class AddBasketItemRequest
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public int CatalogItemId { get; set; }
|
||||
|
||||
public string BasketId { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public AddBasketItemRequest()
|
||||
public class AddBasketItemRequest
|
||||
{
|
||||
Quantity = 1;
|
||||
}
|
||||
}
|
||||
public int CatalogItemId { get; set; }
|
||||
|
||||
public string BasketId { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public AddBasketItemRequest()
|
||||
{
|
||||
Quantity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,18 +1,22 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class BasketData
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string BuyerId { get; set; }
|
||||
|
||||
public List<BasketDataItem> Items { get; set; } = new();
|
||||
|
||||
public BasketData()
|
||||
public class BasketData
|
||||
{
|
||||
public string BuyerId { get; set; }
|
||||
|
||||
public List<BasketDataItem> Items { get; set; } = new List<BasketDataItem>();
|
||||
|
||||
public BasketData()
|
||||
{
|
||||
}
|
||||
|
||||
public BasketData(string buyerId)
|
||||
{
|
||||
BuyerId = buyerId;
|
||||
}
|
||||
}
|
||||
|
||||
public BasketData(string buyerId)
|
||||
{
|
||||
BuyerId = buyerId;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,21 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class BasketDataItem
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public int ProductId { get; set; }
|
||||
public class BasketDataItem
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string ProductName { get; set; }
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public decimal OldUnitPrice { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
public decimal OldUnitPrice { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class CatalogItem
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public class CatalogItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public decimal Price { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public decimal Price { get; set; }
|
||||
|
||||
public string PictureUri { get; set; }
|
||||
}
|
||||
|
||||
public string PictureUri { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,43 +1,48 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class OrderData
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string OrderNumber { get; set; }
|
||||
|
||||
public DateTime Date { get; set; }
|
||||
public class OrderData
|
||||
{
|
||||
public string OrderNumber { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public decimal Total { get; set; }
|
||||
public string Status { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
|
||||
public string City { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Street { get; set; }
|
||||
public string City { get; set; }
|
||||
|
||||
public string State { get; set; }
|
||||
public string Street { get; set; }
|
||||
|
||||
public string Country { get; set; }
|
||||
public string State { get; set; }
|
||||
|
||||
public string ZipCode { get; set; }
|
||||
public string Country { get; set; }
|
||||
|
||||
public string CardNumber { get; set; }
|
||||
public string ZipCode { get; set; }
|
||||
|
||||
public string CardHolderName { get; set; }
|
||||
public string CardNumber { get; set; }
|
||||
|
||||
public bool IsDraft { get; set; }
|
||||
public string CardHolderName { get; set; }
|
||||
|
||||
public DateTime CardExpiration { get; set; }
|
||||
public bool IsDraft { get; set; }
|
||||
|
||||
public string CardExpirationShort { get; set; }
|
||||
public DateTime CardExpiration { get; set; }
|
||||
|
||||
public string CardSecurityNumber { get; set; }
|
||||
public string CardExpirationShort { get; set; }
|
||||
|
||||
public int CardTypeId { get; set; }
|
||||
public string CardSecurityNumber { get; set; }
|
||||
|
||||
public string Buyer { get; set; }
|
||||
public int CardTypeId { get; set; }
|
||||
|
||||
public string Buyer { get; set; }
|
||||
|
||||
public List<OrderItemData> OrderItems { get; } = new List<OrderItemData>();
|
||||
}
|
||||
|
||||
public List<OrderItemData> OrderItems { get; } = new();
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,19 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class OrderItemData
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public string ProductName { get; set; }
|
||||
public class OrderItemData
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public decimal Discount { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
public int Units { get; set; }
|
||||
public decimal Discount { get; set; }
|
||||
|
||||
public int Units { get; set; }
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
|
@ -1,9 +1,16 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class UpdateBasketItemData
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string BasketItemId { get; set; }
|
||||
|
||||
public int NewQty { get; set; }
|
||||
public class UpdateBasketItemData
|
||||
{
|
||||
public string BasketItemId { get; set; }
|
||||
|
||||
public int NewQty { get; set; }
|
||||
|
||||
public UpdateBasketItemData()
|
||||
{
|
||||
NewQty = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,18 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class UpdateBasketItemsRequest
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string BasketId { get; set; }
|
||||
|
||||
public ICollection<UpdateBasketItemData> Updates { get; set; }
|
||||
|
||||
public UpdateBasketItemsRequest()
|
||||
public class UpdateBasketItemsRequest
|
||||
{
|
||||
Updates = new List<UpdateBasketItemData>();
|
||||
public string BasketId { get; set; }
|
||||
|
||||
public ICollection<UpdateBasketItemData> Updates { get; set; }
|
||||
|
||||
public UpdateBasketItemsRequest()
|
||||
{
|
||||
Updates = new List<UpdateBasketItemData>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class UpdateBasketRequest
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string BuyerId { get; set; }
|
||||
|
||||
public IEnumerable<UpdateBasketRequestItemData> Items { get; set; }
|
||||
public class UpdateBasketRequest
|
||||
{
|
||||
public string BuyerId { get; set; }
|
||||
|
||||
public IEnumerable<UpdateBasketRequestItemData> Items { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
|
||||
public class UpdateBasketRequestItemData
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
|
||||
{
|
||||
public string Id { get; set; } // Basket id
|
||||
public class UpdateBasketRequestItemData
|
||||
{
|
||||
public string Id { get; set; } // Basket id
|
||||
|
||||
public int ProductId { get; set; } // Catalog item id
|
||||
public int ProductId { get; set; } // Catalog item id
|
||||
|
||||
public int Quantity { get; set; } // Quantity
|
||||
public int Quantity { get; set; } // Quantity
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,29 @@
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator;
|
||||
using Serilog;
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
BuildWebHost(args).Run();
|
||||
|
||||
builder.Services.AddReverseProxy(builder.Configuration);
|
||||
builder.Services.AddControllers();
|
||||
|
||||
builder.Services.AddHealthChecks(builder.Configuration);
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
// TODO: Read allowed origins from configuration
|
||||
options.AddPolicy("CorsPolicy",
|
||||
builder => builder
|
||||
.SetIsOriginAllowed((host) => true)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials());
|
||||
});
|
||||
|
||||
builder.Services.AddApplicationServices();
|
||||
builder.Services.AddGrpcServices();
|
||||
|
||||
builder.Services.Configure<UrlsConfig>(builder.Configuration.GetSection("urls"));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseServiceDefaults();
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseCors("CorsPolicy");
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
app.MapReverseProxy();
|
||||
|
||||
await app.RunAsync();
|
||||
IWebHost BuildWebHost(string[] args) =>
|
||||
WebHost
|
||||
.CreateDefaultBuilder(args)
|
||||
.ConfigureAppConfiguration(cb =>
|
||||
{
|
||||
var sources = cb.Sources;
|
||||
sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource()
|
||||
{
|
||||
Optional = true,
|
||||
Path = "appsettings.localhost.json",
|
||||
ReloadOnChange = false
|
||||
});
|
||||
})
|
||||
.UseStartup<Startup>()
|
||||
.UseSerilog((builderContext, config) =>
|
||||
{
|
||||
config
|
||||
.MinimumLevel.Information()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console();
|
||||
})
|
||||
.Build();
|
@ -1,12 +1,29 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:57425/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"Web.Shopping.HttpAggregator": {
|
||||
"commandName": "Project",
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5229/",
|
||||
"launchUrl": "api/values",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"PurchaseForMvc": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/values",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:61632/"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,95 +1,103 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using GrpcBasket;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class BasketService : IBasketService
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
|
||||
{
|
||||
private readonly Basket.BasketClient _basketClient;
|
||||
private readonly ILogger<BasketService> _logger;
|
||||
|
||||
public BasketService(Basket.BasketClient basketClient, ILogger<BasketService> logger)
|
||||
public class BasketService : IBasketService
|
||||
{
|
||||
_basketClient = basketClient;
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly Basket.BasketClient _basketClient;
|
||||
private readonly ILogger<BasketService> _logger;
|
||||
|
||||
public async Task<BasketData> GetByIdAsync(string id)
|
||||
{
|
||||
_logger.LogDebug("grpc client created, request = {@id}", id);
|
||||
var response = await _basketClient.GetBasketByIdAsync(new BasketRequest { Id = id });
|
||||
_logger.LogDebug("grpc response {@response}", response);
|
||||
|
||||
return MapToBasketData(response);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(BasketData currentBasket)
|
||||
{
|
||||
_logger.LogDebug("Grpc update basket currentBasket {@currentBasket}", currentBasket);
|
||||
var request = MapToCustomerBasketRequest(currentBasket);
|
||||
_logger.LogDebug("Grpc update basket request {@request}", request);
|
||||
|
||||
await _basketClient.UpdateBasketAsync(request);
|
||||
}
|
||||
|
||||
private BasketData MapToBasketData(CustomerBasketResponse customerBasketRequest)
|
||||
{
|
||||
if (customerBasketRequest == null)
|
||||
public BasketService(Basket.BasketClient basketClient, ILogger<BasketService> logger)
|
||||
{
|
||||
return null;
|
||||
_basketClient = basketClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
var map = new BasketData
|
||||
{
|
||||
BuyerId = customerBasketRequest.Buyerid
|
||||
};
|
||||
|
||||
customerBasketRequest.Items.ToList().ForEach(item =>
|
||||
public async Task<BasketData> GetById(string id)
|
||||
{
|
||||
if (item.Id != null)
|
||||
{
|
||||
map.Items.Add(new BasketDataItem
|
||||
{
|
||||
Id = item.Id,
|
||||
OldUnitPrice = (decimal)item.Oldunitprice,
|
||||
PictureUrl = item.Pictureurl,
|
||||
ProductId = item.Productid,
|
||||
ProductName = item.Productname,
|
||||
Quantity = item.Quantity,
|
||||
UnitPrice = (decimal)item.Unitprice
|
||||
});
|
||||
}
|
||||
});
|
||||
_logger.LogDebug("grpc client created, request = {@id}", id);
|
||||
var response = await _basketClient.GetBasketByIdAsync(new BasketRequest { Id = id });
|
||||
_logger.LogDebug("grpc response {@response}", response);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private CustomerBasketRequest MapToCustomerBasketRequest(BasketData basketData)
|
||||
{
|
||||
if (basketData == null)
|
||||
{
|
||||
return null;
|
||||
return MapToBasketData(response);
|
||||
}
|
||||
|
||||
var map = new CustomerBasketRequest
|
||||
public async Task UpdateAsync(BasketData currentBasket)
|
||||
{
|
||||
Buyerid = basketData.BuyerId
|
||||
};
|
||||
_logger.LogDebug("Grpc update basket currentBasket {@currentBasket}", currentBasket);
|
||||
var request = MapToCustomerBasketRequest(currentBasket);
|
||||
_logger.LogDebug("Grpc update basket request {@request}", request);
|
||||
|
||||
basketData.Items.ToList().ForEach(item =>
|
||||
await _basketClient.UpdateBasketAsync(request);
|
||||
}
|
||||
|
||||
private BasketData MapToBasketData(CustomerBasketResponse customerBasketRequest)
|
||||
{
|
||||
if (item.Id != null)
|
||||
if (customerBasketRequest == null)
|
||||
{
|
||||
map.Items.Add(new BasketItemResponse
|
||||
{
|
||||
Id = item.Id,
|
||||
Oldunitprice = (double)item.OldUnitPrice,
|
||||
Pictureurl = item.PictureUrl,
|
||||
Productid = item.ProductId,
|
||||
Productname = item.ProductName,
|
||||
Quantity = item.Quantity,
|
||||
Unitprice = (double)item.UnitPrice
|
||||
});
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
var map = new BasketData
|
||||
{
|
||||
BuyerId = customerBasketRequest.Buyerid
|
||||
};
|
||||
|
||||
customerBasketRequest.Items.ToList().ForEach(item =>
|
||||
{
|
||||
if (item.Id != null)
|
||||
{
|
||||
map.Items.Add(new BasketDataItem
|
||||
{
|
||||
Id = item.Id,
|
||||
OldUnitPrice = (decimal)item.Oldunitprice,
|
||||
PictureUrl = item.Pictureurl,
|
||||
ProductId = item.Productid,
|
||||
ProductName = item.Productname,
|
||||
Quantity = item.Quantity,
|
||||
UnitPrice = (decimal)item.Unitprice
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private CustomerBasketRequest MapToCustomerBasketRequest(BasketData basketData)
|
||||
{
|
||||
if (basketData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var map = new CustomerBasketRequest
|
||||
{
|
||||
Buyerid = basketData.BuyerId
|
||||
};
|
||||
|
||||
basketData.Items.ToList().ForEach(item =>
|
||||
{
|
||||
if (item.Id != null)
|
||||
{
|
||||
map.Items.Add(new BasketItemResponse
|
||||
{
|
||||
Id = item.Id,
|
||||
Oldunitprice = (double)item.OldUnitPrice,
|
||||
Pictureurl = item.PictureUrl,
|
||||
Productid = item.ProductId,
|
||||
Productname = item.ProductName,
|
||||
Quantity = item.Quantity,
|
||||
Unitprice = (double)item.UnitPrice
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,44 +1,52 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using CatalogApi;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class CatalogService : ICatalogService
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
|
||||
{
|
||||
private readonly Catalog.CatalogClient _client;
|
||||
private readonly ILogger<CatalogService> _logger;
|
||||
|
||||
public CatalogService(Catalog.CatalogClient client, ILogger<CatalogService> logger)
|
||||
public class CatalogService : ICatalogService
|
||||
{
|
||||
_client = client;
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly Catalog.CatalogClient _client;
|
||||
private readonly ILogger<CatalogService> _logger;
|
||||
|
||||
public async Task<CatalogItem> GetCatalogItemAsync(int id)
|
||||
{
|
||||
var request = new CatalogItemRequest { Id = id };
|
||||
_logger.LogInformation("grpc request {@request}", request);
|
||||
var response = await _client.GetItemByIdAsync(request);
|
||||
_logger.LogInformation("grpc response {@response}", response);
|
||||
return MapToCatalogItemResponse(response);
|
||||
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids)
|
||||
{
|
||||
var request = new CatalogItemsRequest { Ids = string.Join(",", ids), PageIndex = 1, PageSize = 10 };
|
||||
_logger.LogInformation("grpc request {@request}", request);
|
||||
var response = await _client.GetItemsByIdsAsync(request);
|
||||
_logger.LogInformation("grpc response {@response}", response);
|
||||
return response.Data.Select(this.MapToCatalogItemResponse);
|
||||
|
||||
}
|
||||
|
||||
private CatalogItem MapToCatalogItemResponse(CatalogItemResponse catalogItemResponse)
|
||||
{
|
||||
return new CatalogItem
|
||||
public CatalogService(Catalog.CatalogClient client, ILogger<CatalogService> logger)
|
||||
{
|
||||
Id = catalogItemResponse.Id,
|
||||
Name = catalogItemResponse.Name,
|
||||
PictureUri = catalogItemResponse.PictureUri,
|
||||
Price = (decimal)catalogItemResponse.Price
|
||||
};
|
||||
_client = client;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<CatalogItem> GetCatalogItemAsync(int id)
|
||||
{
|
||||
var request = new CatalogItemRequest { Id = id };
|
||||
_logger.LogInformation("grpc request {@request}", request);
|
||||
var response = await _client.GetItemByIdAsync(request);
|
||||
_logger.LogInformation("grpc response {@response}", response);
|
||||
return MapToCatalogItemResponse(response);
|
||||
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids)
|
||||
{
|
||||
var request = new CatalogItemsRequest { Ids = string.Join(",", ids), PageIndex = 1, PageSize = 10 };
|
||||
_logger.LogInformation("grpc request {@request}", request);
|
||||
var response = await _client.GetItemsByIdsAsync(request);
|
||||
_logger.LogInformation("grpc response {@response}", response);
|
||||
return response.Data.Select(this.MapToCatalogItemResponse);
|
||||
|
||||
}
|
||||
|
||||
private CatalogItem MapToCatalogItemResponse(CatalogItemResponse catalogItemResponse)
|
||||
{
|
||||
return new CatalogItem
|
||||
{
|
||||
Id = catalogItemResponse.Id,
|
||||
Name = catalogItemResponse.Name,
|
||||
PictureUri = catalogItemResponse.PictureUri,
|
||||
Price = (decimal)catalogItemResponse.Price
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IBasketService
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
|
||||
{
|
||||
Task<BasketData> GetByIdAsync(string id);
|
||||
public interface IBasketService
|
||||
{
|
||||
Task<BasketData> GetById(string id);
|
||||
|
||||
Task UpdateAsync(BasketData currentBasket);
|
||||
Task UpdateAsync(BasketData currentBasket);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface ICatalogService
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
|
||||
{
|
||||
Task<CatalogItem> GetCatalogItemAsync(int id);
|
||||
public interface ICatalogService
|
||||
{
|
||||
Task<CatalogItem> GetCatalogItemAsync(int id);
|
||||
|
||||
Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids);
|
||||
Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IOrderApiClient
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
|
||||
{
|
||||
Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket);
|
||||
public interface IOrderApiClient
|
||||
{
|
||||
Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IOrderingService
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
|
||||
{
|
||||
Task<OrderData> GetOrderDraftAsync(BasketData basketData);
|
||||
public interface IOrderingService
|
||||
{
|
||||
Task<OrderData> GetOrderDraftAsync(BasketData basketData);
|
||||
}
|
||||
}
|
@ -1,28 +1,40 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
|
||||
public class OrderApiClient : IOrderApiClient
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
|
||||
{
|
||||
private readonly HttpClient _apiClient;
|
||||
private readonly ILogger<OrderApiClient> _logger;
|
||||
private readonly UrlsConfig _urls;
|
||||
|
||||
public OrderApiClient(HttpClient httpClient, ILogger<OrderApiClient> logger, IOptions<UrlsConfig> config)
|
||||
public class OrderApiClient : IOrderApiClient
|
||||
{
|
||||
_apiClient = httpClient;
|
||||
_logger = logger;
|
||||
_urls = config.Value;
|
||||
}
|
||||
private readonly HttpClient _apiClient;
|
||||
private readonly ILogger<OrderApiClient> _logger;
|
||||
private readonly UrlsConfig _urls;
|
||||
|
||||
public async Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket)
|
||||
{
|
||||
var url = $"{_urls.Orders}{UrlsConfig.OrdersOperations.GetOrderDraft()}";
|
||||
var content = new StringContent(JsonSerializer.Serialize(basket), System.Text.Encoding.UTF8, "application/json");
|
||||
var response = await _apiClient.PostAsync(url, content);
|
||||
public OrderApiClient(HttpClient httpClient, ILogger<OrderApiClient> logger, IOptions<UrlsConfig> config)
|
||||
{
|
||||
_apiClient = httpClient;
|
||||
_logger = logger;
|
||||
_urls = config.Value;
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
public async Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket)
|
||||
{
|
||||
var url = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft();
|
||||
var content = new StringContent(JsonSerializer.Serialize(basket), System.Text.Encoding.UTF8, "application/json");
|
||||
var response = await _apiClient.PostAsync(url, content);
|
||||
|
||||
var ordersDraftResponse = await response.Content.ReadAsStringAsync();
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return JsonSerializer.Deserialize<OrderData>(ordersDraftResponse, JsonDefaults.CaseInsensitiveOptions);
|
||||
var ordersDraftResponse = await response.Content.ReadAsStringAsync();
|
||||
|
||||
return JsonSerializer.Deserialize<OrderData>(ordersDraftResponse, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,72 +1,79 @@
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using GrpcOrdering;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class OrderingService : IOrderingService
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
|
||||
{
|
||||
private readonly GrpcOrdering.OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
|
||||
private readonly ILogger<OrderingService> _logger;
|
||||
|
||||
public OrderingService(GrpcOrdering.OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
|
||||
public class OrderingService : IOrderingService
|
||||
{
|
||||
_orderingGrpcClient = orderingGrpcClient;
|
||||
_logger = logger;
|
||||
}
|
||||
private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
|
||||
private readonly ILogger<OrderingService> _logger;
|
||||
|
||||
public async Task<OrderData> GetOrderDraftAsync(BasketData basketData)
|
||||
{
|
||||
_logger.LogDebug(" grpc client created, basketData={@basketData}", basketData);
|
||||
|
||||
var command = MapToOrderDraftCommand(basketData);
|
||||
var response = await _orderingGrpcClient.CreateOrderDraftFromBasketDataAsync(command);
|
||||
_logger.LogDebug(" grpc response: {@response}", response);
|
||||
|
||||
return MapToResponse(response, basketData);
|
||||
}
|
||||
|
||||
private OrderData MapToResponse(GrpcOrdering.OrderDraftDTO orderDraft, BasketData basketData)
|
||||
{
|
||||
if (orderDraft == null)
|
||||
public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
|
||||
{
|
||||
return null;
|
||||
_orderingGrpcClient = orderingGrpcClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
var data = new OrderData
|
||||
public async Task<OrderData> GetOrderDraftAsync(BasketData basketData)
|
||||
{
|
||||
Buyer = basketData.BuyerId,
|
||||
Total = (decimal)orderDraft.Total,
|
||||
};
|
||||
_logger.LogDebug(" grpc client created, basketData={@basketData}", basketData);
|
||||
|
||||
orderDraft.OrderItems.ToList().ForEach(o => data.OrderItems.Add(new OrderItemData
|
||||
var command = MapToOrderDraftCommand(basketData);
|
||||
var response = await _orderingGrpcClient.CreateOrderDraftFromBasketDataAsync(command);
|
||||
_logger.LogDebug(" grpc response: {@response}", response);
|
||||
|
||||
return MapToResponse(response, basketData);
|
||||
}
|
||||
|
||||
private OrderData MapToResponse(GrpcOrdering.OrderDraftDTO orderDraft, BasketData basketData)
|
||||
{
|
||||
Discount = (decimal)o.Discount,
|
||||
PictureUrl = o.PictureUrl,
|
||||
ProductId = o.ProductId,
|
||||
ProductName = o.ProductName,
|
||||
UnitPrice = (decimal)o.UnitPrice,
|
||||
Units = o.Units,
|
||||
}));
|
||||
if (orderDraft == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = new OrderData
|
||||
{
|
||||
Buyer = basketData.BuyerId,
|
||||
Total = (decimal)orderDraft.Total,
|
||||
};
|
||||
|
||||
orderDraft.OrderItems.ToList().ForEach(o => data.OrderItems.Add(new OrderItemData
|
||||
{
|
||||
Discount = (decimal)o.Discount,
|
||||
PictureUrl = o.PictureUrl,
|
||||
ProductId = o.ProductId,
|
||||
ProductName = o.ProductName,
|
||||
UnitPrice = (decimal)o.UnitPrice,
|
||||
Units = o.Units,
|
||||
}));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
|
||||
{
|
||||
var command = new CreateOrderDraftCommand
|
||||
{
|
||||
BuyerId = basketData.BuyerId,
|
||||
};
|
||||
|
||||
basketData.Items.ForEach(i => command.Items.Add(new BasketItem
|
||||
{
|
||||
Id = i.Id,
|
||||
OldUnitPrice = (double)i.OldUnitPrice,
|
||||
PictureUrl = i.PictureUrl,
|
||||
ProductId = i.ProductId,
|
||||
ProductName = i.ProductName,
|
||||
Quantity = i.Quantity,
|
||||
UnitPrice = (double)i.UnitPrice,
|
||||
}));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private GrpcOrdering.CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
|
||||
{
|
||||
var command = new GrpcOrdering.CreateOrderDraftCommand
|
||||
{
|
||||
BuyerId = basketData.BuyerId,
|
||||
};
|
||||
|
||||
basketData.Items.ForEach(i => command.Items.Add(new GrpcOrdering.BasketItem
|
||||
{
|
||||
Id = i.Id,
|
||||
OldUnitPrice = (double)i.OldUnitPrice,
|
||||
PictureUrl = i.PictureUrl,
|
||||
ProductId = i.ProductId,
|
||||
ProductName = i.ProductName,
|
||||
Quantity = i.Quantity,
|
||||
UnitPrice = (double)i.UnitPrice,
|
||||
}));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
}
|
||||
|
225
src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs
Normal file
225
src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs
Normal file
@ -0,0 +1,225 @@
|
||||
using CatalogApi;
|
||||
using Devspaces.Support;
|
||||
using GrpcBasket;
|
||||
using GrpcOrdering;
|
||||
using HealthChecks.UI.Client;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure;
|
||||
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("self", () => HealthCheckResult.Healthy())
|
||||
.AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" })
|
||||
.AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" })
|
||||
.AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" })
|
||||
.AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" })
|
||||
.AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" });
|
||||
|
||||
services.AddCustomMvc(Configuration)
|
||||
.AddCustomAuthentication(Configuration)
|
||||
.AddDevspaces()
|
||||
.AddApplicationServices()
|
||||
.AddGrpcServices();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
|
||||
{
|
||||
var pathBase = Configuration["PATH_BASE"];
|
||||
if (!string.IsNullOrEmpty(pathBase))
|
||||
{
|
||||
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
|
||||
app.UsePathBase(pathBase);
|
||||
}
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseSwagger().UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1");
|
||||
|
||||
c.OAuthClientId("webshoppingaggswaggerui");
|
||||
c.OAuthClientSecret(string.Empty);
|
||||
c.OAuthRealm(string.Empty);
|
||||
c.OAuthAppName("web shopping bff Swagger UI");
|
||||
});
|
||||
|
||||
app.UseRouting();
|
||||
app.UseCors("CorsPolicy");
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapDefaultControllerRoute();
|
||||
endpoints.MapControllers();
|
||||
endpoints.MapHealthChecks("/hc", new HealthCheckOptions()
|
||||
{
|
||||
Predicate = _ => true,
|
||||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
||||
});
|
||||
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
|
||||
{
|
||||
Predicate = r => r.Name.Contains("self")
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
|
||||
|
||||
var identityUrl = configuration.GetValue<string>("urls:identity");
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
|
||||
})
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.Authority = identityUrl;
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.Audience = "webshoppingagg";
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddOptions();
|
||||
services.Configure<UrlsConfig>(configuration.GetSection("urls"));
|
||||
|
||||
services.AddControllers()
|
||||
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
|
||||
|
||||
services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.DescribeAllEnumsAsStrings();
|
||||
|
||||
options.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "Shopping Aggregator for Web Clients",
|
||||
Version = "v1",
|
||||
Description = "Shopping Aggregator for Web Clients"
|
||||
});
|
||||
|
||||
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
|
||||
{
|
||||
Type = SecuritySchemeType.OAuth2,
|
||||
Flows = new OpenApiOAuthFlows()
|
||||
{
|
||||
Implicit = new OpenApiOAuthFlow()
|
||||
{
|
||||
AuthorizationUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"),
|
||||
TokenUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"),
|
||||
|
||||
Scopes = new Dictionary<string, string>()
|
||||
{
|
||||
{ "webshoppingagg", "Shopping Aggregator for Web Clients" }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
options.OperationFilter<AuthorizeCheckOperationFilter>();
|
||||
});
|
||||
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("CorsPolicy",
|
||||
builder => builder
|
||||
.SetIsOriginAllowed((host) => true)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials());
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
|
||||
{
|
||||
//register delegating handlers
|
||||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
|
||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
|
||||
//register http services
|
||||
|
||||
services.AddHttpClient<IOrderApiClient, OrderApiClient>()
|
||||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
|
||||
.AddDevspacesSupport();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddGrpcServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<IBasketService, BasketService>();
|
||||
|
||||
services.AddGrpcClient<Basket.BasketClient>((services, options) =>
|
||||
{
|
||||
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket;
|
||||
options.Address = new Uri(basketApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<ICatalogService, CatalogService>();
|
||||
|
||||
services.AddGrpcClient<Catalog.CatalogClient>((services, options) =>
|
||||
{
|
||||
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog;
|
||||
options.Address = new Uri(catalogApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
services.AddScoped<IOrderingService, OrderingService>();
|
||||
|
||||
services.AddGrpcClient<OrderingGrpc.OrderingGrpcClient>((services, options) =>
|
||||
{
|
||||
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering;
|
||||
options.Address = new Uri(orderingApi);
|
||||
}).AddInterceptor<GrpcExceptionInterceptor>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AssemblyName>Web.Shopping.HttpAggregator</AssemblyName>
|
||||
<RootNamespace>Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
|
||||
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Yarp.ReverseProxy" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Uris" />
|
||||
<PackageReference Include="Google.Protobuf" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" />
|
||||
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Services\Services.Common\Services.Common.csproj" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="5.0.1" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="5.0.1" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.14.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" Version="2.34.0" />
|
||||
<PackageReference Include="Grpc.Core" Version="2.34.0" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.34.0" />
|
||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.34.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.34.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.2" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0-dev-00834" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
|
||||
<!--<PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.6.0-rc1.19456.4" />-->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\Devspaces.Support\Devspaces.Support.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,8 +1,15 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug"
|
||||
}
|
||||
},
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,138 +1,15 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"System.Net.Http": "Warning"
|
||||
}
|
||||
},
|
||||
"OpenApi": {
|
||||
"Endpoint": {
|
||||
"Name": "Purchase BFF V1"
|
||||
},
|
||||
"Document": {
|
||||
"Description": "Shopping Aggregator for Web Clients",
|
||||
"Title": "Shopping Aggregator for Web Clients",
|
||||
"Version": "v1"
|
||||
},
|
||||
"Auth": {
|
||||
"ClientId": "webshoppingaggswaggerui",
|
||||
"AppName": "Web Shopping BFF Swagger UI"
|
||||
}
|
||||
},
|
||||
"Identity": {
|
||||
"Url": "http://localhost:5223",
|
||||
"Audience": "webshoppingagg",
|
||||
"Scopes": {
|
||||
"webshoppingagg": "Shopping Aggregator for Web Clients"
|
||||
}
|
||||
},
|
||||
"ReverseProxy": {
|
||||
"Routes": {
|
||||
"c-short": {
|
||||
"ClusterId": "catalog",
|
||||
"Match": {
|
||||
"Path": "c/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/c" }
|
||||
]
|
||||
},
|
||||
"c-long": {
|
||||
"ClusterId": "catalog",
|
||||
"Match": {
|
||||
"Path": "catalog-api/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/catalog-api" }
|
||||
]
|
||||
},
|
||||
"b-short": {
|
||||
"ClusterId": "basket",
|
||||
"Match": {
|
||||
"Path": "b/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/b" }
|
||||
]
|
||||
},
|
||||
"b-long": {
|
||||
"ClusterId": "basket",
|
||||
"Match": {
|
||||
"Path": "basket-api/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/basket-api" }
|
||||
]
|
||||
},
|
||||
"o-short": {
|
||||
"ClusterId": "orders",
|
||||
"Match": {
|
||||
"Path": "o/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/o" }
|
||||
]
|
||||
},
|
||||
"o-long": {
|
||||
"ClusterId": "orders",
|
||||
"Match": {
|
||||
"Path": "ordering-api/{**catch-all}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathRemovePrefix": "/ordering-api" }
|
||||
]
|
||||
},
|
||||
"h-long": {
|
||||
"ClusterId": "signalr",
|
||||
"Match": {
|
||||
"Path": "hub/notificationhub/{**catch-all}"
|
||||
}
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"Clusters": {
|
||||
"basket": {
|
||||
"Destinations": {
|
||||
"destination0": {
|
||||
"Address": "http://localhost:5221"
|
||||
}
|
||||
}
|
||||
},
|
||||
"catalog": {
|
||||
"Destinations": {
|
||||
"destination0": {
|
||||
"Address": "http://localhost:5222"
|
||||
}
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"Destinations": {
|
||||
"destination0": {
|
||||
"Address": "http://localhost:5224"
|
||||
}
|
||||
}
|
||||
},
|
||||
"signalr": {
|
||||
"Destinations": {
|
||||
"destination0": {
|
||||
"Address": "http://localhost:5225"
|
||||
}
|
||||
}
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Urls": {
|
||||
"Basket": "http://localhost:5221",
|
||||
"Catalog": "http://localhost:5222",
|
||||
"Orders": "http://localhost:5224",
|
||||
"Identity": "http://localhost:5223",
|
||||
"Signalr": "http://localhost:5225",
|
||||
"GrpcBasket": "http://localhost:6221",
|
||||
"GrpcCatalog": "http://localhost:6222",
|
||||
"GrpcOrdering": "http://localhost:6224"
|
||||
},
|
||||
"CatalogUrlHC": "http://localhost:5222/hc",
|
||||
"OrderingUrlHC": "http://localhost:5224/hc",
|
||||
"BasketUrlHC": "http://localhost:5221/hc",
|
||||
"IdentityUrlHC": "http://localhost:5223/hc"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"urls": {
|
||||
"basket": "http://localhost:55105",
|
||||
"catalog": "http://localhost:55101",
|
||||
"orders": "http://localhost:55102",
|
||||
"identity": "http://localhost:55105",
|
||||
"grpcBasket": "http://localhost:5580",
|
||||
"grpcCatalog": "http://localhost:81",
|
||||
"grpcOrdering": "http://localhost:5581"
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user