Skip to content

Commit

Permalink
Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
Scott Galloway committed Sep 27, 2024
1 parent 13fac2d commit 83cef92
Show file tree
Hide file tree
Showing 29 changed files with 350 additions and 298 deletions.
5 changes: 2 additions & 3 deletions Mostlylucid.SchedulerService/API/EmailEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Mostlylucid.SchedulerService.Services;
using Mostlylucid.Services.Email;
using Mostlylucid.Shared;
using Mostlylucid.Shared.Models.Email;

namespace Mostlylucid.SchedulerService.API;
Expand All @@ -10,8 +9,8 @@ public static class EmailEndPointsExtension
{
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/triggeremail", ([FromServices] NewsletterSendingService newsletterSendingService) => newsletterSendingService.SendNewsletter(SubscriptionType.EveryPost)).WithName("Email Trigger API");
group.MapGet("/handler2", () => "Hello").WithName("Email Handler2 API");
group.MapGet("/sendfortoken", ([FromServices] NewsletterSendingService newsletterSendingService, [FromBody] string token) => newsletterSendingService.SendImmediateEmailForSubscription(token)).WithName("Email Trigger API for Token");

group.MapGet("/send", Send).WithName("Email Send API");
return group;
}
Expand Down
3 changes: 3 additions & 0 deletions Mostlylucid.SchedulerService/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Mostlylucid.Services.Email;
using Mostlylucid.Services.EmailSubscription;
using Mostlylucid.Services.Markdown;
using Mostlylucid.Shared.Config;
using Serilog;
using Serilog.Debugging;

Expand Down Expand Up @@ -36,6 +37,8 @@
services.AddHealthChecks();
services.AddScoped<NewsletterManagementService>();
services.AddScoped<NewsletterSendingService>();

services.ConfigurePOCO<NewsletterConfig>(config);
services.AddScoped<IBlogService, BlogService>();
services.SetupEmail(configuration);
services.AddScoped<MarkdownRenderingService>();
Expand Down
8 changes: 4 additions & 4 deletions Mostlylucid.SchedulerService/Services/JobInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public static void InitializeJobs(this IApplicationBuilder app)
var scope= app.ApplicationServices.CreateScope();
var recurringJobManager = scope.ServiceProvider.GetRequiredService<RecurringJobManager>();

recurringJobManager.AddOrUpdate<NewsletterSendingService>(AutoNewsletterJob, x => x.SendNewsletter(SubscriptionType.EveryPost), Cron.Hourly);
recurringJobManager.AddOrUpdate<NewsletterSendingService>(DailyNewsletterJob, x => x.SendNewsletter(SubscriptionType.Daily), "0 17 * * *");
recurringJobManager.AddOrUpdate<NewsletterSendingService>(WeeklyNewsletterJob, x => x.SendNewsletter(SubscriptionType.Weekly), "0 17 * * *");
recurringJobManager.AddOrUpdate<NewsletterSendingService>(MonthlyNewsletterJob, x => x.SendNewsletter(SubscriptionType.Monthly), "0 17 * * *");
recurringJobManager.AddOrUpdate<NewsletterSendingService>(AutoNewsletterJob, x => x.SendScheduledNewsletter(SubscriptionType.EveryPost), Cron.Hourly);
recurringJobManager.AddOrUpdate<NewsletterSendingService>(DailyNewsletterJob, x => x.SendScheduledNewsletter(SubscriptionType.Daily), "0 17 * * *");
recurringJobManager.AddOrUpdate<NewsletterSendingService>(WeeklyNewsletterJob, x => x.SendScheduledNewsletter(SubscriptionType.Weekly), "0 17 * * *");
recurringJobManager.AddOrUpdate<NewsletterSendingService>(MonthlyNewsletterJob, x => x.SendScheduledNewsletter(SubscriptionType.Monthly), "0 17 * * *");
}
}
127 changes: 91 additions & 36 deletions Mostlylucid.SchedulerService/Services/NewsletterSendingService.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,111 @@
using System.Diagnostics.CodeAnalysis;
using Mostlylucid.DbContext.EntityFramework;
using Mostlylucid.Services.Email;
using Mostlylucid.Services.Email;
using Mostlylucid.Services.EmailSubscription;
using Mostlylucid.Shared;
using Mostlylucid.Shared.Config;
using Mostlylucid.Shared.Helpers;
using Mostlylucid.Shared.Models.EmailSubscription;
using Serilog;
using Serilog.Events;
using SerilogTracing;

namespace Mostlylucid.SchedulerService.Services;

public class NewsletterSendingService(IServiceScopeFactory scopeFactory)
public class NewsletterSendingService(
IServiceScopeFactory scopeFactory,
NewsletterConfig newsletterConfig,
ILogger<NewsletterSendingService> logger)
{
public async Task SendNewsletter(SubscriptionType subscriptionType)
private string GetPostUrl(string language, string slug)
{

var scope = scopeFactory.CreateScope();
return language == Constants.EnglishLanguage ? $"{newsletterConfig.AppHostUrl}/post/{slug}" : $"{newsletterConfig.AppHostUrl}/{language}/post/{slug}";
}

public async Task SendScheduledNewsletter(SubscriptionType subscriptionType)
{
using var scope = scopeFactory.CreateScope();
var activity = Log.Logger.StartActivity("SendScheduledNewsletter");
var newsletterManagementService = scope.ServiceProvider.GetRequiredService<NewsletterManagementService>();
var emailSender = scope.ServiceProvider.GetRequiredService<IEmailSenderHostedService>();

var subscriptions =await newsletterManagementService.GetSubscriptions(subscriptionType);
var posts =await newsletterManagementService.GetPostsToSend(subscriptionType);
foreach (var subscription in subscriptions)
{
// foreach (var post in posts)
// {
//
// await emailSender.SendEmailAsync(subscription.Email, emailModel);
// }
await newsletterManagementService.UpdateLastSendForSubscription(subscription.Id, DateTime.Now);
}
var subscriptions = await newsletterManagementService.GetSubscriptions(subscriptionType);
foreach (var subscription in subscriptions)
{
logger.LogInformation("Sending newsletter for subscription {Subscription}", subscription);
await SendNewsletterForSubscription(subscription, activity);
}
logger.LogInformation("Updating last send for subscription type {SubscriptionType}", subscriptionType);
await newsletterManagementService.UpdateLastSend(subscriptionType, DateTime.Now);
}

public async Task SendImmediateEmailForSubscription(string token)
private async Task<bool> SendNewsletterForSubscription(EmailSubscriptionModel subscription, LoggerActivity activity)
{
var scope = scopeFactory.CreateScope();
var emailSubscriptionService = scope.ServiceProvider.GetRequiredService<EmailSubscriptionService>();
activity?.Activity?.SetTag("subscription", subscription);
try
{

using var scope = scopeFactory.CreateScope();
var newsletterManagementService = scope.ServiceProvider.GetRequiredService<NewsletterManagementService>();
var emailSender = scope.ServiceProvider.GetRequiredService<IEmailSenderHostedService>();
var emailSubscription = await emailSubscriptionService.GetByToken(token);
if (emailSubscription == null)
var posts = await newsletterManagementService.GetPostsToSend(subscription.SubscriptionType);
var emailModel = new EmailTemplateModel()
{
ToEmail = subscription.Email,
Subject = "mostlylucid newsletter",
Posts = posts.Select(p => new EmailPostModel()
{
Title = p.Title,
Language = p.Language,
PlainTextContent = p.PlainTextContent.TruncateAtWord(200),
Url = GetPostUrl(p.Language, p.Slug),
PublishedDate = p.PublishedDate
}).ToList(),
};
await emailSender.SendEmailAsync(emailModel);
activity?.Activity?.SetTag("email", subscription.Email);
activity?.Complete(LogEventLevel.Information);
await newsletterManagementService.UpdateLastSendForSubscription(subscription.Id, DateTime.Now);
return true;
}
catch (Exception e)
{
return;
activity?.Complete(LogEventLevel.Error, e);
logger.LogError(e, "Error sending newsletter for subscription {Subscription}", subscription);
return false;
}


}

public async Task SendEmail(SubscriptionType subscriptionType, DateTime fromDateTime, DateTime toDateTime,
string email)

public async Task<bool> SendImmediateEmailForSubscription(string token)
{

var emailTemplageModel = new EmailTemplateModel
using var activity = Log.Logger.StartActivity("SendImmediateEmailForSubscription");
try
{
ToEmail = email
};
var scope = scopeFactory.CreateScope();
var emailSubscriptionService = scope.ServiceProvider.GetRequiredService<EmailSubscriptionService>();

var emailSubscription = await emailSubscriptionService.GetByToken(token);
activity.AddProperty("token", token);
if (emailSubscription == null)
{
logger.LogWarning("Email subscription not found for token {token}", token);
activity.Complete(LogEventLevel.Warning);
return false;
}

logger.LogInformation("Sending email for subscription {EmailSubscription}", emailSubscription);
if (await SendNewsletterForSubscription(emailSubscription, activity))
{
activity.Complete(LogEventLevel.Information);
}
else
{
return false;
}
return true;
}
catch (Exception e)
{
logger.LogError(e, "Error sending email for subscription");
activity?.Complete(LogEventLevel.Error, e);
return false;
}
}

}
5 changes: 5 additions & 0 deletions Mostlylucid.SchedulerService/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
"ToMail": "scott.galloway@gmail.com",
"EmailSubject": "Mostlylucid"

},
"Newsletter": {
"SchedulerServiceUrl" : "http://localhost:5000",
"AppHostUrl" : "https://localhost:7240"

},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Reflection;
using FakeItEasy;
using FluentEmail.Core;
using FluentEmail.Core.Models;
using Microsoft.Extensions.Logging.Testing;
using Mostlylucid.Services.Email;
using Mostlylucid.Shared.Config;
using Mostlylucid.Shared.Models.Email;

namespace Mostlylucid.Service.Test.IEmailServiceTests;

public class EmailService_Send_Tests
{
private readonly string _toEmail = "test@test.com";
private string _senderEmail = "test1@test.com";
private readonly CancellationToken ct = new CancellationToken();
private readonly string _senderName = "Test";
private SendResponse _sendResponse;
private Fake<IFluentEmail> GetEmailService()
{
var smtpSettings = new SmtpSettings();
smtpSettings.ToMail = _toEmail;
var fluentEmail = new FakeItEasy.Fake<IFluentEmail>();
fluentEmail.CallsTo(x =>
x.UsingTemplateFromEmbedded(A<string>.Ignored, A<object>.Ignored, A<Assembly>.Ignored, true)).Returns(fluentEmail.FakedObject);
fluentEmail.CallsTo(x => x.To(A<string>.Ignored)).Returns(fluentEmail.FakedObject);
fluentEmail.CallsTo(x => x.SetFrom(A<string>.Ignored, A<string>.Ignored)).Returns(fluentEmail.FakedObject);
fluentEmail.CallsTo(x => x.Subject(A<string>.Ignored)).Returns(fluentEmail.FakedObject);
fluentEmail.CallsTo(x => x.SendAsync(ct)).Returns(_sendResponse);

return fluentEmail;
}

[Fact]
public async Task Test_Send()
{
var fakeLogger =new FakeLogger<EmailService>();
var fluentMail = GetEmailService();

var emailService = new EmailService(new SmtpSettings(), fluentMail.FakedObject,fakeLogger);
var emailModel = new BaseEmailModel();


}
}
30 changes: 30 additions & 0 deletions Mostlylucid.Service.Test/Mostlylucid.Service.Test.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="FakeItEasy" Version="8.3.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.Testing" Version="8.9.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageReference Include="xunit" Version="2.5.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
</ItemGroup>

<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Mostlylucid.Services\Mostlylucid.Services.csproj" />
<ProjectReference Include="..\Mostlylucid.Shared\Mostlylucid.Shared.csproj" />
</ItemGroup>

</Project>
60 changes: 45 additions & 15 deletions Mostlylucid.Services/Email/EmailService.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
using System.Reflection;
using FluentEmail.Core;
using FluentEmail.Core.Models;
using Microsoft.Extensions.Logging;
using Mostlylucid.Shared.Config;
using Mostlylucid.Shared.Models.Email;
using Mostlylucid.Shared.Models.EmailSubscription;
using Serilog;
using Serilog.Events;
using SerilogTracing;

namespace Mostlylucid.Services.Email;

public class EmailService(SmtpSettings smtpSettings, IFluentEmail fluentEmail)
public class EmailService(SmtpSettings smtpSettings, IFluentEmail fluentEmail, ILogger<EmailService> logger)
: IEmailService
{
private readonly string _nameSpace= typeof(EmailService).Namespace! + ".Templates.";
private readonly string _nameSpace = typeof(EmailService).Namespace! + ".Templates.";

public async Task SendCommentEmail(CommentEmailModel commentModel)
{
Expand All @@ -19,34 +25,58 @@ public async Task SendCommentEmail(CommentEmailModel commentModel)

public async Task SendContactEmail(ContactEmailModel contactModel)
{
var templatePath = _nameSpace +"ContactEmailModel.cshtml";
var templatePath = _nameSpace + "ContactEmailModel.cshtml";

await SendMail(contactModel, templatePath);
}

public async Task SendConfirmationEmail(ConfirmEmailModel confirmEmailModel)
{
var templatePath = _nameSpace +"ConfirmationMailTemplate.cshtml";
var templatePath = _nameSpace + "ConfirmationMailTemplate.cshtml";

await SendMail(confirmEmailModel, templatePath, confirmEmailModel.ToEmail);
}

public async Task SendNewsletterEmail(EmailTemplateModel newsletterEmailModel)
{
var templatePath = _nameSpace +"NewsletterTemplate.cshtml";
var templatePath = _nameSpace + "NewsletterTemplate.cshtml";

await SendMail(newsletterEmailModel, templatePath, newsletterEmailModel.ToEmail);
}

private async Task SendMail(BaseEmailModel model, string template, string? toEmail = null)
private async Task<SendResponse?> SendMail(BaseEmailModel model, string template, string? toEmail = null)
{
var assembly = Assembly.GetAssembly(typeof(EmailService));

// Use FluentEmail to send the email
var email = fluentEmail.UsingTemplateFromEmbedded(template, model, assembly);
await email.To(toEmail?? smtpSettings.ToMail)
.SetFrom(smtpSettings.SenderEmail, smtpSettings.SenderName)
.Subject("New Comment")
.SendAsync();
using var activity = Log.Logger.StartActivity("SendMail");
try
{
activity.AddProperty("ToEmail", toEmail);
activity.AddProperty("Template", template);
activity.AddProperty("Model", model);
var assembly = Assembly.GetAssembly(typeof(EmailService));

// Use FluentEmail to send the email
var email = fluentEmail.UsingTemplateFromEmbedded(template, model, assembly);
var response = await email.To(toEmail ?? smtpSettings.ToMail)
.SetFrom(smtpSettings.SenderEmail, smtpSettings.SenderName)
.Subject("New Comment")
.SendAsync();
if (response.Successful)
{
logger.LogInformation("Email sent to {ToEmail}", toEmail);
}
else
{
activity.Complete(LogEventLevel.Error);
logger.LogError("Email failed to send to {ToEmail}, {ErrorMessages}", toEmail, response.ErrorMessages);
}

return response;
}
catch (Exception e)
{
activity.Complete(LogEventLevel.Error,e);
logger.LogError(e, "Error sending email");
return null;
}
}
}
Loading

0 comments on commit 83cef92

Please sign in to comment.