diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/IInvitationEmailSender.cs b/src/Squidex.Domain.Apps.Entities/History/Notifications/INotificationEmailSender.cs similarity index 63% rename from src/Squidex.Domain.Apps.Entities/Apps/Invitation/IInvitationEmailSender.cs rename to src/Squidex.Domain.Apps.Entities/History/Notifications/INotificationEmailSender.cs index 536ac9c07..9b837cb6f 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/IInvitationEmailSender.cs +++ b/src/Squidex.Domain.Apps.Entities/History/Notifications/INotificationEmailSender.cs @@ -8,14 +8,12 @@ using System.Threading.Tasks; using Squidex.Shared.Users; -namespace Squidex.Domain.Apps.Entities.Apps.Invitation +namespace Squidex.Domain.Apps.Entities.History.Notifications { - public interface IInvitationEmailSender + public interface INotificationEmailSender { bool IsActive { get; } - Task SendNewUserEmailAsync(IUser assigner, IUser assignee, string appName); - - Task SendExistingUserEmailAsync(IUser assigner, IUser assignee, string appName); + Task SendContributorEmailAsync(IUser assigner, IUser assignee, string appName, bool isCreated); } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/NoopInvitationEmailSender.cs b/src/Squidex.Domain.Apps.Entities/History/Notifications/NoopNotificationEmailSender.cs similarity index 62% rename from src/Squidex.Domain.Apps.Entities/Apps/Invitation/NoopInvitationEmailSender.cs rename to src/Squidex.Domain.Apps.Entities/History/Notifications/NoopNotificationEmailSender.cs index 899d8bdb6..f970ecb59 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/NoopInvitationEmailSender.cs +++ b/src/Squidex.Domain.Apps.Entities/History/Notifications/NoopNotificationEmailSender.cs @@ -9,21 +9,16 @@ using System.Threading.Tasks; using Squidex.Infrastructure.Tasks; using Squidex.Shared.Users; -namespace Squidex.Domain.Apps.Entities.Apps.Invitation +namespace Squidex.Domain.Apps.Entities.History.Notifications { - public sealed class NoopInvitationEmailSender : IInvitationEmailSender + public sealed class NoopNotificationEmailSender : INotificationEmailSender { public bool IsActive { get { return false; } } - public Task SendExistingUserEmailAsync(IUser assigner, IUser assignee, string appName) - { - return TaskHelper.Done; - } - - public Task SendNewUserEmailAsync(IUser assigner, IUser assignee, string appName) + public Task SendContributorEmailAsync(IUser assigner, IUser assignee, string appName, bool isCreated) { return TaskHelper.Done; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailEventConsumer.cs b/src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailEventConsumer.cs similarity index 82% rename from src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailEventConsumer.cs rename to src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailEventConsumer.cs index 58e726840..3a26e2814 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailEventConsumer.cs +++ b/src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailEventConsumer.cs @@ -14,18 +14,18 @@ using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Tasks; using Squidex.Shared.Users; -namespace Squidex.Domain.Apps.Entities.Apps.Invitation +namespace Squidex.Domain.Apps.Entities.History.Notifications { - public sealed class InvitationEmailEventConsumer : IEventConsumer + public sealed class NotificationEmailEventConsumer : IEventConsumer { private static readonly Duration MaxAge = Duration.FromDays(2); - private readonly IInvitationEmailSender emailSender; + private readonly INotificationEmailSender emailSender; private readonly IUserResolver userResolver; private readonly ISemanticLog log; public string Name { - get { return "InvitationEmailSender"; } + get { return "NotificationEmailSender"; } } public string EventsFilter @@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation get { return "^app-"; } } - public InvitationEmailEventConsumer(IInvitationEmailSender emailSender, IUserResolver userResolver, ISemanticLog log) + public NotificationEmailEventConsumer(INotificationEmailSender emailSender, IUserResolver userResolver, ISemanticLog log) { Guard.NotNull(emailSender, nameof(emailSender)); Guard.NotNull(userResolver, nameof(userResolver)); @@ -104,14 +104,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation var appName = appContributorAssigned.AppId.Name; - if (appContributorAssigned.IsCreated) - { - await emailSender.SendNewUserEmailAsync(assigner, assignee, appName); - } - else - { - await emailSender.SendExistingUserEmailAsync(assigner, assignee, appName); - } + var isCreated = appContributorAssigned.IsCreated; + + await emailSender.SendContributorEmailAsync(assigner, assignee, appName, isCreated); } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailSender.cs b/src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailSender.cs similarity index 77% rename from src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailSender.cs rename to src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailSender.cs index 21ffd7c0b..3ab75e9af 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailSender.cs +++ b/src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailSender.cs @@ -12,22 +12,22 @@ using Squidex.Infrastructure.Email; using Squidex.Infrastructure.Log; using Squidex.Shared.Users; -namespace Squidex.Domain.Apps.Entities.Apps.Invitation +namespace Squidex.Domain.Apps.Entities.History.Notifications { - public sealed class InvitationEmailSender : IInvitationEmailSender + public sealed class NotificationEmailSender : INotificationEmailSender { private readonly IEmailSender emailSender; private readonly IEmailUrlGenerator emailUrlGenerator; private readonly ISemanticLog log; - private readonly InvitationEmailTextOptions texts; + private readonly NotificationEmailTextOptions texts; public bool IsActive { get { return true; } } - public InvitationEmailSender( - IOptions texts, + public NotificationEmailSender( + IOptions texts, IEmailSender emailSender, IEmailUrlGenerator emailUrlGenerator, ISemanticLog log) @@ -43,14 +43,20 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation this.log = log; } - public Task SendExistingUserEmailAsync(IUser assigner, IUser assignee, string appName) + public Task SendContributorEmailAsync(IUser assigner, IUser assignee, string appName, bool isCreated) { - return SendEmailAsync(texts.ExistingUserSubject, texts.ExistingUserBody, assigner, assignee, appName); - } + Guard.NotNull(assigner, nameof(assigner)); + Guard.NotNull(assignee, nameof(assignee)); + Guard.NotNull(appName, nameof(appName)); - public Task SendNewUserEmailAsync(IUser assigner, IUser assignee, string appName) - { - return SendEmailAsync(texts.NewUserSubject, texts.NewUserBody, assigner, assignee, appName); + if (assignee.HasConsent()) + { + return SendEmailAsync(texts.ExistingUserSubject, texts.ExistingUserBody, assigner, assignee, appName); + } + else + { + return SendEmailAsync(texts.NewUserSubject, texts.NewUserBody, assigner, assignee, appName); + } } private async Task SendEmailAsync(string emailSubj, string emailBody, IUser assigner, IUser assignee, string appName) diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailTextOptions.cs b/src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailTextOptions.cs similarity index 83% rename from src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailTextOptions.cs rename to src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailTextOptions.cs index 8c564626d..cb56fe86d 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailTextOptions.cs +++ b/src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailTextOptions.cs @@ -5,9 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Entities.Apps.Invitation +namespace Squidex.Domain.Apps.Entities.History.Notifications { - public sealed class InvitationEmailTextOptions + public sealed class NotificationEmailTextOptions { public string NewUserSubject { get; set; } diff --git a/src/Squidex/Config/Domain/EntitiesServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs index 28f6665f2..1b378c173 100644 --- a/src/Squidex/Config/Domain/EntitiesServices.cs +++ b/src/Squidex/Config/Domain/EntitiesServices.cs @@ -35,6 +35,7 @@ using Squidex.Domain.Apps.Entities.Contents.Edm; using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.History; +using Squidex.Domain.Apps.Entities.History.Notifications; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.Indexes; @@ -156,22 +157,22 @@ namespace Squidex.Config.Domain { services.AddSingleton(Options.Create(emailOptions)); - services.Configure( - config.GetSection("email:invitations")); + services.Configure( + config.GetSection("email:notifications")); services.AddSingletonAs() .As(); - services.AddSingletonAs() - .AsOptional(); + services.AddSingletonAs() + .AsOptional(); } else { - services.AddSingletonAs() - .AsOptional(); + services.AddSingletonAs() + .AsOptional(); } - services.AddSingletonAs() + services.AddSingletonAs() .As(); } diff --git a/src/Squidex/app/features/apps/pages/apps-page.component.html b/src/Squidex/app/features/apps/pages/apps-page.component.html index 3e81c2ffa..37666ae09 100644 --- a/src/Squidex/app/features/apps/pages/apps-page.component.html +++ b/src/Squidex/app/features/apps/pages/apps-page.component.html @@ -19,7 +19,9 @@

{{app.name}}

- Edit + Content | + Assets | + Settings
diff --git a/src/Squidex/app/features/apps/pages/apps-page.component.ts b/src/Squidex/app/features/apps/pages/apps-page.component.ts index 5866dbe00..d5eba2e0a 100644 --- a/src/Squidex/app/features/apps/pages/apps-page.component.ts +++ b/src/Squidex/app/features/apps/pages/apps-page.component.ts @@ -73,4 +73,8 @@ export class AppsPageComponent implements OnInit { this.addAppTemplate = template; this.addAppDialog.show(); } + + public stop(event: Event) { + event.stopPropagation(); + } } \ No newline at end of file diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json index 932603d27..802116cc0 100644 --- a/src/Squidex/appsettings.json +++ b/src/Squidex/appsettings.json @@ -96,7 +96,7 @@ */ "port": 465 }, - "invitations": { + "notifications": { /* * The email subject when a new user is added as contributor. */ @@ -112,7 +112,7 @@ /* * The email body when an existing user is added as contributor. */ - "existingUserBody": "Dear User,\r\n\r\n{{$ASSIGNER_NAME}} ($ASSIGNER_EMAIL) has invited you to join App {{var:app_name}} at Squidex Headless CMS.\r\n\r\nLogin or reload the Management UI to see the App\r\n\r\nThank you very much,\r\nThe Squidex Team\r\n\r\n<> [$UI_URL]" + "existingUserBody": "Dear User,\r\n\r\n{{$ASSIGNER_NAME}} ($ASSIGNER_EMAIL) has invited you to join App {{var:app_name}} at Squidex Headless CMS.\r\n\r\nLogin or reload the Management UI to see the App.\r\n\r\nThank you very much,\r\nThe Squidex Team\r\n\r\n<> [$UI_URL]" } }, diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InvitationEmailEventConsumerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailEventConsumerTests.cs similarity index 86% rename from tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InvitationEmailEventConsumerTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailEventConsumerTests.cs index 2ce1ee4a9..815c45dab 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InvitationEmailEventConsumerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailEventConsumerTests.cs @@ -16,11 +16,11 @@ using Squidex.Infrastructure.Log; using Squidex.Shared.Users; using Xunit; -namespace Squidex.Domain.Apps.Entities.Apps.Invitation +namespace Squidex.Domain.Apps.Entities.History.Notifications { - public class InvitationEmailEventConsumerTests + public class NotificationEmailEventConsumerTests { - private readonly IInvitationEmailSender emailSender = A.Fake(); + private readonly INotificationEmailSender emailSender = A.Fake(); private readonly IUserResolver userResolver = A.Fake(); private readonly IUser assigner = A.Fake(); private readonly IUser assignee = A.Fake(); @@ -28,9 +28,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation private readonly string assignerId = Guid.NewGuid().ToString(); private readonly string assigneeId = Guid.NewGuid().ToString(); private readonly string appName = "my-app"; - private readonly InvitationEmailEventConsumer sut; + private readonly NotificationEmailEventConsumer sut; - public InvitationEmailEventConsumerTests() + public NotificationEmailEventConsumerTests() { A.CallTo(() => emailSender.IsActive) .Returns(true); @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation A.CallTo(() => userResolver.FindByIdOrEmailAsync(assigneeId)) .Returns(assignee); - sut = new InvitationEmailEventConsumer(emailSender, userResolver, log); + sut = new NotificationEmailEventConsumer(emailSender, userResolver, log); } [Fact] @@ -136,7 +136,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation await sut.On(@event); - A.CallTo(() => emailSender.SendNewUserEmailAsync(assigner, assignee, appName)) + A.CallTo(() => emailSender.SendContributorEmailAsync(assigner, assignee, appName, true)) .MustHaveHappened(); } @@ -147,7 +147,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation await sut.On(@event); - A.CallTo(() => emailSender.SendExistingUserEmailAsync(assigner, assignee, appName)) + A.CallTo(() => emailSender.SendContributorEmailAsync(assigner, assignee, appName, false)) .MustHaveHappened(); } @@ -165,10 +165,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation private void MustNotSendEmail() { - A.CallTo(() => emailSender.SendNewUserEmailAsync(A.Ignored, A.Ignored, A.Ignored)) - .MustNotHaveHappened(); - - A.CallTo(() => emailSender.SendExistingUserEmailAsync(A.Ignored, A.Ignored, A.Ignored)) + A.CallTo(() => emailSender.SendContributorEmailAsync(A.Ignored, A.Ignored, A.Ignored, A.Ignored)) .MustNotHaveHappened(); } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InvitationEmailSenderTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailSenderTests.cs similarity index 76% rename from tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InvitationEmailSenderTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailSenderTests.cs index 9c50f13fc..7ed9d09b9 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InvitationEmailSenderTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailSenderTests.cs @@ -18,36 +18,38 @@ using Squidex.Shared.Identity; using Squidex.Shared.Users; using Xunit; -namespace Squidex.Domain.Apps.Entities.Apps.Invitation +namespace Squidex.Domain.Apps.Entities.History.Notifications { - public class InvitationEmailSenderTests + public class NotificationEmailSenderTests { private readonly IEmailSender emailSender = A.Fake(); private readonly IEmailUrlGenerator emailUrlGenerator = A.Fake(); private readonly IUser assigner = A.Fake(); private readonly IUser assignee = A.Fake(); private readonly ISemanticLog log = A.Fake(); + private readonly List assignerClaims = new List { new Claim(SquidexClaimTypes.DisplayName, "Sebastian Stehle") }; + private readonly List assigneeClaims = new List { new Claim(SquidexClaimTypes.DisplayName, "Qaisar Ahmad") }; private readonly string appName = "my-app"; private readonly string uiUrl = "my-ui"; - private readonly InvitationEmailTextOptions texts = new InvitationEmailTextOptions(); - private readonly InvitationEmailSender sut; + private readonly NotificationEmailTextOptions texts = new NotificationEmailTextOptions(); + private readonly NotificationEmailSender sut; - public InvitationEmailSenderTests() + public NotificationEmailSenderTests() { A.CallTo(() => assigner.Email) .Returns("sebastian@squidex.io"); A.CallTo(() => assigner.Claims) - .Returns(new List { new Claim(SquidexClaimTypes.DisplayName, "Sebastian Stehle") }); + .Returns(assignerClaims); A.CallTo(() => assignee.Email) .Returns("qaisar@squidex.io"); A.CallTo(() => assignee.Claims) - .Returns(new List { new Claim(SquidexClaimTypes.DisplayName, "Qaisar Ahmad") }); + .Returns(assigneeClaims); A.CallTo(() => emailUrlGenerator.GenerateUIUrl()) .Returns(uiUrl); - sut = new InvitationEmailSender(Options.Create(texts), emailSender, emailUrlGenerator, log); + sut = new NotificationEmailSender(Options.Create(texts), emailSender, emailUrlGenerator, log); } [Fact] @@ -89,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation [Fact] public async Task Should_not_send_email_if_texts_for_new_user_are_empty() { - await sut.SendNewUserEmailAsync(assigner, assignee, appName); + await sut.SendContributorEmailAsync(assigner, assignee, appName, true); A.CallTo(() => emailSender.SendAsync(assignee.Email, A.Ignored, A.Ignored)) .MustNotHaveHappened(); @@ -100,7 +102,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation [Fact] public async Task Should_not_send_email_if_texts_for_existing_user_are_empty() { - await sut.SendExistingUserEmailAsync(assigner, assignee, appName); + await sut.SendContributorEmailAsync(assigner, assignee, appName, true); A.CallTo(() => emailSender.SendAsync(assignee.Email, A.Ignored, A.Ignored)) .MustNotHaveHappened(); @@ -109,12 +111,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation } [Fact] - public async Task Should_send_email_for_existing_user() + public async Task Should_send_email_when_consent_given() { + assigneeClaims.Add(new Claim(SquidexClaimTypes.Consent, "True")); + texts.ExistingUserSubject = "email-subject"; texts.ExistingUserBody = "email-body"; - await sut.SendExistingUserEmailAsync(assigner, assignee, appName); + await sut.SendContributorEmailAsync(assigner, assignee, appName, true); A.CallTo(() => emailSender.SendAsync(assignee.Email, "email-subject", "email-body")) .MustHaveHappened(); @@ -125,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation texts.NewUserSubject = pattern; texts.NewUserBody = pattern; - await sut.SendNewUserEmailAsync(assigner, assignee, appName); + await sut.SendContributorEmailAsync(assigner, assignee, appName, true); A.CallTo(() => emailSender.SendAsync(assignee.Email, result, result)) .MustHaveHappened();