// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using NodaTime; using Squidex.Areas.Api.Controllers.Rules.Models; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Rules { /// /// Manages and retrieves information about schemas. /// [ApiExplorerSettings(GroupName = nameof(Rules))] public sealed class RulesController : ApiController { private readonly IAppProvider appProvider; private readonly IRuleEventRepository ruleEventsRepository; private readonly RuleRegistry ruleRegistry; public RulesController(ICommandBus commandBus, IAppProvider appProvider, IRuleEventRepository ruleEventsRepository, RuleRegistry ruleRegistry) : base(commandBus) { this.appProvider = appProvider; this.ruleEventsRepository = ruleEventsRepository; this.ruleRegistry = ruleRegistry; } /// /// Get the supported rule actions. /// /// /// 200 => Rule actions returned. /// [HttpGet] [Route("rules/actions/")] [ProducesResponseType(typeof(Dictionary), 200)] [ApiPermission] [ApiCosts(0)] public IActionResult GetActions() { var etag = string.Join(";", ruleRegistry.Actions.Select(x => x.Key)).Sha256Base64(); var response = ruleRegistry.Actions.ToDictionary(x => x.Key, x => RuleElementDto.FromDefinition(x.Value)); Response.Headers[HeaderNames.ETag] = etag; return Ok(response); } /// /// Get rules. /// /// The name of the app. /// /// 200 => Rules returned. /// 404 => App not found. /// [HttpGet] [Route("apps/{app}/rules/")] [ProducesResponseType(typeof(RulesDto), 200)] [ApiPermission(Permissions.AppRulesRead)] [ApiCosts(1)] public async Task GetRules(string app) { var rules = await appProvider.GetRulesAsync(AppId); var response = RulesDto.FromRules(rules, this, app); Response.Headers[HeaderNames.ETag] = response.GenerateEtag(); return Ok(response); } /// /// Create a new rule. /// /// The name of the app. /// The rule object that needs to be added to the app. /// /// 201 => Rule created. /// 400 => Rule is not valid. /// 404 => App not found. /// [HttpPost] [Route("apps/{app}/rules/")] [ProducesResponseType(typeof(RuleDto), 201)] [ApiPermission(Permissions.AppRulesCreate)] [ApiCosts(1)] public async Task PostRule(string app, [FromBody] CreateRuleDto request) { var command = request.ToCommand(); var response = await InvokeCommandAsync(app, command); return CreatedAtAction(nameof(GetRules), new { app }, response); } /// /// Update a rule. /// /// The name of the app. /// The id of the rule to update. /// The rule object that needs to be added to the app. /// /// 200 => Rule updated. /// 400 => Rule is not valid. /// 404 => Rule or app not found. /// /// /// All events for the specified schemas will be sent to the url. The timeout is 2 seconds. /// [HttpPut] [Route("apps/{app}/rules/{id}/")] [ProducesResponseType(typeof(RuleDto), 400)] [ApiPermission(Permissions.AppRulesUpdate)] [ApiCosts(1)] public async Task PutRule(string app, Guid id, [FromBody] UpdateRuleDto request) { var command = request.ToCommand(id); var response = await InvokeCommandAsync(app, command); return Ok(response); } /// /// Enable a rule. /// /// The name of the app. /// The id of the rule to enable. /// /// 200 => Rule enabled. /// 400 => Rule already enabled. /// 404 => Rule or app not found. /// [HttpPut] [Route("apps/{app}/rules/{id}/enable/")] [ProducesResponseType(typeof(RuleDto), 200)] [ApiPermission(Permissions.AppRulesDisable)] [ApiCosts(1)] public async Task EnableRule(string app, Guid id) { var command = new EnableRule { RuleId = id }; var response = await InvokeCommandAsync(app, command); return Ok(response); } /// /// Disable a rule. /// /// The name of the app. /// The id of the rule to disable. /// /// 200 => Rule disabled. /// 400 => Rule already disabled. /// 404 => Rule or app not found. /// [HttpPut] [Route("apps/{app}/rules/{id}/disable/")] [ProducesResponseType(typeof(RuleDto), 200)] [ApiPermission(Permissions.AppRulesDisable)] [ApiCosts(1)] public async Task DisableRule(string app, Guid id) { var command = new DisableRule { RuleId = id }; var response = await InvokeCommandAsync(app, command); return Ok(response); } /// /// Delete a rule. /// /// The name of the app. /// The id of the rule to delete. /// /// 204 => Rule deleted. /// 404 => Rule or app not found. /// [HttpDelete] [Route("apps/{app}/rules/{id}/")] [ApiPermission(Permissions.AppRulesDelete)] [ApiCosts(1)] public async Task DeleteRule(string app, Guid id) { await CommandBus.PublishAsync(new DeleteRule { RuleId = id }); return NoContent(); } /// /// Get rule events. /// /// The name of the app. /// The number of events to skip. /// The number of events to take. /// /// 200 => Rule events returned. /// 404 => App not found. /// [HttpGet] [Route("apps/{app}/rules/events/")] [ProducesResponseType(typeof(RuleEventsDto), 200)] [ApiPermission(Permissions.AppRulesRead)] [ApiCosts(0)] public async Task GetEvents(string app, [FromQuery] int skip = 0, [FromQuery] int take = 20) { var taskForItems = ruleEventsRepository.QueryByAppAsync(AppId, skip, take); var taskForCount = ruleEventsRepository.CountByAppAsync(AppId); await Task.WhenAll(taskForItems, taskForCount); var response = RuleEventsDto.FromRuleEvents(taskForItems.Result, taskForCount.Result, this, app); return Ok(response); } /// /// Retry the event immediately. /// /// The name of the app. /// The event to enqueue. /// /// 204 => Rule enqueued. /// 404 => App or rule event not found. /// [HttpPut] [Route("apps/{app}/rules/events/{id}/")] [ApiPermission(Permissions.AppRulesEvents)] [ApiCosts(0)] public async Task PutEvent(string app, Guid id) { var ruleEvent = await ruleEventsRepository.FindAsync(id); if (ruleEvent == null) { return NotFound(); } await ruleEventsRepository.EnqueueAsync(id, SystemClock.Instance.GetCurrentInstant()); return NoContent(); } /// /// Cancels the event and retries. /// /// The name of the app. /// The event to enqueue. /// /// 204 => Rule deqeued. /// 404 => App or rule event not found. /// [HttpDelete] [Route("apps/{app}/rules/events/{id}/")] [ApiPermission(Permissions.AppRulesEvents)] [ApiCosts(0)] public async Task DeleteEvent(string app, Guid id) { var ruleEvent = await ruleEventsRepository.FindAsync(id); if (ruleEvent == null) { return NotFound(); } await ruleEventsRepository.CancelAsync(id); return NoContent(); } private async Task InvokeCommandAsync(string app, ICommand command) { var context = await CommandBus.PublishAsync(command); var result = context.Result(); var response = RuleDto.FromRule(result, this, app); return response; } } }