Browse Source

Finally deployed

pull/2032/head
Siarhei-Sialitski 2 years ago
parent
commit
d60be0ccaa
64 changed files with 1619 additions and 86 deletions
  1. +35
    -33
      deploy/k8s/helm/app.yaml
  2. +21
    -0
      deploy/k8s/helm/coupon-api/.helmignore
  3. +5
    -0
      deploy/k8s/helm/coupon-api/Chart.yaml
  4. +9
    -0
      deploy/k8s/helm/coupon-api/templates/NOTES.txt
  5. +32
    -0
      deploy/k8s/helm/coupon-api/templates/_helpers.tpl
  6. +60
    -0
      deploy/k8s/helm/coupon-api/templates/_names.tpl
  7. +21
    -0
      deploy/k8s/helm/coupon-api/templates/configmap.yaml
  8. +99
    -0
      deploy/k8s/helm/coupon-api/templates/deployment.yaml
  9. +23
    -0
      deploy/k8s/helm/coupon-api/templates/service.yaml
  10. +61
    -0
      deploy/k8s/helm/coupon-api/values.yaml
  11. +45
    -43
      deploy/k8s/helm/deploy-all.ps1
  12. +2
    -0
      deploy/k8s/helm/webshoppingagg/templates/configmap.yaml
  13. +2
    -0
      deploy/k8s/helm/webstatus/templates/configmap.yaml
  14. +6
    -5
      deploy/k8s/helm/webstatus/values.yaml
  15. +1
    -0
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile
  16. +1
    -0
      src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile
  17. +1
    -0
      src/Services/Basket/Basket.API/Dockerfile
  18. +1
    -0
      src/Services/Catalog/Catalog.API/Dockerfile
  19. +30
    -0
      src/Services/Coupon/Coupon.API/Controllers/CouponController.cs
  20. +63
    -0
      src/Services/Coupon/Coupon.API/Coupon.API.csproj
  21. +15
    -0
      src/Services/Coupon/Coupon.API/CouponSettings.cs
  22. +62
    -0
      src/Services/Coupon/Coupon.API/Dockerfile
  23. +49
    -0
      src/Services/Coupon/Coupon.API/Extensions/IHostBuilderExtensions.cs
  24. +204
    -0
      src/Services/Coupon/Coupon.API/Extensions/IServiceCollectionExtensions.cs
  25. +35
    -0
      src/Services/Coupon/Coupon.API/Filters/AuthorizeCheckOperationFilter.cs
  26. +16
    -0
      src/Services/Coupon/Coupon.API/Filters/ValidateModelAttribute.cs
  27. +33
    -0
      src/Services/Coupon/Coupon.API/GlobalUsings.cs
  28. +52
    -0
      src/Services/Coupon/Coupon.API/Infrastructure/CouponSeed.cs
  29. +20
    -0
      src/Services/Coupon/Coupon.API/Infrastructure/Models/Coupon.cs
  30. +25
    -0
      src/Services/Coupon/Coupon.API/Infrastructure/Repositories/CouponContext.cs
  31. +42
    -0
      src/Services/Coupon/Coupon.API/Infrastructure/Repositories/CouponRepository.cs
  32. +14
    -0
      src/Services/Coupon/Coupon.API/Infrastructure/Repositories/ICouponRepository.cs
  33. +56
    -0
      src/Services/Coupon/Coupon.API/IntegrationEvents/EventHandlers/OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler.cs
  34. +22
    -0
      src/Services/Coupon/Coupon.API/IntegrationEvents/EventHandlers/OrderStatusChangedToCancelledIntegrationEventHandler.cs
  35. +14
    -0
      src/Services/Coupon/Coupon.API/IntegrationEvents/Events/OrderCouponConfirmedIntegrationEvent.cs
  36. +14
    -0
      src/Services/Coupon/Coupon.API/IntegrationEvents/Events/OrderCouponRejectedIntegrationEvent.cs
  37. +18
    -0
      src/Services/Coupon/Coupon.API/IntegrationEvents/Events/OrderStatusChangedToAwaitingCouponValidationIntegrationEvent.cs
  38. +18
    -0
      src/Services/Coupon/Coupon.API/IntegrationEvents/Events/OrderStatusChangedToCancelledIntegrationEvent.cs
  39. +53
    -0
      src/Services/Coupon/Coupon.API/Program.cs
  40. +12
    -0
      src/Services/Coupon/Coupon.API/Properties/launchSettings.json
  41. +94
    -0
      src/Services/Coupon/Coupon.API/Startup.cs
  42. +18
    -0
      src/Services/Coupon/Coupon.API/appsettings.Development.json
  43. +28
    -0
      src/Services/Coupon/Coupon.API/appsettings.json
  44. +1
    -0
      src/Services/Identity/Identity.API/Dockerfile
  45. +2
    -2
      src/Services/Identity/Identity.API/Properties/launchSettings.json
  46. +1
    -0
      src/Services/Ordering/Ordering.API/Dockerfile
  47. +1
    -0
      src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile
  48. +1
    -0
      src/Services/Ordering/Ordering.SignalrHub/Dockerfile
  49. +1
    -0
      src/Services/Payment/Payment.API/Dockerfile
  50. +1
    -0
      src/Services/Webhooks/Webhooks.API/Dockerfile
  51. +1
    -0
      src/Web/WebMVC/Dockerfile
  52. +22
    -0
      src/Web/WebSPA/Client/src/modules/basket/basket.service.ts
  53. +9
    -0
      src/Web/WebSPA/Client/src/modules/orders/orders-detail/orders-detail.component.html
  54. +32
    -0
      src/Web/WebSPA/Client/src/modules/orders/orders-new/orders-new.component.html
  55. +28
    -1
      src/Web/WebSPA/Client/src/modules/orders/orders-new/orders-new.component.ts
  56. +9
    -0
      src/Web/WebSPA/Client/src/modules/orders/orders.service.ts
  57. +5
    -0
      src/Web/WebSPA/Client/src/modules/shared/models/coupon.model.ts
  58. +3
    -0
      src/Web/WebSPA/Client/src/modules/shared/models/order-detail.model.ts
  59. +3
    -0
      src/Web/WebSPA/Client/src/modules/shared/models/order.model.ts
  60. +1
    -0
      src/Web/WebSPA/Dockerfile
  61. +1
    -0
      src/Web/WebStatus/Dockerfile
  62. +1
    -0
      src/Web/WebhookClient/Dockerfile
  63. +8
    -0
      src/docker-compose.yml
  64. +56
    -2
      src/eShopOnContainers-ServicesAndWebApps.sln

+ 35
- 33
deploy/k8s/helm/app.yaml View File

@ -1,38 +1,40 @@
# This helm values file defines app-based settings
# Charts use those values, so this file **MUST** be included in all chart releases
app: # app global settings
name: "my-eshop" # Override for custom app name
ingress: # ingress related settings
app: # app global settings
name: "my-eshop" # Override for custom app name
ingress: # ingress related settings
entries:
basket: basket-api # ingress entry for basket api
catalog: catalog-api # ingress entry for catalog api
ordering: ordering-api # ingress entry for ordering api
identity: identity # ingress entry for identity api
mvc: webmvc # ingress entry for web mvc
spa: "" # ingress entry for web spa
status: webstatus # ingress entry for web status
webshoppingapigw: webshoppingapigw # ingress entry for web shopping Agw
mobileshoppingapigw: mobileshoppingapigw # ingress entry for mobile shopping Agw
webshoppingagg: webshoppingagg # ingress entry for web shopping aggregator
mobileshoppingagg: mobileshoppingagg # ingress entry for mobile shopping aggregator
payment: payment-api # ingress entry for payment api
webhooks: webhooks-api # ingress entry for webhooks api
webhooksweb: webhooks-web # ingress entry for webhooks web demo client
basket: basket-api # ingress entry for basket api
catalog: catalog-api # ingress entry for catalog api
coupon: coupon-api # ingress entry for coupon api
ordering: ordering-api # ingress entry for ordering api
identity: identity # ingress entry for identity api
mvc: webmvc # ingress entry for web mvc
spa: "" # ingress entry for web spa
status: webstatus # ingress entry for web status
webshoppingapigw: webshoppingapigw # ingress entry for web shopping Agw
mobileshoppingapigw: mobileshoppingapigw # ingress entry for mobile shopping Agw
webshoppingagg: webshoppingagg # ingress entry for web shopping aggregator
mobileshoppingagg: mobileshoppingagg # ingress entry for mobile shopping aggregator
payment: payment-api # ingress entry for payment api
webhooks: webhooks-api # ingress entry for webhooks api
webhooksweb: webhooks-web # ingress entry for webhooks web demo client
svc:
basket: basket-api # service name for basket api
catalog: catalog-api # service name for catalog api
ordering: ordering-api # service name for ordering api
orderingbackgroundtasks: ordering-backgroundtasks # service name for orderingbackgroundtasks
orderingsignalrhub: ordering-signalrhub # service name for orderingsignalrhub
identity: identity-api # service name for identity api
mvc: webmvc # service name for web mvc
spa: webspa # service name for web spa
status: webstatus # service name for web status
webshoppingapigw: webshoppingapigw # service name for web shopping Agw
mobileshoppingapigw: mobileshoppingapigw # service name for mobile shopping Agw
webshoppingagg: webshoppingagg # service name for web shopping aggregator
mobileshoppingagg: mobileshoppingagg # service name for mobile shopping aggregator
payment: payment-api # service name for payment api
webhooks: webhooks-api # service name for webhooks api
webhooksweb: webhooks-client # service name for webhooks web
basket: basket-api # service name for basket api
catalog: catalog-api # service name for catalog api
coupon: coupon-api # service name for coupon api
ordering: ordering-api # service name for ordering api
orderingbackgroundtasks: ordering-backgroundtasks # service name for orderingbackgroundtasks
orderingsignalrhub: ordering-signalrhub # service name for orderingsignalrhub
identity: identity-api # service name for identity api
mvc: webmvc # service name for web mvc
spa: webspa # service name for web spa
status: webstatus # service name for web status
webshoppingapigw: webshoppingapigw # service name for web shopping Agw
mobileshoppingapigw: mobileshoppingapigw # service name for mobile shopping Agw
webshoppingagg: webshoppingagg # service name for web shopping aggregator
mobileshoppingagg: mobileshoppingagg # service name for mobile shopping aggregator
payment: payment-api # service name for payment api
webhooks: webhooks-api # service name for webhooks api
webhooksweb: webhooks-client # service name for webhooks web

+ 21
- 0
deploy/k8s/helm/coupon-api/.helmignore View File

@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj

+ 5
- 0
deploy/k8s/helm/coupon-api/Chart.yaml View File

@ -0,0 +1,5 @@
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: coupon-api
version: 0.1.0

+ 9
- 0
deploy/k8s/helm/coupon-api/templates/NOTES.txt View File

@ -0,0 +1,9 @@
eShop Coupon API installed.
----------------------------
This API is not directly exposed outside cluster. If need to access it use:
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "coupon-api.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

+ 32
- 0
deploy/k8s/helm/coupon-api/templates/_helpers.tpl View File

@ -0,0 +1,32 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "coupon-api.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "coupon-api.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "coupon-api.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

+ 60
- 0
deploy/k8s/helm/coupon-api/templates/_names.tpl View File

@ -0,0 +1,60 @@
{{- define "suffix-name" -}}
{{- if .Values.app.name -}}
{{- .Values.app.name -}}
{{- else -}}
{{- .Release.Name -}}
{{- end -}}
{{- end -}}
{{- define "sql-name" -}}
{{- if .Values.inf.sql.host -}}
{{- .Values.inf.sql.host -}}
{{- else -}}
{{- printf "%s" "sql-data" -}}
{{- end -}}
{{- end -}}
{{- define "mongo-name" -}}
{{- if .Values.inf.mongo.host -}}
{{- .Values.inf.mongo.host -}}
{{- else -}}
{{- printf "%s" "nosql-data" -}}
{{- end -}}
{{- end -}}
{{- define "url-of" -}}
{{- $name := first .}}
{{- $ctx := last .}}
{{- if eq $name "" -}}
{{- $ctx.Values.inf.k8s.dns -}}
{{- else -}}
{{- printf "%s/%s" $ctx.Values.inf.k8s.dns $name -}} {{/*Value is just <dns>/<name> */}}
{{- end -}}
{{- end -}}
{{- define "pathBase" -}}
{{- if .Values.inf.k8s.suffix -}}
{{- $suffix := include "suffix-name" . -}}
{{- printf "%s-%s" .Values.pathBase $suffix -}}
{{- else -}}
{{- .Values.pathBase -}}
{{- end -}}
{{- end -}}
{{- define "fqdn-image" -}}
{{- if .Values.inf.registry -}}
{{- printf "%s/%s" .Values.inf.registry.server .Values.image.repository -}}
{{- else -}}
{{- .Values.image.repository -}}
{{- end -}}
{{- end -}}
{{- define "protocol" -}}
{{- if .Values.inf.tls.enabled -}}
{{- printf "%s" "https" -}}
{{- else -}}
{{- printf "%s" "http" -}}
{{- end -}}
{{- end -}}

+ 21
- 0
deploy/k8s/helm/coupon-api/templates/configmap.yaml View File

@ -0,0 +1,21 @@
{{- $name := include "coupon-api.fullname" . -}}
{{- $mongo := include "mongo-name" . -}}
{{- $webshoppingapigw := include "url-of" (list .Values.app.ingress.entries.webshoppingapigw .) -}}
{{- $protocol := include "protocol" . -}}
kind: ConfigMap
apiVersion: v1
metadata:
name: "cfg-{{ $name }}"
labels:
app: {{ template "coupon-api.name" . }}
chart: {{ template "coupon-api.chart" .}}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
data:
coupon__MongoDatabase: "CouponDb"
coupon__ConnectionString: mongodb://nosql-data
urls__IdentityUrl: http://{{ .Values.app.svc.identity }}
all__EventBusConnection: {{ .Values.inf.eventbus.constr }}
all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}"
all__UseAzureServiceBus: "{{ .Values.inf.eventbus.useAzure }}"

+ 99
- 0
deploy/k8s/helm/coupon-api/templates/deployment.yaml View File

@ -0,0 +1,99 @@
{{- $name := include "coupon-api.fullname" . -}}
{{- $cfgname := printf "%s-%s" "cfg" $name -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "coupon-api.fullname" . }}
labels:
ufo: {{ $cfgname}}
app: {{ template "coupon-api.name" . }}
chart: {{ template "coupon-api.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ template "coupon-api.name" . }}
release: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ template "coupon-api.name" . }}
release: {{ .Release.Name }}
{{ if .Values.inf.mesh.enabled -}}
annotations:
linkerd.io/inject: enabled
{{- end }}
spec:
{{ if .Values.inf.registry -}}
imagePullSecrets:
- name: {{ .Values.inf.registry.secretName }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
{{ if .Values.probes -}}
{{- if .Values.probes.liveness -}}
livenessProbe:
httpGet:
port: {{ .Values.probes.liveness.port }}
path: {{ .Values.probes.liveness.path }}
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
{{- end -}}
{{- end -}}
{{- if .Values.probes -}}
{{- if .Values.probes.readiness }}
readinessProbe:
httpGet:
port: {{ .Values.probes.readiness.port }}
path: {{ .Values.probes.readiness.path }}
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
{{- end -}}
{{- end }}
image: "{{ template "fqdn-image" . }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: PATH_BASE
value: {{ include "pathBase" . }}
- name: k8sname
value: {{ .Values.clusterName }}
{{- if .Values.env.values -}}
{{- range .Values.env.values }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end -}}
{{- end -}}
{{- if .Values.env.configmap -}}
{{- range .Values.env.configmap }}
- name: {{ .name }}
valueFrom:
configMapKeyRef:
name: {{ $cfgname }}
key: {{ .key }}
{{- end -}}
{{- end }}
ports:
- name: http
containerPort: 80
protocol: TCP
- name: grpc
containerPort: 81
protocol: TCP
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}

+ 23
- 0
deploy/k8s/helm/coupon-api/templates/service.yaml View File

@ -0,0 +1,23 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.app.svc.coupon }}
labels:
app: {{ template "coupon-api.name" . }}
chart: {{ template "coupon-api.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
- port: {{ .Values.service.grpcPort }}
targetPort: grpc
protocol: TCP
name: grpc
selector:
app: {{ template "coupon-api.name" . }}
release: {{ .Release.Name }}

+ 61
- 0
deploy/k8s/helm/coupon-api/values.yaml View File

@ -0,0 +1,61 @@
replicaCount: 1
clusterName: eshop-aks
pathBase: /coupon-api
image:
repository: eshop/coupon.api
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
grpcPort: 81
resources: {}
nodeSelector: {}
tolerations: []
affinity: {}
# env defines the environment variables that will be declared in the pod
env:
urls:
# configmap declares variables which value is taken from the config map defined in template configmap.yaml (name is name of var and key the key in configmap).
configmap:
- name: ConnectionString
key: coupon__ConnectionString
- name: ApplicationInsights__InstrumentationKey
key: all__InstrumentationKey
- name: EventBusConnection
key: all__EventBusConnection
- name: AzureServiceBusEnabled
key: all__UseAzureServiceBus
- name: CouponMongoDatabase
key: coupon__MongoDatabase
- name: IdentityUrl
key: urls__IdentityUrl
# values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value)
values:
- name: ASPNETCORE_ENVIRONMENT
value: Development
- name: OrchestratorType
value: "K8S"
- name: PORT
value: "80"
- name: GRPC_PORT
value: "81"
probes:
liveness:
path: /liveness
initialDelaySeconds: 10
periodSeconds: 15
port: 80
readiness:
path: /hc
timeoutSeconds: 5
initialDelaySeconds: 90
periodSeconds: 60
port: 80

+ 45
- 43
deploy/k8s/helm/deploy-all.ps1 View File

@ -1,27 +1,27 @@
Param(
[parameter(Mandatory=$false)][string]$registry,
[parameter(Mandatory=$false)][string]$dockerUser,
[parameter(Mandatory=$false)][string]$dockerPassword,
[parameter(Mandatory=$false)][string]$externalDns,
[parameter(Mandatory=$false)][string]$appName="eshop",
[parameter(Mandatory=$false)][bool]$deployInfrastructure=$true,
[parameter(Mandatory=$false)][bool]$deployCharts=$true,
[parameter(Mandatory=$false)][bool]$clean=$true,
[parameter(Mandatory=$false)][string]$aksName="",
[parameter(Mandatory=$false)][string]$aksRg="",
[parameter(Mandatory=$false)][string]$imageTag="latest",
[parameter(Mandatory=$false)][bool]$useLocalk8s=$false,
[parameter(Mandatory=$false)][bool]$useMesh=$false,
[parameter(Mandatory=$false)][string][ValidateSet('Always','IfNotPresent','Never', IgnoreCase=$false)]$imagePullPolicy="Always",
[parameter(Mandatory=$false)][string][ValidateSet('prod','staging','none','custom', IgnoreCase=$false)]$sslSupport = "none",
[parameter(Mandatory=$false)][string]$tlsSecretName = "eshop-tls-custom",
[parameter(Mandatory=$false)][string]$chartsToDeploy="*",
[parameter(Mandatory=$false)][string]$ingressMeshAnnotationsFile="ingress_values_linkerd.yaml"
)
function Install-Chart {
Param([string]$chart,[string]$initialOptions, [bool]$customRegistry)
$options=$initialOptions
[parameter(Mandatory = $false)][string]$registry,
[parameter(Mandatory = $false)][string]$dockerUser,
[parameter(Mandatory = $false)][string]$dockerPassword,
[parameter(Mandatory = $false)][string]$externalDns,
[parameter(Mandatory = $false)][string]$appName = "eshop",
[parameter(Mandatory = $false)][bool]$deployInfrastructure = $true,
[parameter(Mandatory = $false)][bool]$deployCharts = $true,
[parameter(Mandatory = $false)][bool]$clean = $true,
[parameter(Mandatory = $false)][string]$aksName = "",
[parameter(Mandatory = $false)][string]$aksRg = "",
[parameter(Mandatory = $false)][string]$imageTag = "latest",
[parameter(Mandatory = $false)][bool]$useLocalk8s = $false,
[parameter(Mandatory = $false)][bool]$useMesh = $false,
[parameter(Mandatory = $false)][string][ValidateSet('Always', 'IfNotPresent', 'Never', IgnoreCase = $false)]$imagePullPolicy = "Always",
[parameter(Mandatory = $false)][string][ValidateSet('prod', 'staging', 'none', 'custom', IgnoreCase = $false)]$sslSupport = "none",
[parameter(Mandatory = $false)][string]$tlsSecretName = "eshop-tls-custom",
[parameter(Mandatory = $false)][string]$chartsToDeploy = "*",
[parameter(Mandatory = $false)][string]$ingressMeshAnnotationsFile = "ingress_values_linkerd.yaml"
)
function Install-Chart {
Param([string]$chart, [string]$initialOptions, [bool]$customRegistry)
$options = $initialOptions
if ($sslEnabled) {
$options = "$options --set ingress.tls[0].secretName=$tlsSecretName --set ingress.tls[0].hosts={$dns}"
if ($sslSupport -ne "custom") {
@ -32,7 +32,8 @@ function Install-Chart {
$options = "$options --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret"
}
if ($chart -ne "eshop-common" -or $customRegistry) { # eshop-common is ignored when no secret must be deployed
if ($chart -ne "eshop-common" -or $customRegistry) {
# eshop-common is ignored when no secret must be deployed
$command = "install $appName-$chart $options $chart"
Write-Host "Helm Command: helm $command" -ForegroundColor Gray
Invoke-Expression 'cmd /c "helm $command"'
@ -40,32 +41,32 @@ function Install-Chart {
}
$dns = $externalDns
$sslEnabled=$false
$sslIssuer=""
$sslEnabled = $false
$sslIssuer = ""
if ($sslSupport -eq "staging") {
$sslEnabled=$true
$tlsSecretName="eshop-letsencrypt-staging"
$sslIssuer="letsencrypt-staging"
$sslEnabled = $true
$tlsSecretName = "eshop-letsencrypt-staging"
$sslIssuer = "letsencrypt-staging"
}
elseif ($sslSupport -eq "prod") {
$sslEnabled=$true
$tlsSecretName="eshop-letsencrypt-prod"
$sslIssuer="letsencrypt-prod"
$sslEnabled = $true
$tlsSecretName = "eshop-letsencrypt-prod"
$sslIssuer = "letsencrypt-prod"
}
elseif ($sslSupport -eq "custom") {
$sslEnabled=$true
$sslEnabled = $true
}
$ingressValuesFile="ingress_values.yaml"
$ingressValuesFile = "ingress_values.yaml"
if ($useLocalk8s -eq $true) {
$ingressValuesFile="ingress_values_dockerk8s.yaml"
$dns="localhost"
$ingressValuesFile = "ingress_values_dockerk8s.yaml"
$dns = "localhost"
}
if ($externalDns -eq "aks") {
if ([string]::IsNullOrEmpty($aksName) -or [string]::IsNullOrEmpty($aksRg)) {
if ([string]::IsNullOrEmpty($aksName) -or [string]::IsNullOrEmpty($aksRg)) {
Write-Host "Error: When using -dns aks, MUST set -aksName and -aksRg too." -ForegroundColor Red
exit 1
}
@ -95,21 +96,22 @@ if ($useLocalk8s -and $sslEnabled) {
}
if ($clean) {
$listOfReleases=$(helm ls --filter eshop -q)
$listOfReleases = $(helm ls --filter eshop -q)
if ([string]::IsNullOrEmpty($listOfReleases)) {
Write-Host "No previous releases found!" -ForegroundColor Green
}else{
}
else {
Write-Host "Previous releases found" -ForegroundColor Green
Write-Host "Cleaning previous helm releases..." -ForegroundColor Green
helm uninstall $listOfReleases
Write-Host "Previous releases deleted" -ForegroundColor Green
}
}
}
$useCustomRegistry=$false
$useCustomRegistry = $false
if (-not [string]::IsNullOrEmpty($registry)) {
$useCustomRegistry=$true
$useCustomRegistry = $true
if ([string]::IsNullOrEmpty($dockerUser) -or [string]::IsNullOrEmpty($dockerPassword)) {
Write-Host "Error: Must use -dockerUser AND -dockerPassword if specifying custom registry" -ForegroundColor Red
exit 1
@ -119,7 +121,7 @@ if (-not [string]::IsNullOrEmpty($registry)) {
Write-Host "Begin eShopOnContainers installation using Helm" -ForegroundColor Green
$infras = ("sql-data", "nosql-data", "rabbitmq", "keystore-data", "basket-data")
$charts = ("eshop-common", "basket-api","catalog-api", "identity-api", "mobileshoppingagg","ordering-api","ordering-backgroundtasks","ordering-signalrhub", "payment-api", "webmvc", "webshoppingagg", "webspa", "webstatus", "webhooks-api", "webhooks-web")
$charts = ("eshop-common", "basket-api", "catalog-api", "coupon-api", "identity-api", "mobileshoppingagg", "ordering-api", "ordering-backgroundtasks", "ordering-signalrhub", "payment-api", "webmvc", "webshoppingagg", "webspa", "webstatus", "webhooks-api", "webhooks-web")
$gateways = ("apigwms", "apigwws")
if ($deployInfrastructure) {


+ 2
- 0
deploy/k8s/helm/webshoppingagg/templates/configmap.yaml View File

@ -16,10 +16,12 @@ data:
webshoppingagg__keystore: {{ .Values.inf.redis.keystore.constr }}
internalurls__basket: http://{{ .Values.app.svc.basket }}
internalurls__catalog: http://{{ .Values.app.svc.catalog }}
internalurls__coupon: http://{{ .Values.app.svc.coupon }}
internalurls__identity: http://{{ .Values.app.svc.identity }}
internalurls__ordering: http://{{ .Values.app.svc.ordering }}
internalurls__basket__hc: http://{{ .Values.app.svc.basket }}/hc
internalurls__catalog__hc: http://{{ .Values.app.svc.catalog }}/hc
internalurls__coupon__hc: http://{{ .Values.app.svc.coupon }}/hc
internalurls__identity__hc: http://{{ .Values.app.svc.identity }}/hc
internalurls__ordering__hc: http://{{ .Values.app.svc.ordering }}/hc
internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc


+ 2
- 0
deploy/k8s/helm/webstatus/templates/configmap.yaml View File

@ -43,3 +43,5 @@ data:
internalurls__payment__hc: http://{{ .Values.app.svc.payment }}/hc
name__signalrhub__hc: Ordering SignalR Hub HTTP Check
internalurls__signalrhub__hc: http://{{ .Values.app.svc.orderingsignalrhub }}/hc
name__coupon__hc: Coupon HTTP Check
internalurls__coupon__hc: http://{{ .Values.app.svc.coupon }}/hc

+ 6
- 5
deploy/k8s/helm/webstatus/values.yaml View File

@ -13,14 +13,11 @@ service:
ingress:
enabled: true
annotations: {
}
annotations: {}
tls: []
resources: {}
nodeSelector: {}
tolerations: []
@ -78,10 +75,14 @@ env:
key: name__orderingbackground__hc
- name: HealthChecks-UI__HealthChecks__10__Uri
key: internalurls__orderingbackground__hc
- name: HealthChecks-UI__HealthChecks__11__Name
key: name__coupon__hc
- name: HealthChecks-UI__HealthChecks__11__Uri
key: internalurls__coupon__hc
# values define environment variables with a fixed value (no configmap involved) (name is name of var, and value is its value)
values:
- name: ASPNETCORE_ENVIRONMENT
value: Development
- name: OrchestratorType
value: 'K8S'
value: "K8S"

+ 1
- 0
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 1
- 0
src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 1
- 0
src/Services/Basket/Basket.API/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 1
- 0
src/Services/Catalog/Catalog.API/Dockerfile View File

@ -25,6 +25,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 30
- 0
src/Services/Coupon/Coupon.API/Controllers/CouponController.cs View File

@ -0,0 +1,30 @@
namespace Coupon.API.Controllers;
[Authorize]
[ApiController]
[Route("api/v1/[controller]")]
public class CouponController : ControllerBase
{
private readonly ICouponRepository _couponRepository;
public CouponController(ICouponRepository couponRepository)
{
_couponRepository = couponRepository;
}
[HttpGet("{code}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Infrastructure.Models.Coupon>> GetCouponByCodeAsync(string code)
{
var coupon = await _couponRepository.FindCouponByCodeAsync(code);
if (coupon is null || coupon.Consumed)
{
return NotFound();
}
return coupon;
}
}

+ 63
- 0
src/Services/Coupon/Coupon.API/Coupon.API.csproj View File

@ -0,0 +1,63 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<UserSecretsId>1d5bc948-90f1-4906-a1f8-8edaa1ed9e2e</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..\..\..</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<Content Remove="C:\Users\Siarhei_Sialitski\.nuget\packages\mongodb.driver.core\2.13.3\contentFiles\any\netstandard2.1\Core\Compression\Snappy\lib\win\snappy32.dll" />
<Content Remove="C:\Users\Siarhei_Sialitski\.nuget\packages\mongodb.driver.core\2.13.3\contentFiles\any\netstandard2.1\Core\Compression\Snappy\lib\win\snappy64.dll" />
<Content Remove="C:\Users\Siarhei_Sialitski\.nuget\packages\mongodb.driver.core\2.13.3\contentFiles\any\netstandard2.1\Core\Compression\Zstandard\lib\win\libzstd.dll" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="6.0.3" />
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="6.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="6.0.2" />
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="6.0.2" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="6.0.4" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.2.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.20.0" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="2.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.23" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.15.0" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.13.3" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.Http" Version="7.2.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.16.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
</ItemGroup>
<ItemGroup>
<Content Remove="C:\Users\Siarhei_Sialitski\.nuget\packages\mongodb.driver.core\2.13.3\build\../runtimes/win/native/snappy32.dll" />
</ItemGroup>
<ItemGroup>
<Content Remove="C:\Users\Siarhei_Sialitski\.nuget\packages\mongodb.driver.core\2.13.3\build\../runtimes/win/native/snappy64.dll" />
</ItemGroup>
<ItemGroup>
<Content Remove="C:\Users\Siarhei_Sialitski\.nuget\packages\mongodb.driver.core\2.13.3\build\../runtimes/win/native/snappy64.dll" />
</ItemGroup>
<ItemGroup>
<Content Remove="C:\Users\Siarhei_Sialitski\.nuget\packages\mongodb.driver.core\2.13.3\build\../runtimes/win/native/libzstd.dll" />
</ItemGroup>
</Project>

+ 15
- 0
src/Services/Coupon/Coupon.API/CouponSettings.cs View File

@ -0,0 +1,15 @@
namespace Coupon.API
{
public class CouponSettings
{
public string ConnectionString { get; set; }
public string CouponMongoDatabase { get; set; }
public string EventBusConnection { get; set; }
public bool UseCustomizationData { get; set; }
public bool AzureStorageEnabled { get; set; }
}
}

+ 62
- 0
src/Services/Coupon/Coupon.API/Dockerfile View File

@ -0,0 +1,62 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
# It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles
# to take advantage of Docker's build cache, to speed up local container builds
COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWebApps.sln"
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"
COPY "BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj" "BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj"
COPY "BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj" "BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj"
COPY "BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj" "BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj"
COPY "Services/Basket/Basket.API/Basket.API.csproj" "Services/Basket/Basket.API/Basket.API.csproj"
COPY "Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj" "Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj"
COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj"
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"
COPY "Services/Ordering/Ordering.Domain/Ordering.Domain.csproj" "Services/Ordering/Ordering.Domain/Ordering.Domain.csproj"
COPY "Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj" "Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj"
COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" "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/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"
COPY "Web/WebMVC/WebMVC.csproj" "Web/WebMVC/WebMVC.csproj"
COPY "Web/WebSPA/WebSPA.csproj" "Web/WebSPA/WebSPA.csproj"
COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj"
COPY "docker-compose.dcproj" "docker-compose.dcproj"
COPY "NuGet.config" "NuGet.config"
RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln"
COPY . .
WORKDIR "/src/Services/Coupon/Coupon.API"
FROM build AS publish
RUN dotnet publish "Coupon.API.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Coupon.API.dll"]

+ 49
- 0
src/Services/Coupon/Coupon.API/Extensions/IHostBuilderExtensions.cs View File

@ -0,0 +1,49 @@
using System;
using Microsoft.Data.SqlClient;
using Coupon.API.IntegrationEvents.EventHandlers;
using Coupon.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Polly;
namespace Coupon.API.Extensions
{
public static class IHostBuilderExtensions
{
public static IHost SeedDatabaseStrategy<TContext>(this IHost host, Action<TContext> seeder)
{
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetService<TContext>();
var policy = Policy.Handle<SqlException>()
.WaitAndRetry(new TimeSpan[]
{
TimeSpan.FromSeconds(3),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(8),
});
policy.Execute(() =>
{
seeder.Invoke(context);
});
}
return host;
}
public static IHost SubscribersIntegrationEvents(this IHost host)
{
using (var scope = host.Services.CreateScope())
{
var eventBus = scope.ServiceProvider.GetRequiredService<IEventBus>();
eventBus.Subscribe<OrderStatusChangedToAwaitingCouponValidationIntegrationEvent, OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler>();
}
return host;
}
}
}

+ 204
- 0
src/Services/Coupon/Coupon.API/Extensions/IServiceCollectionExtensions.cs View File

@ -0,0 +1,204 @@
namespace Coupon.API.Extensions;
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddCouponRegister(this IServiceCollection services, IConfiguration configuration)
{
services.AddTransient<ICouponRepository, CouponRepository>()
.AddTransient<IServiceBusPersisterConnection, DefaultServiceBusPersisterConnection>(service =>
{
var settings = service.GetRequiredService<IOptions<CouponSettings>>().Value;
var serviceBusConnection = settings.EventBusConnection;
return new DefaultServiceBusPersisterConnection(serviceBusConnection);
})
.AddTransient<IRabbitMQPersistentConnection, DefaultRabbitMQPersistentConnection>(service =>
{
var factory = new ConnectionFactory()
{
HostName = configuration["EventBusConnection"],
DispatchConsumersAsync = true
};
var retryCount = 5;
if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(configuration["EventBusRetryCount"]);
}
return new DefaultRabbitMQPersistentConnection(factory, service.GetService<ILogger<DefaultRabbitMQPersistentConnection>>(), retryCount);
})
.AddTransient<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>()
.AddTransient<CouponContext>();
return services;
}
public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
{
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "eShopOnContainers - Coupon HTTP API",
Version = "v1",
Description = "The Coupon Service HTTP API"
});
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>()
{
{ "coupon", "Coupon API" }
}
}
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
return services;
}
public static IServiceCollection AddCustomSettings(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<CouponSettings>(configuration);
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Instance = context.HttpContext.Request.Path,
Status = StatusCodes.Status400BadRequest,
Detail = "Please refer to the errors property for additional details."
};
return new BadRequestObjectResult(problemDetails)
{
ContentTypes = { "application/problem+json", "application/problem+xml" }
};
};
});
return services;
}
public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
{
var subscriptionClientName = configuration["SubscriptionClientName"];
if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
{
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
return new EventBusServiceBus(serviceBusPersisterConnection, logger, eventBusSubcriptionsManager, iLifetimeScope, subscriptionClientName);
});
}
else
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>
{
var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
var retryCount = 5;
if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(configuration["EventBusRetryCount"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}
return services;
}
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration)
{
var accountName = configuration.GetValue<string>("AzureStorageAccountName");
var accountKey = configuration.GetValue<string>("AzureStorageAccountKey");
var hcBuilder = services.AddHealthChecks();
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy())
.AddMongoDb(
configuration["ConnectionString"],
name: "CouponCollection-check",
tags: new string[] { "couponcollection" });
if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
hcBuilder.AddAzureServiceBusTopic(
configuration["EventBusConnection"],
topicName: "eshop_event_bus",
name: "coupon-servicebus-check",
tags: new string[] { "servicebus" });
}
else
{
hcBuilder.AddRabbitMQ(
$"amqp://{configuration["EventBusConnection"]}",
name: "coupon-rabbitmqbus-check",
tags: new string[] { "rabbitmqbus" });
}
return services;
}
public static IServiceCollection AddCustomPolicies(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
return services;
}
public static IServiceCollection AddAppInsights(this IServiceCollection services, IConfiguration configuration)
{
services.AddApplicationInsightsTelemetry(configuration);
services.AddApplicationInsightsKubernetesEnricher();
return services;
}
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
{
return services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = configuration["IdentityUrl"];
options.RequireHttpsMetadata = false;
options.Audience = "coupon";
}).Services;
}
public static IServiceCollection AddCustomAuthorization(this IServiceCollection services) => services.AddAuthorization();
}

+ 35
- 0
src/Services/Coupon/Coupon.API/Filters/AuthorizeCheckOperationFilter.cs View File

@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Coupon.API.Filters
{
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
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 [] { "CouponApi" }
}
};
}
}
}

+ 16
- 0
src/Services/Coupon/Coupon.API/Filters/ValidateModelAttribute.cs View File

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Coupon.API.Filters
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
}

+ 33
- 0
src/Services/Coupon/Coupon.API/GlobalUsings.cs View File

@ -0,0 +1,33 @@
global using Coupon.API.Filters;
global using Coupon.API.Infrastructure.Repositories;
global using Coupon.API.Infrastructure.Models;
global using Autofac.Extensions.DependencyInjection;
global using Autofac;
global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Mvc.Filters;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.Extensions.Logging;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Options;
global using Polly;
global using Serilog.Context;
global using Serilog;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Threading.Tasks;
global using System;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus;
global using Microsoft.Extensions.Diagnostics.HealthChecks;
global using Microsoft.OpenApi.Models;
global using RabbitMQ.Client;
global using Microsoft.AspNetCore.Authentication.JwtBearer;

+ 52
- 0
src/Services/Coupon/Coupon.API/Infrastructure/CouponSeed.cs View File

@ -0,0 +1,52 @@
namespace Coupon.API.Infrastructure
{
using System.Collections.Generic;
using System.Threading.Tasks;
using Coupon.API.Infrastructure.Models;
using Coupon.API.Infrastructure.Repositories;
public class CouponSeed
{
public async Task SeedAsync(CouponContext context)
{
if (context.Coupons.EstimatedDocumentCount() == 0)
{
var coupons = new List<Coupon>
{
new Coupon
{
Code = "DISC-5",
Discount = 5
},
new Coupon
{
Code = "DISC-10",
Discount = 10
},
new Coupon
{
Code = "DISC-15",
Discount = 15
},
new Coupon
{
Code = "DISC-20",
Discount = 20
},
new Coupon
{
Code = "DISC-25",
Discount = 25
},
new Coupon
{
Code = "DISC-30",
Discount = 30
}
};
await context.Coupons.InsertManyAsync(coupons);
}
}
}
}

+ 20
- 0
src/Services/Coupon/Coupon.API/Infrastructure/Models/Coupon.cs View File

@ -0,0 +1,20 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Coupon.API.Infrastructure.Models
{
public class Coupon
{
[BsonIgnoreIfDefault]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public int Discount { get; set; }
public string Code { get; set; }
public bool Consumed { get; set; }
public int OrderId { get; set; }
}
}

+ 25
- 0
src/Services/Coupon/Coupon.API/Infrastructure/Repositories/CouponContext.cs View File

@ -0,0 +1,25 @@
namespace Coupon.API.Infrastructure.Repositories
{
using Coupon.API.Infrastructure.Models;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
public class CouponContext
{
private readonly IMongoDatabase _database = null;
public CouponContext(IOptions<CouponSettings> settings)
{
var client = new MongoClient(settings.Value.ConnectionString);
if (client is null)
{
throw new MongoConfigurationException("Cannot connect to the database. The connection string is not valid or the database is not accessible");
}
_database = client.GetDatabase(settings.Value.CouponMongoDatabase);
}
public IMongoCollection<Coupon> Coupons => _database.GetCollection<Coupon>("CouponCollection");
}
}

+ 42
- 0
src/Services/Coupon/Coupon.API/Infrastructure/Repositories/CouponRepository.cs View File

@ -0,0 +1,42 @@
namespace Coupon.API.Infrastructure.Repositories
{
using System.Threading.Tasks;
using Coupon.API.Infrastructure.Models;
using MongoDB.Driver;
public class CouponRepository : ICouponRepository
{
private readonly CouponContext _couponContext;
public CouponRepository(CouponContext couponContext)
{
_couponContext = couponContext;
}
public async Task UpdateCouponConsumedByCodeAsync(string code, int orderId)
{
var filter = Builders<Coupon>.Filter.Eq("Code", code);
var update = Builders<Coupon>.Update
.Set(coupon => coupon.Consumed, true)
.Set(coupon => coupon.OrderId, orderId);
await _couponContext.Coupons.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = false });
}
public async Task UpdateCouponReleasedByOrderIdAsync(int orderId)
{
var filter = Builders<Coupon>.Filter.Eq("OrderId", orderId);
var update = Builders<Coupon>.Update
.Set(coupon => coupon.Consumed, false)
.Set(coupon => coupon.OrderId, 0);
await _couponContext.Coupons.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = false });
}
public async Task<Coupon> FindCouponByCodeAsync(string code)
{
var filter = Builders<Coupon>.Filter.Eq("Code", code);
return await _couponContext.Coupons.Find(filter).FirstOrDefaultAsync();
}
}
}

+ 14
- 0
src/Services/Coupon/Coupon.API/Infrastructure/Repositories/ICouponRepository.cs View File

@ -0,0 +1,14 @@
namespace Coupon.API.Infrastructure.Repositories
{
using System.Threading.Tasks;
using Coupon.API.Infrastructure.Models;
public interface ICouponRepository
{
Task<Coupon> FindCouponByCodeAsync(string code);
Task UpdateCouponConsumedByCodeAsync(string code, int orderId);
Task UpdateCouponReleasedByOrderIdAsync(int orderId);
}
}

+ 56
- 0
src/Services/Coupon/Coupon.API/IntegrationEvents/EventHandlers/OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler.cs View File

@ -0,0 +1,56 @@
using System.Threading.Tasks;
using Coupon.API.Infrastructure.Repositories;
using Coupon.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Serilog;
using Serilog.Context;
namespace Coupon.API.IntegrationEvents.EventHandlers
{
public class OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToAwaitingCouponValidationIntegrationEvent>
{
private readonly ICouponRepository _couponRepository;
private readonly IEventBus _eventBus;
public OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler(ICouponRepository couponRepository, IEventBus eventBus)
{
_couponRepository = couponRepository;
_eventBus = eventBus;
}
public async Task Handle(OrderStatusChangedToAwaitingCouponValidationIntegrationEvent @event)
{
await Task.Delay(3000);
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-Coupon.API"))
{
Log.Information("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, "Coupon.API", @event);
var couponIntegrationEvent = await ProcessIntegrationEventAsync(@event);
Log.Information("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", couponIntegrationEvent.Id, "Coupon.API", couponIntegrationEvent);
_eventBus.Publish(couponIntegrationEvent);
}
}
private async Task<IntegrationEvent> ProcessIntegrationEventAsync(OrderStatusChangedToAwaitingCouponValidationIntegrationEvent integrationEvent)
{
var coupon = await _couponRepository.FindCouponByCodeAsync(integrationEvent.Code);
Log.Information("----- Coupon \"{CouponCode}\": {@Coupon}", integrationEvent.Code, coupon);
if (coupon == null || coupon.Consumed)
{
return new OrderCouponRejectedIntegrationEvent(integrationEvent.OrderId, coupon.Code);
}
Log.Information("Consumed coupon: {DiscountCode}", integrationEvent.Code);
await _couponRepository.UpdateCouponConsumedByCodeAsync(integrationEvent.Code, integrationEvent.OrderId);
return new OrderCouponConfirmedIntegrationEvent(integrationEvent.OrderId, coupon.Discount);
}
}
}

+ 22
- 0
src/Services/Coupon/Coupon.API/IntegrationEvents/EventHandlers/OrderStatusChangedToCancelledIntegrationEventHandler.cs View File

@ -0,0 +1,22 @@
using System.Threading.Tasks;
using Coupon.API.Infrastructure.Repositories;
using Coupon.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
namespace Coupon.API.IntegrationEvents.EventHandlers
{
public class OrderStatusChangedToCancelledIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToCancelledIntegrationEvent>
{
private readonly ICouponRepository _couponRepository;
public OrderStatusChangedToCancelledIntegrationEventHandler(ICouponRepository couponRepository)
{
_couponRepository = couponRepository;
}
public async Task Handle(OrderStatusChangedToCancelledIntegrationEvent @event)
{
await _couponRepository.UpdateCouponReleasedByOrderIdAsync(@event.OrderId);
}
}
}

+ 14
- 0
src/Services/Coupon/Coupon.API/IntegrationEvents/Events/OrderCouponConfirmedIntegrationEvent.cs View File

@ -0,0 +1,14 @@
namespace Coupon.API.IntegrationEvents.Events;
public record OrderCouponConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public int Discount { get; }
public OrderCouponConfirmedIntegrationEvent(int orderId, int discount)
{
OrderId = orderId;
Discount = discount;
}
}

+ 14
- 0
src/Services/Coupon/Coupon.API/IntegrationEvents/Events/OrderCouponRejectedIntegrationEvent.cs View File

@ -0,0 +1,14 @@
namespace Coupon.API.IntegrationEvents.Events;
public record OrderCouponRejectedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public string Code { get; }
public OrderCouponRejectedIntegrationEvent(int orderId, string code)
{
OrderId = orderId;
Code = code;
}
}

+ 18
- 0
src/Services/Coupon/Coupon.API/IntegrationEvents/Events/OrderStatusChangedToAwaitingCouponValidationIntegrationEvent.cs View File

@ -0,0 +1,18 @@
using Newtonsoft.Json;
namespace Coupon.API.IntegrationEvents.Events;
public record OrderStatusChangedToAwaitingCouponValidationIntegrationEvent : IntegrationEvent
{
[JsonProperty]
public int OrderId { get; private set; }
[JsonProperty]
public string OrderStatus { get; private set; }
[JsonProperty]
public string BuyerName { get; private set; }
[JsonProperty]
public string Code { get; private set; }
}

+ 18
- 0
src/Services/Coupon/Coupon.API/IntegrationEvents/Events/OrderStatusChangedToCancelledIntegrationEvent.cs View File

@ -0,0 +1,18 @@
using Newtonsoft.Json;
namespace Coupon.API.IntegrationEvents.Events;
public record OrderStatusChangedToCancelledIntegrationEvent : IntegrationEvent
{
[JsonProperty]
public int OrderId { get; private set; }
[JsonProperty]
public string OrderStatus { get; private set; }
[JsonProperty]
public string BuyerName { get; private set; }
[JsonProperty]
public string DiscountCode { get; private set; }
}

+ 53
- 0
src/Services/Coupon/Coupon.API/Program.cs View File

@ -0,0 +1,53 @@
using System.IO;
using Autofac.Extensions.DependencyInjection;
using Coupon.API.Extensions;
using Coupon.API.Infrastructure;
using Coupon.API.Infrastructure.Repositories;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
namespace Coupon.API
{
public class Program
{
public static void Main(string[] args) =>
CreateHostBuilder(args)
.Build()
.SeedDatabaseStrategy<CouponContext>(context => new CouponSeed().SeedAsync(context).Wait())
.SubscribersIntegrationEvents()
.Run();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureAppConfiguration((host, builder) =>
{
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{host.HostingEnvironment.EnvironmentName}.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
var config = builder.Build();
if (config.GetValue("UseVault", false))
{
builder.AddAzureKeyVault($"https://{config["Vault:Name"]}.vault.azure.net/", config["Vault:ClientId"], config["Vault:ClientSecret"]);
}
})
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>())
.UseSerilog((host, builder) =>
{
builder.MinimumLevel.Verbose()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.WithProperty("ApplicationContext", host.HostingEnvironment.ApplicationName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(host.Configuration["Serilog:SeqServerUrl"]) ? "http://seq" : host.Configuration["Serilog:SeqServerUrl"])
.WriteTo.Http(string.IsNullOrWhiteSpace(host.Configuration["Serilog:LogstashUrl"]) ? "http://logstash:8080" : host.Configuration["Serilog:LogstashUrl"])
.ReadFrom.Configuration(host.Configuration);
});
}
}

+ 12
- 0
src/Services/Coupon/Coupon.API/Properties/launchSettings.json View File

@ -0,0 +1,12 @@
{
"profiles": {
"Coupon.API": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:49935;http://localhost:49936"
}
}
}

+ 94
- 0
src/Services/Coupon/Coupon.API/Startup.cs View File

@ -0,0 +1,94 @@
using Coupon.API.Extensions;
using Coupon.API.Filters;
using Coupon.API.IntegrationEvents.EventHandlers;
using Coupon.API.IntegrationEvents.Events;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
namespace Coupon.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options => options.Filters.Add<ValidateModelAttribute>());
services.AddCustomSettings(Configuration)
.AddCouponRegister(Configuration)
.AddCustomPolicies()
.AddAppInsights(Configuration)
.AddEventBus(Configuration)
.AddCustomAuthentication(Configuration)
.AddCustomAuthorization()
.AddSwagger(Configuration)
.AddCustomHealthCheck(Configuration);
services.AddTransient<IIntegrationEventHandler<OrderStatusChangedToAwaitingCouponValidationIntegrationEvent>, OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler>();
services.AddTransient<IIntegrationEventHandler<OrderStatusChangedToCancelledIntegrationEvent>, OrderStatusChangedToCancelledIntegrationEventHandler>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
app.UsePathBase(pathBase);
}
app.UseSwagger()
.UseSwaggerUI(options =>
{
options.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Coupon.API V1");
options.OAuthClientId("couponswaggerui");
options.OAuthAppName("eShop-Learn.Coupon.API Swagger UI");
})
.UseCors("CorsPolicy")
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHealthChecks("/hc", new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
{
Predicate = r => r.Name.Contains("self")
});
});
ConfigureEventBus(app);
}
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<OrderStatusChangedToAwaitingCouponValidationIntegrationEvent, IIntegrationEventHandler<OrderStatusChangedToAwaitingCouponValidationIntegrationEvent>>();
eventBus.Subscribe<OrderStatusChangedToCancelledIntegrationEvent, IIntegrationEventHandler<OrderStatusChangedToCancelledIntegrationEvent>>();
}
}
}

+ 18
- 0
src/Services/Coupon/Coupon.API/appsettings.Development.json View File

@ -0,0 +1,18 @@
{
"ConnectionString": "mongodb://localhost:27017",
"CouponMongoDatabase": "CouponDb",
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Debug",
"System": "Warning"
}
}
},
"IdentityUrlExternal": "http://localhost:5105",
"IdentityUrl": "http://localhost:5105",
"AzureServiceBusEnabled": false,
"EventBusConnection": "localhost"
}

+ 28
- 0
src/Services/Coupon/Coupon.API/appsettings.json View File

@ -0,0 +1,28 @@
{
"ConnectionString": null,
"CouponMongoDatabase": "CouponDb",
"UseCustomizationData": false,
"Serilog": {
"SeqServerUrl": null,
"LogstashUrl": null,
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.eShopOnContainers": "Information",
"System": "Warning"
}
}
},
"SubscriptionClientName": "Coupon",
"ApplicationInsights": {
"InstrumentationKey": ""
},
"EventBusRetryCount": 5,
"UseVault": false,
"Vault": {
"Name": "eshop",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret"
}
}

+ 1
- 0
src/Services/Identity/Identity.API/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 2
- 2
src/Services/Identity/Identity.API/Properties/launchSettings.json View File

@ -3,8 +3,8 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:54010/",
"sslPort": 0
"applicationUrl": "http://localhost:49318/",
"sslPort": 44350
}
},
"profiles": {


+ 1
- 0
src/Services/Ordering/Ordering.API/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 1
- 0
src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 1
- 0
src/Services/Ordering/Ordering.SignalrHub/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 1
- 0
src/Services/Payment/Payment.API/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 1
- 0
src/Services/Webhooks/Webhooks.API/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 1
- 0
src/Web/WebMVC/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 22
- 0
src/Web/WebSPA/Client/src/modules/basket/basket.service.ts View File

@ -13,6 +13,7 @@ import { StorageService } from '../shared/services/storage.service';
import { Observable, Observer, Subject } from 'rxjs';
import { map, catchError, tap } from 'rxjs/operators';
import { ICoupon } from '../shared/models/coupon.model';
@Injectable()
export class BasketService {
@ -95,6 +96,27 @@ export class BasketService {
}));
}
checkValidationCoupon(code: string): Observable<ICoupon> {
let url = this.purchaseUrl + `/cp/api/v1/coupon/${code}`;
return this.service
.get(url)
.pipe<ICoupon>(map<Response, ICoupon>((response) =>
{
console.log(`Coupon: ${response.json()} (${response.ok})`);
var item = <ICoupon>response.json[0];
if (response.ok) {
item.message = "Valid coupon";
}
else {
item.message = "The coupon is not valid or has already been used";
}
return item;
}));
}
mapBasketInfoCheckout(order: IOrder): IBasketCheckout {
let basketCheckout = <IBasketCheckout>{};


+ 9
- 0
src/Web/WebSPA/Client/src/modules/orders/orders-detail/orders-detail.component.html View File

@ -34,6 +34,15 @@
</div>
</article>
<div class="d-flex align-items-center justify-content-end mt-4 mb-4 text-uppercase">
<div>Subtotal</div>
<div class="ml-3">${{order.subtotal | number:'.2-2'}}</div>
</div>
<div class="d-flex align-items-center justify-content-end mt-4 mb-4 text-uppercase">
<div>{{order.coupon}}</div>
<div class="ml-3">- ${{order.discount | number:'.2-2'}}</div>
</div>-->
<div class="d-flex align-items-center justify-content-end mt-4 mb-4 text-uppercase u-text-xl">
<div>Total</div>
<div class="ml-3">${{order.total | number:'.2-2'}}</div>


+ 32
- 0
src/Web/WebSPA/Client/src/modules/orders/orders-new/orders-new.component.html View File

@ -84,6 +84,38 @@
</div>
</article>
<div class="d-flex align-items-center justify-content-end mt-4 mb-4 text-uppercase">
<div>Subtotal</div>
<div class="ml-3">${{order.total | number:'.2-2'}}</div>
</div>
<div class="d-flex flex-nowrap justify-content-between align-items-center mb-3 mt-3">
<div>
<div *ngIf="!coupon">
<div class="u-text-uppercase">Have a discount code?</div>
<div class="d-flex flex-nowrap justify-content-between align-items-center mt-1">
<input #discountcode
class="esh-orders_new-coupon mr-2 form-control"
type="text"
placeholder="Coupon number"
(keydown)="keyDownValidationCoupon($event, discountcode.value)">
<button type="button"
(click)="checkValidationCoupon(discountcode.value)"
class="btn btn-secondary u-minwidth-unset">
Apply
</button>
</div>
<div class="mt-1" *ngIf="couponValidationMessage">{{couponValidationMessage}}</div>
</div>
</div>
<div class="d-flex align-items-center justify-content-end text-uppercase">
<div *ngIf="coupon?.code">{{coupon?.code}}</div>
<div>
<div class="text-right ml-3" *ngIf="coupon?.discount">-${{coupon?.discount | number:'.2-2'}}</div>
</div>
</div>
</div>
<div class="divider d-flex align-items-center justify-content-end mb-4 pt-4 text-uppercase u-text-xl">
<div>Total</div>
<div class="ml-3">${{ order.total | number:'.2-2'}}</div>


+ 28
- 1
src/Web/WebSPA/Client/src/modules/orders/orders-new/orders-new.component.ts View File

@ -6,7 +6,7 @@ import { OrdersService } from '../orders.service';
import { BasketService } from '../../basket/basket.service';
import { IOrder } from '../../shared/models/order.model';
import { BasketWrapperService } from '../../shared/services/basket.wrapper.service';
import { ICoupon } from '../../shared/models/coupon.model';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';
@ -19,7 +19,9 @@ export class OrdersNewComponent implements OnInit {
newOrderForm: FormGroup; // new order form
isOrderProcessing: boolean;
errorReceived: boolean;
coupon: ICoupon;
order: IOrder;
couponValidationMessage: string;
constructor(private orderService: OrdersService, private basketService: BasketService, fb: FormBuilder, private router: Router) {
// Obtain user profile information
@ -39,6 +41,23 @@ export class OrdersNewComponent implements OnInit {
ngOnInit() {
}
keyDownValidationCoupon(event: KeyboardEvent, discountCode: string) {
if(event.keyCode === 13) {
event.preventDefault();
this.checkValidationCoupon(discountCode);
}
}
checkValidationCoupon(discountCode: string) {
this.couponValidationMessage = null;
this.coupon = null;
this.orderService
.checkValidationCoupon(discountCode)
.subscribe(
coupon => this.coupon = coupon,
error => this.couponValidationMessage = 'The coupon is not valid or it\'s been redeemed already!' );
}
submitForm(value: any) {
this.order.street = this.newOrderForm.controls['street'].value;
this.order.city = this.newOrderForm.controls['city'].value;
@ -49,6 +68,14 @@ export class OrdersNewComponent implements OnInit {
this.order.cardholdername = this.newOrderForm.controls['cardholdername'].value;
this.order.cardexpiration = new Date(20 + this.newOrderForm.controls['expirationdate'].value.split('/')[1], this.newOrderForm.controls['expirationdate'].value.split('/')[0]);
this.order.cardsecuritynumber = this.newOrderForm.controls['securitycode'].value;
if (this.coupon) {
console.log(`Coupon: ${this.coupon.code} (${this.coupon.discount})`);
this.order.coupon = this.coupon.code;
this.order.discount = this.coupon.discount;
}
let basketCheckout = this.basketService.mapBasketInfoCheckout(this.order);
this.basketService.setBasketCheckout(basketCheckout)
.pipe(catchError((errMessage) => {


+ 9
- 0
src/Web/WebSPA/Client/src/modules/orders/orders.service.ts View File

@ -10,6 +10,7 @@ import { BasketWrapperService } from '../shared/services/basket.wrapper.service'
import { Observable } from 'rxjs';
import { tap, map } from 'rxjs/operators';
import { ICoupon } from '../shared/models/coupon.model';
@Injectable()
export class OrdersService {
@ -48,6 +49,14 @@ export class OrdersService {
}));
}
checkValidationCoupon(code: string): Observable<ICoupon> {
let url = this.ordersUrl + `/cp/api/v1/coupon/${code}`;
return this.service.get(url).pipe<ICoupon>(tap((response: any) => {
return response;
}));
}
mapOrderAndIdentityInfoNewOrder(): IOrder {
let order = <IOrder>{};
let basket = this.basketService.basket;


+ 5
- 0
src/Web/WebSPA/Client/src/modules/shared/models/coupon.model.ts View File

@ -0,0 +1,5 @@
export interface ICoupon {
discount: number;
code: string;
message: string
}

+ 3
- 0
src/Web/WebSPA/Client/src/modules/shared/models/order-detail.model.ts View File

@ -10,6 +10,9 @@ export interface IOrderDetail {
state: string;
zipcode: string;
country: number;
subtotal: number;
coupon: string;
discount: number;
total: number;
orderitems: IOrderItem[];
}

+ 3
- 0
src/Web/WebSPA/Client/src/modules/shared/models/order.model.ts View File

@ -14,6 +14,9 @@ export interface IOrder {
cardtypeid: number;
buyer: string;
ordernumber: string;
subtotal: number,
coupon: string;
discount: number;
total: number;
orderItems: IOrderItem[];
}

+ 1
- 0
src/Web/WebSPA/Dockerfile View File

@ -35,6 +35,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 1
- 0
src/Web/WebStatus/Dockerfile View File

@ -24,6 +24,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 1
- 0
src/Web/WebhookClient/Dockerfile View File

@ -25,6 +25,7 @@ COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
COPY "Services/Coupon/Coupon.API/Coupon.API.csproj" "Services/Coupon/Coupon.API/Coupon.API.csproj"
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"


+ 8
- 0
src/docker-compose.yml View File

@ -44,6 +44,14 @@ services:
- sqldata
- rabbitmq
coupon-api:
image: ${REGISTRY:-eshop}/coupon.api:${PLATFORM:-linux}-${TAG:-latest}
build:
context: .
dockerfile: Services/Coupon/Coupon.API/Dockerfile
depends_on:
- nosqldata
ordering-api:
image: ${REGISTRY:-eshop}/ordering.api:${PLATFORM:-linux}-${TAG:-latest}
build:


+ 56
- 2
src/eShopOnContainers-ServicesAndWebApps.sln View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29020.237
# Visual Studio Version 17
VisualStudioVersion = 17.2.32929.388
MinimumVisualStudioVersion = 10.0.40219.1
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{FEA0C318-FFED-4D39-8781-265718CA43DD}"
EndProject
@ -124,6 +124,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{373D8AA1
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Tests", "BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{95D735BE-2899-4495-BE3F-2600E93B4E3C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Coupon", "Coupon", "{71D3CD0A-5583-4F82-99D6-5035BF0EE2C0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Coupon.API", "Services\Coupon\Coupon.API\Coupon.API.csproj", "{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -1530,6 +1534,54 @@ Global
{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x64.Build.0 = Release|Any CPU
{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x86.ActiveCfg = Release|Any CPU
{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x86.Build.0 = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|ARM.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|iPhone.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|x64.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|x64.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|x86.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.AppStore|x86.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|ARM.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|ARM.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|iPhone.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|x64.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|x64.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|x86.ActiveCfg = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Debug|x86.Build.0 = Debug|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|Any CPU.Build.0 = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|ARM.ActiveCfg = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|ARM.Build.0 = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|iPhone.ActiveCfg = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|iPhone.Build.0 = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|x64.ActiveCfg = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|x64.Build.0 = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|x86.ActiveCfg = Release|Any CPU
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1588,6 +1640,8 @@ Global
{B62E859F-825E-4C8B-93EC-5966EACFD026} = {798BFC44-2CCD-45FA-B37A-5173B03C2B30}
{373D8AA1-36BE-49EC-89F0-6CB736666285} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{95D735BE-2899-4495-BE3F-2600E93B4E3C} = {373D8AA1-36BE-49EC-89F0-6CB736666285}
{71D3CD0A-5583-4F82-99D6-5035BF0EE2C0} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{53D3EE9A-EA1E-46A7-9B62-1D19E6FE2083} = {71D3CD0A-5583-4F82-99D6-5035BF0EE2C0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9}


Loading…
Cancel
Save