Skip to content

Commit

Permalink
Added email delivery of messages
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Jan 12, 2024
1 parent 7900af7 commit 63d7507
Show file tree
Hide file tree
Showing 30 changed files with 881 additions and 58 deletions.
3 changes: 2 additions & 1 deletion src/AWSLambdas.Api.WorkerHost/HostExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ public static void AddDependencies(this IServiceCollection services, IConfigurat
#endif

services.AddSingleton<IRecorder>(c =>
new CrashTraceOnlyRecorder("Azure API Lambdas", c.Resolve<ILoggerFactory>(),
new CrashTraceOnlyRecorder("AWS API Lambdas", c.Resolve<ILoggerFactory>(),
c.Resolve<ICrashReporter>()));
services.AddSingleton<IServiceClient>(c =>
new InterHostServiceClient(c.Resolve<IHttpClientFactory>(),
c.Resolve<JsonSerializerOptions>(),
c.Resolve<IHostSettings>().GetAncillaryApiHostBaseUrl()));
services.AddSingleton<IQueueMonitoringApiRelayWorker<UsageMessage>, DeliverUsageRelayWorker>();
services.AddSingleton<IQueueMonitoringApiRelayWorker<AuditMessage>, DeliverAuditRelayWorker>();
services.AddSingleton<IQueueMonitoringApiRelayWorker<EmailMessage>, DeliverEmailRelayWorker>();
}
}
24 changes: 24 additions & 0 deletions src/AWSLambdas.Api.WorkerHost/Lambdas/DeliverEmail.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Core;
using Amazon.Lambda.SQSEvents;
using Application.Persistence.Shared.ReadModels;
using AWSLambdas.Api.WorkerHost.Extensions;
using Infrastructure.Workers.Api;

namespace AWSLambdas.Api.WorkerHost.Lambdas;

public class DeliverEmail
{
private readonly IQueueMonitoringApiRelayWorker<EmailMessage> _worker;

public DeliverEmail(IQueueMonitoringApiRelayWorker<EmailMessage> worker)
{
_worker = worker;
}

[LambdaFunction]
public async Task<bool> Run(SQSEvent sqsEvent, ILambdaContext context)
{
return await sqsEvent.RelayRecordsAsync(_worker, CancellationToken.None);
}
}
17 changes: 17 additions & 0 deletions src/AWSLambdas.Api.WorkerHost/serverless.template
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@
"PackageType": "Zip",
"Handler": "AWSLambdas.Api.WorkerHost::AWSLambdas.Api.WorkerHost.Lambdas.DeliverAudit_Run_Generated::Run"
}
},
"AWSLambdasApiWorkerHostLambdasDeliverEmailRunGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations"
},
"Properties": {
"Runtime": "dotnet6",
"CodeUri": ".",
"MemorySize": 256,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
"PackageType": "Zip",
"Handler": "AWSLambdas.Api.WorkerHost::AWSLambdas.Api.WorkerHost.Lambdas.DeliverEmail_Run_Generated::Run"
}
}
}
}
279 changes: 260 additions & 19 deletions src/AncillaryApplication.UnitTests/AncillaryApplicationSpec.cs

Large diffs are not rendered by default.

90 changes: 83 additions & 7 deletions src/AncillaryApplication/AncillaryApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,45 @@ public class AncillaryApplication : IAncillaryApplication
{
private readonly IAuditMessageQueueRepository _auditMessageQueueRepository;
private readonly IAuditRepository _auditRepository;
private readonly IEmailDeliveryService _emailDeliveryService;
private readonly IEmailMessageQueueRepository _emailMessageQueueRepository;
private readonly IIdentifierFactory _idFactory;
private readonly IRecorder _recorder;
private readonly IUsageDeliveryService _usageDeliveryService;
private readonly IUsageMessageQueueRepository _usageMessageQueueRepository;
private readonly IUsageReportingService _usageReportingService;

public AncillaryApplication(IRecorder recorder, IIdentifierFactory idFactory,
IUsageMessageQueueRepository usageMessageQueueRepository, IUsageReportingService usageReportingService,
IAuditMessageQueueRepository auditMessageQueueRepository, IAuditRepository auditRepository)
IUsageMessageQueueRepository usageMessageQueueRepository, IUsageDeliveryService usageDeliveryService,
IAuditMessageQueueRepository auditMessageQueueRepository, IAuditRepository auditRepository,
IEmailMessageQueueRepository emailMessageQueueRepository, IEmailDeliveryService emailDeliveryService)
{
_recorder = recorder;
_idFactory = idFactory;
_usageMessageQueueRepository = usageMessageQueueRepository;
_usageReportingService = usageReportingService;
_usageDeliveryService = usageDeliveryService;
_auditMessageQueueRepository = auditMessageQueueRepository;
_auditRepository = auditRepository;
_emailMessageQueueRepository = emailMessageQueueRepository;
_emailDeliveryService = emailDeliveryService;
}

public async Task<Result<bool, Error>> DeliverEmailAsync(ICallerContext context, string messageAsJson,
CancellationToken cancellationToken)
{
var rehydrated = RehydrateMessage<EmailMessage>(messageAsJson);
if (!rehydrated.IsSuccessful)
{
return rehydrated.Error;
}

var delivered = await DeliverEmailAsync(context, rehydrated.Value, cancellationToken);
if (!delivered.IsSuccessful)
{
return delivered.Error;
}

_recorder.TraceInformation(context.ToCall(), "Delivered email message: {Message}", messageAsJson);
return true;
}

public async Task<Result<bool, Error>> DeliverUsageAsync(ICallerContext context, string messageAsJson,
Expand Down Expand Up @@ -89,13 +113,25 @@ public async Task<Result<bool, Error>> DeliverAuditAsync(ICallerContext context,
return true;
}

#if TESTINGONLY
public async Task<Result<Error>> DrainAllEmailsAsync(ICallerContext context, CancellationToken cancellationToken)
{
await DrainAllAsync(_emailMessageQueueRepository,
message => DeliverEmailAsync(context, message, cancellationToken), cancellationToken);

_recorder.TraceInformation(context.ToCall(), "Drained all email messages");

return Result.Ok;
}
#endif

#if TESTINGONLY
public async Task<Result<Error>> DrainAllUsagesAsync(ICallerContext context, CancellationToken cancellationToken)
{
await DrainAllAsync(_usageMessageQueueRepository,
message => DeliverUsageAsync(context, message, cancellationToken), cancellationToken);

_recorder.TraceInformation(context.ToCall(), "Drained all usages");
_recorder.TraceInformation(context.ToCall(), "Drained all usage messages");

return Result.Ok;
}
Expand All @@ -107,12 +143,52 @@ public async Task<Result<Error>> DrainAllAuditsAsync(ICallerContext context, Can
await DrainAllAsync(_auditMessageQueueRepository,
message => DeliverAuditAsync(context, message, cancellationToken), cancellationToken);

_recorder.TraceInformation(context.ToCall(), "Drained all audits");
_recorder.TraceInformation(context.ToCall(), "Drained all audit messages");

return Result.Ok;
}
#endif

private async Task<Result<bool, Error>> DeliverEmailAsync(ICallerContext context, EmailMessage message,
CancellationToken cancellationToken)
{
if (message.Html.IsInvalidParameter(x => x.Exists(), nameof(EmailMessage.Html), out _))
{
return Error.RuleViolation(Resources.AncillaryApplication_MissingEmailHtml);
}

if (message.Html!.Subject.IsInvalidParameter(x => x.HasValue(), nameof(EmailMessage.Html.Subject), out _))
{
return Error.RuleViolation(Resources.AncillaryApplication_MissingEmailSubject);
}

if (message.Html.HtmlBody.IsInvalidParameter(x => x.HasValue(), nameof(EmailMessage.Html.HtmlBody), out _))
{
return Error.RuleViolation(Resources.AncillaryApplication_MissingEmailBody);
}

if (message.Html.ToEmailAddress.IsInvalidParameter(x => x.HasValue(), nameof(EmailMessage.Html.ToEmailAddress),
out _))
{
return Error.RuleViolation(Resources.AncillaryApplication_MissingEmailRecipient);
}

if (message.Html.FromEmailAddress.IsInvalidParameter(x => x.HasValue(),
nameof(EmailMessage.Html.FromEmailAddress), out _))
{
return Error.RuleViolation(Resources.AncillaryApplication_MissingEmailSender);
}

await _emailDeliveryService.DeliverAsync(context, message.Html.Subject!, message.Html.HtmlBody!,
message.Html.ToEmailAddress!, message.Html.ToDisplayName, message.Html.FromEmailAddress!,
message.Html.FromDisplayName,
cancellationToken);

_recorder.TraceInformation(context.ToCall(), "Delivered email to {For}", message.Html.ToEmailAddress!);

return true;
}

private async Task<Result<bool, Error>> DeliverUsageAsync(ICallerContext context, UsageMessage message,
CancellationToken cancellationToken)
{
Expand All @@ -126,7 +202,7 @@ private async Task<Result<bool, Error>> DeliverUsageAsync(ICallerContext context
return Error.RuleViolation(Resources.AncillaryApplication_MissingUsageEventName);
}

await _usageReportingService.TrackAsync(context, message.ForId!, message.EventName!, message.Additional,
await _usageDeliveryService.DeliverAsync(context, message.ForId!, message.EventName!, message.Additional,
cancellationToken);

_recorder.TraceInformation(context.ToCall(), "Delivered usage for {For}", message.ForId!);
Expand Down
7 changes: 7 additions & 0 deletions src/AncillaryApplication/IAncillaryApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ public interface IAncillaryApplication
Task<Result<bool, Error>> DeliverAuditAsync(ICallerContext context, string messageAsJson,
CancellationToken cancellationToken);

Task<Result<bool, Error>> DeliverEmailAsync(ICallerContext context, string messageAsJson,
CancellationToken cancellationToken);

Task<Result<bool, Error>> DeliverUsageAsync(ICallerContext context, string messageAsJson,
CancellationToken cancellationToken);

#if TESTINGONLY
Task<Result<Error>> DrainAllAuditsAsync(ICallerContext context, CancellationToken cancellationToken);
#endif

#if TESTINGONLY
Task<Result<Error>> DrainAllEmailsAsync(ICallerContext context, CancellationToken cancellationToken);
#endif

#if TESTINGONLY
Task<Result<Error>> DrainAllUsagesAsync(ICallerContext context, CancellationToken cancellationToken);
#endif
Expand Down
45 changes: 45 additions & 0 deletions src/AncillaryApplication/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/AncillaryApplication/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,19 @@
<data name="AncillaryApplication_MissingTenantId" xml:space="preserve">
<value>The audit message is missing a 'TenantId'</value>
</data>
<data name="AncillaryApplication_MissingEmailHtml" xml:space="preserve">
<value>The email message is missing the 'HTML' email</value>
</data>
<data name="AncillaryApplication_MissingEmailSubject" xml:space="preserve">
<value>The email message is missing a 'Subject'</value>
</data>
<data name="AncillaryApplication_MissingEmailBody" xml:space="preserve">
<value>The email message is missing a 'HtmlBody'</value>
</data>
<data name="AncillaryApplication_MissingEmailRecipient" xml:space="preserve">
<value>The email message is missing a 'ToEmailAddress' recipient</value>
</data>
<data name="AncillaryApplication_MissingEmailSender" xml:space="preserve">
<value>The email message is missing a 'FromEmailAddress' sender</value>
</data>
</root>
6 changes: 3 additions & 3 deletions src/AncillaryInfrastructure.IntegrationTests/AuditsApiSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public async Task WhenDeliverAudit_ThenDelivers()
{
Message = new AuditMessage
{
MessageId = "amessageid",
TenantId = "atenantid",
CallId = "acallid",
CallerId = "acallerid",
TenantId = "atenantid",
AuditCode = "anauditcode",
AgainstId = "anagainstid",
MessageId = "amessageid",
MessageTemplate = "amessagetemplate",
Arguments = new List<string> { "anarg1", "anarg2" }
}.ToJson()!
Expand Down Expand Up @@ -128,6 +128,6 @@ public async Task WhenDrainAllAuditsAndSome_ThenDrains()

private static void OverrideDependencies(IServiceCollection services)
{
services.AddSingleton<IUsageReportingService, StubUsageReportingService>();
services.AddSingleton<IUsageDeliveryService, StubUsageDeliveryService>();
}
}
Loading

0 comments on commit 63d7507

Please sign in to comment.