Merge branch 'features/webhooks' into dev
This commit is contained in:
commit
0eae3c3ea3
@ -14,7 +14,7 @@ services:
|
||||
image: mongo
|
||||
|
||||
identity-api-test:
|
||||
image: eshop/identity-api-test:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/identity-api-test:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Identity/Identity.API/Dockerfile
|
||||
@ -22,7 +22,7 @@ services:
|
||||
- sql-data-test
|
||||
|
||||
basket-api-test:
|
||||
image: eshop/basket-api-test:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/basket-api-test:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Basket/Basket.API/Dockerfile
|
||||
@ -35,7 +35,7 @@ services:
|
||||
- ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests
|
||||
|
||||
basket-api-unit-test:
|
||||
image: eshop/basket-api-test:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/basket-api-test:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Basket/Basket.API/Dockerfile
|
||||
@ -48,7 +48,7 @@ services:
|
||||
- ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests
|
||||
|
||||
catalog-api-test:
|
||||
image: eshop/catalog-api-test:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/catalog-api-test:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Catalog/Catalog.API/Dockerfile
|
||||
@ -60,7 +60,7 @@ services:
|
||||
- ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests
|
||||
|
||||
catalog-api-unit-test:
|
||||
image: eshop/catalog-api-test:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/catalog-api-test:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Catalog/Catalog.API/Dockerfile
|
||||
@ -72,7 +72,7 @@ services:
|
||||
- ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests
|
||||
|
||||
ordering-api-test:
|
||||
image: eshop/ordering-api-test:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/ordering-api-test:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Ordering/Ordering.API/Dockerfile
|
||||
@ -84,7 +84,7 @@ services:
|
||||
- ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests
|
||||
|
||||
ordering-api-unit-test:
|
||||
image: eshop/ordering-api-test:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/ordering-api-test:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Ordering/Ordering.API/Dockerfile
|
||||
@ -96,7 +96,7 @@ services:
|
||||
- ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests
|
||||
|
||||
marketing-api-test:
|
||||
image: eshop/marketing-api-test:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/marketing-api-test:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Marketing/Marketing.API/Dockerfile
|
||||
@ -110,7 +110,7 @@ services:
|
||||
- ${BUILD_ARTIFACTSTAGINGDIRECTORY:-./tests-results/}:/tests
|
||||
|
||||
payment-api-test:
|
||||
image: eshop/payment-api-test:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/payment-api-test:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Payment/Payment.API/Dockerfile
|
||||
@ -118,7 +118,7 @@ services:
|
||||
- rabbitmq-test
|
||||
|
||||
locations-api-test:
|
||||
image: eshop/locations-api-test:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/locations-api-test:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Location/Locations.API/Dockerfile
|
||||
|
@ -41,6 +41,8 @@ services:
|
||||
- OrderingApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102
|
||||
- MobileShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5120
|
||||
- WebShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5121
|
||||
- WebhooksApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5113
|
||||
- WebhooksWebClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5114
|
||||
- UseCustomizationData=True
|
||||
- ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY}
|
||||
- OrchestratorType=${ORCHESTRATOR_TYPE}
|
||||
@ -182,6 +184,19 @@ services:
|
||||
ports:
|
||||
- "5109:80" # Important: In a production environment your should remove the external port (5109) kept here for microservice debugging purposes.
|
||||
# The API Gateway redirects and access through the internal port (80).
|
||||
webhooks.api:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||
- ConnectionString=${ESHOP_AZURE_WEBHOOKS_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.WebhooksDb;User Id=sa;Password=Pass@word}
|
||||
- EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq}
|
||||
- EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME}
|
||||
- EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD}
|
||||
- IdentityUrl=http://identity.api
|
||||
- IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105
|
||||
ports:
|
||||
- "5113:80" # Important: In a production environment your should remove the external port (5109) kept here for microservice debugging purposes.
|
||||
# The API Gateway redirects and access through the internal port (80).
|
||||
|
||||
mobileshoppingapigw:
|
||||
environment:
|
||||
@ -380,3 +395,13 @@ services:
|
||||
ports:
|
||||
- "5100:80"
|
||||
|
||||
webhooks.client:
|
||||
environment:
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||
- Token=6168DB8D-DC58-4094-AF24-483278923590 # Webhooks are registered with this token (any value is valid) but the client won't check it
|
||||
- IdentityUrl=http://10.0.75.1:5105
|
||||
- CallBackUrl=http://localhost:5114
|
||||
- WebhooksUrl=http://webhooks.api
|
||||
- SelfUrl=http://webhooks.client/
|
||||
ports:
|
||||
- "5114:80"
|
@ -14,7 +14,7 @@ services:
|
||||
image: rabbitmq:3-management-alpine
|
||||
|
||||
identity.api:
|
||||
image: eshop/identity.api:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/identity.api:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Identity/Identity.API/Dockerfile
|
||||
@ -22,7 +22,7 @@ services:
|
||||
- sql.data
|
||||
|
||||
basket.api:
|
||||
image: eshop/basket.api:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/basket.api:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Basket/Basket.API/Dockerfile
|
||||
@ -32,7 +32,7 @@ services:
|
||||
- rabbitmq
|
||||
|
||||
catalog.api:
|
||||
image: eshop/catalog.api:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/catalog.api:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Catalog/Catalog.API/Dockerfile
|
||||
@ -41,7 +41,7 @@ services:
|
||||
- rabbitmq
|
||||
|
||||
ordering.api:
|
||||
image: eshop/ordering.api:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/ordering.api:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Ordering/Ordering.API/Dockerfile
|
||||
@ -50,7 +50,7 @@ services:
|
||||
- rabbitmq
|
||||
|
||||
ordering.backgroundtasks:
|
||||
image: eshop/ordering.backgroundtasks:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/ordering.backgroundtasks:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile
|
||||
@ -59,7 +59,7 @@ services:
|
||||
- rabbitmq
|
||||
|
||||
marketing.api:
|
||||
image: eshop/marketing.api:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/marketing.api:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Marketing/Marketing.API/Dockerfile
|
||||
@ -70,7 +70,7 @@ services:
|
||||
- rabbitmq
|
||||
|
||||
payment.api:
|
||||
image: eshop/payment.api:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/payment.api:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Payment/Payment.API/Dockerfile
|
||||
@ -78,7 +78,7 @@ services:
|
||||
- rabbitmq
|
||||
|
||||
locations.api:
|
||||
image: eshop/locations.api:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/locations.api:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Location/Locations.API/Dockerfile
|
||||
@ -86,8 +86,16 @@ services:
|
||||
- nosql.data
|
||||
- rabbitmq
|
||||
|
||||
webhooks.api:
|
||||
image: ${REGISTRY:-eshop}/webhooks.api:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Webhooks/Webhooks.API/Dockerfile
|
||||
depends_on:
|
||||
- sql.data
|
||||
|
||||
mobileshoppingapigw:
|
||||
image: eshop/ocelotapigw:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/ocelotapigw:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile
|
||||
@ -102,7 +110,7 @@ services:
|
||||
- basket.api
|
||||
|
||||
mobilemarketingapigw:
|
||||
image: eshop/ocelotapigw:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/ocelotapigw:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile
|
||||
@ -117,7 +125,7 @@ services:
|
||||
- basket.api
|
||||
|
||||
webshoppingapigw:
|
||||
image: eshop/ocelotapigw:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/ocelotapigw:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile
|
||||
@ -132,7 +140,7 @@ services:
|
||||
- basket.api
|
||||
|
||||
webmarketingapigw:
|
||||
image: eshop/ocelotapigw:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/ocelotapigw:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile
|
||||
@ -147,7 +155,7 @@ services:
|
||||
- basket.api
|
||||
|
||||
mobileshoppingagg:
|
||||
image: eshop/mobileshoppingagg:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/mobileshoppingagg:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile
|
||||
@ -162,7 +170,7 @@ services:
|
||||
- basket.api
|
||||
|
||||
webshoppingagg:
|
||||
image: eshop/webshoppingagg:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/webshoppingagg:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile
|
||||
@ -177,7 +185,7 @@ services:
|
||||
- basket.api
|
||||
|
||||
ordering.signalrhub:
|
||||
image: eshop/ordering.signalrhub:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/ordering.signalrhub:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/Ordering/Ordering.SignalrHub/Dockerfile
|
||||
@ -192,13 +200,13 @@ services:
|
||||
- basket.api
|
||||
|
||||
webstatus:
|
||||
image: eshop/webstatus:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/webstatus:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Web/WebStatus/Dockerfile
|
||||
|
||||
webspa:
|
||||
image: eshop/webspa:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/webspa:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Web/WebSPA/Dockerfile
|
||||
@ -208,7 +216,7 @@ services:
|
||||
# - webmarketingapigw
|
||||
|
||||
webmvc:
|
||||
image: eshop/webmvc:${TAG:-latest}
|
||||
image: ${REGISTRY:-eshop}/webmvc:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Web/WebMVC/Dockerfile
|
||||
@ -217,3 +225,10 @@ services:
|
||||
- webshoppingapigw
|
||||
- webmarketingapigw
|
||||
|
||||
webhooks.client:
|
||||
image: ${REGISTRY:-eshop}/webhooks.client:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Web/WebhookClient/Dockerfile
|
||||
depends_on:
|
||||
- webhooks.api
|
@ -140,6 +140,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Payment", "Payment", "{C61C
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.API", "src\Services\Payment\Payment.API\Payment.API.csproj", "{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webhooks", "Webhooks", "{E0AA11C4-2873-461D-8F82-53392530FB7A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Webhooks.API", "src\Services\Webhooks\Webhooks.API\Webhooks.API.csproj", "{84E2016E-0435-44C6-8020-3D288AA38B2C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebhookClient", "src\Web\WebhookClient\WebhookClient.csproj", "{766D7E92-6AF0-476C-ADD5-282BF4D8C576}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
||||
@ -1642,6 +1648,102 @@ Global
|
||||
{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84}.Release|x86.Build.0 = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C}.Release|x86.Build.0 = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x64.Build.0 = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -1703,6 +1805,9 @@ Global
|
||||
{120CABB3-0FEA-4B40-B4B5-2D3041798C80} = {0BD0DB92-2D98-44D9-9AC0-C59186D59B0B}
|
||||
{C61C5CFE-4876-4A46-A96E-5BBF596A984A} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
|
||||
{0AB40131-8AD7-436F-9C6B-EDA59CFA3A84} = {C61C5CFE-4876-4A46-A96E-5BBF596A984A}
|
||||
{E0AA11C4-2873-461D-8F82-53392530FB7A} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
|
||||
{84E2016E-0435-44C6-8020-3D288AA38B2C} = {E0AA11C4-2873-461D-8F82-53392530FB7A}
|
||||
{766D7E92-6AF0-476C-ADD5-282BF4D8C576} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9}
|
||||
|
@ -22,6 +22,8 @@ app: # app global settings
|
||||
payment: payment-api # ingress entry for payment api
|
||||
locations: locations-api # ingress entry for locations api
|
||||
marketing: marketing-api # ingress entry for marketing api
|
||||
webhooks: webhooks-api # ingress entry for webhooks api
|
||||
webhooksweb: webhooks-web # ingress entry for webhooks web demo client
|
||||
svc:
|
||||
basket: basket # service name for basket api
|
||||
catalog: catalog # service name for catalog api
|
||||
@ -41,3 +43,5 @@ app: # app global settings
|
||||
payment: payment # service name for payment api
|
||||
locations: locations # service name for locations api
|
||||
marketing: marketing # service name for marketing ap
|
||||
webhooks: webhooks # service name for webhooks api
|
||||
webhooksweb: webhooksweb # service name for webhooks web
|
||||
|
@ -61,7 +61,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", "apigwmm", "apigwms", "apigwwm", "apigwws", "basket-api","catalog-api", "identity-api", "locations-api", "marketing-api", "mobileshoppingagg","ordering-api","ordering-backgroundtasks","ordering-signalrhub", "payment-api", "webmvc", "webshoppingagg", "webspa", "webstatus")
|
||||
$charts = ("eshop-common", "apigwmm", "apigwms", "apigwwm", "apigwws", "basket-api","catalog-api", "identity-api", "locations-api", "marketing-api", "mobileshoppingagg","ordering-api","ordering-backgroundtasks","ordering-signalrhub", "payment-api", "webmvc", "webshoppingagg", "webspa", "webstatus", "webhooks-api", "webhooks-web")
|
||||
|
||||
if ($deployInfrastructure) {
|
||||
foreach ($infra in $infras) {
|
||||
|
@ -9,6 +9,8 @@
|
||||
{{- $mobileshoppingagg := include "url-of" (list .Values.app.ingress.entries.mobileshoppingagg .) -}}
|
||||
{{- $webhoppingagg := include "url-of" (list .Values.app.ingress.entries.webshoppingagg .) -}}
|
||||
{{- $xamarincallback := include "url-of" (list "xamarincallback" .) -}}
|
||||
{{- $webhooks_url := include "url-of" (list .Values.app.ingress.entries.webhooks .) -}}
|
||||
{{- $webhooksweb_url := include "url-of" (list .Values.app.ingress.entries.webhooksweb .) -}}
|
||||
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
@ -32,4 +34,7 @@ data:
|
||||
mobileshoppingagg_e: http://{{ $mobileshoppingagg }}
|
||||
webshoppingagg_e: http://{{ $webhoppingagg }}
|
||||
xamarin_callback_e: http://{{ $xamarincallback }}
|
||||
webhooksapi_e: http://{{ $webhooks_url }}
|
||||
webhooksweb_e: http://{{ $webhooksweb_url }}
|
||||
|
||||
|
||||
|
@ -54,6 +54,10 @@ env:
|
||||
key: webshoppingagg_e
|
||||
- name: XamarinCallback
|
||||
key: xamarin_callback_e
|
||||
- name: WebhooksApiClient
|
||||
key: webhooksapi_e
|
||||
- name: WebhooksWebClient
|
||||
key: webhooksweb_e
|
||||
values:
|
||||
- name: ASPNETCORE_ENVIRONMENT
|
||||
value: Development
|
||||
|
@ -16,6 +16,8 @@ inf:
|
||||
db: IdentityDb # Ordering API SQL db name
|
||||
marketing:
|
||||
db: MarketingDb # Marketing API SQL db name
|
||||
webhooks:
|
||||
db: WebhooksDb # Webhooks DB
|
||||
mongo:
|
||||
# host: my-nosql-data # Uncomment to use specify custom mongo host. By default nosql-data is used
|
||||
locations:
|
||||
|
21
k8s/helm/webhooks-api/.helmignore
Normal file
21
k8s/helm/webhooks-api/.helmignore
Normal 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
k8s/helm/webhooks-api/Chart.yaml
Normal file
5
k8s/helm/webhooks-api/Chart.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
apiVersion: v1
|
||||
appVersion: "1.0"
|
||||
description: A Helm chart for Kubernetes
|
||||
name: webhooks-api
|
||||
version: 0.1.0
|
8
k8s/helm/webhooks-api/templates/NOTES.txt
Normal file
8
k8s/helm/webhooks-api/templates/NOTES.txt
Normal file
@ -0,0 +1,8 @@
|
||||
eShop Ordering 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 "webhooks-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
k8s/helm/webhooks-api/templates/_helpers.tpl
Normal file
32
k8s/helm/webhooks-api/templates/_helpers.tpl
Normal file
@ -0,0 +1,32 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "webhooks-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 "webhooks-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 "webhooks-api.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
51
k8s/helm/webhooks-api/templates/_names.tpl
Normal file
51
k8s/helm/webhooks-api/templates/_names.tpl
Normal file
@ -0,0 +1,51 @@
|
||||
{{- 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 -}}
|
20
k8s/helm/webhooks-api/templates/configmap.yaml
Normal file
20
k8s/helm/webhooks-api/templates/configmap.yaml
Normal file
@ -0,0 +1,20 @@
|
||||
{{- $name := include "webhooks-api.fullname" . -}}
|
||||
{{- $sqlsrv := include "sql-name" . -}}
|
||||
{{- $identity := include "url-of" (list .Values.app.ingress.entries.identity .) -}}
|
||||
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: "cfg-{{ $name }}"
|
||||
labels:
|
||||
app: {{ template "webhooks-api.name" . }}
|
||||
chart: {{ template "webhooks-api.chart" .}}
|
||||
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 }};
|
||||
urls__IdentityUrl: http://{{ $identity }}
|
||||
urls__IdentityUrlExternal: http://{{ $identity }}
|
||||
all__EventBusConnection: {{ .Values.inf.eventbus.constr }}
|
||||
all__InstrumentationKey: {{ .Values.inf.appinsights.key }}
|
||||
all__UseAzureServiceBus: "{{ .Values.inf.eventbus.useAzure }}"
|
71
k8s/helm/webhooks-api/templates/deployment.yaml
Normal file
71
k8s/helm/webhooks-api/templates/deployment.yaml
Normal file
@ -0,0 +1,71 @@
|
||||
{{- $name := include "webhooks-api.fullname" . -}}
|
||||
{{- $cfgname := printf "%s-%s" "cfg" $name -}}
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ template "webhooks-api.fullname" . }}
|
||||
labels:
|
||||
ufo: {{ $cfgname}}
|
||||
app: {{ template "webhooks-api.name" . }}
|
||||
chart: {{ template "webhooks-api.chart" . }}
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ template "webhooks-api.name" . }}
|
||||
release: {{ .Release.Name }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ template "webhooks-api.name" . }}
|
||||
release: {{ .Release.Name }}
|
||||
spec:
|
||||
{{ if .Values.inf.registry -}}
|
||||
imagePullSecrets:
|
||||
- name: {{ .Values.inf.registry.secretName }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
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
|
||||
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 }}
|
||||
|
33
k8s/helm/webhooks-api/templates/ingress.yaml
Normal file
33
k8s/helm/webhooks-api/templates/ingress.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $ingressPath := include "pathBase" . -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ template "webhooks-api.fullname" . }}
|
||||
labels:
|
||||
app: {{ template "webhooks-api.name" . }}
|
||||
chart: {{ template "webhooks-api.chart" . }}
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{ toYaml . | indent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
- {{ .Values.inf.k8s.dns }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ .Values.inf.k8s.dns }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ $ingressPath }}
|
||||
backend:
|
||||
serviceName: {{ .Values.app.svc.webhooks }}
|
||||
servicePort: http
|
||||
{{- end }}
|
19
k8s/helm/webhooks-api/templates/service.yaml
Normal file
19
k8s/helm/webhooks-api/templates/service.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Values.app.svc.webhooks }}
|
||||
labels:
|
||||
app: {{ template "webhooks-api.name" . }}
|
||||
chart: {{ template "webhooks-api.chart" . }}
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: {{ template "webhooks-api.name" . }}
|
||||
release: {{ .Release.Name }}
|
53
k8s/helm/webhooks-api/values.yaml
Normal file
53
k8s/helm/webhooks-api/values.yaml
Normal file
@ -0,0 +1,53 @@
|
||||
replicaCount: 1
|
||||
clusterName: eshop-aks
|
||||
pathBase: /webhooks-api
|
||||
|
||||
image:
|
||||
repository: eshop/webhooks.api
|
||||
tag: latest
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations: {}
|
||||
hosts:
|
||||
- chart-example.local
|
||||
tls: []
|
||||
|
||||
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: webhooks__ConnectionString
|
||||
- name: ApplicationInsights__InstrumentationKey
|
||||
key: all__InstrumentationKey
|
||||
- name: EventBusConnection
|
||||
key: all__EventBusConnection
|
||||
- name: AzureServiceBusEnabled
|
||||
key: all__UseAzureServiceBus
|
||||
- name: IdentityUrl
|
||||
key: urls__IdentityUrl
|
||||
- name: IdentityUrlExternal
|
||||
key: urls__IdentityUrlExternal
|
||||
# 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'
|
||||
|
21
k8s/helm/webhooks-web/.helmignore
Normal file
21
k8s/helm/webhooks-web/.helmignore
Normal 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
k8s/helm/webhooks-web/Chart.yaml
Normal file
5
k8s/helm/webhooks-web/Chart.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
apiVersion: v1
|
||||
appVersion: "1.0"
|
||||
description: A Helm chart for Kubernetes
|
||||
name: webhooks-web
|
||||
version: 0.1.0
|
8
k8s/helm/webhooks-web/templates/NOTES.txt
Normal file
8
k8s/helm/webhooks-web/templates/NOTES.txt
Normal file
@ -0,0 +1,8 @@
|
||||
eShop Ordering 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 "webhooks-web.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
k8s/helm/webhooks-web/templates/_helpers.tpl
Normal file
32
k8s/helm/webhooks-web/templates/_helpers.tpl
Normal file
@ -0,0 +1,32 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "webhooks-web.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 "webhooks-web.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 "webhooks-web.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
51
k8s/helm/webhooks-web/templates/_names.tpl
Normal file
51
k8s/helm/webhooks-web/templates/_names.tpl
Normal file
@ -0,0 +1,51 @@
|
||||
{{- 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 -}}
|
19
k8s/helm/webhooks-web/templates/configmap.yaml
Normal file
19
k8s/helm/webhooks-web/templates/configmap.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
{{- $name := include "webhooks-web.fullname" . -}}
|
||||
{{- $identity := include "url-of" (list .Values.app.ingress.entries.identity .) -}}
|
||||
{{- $webhooksweb := include "url-of" (list .Values.app.ingress.entries.webhooksweb .) -}}
|
||||
{{- $webhooks := include "url-of" (list .Values.app.ingress.entries.webhooks .) -}}
|
||||
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: "cfg-{{ $name }}"
|
||||
labels:
|
||||
app: {{ template "webhooks-web.name" . }}
|
||||
chart: {{ template "webhooks-web.chart" .}}
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
data:
|
||||
urls__webhooks: http://{{ $webhooks }}
|
||||
identity_e: http://{{ $identity }}
|
||||
webhooksweb_e: http://{{ $webhooksweb }}
|
||||
urls_webhooksweb: http://{{ .Values.app.svc.webhooksweb }}
|
71
k8s/helm/webhooks-web/templates/deployment.yaml
Normal file
71
k8s/helm/webhooks-web/templates/deployment.yaml
Normal file
@ -0,0 +1,71 @@
|
||||
{{- $name := include "webhooks-web.fullname" . -}}
|
||||
{{- $cfgname := printf "%s-%s" "cfg" $name -}}
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ template "webhooks-web.fullname" . }}
|
||||
labels:
|
||||
ufo: {{ $cfgname}}
|
||||
app: {{ template "webhooks-web.name" . }}
|
||||
chart: {{ template "webhooks-web.chart" . }}
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ template "webhooks-web.name" . }}
|
||||
release: {{ .Release.Name }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ template "webhooks-web.name" . }}
|
||||
release: {{ .Release.Name }}
|
||||
spec:
|
||||
{{ if .Values.inf.registry -}}
|
||||
imagePullSecrets:
|
||||
- name: {{ .Values.inf.registry.secretName }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
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
|
||||
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 }}
|
||||
|
33
k8s/helm/webhooks-web/templates/ingress.yaml
Normal file
33
k8s/helm/webhooks-web/templates/ingress.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $ingressPath := include "pathBase" . -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ template "webhooks-web.fullname" . }}
|
||||
labels:
|
||||
app: {{ template "webhooks-web.name" . }}
|
||||
chart: {{ template "webhooks-web.chart" . }}
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{ toYaml . | indent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
- {{ .Values.inf.k8s.dns }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ .Values.inf.k8s.dns }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ $ingressPath }}
|
||||
backend:
|
||||
serviceName: {{ .Values.app.svc.webhooksweb }}
|
||||
servicePort: http
|
||||
{{- end }}
|
19
k8s/helm/webhooks-web/templates/service.yaml
Normal file
19
k8s/helm/webhooks-web/templates/service.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Values.app.svc.webhooksweb }}
|
||||
labels:
|
||||
app: {{ template "webhooks-web.name" . }}
|
||||
chart: {{ template "webhooks-web.chart" . }}
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: {{ template "webhooks-web.name" . }}
|
||||
release: {{ .Release.Name }}
|
52
k8s/helm/webhooks-web/values.yaml
Normal file
52
k8s/helm/webhooks-web/values.yaml
Normal file
@ -0,0 +1,52 @@
|
||||
replicaCount: 1
|
||||
clusterName: eshop-aks
|
||||
pathBase: /webhooks-web
|
||||
|
||||
image:
|
||||
repository: eshop/webhooks.client
|
||||
tag: latest
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations: {}
|
||||
hosts:
|
||||
- chart-example.local
|
||||
tls: []
|
||||
|
||||
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: WebhooksUrl
|
||||
key: urls__webhooks
|
||||
- name: IdentityUrl
|
||||
key: identity_e
|
||||
- name: CallbackUrl
|
||||
key: webhooksweb_e
|
||||
- name: SelfUrl
|
||||
key: webhooksweb_e
|
||||
# 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: Production
|
||||
- name: OrchestratorType
|
||||
value: 'K8S'
|
||||
- name: Token
|
||||
value: "WebHooks-Demo-Web" # Can use whatever you want
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Polly" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0-preview3-35497" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -17,7 +17,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
|
||||
new ApiResource("locations", "Locations Service"),
|
||||
new ApiResource("mobileshoppingagg", "Mobile Shopping Aggregator"),
|
||||
new ApiResource("webshoppingagg", "Web Shopping Aggregator"),
|
||||
new ApiResource("orders.signalrhub", "Ordering Signalr Hub")
|
||||
new ApiResource("orders.signalrhub", "Ordering Signalr Hub"),
|
||||
new ApiResource("webhooks", "Webhooks registration Service"),
|
||||
};
|
||||
}
|
||||
|
||||
@ -33,7 +34,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
|
||||
}
|
||||
|
||||
// client want to access resources (aka scopes)
|
||||
public static IEnumerable<Client> GetClients(Dictionary<string,string> clientsUrl)
|
||||
public static IEnumerable<Client> GetClients(Dictionary<string, string> clientsUrl)
|
||||
{
|
||||
return new List<Client>
|
||||
{
|
||||
@ -57,7 +58,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
|
||||
"locations",
|
||||
"marketing",
|
||||
"webshoppingagg",
|
||||
"orders.signalrhub"
|
||||
"orders.signalrhub",
|
||||
"webhooks"
|
||||
},
|
||||
},
|
||||
new Client
|
||||
@ -84,7 +86,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
|
||||
"basket",
|
||||
"locations",
|
||||
"marketing",
|
||||
"mobileshoppingagg"
|
||||
"mobileshoppingagg",
|
||||
"webhooks"
|
||||
},
|
||||
//Allow requesting refresh tokens for long lived API access
|
||||
AllowOfflineAccess = true,
|
||||
@ -122,7 +125,40 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
|
||||
"locations",
|
||||
"marketing",
|
||||
"webshoppingagg",
|
||||
"orders.signalrhub"
|
||||
"orders.signalrhub",
|
||||
"webhooks"
|
||||
},
|
||||
AccessTokenLifetime = 60*60*2, // 2 hours
|
||||
IdentityTokenLifetime= 60*60*2 // 2 hours
|
||||
},
|
||||
new Client
|
||||
{
|
||||
ClientId = "webhooksclient",
|
||||
ClientName = "Webhooks Client",
|
||||
ClientSecrets = new List<Secret>
|
||||
{
|
||||
new Secret("secret".Sha256())
|
||||
},
|
||||
ClientUri = $"{clientsUrl["WebhooksWeb"]}", // public uri of the client
|
||||
AllowedGrantTypes = GrantTypes.Hybrid,
|
||||
AllowAccessTokensViaBrowser = false,
|
||||
RequireConsent = false,
|
||||
AllowOfflineAccess = true,
|
||||
AlwaysIncludeUserClaimsInIdToken = true,
|
||||
RedirectUris = new List<string>
|
||||
{
|
||||
$"{clientsUrl["WebhooksWeb"]}/signin-oidc"
|
||||
},
|
||||
PostLogoutRedirectUris = new List<string>
|
||||
{
|
||||
$"{clientsUrl["WebhooksWeb"]}/signout-callback-oidc"
|
||||
},
|
||||
AllowedScopes = new List<string>
|
||||
{
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile,
|
||||
IdentityServerConstants.StandardScopes.OfflineAccess,
|
||||
"webhooks"
|
||||
},
|
||||
AccessTokenLifetime = 60*60*2, // 2 hours
|
||||
IdentityTokenLifetime= 60*60*2 // 2 hours
|
||||
@ -157,7 +193,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
|
||||
"basket",
|
||||
"locations",
|
||||
"marketing",
|
||||
"webshoppingagg"
|
||||
"webshoppingagg",
|
||||
"webhooks"
|
||||
},
|
||||
},
|
||||
new Client
|
||||
@ -249,6 +286,21 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
|
||||
{
|
||||
"webshoppingagg"
|
||||
}
|
||||
},
|
||||
new Client
|
||||
{
|
||||
ClientId = "webhooksswaggerui",
|
||||
ClientName = "WebHooks Service Swagger UI",
|
||||
AllowedGrantTypes = GrantTypes.Implicit,
|
||||
AllowAccessTokensViaBrowser = true,
|
||||
|
||||
RedirectUris = { $"{clientsUrl["WebhooksApi"]}/swagger/oauth2-redirect.html" },
|
||||
PostLogoutRedirectUris = { $"{clientsUrl["WebhooksApi"]}/swagger/" },
|
||||
|
||||
AllowedScopes =
|
||||
{
|
||||
"webhooks"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
|
||||
clientUrls.Add("OrderingApi", configuration.GetValue<string>("OrderingApiClient"));
|
||||
clientUrls.Add("MobileShoppingAgg", configuration.GetValue<string>("MobileShoppingAggClient"));
|
||||
clientUrls.Add("WebShoppingAgg", configuration.GetValue<string>("WebShoppingAggClient"));
|
||||
clientUrls.Add("WebhooksApi", configuration.GetValue<string>("WebhooksApiClient"));
|
||||
clientUrls.Add("WebhooksWeb", configuration.GetValue<string>("WebhooksWebClient"));
|
||||
|
||||
if (!context.Clients.Any())
|
||||
{
|
||||
|
@ -0,0 +1,15 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Webhooks.API.Controllers
|
||||
{
|
||||
|
||||
public class HomeController : Controller
|
||||
{
|
||||
// GET: /<controller>/
|
||||
public IActionResult Index()
|
||||
{
|
||||
return new RedirectResult("~/swagger");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Webhooks.API.Model;
|
||||
|
||||
namespace Webhooks.API.Controllers
|
||||
{
|
||||
public class WebhookSubscriptionRequest : IValidatableObject
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string Event { get; set; }
|
||||
public string GrantUrl { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (!Uri.IsWellFormedUriString(GrantUrl, UriKind.Absolute))
|
||||
{
|
||||
yield return new ValidationResult("GrantUrl is not valid", new[] { nameof(GrantUrl) });
|
||||
}
|
||||
|
||||
if (!Uri.IsWellFormedUriString(Url, UriKind.Absolute))
|
||||
{
|
||||
yield return new ValidationResult("Url is not valid", new[] { nameof(Url) });
|
||||
}
|
||||
|
||||
var isOk = Enum.TryParse<WebhookType>(Event, ignoreCase: true, result: out WebhookType whtype);
|
||||
if (!isOk)
|
||||
{
|
||||
yield return new ValidationResult($"{Event} is invalid event name", new[] { nameof(Event) });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Webhooks.API.Infrastructure;
|
||||
using Webhooks.API.Model;
|
||||
using Webhooks.API.Services;
|
||||
|
||||
namespace Webhooks.API.Controllers
|
||||
{
|
||||
[Route("api/v1/[controller]")]
|
||||
[ApiController]
|
||||
public class WebhooksController : ControllerBase
|
||||
{
|
||||
private readonly WebhooksContext _dbContext;
|
||||
private readonly IIdentityService _identityService;
|
||||
private readonly IGrantUrlTesterService _grantUrlTester;
|
||||
|
||||
public WebhooksController(WebhooksContext dbContext, IIdentityService identityService, IGrantUrlTesterService grantUrlTester)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_identityService = identityService;
|
||||
_grantUrlTester = grantUrlTester;
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<WebhookSubscription>), (int)HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> ListByUser()
|
||||
{
|
||||
var userId = _identityService.GetUserIdentity();
|
||||
var data = await _dbContext.Subscriptions.Where(s => s.UserId == userId).ToListAsync();
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("{id:int}")]
|
||||
[ProducesResponseType(typeof(WebhookSubscription), (int)HttpStatusCode.OK)]
|
||||
[ProducesResponseType((int)HttpStatusCode.NotFound)]
|
||||
public async Task<IActionResult> GetByUserAndId(int id)
|
||||
{
|
||||
var userId = _identityService.GetUserIdentity();
|
||||
var subscription = await _dbContext.Subscriptions.SingleOrDefaultAsync(s => s.Id == id && s.UserId == userId);
|
||||
if (subscription != null)
|
||||
{
|
||||
return Ok(subscription);
|
||||
}
|
||||
return NotFound($"Subscriptions {id} not found");
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
[ProducesResponseType((int)HttpStatusCode.Created)]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(418)]
|
||||
public async Task<IActionResult> SubscribeWebhook(WebhookSubscriptionRequest request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return ValidationProblem(ModelState);
|
||||
}
|
||||
|
||||
var userId = _identityService.GetUserIdentity();
|
||||
|
||||
|
||||
var grantOk = await _grantUrlTester.TestGrantUrl(request.Url, request.GrantUrl, request.Token ?? string.Empty);
|
||||
|
||||
if (grantOk)
|
||||
{
|
||||
var subscription = new WebhookSubscription()
|
||||
{
|
||||
Date = DateTime.UtcNow,
|
||||
DestUrl = request.Url,
|
||||
Token = request.Token,
|
||||
Type = Enum.Parse<WebhookType>(request.Event, ignoreCase: true),
|
||||
UserId = _identityService.GetUserIdentity()
|
||||
};
|
||||
|
||||
_dbContext.Add(subscription);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return CreatedAtAction("GetByUserAndId", new { id = subscription.Id }, subscription);
|
||||
}
|
||||
else
|
||||
{
|
||||
return StatusCode(418, "Grant url can't be validated");
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpDelete("{id:int}")]
|
||||
[ProducesResponseType((int)HttpStatusCode.Accepted)]
|
||||
[ProducesResponseType((int)HttpStatusCode.NotFound)]
|
||||
public async Task<IActionResult> UnsubscribeWebhook(int id)
|
||||
{
|
||||
var userId = _identityService.GetUserIdentity();
|
||||
var subscription = await _dbContext.Subscriptions.SingleOrDefaultAsync(s => s.Id == id && s.UserId == userId);
|
||||
|
||||
if (subscription != null)
|
||||
{
|
||||
_dbContext.Remove(subscription);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
return NotFound($"Subscriptions {id} not found");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
19
src/Services/Webhooks/Webhooks.API/Dockerfile
Normal file
19
src/Services/Webhooks/Webhooks.API/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM microsoft/dotnet:2.2-aspnetcore-runtime AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
|
||||
FROM microsoft/dotnet:2.2-sdk AS build
|
||||
WORKDIR /src
|
||||
COPY ["src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj", "src/Services/Webhooks/Webhooks.API/"]
|
||||
RUN dotnet restore "src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/src/Services/Webhooks/Webhooks.API"
|
||||
RUN dotnet build "Webhooks.API.csproj" -c Release -o /app
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "Webhooks.API.csproj" -c Release -o /app
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app .
|
||||
ENTRYPOINT ["dotnet", "Webhooks.API.dll"]
|
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.Exceptions
|
||||
{
|
||||
public class WebhooksDomainException : Exception
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Webhooks.API.Infrastructure.ActionResult
|
||||
{
|
||||
class InternalServerErrorObjectResult : ObjectResult
|
||||
{
|
||||
public InternalServerErrorObjectResult(object error) : base(error)
|
||||
{
|
||||
StatusCode = StatusCodes.Status500InternalServerError;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Swashbuckle.AspNetCore.Swagger;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.Infrastructure
|
||||
{
|
||||
public class AuthorizeCheckOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(Operation operation, OperationFilterContext context)
|
||||
{
|
||||
// Check for authorize attribute
|
||||
var hasAuthorize = context.ApiDescription.ControllerAttributes().OfType<AuthorizeAttribute>().Any() ||
|
||||
context.ApiDescription.ActionAttributes().OfType<AuthorizeAttribute>().Any();
|
||||
|
||||
if (hasAuthorize)
|
||||
{
|
||||
operation.Responses.Add("401", new Response { Description = "Unauthorized" });
|
||||
operation.Responses.Add("403", new Response { Description = "Forbidden" });
|
||||
|
||||
operation.Security = new List<IDictionary<string, IEnumerable<string>>>();
|
||||
operation.Security.Add(new Dictionary<string, IEnumerable<string>>
|
||||
{
|
||||
{ "oauth2", new [] { "webhooksapi" } }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Webhooks.API.Exceptions;
|
||||
using Webhooks.API.Infrastructure.ActionResult;
|
||||
|
||||
namespace Webhooks.API.Infrastructure
|
||||
{
|
||||
public class HttpGlobalExceptionFilter : IExceptionFilter
|
||||
{
|
||||
private readonly IHostingEnvironment env;
|
||||
private readonly ILogger<HttpGlobalExceptionFilter> logger;
|
||||
|
||||
public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
|
||||
{
|
||||
this.env = env;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public void OnException(ExceptionContext context)
|
||||
{
|
||||
logger.LogError(new EventId(context.Exception.HResult),
|
||||
context.Exception,
|
||||
context.Exception.Message);
|
||||
|
||||
if (context.Exception.GetType() == typeof(WebhooksDomainException))
|
||||
{
|
||||
var problemDetails = new ValidationProblemDetails()
|
||||
{
|
||||
Instance = context.HttpContext.Request.Path,
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Detail = "Please refer to the errors property for additional details."
|
||||
};
|
||||
|
||||
problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() });
|
||||
|
||||
context.Result = new BadRequestObjectResult(problemDetails);
|
||||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
}
|
||||
else
|
||||
{
|
||||
var json = new JsonErrorResponse
|
||||
{
|
||||
Messages = new[] { "An error ocurred." }
|
||||
};
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
json.DeveloperMeesage = context.Exception;
|
||||
}
|
||||
|
||||
context.Result = new InternalServerErrorObjectResult(json);
|
||||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||
}
|
||||
context.ExceptionHandled = true;
|
||||
}
|
||||
|
||||
private class JsonErrorResponse
|
||||
{
|
||||
public string[] Messages { get; set; }
|
||||
|
||||
public object DeveloperMeesage { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Webhooks.API.Model;
|
||||
|
||||
namespace Webhooks.API.Infrastructure
|
||||
{
|
||||
public class WebhooksContext : DbContext
|
||||
{
|
||||
|
||||
public WebhooksContext(DbContextOptions<WebhooksContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
public DbSet<WebhookSubscription> Subscriptions { get; set; }
|
||||
}
|
||||
|
||||
public class WebhooksContextDesignFactory : IDesignTimeDbContextFactory<WebhooksContext>
|
||||
{
|
||||
public WebhooksContext CreateDbContext(string[] args)
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<WebhooksContext>()
|
||||
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;Integrated Security=true");
|
||||
|
||||
return new WebhooksContext(optionsBuilder.Options);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.IntegrationEvents
|
||||
{
|
||||
public class OrderStatusChangedToPaidIntegrationEvent : IntegrationEvent
|
||||
{
|
||||
public int OrderId { get; }
|
||||
public IEnumerable<OrderStockItem> OrderStockItems { get; }
|
||||
|
||||
public OrderStatusChangedToPaidIntegrationEvent(int orderId,
|
||||
IEnumerable<OrderStockItem> orderStockItems)
|
||||
{
|
||||
OrderId = orderId;
|
||||
OrderStockItems = orderStockItems;
|
||||
}
|
||||
}
|
||||
|
||||
public class OrderStockItem
|
||||
{
|
||||
public int ProductId { get; }
|
||||
public int Units { get; }
|
||||
|
||||
public OrderStockItem(int productId, int units)
|
||||
{
|
||||
ProductId = productId;
|
||||
Units = units;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Webhooks.API.Model;
|
||||
using Webhooks.API.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Webhooks.API.IntegrationEvents
|
||||
{
|
||||
public class OrderStatusChangedToPaidIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToPaidIntegrationEvent>
|
||||
{
|
||||
private readonly IWebhooksRetriever _retriever;
|
||||
private readonly IWebhooksSender _sender;
|
||||
private readonly ILogger _logger;
|
||||
public OrderStatusChangedToPaidIntegrationEventHandler(IWebhooksRetriever retriever, IWebhooksSender sender, ILogger<OrderStatusChangedToShippedIntegrationEventHandler> logger )
|
||||
{
|
||||
_retriever = retriever;
|
||||
_sender = sender;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event)
|
||||
{
|
||||
var subscriptions = await _retriever.GetSubscriptionsOfType(WebhookType.OrderPaid);
|
||||
_logger.LogInformation($"Received OrderStatusChangedToShippedIntegrationEvent and got {subscriptions.Count()} subscriptions to process");
|
||||
var whook = new WebhookData(WebhookType.OrderPaid, @event);
|
||||
await _sender.SendAll(subscriptions, whook);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.IntegrationEvents
|
||||
{
|
||||
public class OrderStatusChangedToShippedIntegrationEvent : IntegrationEvent
|
||||
{
|
||||
public int OrderId { get; private set; }
|
||||
public string OrderStatus { get; private set; }
|
||||
public string BuyerName { get; private set; }
|
||||
|
||||
public OrderStatusChangedToShippedIntegrationEvent(int orderId, string orderStatus, string buyerName)
|
||||
{
|
||||
OrderId = orderId;
|
||||
OrderStatus = orderStatus;
|
||||
BuyerName = buyerName;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Webhooks.API.Model;
|
||||
using Webhooks.API.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Webhooks.API.IntegrationEvents
|
||||
{
|
||||
public class OrderStatusChangedToShippedIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToShippedIntegrationEvent>
|
||||
{
|
||||
private readonly IWebhooksRetriever _retriever;
|
||||
private readonly IWebhooksSender _sender;
|
||||
private readonly ILogger _logger;
|
||||
public OrderStatusChangedToShippedIntegrationEventHandler(IWebhooksRetriever retriever, IWebhooksSender sender, ILogger<OrderStatusChangedToShippedIntegrationEventHandler> logger )
|
||||
{
|
||||
_retriever = retriever;
|
||||
_sender = sender;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(OrderStatusChangedToShippedIntegrationEvent @event)
|
||||
{
|
||||
var subscriptions = await _retriever.GetSubscriptionsOfType(WebhookType.OrderShipped);
|
||||
_logger.LogInformation($"Received OrderStatusChangedToShippedIntegrationEvent and got {subscriptions.Count()} subscriptions to process");
|
||||
var whook = new WebhookData(WebhookType.OrderShipped, @event);
|
||||
await _sender.SendAll(subscriptions, whook);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.IntegrationEvents
|
||||
{
|
||||
public class ProductPriceChangedIntegrationEvent : IntegrationEvent
|
||||
{
|
||||
public int ProductId { get; private set; }
|
||||
|
||||
public decimal NewPrice { get; private set; }
|
||||
|
||||
public decimal OldPrice { get; private set; }
|
||||
|
||||
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice)
|
||||
{
|
||||
ProductId = productId;
|
||||
NewPrice = newPrice;
|
||||
OldPrice = oldPrice;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.IntegrationEvents
|
||||
{
|
||||
public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>
|
||||
{
|
||||
public async Task Handle(ProductPriceChangedIntegrationEvent @event)
|
||||
{
|
||||
int i = 0;
|
||||
}
|
||||
}
|
||||
}
|
47
src/Services/Webhooks/Webhooks.API/Migrations/20190118091148_Initial.Designer.cs
generated
Normal file
47
src/Services/Webhooks/Webhooks.API/Migrations/20190118091148_Initial.Designer.cs
generated
Normal file
@ -0,0 +1,47 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Webhooks.API.Infrastructure;
|
||||
|
||||
namespace Webhooks.API.Migrations
|
||||
{
|
||||
[DbContext(typeof(WebhooksContext))]
|
||||
[Migration("20190118091148_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.2.1-servicing-10028")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128)
|
||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
modelBuilder.Entity("Webhooks.API.Model.WebhookSubscription", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
b.Property<DateTime>("Date");
|
||||
|
||||
b.Property<string>("DestUrl");
|
||||
|
||||
b.Property<string>("Token");
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Subscriptions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Webhooks.API.Migrations
|
||||
{
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Subscriptions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false)
|
||||
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
|
||||
Type = table.Column<int>(nullable: false),
|
||||
Date = table.Column<DateTime>(nullable: false),
|
||||
DestUrl = table.Column<string>(nullable: true),
|
||||
Token = table.Column<string>(nullable: true),
|
||||
UserId = table.Column<string>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Subscriptions", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Subscriptions");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Webhooks.API.Infrastructure;
|
||||
|
||||
namespace Webhooks.API.Migrations
|
||||
{
|
||||
[DbContext(typeof(WebhooksContext))]
|
||||
partial class WebhooksContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.2.1-servicing-10028")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128)
|
||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
modelBuilder.Entity("Webhooks.API.Model.WebhookSubscription", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
b.Property<DateTime>("Date");
|
||||
|
||||
b.Property<string>("DestUrl");
|
||||
|
||||
b.Property<string>("Token");
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Subscriptions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
26
src/Services/Webhooks/Webhooks.API/Model/WebhookData.cs
Normal file
26
src/Services/Webhooks/Webhooks.API/Model/WebhookData.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.Model
|
||||
{
|
||||
public class WebhookData
|
||||
{
|
||||
public DateTime When { get; }
|
||||
|
||||
public string Payload { get; }
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
public WebhookData(WebhookType hookType, object data)
|
||||
{
|
||||
When = DateTime.UtcNow;
|
||||
Type = hookType.ToString();
|
||||
Payload = JsonConvert.SerializeObject(data);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.Model
|
||||
{
|
||||
public class WebhookSubscription
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public WebhookType Type { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public string DestUrl { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
}
|
14
src/Services/Webhooks/Webhooks.API/Model/WebhookType.cs
Normal file
14
src/Services/Webhooks/Webhooks.API/Model/WebhookType.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.Model
|
||||
{
|
||||
public enum WebhookType
|
||||
{
|
||||
CatalogItemPriceChange = 1,
|
||||
OrderShipped = 2,
|
||||
OrderPaid = 3
|
||||
}
|
||||
}
|
27
src/Services/Webhooks/Webhooks.API/Program.cs
Normal file
27
src/Services/Webhooks/Webhooks.API/Program.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Webhooks.API.Infrastructure;
|
||||
|
||||
namespace Webhooks.API
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateWebHostBuilder(args).Build()
|
||||
.MigrateDbContext<WebhooksContext>((_,__) => { })
|
||||
.Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStartup<Startup>();
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:62486",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Webhooks.API": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:5000"
|
||||
},
|
||||
"Docker": {
|
||||
"commandName": "Docker",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.Services
|
||||
{
|
||||
class GrantUrlTesterService : IGrantUrlTesterService
|
||||
{
|
||||
private readonly IHttpClientFactory _clientFactory;
|
||||
private readonly ILogger _logger;
|
||||
public GrantUrlTesterService(IHttpClientFactory factory, ILogger<IGrantUrlTesterService> logger)
|
||||
{
|
||||
_clientFactory = factory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> TestGrantUrl(string urlHook, string url, string token)
|
||||
{
|
||||
if (!CheckSameOrigin(urlHook, url))
|
||||
{
|
||||
_logger.LogWarning($"Url of the hook ({urlHook} and the grant url ({url} do not belong to same origin)");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var client = _clientFactory.CreateClient("GrantClient");
|
||||
var msg = new HttpRequestMessage(HttpMethod.Options, url);
|
||||
msg.Headers.Add("X-eshop-whtoken", token);
|
||||
_logger.LogInformation($"Sending the OPTIONS message to {url} with token {token ?? string.Empty}");
|
||||
try
|
||||
{
|
||||
var response = await client.SendAsync(msg);
|
||||
var tokenReceived = response.Headers.TryGetValues("X-eshop-whtoken", out var tokenValues) ? tokenValues.FirstOrDefault() : null;
|
||||
var tokenExpected = string.IsNullOrWhiteSpace(token) ? null : token;
|
||||
_logger.LogInformation($"Response code is {response.StatusCode} for url {url} and token in header was {tokenReceived} (expected token was {tokenExpected})");
|
||||
return response.IsSuccessStatusCode && tokenReceived == tokenExpected;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Exception {ex.GetType().Name} when sending OPTIONS request. Url can't be granted.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckSameOrigin(string urlHook, string url)
|
||||
{
|
||||
var firstUrl = new Uri(urlHook, UriKind.Absolute);
|
||||
var secondUrl = new Uri(url, UriKind.Absolute);
|
||||
|
||||
return firstUrl.Scheme == secondUrl.Scheme &&
|
||||
firstUrl.Port == secondUrl.Port &&
|
||||
firstUrl.Host == firstUrl.Host;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Webhooks.API.Services
|
||||
{
|
||||
public interface IGrantUrlTesterService
|
||||
{
|
||||
Task<bool> TestGrantUrl(string urlHook, string url, string token);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace Webhooks.API.Services
|
||||
{
|
||||
public interface IIdentityService
|
||||
{
|
||||
string GetUserIdentity();
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Webhooks.API.Model;
|
||||
|
||||
namespace Webhooks.API.Services
|
||||
{
|
||||
public interface IWebhooksRetriever
|
||||
{
|
||||
|
||||
Task<IEnumerable<WebhookSubscription>> GetSubscriptionsOfType(WebhookType type);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Webhooks.API.Model;
|
||||
|
||||
namespace Webhooks.API.Services
|
||||
{
|
||||
public interface IWebhooksSender
|
||||
{
|
||||
Task SendAll(IEnumerable<WebhookSubscription> receivers, WebhookData data);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
|
||||
namespace Webhooks.API.Services
|
||||
{
|
||||
public class IdentityService : IIdentityService
|
||||
{
|
||||
private IHttpContextAccessor _context;
|
||||
|
||||
public IdentityService(IHttpContextAccessor context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public string GetUserIdentity()
|
||||
{
|
||||
return _context.HttpContext.User.FindFirst("sub").Value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Webhooks.API.Infrastructure;
|
||||
using Webhooks.API.Model;
|
||||
|
||||
namespace Webhooks.API.Services
|
||||
{
|
||||
public class WebhooksRetriever : IWebhooksRetriever
|
||||
{
|
||||
private readonly WebhooksContext _db;
|
||||
public WebhooksRetriever(WebhooksContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
public async Task<IEnumerable<WebhookSubscription>> GetSubscriptionsOfType(WebhookType type)
|
||||
{
|
||||
var data = await _db.Subscriptions.Where(s => s.Type == type).ToListAsync();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Webhooks.API.Model;
|
||||
|
||||
namespace Webhooks.API.Services
|
||||
{
|
||||
public class WebhooksSender : IWebhooksSender
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger _logger;
|
||||
public WebhooksSender(IHttpClientFactory httpClientFactory, ILogger<WebhooksSender> logger)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task SendAll(IEnumerable<WebhookSubscription> receivers, WebhookData data)
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
var json = JsonConvert.SerializeObject(data);
|
||||
var tasks = receivers.Select(r => OnSendData(r, json, client));
|
||||
await Task.WhenAll(tasks.ToArray());
|
||||
}
|
||||
|
||||
private Task OnSendData(WebhookSubscription subs, string jsonData, HttpClient client)
|
||||
{
|
||||
var request = new HttpRequestMessage()
|
||||
{
|
||||
RequestUri = new Uri(subs.DestUrl, UriKind.Absolute),
|
||||
Method = HttpMethod.Post,
|
||||
Content = new StringContent(jsonData, Encoding.UTF8, "application/json")
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(subs.Token))
|
||||
{
|
||||
request.Headers.Add("X-eshop-whtoken", subs.Token);
|
||||
}
|
||||
_logger.LogDebug($"Sending hook to {subs.DestUrl} of type {subs.Type.ToString()}");
|
||||
return client.SendAsync(request);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
372
src/Services/Webhooks/Webhooks.API/Startup.cs
Normal file
372
src/Services/Webhooks/Webhooks.API/Startup.cs
Normal file
@ -0,0 +1,372 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Autofac;
|
||||
using Autofac.Extensions.DependencyInjection;
|
||||
using HealthChecks.UI.Client;
|
||||
using Microsoft.ApplicationInsights.Extensibility;
|
||||
using Microsoft.ApplicationInsights.ServiceFabric;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RabbitMQ.Client;
|
||||
using Swashbuckle.AspNetCore.Swagger;
|
||||
using Webhooks.API.Infrastructure;
|
||||
using Webhooks.API.IntegrationEvents;
|
||||
using Webhooks.API.Services;
|
||||
|
||||
namespace Webhooks.API
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
|
||||
public IServiceProvider ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.AddAppInsight(Configuration)
|
||||
.AddCustomMVC(Configuration)
|
||||
.AddCustomDbContext(Configuration)
|
||||
.AddSwagger(Configuration)
|
||||
.AddCustomHealthCheck(Configuration)
|
||||
.AddHttpClientServices(Configuration)
|
||||
.AddIntegrationServices(Configuration)
|
||||
.AddEventBus(Configuration)
|
||||
.AddCustomAuthentication(Configuration)
|
||||
.AddSingleton<IHttpContextAccessor, HttpContextAccessor>()
|
||||
.AddTransient<IIdentityService, IdentityService>()
|
||||
.AddTransient<IGrantUrlTesterService, GrantUrlTesterService>()
|
||||
.AddTransient<IWebhooksRetriever, WebhooksRetriever>()
|
||||
.AddTransient<IWebhooksSender, WebhooksSender>();
|
||||
|
||||
var container = new ContainerBuilder();
|
||||
container.Populate(services);
|
||||
return new AutofacServiceProvider(container.Build());
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||
{
|
||||
loggerFactory.AddAzureWebAppDiagnostics();
|
||||
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
|
||||
|
||||
var pathBase = Configuration["PATH_BASE"];
|
||||
|
||||
if (!string.IsNullOrEmpty(pathBase))
|
||||
{
|
||||
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
|
||||
app.UsePathBase(pathBase);
|
||||
}
|
||||
|
||||
app.UseHealthChecks("/hc", new HealthCheckOptions()
|
||||
{
|
||||
Predicate = _ => true,
|
||||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
||||
});
|
||||
|
||||
app.UseHealthChecks("/liveness", new HealthCheckOptions
|
||||
{
|
||||
Predicate = r => r.Name.Contains("self")
|
||||
});
|
||||
|
||||
app.UseCors("CorsPolicy");
|
||||
|
||||
ConfigureAuth(app);
|
||||
|
||||
app.UseMvcWithDefaultRoute();
|
||||
|
||||
app.UseSwagger()
|
||||
.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Webhooks.API V1");
|
||||
c.OAuthClientId("webhooksswaggerui");
|
||||
c.OAuthAppName("WebHooks Service Swagger UI");
|
||||
});
|
||||
|
||||
ConfigureEventBus(app);
|
||||
}
|
||||
|
||||
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
||||
{
|
||||
/*
|
||||
if (Configuration.GetValue<bool>("UseLoadTest"))
|
||||
{
|
||||
app.UseMiddleware<ByPassAuthMiddleware>();
|
||||
}
|
||||
*/
|
||||
|
||||
app.UseAuthentication();
|
||||
}
|
||||
|
||||
protected virtual void ConfigureEventBus(IApplicationBuilder app)
|
||||
{
|
||||
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
|
||||
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
|
||||
eventBus.Subscribe<OrderStatusChangedToShippedIntegrationEvent, OrderStatusChangedToShippedIntegrationEventHandler>();
|
||||
eventBus.Subscribe<OrderStatusChangedToPaidIntegrationEvent, OrderStatusChangedToPaidIntegrationEventHandler>();
|
||||
}
|
||||
}
|
||||
|
||||
static class CustomExtensionMethods
|
||||
{
|
||||
public static IServiceCollection AddAppInsight(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddApplicationInsightsTelemetry(configuration);
|
||||
var orchestratorType = configuration.GetValue<string>("OrchestratorType");
|
||||
|
||||
if (orchestratorType?.ToUpper() == "K8S")
|
||||
{
|
||||
// Enable K8s telemetry initializer
|
||||
services.EnableKubernetes();
|
||||
}
|
||||
if (orchestratorType?.ToUpper() == "SF")
|
||||
{
|
||||
// Enable SF telemetry initializer
|
||||
services.AddSingleton<ITelemetryInitializer>((serviceProvider) =>
|
||||
new FabricTelemetryInitializer());
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCustomMVC(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddMvc(options =>
|
||||
{
|
||||
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
|
||||
.AddControllersAsServices();
|
||||
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("CorsPolicy",
|
||||
builder => builder
|
||||
.SetIsOriginAllowed((host) => true)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials());
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddDbContext<WebhooksContext>(options =>
|
||||
{
|
||||
options.UseSqlServer(configuration["ConnectionString"],
|
||||
sqlServerOptionsAction: sqlOptions =>
|
||||
{
|
||||
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
|
||||
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
||||
sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
|
||||
});
|
||||
|
||||
// Changing default behavior when client evaluation occurs to throw.
|
||||
// Default in EF Core would be to log a warning when client evaluation is performed.
|
||||
options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
|
||||
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.DescribeAllEnumsAsStrings();
|
||||
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
|
||||
{
|
||||
Title = "eShopOnContainers - Webhooks HTTP API",
|
||||
Version = "v1",
|
||||
Description = "The Webhooks Microservice HTTP API. This is a simple webhooks CRUD registration entrypoint",
|
||||
TermsOfService = "Terms Of Service"
|
||||
});
|
||||
|
||||
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
|
||||
{
|
||||
Type = "oauth2",
|
||||
Flow = "implicit",
|
||||
AuthorizationUrl = $"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize",
|
||||
TokenUrl = $"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token",
|
||||
Scopes = new Dictionary<string, string>()
|
||||
{
|
||||
{ "webhooks", "Webhooks API" }
|
||||
}
|
||||
});
|
||||
|
||||
options.OperationFilter<AuthorizeCheckOperationFilter>();
|
||||
});
|
||||
|
||||
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, subscriptionClientName, iLifetimeScope);
|
||||
});
|
||||
|
||||
}
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
|
||||
services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
|
||||
services.AddTransient<OrderStatusChangedToShippedIntegrationEventHandler>();
|
||||
services.AddTransient<OrderStatusChangedToPaidIntegrationEventHandler>();
|
||||
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())
|
||||
.AddSqlServer(
|
||||
configuration["ConnectionString"],
|
||||
name: "WebhooksApiDb-check",
|
||||
tags: new string[] { "webhooksdb" });
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(Timeout.InfiniteTimeSpan);
|
||||
//add http client services
|
||||
services.AddHttpClient("GrantClient")
|
||||
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
|
||||
//.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
|
||||
sp => (DbConnection c) => new IntegrationEventLogService(c));
|
||||
|
||||
if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
|
||||
{
|
||||
services.AddSingleton<IServiceBusPersisterConnection>(sp =>
|
||||
{
|
||||
var logger = sp.GetRequiredService<ILogger<DefaultServiceBusPersisterConnection>>();
|
||||
var serviceBusConnection = new ServiceBusConnectionStringBuilder(configuration["EventBusConnection"]);
|
||||
return new DefaultServiceBusPersisterConnection(serviceBusConnection, logger);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
|
||||
{
|
||||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
|
||||
|
||||
var factory = new ConnectionFactory()
|
||||
{
|
||||
HostName = configuration["EventBusConnection"]
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(configuration["EventBusUserName"]))
|
||||
{
|
||||
factory.UserName = configuration["EventBusUserName"];
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(configuration["EventBusPassword"]))
|
||||
{
|
||||
factory.Password = configuration["EventBusPassword"];
|
||||
}
|
||||
|
||||
var retryCount = 5;
|
||||
if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"]))
|
||||
{
|
||||
retryCount = int.Parse(configuration["EventBusRetryCount"]);
|
||||
}
|
||||
|
||||
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
|
||||
});
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
// prevent from mapping "sub" claim to nameidentifier.
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
|
||||
|
||||
var identityUrl = configuration.GetValue<string>("IdentityUrl");
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
|
||||
}).AddJwtBearer(options =>
|
||||
{
|
||||
options.Authority = identityUrl;
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.Audience = "webhooks";
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
32
src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj
Normal file
32
src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj
Normal file
@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.0.2105168" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" />
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.0-beta8" />
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.1.1-beta1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="2.2.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" />
|
||||
<ProjectReference Include="..\..\..\BuildingBlocks\WebHostCustomization\WebHost.Customization\WebHost.Customization.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word"
|
||||
}
|
10
src/Services/Webhooks/Webhooks.API/appsettings.json
Normal file
10
src/Services/Webhooks/Webhooks.API/appsettings.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"SubscriptionClientName": "Webhooks",
|
||||
"EventBusRetryCount": 5
|
||||
}
|
1
src/Web/WebhookClient/.dockerignore
Normal file
1
src/Web/WebhookClient/.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
!wwwroot
|
1
src/Web/WebhookClient/.gitignore
vendored
Normal file
1
src/Web/WebhookClient/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
!wwwroot/lib
|
40
src/Web/WebhookClient/Controllers/AccountController.cs
Normal file
40
src/Web/WebhookClient/Controllers/AccountController.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebhookClient.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
public class AccountController : Controller
|
||||
{
|
||||
public async Task<IActionResult> SignIn(string returnUrl)
|
||||
{
|
||||
var user = User as ClaimsPrincipal;
|
||||
|
||||
var token = await HttpContext.GetTokenAsync("access_token");
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
ViewData["access_token"] = token;
|
||||
}
|
||||
|
||||
return RedirectToPage("/Index");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Signout()
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
|
||||
var homeUrl = Url.Page("/Index");
|
||||
return new SignOutResult(OpenIdConnectDefaults.AuthenticationScheme,
|
||||
new AuthenticationProperties { RedirectUri = homeUrl });
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using WebhookClient.Models;
|
||||
using WebhookClient.Services;
|
||||
|
||||
namespace WebhookClient.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("webhook-received")]
|
||||
public class WebhooksReceivedController : Controller
|
||||
{
|
||||
|
||||
private readonly Settings _settings;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHooksRepository _hooksRepository;
|
||||
|
||||
public WebhooksReceivedController(IOptions<Settings> settings, ILogger<WebhooksReceivedController> logger, IHooksRepository hooksRepository)
|
||||
{
|
||||
_settings = settings.Value;
|
||||
_logger = logger;
|
||||
_hooksRepository = hooksRepository;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> NewWebhook(WebhookData hook)
|
||||
{
|
||||
var header = Request.Headers[HeaderNames.WebHookCheckHeader];
|
||||
var token = header.FirstOrDefault();
|
||||
|
||||
_logger.LogInformation($"Received hook with token {token}. My token is {_settings.Token}. Token validation is set to {_settings.ValidateToken}");
|
||||
|
||||
if (!_settings.ValidateToken || _settings.Token == token)
|
||||
{
|
||||
_logger.LogInformation($"Received hook is going to be processed");
|
||||
var newHook = new WebHookReceived()
|
||||
{
|
||||
Data = hook.Payload,
|
||||
When = hook.When,
|
||||
Token = token
|
||||
};
|
||||
await _hooksRepository.AddNew(newHook);
|
||||
_logger.LogInformation($"Received hook was processed.");
|
||||
return Ok(newHook);
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Received hook is NOT processed - Bad Request returned.");
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
}
|
20
src/Web/WebhookClient/Dockerfile
Normal file
20
src/Web/WebhookClient/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
||||
FROM microsoft/dotnet:2.2-aspnetcore-runtime AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
FROM microsoft/dotnet:2.2-sdk AS build
|
||||
WORKDIR /src
|
||||
COPY ["src/Web/WebhookClient/WebhookClient.csproj", "src/Web/WebhookClient/"]
|
||||
RUN dotnet restore "src/Web/WebhookClient/WebhookClient.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/src/Web/WebhookClient"
|
||||
RUN dotnet build "WebhookClient.csproj" -c Release -o /app
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "WebhookClient.csproj" -c Release -o /app
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app .
|
||||
ENTRYPOINT ["dotnet", "WebhookClient.dll"]
|
12
src/Web/WebhookClient/HeaderNames.cs
Normal file
12
src/Web/WebhookClient/HeaderNames.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebhookClient
|
||||
{
|
||||
static class HeaderNames
|
||||
{
|
||||
public const string WebHookCheckHeader = "X-eshop-whtoken";
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebhookClient
|
||||
{
|
||||
public class HttpClientAuthorizationDelegatingHandler
|
||||
: DelegatingHandler
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccesor;
|
||||
|
||||
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccesor)
|
||||
{
|
||||
_httpContextAccesor = httpContextAccesor;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var authorizationHeader = _httpContextAccesor.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 _httpContextAccesor.HttpContext
|
||||
.GetTokenAsync(ACCESS_TOKEN);
|
||||
}
|
||||
}
|
||||
}
|
15
src/Web/WebhookClient/Models/WebHookReceived.cs
Normal file
15
src/Web/WebhookClient/Models/WebHookReceived.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebhookClient.Models
|
||||
{
|
||||
public class WebHookReceived
|
||||
{
|
||||
public DateTime When { get; set; }
|
||||
public string Data { get; set; }
|
||||
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
16
src/Web/WebhookClient/Models/WebhookData.cs
Normal file
16
src/Web/WebhookClient/Models/WebhookData.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebhookClient.Models
|
||||
{
|
||||
public class WebhookData
|
||||
{
|
||||
public DateTime When { get; set; }
|
||||
|
||||
public string Payload { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
}
|
||||
}
|
14
src/Web/WebhookClient/Models/WebhookResponse.cs
Normal file
14
src/Web/WebhookClient/Models/WebhookResponse.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebhookClient.Models
|
||||
{
|
||||
public class WebhookResponse
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public string DestUrl { get; set; }
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
15
src/Web/WebhookClient/Models/WebhookSubscriptionRequest.cs
Normal file
15
src/Web/WebhookClient/Models/WebhookSubscriptionRequest.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebhookClient.Models
|
||||
{
|
||||
public class WebhookSubscriptionRequest
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string Event { get; set; }
|
||||
public string GrantUrl { get; set; }
|
||||
}
|
||||
}
|
26
src/Web/WebhookClient/Pages/Error.cshtml
Normal file
26
src/Web/WebhookClient/Pages/Error.cshtml
Normal file
@ -0,0 +1,26 @@
|
||||
@page
|
||||
@model ErrorModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
23
src/Web/WebhookClient/Pages/Error.cshtml.cs
Normal file
23
src/Web/WebhookClient/Pages/Error.cshtml.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace WebhookClient.Pages
|
||||
{
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public class ErrorModel : PageModel
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||
}
|
||||
}
|
||||
}
|
31
src/Web/WebhookClient/Pages/Index.cshtml
Normal file
31
src/Web/WebhookClient/Pages/Index.cshtml
Normal file
@ -0,0 +1,31 @@
|
||||
@page
|
||||
@model IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "Home page";
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">Welcome</h1>
|
||||
<p>eShopOnContainers - Webhook client</p>
|
||||
@if (!User.Identity.IsAuthenticated)
|
||||
{
|
||||
<a class="btn-primary btn" href="@Url.Action("SignIn", "Account")">Login</a>
|
||||
<p>Why I need to login? You only need to login <bold>to setup a new webhook</bold>.</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="table">
|
||||
<h3>Current webhooks received</h3>
|
||||
<p>(Data since last time web started up)<p>
|
||||
<table class="table">
|
||||
@foreach (var webhook in Model.WebHooksReceived)
|
||||
{
|
||||
<tr>
|
||||
<td>@webhook.When</td>
|
||||
<td><pre>@webhook.Data</pre></td>
|
||||
<td>@(webhook.Token ?? "--None--")</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
|
30
src/Web/WebhookClient/Pages/Index.cshtml.cs
Normal file
30
src/Web/WebhookClient/Pages/Index.cshtml.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using WebhookClient.Models;
|
||||
using WebhookClient.Services;
|
||||
|
||||
namespace WebhookClient.Pages
|
||||
{
|
||||
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
private readonly IHooksRepository _hooksRepository;
|
||||
|
||||
public IndexModel(IHooksRepository hooksRepository)
|
||||
{
|
||||
_hooksRepository = hooksRepository;
|
||||
}
|
||||
|
||||
public IEnumerable<WebHookReceived> WebHooksReceived { get; private set; }
|
||||
|
||||
public async Task OnGet()
|
||||
{
|
||||
WebHooksReceived = await _hooksRepository.GetAll();
|
||||
}
|
||||
}
|
||||
}
|
8
src/Web/WebhookClient/Pages/Privacy.cshtml
Normal file
8
src/Web/WebhookClient/Pages/Privacy.cshtml
Normal file
@ -0,0 +1,8 @@
|
||||
@page
|
||||
@model PrivacyModel
|
||||
@{
|
||||
ViewData["Title"] = "Privacy Policy";
|
||||
}
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<p>Use this page to detail your site's privacy policy.</p>
|
16
src/Web/WebhookClient/Pages/Privacy.cshtml.cs
Normal file
16
src/Web/WebhookClient/Pages/Privacy.cshtml.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace WebhookClient.Pages
|
||||
{
|
||||
public class PrivacyModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
19
src/Web/WebhookClient/Pages/RegisterWebhook.cshtml
Normal file
19
src/Web/WebhookClient/Pages/RegisterWebhook.cshtml
Normal file
@ -0,0 +1,19 @@
|
||||
@page
|
||||
@model WebhookClient.Pages.RegisterWebhookModel
|
||||
@{
|
||||
ViewData["Title"] = "RegisterWebhook";
|
||||
}
|
||||
|
||||
<h3>Register webhook</h3>
|
||||
|
||||
<p>This page registers the "OrderPaid" Webhook by sending a POST to webhooks.</p>
|
||||
|
||||
<form method="post">
|
||||
<p>Token: <input type="text" asp-for="Token" /></p>
|
||||
<input type="submit" value="send" />
|
||||
</form>
|
||||
|
||||
@if (Model.ResponseCode != (int)System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
<p>Error @Model.ResponseCode (@Model.ResponseMessage) when calling the Webhooks API (@Model.RequestUrl) with GrantUrl: @Model.GrantUrl):(</p>
|
||||
}
|
81
src/Web/WebhookClient/Pages/RegisterWebhook.cshtml.cs
Normal file
81
src/Web/WebhookClient/Pages/RegisterWebhook.cshtml.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Options;
|
||||
using WebhookClient.Models;
|
||||
|
||||
namespace WebhookClient.Pages
|
||||
{
|
||||
[Authorize]
|
||||
|
||||
public class RegisterWebhookModel : PageModel
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
[BindProperty] public string Token { get; set; }
|
||||
|
||||
public int ResponseCode { get; set; }
|
||||
public string RequestUrl { get; set; }
|
||||
public string GrantUrl { get; set; }
|
||||
public string ResponseMessage { get; set; }
|
||||
|
||||
|
||||
public RegisterWebhookModel(IOptions<Settings> settings, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_settings = settings.Value;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
ResponseCode = (int)HttpStatusCode.OK;
|
||||
Token = _settings.Token;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost()
|
||||
{
|
||||
ResponseCode = (int)HttpStatusCode.OK;
|
||||
var protocol = Request.IsHttps ? "https" : "http";
|
||||
var selfurl = !string.IsNullOrEmpty(_settings.SelfUrl) ? _settings.SelfUrl : $"{protocol}://{Request.Host}/{Request.PathBase}";
|
||||
if (!selfurl.EndsWith("/"))
|
||||
{
|
||||
selfurl = selfurl + "/";
|
||||
}
|
||||
var granturl = $"{selfurl}check";
|
||||
var url = $"{selfurl}webhook-received";
|
||||
var client = _httpClientFactory.CreateClient("GrantClient");
|
||||
|
||||
var payload = new WebhookSubscriptionRequest()
|
||||
{
|
||||
Event = "OrderPaid",
|
||||
GrantUrl = granturl,
|
||||
Url = url,
|
||||
Token = Token
|
||||
};
|
||||
var response = await client.PostAsync<WebhookSubscriptionRequest>(_settings.WebhooksUrl + "/api/v1/webhooks", payload, new JsonMediaTypeFormatter());
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return RedirectToPage("WebhooksList");
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseCode = (int)response.StatusCode;
|
||||
ResponseMessage = response.ReasonPhrase;
|
||||
GrantUrl = granturl;
|
||||
RequestUrl = $"{response.RequestMessage.Method} {response.RequestMessage.RequestUri}";
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
}
|
79
src/Web/WebhookClient/Pages/Shared/_Layout.cshtml
Normal file
79
src/Web/WebhookClient/Pages/Shared/_Layout.cshtml
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - WebhookClient</title>
|
||||
|
||||
<environment include="Development">
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css"
|
||||
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
|
||||
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
|
||||
</environment>
|
||||
<link rel="stylesheet" href="~/css/site.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" asp-area="" asp-page="/Index">WebhookClient</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
|
||||
<ul class="navbar-nav flex-grow-1">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-page="/RegisterWebhook">Register webhook</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-page="/WebhooksList">Webhooks registered (in API)</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="container">
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted">
|
||||
<div class="container">
|
||||
© 2019 - WebhookClient - <a asp-area="" asp-page="/RegisterWebhook">Register Webhook</a> | <a asp-area="" asp-page="/WebhooksList">Webhooks registered in API</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<environment include="Development">
|
||||
<script src="~/lib/jquery/dist/jquery.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
|
||||
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
|
||||
asp-fallback-test="window.jQuery"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js"
|
||||
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
|
||||
</script>
|
||||
</environment>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
|
||||
@RenderSection("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,18 @@
|
||||
<environment include="Development">
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0/jquery.validate.min.js"
|
||||
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-F6h55Qw6sweK+t7SiOJX+2bpSAa3b/fnlrVCJvmEj1A=">
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-9GycpJnliUjJDVDqP0UEu/bsm9U+3dnQUH8+3W10vkY=">
|
||||
</script>
|
||||
</environment>
|
28
src/Web/WebhookClient/Pages/WebhooksList.cshtml
Normal file
28
src/Web/WebhookClient/Pages/WebhooksList.cshtml
Normal file
@ -0,0 +1,28 @@
|
||||
@page
|
||||
@model WebhookClient.Pages.WebhooksListModel
|
||||
@{
|
||||
ViewData["Title"] = "WebhooksList";
|
||||
}
|
||||
|
||||
<h1>List of Webhooks registered by user @User.Identity.Name</h1>
|
||||
|
||||
<table class="table">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th scope="col">Date</th>
|
||||
<th scope="col">Destination Url</th>
|
||||
<th scope="col">Validation token</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@foreach (var whr in Model.Webhooks)
|
||||
{
|
||||
<tr>
|
||||
<td>@whr.Date</td>
|
||||
<td>@whr.DestUrl</td>
|
||||
<td>@whr.Token</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
</table>
|
||||
|
28
src/Web/WebhookClient/Pages/WebhooksList.cshtml.cs
Normal file
28
src/Web/WebhookClient/Pages/WebhooksList.cshtml.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using WebhookClient.Models;
|
||||
using WebhookClient.Services;
|
||||
|
||||
namespace WebhookClient.Pages
|
||||
{
|
||||
public class WebhooksListModel : PageModel
|
||||
{
|
||||
private readonly IWebhooksClient _webhooksClient;
|
||||
|
||||
public IEnumerable<WebhookResponse> Webhooks { get; private set; }
|
||||
|
||||
public WebhooksListModel(IWebhooksClient webhooksClient)
|
||||
{
|
||||
_webhooksClient = webhooksClient;
|
||||
}
|
||||
|
||||
public async Task OnGet()
|
||||
{
|
||||
Webhooks = await _webhooksClient.LoadWebhooks();
|
||||
}
|
||||
}
|
||||
}
|
3
src/Web/WebhookClient/Pages/_ViewImports.cshtml
Normal file
3
src/Web/WebhookClient/Pages/_ViewImports.cshtml
Normal file
@ -0,0 +1,3 @@
|
||||
@using WebhookClient
|
||||
@namespace WebhookClient.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
3
src/Web/WebhookClient/Pages/_ViewStart.cshtml
Normal file
3
src/Web/WebhookClient/Pages/_ViewStart.cshtml
Normal file
@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
24
src/Web/WebhookClient/Program.cs
Normal file
24
src/Web/WebhookClient/Program.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WebhookClient
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStartup<Startup>();
|
||||
}
|
||||
}
|
32
src/Web/WebhookClient/Properties/launchSettings.json
Normal file
32
src/Web/WebhookClient/Properties/launchSettings.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:51921",
|
||||
"sslPort": 44398
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"WebhookClient": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000"
|
||||
},
|
||||
"Docker": {
|
||||
"commandName": "Docker",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}"
|
||||
}
|
||||
}
|
||||
}
|
14
src/Web/WebhookClient/Services/IHooksRepository.cs
Normal file
14
src/Web/WebhookClient/Services/IHooksRepository.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using WebhookClient.Models;
|
||||
|
||||
namespace WebhookClient.Services
|
||||
{
|
||||
public interface IHooksRepository
|
||||
{
|
||||
Task<IEnumerable<WebHookReceived>> GetAll();
|
||||
Task AddNew(WebHookReceived hook);
|
||||
}
|
||||
}
|
13
src/Web/WebhookClient/Services/IWebhooksClient.cs
Normal file
13
src/Web/WebhookClient/Services/IWebhooksClient.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using WebhookClient.Models;
|
||||
|
||||
namespace WebhookClient.Services
|
||||
{
|
||||
public interface IWebhooksClient
|
||||
{
|
||||
Task<IEnumerable<WebhookResponse>> LoadWebhooks();
|
||||
}
|
||||
}
|
26
src/Web/WebhookClient/Services/InMemoryHooksRepository.cs
Normal file
26
src/Web/WebhookClient/Services/InMemoryHooksRepository.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using WebhookClient.Models;
|
||||
|
||||
namespace WebhookClient.Services
|
||||
{
|
||||
public class InMemoryHooksRepository : IHooksRepository
|
||||
{
|
||||
private readonly List<WebHookReceived> _data;
|
||||
|
||||
public InMemoryHooksRepository() => _data = new List<WebHookReceived>();
|
||||
|
||||
public Task AddNew(WebHookReceived hook)
|
||||
{
|
||||
_data.Add(hook);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<WebHookReceived>> GetAll()
|
||||
{
|
||||
return Task.FromResult(_data.AsEnumerable());
|
||||
}
|
||||
}
|
||||
}
|
30
src/Web/WebhookClient/Services/WebhooksClient.cs
Normal file
30
src/Web/WebhookClient/Services/WebhooksClient.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using WebhookClient.Models;
|
||||
|
||||
namespace WebhookClient.Services
|
||||
{
|
||||
public class WebhooksClient : IWebhooksClient
|
||||
{
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly Settings _settings;
|
||||
public WebhooksClient(IHttpClientFactory httpClientFactory, IOptions<Settings> settings)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_settings = settings.Value;
|
||||
}
|
||||
public async Task<IEnumerable<WebhookResponse>> LoadWebhooks()
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient("GrantClient");
|
||||
var response = await client.GetAsync(_settings.WebhooksUrl + "/api/v1/webhooks");
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var subscriptions = JsonConvert.DeserializeObject<IEnumerable<WebhookResponse>>(json);
|
||||
return subscriptions;
|
||||
}
|
||||
}
|
||||
}
|
19
src/Web/WebhookClient/Settings.cs
Normal file
19
src/Web/WebhookClient/Settings.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebhookClient
|
||||
{
|
||||
public class Settings
|
||||
{
|
||||
public string Token { get; set; }
|
||||
public string IdentityUrl { get; set; }
|
||||
public string CallBackUrl { get; set; }
|
||||
public string WebhooksUrl { get; set; }
|
||||
public string SelfUrl { get; set; }
|
||||
|
||||
public bool ValidateToken { get; set; }
|
||||
|
||||
}
|
||||
}
|
151
src/Web/WebhookClient/Startup.cs
Normal file
151
src/Web/WebhookClient/Startup.cs
Normal file
@ -0,0 +1,151 @@
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using WebhookClient.Services;
|
||||
|
||||
namespace WebhookClient
|
||||
{
|
||||
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
|
||||
.AddSession(opt =>
|
||||
{
|
||||
opt.Cookie.Name = ".eShopWebhooks.Session";
|
||||
})
|
||||
.AddConfiguration(Configuration)
|
||||
.AddHttpClientServices(Configuration)
|
||||
.AddCustomAuthentication(Configuration)
|
||||
.AddTransient<IWebhooksClient, WebhooksClient>()
|
||||
.AddSingleton<IHooksRepository, InMemoryHooksRepository>()
|
||||
.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
|
||||
var pathBase = Configuration["PATH_BASE"];
|
||||
if (!string.IsNullOrEmpty(pathBase))
|
||||
{
|
||||
app.UsePathBase(pathBase);
|
||||
}
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
}
|
||||
app.UseAuthentication();
|
||||
app.Map("/check", capp =>
|
||||
{
|
||||
capp.Run(async (context) =>
|
||||
{
|
||||
if ("OPTIONS".Equals(context.Request.Method, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var validateToken = bool.TrueString.Equals(Configuration["ValidateToken"], StringComparison.InvariantCultureIgnoreCase);
|
||||
var header = context.Request.Headers[HeaderNames.WebHookCheckHeader];
|
||||
var value = header.FirstOrDefault();
|
||||
var tokenToValidate = Configuration["Token"];
|
||||
if (!validateToken || value == tokenToValidate)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tokenToValidate))
|
||||
{
|
||||
context.Response.Headers.Add(HeaderNames.WebHookCheckHeader, tokenToValidate);
|
||||
}
|
||||
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.Response.WriteAsync("Invalid token");
|
||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
}
|
||||
});
|
||||
});
|
||||
app.UseStaticFiles();
|
||||
app.UseSession();
|
||||
app.UseMvcWithDefaultRoute();
|
||||
}
|
||||
}
|
||||
|
||||
static class ServiceExtensions
|
||||
{
|
||||
public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddOptions();
|
||||
services.Configure<Settings>(configuration);
|
||||
return services;
|
||||
}
|
||||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var identityUrl = configuration.GetValue<string>("IdentityUrl");
|
||||
var callBackUrl = configuration.GetValue<string>("CallBackUrl");
|
||||
|
||||
// Add Authentication services
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromHours(2))
|
||||
.AddOpenIdConnect(options =>
|
||||
{
|
||||
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
options.Authority = identityUrl.ToString();
|
||||
options.SignedOutRedirectUri = callBackUrl.ToString();
|
||||
options.ClientId = "webhooksclient";
|
||||
options.ClientSecret = "secret";
|
||||
options.ResponseType = "code id_token";
|
||||
options.SaveTokens = true;
|
||||
options.GetClaimsFromUserInfoEndpoint = true;
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.Scope.Add("openid");
|
||||
options.Scope.Add("webhooks");
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
|
||||
services.AddHttpClient("extendedhandlerlifetime").SetHandlerLifetime(Timeout.InfiniteTimeSpan);
|
||||
|
||||
//add http client services
|
||||
services.AddHttpClient("GrantClient")
|
||||
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
|
||||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
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