diff --git a/LiteCharms.Abstractions/Constants.cs b/LiteCharms.Abstractions/Constants.cs index 31be72a..ae08f94 100644 --- a/LiteCharms.Abstractions/Constants.cs +++ b/LiteCharms.Abstractions/Constants.cs @@ -5,6 +5,8 @@ public static class Constants public const int QueueBounds = 100000; public const string ShopSchedulerName = "shop"; + public const string ShopEmailFromName = "Khongisa Shop"; + public const string ShopEmailFromAddress = "shop@litecharms.co.za"; public const string EmailServiceBus = nameof(EmailServiceBus); public const string GeneralServiceBus = nameof(GeneralServiceBus); diff --git a/LiteCharms.Abstractions/EventBase.cs b/LiteCharms.Abstractions/EventBase.cs new file mode 100644 index 0000000..6c11736 --- /dev/null +++ b/LiteCharms.Abstractions/EventBase.cs @@ -0,0 +1,12 @@ +using static LiteCharms.Abstractions.Timezones; + +namespace LiteCharms.Abstractions; + +public abstract class EventBase +{ + public Guid Id { get; set; } = Guid.CreateVersion7(); + + public DateTimeOffset EnqueueAt { get; set; } = SouthAfricanTimeZone.UtcNow(); + + public string CorrelationId { get; set; } = Guid.CreateVersion7().ToString(); +} diff --git a/LiteCharms.Features/LiteCharms.Features.csproj b/LiteCharms.Features/LiteCharms.Features.csproj index 6f9f146..f541547 100644 --- a/LiteCharms.Features/LiteCharms.Features.csproj +++ b/LiteCharms.Features/LiteCharms.Features.csproj @@ -54,6 +54,7 @@ + @@ -61,8 +62,4 @@ - - - - diff --git a/LiteCharms.Features/Notifications/Events/Handlers/ProcessEmailNotificationsEventHandler.cs b/LiteCharms.Features/Notifications/Events/Handlers/ProcessEmailNotificationsEventHandler.cs new file mode 100644 index 0000000..61b24d5 --- /dev/null +++ b/LiteCharms.Features/Notifications/Events/Handlers/ProcessEmailNotificationsEventHandler.cs @@ -0,0 +1,70 @@ +using LiteCharms.Features.Email.Commands; +using LiteCharms.Infrastructure.Database; +using static LiteCharms.Abstractions.Constants; + +namespace LiteCharms.Features.Notifications.Events.Handlers; + +public class ProcessEmailNotificationsEventHandler(IDbContextFactory contextFactory, ILogger logger, ISender mediator) : + INotificationHandler +{ + public async ValueTask Handle(ProcessEmailNotificationsEvent message, CancellationToken cancellationToken) + { + try + { + using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var notifications = await context.Notifications + .OrderByDescending(o => o.Priority) + .ThenBy(o => o.CreatedAt) + .Where(n => n.CorrelationIdType == Models.CorrelationIdTypes.Email) + .Where(n => n.Direction == Models.NotificationDirection.Outgoing) + .Take(message.MaxRecords) + .ToListAsync(cancellationToken); + + foreach (var notification in notifications) + { + var sendResult = await SendEmailAsync(notification, cancellationToken); + + if(sendResult.IsFailed) + { + var errors = new List(1000); + + errors.AddRange(sendResult.Errors.Select(e => e.Message)); + + if (sendResult.Reasons?.Count > 0) + errors.AddRange(sendResult.Reasons.Select(e => e.Message)); + + notification.HasError = true; + notification.Errors = [.. errors]; + } + + notification.Processed = true; + } + + await context.SaveChangesAsync(cancellationToken); + } + catch (Exception ex) + { + logger.LogError(ex, ex.Message); + } + } + + private async Task SendEmailAsync(Entities.Notification notification, CancellationToken cancellationToken = default) + { + try + { + var request = SendEmailCommand.Create(notification.Sender!, notification.SenderName!, ShopEmailFromAddress, + ShopEmailFromName, notification.Subject!, notification.Message!); + + var result = await mediator.Send(request, cancellationToken); + + return result.IsFailed + ? Result.Fail(result.Errors) + : Result.Ok(); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features/Notifications/Events/ProcessEmailNotificationsEvent.cs b/LiteCharms.Features/Notifications/Events/ProcessEmailNotificationsEvent.cs new file mode 100644 index 0000000..3735ecd --- /dev/null +++ b/LiteCharms.Features/Notifications/Events/ProcessEmailNotificationsEvent.cs @@ -0,0 +1,16 @@ +using LiteCharms.Abstractions; + +namespace LiteCharms.Features.Notifications.Events; + +public class ProcessEmailNotificationsEvent : EventBase, IEvent +{ + public string Name { get; set; } = nameof(ProcessEmailNotificationsEvent); + + public int MaxRecords { get; set; } + + public ProcessEmailNotificationsEvent() => MaxRecords = 1000; + + private ProcessEmailNotificationsEvent(int maxRecords = 1000) => MaxRecords = maxRecords; + + public static ProcessEmailNotificationsEvent Create(int maxRecords = 1000) => new(maxRecords); +} diff --git a/LiteCharms.Features/Notifications/Queries/Handlers/GetNotificationQueryHandler.cs b/LiteCharms.Features/Notifications/Queries/Handlers/GetNotificationQueryHandler.cs index 106dd88..5eac5d8 100644 --- a/LiteCharms.Features/Notifications/Queries/Handlers/GetNotificationQueryHandler.cs +++ b/LiteCharms.Features/Notifications/Queries/Handlers/GetNotificationQueryHandler.cs @@ -12,7 +12,7 @@ public class GetNotificationQueryHandler(IDbContextFactory contex { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var notification = await context.Notifications.FindAsync(new object[] { request.NotificationId }, cancellationToken); + var notification = await context.Notifications.FirstOrDefaultAsync(n => n.Id == request.NotificationId, cancellationToken); return notification is not null ? Result.Ok(notification.ToModel())