Browse Source

Better structure for notifications.

pull/355/head
Sebastian Stehle 8 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 System.Threading.Tasks;
using Squidex.Shared.Users; 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; } bool IsActive { get; }
Task SendNewUserEmailAsync(IUser assigner, IUser assignee, string appName); Task SendContributorEmailAsync(IUser assigner, IUser assignee, string appName, bool isCreated);
Task SendExistingUserEmailAsync(IUser assigner, IUser assignee, string appName);
} }
} }

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.Infrastructure.Tasks;
using Squidex.Shared.Users; 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 public bool IsActive
{ {
get { return false; } get { return false; }
} }
public Task SendExistingUserEmailAsync(IUser assigner, IUser assignee, string appName) public Task SendContributorEmailAsync(IUser assigner, IUser assignee, string appName, bool isCreated)
{
return TaskHelper.Done;
}
public Task SendNewUserEmailAsync(IUser assigner, IUser assignee, string appName)
{ {
return TaskHelper.Done; 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.Infrastructure.Tasks;
using Squidex.Shared.Users; 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 static readonly Duration MaxAge = Duration.FromDays(2);
private readonly IInvitationEmailSender emailSender; private readonly INotificationEmailSender emailSender;
private readonly IUserResolver userResolver; private readonly IUserResolver userResolver;
private readonly ISemanticLog log; private readonly ISemanticLog log;
public string Name public string Name
{ {
get { return "InvitationEmailSender"; } get { return "NotificationEmailSender"; }
} }
public string EventsFilter public string EventsFilter
@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
get { return "^app-"; } 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(emailSender, nameof(emailSender));
Guard.NotNull(userResolver, nameof(userResolver)); Guard.NotNull(userResolver, nameof(userResolver));
@ -104,14 +104,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
var appName = appContributorAssigned.AppId.Name; var appName = appContributorAssigned.AppId.Name;
if (appContributorAssigned.IsCreated) var isCreated = appContributorAssigned.IsCreated;
{
await emailSender.SendNewUserEmailAsync(assigner, assignee, appName); await emailSender.SendContributorEmailAsync(assigner, assignee, appName, isCreated);
}
else
{
await emailSender.SendExistingUserEmailAsync(assigner, assignee, appName);
}
} }
} }

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.Infrastructure.Log;
using Squidex.Shared.Users; 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 IEmailSender emailSender;
private readonly IEmailUrlGenerator emailUrlGenerator; private readonly IEmailUrlGenerator emailUrlGenerator;
private readonly ISemanticLog log; private readonly ISemanticLog log;
private readonly InvitationEmailTextOptions texts; private readonly NotificationEmailTextOptions texts;
public bool IsActive public bool IsActive
{ {
get { return true; } get { return true; }
} }
public InvitationEmailSender( public NotificationEmailSender(
IOptions<InvitationEmailTextOptions> texts, IOptions<NotificationEmailTextOptions> texts,
IEmailSender emailSender, IEmailSender emailSender,
IEmailUrlGenerator emailUrlGenerator, IEmailUrlGenerator emailUrlGenerator,
ISemanticLog log) ISemanticLog log)
@ -43,14 +43,20 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
this.log = log; 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) if (assignee.HasConsent())
{ {
return SendEmailAsync(texts.NewUserSubject, texts.NewUserBody, assigner, assignee, appName); 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) 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. // 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; } 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.GraphQL;
using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Domain.Apps.Entities.History; 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;
using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Rules.Indexes; using Squidex.Domain.Apps.Entities.Rules.Indexes;
@ -156,22 +157,22 @@ namespace Squidex.Config.Domain
{ {
services.AddSingleton(Options.Create(emailOptions)); services.AddSingleton(Options.Create(emailOptions));
services.Configure<InvitationEmailTextOptions>( services.Configure<NotificationEmailTextOptions>(
config.GetSection("email:invitations")); config.GetSection("email:notifications"));
services.AddSingletonAs<SmtpEmailSender>() services.AddSingletonAs<SmtpEmailSender>()
.As<IEmailSender>(); .As<IEmailSender>();
services.AddSingletonAs<InvitationEmailSender>() services.AddSingletonAs<NotificationEmailSender>()
.AsOptional<IInvitationEmailSender>(); .AsOptional<INotificationEmailSender>();
} }
else else
{ {
services.AddSingletonAs<NoopInvitationEmailSender>() services.AddSingletonAs<NoopNotificationEmailSender>()
.AsOptional<IInvitationEmailSender>(); .AsOptional<INotificationEmailSender>();
} }
services.AddSingletonAs<InvitationEmailEventConsumer>() services.AddSingletonAs<NotificationEmailEventConsumer>()
.As<IEventConsumer>(); .As<IEventConsumer>();
} }

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

@ -19,7 +19,9 @@
<h4 class="card-title">{{app.name}}</h4> <h4 class="card-title">{{app.name}}</h4>
<div class="card-text"> <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> </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.addAppTemplate = template;
this.addAppDialog.show(); this.addAppDialog.show();
} }
public stop(event: Event) {
event.stopPropagation();
}
} }

4
src/Squidex/appsettings.json

@ -96,7 +96,7 @@
*/ */
"port": 465 "port": 465
}, },
"invitations": { "notifications": {
/* /*
* The email subject when a new user is added as contributor. * 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. * 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 Squidex.Shared.Users;
using Xunit; 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 IUserResolver userResolver = A.Fake<IUserResolver>();
private readonly IUser assigner = A.Fake<IUser>(); private readonly IUser assigner = A.Fake<IUser>();
private readonly IUser assignee = 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 assignerId = Guid.NewGuid().ToString();
private readonly string assigneeId = Guid.NewGuid().ToString(); private readonly string assigneeId = Guid.NewGuid().ToString();
private readonly string appName = "my-app"; private readonly string appName = "my-app";
private readonly InvitationEmailEventConsumer sut; private readonly NotificationEmailEventConsumer sut;
public InvitationEmailEventConsumerTests() public NotificationEmailEventConsumerTests()
{ {
A.CallTo(() => emailSender.IsActive) A.CallTo(() => emailSender.IsActive)
.Returns(true); .Returns(true);
@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
A.CallTo(() => userResolver.FindByIdOrEmailAsync(assigneeId)) A.CallTo(() => userResolver.FindByIdOrEmailAsync(assigneeId))
.Returns(assignee); .Returns(assignee);
sut = new InvitationEmailEventConsumer(emailSender, userResolver, log); sut = new NotificationEmailEventConsumer(emailSender, userResolver, log);
} }
[Fact] [Fact]
@ -136,7 +136,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
await sut.On(@event); await sut.On(@event);
A.CallTo(() => emailSender.SendNewUserEmailAsync(assigner, assignee, appName)) A.CallTo(() => emailSender.SendContributorEmailAsync(assigner, assignee, appName, true))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -147,7 +147,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
await sut.On(@event); await sut.On(@event);
A.CallTo(() => emailSender.SendExistingUserEmailAsync(assigner, assignee, appName)) A.CallTo(() => emailSender.SendContributorEmailAsync(assigner, assignee, appName, false))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -165,10 +165,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
private void MustNotSendEmail() private void MustNotSendEmail()
{ {
A.CallTo(() => emailSender.SendNewUserEmailAsync(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();
A.CallTo(() => emailSender.SendExistingUserEmailAsync(A<IUser>.Ignored, A<IUser>.Ignored, A<string>.Ignored))
.MustNotHaveHappened(); .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 Squidex.Shared.Users;
using Xunit; 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 IEmailSender emailSender = A.Fake<IEmailSender>();
private readonly IEmailUrlGenerator emailUrlGenerator = A.Fake<IEmailUrlGenerator>(); private readonly IEmailUrlGenerator emailUrlGenerator = A.Fake<IEmailUrlGenerator>();
private readonly IUser assigner = A.Fake<IUser>(); private readonly IUser assigner = A.Fake<IUser>();
private readonly IUser assignee = A.Fake<IUser>(); private readonly IUser assignee = A.Fake<IUser>();
private readonly ISemanticLog log = A.Fake<ISemanticLog>(); 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 appName = "my-app";
private readonly string uiUrl = "my-ui"; private readonly string uiUrl = "my-ui";
private readonly InvitationEmailTextOptions texts = new InvitationEmailTextOptions(); private readonly NotificationEmailTextOptions texts = new NotificationEmailTextOptions();
private readonly InvitationEmailSender sut; private readonly NotificationEmailSender sut;
public InvitationEmailSenderTests() public NotificationEmailSenderTests()
{ {
A.CallTo(() => assigner.Email) A.CallTo(() => assigner.Email)
.Returns("sebastian@squidex.io"); .Returns("sebastian@squidex.io");
A.CallTo(() => assigner.Claims) A.CallTo(() => assigner.Claims)
.Returns(new List<Claim> { new Claim(SquidexClaimTypes.DisplayName, "Sebastian Stehle") }); .Returns(assignerClaims);
A.CallTo(() => assignee.Email) A.CallTo(() => assignee.Email)
.Returns("qaisar@squidex.io"); .Returns("qaisar@squidex.io");
A.CallTo(() => assignee.Claims) A.CallTo(() => assignee.Claims)
.Returns(new List<Claim> { new Claim(SquidexClaimTypes.DisplayName, "Qaisar Ahmad") }); .Returns(assigneeClaims);
A.CallTo(() => emailUrlGenerator.GenerateUIUrl()) A.CallTo(() => emailUrlGenerator.GenerateUIUrl())
.Returns(uiUrl); .Returns(uiUrl);
sut = new InvitationEmailSender(Options.Create(texts), emailSender, emailUrlGenerator, log); sut = new NotificationEmailSender(Options.Create(texts), emailSender, emailUrlGenerator, log);
} }
[Fact] [Fact]
@ -89,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
[Fact] [Fact]
public async Task Should_not_send_email_if_texts_for_new_user_are_empty() 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)) A.CallTo(() => emailSender.SendAsync(assignee.Email, A<string>.Ignored, A<string>.Ignored))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -100,7 +102,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
[Fact] [Fact]
public async Task Should_not_send_email_if_texts_for_existing_user_are_empty() 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)) A.CallTo(() => emailSender.SendAsync(assignee.Email, A<string>.Ignored, A<string>.Ignored))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -109,12 +111,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
} }
[Fact] [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.ExistingUserSubject = "email-subject";
texts.ExistingUserBody = "email-body"; 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")) A.CallTo(() => emailSender.SendAsync(assignee.Email, "email-subject", "email-body"))
.MustHaveHappened(); .MustHaveHappened();
@ -125,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
texts.NewUserSubject = pattern; texts.NewUserSubject = pattern;
texts.NewUserBody = 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)) A.CallTo(() => emailSender.SendAsync(assignee.Email, result, result))
.MustHaveHappened(); .MustHaveHappened();
Loading…
Cancel
Save