Skip to content

Commit

Permalink
Merge pull request #6 from leancodepl/feature/low-rate-email
Browse files Browse the repository at this point in the history
Low rate email to admin
  • Loading branch information
lukaszgarstecki authored Nov 29, 2023
2 parents a31dd76 + dd87fb8 commit a1d3f0e
Show file tree
Hide file tree
Showing 18 changed files with 233 additions and 25 deletions.
3 changes: 3 additions & 0 deletions backend/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
<PackageReference Update="LeanCode.Components" Version="$(CoreLibVersion)" />
<PackageReference Update="LeanCode.CQRS.AspNetCore" Version="$(CoreLibVersion)" />
<PackageReference Update="LeanCode.CQRS.Execution" Version="$(CoreLibVersion)" />
<PackageReference Update="LeanCode.CQRS.MassTransitRelay" Version="$(CoreLibVersion)" />
<PackageReference Update="LeanCode.CQRS.Validation.Fluent" Version="$(CoreLibVersion)" />
<PackageReference Update="LeanCode.DomainModels.EF" Version="$(CoreLibVersion)" />
<PackageReference Update="LeanCode.SendGrid" Version="$(CoreLibVersion)" />
<PackageReference Update="LeanCode.TimeProvider" Version="$(CoreLibVersion)" />
</ItemGroup>

<ItemGroup>
<PackageReference Update="FluentValidation" Version="11.5.1" />
<PackageReference Update="MassTransit" Version="8.1.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore" Version="$(EFCoreVersion)" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Relational" Version="$(EFCoreVersion)" />
<PackageReference Update="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
using System.Reflection;
using FluentValidation;
using LeanCode.AppRating.Contracts;
using LeanCode.AppRating.CQRS;
using LeanCode.AppRating.DataAccess;
using LeanCode.AppRating.Handlers;
using LeanCode.CQRS.AspNetCore;
using LeanCode.CQRS.Validation;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace LeanCode.AppRating.Configuration;

public sealed record class AppRatingReportsConfiguration(
double LowRatingUpperBoundInclusive,
string LowRatingEmailCulture,
string LowRatingEmailSubjectKey,
string FromEmail,
string[] ToEmails,
string[] BccEmails
) { }
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace LeanCode.AppRating.DataAccess;

public sealed record class AppRatingEntity<TUserId>(
public sealed record class AppRating<TUserId>(
TUserId UserId,
DateTimeOffset DateCreated,
double Rating,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace LeanCode.AppRating.DataAccess;
public interface IAppRatingStore<TUserId>
where TUserId : notnull, IEquatable<TUserId>
{
public DbSet<AppRatingEntity<TUserId>> AppRatings { get; }
public DbSet<AppRating<TUserId>> AppRatings { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static class ModelBuilderExtensions
public static void ConfigureAppRatingEntity<TUserId>(this ModelBuilder builder, SqlDbType sqlDbType)
where TUserId : notnull, IEquatable<TUserId>
{
builder.Entity<AppRatingEntity<TUserId>>(c =>
builder.Entity<AppRating<TUserId>>(c =>
{
c.HasKey(e => new { e.UserId, e.DateCreated });

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace LeanCode.AppRating.EmailViewModels;

public class LowRateSubmittedEmail
{
public double Rating { get; set; }
public string UserId { get; set; } = null!;
public string? AdditionalComment { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;

namespace LeanCode.AppRating.CQRS;
namespace LeanCode.AppRating.Handlers;

public class RatingAlreadySentQH<TUserId> : IQueryHandler<RatingAlreadySent, bool>
where TUserId : notnull, IEquatable<TUserId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using LeanCode.AppRating.Configuration;
using LeanCode.AppRating.EmailViewModels;
using LeanCode.SendGrid;
using MassTransit;
using SendGrid.Helpers.Mail;
using Serilog;

namespace LeanCode.AppRating.Handlers;

public class SendEmailOnLowRateSubmittedEH<TUserId> : IConsumer<LowRateSubmitted<TUserId>>
where TUserId : notnull, IEquatable<TUserId>
{
private readonly ILogger logger = Log.ForContext<SendEmailOnLowRateSubmittedEH<TUserId>>();
private readonly SendGridRazorClient sendGridRazorClient;
private readonly AppRatingReportsConfiguration appRatingReportsConfiguration;

public SendEmailOnLowRateSubmittedEH(
SendGridRazorClient sendGridRazorClient,
AppRatingReportsConfiguration appRatingReportsConfiguration
)
{
this.sendGridRazorClient = sendGridRazorClient;
this.appRatingReportsConfiguration = appRatingReportsConfiguration;
}

public async Task Consume(ConsumeContext<LowRateSubmitted<TUserId>> context)
{
var vm = new LowRateSubmittedEmail
{
UserId = context.Message.UserId.ToString()!,
AdditionalComment = context.Message.AdditionalComment,
Rating = context.Message.Rating,
};

var message = new SendGridLocalizedRazorMessage(appRatingReportsConfiguration.LowRatingEmailCulture)
.WithSubject(appRatingReportsConfiguration.LowRatingEmailSubjectKey)
.WithSender(appRatingReportsConfiguration.FromEmail)
.WithRecipients(
appRatingReportsConfiguration.ToEmails.Select(e => new EmailAddress() { Email = e, }).ToList()
)
.WithBlindCarbonCopyRecipients(
appRatingReportsConfiguration.BccEmails.Select(e => new EmailAddress() { Email = e, }).ToList()
)
.WithHtmlContent(vm)
.WithPlainTextContent(vm)
.WithNoTracking();

await sendGridRazorClient.SendEmailAsync(message, context.CancellationToken);
logger.Information("Email about low rating from user {UserId} sent", context.Message.UserId);
}
}

public sealed record class LowRateSubmitted<TUserId>(TUserId UserId, double Rating, string? AdditionalComment)
where TUserId : notnull, IEquatable<TUserId>;
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using System.Collections.Immutable;
using System.Runtime.InteropServices;
using FluentValidation;
using LeanCode.AppRating.Configuration;
using LeanCode.AppRating.Contracts;
using LeanCode.AppRating.DataAccess;
using LeanCode.CQRS.Execution;
using LeanCode.CQRS.Validation.Fluent;
using LeanCode.TimeProvider;
using MassTransit;
using Microsoft.AspNetCore.Http;

namespace LeanCode.AppRating.CQRS;
namespace LeanCode.AppRating.Handlers;

public class SubmitAppRatingCV : AbstractValidator<SubmitAppRating>
{
Expand Down Expand Up @@ -38,20 +41,31 @@ public class SubmitAppRatingCH<TUserId> : ICommandHandler<SubmitAppRating>
{
private readonly IAppRatingStore<TUserId> store;
private readonly IUserIdExtractor<TUserId> extractor;
private readonly IPublishEndpoint publishEndpoint;
private readonly AppRatingReportsConfiguration appRatingReportsConfiguration;

public SubmitAppRatingCH(IAppRatingStore<TUserId> store, IUserIdExtractor<TUserId> extractor)
public SubmitAppRatingCH(
IAppRatingStore<TUserId> store,
IUserIdExtractor<TUserId> extractor,
IPublishEndpoint publishEndpoint,
AppRatingReportsConfiguration appRatingReportsConfiguration
)
{
this.store = store;
this.extractor = extractor;
this.publishEndpoint = publishEndpoint;
this.appRatingReportsConfiguration = appRatingReportsConfiguration;
}

public Task ExecuteAsync(HttpContext context, SubmitAppRating command)
public async Task ExecuteAsync(HttpContext context, SubmitAppRating command)
{
var userId = extractor.Extract(context);

store
.AppRatings
.Add(
new AppRatingEntity<TUserId>(
extractor.Extract(context),
new AppRating<TUserId>(
userId,
Time.NowWithOffset,
command.Rating,
command.AdditionalComment,
Expand All @@ -62,6 +76,12 @@ public Task ExecuteAsync(HttpContext context, SubmitAppRating command)
)
);

return Task.CompletedTask;
if (command.Rating < appRatingReportsConfiguration.LowRatingUpperBoundInclusive)
{
await publishEndpoint.Publish(
new LowRateSubmitted<TUserId>(userId, command.Rating, command.AdditionalComment),
context.RequestAborted
);
}
}
}
3 changes: 3 additions & 0 deletions backend/src/LeanCode.AppRating/LeanCode.AppRating.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
<PackageReference Include="LeanCode.Components" />
<PackageReference Include="LeanCode.CQRS.AspNetCore" />
<PackageReference Include="LeanCode.CQRS.Execution" />
<PackageReference Include="LeanCode.CQRS.MassTransitRelay" />
<PackageReference Include="LeanCode.CQRS.Validation.Fluent" />
<PackageReference Include="LeanCode.SendGrid" />
<PackageReference Include="LeanCode.TimeProvider" />

<ProjectReference Include="../LeanCode.AppRating.Contracts/LeanCode.AppRating.Contracts.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentValidation" />
<PackageReference Include="MassTransit" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
<PackageReference Include="Serilog" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using LeanCode.AppRating.Handlers;
using MassTransit;

namespace LeanCode.AppRating;

public static class MassTransitRegistrationConfigurationExtensions
{
public static void AddAppRatingConsumers<TUserId>(this IRegistrationConfigurator configurator)
where TUserId : notnull, IEquatable<TUserId>
{
configurator.AddConsumer(
typeof(SendEmailOnLowRateSubmittedEH<TUserId>),
typeof(AppRatingConsumerDefinition<SendEmailOnLowRateSubmittedEH<TUserId>>)
);
}
}

public class AppRatingConsumerDefinition<TConsumer> : ConsumerDefinition<TConsumer>
where TConsumer : class, IConsumer
{
protected override void ConfigureConsumer(
IReceiveEndpointConfigurator endpointConfigurator,
IConsumerConfigurator<TConsumer> consumerConfigurator,
IRegistrationContext context
)
{
endpointConfigurator.UseMessageRetry(
r => r.Immediate(1).Incremental(3, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using LeanCode.SendGrid;
using SendGrid.Helpers.Mail;

namespace LeanCode.AppRating.IntegrationTests.App;

public class SendGridRazorClientMock : SendGridRazorClient
{
public int SentEmailsCount { get; private set; }

public SendGridRazorClientMock()
: base(default!, default!, default!)
{
SentEmailsCount = 0;
}

public override Task SendEmailAsync(SendGridMessage msg, CancellationToken cancellationToken = default)
{
SentEmailsCount++;
return Task.CompletedTask;
}
}
23 changes: 22 additions & 1 deletion backend/tests/LeanCode.AppRating.IntegrationTests/App/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using System.Security.Claims;
using LeanCode.AppRating.Configuration;
using LeanCode.Components;
using LeanCode.CQRS.AspNetCore;
using LeanCode.CQRS.MassTransitRelay;
using LeanCode.CQRS.Validation.Fluent;
using LeanCode.IntegrationTestHelpers;
using LeanCode.SendGrid;
using LeanCode.Startup.MicrosoftDI;
using MassTransit;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -46,7 +47,27 @@ public override void ConfigureServices(IServiceCollection services)
RegistrationContextExtensions.ConfigureEndpoints(cfg, ctx);
}
);

busCfg.AddAppRatingConsumers<Guid>();
});

var sendGridRazorClientMock = new SendGridRazorClientMock();

services.AddSingleton<SendGridRazorClient>(sendGridRazorClientMock);
services.AddSingleton(sendGridRazorClientMock);

services.AddSingleton(
new AppRatingReportsConfiguration(
2.0,
"en",
"subject",
"[email protected]",
[ "[email protected]" ],
[ "[email protected]" ]
)
);

services.AddBusActivityMonitor();
}

protected override void ConfigureApp(IApplicationBuilder app)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public TestDbContext(DbContextOptions<TestDbContext> options)
config = TestDatabaseConfig.Create();
}

public DbSet<AppRatingEntity<Guid>> AppRatings => Set<AppRatingEntity<Guid>>();
public DbSet<AppRating<Guid>> AppRatings => Set<AppRating<Guid>>();

protected override void OnModelCreating(ModelBuilder builder)
{
Expand Down
26 changes: 22 additions & 4 deletions backend/tests/LeanCode.AppRating.IntegrationTests/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,40 @@
using LeanCode.Logging;
using LeanCode.Startup.MicrosoftDI;
using Microsoft.Extensions.Hosting;
using Xunit;

namespace LeanCode.AppRating.IntegrationTests;

public class TestBase : LeanCodeTestFactory<App.Startup>
public abstract class TestsBase<TApp> : IAsyncLifetime, IDisposable
where TApp : TestApp, new()
{
protected TApp App { get; private set; }

public TestsBase()
{
App = new();
}

Task IAsyncLifetime.InitializeAsync() => App.InitializeAsync();

Task IAsyncLifetime.DisposeAsync() => App.DisposeAsync().AsTask();

void IDisposable.Dispose() => App.Dispose();
}

public class TestApp : LeanCodeTestFactory<App.Startup>
{
public readonly Guid UserId = Guid.Parse("4d3b45e6-a2c1-4d6a-9e23-94e0d9f8ca01");

protected HttpClient Client { get; set; }
protected HttpCommandsExecutor Command { get; set; }
protected HttpQueriesExecutor Query { get; set; }
public HttpCommandsExecutor Command { get; set; }
public HttpQueriesExecutor Query { get; set; }

protected JsonSerializerOptions JsonSerializerOptions { get; } = new() { };

protected override ConfigurationOverrides Configuration => TestDatabaseConfig.Create().GetConfigurationOverrides();

public TestBase()
public TestApp()
{
var claimsPrincipal = GetClaimsPrincipal(UserId);

Expand Down
Loading

0 comments on commit a1d3f0e

Please sign in to comment.