V14: Webhook Management API (#15147)
* Add webhook to management api * Update webhook controllers * Add ByKey webhook controller * Fix typo * Fix typo * Update multiple webhooks * Update using * Remove duplicate constant after merge * Fix typo in file name * Update casing of IWebhookService * Fix typo * Use Webhook entity type * Fix ambiguous reference * Update webhook mapping * Update after change of CreatedAtAction * Use CreatedAtId instead * Update src/Umbraco.Cms.Api.Management/Controllers/Webhook/ByKeyWebhookController.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Cms.Api.Management/Controllers/Webhook/ByKeyWebhookController.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Cms.Api.Management/ViewModels/Webhook/CreateWebhookRequestModel.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Cms.Api.Management/Controllers/Webhook/DeleteWebhookController.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Cms.Api.Management/Controllers/Webhook/CreateWebhookController.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Add Guid to WebhookResponseModel * Cleanup * Add Auth * Move webhook logic from backoffice to management api * Add mapping --------- Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
0f6705795a
commit
f47830b165
@@ -0,0 +1,40 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Webhook;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ByKeyWebhookController : WebhookControllerBase
|
||||
{
|
||||
private readonly IWebhookService _webhookService;
|
||||
private readonly IWebhookPresentationFactory _webhookPresentationFactory;
|
||||
|
||||
public ByKeyWebhookController(IWebhookService webhookService, IWebhookPresentationFactory webhookPresentationFactory)
|
||||
{
|
||||
_webhookService = webhookService;
|
||||
_webhookPresentationFactory = webhookPresentationFactory;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(WebhookResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ByKey(Guid id)
|
||||
{
|
||||
IWebhook? webhook = await _webhookService.GetAsync(id);
|
||||
if (webhook is null)
|
||||
{
|
||||
return WebhookOperationStatusResult(WebhookOperationStatus.NotFound);
|
||||
}
|
||||
|
||||
WebhookResponseModel model = _webhookPresentationFactory.CreateResponseModel(webhook);
|
||||
return Ok(model);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Webhook;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public class CreateWebhookController : WebhookControllerBase
|
||||
{
|
||||
private readonly IWebhookService _webhookService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public CreateWebhookController(
|
||||
IWebhookService webhookService, IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_webhookService = webhookService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Create(CreateWebhookRequestModel createWebhookRequestModel)
|
||||
{
|
||||
IWebhook created = _umbracoMapper.Map<IWebhook>(createWebhookRequestModel)!;
|
||||
|
||||
Attempt<IWebhook, WebhookOperationStatus> result = await _webhookService.CreateAsync(created);
|
||||
|
||||
return result.Success
|
||||
? CreatedAtId<ByKeyWebhookController>(controller => nameof(controller.ByKey), result.Result!.Key)
|
||||
: WebhookOperationStatusResult(result.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Webhook;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public class DeleteWebhookController : WebhookControllerBase
|
||||
{
|
||||
private readonly IWebhookService _webhookService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
public DeleteWebhookController(IWebhookService webhookService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_webhookService = webhookService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[HttpDelete($"{{{nameof(id)}}}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Delete(Guid id)
|
||||
{
|
||||
Attempt<IWebhook?, WebhookOperationStatus> result = await _webhookService.DeleteAsync(id);
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
: WebhookOperationStatusResult(result.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Webhook.Item;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Webhook.Item;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ItemsWebhookEntityController : WebhookEntityControllerBase
|
||||
{
|
||||
private readonly IWebhookService _webhookService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
|
||||
public ItemsWebhookEntityController(IWebhookService webhookService, IUmbracoMapper mapper)
|
||||
{
|
||||
_webhookService = webhookService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet("item")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<WebhookItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult> Items([FromQuery(Name = "ids")] HashSet<Guid> ids)
|
||||
{
|
||||
IEnumerable<IWebhook?> webhooks = await _webhookService.GetMultipleAsync(ids);
|
||||
List<WebhookItemResponseModel> entityResponseModels = _mapper.MapEnumerable<IWebhook?, WebhookItemResponseModel>(webhooks);
|
||||
return Ok(entityResponseModels);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Webhook.Item;
|
||||
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Webhook}")]
|
||||
[ApiExplorerSettings(GroupName = "Webhook")]
|
||||
[Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public class WebhookEntityControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Core.Webhooks;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Webhook;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public class UpdateWebhookController : WebhookControllerBase
|
||||
{
|
||||
private readonly IWebhookService _webhookService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public UpdateWebhookController(
|
||||
IWebhookService webhookService,
|
||||
IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_webhookService = webhookService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateWebhookRequestModel updateWebhookRequestModel)
|
||||
{
|
||||
IWebhook? current = await _webhookService.GetAsync(id);
|
||||
if (current is null)
|
||||
{
|
||||
return WebhookNotFound();
|
||||
}
|
||||
|
||||
IWebhook updated = _umbracoMapper.Map(updateWebhookRequestModel, current);
|
||||
|
||||
Attempt<IWebhook, WebhookOperationStatus> result = await _webhookService.UpdateAsync(updated);
|
||||
|
||||
return result.Success
|
||||
? Ok()
|
||||
: WebhookOperationStatusResult(result.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.Builders;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Webhook;
|
||||
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute("webhook")]
|
||||
[ApiExplorerSettings(GroupName = "Webhook")]
|
||||
public abstract class WebhookControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
protected IActionResult WebhookOperationStatusResult(WebhookOperationStatus status) =>
|
||||
status switch
|
||||
{
|
||||
WebhookOperationStatus.NotFound => WebhookNotFound(),
|
||||
WebhookOperationStatus.CancelledByNotification => BadRequest(new ProblemDetailsBuilder()
|
||||
.WithTitle("Cancelled by notification")
|
||||
.WithDetail("A notification handler prevented the webhook operation.")
|
||||
.Build()),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, new ProblemDetailsBuilder()
|
||||
.WithTitle("Unknown webhook operation status.")
|
||||
.Build()),
|
||||
};
|
||||
|
||||
protected IActionResult WebhookNotFound() => NotFound(new ProblemDetailsBuilder()
|
||||
.WithTitle("The webhook could not be found")
|
||||
.Build());
|
||||
}
|
||||
@@ -98,6 +98,7 @@ internal static class BackOfficeAuthPolicyBuilderExtensions
|
||||
AddPolicy(AuthorizationPolicies.TreeAccessScripts, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
|
||||
AddPolicy(AuthorizationPolicies.TreeAccessStylesheets, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
|
||||
AddPolicy(AuthorizationPolicies.TreeAccessTemplates, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
|
||||
AddPolicy(AuthorizationPolicies.TreeAccessWebhooks, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings);
|
||||
|
||||
// Contextual permissions
|
||||
// TODO: Rename policies once we have the old ones removed
|
||||
|
||||
@@ -52,6 +52,7 @@ public static class UmbracoBuilderExtensions
|
||||
.AddScripts()
|
||||
.AddPartialViews()
|
||||
.AddStylesheets()
|
||||
.AddWebhooks()
|
||||
.AddServer()
|
||||
.AddCorsPolicy()
|
||||
.AddBackOfficeAuthentication()
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Web.BackOffice.Mapping;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
|
||||
internal static class WebhookBuilderExtensions
|
||||
{
|
||||
internal static IUmbracoBuilder AddWebhooks(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<WebhookMapDefinition>();
|
||||
builder.Services.AddUnique<IWebhookPresentationFactory, WebhookPresentationFactory>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
public interface IWebhookPresentationFactory
|
||||
{
|
||||
WebhookResponseModel CreateResponseModel(IWebhook webhook);
|
||||
|
||||
IWebhook CreateWebhook(CreateWebhookRequestModel webhookRequestModel);
|
||||
|
||||
IWebhook CreateWebhook(UpdateWebhookRequestModel webhookRequestModel);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Webhooks;
|
||||
using Umbraco.Cms.Web.Common.Models;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Services;
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
internal class WebhookPresentationFactory : IWebhookPresentationFactory
|
||||
{
|
||||
@@ -11,27 +11,38 @@ internal class WebhookPresentationFactory : IWebhookPresentationFactory
|
||||
|
||||
public WebhookPresentationFactory(WebhookEventCollection webhookEventCollection) => _webhookEventCollection = webhookEventCollection;
|
||||
|
||||
public WebhookViewModel Create(IWebhook webhook)
|
||||
public WebhookResponseModel CreateResponseModel(IWebhook webhook)
|
||||
{
|
||||
var target = new WebhookViewModel
|
||||
var target = new WebhookResponseModel
|
||||
{
|
||||
ContentTypeKeys = webhook.ContentTypeKeys,
|
||||
Events = webhook.Events.Select(Create).ToArray(),
|
||||
Url = webhook.Url,
|
||||
Enabled = webhook.Enabled,
|
||||
Id = webhook.Id,
|
||||
Key = webhook.Key,
|
||||
Id = webhook.Key,
|
||||
Headers = webhook.Headers,
|
||||
ContentTypeKeys = webhook.ContentTypeKeys,
|
||||
};
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private WebhookEventViewModel Create(string alias)
|
||||
public IWebhook CreateWebhook(CreateWebhookRequestModel webhookRequestModel)
|
||||
{
|
||||
var target = new Webhook(webhookRequestModel.Url, webhookRequestModel.Enabled, webhookRequestModel.ContentTypeKeys, webhookRequestModel.Events, webhookRequestModel.Headers);
|
||||
return target;
|
||||
}
|
||||
|
||||
public IWebhook CreateWebhook(UpdateWebhookRequestModel webhookRequestModel)
|
||||
{
|
||||
var target = new Webhook(webhookRequestModel.Url, webhookRequestModel.Enabled, webhookRequestModel.ContentTypeKeys, webhookRequestModel.Events, webhookRequestModel.Headers);
|
||||
return target;
|
||||
}
|
||||
|
||||
private WebhookEventResponseModel Create(string alias)
|
||||
{
|
||||
IWebhookEvent? webhookEvent = _webhookEventCollection.FirstOrDefault(x => x.Alias == alias);
|
||||
|
||||
return new WebhookEventViewModel
|
||||
return new WebhookEventResponseModel
|
||||
{
|
||||
EventName = webhookEvent?.EventName ?? alias,
|
||||
EventType = webhookEvent?.EventType ?? Constants.WebhookEvents.Types.Other,
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DataType.Item;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DataType.Item;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Dictionary.Item;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType.Item;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Language.Item;
|
||||
@@ -10,6 +10,7 @@ using Umbraco.Cms.Api.Management.ViewModels.RelationType.Item;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Template.Item;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User.Item;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Item;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Webhook.Item;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
@@ -33,6 +34,7 @@ public class ItemTypeMapDefinition : IMapDefinition
|
||||
mapper.Define<IRelationType, RelationTypeItemResponseModel>((_, _) => new RelationTypeItemResponseModel(), Map);
|
||||
mapper.Define<IUser, UserItemResponseModel>((_, _) => new UserItemResponseModel(), Map);
|
||||
mapper.Define<IUserGroup, UserGroupItemResponseModel>((_, _) => new UserGroupItemResponseModel(), Map);
|
||||
mapper.Define<IWebhook, WebhookItemResponseModel>((_, _) => new WebhookItemResponseModel(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
@@ -119,4 +121,14 @@ public class ItemTypeMapDefinition : IMapDefinition
|
||||
target.Name = source.Name ?? source.Alias;
|
||||
target.Icon = source.Icon;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(IWebhook source, WebhookItemResponseModel target, MapperContext context)
|
||||
{
|
||||
target.Name = string.Empty; //source.Name;
|
||||
target.Url = source.Url;
|
||||
target.Enabled = source.Enabled;
|
||||
target.Events = string.Join(",", source.Events);
|
||||
target.Types = string.Join(",", source.ContentTypeKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
@@ -14,15 +13,6 @@ public class WebhookMapDefinition : IMapDefinition
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
|
||||
public WebhookMapDefinition() : this(
|
||||
StaticServiceProvider.Instance.GetRequiredService<IHostingEnvironment>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>()
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WebhookMapDefinition(IHostingEnvironment hostingEnvironment, ILocalizedTextService localizedTextService)
|
||||
{
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
@@ -31,31 +21,41 @@ public class WebhookMapDefinition : IMapDefinition
|
||||
|
||||
public void DefineMaps(IUmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<WebhookViewModel, IWebhook>((_, _) => new Webhook(string.Empty), Map);
|
||||
mapper.Define<IWebhookEvent, WebhookEventViewModel>((_, _) => new WebhookEventViewModel(), Map);
|
||||
mapper.Define<IWebhookEvent, WebhookEventResponseModel>((_, _) => new WebhookEventResponseModel(), Map);
|
||||
mapper.Define<WebhookLog, WebhookLogViewModel>((_, _) => new WebhookLogViewModel(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -Id -Key -UpdateDate
|
||||
private void Map(WebhookViewModel source, IWebhook target, MapperContext context)
|
||||
{
|
||||
target.ContentTypeKeys = source.ContentTypeKeys;
|
||||
target.Events = source.Events.Select(x => x.Alias).ToArray();
|
||||
target.Url = source.Url;
|
||||
target.Enabled = source.Enabled;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key ?? Guid.NewGuid();
|
||||
target.Headers = source.Headers;
|
||||
mapper.Define<CreateWebhookRequestModel, IWebhook>((_, _) => new Webhook(string.Empty), Map);
|
||||
mapper.Define<UpdateWebhookRequestModel, IWebhook>((_, _) => new Webhook(string.Empty), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(IWebhookEvent source, WebhookEventViewModel target, MapperContext context)
|
||||
private void Map(IWebhookEvent source, WebhookEventResponseModel target, MapperContext context)
|
||||
{
|
||||
target.EventName = source.EventName;
|
||||
target.EventType = source.EventType;
|
||||
target.Alias = source.Alias;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -Id -UpdateDate
|
||||
private void Map(CreateWebhookRequestModel source, IWebhook target, MapperContext context)
|
||||
{
|
||||
target.Url = source.Url;
|
||||
target.Enabled = source.Enabled;
|
||||
target.ContentTypeKeys = source.ContentTypeKeys;
|
||||
target.Events = source.Events;
|
||||
target.Headers = source.Headers;
|
||||
target.Key = source.Id ?? Guid.NewGuid();
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -Id -UpdateDate -Key
|
||||
private void Map(UpdateWebhookRequestModel source, IWebhook target, MapperContext context)
|
||||
{
|
||||
target.Url = source.Url;
|
||||
target.Enabled = source.Enabled;
|
||||
target.ContentTypeKeys = source.ContentTypeKeys;
|
||||
target.Events = source.Events;
|
||||
target.Headers = source.Headers;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(WebhookLog source, WebhookLogViewModel target, MapperContext context)
|
||||
{
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
|
||||
public class CreateWebhookRequestModel : WebhookModelBase
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
|
||||
public string[] Events { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Webhook.Item;
|
||||
|
||||
public class WebhookItemResponseModel
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Events { get; set; } = string.Empty;
|
||||
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
public string Types { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
|
||||
public class UpdateWebhookRequestModel : WebhookModelBase
|
||||
{
|
||||
public string[] Events { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
|
||||
public class WebhookEventResponseModel
|
||||
{
|
||||
public string EventName { get; set; } = string.Empty;
|
||||
|
||||
public string EventType { get; set; } = string.Empty;
|
||||
|
||||
public string Alias { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
|
||||
public class WebhookModelBase
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
[Required]
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
public Guid[] ContentTypeKeys { get; set; } = Array.Empty<Guid>();
|
||||
|
||||
public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Webhook;
|
||||
|
||||
public class WebhookResponseModel : WebhookModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public WebhookEventResponseModel[] Events { get; set; } = Array.Empty<WebhookEventResponseModel>();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Persistence.Repositories;
|
||||
|
||||
@@ -20,30 +20,40 @@ public interface IWebhookRepository
|
||||
Task<IWebhook> CreateAsync(IWebhook webhook);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a webhook by key
|
||||
/// Gets a webhook by key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the webhook which will be retrieved.</param>
|
||||
/// <returns>The <see cref="IWebhook" /> webhook with the given key.</returns>
|
||||
Task<IWebhook?> GetAsync(Guid key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a webhook by key
|
||||
/// Gets webhooks by keys.
|
||||
/// </summary>
|
||||
/// <param name="keys">The alias of an event, which is referenced by a webhook.</param>
|
||||
/// <returns>
|
||||
/// A paged model of <see cref="IWebhook" />.
|
||||
/// </returns>
|
||||
Task<PagedModel<IWebhook>> GetByIdsAsync(IEnumerable<Guid> keys) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a webhook by key.
|
||||
/// </summary>
|
||||
/// <param name="alias">The alias of an event, which is referenced by a webhook.</param>
|
||||
/// <returns>
|
||||
/// A paged model of <see cref="IWebhook" />
|
||||
/// A paged model of <see cref="IWebhook" />.
|
||||
/// </returns>
|
||||
Task<PagedModel<IWebhook>> GetByAliasAsync(string alias);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a webhook by key
|
||||
/// Gets a webhook by key.
|
||||
/// </summary>
|
||||
/// <param name="webhook">The webhook to be deleted.</param>
|
||||
/// <returns><placeholder>A <see cref="Task"/> representing the asynchronous operation.</placeholder></returns>
|
||||
Task DeleteAsync(IWebhook webhook);
|
||||
|
||||
/// <summary>
|
||||
/// Updates a given webhook
|
||||
/// Updates a given webhook.
|
||||
/// </summary>
|
||||
/// <param name="webhook">The webhook to be updated.</param>
|
||||
/// <returns>The updated <see cref="IWebhook" /> webhook.</returns>
|
||||
|
||||
@@ -29,6 +29,13 @@ public interface IWebhookService
|
||||
/// <param name="key">The unique key of the webhook.</param>
|
||||
Task<IWebhook?> GetAsync(Guid key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all webhooks with the given keys.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable list of <see cref="IWebhook" /> objects.</returns>
|
||||
Task<IEnumerable<IWebhook?>> GetMultipleAsync(IEnumerable<Guid> keys)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all webhooks.
|
||||
/// </summary>
|
||||
|
||||
@@ -135,6 +135,16 @@ public class WebhookService : IWebhookService
|
||||
return webhook;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<IWebhook?>> GetMultipleAsync(IEnumerable<Guid> keys)
|
||||
{
|
||||
using ICoreScope scope = _provider.CreateCoreScope();
|
||||
PagedModel<IWebhook> webhooks = await _webhookRepository.GetByIdsAsync(keys);
|
||||
scope.Complete();
|
||||
|
||||
return webhooks.Items;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PagedModel<IWebhook>> GetAllAsync(int skip, int take)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using NPoco;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
@@ -58,6 +58,24 @@ public class WebhookRepository : IWebhookRepository
|
||||
return webhookDto is null ? null : await DtoToEntity(webhookDto);
|
||||
}
|
||||
|
||||
public async Task<PagedModel<IWebhook>> GetByIdsAsync(IEnumerable<Guid> keys)
|
||||
{
|
||||
Sql<ISqlContext>? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql()
|
||||
.SelectAll()
|
||||
.From<WebhookDto>()
|
||||
.InnerJoin<Webhook2EventsDto>()
|
||||
.On<WebhookDto, Webhook2EventsDto>(left => left.Id, right => right.WebhookId)
|
||||
.WhereIn<Webhook2EventsDto>(x => x.WebhookId, keys);
|
||||
|
||||
List<WebhookDto>? webhookDtos = await _scopeAccessor.AmbientScope?.Database.FetchAsync<WebhookDto>(sql)!;
|
||||
|
||||
return new PagedModel<IWebhook>
|
||||
{
|
||||
Items = await DtosToEntities(webhookDtos),
|
||||
Total = webhookDtos.Count,
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<PagedModel<IWebhook>> GetByAliasAsync(string alias)
|
||||
{
|
||||
Sql<ISqlContext>? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql()
|
||||
|
||||
@@ -581,10 +581,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
"mediaPickerThreeBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<MediaPickerThreeController>(
|
||||
controller => controller.UploadMedia(null!))
|
||||
},
|
||||
{
|
||||
"webhooksApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<WebhookController>(
|
||||
controller => controller.GetAll(0, 0))
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Core.Webhooks;
|
||||
using Umbraco.Cms.Web.BackOffice.Services;
|
||||
using Umbraco.Cms.Web.Common.Attributes;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Cms.Web.Common.Models;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Controllers;
|
||||
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public class WebhookController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IWebhookService _webhookService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
private readonly WebhookEventCollection _webhookEventCollection;
|
||||
private readonly IWebhookLogService _webhookLogService;
|
||||
private readonly IWebhookPresentationFactory _webhookPresentationFactory;
|
||||
|
||||
public WebhookController(IWebhookService webhookService, IUmbracoMapper umbracoMapper, WebhookEventCollection webhookEventCollection, IWebhookLogService webhookLogService, IWebhookPresentationFactory webhookPresentationFactory)
|
||||
{
|
||||
_webhookService = webhookService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_webhookEventCollection = webhookEventCollection;
|
||||
_webhookLogService = webhookLogService;
|
||||
_webhookPresentationFactory = webhookPresentationFactory;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll(int skip = 0, int take = int.MaxValue)
|
||||
{
|
||||
PagedModel<IWebhook> webhooks = await _webhookService.GetAllAsync(skip, take);
|
||||
|
||||
IEnumerable<WebhookViewModel> webhookViewModels = webhooks.Items.Select(_webhookPresentationFactory.Create);
|
||||
|
||||
return Ok(webhookViewModels);
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Update(WebhookViewModel webhookViewModel)
|
||||
{
|
||||
IWebhook webhook = _umbracoMapper.Map<IWebhook>(webhookViewModel)!;
|
||||
|
||||
Attempt<IWebhook, WebhookOperationStatus> result = await _webhookService.UpdateAsync(webhook);
|
||||
return result.Success ? Ok(_webhookPresentationFactory.Create(webhook)) : WebhookOperationStatusResult(result.Status);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(WebhookViewModel webhookViewModel)
|
||||
{
|
||||
IWebhook webhook = _umbracoMapper.Map<IWebhook>(webhookViewModel)!;
|
||||
Attempt<IWebhook, WebhookOperationStatus> result = await _webhookService.CreateAsync(webhook);
|
||||
return result.Success ? Ok(_webhookPresentationFactory.Create(webhook)) : WebhookOperationStatusResult(result.Status);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetByKey(Guid key)
|
||||
{
|
||||
IWebhook? webhook = await _webhookService.GetAsync(key);
|
||||
|
||||
return webhook is null ? NotFound() : Ok(_webhookPresentationFactory.Create(webhook));
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public async Task<IActionResult> Delete(Guid key)
|
||||
{
|
||||
Attempt<IWebhook?, WebhookOperationStatus> result = await _webhookService.DeleteAsync(key);
|
||||
return result.Success ? Ok() : WebhookOperationStatusResult(result.Status);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetEvents()
|
||||
{
|
||||
List<WebhookEventViewModel> viewModels = _umbracoMapper.MapEnumerable<IWebhookEvent, WebhookEventViewModel>(_webhookEventCollection.AsEnumerable());
|
||||
return Ok(viewModels);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetLogs(int skip = 0, int take = int.MaxValue)
|
||||
{
|
||||
PagedModel<WebhookLog> logs = await _webhookLogService.Get(skip, take);
|
||||
List<WebhookLogViewModel> mappedLogs = _umbracoMapper.MapEnumerable<WebhookLog, WebhookLogViewModel>(logs.Items);
|
||||
return Ok(new PagedResult<WebhookLogViewModel>(logs.Total, 0, 0)
|
||||
{
|
||||
Items = mappedLogs,
|
||||
});
|
||||
}
|
||||
|
||||
private IActionResult WebhookOperationStatusResult(WebhookOperationStatus status) =>
|
||||
status switch
|
||||
{
|
||||
WebhookOperationStatus.CancelledByNotification => ValidationProblem(new SimpleNotificationModel(new BackOfficeNotification[]
|
||||
{
|
||||
new("Cancelled by notification", "The operation was cancelled by a notification", NotificationStyle.Error),
|
||||
})),
|
||||
WebhookOperationStatus.NotFound => NotFound("Could not find the webhook"),
|
||||
WebhookOperationStatus.NoEvents => ValidationProblem(new SimpleNotificationModel(new BackOfficeNotification[]
|
||||
{
|
||||
new("No events", "The webhook does not have any events", NotificationStyle.Error),
|
||||
})),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError),
|
||||
|
||||
};
|
||||
}
|
||||
@@ -94,8 +94,6 @@ public static partial class UmbracoBuilderExtensions
|
||||
builder.Services.AddSingleton<BackOfficeServerVariables>();
|
||||
builder.Services.AddScoped<BackOfficeSessionIdValidator>();
|
||||
builder.Services.AddScoped<BackOfficeSecurityStampValidator>();
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<WebhookMapDefinition>();
|
||||
|
||||
// register back office trees
|
||||
// the collection builder only accepts types inheriting from TreeControllerBase
|
||||
// and will filter out those that are not attributed with TreeAttribute
|
||||
@@ -122,7 +120,6 @@ public static partial class UmbracoBuilderExtensions
|
||||
builder.Services.AddUnique<IConflictingRouteService, ConflictingRouteService>();
|
||||
builder.Services.AddSingleton<UnhandledExceptionLoggerMiddleware>();
|
||||
builder.Services.AddTransient<BlockGridSampleHelper>();
|
||||
builder.Services.AddUnique<IWebhookPresentationFactory, WebhookPresentationFactory>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Web.Common.Models;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Services;
|
||||
|
||||
[Obsolete("Will be moved to a new namespace in V14")]
|
||||
public interface IWebhookPresentationFactory
|
||||
{
|
||||
WebhookViewModel Create(IWebhook webhook);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Cms.Core.Webhooks;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Models;
|
||||
|
||||
[DataContract]
|
||||
public class WebhookEventViewModel
|
||||
{
|
||||
[DataMember(Name = "eventName")]
|
||||
public string EventName { get; set; } = string.Empty;
|
||||
|
||||
[DataMember(Name = "eventType")]
|
||||
public string EventType { get; set; } = string.Empty;
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Models;
|
||||
|
||||
[DataContract]
|
||||
public class WebhookViewModel
|
||||
{
|
||||
[DataMember(Name = "id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember(Name = "key")]
|
||||
public Guid? Key { get; set; }
|
||||
|
||||
[DataMember(Name = "url")]
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
[DataMember(Name = "events")]
|
||||
public WebhookEventViewModel[] Events { get; set; } = Array.Empty<WebhookEventViewModel>();
|
||||
|
||||
[DataMember(Name = "contentTypeKeys")]
|
||||
public Guid[] ContentTypeKeys { get; set; } = Array.Empty<Guid>();
|
||||
|
||||
[DataMember(Name = "enabled")]
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
[DataMember(Name = "headers")]
|
||||
public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
Reference in New Issue
Block a user