mirror of https://github.com/Squidex/squidex.git
22 changed files with 435 additions and 42 deletions
@ -0,0 +1,29 @@ |
|||
// ==========================================================================
|
|||
// MongoSchemaEntityWebhook.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using Squidex.Read.Schemas; |
|||
|
|||
namespace Squidex.Read.MongoDb.Schemas |
|||
{ |
|||
public sealed class MongoSchemaEntityWebhook : ISchemaWebhookEntity |
|||
{ |
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public Guid Id { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public Uri Url { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public string SecurityToken { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// ==========================================================================
|
|||
// MongoSchemaWebhookEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using Squidex.Read.Schemas; |
|||
|
|||
namespace Squidex.Read.MongoDb.Schemas |
|||
{ |
|||
public class MongoSchemaWebhookEntity : ISchemaWebhookEntity |
|||
{ |
|||
[BsonId] |
|||
[BsonElement] |
|||
[BsonRepresentation(BsonType.String)] |
|||
public Guid Id { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public Uri Url { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public string SecurityToken { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public Guid SchemaId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,121 @@ |
|||
// ==========================================================================
|
|||
// MongoSchemaWebhookRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using Squidex.Events.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Read.Schemas; |
|||
using Squidex.Read.Schemas.Repositories; |
|||
|
|||
namespace Squidex.Read.MongoDb.Schemas |
|||
{ |
|||
public class MongoSchemaWebhookRepository : MongoRepositoryBase<MongoSchemaWebhookEntity>, ISchemaWebhookRepository, IEventConsumer |
|||
{ |
|||
private static readonly List<ISchemaWebhookEntity> EmptyWebhooks = new List<ISchemaWebhookEntity>(); |
|||
private Dictionary<Guid, List<MongoSchemaWebhookEntity>> inMemoryWebhooks; |
|||
private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1); |
|||
|
|||
public string Name |
|||
{ |
|||
get { return GetType().Name; } |
|||
} |
|||
|
|||
public string EventsFilter |
|||
{ |
|||
get { return "^schema-"; } |
|||
} |
|||
|
|||
public MongoSchemaWebhookRepository(IMongoDatabase database) |
|||
: base(database) |
|||
{ |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "Projections_SchemaWebhooks"; |
|||
} |
|||
|
|||
protected override Task SetupCollectionAsync(IMongoCollection<MongoSchemaWebhookEntity> collection) |
|||
{ |
|||
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.SchemaId)); |
|||
} |
|||
|
|||
public Task On(Envelope<IEvent> @event) |
|||
{ |
|||
return this.DispatchActionAsync(@event.Payload, @event.Headers); |
|||
} |
|||
|
|||
protected async Task On(WebhookAdded @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureWebooksLoadedAsync(); |
|||
|
|||
var webhook = SimpleMapper.Map(@event, new MongoSchemaWebhookEntity { SchemaId = @event.SchemaId.Id }); |
|||
|
|||
inMemoryWebhooks.GetOrAddNew(webhook.SchemaId).Add(webhook); |
|||
|
|||
await Collection.InsertOneAsync(webhook); |
|||
} |
|||
|
|||
protected async Task On(WebhookDeleted @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureWebooksLoadedAsync(); |
|||
|
|||
inMemoryWebhooks.GetOrDefault(@event.SchemaId.Id)?.RemoveAll(w => w.Id == @event.Id); |
|||
|
|||
await Collection.DeleteManyAsync(x => x.Id == @event.Id); |
|||
} |
|||
|
|||
protected async Task On(SchemaDeleted @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureWebooksLoadedAsync(); |
|||
|
|||
inMemoryWebhooks.Remove(@event.SchemaId.Id); |
|||
|
|||
await Collection.DeleteManyAsync(x => x.SchemaId == @event.SchemaId.Id); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<ISchemaWebhookEntity>> QueryBySchemaAsync(Guid schemaId) |
|||
{ |
|||
await EnsureWebooksLoadedAsync(); |
|||
|
|||
return inMemoryWebhooks.GetOrDefault(schemaId)?.OfType<ISchemaWebhookEntity>()?.ToList() ?? EmptyWebhooks; |
|||
} |
|||
|
|||
private async Task EnsureWebooksLoadedAsync() |
|||
{ |
|||
if (inMemoryWebhooks == null) |
|||
{ |
|||
try |
|||
{ |
|||
await lockObject.WaitAsync(); |
|||
|
|||
if (inMemoryWebhooks == null) |
|||
{ |
|||
var webhooks = await Collection.Find(new BsonDocument()).ToListAsync(); |
|||
|
|||
inMemoryWebhooks = webhooks.GroupBy(x => x.SchemaId).ToDictionary(x => x.Key, x => x.ToList()); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
lockObject.Release(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// ISchemaWebhookEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Read.Schemas |
|||
{ |
|||
public interface ISchemaWebhookEntity |
|||
{ |
|||
Guid Id { get; } |
|||
|
|||
Uri Url { get; } |
|||
|
|||
string SecurityToken { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// ISchemaWebhookRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Read.Schemas.Repositories |
|||
{ |
|||
public interface ISchemaWebhookRepository |
|||
{ |
|||
Task<IReadOnlyList<ISchemaWebhookEntity>> QueryBySchemaAsync(Guid schemaId); |
|||
} |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
// ==========================================================================
|
|||
// WebhookInvoker.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Net.Http; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
using Squidex.Events.Contents; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Tasks; |
|||
using Squidex.Read.Schemas.Repositories; |
|||
|
|||
namespace Squidex.Read.Schemas |
|||
{ |
|||
public sealed class WebhookInvoker : IEventConsumer |
|||
{ |
|||
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); |
|||
|
|||
private readonly ISchemaWebhookRepository webhookRepository; |
|||
private readonly ISemanticLog log; |
|||
private readonly JsonSerializer webhookSerializer; |
|||
|
|||
public string Name |
|||
{ |
|||
get { return GetType().Name; } |
|||
} |
|||
|
|||
public string EventsFilter |
|||
{ |
|||
get { return "^content-"; } |
|||
} |
|||
|
|||
public WebhookInvoker(ISchemaWebhookRepository webhookRepository, JsonSerializer webhookSerializer, ISemanticLog log) |
|||
{ |
|||
Guard.NotNull(webhookRepository, nameof(webhookRepository)); |
|||
Guard.NotNull(webhookSerializer, nameof(webhookSerializer)); |
|||
Guard.NotNull(log, nameof(log)); |
|||
|
|||
this.webhookRepository = webhookRepository; |
|||
this.webhookSerializer = webhookSerializer; |
|||
|
|||
this.log = log; |
|||
} |
|||
|
|||
public Task ClearAsync() |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public async Task On(Envelope<IEvent> @event) |
|||
{ |
|||
if (@event.Payload is ContentEvent contentEvent) |
|||
{ |
|||
var hooks = await webhookRepository.QueryBySchemaAsync(contentEvent.SchemaId.Id); |
|||
|
|||
if (hooks.Count > 0) |
|||
{ |
|||
var payload = CreatePayload(@event); |
|||
|
|||
foreach (var hook in hooks) |
|||
{ |
|||
DispatchEventAsync(payload, hook).Forget(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private JObject CreatePayload(Envelope<IEvent> @event) |
|||
{ |
|||
return new JObject( |
|||
new JProperty("type", @event.Payload.GetType().Name), |
|||
new JProperty("meta", JObject.FromObject(@event.Headers, webhookSerializer)), |
|||
new JProperty("data", JObject.FromObject(@event.Headers, webhookSerializer))); |
|||
} |
|||
|
|||
private async Task DispatchEventAsync(JObject payload, ISchemaWebhookEntity webhook) |
|||
{ |
|||
try |
|||
{ |
|||
using (log.MeasureInformation(w => w |
|||
.WriteProperty("Action", "SendToHook") |
|||
.WriteProperty("Status", "Invoked"))) |
|||
{ |
|||
using (var client = new HttpClient()) |
|||
{ |
|||
client.Timeout = Timeout; |
|||
|
|||
var message = new HttpRequestMessage(HttpMethod.Post, webhook.Url) |
|||
{ |
|||
Content = new StringContent(payload.ToString(), Encoding.UTF8, "application/json") |
|||
}; |
|||
|
|||
message.Headers.TryAddWithoutValidation("X-SecurityToken", webhook.SecurityToken); |
|||
message.Headers.Add("User-Agent", "Squidex"); |
|||
|
|||
var response = await client.SendAsync(message); |
|||
|
|||
response.EnsureSuccessStatusCode(); |
|||
} |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
log.LogError(ex, w => w |
|||
.WriteProperty("Action", "SendToHook") |
|||
.WriteProperty("Status", "Failed")); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue