diff --git a/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs index 844aca37b..96972bce5 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs @@ -47,7 +47,7 @@ namespace Squidex.Extensions.Actions.Comment { AppId = contentEvent.AppId, Actor = actor, - CommentsId = contentEvent.Id, + CommentsId = contentEvent.Id.ToString(), Text = text }; @@ -59,7 +59,7 @@ namespace Squidex.Extensions.Actions.Comment protected override async Task ExecuteJobAsync(CommentJob job, CancellationToken ct = default) { - if (job.CommentsId == Guid.Empty) + if (string.IsNullOrWhiteSpace(job.CommentsId)) { return Result.Ignored(); } @@ -78,7 +78,7 @@ namespace Squidex.Extensions.Actions.Comment public RefToken Actor { get; set; } - public Guid CommentsId { get; set; } + public string CommentsId { get; set; } public string Text { get; set; } } diff --git a/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationAction.cs b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationAction.cs new file mode 100644 index 000000000..e54e8d851 --- /dev/null +++ b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationAction.cs @@ -0,0 +1,43 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules; + +namespace Squidex.Extensions.Actions.Notification +{ + [RuleAction( + Title = "Notification", + IconImage = "", + IconColor = "#3389ff", + Display = "Send a notification", + Description = "Send an integrated notification to a user.")] + public sealed class NotificationAction : RuleAction + { + [Required] + [Display(Name = "User", Description = "The user id or email.")] + [DataType(DataType.Text)] + public string User { get; set; } + + [Required] + [Display(Name = "Title", Description = "The text to send.")] + [DataType(DataType.MultilineText)] + [Formattable] + public string Text { get; set; } + + [Display(Name = "Url", Description = "The optional url to attach to the notification.")] + [DataType(DataType.Text)] + [Formattable] + public string Url { get; set; } + + [Display(Name = "Client", Description = "An optional client name.")] + [DataType(DataType.Text)] + public string Client { get; set; } + } +} diff --git a/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs new file mode 100644 index 000000000..dda2475d7 --- /dev/null +++ b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs @@ -0,0 +1,102 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Entities.Comments.Commands; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Reflection; +using Squidex.Shared.Users; + +namespace Squidex.Extensions.Actions.Notification +{ + public sealed class NotificationActionHandler : RuleActionHandler + { + private const string Description = "Send a Notification"; + private static readonly NamedId NoApp = NamedId.Of(Guid.Empty, "none"); + private readonly ICommandBus commandBus; + private readonly IUserResolver userResolver; + + public NotificationActionHandler(RuleEventFormatter formatter, ICommandBus commandBus, IUserResolver userResolver) + : base(formatter) + { + Guard.NotNull(commandBus); + Guard.NotNull(userResolver); + + this.commandBus = commandBus; + + this.userResolver = userResolver; + } + + protected override async Task<(string Description, NotificationJob Data)> CreateJobAsync(EnrichedEvent @event, NotificationAction action) + { + if (@event is EnrichedUserEventBase userEvent) + { + var text = Format(action.Text, @event); + + var actor = userEvent.Actor; + + if (!string.IsNullOrEmpty(action.Client)) + { + actor = new RefToken(RefTokenType.Client, action.Client); + } + + var user = await userResolver.FindByIdOrEmailAsync(action.User); + + if (user == null) + { + throw new InvalidOperationException($"Cannot find user by '{action.User}'"); + } + + var ruleJob = new NotificationJob { Actor = actor, CommentsId = user.Id, Text = text }; + + if (!string.IsNullOrWhiteSpace(action.Url)) + { + var url = Format(action.Url, @event); + + if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri)) + { + ruleJob.Url = uri; + } + } + + return (Description, ruleJob); + } + + return ("Ignore", new NotificationJob()); + } + + protected override async Task ExecuteJobAsync(NotificationJob job, CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(job.CommentsId)) + { + return Result.Ignored(); + } + + var command = SimpleMapper.Map(job, new CreateComment { AppId = NoApp }); + + await commandBus.PublishAsync(command); + + return Result.Success($"Notified: {job.Text}"); + } + } + + public sealed class NotificationJob + { + public RefToken Actor { get; set; } + + public string CommentsId { get; set; } + + public string Text { get; set; } + + public Uri Url { get; set; } + } +} diff --git a/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationPlugin.cs b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationPlugin.cs new file mode 100644 index 000000000..d0b000911 --- /dev/null +++ b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationPlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.Notification +{ + public sealed class NotificationPlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration config) + { + services.AddRuleAction(); + } + } +} diff --git a/frontend/app/features/settings/pages/more/more-page.component.html b/frontend/app/features/settings/pages/more/more-page.component.html index 111542628..ae419a695 100644 --- a/frontend/app/features/settings/pages/more/more-page.component.html +++ b/frontend/app/features/settings/pages/more/more-page.component.html @@ -14,7 +14,7 @@
- +
@@ -55,9 +55,9 @@
- + - +