Browse Source

API updated.

pull/157/head
Sebastian Stehle 8 years ago
parent
commit
46ee7459ad
  1. 3
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  2. 14
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs
  3. 13
      src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs
  4. 12
      src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs
  5. 18
      src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs
  6. 2
      src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs
  7. 21
      src/Squidex/Config/Domain/ReadModule.cs
  8. 12
      src/Squidex/Config/Domain/StoreMongoDbModule.cs
  9. 6
      src/Squidex/Config/Domain/WriteModule.cs
  10. 25
      src/Squidex/Controllers/Api/Rules/Models/Actions/WebhookActionDto.cs
  11. 34
      src/Squidex/Controllers/Api/Rules/Models/Converters/RuleActionDtoFactory.cs
  12. 57
      src/Squidex/Controllers/Api/Rules/Models/Converters/RuleConverter.cs
  13. 38
      src/Squidex/Controllers/Api/Rules/Models/Converters/RuleTriggerDtoFactory.cs
  14. 16
      src/Squidex/Controllers/Api/Rules/Models/CreateRuleDto.cs
  15. 23
      src/Squidex/Controllers/Api/Rules/Models/RuleActionDto.cs
  16. 67
      src/Squidex/Controllers/Api/Rules/Models/RuleDto.cs
  17. 17
      src/Squidex/Controllers/Api/Rules/Models/RuleEventDto.cs
  18. 12
      src/Squidex/Controllers/Api/Rules/Models/RuleEventsDto.cs
  19. 23
      src/Squidex/Controllers/Api/Rules/Models/RuleTriggerDto.cs
  20. 36
      src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerDto.cs
  21. 6
      src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerSchemaDto.cs
  22. 20
      src/Squidex/Controllers/Api/Rules/Models/UpdateRuleDto.cs
  23. 245
      src/Squidex/Controllers/Api/Rules/RulesController.cs
  24. 1
      src/Squidex/Controllers/Api/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs
  25. 3
      src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs
  26. 2
      src/Squidex/Controllers/Api/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs
  27. 2
      src/Squidex/Controllers/Api/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs
  28. 2
      src/Squidex/Controllers/Api/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs
  29. 2
      src/Squidex/Controllers/Api/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs
  30. 2
      src/Squidex/Controllers/Api/Schemas/Models/Fields/JsonFieldPropertiesDto.cs
  31. 2
      src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberFieldPropertiesDto.cs
  32. 2
      src/Squidex/Controllers/Api/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs
  33. 2
      src/Squidex/Controllers/Api/Schemas/Models/Fields/StringFieldPropertiesDto.cs
  34. 2
      src/Squidex/Controllers/Api/Schemas/Models/Fields/TagsFieldPropertiesDto.cs
  35. 89
      src/Squidex/Controllers/Api/Webhooks/Models/WebhookDto.cs
  36. 220
      src/Squidex/Controllers/Api/Webhooks/WebhooksController.cs
  37. 6
      src/Squidex/Controllers/JsonInheritanceConverter2.cs
  38. 102
      tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleEnqueuerTests.cs

3
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -12,7 +12,6 @@ using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Events;
@ -114,7 +113,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var dumpBuilder = new StringBuilder(result.Dump);
dumpBuilder.AppendLine();
dumpBuilder.AppendFormat("Elapesed {0}.", actionWatch.Elapsed);
dumpBuilder.AppendFormat("Elapsed {0}.", actionWatch.Elapsed);
dumpBuilder.AppendLine();
if (result.Exception is TimeoutException || result.Exception is OperationCanceledException)

14
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs

@ -32,18 +32,18 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
return false;
}
private static bool MatchsSchema(ContentChangedTriggerSchema webhookSchema, SchemaEvent @event)
private static bool MatchsSchema(ContentChangedTriggerSchema schema, SchemaEvent @event)
{
return @event.SchemaId.Id == webhookSchema.SchemaId;
return @event.SchemaId.Id == schema.SchemaId;
}
private static bool MatchsType(ContentChangedTriggerSchema webhookSchema, SchemaEvent @event)
private static bool MatchsType(ContentChangedTriggerSchema schema, SchemaEvent @event)
{
return
(webhookSchema.SendCreate && @event is ContentCreated) ||
(webhookSchema.SendUpdate && @event is ContentUpdated) ||
(webhookSchema.SendDelete && @event is ContentDeleted) ||
(webhookSchema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Status == Status.Published);
(schema.SendCreate && @event is ContentCreated) ||
(schema.SendUpdate && @event is ContentUpdated) ||
(schema.SendDelete && @event is ContentDeleted) ||
(schema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Status == Status.Published);
}
}
}

13
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs

@ -48,11 +48,20 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
public async Task<IReadOnlyList<IRuleEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20)
{
var webhookEventEntities =
var ruleEventEntities =
await Collection.Find(x => x.AppId == appId).Skip(skip).Limit(take).SortByDescending(x => x.Created)
.ToListAsync();
return webhookEventEntities;
return ruleEventEntities;
}
public async Task<IRuleEventEntity> FindAsync(Guid id)
{
var ruleEvent =
await Collection.Find(x => x.Id == id)
.FirstOrDefaultAsync();
return ruleEvent;
}
public async Task<int> CountByAppAsync(Guid appId)

12
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs

@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
{
private static readonly List<IRuleEntity> EmptyRules = new List<IRuleEntity>();
private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1);
private Dictionary<Guid, List<IRuleEntity>> inMemoryWebhooks;
private Dictionary<Guid, List<IRuleEntity>> inMemoryRules;
public MongoRuleRepository(IMongoDatabase database)
: base(database)
@ -55,20 +55,20 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
{
await EnsureRulesLoadedAsync();
return inMemoryWebhooks.GetOrDefault(appId) ?? EmptyRules;
return inMemoryRules.GetOrDefault(appId) ?? EmptyRules;
}
private async Task EnsureRulesLoadedAsync()
{
if (inMemoryWebhooks == null)
if (inMemoryRules == null)
{
try
{
await lockObject.WaitAsync();
if (inMemoryWebhooks == null)
if (inMemoryRules == null)
{
inMemoryWebhooks = new Dictionary<Guid, List<IRuleEntity>>();
inMemoryRules = new Dictionary<Guid, List<IRuleEntity>>();
var webhooks =
await Collection.Find(new BsonDocument())
@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
foreach (var webhook in webhooks)
{
inMemoryWebhooks.GetOrAddNew(webhook.AppId).Add(webhook);
inMemoryRules.GetOrAddNew(webhook.AppId).Add(webhook);
}
}
}

18
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs

@ -41,8 +41,8 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
{
w.Rule = RuleEventDispatcher.Create(@event);
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w);
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryRules.GetOrAddNew(w.AppId).Add(w);
});
}
@ -54,8 +54,8 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
{
w.Rule.Apply(@event);
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w);
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryRules.GetOrAddNew(w.AppId).Add(w);
});
}
@ -67,8 +67,8 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
{
w.Rule.Apply(@event);
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w);
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryRules.GetOrAddNew(w.AppId).Add(w);
});
}
@ -80,8 +80,8 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
{
w.Rule.Apply(@event);
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w);
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryRules.GetOrAddNew(w.AppId).Add(w);
});
}
@ -89,7 +89,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
{
await EnsureRulesLoadedAsync();
inMemoryWebhooks.GetOrAddNew(@event.AppId.Id).RemoveAll(x => x.Id == @event.RuleId);
inMemoryRules.GetOrAddNew(@event.AppId.Id).RemoveAll(x => x.Id == @event.RuleId);
await Collection.DeleteManyAsync(x => x.Id == @event.RuleId);
}

2
src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs

@ -31,5 +31,7 @@ namespace Squidex.Domain.Apps.Read.Rules.Repositories
Task<int> CountByAppAsync(Guid appId);
Task<IReadOnlyList<IRuleEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20);
Task<IRuleEventEntity> FindAsync(Guid id);
}
}

21
src/Squidex/Config/Domain/ReadModule.cs

@ -11,6 +11,9 @@ using System.Linq;
using Autofac;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.ActionHandlers;
using Squidex.Domain.Apps.Core.HandleRules.Triggers;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Read.Apps.Services.Implementations;
@ -18,10 +21,10 @@ using Squidex.Domain.Apps.Read.Contents;
using Squidex.Domain.Apps.Read.Contents.Edm;
using Squidex.Domain.Apps.Read.Contents.GraphQL;
using Squidex.Domain.Apps.Read.History;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Read.Schemas.Services.Implementations;
using Squidex.Domain.Apps.Read.Webhooks;
using Squidex.Domain.Users;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
@ -104,17 +107,27 @@ namespace Squidex.Config.Domain
.AsSelf()
.InstancePerDependency();
builder.RegisterType<WebhookDequeuer>()
builder.RegisterType<RuleDequeuer>()
.As<IExternalSystem>()
.AsSelf()
.InstancePerDependency();
builder.RegisterType<WebhookEnqueuer>()
builder.RegisterType<RuleEnqueuer>()
.As<IEventConsumer>()
.AsSelf()
.InstancePerDependency();
builder.RegisterType<WebhookSender>()
builder.RegisterType<ContentChangedTriggerHandler>()
.As<IRuleTriggerHandler>()
.AsSelf()
.SingleInstance();
builder.RegisterType<WebhookActionHandler>()
.As<IRuleActionHandler>()
.AsSelf()
.SingleInstance();
builder.RegisterType<RuleService>()
.AsSelf()
.SingleInstance();

12
src/Squidex/Config/Domain/StoreMongoDbModule.cs

@ -22,11 +22,11 @@ using Squidex.Domain.Apps.Read.MongoDb.Apps;
using Squidex.Domain.Apps.Read.MongoDb.Assets;
using Squidex.Domain.Apps.Read.MongoDb.Contents;
using Squidex.Domain.Apps.Read.MongoDb.History;
using Squidex.Domain.Apps.Read.MongoDb.Rules;
using Squidex.Domain.Apps.Read.MongoDb.Schemas;
using Squidex.Domain.Apps.Read.MongoDb.Webhooks;
using Squidex.Domain.Apps.Read.Rules.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Services.Implementations;
using Squidex.Domain.Apps.Read.Webhooks.Repositories;
using Squidex.Domain.Users;
using Squidex.Domain.Users.MongoDb;
using Squidex.Domain.Users.MongoDb.Infrastructure;
@ -136,9 +136,9 @@ namespace Squidex.Config.Domain
.AsSelf()
.SingleInstance();
builder.RegisterType<MongoWebhookEventRepository>()
builder.RegisterType<MongoRuleEventRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration))
.As<IWebhookEventRepository>()
.As<IRuleEventRepository>()
.As<IExternalSystem>()
.AsSelf()
.SingleInstance();
@ -171,9 +171,9 @@ namespace Squidex.Config.Domain
.AsSelf()
.SingleInstance();
builder.RegisterType<MongoWebhookRepository>()
builder.RegisterType<MongoRuleRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration))
.As<IWebhookRepository>()
.As<IRuleRepository>()
.As<IEventConsumer>()
.As<IExternalSystem>()
.AsSelf()

6
src/Squidex/Config/Domain/WriteModule.cs

@ -13,8 +13,8 @@ using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Write.Apps;
using Squidex.Domain.Apps.Write.Assets;
using Squidex.Domain.Apps.Write.Contents;
using Squidex.Domain.Apps.Write.Rules;
using Squidex.Domain.Apps.Write.Schemas;
using Squidex.Domain.Apps.Write.Webhooks;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Pipeline.CommandMiddlewares;
@ -75,7 +75,7 @@ namespace Squidex.Config.Domain
.As<ICommandMiddleware>()
.SingleInstance();
builder.RegisterType<WebhookCommandMiddleware>()
builder.RegisterType<RuleCommandMiddleware>()
.As<ICommandMiddleware>()
.SingleInstance();
@ -95,7 +95,7 @@ namespace Squidex.Config.Domain
.AsSelf()
.SingleInstance();
builder.Register<DomainObjectFactoryFunction<WebhookDomainObject>>(c => (id => new WebhookDomainObject(id, -1)))
builder.Register<DomainObjectFactoryFunction<RuleDomainObject>>(c => (id => new RuleDomainObject(id, -1)))
.AsSelf()
.SingleInstance();

25
src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs → src/Squidex/Controllers/Api/Rules/Models/Actions/WebhookActionDto.cs

@ -1,5 +1,5 @@
// ==========================================================================
// WebhookCreatedDto.cs
// WebhookActionDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -8,25 +8,30 @@
using System;
using System.ComponentModel.DataAnnotations;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Webhooks.Models
namespace Squidex.Controllers.Api.Rules.Models.Actions
{
public sealed class WebhookCreatedDto
[JsonSchema("Webhook")]
public sealed class WebhookActionDto : RuleActionDto
{
/// <summary>
/// The id of the webhook.
/// The url of the rule.
/// </summary>
public Guid Id { get; set; }
[Required]
public Uri Url { get; set; }
/// <summary>
/// The shared secret that is used to calculate the signature.
/// </summary>
[Required]
public string SharedSecret { get; set; }
/// <summary>
/// The version of the schema.
/// </summary>
public long Version { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new WebhookAction());
}
}
}

34
src/Squidex/Controllers/Api/Rules/Models/Converters/RuleActionDtoFactory.cs

@ -0,0 +1,34 @@
// ==========================================================================
// RuleActionDtoFactory.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Controllers.Api.Rules.Models.Actions;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Rules.Models.Converters
{
public sealed class RuleActionDtoFactory : IRuleActionVisitor<RuleActionDto>
{
private static readonly RuleActionDtoFactory Instance = new RuleActionDtoFactory();
private RuleActionDtoFactory()
{
}
public static RuleActionDto Create(RuleAction properties)
{
return properties.Accept(Instance);
}
public RuleActionDto Visit(WebhookAction action)
{
return SimpleMapper.Map(action, new WebhookActionDto());
}
}
}

57
src/Squidex/Controllers/Api/Rules/Models/Converters/RuleConverter.cs

@ -0,0 +1,57 @@
// ==========================================================================
// RuleConverter.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Write.Rules.Commands;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Rules.Models.Converters
{
public static class RuleConverter
{
public static RuleDto ToModel(this IRuleEntity entity)
{
var dto = new RuleDto();
SimpleMapper.Map(entity, dto);
SimpleMapper.Map(entity.Rule, dto);
if (entity.Rule.Trigger != null)
{
dto.Trigger = RuleTriggerDtoFactory.Create(entity.Rule.Trigger);
}
if (entity.Rule.Action != null)
{
dto.Action = RuleActionDtoFactory.Create(entity.Rule.Action);
}
return dto;
}
public static UpdateRule ToCommand(this UpdateRuleDto dto)
{
var command = new UpdateRule
{
Trigger = dto.Trigger?.ToTrigger(), Action = dto.Action?.ToAction()
};
return command;
}
public static CreateRule ToCommand(this CreateRuleDto dto)
{
var command = new CreateRule
{
Trigger = dto.Trigger.ToTrigger(), Action = dto.Action.ToAction()
};
return command;
}
}
}

38
src/Squidex/Controllers/Api/Rules/Models/Converters/RuleTriggerDtoFactory.cs

@ -0,0 +1,38 @@
// ==========================================================================
// RuleTriggerDtoFactory.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using Squidex.Controllers.Api.Rules.Models.Triggers;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Rules.Models.Converters
{
public sealed class RuleTriggerDtoFactory : IRuleTriggerVisitor<RuleTriggerDto>
{
private static readonly RuleTriggerDtoFactory Instance = new RuleTriggerDtoFactory();
private RuleTriggerDtoFactory()
{
}
public static RuleTriggerDto Create(RuleTrigger properties)
{
return properties.Accept(Instance);
}
public RuleTriggerDto Visit(ContentChangedTrigger trigger)
{
return new ContentChangedTriggerDto
{
Schemas = trigger.Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchemaDto())).ToList()
};
}
}
}

16
src/Squidex/Controllers/Api/Webhooks/Models/UpdateWebhookDto.cs → src/Squidex/Controllers/Api/Rules/Models/CreateRuleDto.cs

@ -1,29 +1,27 @@
// ==========================================================================
// UpdateWebhookDto.cs
// CreateRuleDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Webhooks.Models
namespace Squidex.Controllers.Api.Rules.Models
{
public sealed class UpdateWebhookDto
public sealed class CreateRuleDto
{
/// <summary>
/// The url of the webhook.
/// The trigger properties.
/// </summary>
[Required]
public Uri Url { get; set; }
public RuleTriggerDto Trigger { get; set; }
/// <summary>
/// The schema settings.
/// The action properties.
/// </summary>
[Required]
public List<WebhookSchemaDto> Schemas { get; set; }
public RuleActionDto Action { get; set; }
}
}

23
src/Squidex/Controllers/Api/Rules/Models/RuleActionDto.cs

@ -0,0 +1,23 @@
// ==========================================================================
// RuleActionDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Runtime.Serialization;
using Newtonsoft.Json;
using NJsonSchema.Converters;
using Squidex.Controllers.Api.Rules.Models.Actions;
using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Controllers.Api.Rules.Models
{
[JsonConverter(typeof(JsonInheritanceConverter), "actionType")]
[KnownType(typeof(WebhookActionDto))]
public abstract class RuleActionDto
{
public abstract RuleAction ToAction();
}
}

67
src/Squidex/Controllers/Api/Rules/Models/RuleDto.cs

@ -0,0 +1,67 @@
// ==========================================================================
// RuleDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using NodaTime;
using Squidex.Infrastructure;
namespace Squidex.Controllers.Api.Rules.Models
{
public sealed class RuleDto
{
/// <summary>
/// The id of the rule.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// The user that has created the rule.
/// </summary>
[Required]
public RefToken CreatedBy { get; set; }
/// <summary>
/// The user that has updated the rule.
/// </summary>
[Required]
public RefToken LastModifiedBy { get; set; }
/// <summary>
/// The date and time when the rule has been created.
/// </summary>
public Instant Created { get; set; }
/// <summary>
/// The date and time when the rule has been modified last.
/// </summary>
public Instant LastModified { get; set; }
/// <summary>
/// The version of the rule.
/// </summary>
public int Version { get; set; }
/// <summary>
/// The trigger properties.
/// </summary>
[Required]
public RuleTriggerDto Trigger { get; set; }
/// <summary>
/// The action properties.
/// </summary>
[Required]
public RuleActionDto Action { get; set; }
/// <summary>
/// Determines if the rule is enabled.
/// </summary>
public bool IsEnabled { get; set; }
}
}

17
src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventDto.cs → src/Squidex/Controllers/Api/Rules/Models/RuleEventDto.cs

@ -1,5 +1,5 @@
// ==========================================================================
// WebhookEventDto.cs
// RuleEventDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -9,11 +9,12 @@
using System;
using System.ComponentModel.DataAnnotations;
using NodaTime;
using Squidex.Domain.Apps.Read.Webhooks;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Read.Rules;
namespace Squidex.Controllers.Api.Webhooks.Models
namespace Squidex.Controllers.Api.Rules.Models
{
public sealed class WebhookEventDto
public sealed class RuleEventDto
{
/// <summary>
/// The id of the event.
@ -26,10 +27,10 @@ namespace Squidex.Controllers.Api.Webhooks.Models
public Instant Created { get; set; }
/// <summary>
/// The request url.
/// The description
/// </summary>
[Required]
public Uri RequestUrl { get; set; }
public string Description { get; set; }
/// <summary>
/// The name of the event.
@ -55,11 +56,11 @@ namespace Squidex.Controllers.Api.Webhooks.Models
/// <summary>
/// The result of the event.
/// </summary>
public WebhookResult Result { get; set; }
public RuleResult Result { get; set; }
/// <summary>
/// The result of the job.
/// </summary>
public WebhookJobResult JobResult { get; set; }
public RuleJobResult JobResult { get; set; }
}
}

12
src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventsDto.cs → src/Squidex/Controllers/Api/Rules/Models/RuleEventsDto.cs

@ -1,23 +1,23 @@
// ==========================================================================
// WebhookEventsDto.cs
// RuleEventsDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Controllers.Api.Webhooks.Models
namespace Squidex.Controllers.Api.Rules.Models
{
public sealed class WebhookEventsDto
public sealed class RuleEventsDto
{
/// <summary>
/// The total number of webhook events.
/// The total number of rule events.
/// </summary>
public long Total { get; set; }
/// <summary>
/// The webhook events.
/// The rule events.
/// </summary>
public WebhookEventDto[] Items { get; set; }
public RuleEventDto[] Items { get; set; }
}
}

23
src/Squidex/Controllers/Api/Rules/Models/RuleTriggerDto.cs

@ -0,0 +1,23 @@
// ==========================================================================
// RuleTriggerDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Runtime.Serialization;
using Newtonsoft.Json;
using NJsonSchema.Converters;
using Squidex.Controllers.Api.Rules.Models.Triggers;
using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Controllers.Api.Rules.Models
{
[JsonConverter(typeof(JsonInheritanceConverter), "triggerType")]
[KnownType(typeof(ContentChangedTriggerDto))]
public abstract class RuleTriggerDto
{
public abstract RuleTrigger ToTrigger();
}
}

36
src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerDto.cs

@ -0,0 +1,36 @@
// ==========================================================================
// ContentChangedTriggerDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Rules.Models.Triggers
{
[JsonSchema("ContentChanged")]
public sealed class ContentChangedTriggerDto : RuleTriggerDto
{
/// <summary>
/// The schema settings.
/// </summary>
[Required]
public List<ContentChangedTriggerSchemaDto> Schemas { get; set; }
public override RuleTrigger ToTrigger()
{
return new ContentChangedTrigger
{
Schemas = Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchema())).ToList()
};
}
}
}

6
src/Squidex/Controllers/Api/Webhooks/Models/WebhookSchemaDto.cs → src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerSchemaDto.cs

@ -1,5 +1,5 @@
// ==========================================================================
// WebhookSchemaDto.cs
// ContentChangedTriggerSchemaDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -8,9 +8,9 @@
using System;
namespace Squidex.Controllers.Api.Webhooks.Models
namespace Squidex.Controllers.Api.Rules.Models.Triggers
{
public sealed class WebhookSchemaDto
public sealed class ContentChangedTriggerSchemaDto
{
/// <summary>
/// The id of the schema.

20
src/Squidex/Controllers/Api/Webhooks/Models/CreateWebhookDto.cs → src/Squidex/Controllers/Api/Rules/Models/UpdateRuleDto.cs

@ -1,29 +1,23 @@
// ==========================================================================
// CreateWebhookDto.cs
// UpdateRuleDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Webhooks.Models
namespace Squidex.Controllers.Api.Rules.Models
{
public sealed class CreateWebhookDto
public sealed class UpdateRuleDto
{
/// <summary>
/// The url of the webhook.
/// The trigger properties.
/// </summary>
[Required]
public Uri Url { get; set; }
public RuleTriggerDto Trigger { get; set; }
/// <summary>
/// The schema settings.
/// The action properties.
/// </summary>
[Required]
public List<WebhookSchemaDto> Schemas { get; set; }
public RuleActionDto Action { get; set; }
}
}

245
src/Squidex/Controllers/Api/Rules/RulesController.cs

@ -0,0 +1,245 @@
// ==========================================================================
// RulesController.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NodaTime;
using NSwag.Annotations;
using Squidex.Controllers.Api.Rules.Models;
using Squidex.Controllers.Api.Rules.Models.Converters;
using Squidex.Domain.Apps.Read.Rules.Repositories;
using Squidex.Domain.Apps.Write.Rules.Commands;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
namespace Squidex.Controllers.Api.Rules
{
/// <summary>
/// Manages and retrieves information about schemas.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Rules))]
[MustBeAppDeveloper]
public sealed class RulesController : ControllerBase
{
private readonly IRuleRepository rulesRepository;
private readonly IRuleEventRepository ruleEventsRepository;
public RulesController(ICommandBus commandBus,
IRuleRepository rulesRepository,
IRuleEventRepository ruleEventsRepository)
: base(commandBus)
{
this.rulesRepository = rulesRepository;
this.ruleEventsRepository = ruleEventsRepository;
}
/// <summary>
/// Get rules.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <returns>
/// 200 => Rules returned.
/// 404 => App not found.
/// </returns>
[HttpGet]
[Route("apps/{app}/rules/")]
[ProducesResponseType(typeof(RuleDto[]), 200)]
[ApiCosts(1)]
public async Task<IActionResult> GetRules(string app)
{
var rules = await rulesRepository.QueryByAppAsync(App.Id);
var response = rules.Select(r => r.ToModel());
return Ok(response);
}
/// <summary>
/// Create a new rule.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="request">The rule object that needs to be added to the app.</param>
/// <returns>
/// 201 => Rule created.
/// 400 => Rule is not valid.
/// 404 => App not found.
/// </returns>
[HttpPost]
[Route("apps/{app}/rules/")]
[ProducesResponseType(typeof(EntityCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiCosts(1)]
public async Task<IActionResult> PostRule(string app, [FromBody] CreateRuleDto request)
{
var command = request.ToCommand();
var context = await CommandBus.PublishAsync(command);
var result = context.Result<EntityCreatedResult<Guid>>();
var response = new EntityCreatedDto { Id = result.IdOrValue.ToString(), Version = result.Version };
return CreatedAtAction(nameof(GetRules), new { app }, response);
}
/// <summary>
/// Update a rule.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The id of the rule to update.</param>
/// <param name="request">The rule object that needs to be added to the app.</param>
/// <returns>
/// 204 => Rule updated.
/// 400 => Rule is not valid.
/// 404 => Rule or app not found.
/// </returns>
/// <remarks>
/// All events for the specified schemas will be sent to the url. The timeout is 2 seconds.
/// </remarks>
[HttpPut]
[Route("apps/{app}/rules/{id}/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiCosts(1)]
public async Task<IActionResult> PutRule(string app, Guid id, [FromBody] UpdateRuleDto request)
{
var command = request.ToCommand();
await CommandBus.PublishAsync(command);
return NoContent();
}
/// <summary>
/// Enable a rule.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The id of the rule to enable.</param>
/// <returns>
/// 204 => Rule enabled.
/// 400 => Rule already enabled.
/// 404 => Rule or app not found.
/// </returns>
[HttpPut]
[Route("apps/{app}/rules/{id}/enable/")]
[ApiCosts(1)]
public async Task<IActionResult> EnableRule(string app, Guid id)
{
await CommandBus.PublishAsync(new EnableRule { RuleId = id });
return NoContent();
}
/// <summary>
/// Disable a rule.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The id of the rule to disable.</param>
/// <returns>
/// 204 => Rule disabled.
/// 400 => Rule already disabled.
/// 404 => Rule or app not found.
/// </returns>
[HttpPut]
[Route("apps/{app}/rules/{id}/disable/")]
[ApiCosts(1)]
public async Task<IActionResult> DisableRule(string app, Guid id)
{
await CommandBus.PublishAsync(new DisableRule { RuleId = id });
return NoContent();
}
/// <summary>
/// Delete a rule.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The id of the rule to delete.</param>
/// <returns>
/// 204 => Rule has been deleted.
/// 404 => Rule or app not found.
/// </returns>
[HttpDelete]
[Route("apps/{app}/rules/{id}/")]
[ApiCosts(1)]
public async Task<IActionResult> DeleteRule(string app, Guid id)
{
await CommandBus.PublishAsync(new DeleteRule { RuleId = id });
return NoContent();
}
/// <summary>
/// Get rule events.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="skip">The number of events to skip.</param>
/// <param name="take">The number of events to take.</param>
/// <returns>
/// 200 => Rule events returned.
/// 404 => App not found.
/// </returns>
[HttpGet]
[Route("apps/{app}/rules/events/")]
[ProducesResponseType(typeof(RuleEventsDto), 200)]
[ApiCosts(0)]
public async Task<IActionResult> GetEvents(string app, [FromQuery] int skip = 0, [FromQuery] int take = 20)
{
var taskForItems = ruleEventsRepository.QueryByAppAsync(App.Id, skip, take);
var taskForCount = ruleEventsRepository.CountByAppAsync(App.Id);
await Task.WhenAll(taskForItems, taskForCount);
var response = new RuleEventsDto
{
Total = taskForCount.Result,
Items = taskForItems.Result.Select(x =>
{
var itemModel = new RuleEventDto();
SimpleMapper.Map(x, itemModel);
SimpleMapper.Map(x.Job, itemModel);
return itemModel;
}).ToArray()
};
return Ok(response);
}
/// <summary>
/// Enqueue the event to be send.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The event to enqueue.</param>
/// <returns>
/// 200 => Rule enqueued.
/// 404 => App or rule event not found.
/// </returns>
[HttpPut]
[Route("apps/{app}/rules/events/{id}/")]
[ApiCosts(0)]
public async Task<IActionResult> PutEvent(string app, Guid id)
{
var entity = await ruleEventsRepository.FindAsync(id);
if (entity == null)
{
return NotFound();
}
await ruleEventsRepository.EnqueueAsync(id, SystemClock.Instance.GetCurrentInstant());
return Ok();
}
}
}

1
src/Squidex/Controllers/Api/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs

@ -7,6 +7,7 @@
// ==========================================================================
using System.Linq;
using Squidex.Controllers.Api.Schemas.Models.Fields;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;

3
src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs

@ -9,7 +9,8 @@
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Squidex.Controllers.Api.Schemas.Models.Converters;
using NJsonSchema.Converters;
using Squidex.Controllers.Api.Schemas.Models.Fields;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Controllers.Api.Schemas.Models

2
src/Squidex/Controllers/Api/Schemas/Models/AssetsFieldPropertiesDto.cs → src/Squidex/Controllers/Api/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs

@ -10,7 +10,7 @@ using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models
namespace Squidex.Controllers.Api.Schemas.Models.Fields
{
[JsonSchema("Assets")]
public sealed class AssetsFieldPropertiesDto : FieldPropertiesDto

2
src/Squidex/Controllers/Api/Schemas/Models/BooleanFieldPropertiesDto.cs → src/Squidex/Controllers/Api/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs

@ -12,7 +12,7 @@ using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models
namespace Squidex.Controllers.Api.Schemas.Models.Fields
{
[JsonSchema("Boolean")]
public sealed class BooleanFieldPropertiesDto : FieldPropertiesDto

2
src/Squidex/Controllers/Api/Schemas/Models/DateTimeFieldPropertiesDto.cs → src/Squidex/Controllers/Api/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs

@ -13,7 +13,7 @@ using NodaTime;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models
namespace Squidex.Controllers.Api.Schemas.Models.Fields
{
[JsonSchema("DateTime")]
public sealed class DateTimeFieldPropertiesDto : FieldPropertiesDto

2
src/Squidex/Controllers/Api/Schemas/Models/GeolocationFieldPropertiesDto.cs → src/Squidex/Controllers/Api/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs

@ -12,7 +12,7 @@ using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models
namespace Squidex.Controllers.Api.Schemas.Models.Fields
{
[JsonSchema("Geolocation")]
public sealed class GeolocationFieldPropertiesDto : FieldPropertiesDto

2
src/Squidex/Controllers/Api/Schemas/Models/JsonFieldPropertiesDto.cs → src/Squidex/Controllers/Api/Schemas/Models/Fields/JsonFieldPropertiesDto.cs

@ -10,7 +10,7 @@ using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models
namespace Squidex.Controllers.Api.Schemas.Models.Fields
{
[JsonSchema("Json")]
public sealed class JsonFieldPropertiesDto : FieldPropertiesDto

2
src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs → src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberFieldPropertiesDto.cs

@ -12,7 +12,7 @@ using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models
namespace Squidex.Controllers.Api.Schemas.Models.Fields
{
[JsonSchema("Number")]
public sealed class NumberFieldPropertiesDto : FieldPropertiesDto

2
src/Squidex/Controllers/Api/Schemas/Models/ReferencesFieldPropertiesDto.cs → src/Squidex/Controllers/Api/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs

@ -11,7 +11,7 @@ using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models
namespace Squidex.Controllers.Api.Schemas.Models.Fields
{
[JsonSchema("References")]
public sealed class ReferencesFieldPropertiesDto : FieldPropertiesDto

2
src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs → src/Squidex/Controllers/Api/Schemas/Models/Fields/StringFieldPropertiesDto.cs

@ -12,7 +12,7 @@ using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models
namespace Squidex.Controllers.Api.Schemas.Models.Fields
{
[JsonSchema("String")]
public sealed class StringFieldPropertiesDto : FieldPropertiesDto

2
src/Squidex/Controllers/Api/Schemas/Models/TagsFieldPropertiesDto.cs → src/Squidex/Controllers/Api/Schemas/Models/Fields/TagsFieldPropertiesDto.cs

@ -10,7 +10,7 @@ using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models
namespace Squidex.Controllers.Api.Schemas.Models.Fields
{
[JsonSchema("Tags")]
public sealed class TagsFieldPropertiesDto : FieldPropertiesDto

89
src/Squidex/Controllers/Api/Webhooks/Models/WebhookDto.cs

@ -1,89 +0,0 @@
// ==========================================================================
// WebhookDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using NodaTime;
using Squidex.Infrastructure;
namespace Squidex.Controllers.Api.Webhooks.Models
{
public sealed class WebhookDto
{
/// <summary>
/// The id of the webhook.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// The user that has created the webhook.
/// </summary>
[Required]
public RefToken CreatedBy { get; set; }
/// <summary>
/// The user that has updated the webhook.
/// </summary>
[Required]
public RefToken LastModifiedBy { get; set; }
/// <summary>
/// The date and time when the webhook has been created.
/// </summary>
public Instant Created { get; set; }
/// <summary>
/// The date and time when the webhook has been modified last.
/// </summary>
public Instant LastModified { get; set; }
/// <summary>
/// The version of the webhook.
/// </summary>
public int Version { get; set; }
/// <summary>
/// The number of succceeded calls.
/// </summary>
public long TotalSucceeded { get; set; }
/// <summary>
/// The number of failed calls.
/// </summary>
public long TotalFailed { get; set; }
/// <summary>
/// The number of timedout calls.
/// </summary>
public long TotalTimedout { get; set; }
/// <summary>
/// The average response time in milliseconds.
/// </summary>
public long AverageRequestTimeMs { get; set; }
/// <summary>
/// The url of the webhook.
/// </summary>
[Required]
public Uri Url { get; set; }
/// <summary>
/// The shared secret that is used to calculate the signature.
/// </summary>
[Required]
public string SharedSecret { get; set; }
/// <summary>
/// The schema settings.
/// </summary>
[Required]
public List<WebhookSchemaDto> Schemas { get; set; }
}
}

220
src/Squidex/Controllers/Api/Webhooks/WebhooksController.cs

@ -1,220 +0,0 @@
// ==========================================================================
// WebhooksController.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NodaTime;
using NSwag.Annotations;
using Squidex.Controllers.Api.Webhooks.Models;
using Squidex.Domain.Apps.Core.Webhooks;
using Squidex.Domain.Apps.Read.Webhooks.Repositories;
using Squidex.Domain.Apps.Write.Webhooks.Commands;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
namespace Squidex.Controllers.Api.Webhooks
{
/// <summary>
/// Manages and retrieves information about schemas.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Webhooks))]
[MustBeAppDeveloper]
public sealed class WebhooksController : ControllerBase
{
private readonly IWebhookRepository webhooksRepository;
private readonly IWebhookEventRepository webhookEventsRepository;
public WebhooksController(ICommandBus commandBus,
IWebhookRepository webhooksRepository,
IWebhookEventRepository webhookEventsRepository)
: base(commandBus)
{
this.webhooksRepository = webhooksRepository;
this.webhookEventsRepository = webhookEventsRepository;
}
/// <summary>
/// Get webhooks.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <returns>
/// 200 => Webhooks returned.
/// 404 => App not found.
/// </returns>
[HttpGet]
[Route("apps/{app}/webhooks/")]
[ProducesResponseType(typeof(WebhookDto[]), 200)]
[ApiCosts(1)]
public async Task<IActionResult> GetWebhooks(string app)
{
var webhooks = await webhooksRepository.QueryByAppAsync(App.Id);
var response = webhooks.Select(w =>
{
var totalCount = w.TotalTimedout + w.TotalSucceeded + w.TotalFailed;
var totalAverage = totalCount == 0 ? 0 : w.TotalRequestTime / totalCount;
var schemas = w.Schemas.Select(s => SimpleMapper.Map(s, new WebhookSchemaDto())).ToList();
return SimpleMapper.Map(w, new WebhookDto { AverageRequestTimeMs = totalAverage, Schemas = schemas });
});
return Ok(response);
}
/// <summary>
/// Create a new webhook.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="request">The webhook object that needs to be added to the app.</param>
/// <returns>
/// 201 => Webhook created.
/// 400 => Webhook is not valid.
/// 404 => App not found.
/// </returns>
/// <remarks>
/// All events for the specified schemas will be sent to the url. The timeout is 2 seconds.
/// </remarks>
[HttpPost]
[Route("apps/{app}/webhooks/")]
[ProducesResponseType(typeof(EntityCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiCosts(1)]
public async Task<IActionResult> PostWebhook(string app, [FromBody] CreateWebhookDto request)
{
var schemas = request.Schemas.Select(s => SimpleMapper.Map(s, new WebhookSchema())).ToList();
var command = new CreateWebhook { Url = request.Url, Schemas = schemas };
var context = await CommandBus.PublishAsync(command);
var result = context.Result<EntityCreatedResult<Guid>>();
var response = new WebhookCreatedDto { Id = result.IdOrValue, SharedSecret = command.SharedSecret, Version = result.Version };
return CreatedAtAction(nameof(GetWebhooks), new { app }, response);
}
/// <summary>
/// Update a webhook.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The id of the webhook to update.</param>
/// <param name="request">The webhook object that needs to be added to the app.</param>
/// <returns>
/// 203 => Webhook updated.
/// 400 => Webhook is not valid.
/// 404 => Webhook or app not found.
/// </returns>
/// <remarks>
/// All events for the specified schemas will be sent to the url. The timeout is 2 seconds.
/// </remarks>
[HttpPut]
[Route("apps/{app}/webhooks/{id}/")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiCosts(1)]
public async Task<IActionResult> PutWebhook(string app, Guid id, [FromBody] CreateWebhookDto request)
{
var schemas = request.Schemas.Select(s => SimpleMapper.Map(s, new WebhookSchema())).ToList();
var command = new UpdateWebhook { WebhookId = id, Url = request.Url, Schemas = schemas };
await CommandBus.PublishAsync(command);
return NoContent();
}
/// <summary>
/// Delete a webhook.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The id of the webhook to delete.</param>
/// <returns>
/// 204 => Webhook has been deleted.
/// 404 => Webhook or app not found.
/// </returns>
[HttpDelete]
[Route("apps/{app}/webhooks/{id}/")]
[ApiCosts(1)]
public async Task<IActionResult> DeleteWebhook(string app, Guid id)
{
await CommandBus.PublishAsync(new DeleteWebhook { WebhookId = id });
return NoContent();
}
/// <summary>
/// Get webhook events.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="skip">The number of events to skip.</param>
/// <param name="take">The number of events to take.</param>
/// <returns>
/// 200 => Webhook events returned.
/// 404 => App not found.
/// </returns>
[HttpGet]
[Route("apps/{app}/webhooks/events/")]
[ProducesResponseType(typeof(WebhookEventsDto), 200)]
[ApiCosts(0)]
public async Task<IActionResult> GetEvents(string app, [FromQuery] int skip = 0, [FromQuery] int take = 20)
{
var taskForItems = webhookEventsRepository.QueryByAppAsync(App.Id, skip, take);
var taskForCount = webhookEventsRepository.CountByAppAsync(App.Id);
await Task.WhenAll(taskForItems, taskForCount);
var response = new WebhookEventsDto
{
Total = taskForCount.Result,
Items = taskForItems.Result.Select(x =>
{
var itemModel = new WebhookEventDto();
SimpleMapper.Map(x, itemModel);
SimpleMapper.Map(x.Job, itemModel);
return itemModel;
}).ToArray()
};
return Ok(response);
}
/// <summary>
/// Enqueue the event to be send.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The event to enqueue.</param>
/// <returns>
/// 200 => Webhook enqueued.
/// 404 => App or webhook event not found.
/// </returns>
[HttpPut]
[Route("apps/{app}/webhooks/events/{id}/")]
[ApiCosts(0)]
public async Task<IActionResult> PutEvent(string app, Guid id)
{
var entity = await webhookEventsRepository.FindAsync(id);
if (entity == null)
{
return NotFound();
}
await webhookEventsRepository.EnqueueAsync(id, SystemClock.Instance.GetCurrentInstant());
return Ok();
}
}
}

6
src/Squidex/Controllers/Api/Schemas/Models/Converters/JsonInheritanceConverter.cs → src/Squidex/Controllers/JsonInheritanceConverter2.cs

@ -16,9 +16,9 @@ using NJsonSchema.Annotations;
#pragma warning disable SA1306 // Field names must begin with lower-case letter
namespace Squidex.Controllers.Api.Schemas.Models.Converters
namespace Squidex.Controllers
{
public sealed class JsonInheritanceConverter : JsonConverter
public sealed class JsonInheritanceConverter2 : JsonConverter
{
private readonly string discriminator;
@ -54,7 +54,7 @@ namespace Squidex.Controllers.Api.Schemas.Models.Converters
}
}
public JsonInheritanceConverter(string discriminator)
public JsonInheritanceConverter2(string discriminator)
{
this.discriminator = discriminator;
}

102
tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleEnqueuerTests.cs

@ -0,0 +1,102 @@
// ==========================================================================
// RuleEnqueuerTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Domain.Apps.Read.Rules.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Xunit;
namespace Squidex.Domain.Apps.Read.Rules
{
public class RuleEnqueuerTests
{
private readonly IRuleRepository ruleRepository = A.Fake<IRuleRepository>();
private readonly IRuleEventRepository ruleEventRepository = A.Fake<IRuleEventRepository>();
private readonly RuleService ruleService = A.Fake<RuleService>();
private readonly Instant now = SystemClock.Instance.GetCurrentInstant();
private readonly NamedId<Guid> appId = new NamedId<Guid>(Guid.NewGuid(), "my-app");
private readonly RuleEnqueuer sut;
public RuleEnqueuerTests()
{
sut = new RuleEnqueuer(
ruleEventRepository,
ruleRepository,
ruleService);
}
[Fact]
public void Should_return_contents_filter_for_events_filter()
{
Assert.Equal(".*", sut.EventsFilter);
}
[Fact]
public void Should_return_type_name_for_name()
{
Assert.Equal(typeof(RuleEnqueuer).Name, sut.Name);
}
[Fact]
public Task Should_do_nothing_on_clear()
{
return sut.ClearAsync();
}
[Fact]
public async Task Should_update_repositories_on_with_jobs_from_sender()
{
var @event = Envelope.Create(new ContentCreated { AppId = appId });
var rule1 = new Rule(new ContentChangedTrigger(), new WebhookAction { Url = new Uri("https://squidex.io") });
var rule2 = new Rule(new ContentChangedTrigger(), new WebhookAction { Url = new Uri("https://squidex.io") });
var rule3 = new Rule(new ContentChangedTrigger(), new WebhookAction { Url = new Uri("https://squidex.io") });
var job1 = new RuleJob { Created = now };
var job2 = new RuleJob { Created = now };
var ruleEntity1 = A.Fake<IRuleEntity>();
var ruleEntity2 = A.Fake<IRuleEntity>();
var ruleEntity3 = A.Fake<IRuleEntity>();
A.CallTo(() => ruleEntity1.Rule).Returns(rule1);
A.CallTo(() => ruleEntity2.Rule).Returns(rule2);
A.CallTo(() => ruleEntity3.Rule).Returns(rule3);
A.CallTo(() => ruleRepository.QueryCachedByAppAsync(appId.Id))
.Returns(new List<IRuleEntity> { ruleEntity1, ruleEntity2, ruleEntity3 });
A.CallTo(() => ruleService.CreateJob(rule1, @event))
.Returns(job1);
A.CallTo(() => ruleService.CreateJob(rule2, @event))
.Returns(job2);
A.CallTo(() => ruleService.CreateJob(rule3, @event))
.Returns(null);
await sut.On(@event);
A.CallTo(() => ruleEventRepository.EnqueueAsync(job1, now))
.MustHaveHappened();
A.CallTo(() => ruleEventRepository.EnqueueAsync(job2, now))
.MustHaveHappened();
}
}
}
Loading…
Cancel
Save