@ -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,29 @@ | |||
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; | |||
namespace Webhooks.API.IntegrationEvents | |||
{ | |||
public class OrderStatusChangedToShippedIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToShippedIntegrationEvent> | |||
{ | |||
private readonly IWebhooksRetriever _retriever; | |||
private readonly IWebhooksSender _sender; | |||
public OrderStatusChangedToShippedIntegrationEventHandler(IWebhooksRetriever retriever, IWebhooksSender sender ) | |||
{ | |||
_retriever = retriever; | |||
_sender = sender; | |||
} | |||
public async Task Handle(OrderStatusChangedToShippedIntegrationEvent @event) | |||
{ | |||
var subscriptions = await _retriever.GetSubscriptionsOfType(WebhookType.OrderShipped); | |||
var whook = new WebhookData(WebhookType.OrderShipped, @event); | |||
await _sender.SendAll(subscriptions, whook); | |||
} | |||
} | |||
} |
@ -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,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,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); | |||
} | |||
} | |||
} |
@ -0,0 +1,24 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.AspNetCore.Http | |||
{ | |||
static class ISessionExtensions | |||
{ | |||
public static void Set<T>(this ISession session, string key, T value) | |||
{ | |||
session.SetString(key, JsonConvert.SerializeObject(value)); | |||
} | |||
public static T Get<T>(this ISession session, string key) | |||
{ | |||
var value = session.GetString(key); | |||
return value == null ? default(T) : | |||
JsonConvert.DeserializeObject<T>(value); | |||
} | |||
} | |||
} |
@ -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); | |||
} | |||
} | |||
} |
@ -0,0 +1,13 @@ | |||
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; } | |||
} | |||
} |
@ -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; } | |||
} | |||
} |
@ -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; } | |||
} | |||
} |
@ -0,0 +1,14 @@ | |||
@page | |||
@model WebhookClient.Pages.RegisterWebhookModel | |||
@{ | |||
ViewData["Title"] = "RegisterWebhook"; | |||
} | |||
<h3>Register webhook</h3> | |||
<p>This page registers the "OrderShipped" 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> |
@ -0,0 +1,55 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net.Http; | |||
using System.Net.Http.Formatting; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.AspNetCore.Mvc.RazorPages; | |||
using Microsoft.Extensions.Options; | |||
using WebhookClient.Models; | |||
namespace WebhookClient.Pages | |||
{ | |||
public class RegisterWebhookModel : PageModel | |||
{ | |||
private readonly Settings _settings; | |||
private readonly IHttpClientFactory _httpClientFactory; | |||
[BindProperty] public string Token { get; set; } | |||
public RegisterWebhookModel(IOptions<Settings> settings, IHttpClientFactory httpClientFactory) | |||
{ | |||
_settings = settings.Value; | |||
_httpClientFactory = httpClientFactory; | |||
} | |||
public void OnGet() | |||
{ | |||
Token = _settings.Token; | |||
} | |||
public async Task OnPost() | |||
{ | |||
var protocol = Request.IsHttps ? "https" : "http"; | |||
var selfurl = !string.IsNullOrEmpty(_settings.SelfUrl) ? _settings.SelfUrl : $"{protocol}://{Request.Host}/{Request.PathBase}"; | |||
var granturl = $"{selfurl}check"; | |||
var url = $"{selfurl}webhook"; | |||
var client = _httpClientFactory.CreateClient("GrantClient"); | |||
var payload = new WebhookSubscriptionRequest() | |||
{ | |||
Event = "OrderShipped", | |||
GrantUrl = granturl, | |||
Url = url, | |||
Token = Token | |||
}; | |||
var response = await client.PostAsync<WebhookSubscriptionRequest>(_settings.WebhooksUrl + "/api/v1/webhooks", payload, new JsonMediaTypeFormatter()); | |||
RedirectToPage("Index"); | |||
} | |||
} | |||
} |
@ -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> | |||
@ -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(); | |||
} | |||
} | |||
} |
@ -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(); | |||
} | |||
} |
@ -0,0 +1,29 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
using WebhookClient.Models; | |||
namespace WebhookClient.Services | |||
{ | |||
public class WebhooksClient : IWebhooksClient | |||
{ | |||
public async Task<IEnumerable<WebhookResponse>> LoadWebhooks() | |||
{ | |||
return new[]{ | |||
new WebhookResponse() | |||
{ | |||
Date = DateTime.Now, | |||
DestUrl = "http://aaaaa.me", | |||
Token = "3282832as2" | |||
}, | |||
new WebhookResponse() | |||
{ | |||
Date = DateTime.Now.Subtract(TimeSpan.FromSeconds(392)), | |||
DestUrl = "http://bbbbb.me", | |||
Token = "ds2" | |||
} | |||
}; | |||
} | |||
} | |||
} |
@ -0,0 +1,17 @@ | |||
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; } | |||
} | |||
} |