Moved using statements to globalusing file in webhooks.api
This commit is contained in:
parent
ac9874e44a
commit
2cdb56fb69
@ -1,15 +1,11 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
namespace Webhooks.API.Controllers;
|
||||||
|
|
||||||
namespace Webhooks.API.Controllers
|
public class HomeController : Controller
|
||||||
{
|
{
|
||||||
|
// GET: /<controller>/
|
||||||
public class HomeController : Controller
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
// GET: /<controller>/
|
return new RedirectResult("~/swagger");
|
||||||
public IActionResult Index()
|
|
||||||
{
|
|
||||||
return new RedirectResult("~/swagger");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,29 @@
|
|||||||
using System;
|
namespace Webhooks.API.Controllers;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Webhooks.API.Model;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Controllers
|
public class WebhookSubscriptionRequest : IValidatableObject
|
||||||
{
|
{
|
||||||
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)
|
||||||
{
|
{
|
||||||
public string Url { get; set; }
|
if (!Uri.IsWellFormedUriString(GrantUrl, UriKind.Absolute))
|
||||||
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) });
|
||||||
{
|
|
||||||
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) });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,115 +1,100 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
namespace Webhooks.API.Controllers;
|
||||||
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
|
||||||
{
|
{
|
||||||
[Route("api/v1/[controller]")]
|
private readonly WebhooksContext _dbContext;
|
||||||
[ApiController]
|
private readonly IIdentityService _identityService;
|
||||||
public class WebhooksController : ControllerBase
|
private readonly IGrantUrlTesterService _grantUrlTester;
|
||||||
|
|
||||||
|
public WebhooksController(WebhooksContext dbContext, IIdentityService identityService, IGrantUrlTesterService grantUrlTester)
|
||||||
{
|
{
|
||||||
private readonly WebhooksContext _dbContext;
|
_dbContext = dbContext;
|
||||||
private readonly IIdentityService _identityService;
|
_identityService = identityService;
|
||||||
private readonly IGrantUrlTesterService _grantUrlTester;
|
_grantUrlTester = 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
using System;
|
namespace Webhooks.API.Exceptions;
|
||||||
|
|
||||||
namespace Webhooks.API.Exceptions
|
public class WebhooksDomainException : Exception
|
||||||
{
|
{
|
||||||
public class WebhooksDomainException : Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
namespace Webhooks.API.Infrastructure.ActionResult;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Infrastructure.ActionResult
|
class InternalServerErrorObjectResult : ObjectResult
|
||||||
{
|
{
|
||||||
class InternalServerErrorObjectResult : ObjectResult
|
public InternalServerErrorObjectResult(object error) : base(error)
|
||||||
{
|
{
|
||||||
public InternalServerErrorObjectResult(object error) : base(error)
|
StatusCode = StatusCodes.Status500InternalServerError;
|
||||||
{
|
|
||||||
StatusCode = StatusCodes.Status500InternalServerError;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,29 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
namespace Webhooks.API.Infrastructure;
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Infrastructure
|
public class AuthorizeCheckOperationFilter : IOperationFilter
|
||||||
{
|
{
|
||||||
public class AuthorizeCheckOperationFilter : IOperationFilter
|
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||||
{
|
{
|
||||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
// Check for authorize attribute
|
||||||
|
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
|
||||||
|
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
|
||||||
|
|
||||||
|
if (!hasAuthorize) return;
|
||||||
|
|
||||||
|
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
|
||||||
|
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
|
||||||
|
|
||||||
|
var oAuthScheme = new OpenApiSecurityScheme
|
||||||
{
|
{
|
||||||
// Check for authorize attribute
|
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
|
||||||
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
|
};
|
||||||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
|
|
||||||
|
|
||||||
if (!hasAuthorize) return;
|
operation.Security = new List<OpenApiSecurityRequirement>
|
||||||
|
|
||||||
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
|
|
||||||
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
|
|
||||||
|
|
||||||
var oAuthScheme = new OpenApiSecurityScheme
|
|
||||||
{
|
{
|
||||||
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
|
new OpenApiSecurityRequirement
|
||||||
};
|
|
||||||
|
|
||||||
operation.Security = new List<OpenApiSecurityRequirement>
|
|
||||||
{
|
{
|
||||||
new OpenApiSecurityRequirement
|
[ oAuthScheme ] = new [] { "webhooksapi" }
|
||||||
{
|
}
|
||||||
[ oAuthScheme ] = new [] { "webhooksapi" }
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,69 +1,58 @@
|
|||||||
using Microsoft.AspNetCore.Hosting;
|
namespace Webhooks.API.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Net;
|
|
||||||
using Webhooks.API.Exceptions;
|
|
||||||
using Webhooks.API.Infrastructure.ActionResult;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Infrastructure
|
public class HttpGlobalExceptionFilter : IExceptionFilter
|
||||||
{
|
{
|
||||||
public class HttpGlobalExceptionFilter : IExceptionFilter
|
private readonly IWebHostEnvironment env;
|
||||||
|
private readonly ILogger<HttpGlobalExceptionFilter> logger;
|
||||||
|
|
||||||
|
public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
|
||||||
{
|
{
|
||||||
private readonly IWebHostEnvironment env;
|
this.env = env;
|
||||||
private readonly ILogger<HttpGlobalExceptionFilter> logger;
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
|
public void OnException(ExceptionContext context)
|
||||||
|
{
|
||||||
|
logger.LogError(new EventId(context.Exception.HResult),
|
||||||
|
context.Exception,
|
||||||
|
context.Exception.Message);
|
||||||
|
|
||||||
|
if (context.Exception.GetType() == typeof(WebhooksDomainException))
|
||||||
{
|
{
|
||||||
this.env = env;
|
var problemDetails = new ValidationProblemDetails()
|
||||||
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,
|
||||||
Instance = context.HttpContext.Request.Path,
|
Detail = "Please refer to the errors property for additional details."
|
||||||
Status = StatusCodes.Status400BadRequest,
|
};
|
||||||
Detail = "Please refer to the errors property for additional details."
|
|
||||||
};
|
|
||||||
|
|
||||||
problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() });
|
problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() });
|
||||||
|
|
||||||
context.Result = new BadRequestObjectResult(problemDetails);
|
context.Result = new BadRequestObjectResult(problemDetails);
|
||||||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
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;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private class JsonErrorResponse
|
|
||||||
{
|
{
|
||||||
public string[] Messages { get; set; }
|
var json = new JsonErrorResponse
|
||||||
|
{
|
||||||
|
Messages = new[] { "An error ocurred." }
|
||||||
|
};
|
||||||
|
|
||||||
public object DeveloperMeesage { get; set; }
|
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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,21 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
namespace Webhooks.API.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
|
||||||
using Webhooks.API.Model;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Infrastructure
|
public class WebhooksContext : DbContext
|
||||||
{
|
{
|
||||||
public class WebhooksContext : DbContext
|
|
||||||
{
|
|
||||||
|
|
||||||
public WebhooksContext(DbContextOptions<WebhooksContext> options) : base(options)
|
public WebhooksContext(DbContextOptions<WebhooksContext> options) : base(options)
|
||||||
{
|
{
|
||||||
}
|
|
||||||
public DbSet<WebhookSubscription> Subscriptions { get; set; }
|
|
||||||
}
|
}
|
||||||
|
public DbSet<WebhookSubscription> Subscriptions { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class WebhooksContextDesignFactory : IDesignTimeDbContextFactory<WebhooksContext>
|
public class WebhooksContextDesignFactory : IDesignTimeDbContextFactory<WebhooksContext>
|
||||||
|
{
|
||||||
|
public WebhooksContext CreateDbContext(string[] args)
|
||||||
{
|
{
|
||||||
public WebhooksContext CreateDbContext(string[] args)
|
var optionsBuilder = new DbContextOptionsBuilder<WebhooksContext>()
|
||||||
{
|
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;Integrated Security=true");
|
||||||
var optionsBuilder = new DbContextOptionsBuilder<WebhooksContext>()
|
|
||||||
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;Integrated Security=true");
|
|
||||||
|
|
||||||
return new WebhooksContext(optionsBuilder.Options);
|
return new WebhooksContext(optionsBuilder.Options);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,26 @@
|
|||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
namespace Webhooks.API.IntegrationEvents;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Webhooks.API.IntegrationEvents
|
public record OrderStatusChangedToPaidIntegrationEvent : IntegrationEvent
|
||||||
{
|
{
|
||||||
public record OrderStatusChangedToPaidIntegrationEvent : IntegrationEvent
|
public int OrderId { get; }
|
||||||
|
public IEnumerable<OrderStockItem> OrderStockItems { get; }
|
||||||
|
|
||||||
|
public OrderStatusChangedToPaidIntegrationEvent(int orderId,
|
||||||
|
IEnumerable<OrderStockItem> orderStockItems)
|
||||||
{
|
{
|
||||||
public int OrderId { get; }
|
OrderId = orderId;
|
||||||
public IEnumerable<OrderStockItem> OrderStockItems { get; }
|
OrderStockItems = orderStockItems;
|
||||||
|
}
|
||||||
public OrderStatusChangedToPaidIntegrationEvent(int orderId,
|
}
|
||||||
IEnumerable<OrderStockItem> orderStockItems)
|
|
||||||
{
|
public record OrderStockItem
|
||||||
OrderId = orderId;
|
{
|
||||||
OrderStockItems = orderStockItems;
|
public int ProductId { get; }
|
||||||
}
|
public int Units { get; }
|
||||||
}
|
|
||||||
|
public OrderStockItem(int productId, int units)
|
||||||
public record OrderStockItem
|
{
|
||||||
{
|
ProductId = productId;
|
||||||
public int ProductId { get; }
|
Units = units;
|
||||||
public int Units { get; }
|
|
||||||
|
|
||||||
public OrderStockItem(int productId, int units)
|
|
||||||
{
|
|
||||||
ProductId = productId;
|
|
||||||
Units = units;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,22 @@
|
|||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
namespace Webhooks.API.IntegrationEvents;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Webhooks.API.Model;
|
|
||||||
using Webhooks.API.Services;
|
|
||||||
|
|
||||||
namespace Webhooks.API.IntegrationEvents
|
public class OrderStatusChangedToPaidIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToPaidIntegrationEvent>
|
||||||
{
|
{
|
||||||
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)
|
||||||
{
|
{
|
||||||
private readonly IWebhooksRetriever _retriever;
|
_retriever = retriever;
|
||||||
private readonly IWebhooksSender _sender;
|
_sender = sender;
|
||||||
private readonly ILogger _logger;
|
_logger = logger;
|
||||||
public OrderStatusChangedToPaidIntegrationEventHandler(IWebhooksRetriever retriever, IWebhooksSender sender, ILogger<OrderStatusChangedToShippedIntegrationEventHandler> logger)
|
}
|
||||||
{
|
|
||||||
_retriever = retriever;
|
|
||||||
_sender = sender;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event)
|
public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event)
|
||||||
{
|
{
|
||||||
var subscriptions = await _retriever.GetSubscriptionsOfType(WebhookType.OrderPaid);
|
var subscriptions = await _retriever.GetSubscriptionsOfType(WebhookType.OrderPaid);
|
||||||
_logger.LogInformation("Received OrderStatusChangedToShippedIntegrationEvent and got {SubscriptionsCount} subscriptions to process", subscriptions.Count());
|
_logger.LogInformation("Received OrderStatusChangedToShippedIntegrationEvent and got {SubscriptionsCount} subscriptions to process", subscriptions.Count());
|
||||||
var whook = new WebhookData(WebhookType.OrderPaid, @event);
|
var whook = new WebhookData(WebhookType.OrderPaid, @event);
|
||||||
await _sender.SendAll(subscriptions, whook);
|
await _sender.SendAll(subscriptions, whook);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
namespace Webhooks.API.IntegrationEvents;
|
||||||
|
|
||||||
namespace Webhooks.API.IntegrationEvents
|
public record OrderStatusChangedToShippedIntegrationEvent : IntegrationEvent
|
||||||
{
|
{
|
||||||
public record OrderStatusChangedToShippedIntegrationEvent : IntegrationEvent
|
public int OrderId { get; private init; }
|
||||||
{
|
public string OrderStatus { get; private init; }
|
||||||
public int OrderId { get; private init; }
|
public string BuyerName { get; private init; }
|
||||||
public string OrderStatus { get; private init; }
|
|
||||||
public string BuyerName { get; private init; }
|
|
||||||
|
|
||||||
public OrderStatusChangedToShippedIntegrationEvent(int orderId, string orderStatus, string buyerName)
|
public OrderStatusChangedToShippedIntegrationEvent(int orderId, string orderStatus, string buyerName)
|
||||||
{
|
{
|
||||||
OrderId = orderId;
|
OrderId = orderId;
|
||||||
OrderStatus = orderStatus;
|
OrderStatus = orderStatus;
|
||||||
BuyerName = buyerName;
|
BuyerName = buyerName;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,22 @@
|
|||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
namespace Webhooks.API.IntegrationEvents;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Webhooks.API.Model;
|
|
||||||
using Webhooks.API.Services;
|
|
||||||
|
|
||||||
namespace Webhooks.API.IntegrationEvents
|
public class OrderStatusChangedToShippedIntegrationEventHandler : IIntegrationEventHandler<OrderStatusChangedToShippedIntegrationEvent>
|
||||||
{
|
{
|
||||||
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)
|
||||||
{
|
{
|
||||||
private readonly IWebhooksRetriever _retriever;
|
_retriever = retriever;
|
||||||
private readonly IWebhooksSender _sender;
|
_sender = sender;
|
||||||
private readonly ILogger _logger;
|
_logger = logger;
|
||||||
public OrderStatusChangedToShippedIntegrationEventHandler(IWebhooksRetriever retriever, IWebhooksSender sender, ILogger<OrderStatusChangedToShippedIntegrationEventHandler> logger)
|
}
|
||||||
{
|
|
||||||
_retriever = retriever;
|
|
||||||
_sender = sender;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Handle(OrderStatusChangedToShippedIntegrationEvent @event)
|
public async Task Handle(OrderStatusChangedToShippedIntegrationEvent @event)
|
||||||
{
|
{
|
||||||
var subscriptions = await _retriever.GetSubscriptionsOfType(WebhookType.OrderShipped);
|
var subscriptions = await _retriever.GetSubscriptionsOfType(WebhookType.OrderShipped);
|
||||||
_logger.LogInformation("Received OrderStatusChangedToShippedIntegrationEvent and got {SubscriptionCount} subscriptions to process", subscriptions.Count());
|
_logger.LogInformation("Received OrderStatusChangedToShippedIntegrationEvent and got {SubscriptionCount} subscriptions to process", subscriptions.Count());
|
||||||
var whook = new WebhookData(WebhookType.OrderShipped, @event);
|
var whook = new WebhookData(WebhookType.OrderShipped, @event);
|
||||||
await _sender.SendAll(subscriptions, whook);
|
await _sender.SendAll(subscriptions, whook);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
namespace Webhooks.API.IntegrationEvents;
|
||||||
|
|
||||||
namespace Webhooks.API.IntegrationEvents
|
public record ProductPriceChangedIntegrationEvent : IntegrationEvent
|
||||||
{
|
{
|
||||||
public record ProductPriceChangedIntegrationEvent : IntegrationEvent
|
public int ProductId { get; private init; }
|
||||||
|
|
||||||
|
public decimal NewPrice { get; private init; }
|
||||||
|
|
||||||
|
public decimal OldPrice { get; private init; }
|
||||||
|
|
||||||
|
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice)
|
||||||
{
|
{
|
||||||
public int ProductId { get; private init; }
|
ProductId = productId;
|
||||||
|
NewPrice = newPrice;
|
||||||
public decimal NewPrice { get; private init; }
|
OldPrice = oldPrice;
|
||||||
|
|
||||||
public decimal OldPrice { get; private init; }
|
|
||||||
|
|
||||||
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice)
|
|
||||||
{
|
|
||||||
ProductId = productId;
|
|
||||||
NewPrice = newPrice;
|
|
||||||
OldPrice = oldPrice;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
namespace Webhooks.API.IntegrationEvents;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Webhooks.API.IntegrationEvents
|
public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>
|
||||||
{
|
{
|
||||||
public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>
|
public async Task Handle(ProductPriceChangedIntegrationEvent @event)
|
||||||
{
|
{
|
||||||
public async Task Handle(ProductPriceChangedIntegrationEvent @event)
|
int i = 0;
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
using System;
|
namespace Webhooks.API.Model;
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Model
|
public class WebhookData
|
||||||
{
|
{
|
||||||
public class WebhookData
|
public DateTime When { get; }
|
||||||
|
|
||||||
|
public string Payload { get; }
|
||||||
|
|
||||||
|
public string Type { get; }
|
||||||
|
|
||||||
|
public WebhookData(WebhookType hookType, object data)
|
||||||
{
|
{
|
||||||
public DateTime When { get; }
|
When = DateTime.UtcNow;
|
||||||
|
Type = hookType.ToString();
|
||||||
public string Payload { get; }
|
Payload = JsonSerializer.Serialize(data);
|
||||||
|
|
||||||
public string Type { get; }
|
|
||||||
|
|
||||||
public WebhookData(WebhookType hookType, object data)
|
|
||||||
{
|
|
||||||
When = DateTime.UtcNow;
|
|
||||||
Type = hookType.ToString();
|
|
||||||
Payload = JsonSerializer.Serialize(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
using System;
|
namespace Webhooks.API.Model;
|
||||||
|
|
||||||
namespace Webhooks.API.Model
|
public class WebhookSubscription
|
||||||
{
|
{
|
||||||
public class WebhookSubscription
|
public int Id { get; set; }
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public WebhookType Type { get; set; }
|
public WebhookType Type { get; set; }
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
public string DestUrl { get; set; }
|
public string DestUrl { get; set; }
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
namespace Webhooks.API.Model
|
namespace Webhooks.API.Model;
|
||||||
|
|
||||||
|
public enum WebhookType
|
||||||
{
|
{
|
||||||
public enum WebhookType
|
CatalogItemPriceChange = 1,
|
||||||
{
|
OrderShipped = 2,
|
||||||
CatalogItemPriceChange = 1,
|
OrderPaid = 3
|
||||||
OrderShipped = 2,
|
|
||||||
OrderPaid = 3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
using Microsoft.AspNetCore;
|
CreateWebHostBuilder(args).Build()
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Webhooks.API;
|
|
||||||
using Webhooks.API.Infrastructure;
|
|
||||||
|
|
||||||
CreateWebHostBuilder(args).Build()
|
|
||||||
.MigrateDbContext<WebhooksContext>((_, __) => { })
|
.MigrateDbContext<WebhooksContext>((_, __) => { })
|
||||||
.Run();
|
.Run();
|
||||||
|
|
||||||
|
@ -1,57 +1,50 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
namespace Webhooks.API.Services;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Services
|
class GrantUrlTesterService : IGrantUrlTesterService
|
||||||
{
|
{
|
||||||
class GrantUrlTesterService : IGrantUrlTesterService
|
private readonly IHttpClientFactory _clientFactory;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
public GrantUrlTesterService(IHttpClientFactory factory, ILogger<IGrantUrlTesterService> logger)
|
||||||
{
|
{
|
||||||
private readonly IHttpClientFactory _clientFactory;
|
_clientFactory = factory;
|
||||||
private readonly ILogger _logger;
|
_logger = logger;
|
||||||
public GrantUrlTesterService(IHttpClientFactory factory, ILogger<IGrantUrlTesterService> logger)
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TestGrantUrl(string urlHook, string url, string token)
|
||||||
|
{
|
||||||
|
if (!CheckSameOrigin(urlHook, url))
|
||||||
{
|
{
|
||||||
_clientFactory = factory;
|
_logger.LogWarning("Url of the hook ({UrlHook} and the grant url ({Url} do not belong to same origin)", urlHook, url);
|
||||||
_logger = logger;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> TestGrantUrl(string urlHook, string url, string token)
|
|
||||||
|
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}\"", url, token ?? string.Empty);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!CheckSameOrigin(urlHook, url))
|
var response = await client.SendAsync(msg);
|
||||||
{
|
var tokenReceived = response.Headers.TryGetValues("X-eshop-whtoken", out var tokenValues) ? tokenValues.FirstOrDefault() : null;
|
||||||
_logger.LogWarning("Url of the hook ({UrlHook} and the grant url ({Url} do not belong to same origin)", urlHook, url);
|
var tokenExpected = string.IsNullOrWhiteSpace(token) ? null : token;
|
||||||
return false;
|
_logger.LogInformation("Response code is {StatusCode} for url {Url} and token in header was {TokenReceived} (expected token was {TokenExpected})", response.StatusCode, url, tokenReceived, tokenExpected);
|
||||||
}
|
return response.IsSuccessStatusCode && tokenReceived == tokenExpected;
|
||||||
|
|
||||||
|
|
||||||
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}\"", url, 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 {StatusCode} for url {Url} and token in header was {TokenReceived} (expected token was {TokenExpected})", response.StatusCode, url, tokenReceived, tokenExpected);
|
|
||||||
return response.IsSuccessStatusCode && tokenReceived == tokenExpected;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Exception {TypeName} when sending OPTIONS request. Url can't be granted.", ex.GetType().Name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
private bool CheckSameOrigin(string urlHook, string url)
|
|
||||||
{
|
{
|
||||||
var firstUrl = new Uri(urlHook, UriKind.Absolute);
|
_logger.LogWarning("Exception {TypeName} when sending OPTIONS request. Url can't be granted.", ex.GetType().Name);
|
||||||
var secondUrl = new Uri(url, UriKind.Absolute);
|
return false;
|
||||||
|
|
||||||
return firstUrl.Scheme == secondUrl.Scheme &&
|
|
||||||
firstUrl.Port == secondUrl.Port &&
|
|
||||||
firstUrl.Host == firstUrl.Host;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
namespace Webhooks.API.Services;
|
||||||
|
|
||||||
namespace Webhooks.API.Services
|
public interface IGrantUrlTesterService
|
||||||
{
|
{
|
||||||
public interface IGrantUrlTesterService
|
Task<bool> TestGrantUrl(string urlHook, string url, string token);
|
||||||
{
|
|
||||||
Task<bool> TestGrantUrl(string urlHook, string url, string token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
namespace Webhooks.API.Services
|
namespace Webhooks.API.Services;
|
||||||
|
|
||||||
|
public interface IIdentityService
|
||||||
{
|
{
|
||||||
public interface IIdentityService
|
string GetUserIdentity();
|
||||||
{
|
|
||||||
string GetUserIdentity();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
namespace Webhooks.API.Services;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Webhooks.API.Model;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Services
|
public interface IWebhooksRetriever
|
||||||
{
|
{
|
||||||
public interface IWebhooksRetriever
|
|
||||||
{
|
|
||||||
|
|
||||||
Task<IEnumerable<WebhookSubscription>> GetSubscriptionsOfType(WebhookType type);
|
Task<IEnumerable<WebhookSubscription>> GetSubscriptionsOfType(WebhookType type);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
namespace Webhooks.API.Services;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Webhooks.API.Model;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Services
|
public interface IWebhooksSender
|
||||||
{
|
{
|
||||||
public interface IWebhooksSender
|
Task SendAll(IEnumerable<WebhookSubscription> receivers, WebhookData data);
|
||||||
{
|
|
||||||
Task SendAll(IEnumerable<WebhookSubscription> receivers, WebhookData data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
|
namespace Webhooks.API.Services;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Services
|
public class IdentityService : IIdentityService
|
||||||
{
|
{
|
||||||
public class IdentityService : IIdentityService
|
private IHttpContextAccessor _context;
|
||||||
|
|
||||||
|
public IdentityService(IHttpContextAccessor context)
|
||||||
{
|
{
|
||||||
private IHttpContextAccessor _context;
|
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
public IdentityService(IHttpContextAccessor context)
|
public string GetUserIdentity()
|
||||||
{
|
{
|
||||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
return _context.HttpContext.User.FindFirst("sub").Value;
|
||||||
}
|
|
||||||
|
|
||||||
public string GetUserIdentity()
|
|
||||||
{
|
|
||||||
return _context.HttpContext.User.FindFirst("sub").Value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,15 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
namespace Webhooks.API.Services;
|
||||||
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
|
||||||
{
|
{
|
||||||
public class WebhooksRetriever : IWebhooksRetriever
|
private readonly WebhooksContext _db;
|
||||||
|
public WebhooksRetriever(WebhooksContext db)
|
||||||
{
|
{
|
||||||
private readonly WebhooksContext _db;
|
_db = db;
|
||||||
public WebhooksRetriever(WebhooksContext db)
|
}
|
||||||
{
|
public async Task<IEnumerable<WebhookSubscription>> GetSubscriptionsOfType(WebhookType type)
|
||||||
_db = db;
|
{
|
||||||
}
|
var data = await _db.Subscriptions.Where(s => s.Type == type).ToListAsync();
|
||||||
public async Task<IEnumerable<WebhookSubscription>> GetSubscriptionsOfType(WebhookType type)
|
return data;
|
||||||
{
|
|
||||||
var data = await _db.Subscriptions.Where(s => s.Type == type).ToListAsync();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,38 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
namespace Webhooks.API.Services;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Webhooks.API.Model;
|
|
||||||
|
|
||||||
namespace Webhooks.API.Services
|
public class WebhooksSender : IWebhooksSender
|
||||||
{
|
{
|
||||||
public class WebhooksSender : IWebhooksSender
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
public WebhooksSender(IHttpClientFactory httpClientFactory, ILogger<WebhooksSender> logger)
|
||||||
{
|
{
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
private readonly ILogger _logger;
|
_logger = 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 = JsonSerializer.Serialize(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 {DestUrl} of type {Type}", subs.Type.ToString(), subs.Type.ToString());
|
|
||||||
return client.SendAsync(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SendAll(IEnumerable<WebhookSubscription> receivers, WebhookData data)
|
||||||
|
{
|
||||||
|
var client = _httpClientFactory.CreateClient();
|
||||||
|
var json = JsonSerializer.Serialize(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 {DestUrl} of type {Type}", subs.Type.ToString(), subs.Type.ToString());
|
||||||
|
return client.SendAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,350 +1,318 @@
|
|||||||
using Autofac;
|
namespace Webhooks.API;
|
||||||
using Autofac.Extensions.DependencyInjection;
|
|
||||||
using Devspaces.Support;
|
|
||||||
using HealthChecks.UI.Client;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.Azure.ServiceBus;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
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 Microsoft.OpenApi.Models;
|
|
||||||
using RabbitMQ.Client;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data.Common;
|
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading;
|
|
||||||
using Webhooks.API.Infrastructure;
|
|
||||||
using Webhooks.API.IntegrationEvents;
|
|
||||||
using Webhooks.API.Services;
|
|
||||||
|
|
||||||
namespace Webhooks.API
|
public class Startup
|
||||||
{
|
{
|
||||||
public class Startup
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
public IConfiguration Configuration { get; }
|
Configuration = configuration;
|
||||||
|
|
||||||
public Startup(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
Configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public IServiceProvider ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services
|
|
||||||
.AddAppInsight(Configuration)
|
|
||||||
.AddCustomRouting(Configuration)
|
|
||||||
.AddCustomDbContext(Configuration)
|
|
||||||
.AddSwagger(Configuration)
|
|
||||||
.AddCustomHealthCheck(Configuration)
|
|
||||||
.AddDevspaces()
|
|
||||||
.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, ILoggerFactory loggerFactory)
|
|
||||||
{
|
|
||||||
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
|
|
||||||
|
|
||||||
var pathBase = Configuration["PATH_BASE"];
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(pathBase))
|
|
||||||
{
|
|
||||||
loggerFactory.CreateLogger("init").LogDebug("Using PATH BASE '{PathBase}'", pathBase);
|
|
||||||
app.UsePathBase(pathBase);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.UseRouting();
|
|
||||||
app.UseCors("CorsPolicy");
|
|
||||||
ConfigureAuth(app);
|
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
|
||||||
{
|
|
||||||
endpoints.MapDefaultControllerRoute();
|
|
||||||
endpoints.MapControllers();
|
|
||||||
endpoints.MapHealthChecks("/hc", new HealthCheckOptions()
|
|
||||||
{
|
|
||||||
Predicate = _ => true,
|
|
||||||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
|
||||||
});
|
|
||||||
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
|
|
||||||
{
|
|
||||||
Predicate = r => r.Name.Contains("self")
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
app.UseAuthentication();
|
|
||||||
app.UseAuthorization();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 IServiceProvider ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddAppInsight(this IServiceCollection services, IConfiguration configuration)
|
services
|
||||||
{
|
.AddAppInsight(Configuration)
|
||||||
services.AddApplicationInsightsTelemetry(configuration);
|
.AddCustomRouting(Configuration)
|
||||||
services.AddApplicationInsightsKubernetesEnricher();
|
.AddCustomDbContext(Configuration)
|
||||||
|
.AddSwagger(Configuration)
|
||||||
|
.AddCustomHealthCheck(Configuration)
|
||||||
|
.AddDevspaces()
|
||||||
|
.AddHttpClientServices(Configuration)
|
||||||
|
.AddIntegrationServices(Configuration)
|
||||||
|
.AddEventBus(Configuration)
|
||||||
|
.AddCustomAuthentication(Configuration)
|
||||||
|
.AddSingleton<IHttpContextAccessor, HttpContextAccessor>()
|
||||||
|
.AddTransient<IIdentityService, IdentityService>()
|
||||||
|
.AddTransient<IGrantUrlTesterService, GrantUrlTesterService>()
|
||||||
|
.AddTransient<IWebhooksRetriever, WebhooksRetriever>()
|
||||||
|
.AddTransient<IWebhooksSender, WebhooksSender>();
|
||||||
|
|
||||||
return services;
|
var container = new ContainerBuilder();
|
||||||
|
container.Populate(services);
|
||||||
|
return new AutofacServiceProvider(container.Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
|
||||||
|
|
||||||
|
var pathBase = Configuration["PATH_BASE"];
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pathBase))
|
||||||
|
{
|
||||||
|
loggerFactory.CreateLogger("init").LogDebug("Using PATH BASE '{PathBase}'", pathBase);
|
||||||
|
app.UsePathBase(pathBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddCustomRouting(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
app.UseCors("CorsPolicy");
|
||||||
|
ConfigureAuth(app);
|
||||||
|
|
||||||
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
services.AddControllers(options =>
|
endpoints.MapDefaultControllerRoute();
|
||||||
|
endpoints.MapControllers();
|
||||||
|
endpoints.MapHealthChecks("/hc", new HealthCheckOptions()
|
||||||
{
|
{
|
||||||
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
|
Predicate = _ => true,
|
||||||
|
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
||||||
|
});
|
||||||
|
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
|
||||||
|
{
|
||||||
|
Predicate = r => r.Name.Contains("self")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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");
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddCors(options =>
|
ConfigureEventBus(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
services.AddApplicationInsightsKubernetesEnricher();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddCustomRouting(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddControllers(options =>
|
||||||
|
{
|
||||||
|
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
|
||||||
|
});
|
||||||
|
|
||||||
|
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.AddEntityFrameworkSqlServer()
|
||||||
|
.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: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddSwaggerGen(options =>
|
||||||
|
{
|
||||||
|
options.DescribeAllEnumsAsStrings();
|
||||||
|
options.SwaggerDoc("v1", new OpenApiInfo
|
||||||
{
|
{
|
||||||
options.AddPolicy("CorsPolicy",
|
Title = "eShopOnContainers - Webhooks HTTP API",
|
||||||
builder => builder
|
Version = "v1",
|
||||||
.SetIsOriginAllowed((host) => true)
|
Description = "The Webhooks Microservice HTTP API. This is a simple webhooks CRUD registration entrypoint"
|
||||||
.AllowAnyMethod()
|
|
||||||
.AllowAnyHeader()
|
|
||||||
.AllowCredentials());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return services;
|
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
services.AddEntityFrameworkSqlServer()
|
|
||||||
.AddDbContext<WebhooksContext>(options =>
|
|
||||||
{
|
{
|
||||||
options.UseSqlServer(configuration["ConnectionString"],
|
Type = SecuritySchemeType.OAuth2,
|
||||||
sqlServerOptionsAction: sqlOptions =>
|
Flows = new OpenApiOAuthFlows()
|
||||||
{
|
|
||||||
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: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
services.AddSwaggerGen(options =>
|
|
||||||
{
|
|
||||||
options.DescribeAllEnumsAsStrings();
|
|
||||||
options.SwaggerDoc("v1", new OpenApiInfo
|
|
||||||
{
|
{
|
||||||
Title = "eShopOnContainers - Webhooks HTTP API",
|
Implicit = new OpenApiOAuthFlow()
|
||||||
Version = "v1",
|
|
||||||
Description = "The Webhooks Microservice HTTP API. This is a simple webhooks CRUD registration entrypoint"
|
|
||||||
});
|
|
||||||
|
|
||||||
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
Type = SecuritySchemeType.OAuth2,
|
|
||||||
Flows = new OpenApiOAuthFlows()
|
|
||||||
{
|
{
|
||||||
Implicit = new OpenApiOAuthFlow()
|
AuthorizationUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"),
|
||||||
|
TokenUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"),
|
||||||
|
Scopes = new Dictionary<string, string>()
|
||||||
{
|
{
|
||||||
AuthorizationUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"),
|
{ "webhooks", "Webhooks API" }
|
||||||
TokenUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"),
|
|
||||||
Scopes = new Dictionary<string, string>()
|
|
||||||
{
|
|
||||||
{ "webhooks", "Webhooks API" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
options.OperationFilter<AuthorizeCheckOperationFilter>();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return services;
|
options.OperationFilter<AuthorizeCheckOperationFilter>();
|
||||||
}
|
});
|
||||||
public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
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,
|
return services;
|
||||||
eventBusSubcriptionsManager, iLifetimeScope);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>
|
|
||||||
{
|
|
||||||
var subscriptionClientName = configuration["SubscriptionClientName"];
|
|
||||||
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))
|
|
||||||
.AddDevspacesSupport();
|
|
||||||
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 serviceBusConnection = new ServiceBusConnectionStringBuilder(configuration["EventBusConnection"]);
|
|
||||||
var subscriptionClientName = configuration["SubscriptionClientName"];
|
|
||||||
return new DefaultServiceBusPersisterConnection(serviceBusConnection, subscriptionClientName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
|
|
||||||
{
|
|
||||||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
|
|
||||||
|
|
||||||
var factory = new ConnectionFactory()
|
|
||||||
{
|
|
||||||
HostName = configuration["EventBusConnection"],
|
|
||||||
DispatchConsumersAsync = true
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
|
||||||
|
{
|
||||||
|
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
|
||||||
|
{
|
||||||
|
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
|
||||||
|
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
|
||||||
|
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
|
||||||
|
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
|
||||||
|
|
||||||
|
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
|
||||||
|
eventBusSubcriptionsManager, iLifetimeScope);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>
|
||||||
|
{
|
||||||
|
var subscriptionClientName = configuration["SubscriptionClientName"];
|
||||||
|
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))
|
||||||
|
.AddDevspacesSupport();
|
||||||
|
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 serviceBusConnection = new ServiceBusConnectionStringBuilder(configuration["EventBusConnection"]);
|
||||||
|
var subscriptionClientName = configuration["SubscriptionClientName"];
|
||||||
|
return new DefaultServiceBusPersisterConnection(serviceBusConnection, subscriptionClientName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
|
||||||
|
{
|
||||||
|
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
|
||||||
|
|
||||||
|
var factory = new ConnectionFactory()
|
||||||
|
{
|
||||||
|
HostName = configuration["EventBusConnection"],
|
||||||
|
DispatchConsumersAsync = true
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user