Browse Source

Better structure for notifications.

pull/355/head
Sebastian Stehle 7 years ago
parent
commit
858c9a952e
  1. 8
      src/Squidex.Domain.Apps.Entities/History/Notifications/INotificationEmailSender.cs
  2. 11
      src/Squidex.Domain.Apps.Entities/History/Notifications/NoopNotificationEmailSender.cs
  3. 21
      src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailEventConsumer.cs
  4. 28
      src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailSender.cs
  5. 4
      src/Squidex.Domain.Apps.Entities/History/Notifications/NotificationEmailTextOptions.cs
  6. 15
      src/Squidex/Config/Domain/EntitiesServices.cs
  7. 4
      src/Squidex/app/features/apps/pages/apps-page.component.html
  8. 4
      src/Squidex/app/features/apps/pages/apps-page.component.ts
  9. 4
      src/Squidex/appsettings.json
  10. 21
      tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailEventConsumerTests.cs
  11. 30
      tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailSenderTests.cs

8
src/Squidex.Domain.Apps.Entities/Apps/Invitation/IInvitationEmailSender.cs → 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);
}
}

11
src/Squidex.Domain.Apps.Entities/Apps/Invitation/NoopInvitationEmailSender.cs → 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;
}

21
src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailEventConsumer.cs → 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);
}
}

28
src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailSender.cs → 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<InvitationEmailTextOptions> texts,
public NotificationEmailSender(
IOptions<NotificationEmailTextOptions> 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)

4
src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEmailTextOptions.cs → 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; }

15
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<InvitationEmailTextOptions>(
config.GetSection("email:invitations"));
services.Configure<NotificationEmailTextOptions>(
config.GetSection("email:notifications"));
services.AddSingletonAs<SmtpEmailSender>()
.As<IEmailSender>();
services.AddSingletonAs<InvitationEmailSender>()
.AsOptional<IInvitationEmailSender>();
services.AddSingletonAs<NotificationEmailSender>()
.AsOptional<INotificationEmailSender>();
}
else
{
services.AddSingletonAs<NoopInvitationEmailSender>()
.AsOptional<IInvitationEmailSender>();
services.AddSingletonAs<NoopNotificationEmailSender>()
.AsOptional<INotificationEmailSender>();
}
services.AddSingletonAs<InvitationEmailEventConsumer>()
services.AddSingletonAs<NotificationEmailEventConsumer>()
.As<IEventConsumer>();
}

4
src/Squidex/app/features/apps/pages/apps-page.component.html

@ -19,7 +19,9 @@
<h4 class="card-title">{{app.name}}</h4>
<div class="card-text">
<a [routerLink]="['/app', app.name]">Edit</a>
<a (click)="stop($event)" [routerLink]="['/app', app.name, 'content']">Content</a> |
<a (click)="stop($event)" [routerLink]="['/app', app.name, 'assets']">Assets</a> |
<a (click)="stop($event)" [routerLink]="['/app', app.name, 'settings']">Settings</a>
</div>
</div>
</div>

4
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();
}
}

4
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<<Start now!>> [$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<<Start now!>> [$UI_URL]"
}
},

21
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InvitationEmailEventConsumerTests.cs → 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<IInvitationEmailSender>();
private readonly INotificationEmailSender emailSender = A.Fake<INotificationEmailSender>();
private readonly IUserResolver userResolver = A.Fake<IUserResolver>();
private readonly IUser assigner = A.Fake<IUser>();
private readonly IUser assignee = A.Fake<IUser>();
@ -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<IUser>.Ignored, A<IUser>.Ignored, A<string>.Ignored))
.MustNotHaveHappened();
A.CallTo(() => emailSender.SendExistingUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, A<string>.Ignored))
A.CallTo(() => emailSender.SendContributorEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, A<string>.Ignored, A<bool>.Ignored))
.MustNotHaveHappened();
}

30
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InvitationEmailSenderTests.cs → 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<IEmailSender>();
private readonly IEmailUrlGenerator emailUrlGenerator = A.Fake<IEmailUrlGenerator>();
private readonly IUser assigner = A.Fake<IUser>();
private readonly IUser assignee = A.Fake<IUser>();
private readonly ISemanticLog log = A.Fake<ISemanticLog>();
private readonly List<Claim> assignerClaims = new List<Claim> { new Claim(SquidexClaimTypes.DisplayName, "Sebastian Stehle") };
private readonly List<Claim> assigneeClaims = new List<Claim> { 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<Claim> { new Claim(SquidexClaimTypes.DisplayName, "Sebastian Stehle") });
.Returns(assignerClaims);
A.CallTo(() => assignee.Email)
.Returns("qaisar@squidex.io");
A.CallTo(() => assignee.Claims)
.Returns(new List<Claim> { 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<string>.Ignored, A<string>.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<string>.Ignored, A<string>.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();
Loading…
Cancel
Save