Browse Source

Send email when event timing has changed

pull/21/head
EngincanV 6 years ago
parent
commit
d93d394130
  1. 9
      src/EventHub.Application/Emailing/EmailTemplateDefinitionProvider.cs
  2. 2
      src/EventHub.Application/Emailing/EmailTemplates.cs
  3. 157
      src/EventHub.Application/Emailing/Templates/EventTimingChanged.tpl
  4. 1
      src/EventHub.Application/EventHubApplicationModule.cs
  5. 23
      src/EventHub.Application/Events/EventAppService.cs
  6. 82
      src/EventHub.Application/Events/EventTimingChangeNotifier.cs
  7. 51
      src/EventHub.Application/Events/EventTimingChangeWorker.cs
  8. 2
      src/EventHub.Domain/Events/Registrations/EventRegistration.cs
  9. 2633
      src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/20210211115505_IsTimingChangeEmailSent_Added_To_EventRegistration.Designer.cs
  10. 24
      src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/20210211115505_IsTimingChangeEmailSent_Added_To_EventRegistration.cs
  11. 6
      src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/EventHubMigrationsDbContextModelSnapshot.cs
  12. 3
      src/EventHub.EntityFrameworkCore/EntityFrameworkCore/EventHubDbContextModelCreatingExtensions.cs
  13. 4
      src/EventHub.Web/Pages/Events/Edit.cshtml

9
src/EventHub.Application/Emailing/EmailTemplateDefinitionProvider.cs

@ -32,6 +32,15 @@ namespace EventHub.Emailing
isInlineLocalized: true isInlineLocalized: true
) )
); );
context.Add(
new TemplateDefinition(
EmailTemplates.EventTimingChanged)
.WithVirtualFilePath(
"/Emailing/Templates/EventTimingChanged.tpl",
isInlineLocalized: true
)
);
} }
} }
} }

2
src/EventHub.Application/Emailing/EmailTemplates.cs

@ -7,5 +7,7 @@ namespace EventHub.Emailing
public const string EventReminder = "EventHub.Event.Reminder"; public const string EventReminder = "EventHub.Event.Reminder";
public const string NewEventCreated = "EventHub.Event.NewEventCreated"; public const string NewEventCreated = "EventHub.Event.NewEventCreated";
public const string EventTimingChanged = "EventHub.Event.EventTimingChanged";
} }
} }

157
src/EventHub.Application/Emailing/Templates/EventTimingChanged.tpl

@ -0,0 +1,157 @@
<html>
<head>
<meta name="viewport" content="width=device-width">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Eventhub</title>
<style>
@media only screen and (max-width: 620px) {
table[class=mail-body] h1{
font-size: 36px !important;
margin-bottom: 10px !important;
}
table[class=mail-body] .content {
padding: 0 !important;
}
table[class=mail-body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=mail-body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=mail-body] td {
width: 100% !important;
float: left!important;
}
table[class=mail-body] td a.btn {
display: block !important;
text-align: center;
}
table[class=mail-body] td h2 {
margin: 10px 0 !important;
}
table[class=mail-body] td .cont {
padding: 30px !important;
}
}
</style>
</head>
<body>
<table border="0" cellpadding="0" cellspacing="0" class="mail-body" style="background-color: #ffffff;width: 100%; font-size: 16px;line-height: 1.4;font-family: Arial, sans-serif;">
<tbody>
<tr>
<td class="container" width="800" style="display: block;margin: 0 auto !important; max-width: 800px;padding: 10px;width: 800px;">
<div class="content" style="box-sizing: border-box;display: block;margin: 0 auto;max-width: 800px;padding: 0px;">
<div style="display: flex; padding-top: 1.5rem">
<h1 style="justify-content: center;margin-left: auto;margin-right: auto;align-content: center;">EventHub</h1>
</div>
<!-- START CENTERED WHITE CONTAINER -->
<div style="display: flex;">
<h1 style="justify-content: center;margin-left: auto;margin-right: auto;align-content: center;">{{model.title}} event time has been changed!</h1>
</div>
<table class="main" style="background: #ffffff;border-radius: 3px; width: 100%;">
<!-- START MAIN CONTENT AREA -->
<tbody>
<tr>
<td class="wrapper" width="800">
<table class="main" style="background: #ffffff;border-radius: 3px;width: 100%;">
<tbody>
<tr>
<td class="wrapper" width="800">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; vertical-align: top;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%; ">
<tbody>
<tr>
<td style="vertical-align: top;" width="100%">
<div class="cont" style="background: #eeeeee; border-radius: 10px; padding: 40px; margin-top: 20px;">
<table style="border-collapse: separate; vertical-align: top;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%; ">
<tbody>
<tr>
<td width="100%" style="vertical-align: top; font-size: 16px; font-family: Arial, sans-serif; color: #444; margin-top: 20px; margin-bottom: 20px; line-height: 1.75">
<p>
Hi {{model.user_name}},
</p>
<p>
The "{{model.title}}" event time just rescheduled.
</p>
<div>
<table border="0" cellpadding="0" cellspacing="0" style=" border-collapse: separate; vertical-align: top; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
<tbody>
<tr style="text-align: center">
<td style="vertical-align: top;">
<p style="font-size: 13px; font-family: Arial, sans-serif; color: #777777; margin: 0; margin-top: 30px;">Start Time</p>
<p style="font-size: 21px; font-family: Arial, sans-serif; color: #444444; margin-bottom: 30px; margin-top: 6px;">{{model.start_time}}</p>
</td>
<td style="vertical-align: top;">
<p style="font-size: 13px; font-family: Arial, sans-serif; color: #777777; margin: 0; margin-top: 30px;">End Time</p>
<p style="font-size: 21px; font-family: Arial, sans-serif; color: #444444; margin-bottom: 30px; margin-top: 6px;">{{model.end_time}}</p>
</td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 20px; margin-top: 20px; text-align: center;">
<a href="/events/{{model.url}}" class="btn" target="_blank" style="padding: 15px; background: #e90052; border-radius: 40px; text-align: center; color: white; font-weight: bold; text-decoration: none; font-size: 18px; padding-left: 45px; padding-right: 45px;">Event Details</a>
</p>
</div>
<p style="font-size: 16px; font-family: Arial, sans-serif; color: #444; margin-top: 20px; margin-bottom: 0px;">
Thank you,<br> EventHub
</p>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!-- START FOOTER -->
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; vertical-align: top;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
<tbody>
<tr>
<td class="content-block" style="vertical-align: top;" width="800">
<div class="cont" style="background: #ffffff;border-top: 2px solid #eee; padding: 35px 40px 5px; margin-top: 0px; text-align: center;">
<a href="https://eventhub.abp.io/" style="font-size: 16px; font-family: Arial, sans-serif; color: #777; font-weight: bold; margin-right: 30px; text-decoration: none;">Home</a>
</div>
</td>
</tr>
<tr>
<td class="content-block powered-by" style="vertical-align: top;" width="100%">
<div class="cont" style="background: #ffffff; border-radius: 10px; padding: 5px 40px; margin-top: 10px; margin-bottom: 50px; text-align: center;">
<a href="mailto:info@eventhub.abp.io" style="font-size: 16px; font-family: Arial, sans-serif; color: #777; font-weight: bold; margin-right: 30px; text-decoration: none;"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAARCAYAAADHeGwwAAABMElEQVQ4je3TvyvFYRQG8M+9LEwWiV0xyeAi2ZTUFQuZDAqTxUCJzY9BFotS/gAySAZFEgYlySzFLosfs169w6XL/V6uzbO973vO85z3OeekstlsGRYxrrTYw1g56jGNO6yivAQyS5hFdRpHGMQauvDwC+IXDGAGnTgJApXYxAJO0YLrH5Dfog3bmMAhatI5AUF1B4/oiIFJcYAMbrCRa3X6E0EvzlEbbZtLILCMHlQESzCS+/hZIKARF7Ef8+jDc564VwxhCq24jL/4gHwCAVXYxyR2o7e3Oe/30cbQuzEcB7/zEX03kkF8BU2RJFS3hVS07wnrGP2GI9HMD6MB/eiOd9Wx6vZCyUmXKhM9Hojn8JO6JInFbG2YrLMi4t/xVZNLhn+BgghNvkLzn7Bz9QbIADMmEt9imAAAAABJRU5ErkJggg==" width="16"> info@eventhub.abp.io</a>
<span style="font-size: 16px; font-family: Arial, sans-serif; color: #777; margin-right: 0px; text-decoration: none;"><strong>EventHub</strong> - Powered by Volosoft</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</td>
</tr>
</tbody>
</table>
</body>
</html>

1
src/EventHub.Application/EventHubApplicationModule.cs

@ -24,6 +24,7 @@ namespace EventHub
{ {
context.AddBackgroundWorker<EventReminderWorker>(); context.AddBackgroundWorker<EventReminderWorker>();
context.AddBackgroundWorker<NewEventDetectorWorker>(); context.AddBackgroundWorker<NewEventDetectorWorker>();
context.AddBackgroundWorker<EventTimingChangeWorker>();
} }
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)

23
src/EventHub.Application/Events/EventAppService.cs

@ -24,6 +24,7 @@ namespace EventHub.Events
private readonly IRepository<AppUser, Guid> _userRepository; private readonly IRepository<AppUser, Guid> _userRepository;
private readonly IRepository<Country, Guid> _countriesRepository; private readonly IRepository<Country, Guid> _countriesRepository;
private readonly IBlobContainer<EventCoverImageContainer> _eventBlobContainer; private readonly IBlobContainer<EventCoverImageContainer> _eventBlobContainer;
private readonly IRepository<EventRegistration, Guid> _eventRegistrationRepository;
public EventAppService( public EventAppService(
EventManager eventManager, EventManager eventManager,
@ -32,7 +33,8 @@ namespace EventHub.Events
IRepository<Organization, Guid> organizationRepository, IRepository<Organization, Guid> organizationRepository,
IRepository<AppUser, Guid> userRepository, IRepository<AppUser, Guid> userRepository,
IRepository<Country, Guid> countriesRepository, IRepository<Country, Guid> countriesRepository,
IBlobContainer<EventCoverImageContainer> eventBlobContainer) IBlobContainer<EventCoverImageContainer> eventBlobContainer,
IRepository<EventRegistration, Guid> eventRegistrationRepository)
{ {
_eventManager = eventManager; _eventManager = eventManager;
_eventRegistrationManager = eventRegistrationManager; _eventRegistrationManager = eventRegistrationManager;
@ -41,6 +43,7 @@ namespace EventHub.Events
_userRepository = userRepository; _userRepository = userRepository;
_countriesRepository = countriesRepository; _countriesRepository = countriesRepository;
_eventBlobContainer = eventBlobContainer; _eventBlobContainer = eventBlobContainer;
_eventRegistrationRepository = eventRegistrationRepository;
} }
[Authorize] [Authorize]
@ -234,6 +237,7 @@ namespace EventHub.Events
@event.SetTime(input.StartTime, input.EndTime); @event.SetTime(input.StartTime, input.EndTime);
@event.TimingChangeCount = @event.TimingChangeCount + 1; @event.TimingChangeCount = @event.TimingChangeCount + 1;
await UpdateEventRegistrationForTimingChangeAsync(@event);
await _eventRepository.UpdateAsync(@event); await _eventRepository.UpdateAsync(@event);
} }
@ -251,5 +255,22 @@ namespace EventHub.Events
return await _eventBlobContainer.GetAllBytesOrNullAsync(blobName); return await _eventBlobContainer.GetAllBytesOrNullAsync(blobName);
} }
private async Task UpdateEventRegistrationForTimingChangeAsync(Event @event)
{
var eventRegistrationQueryable = await _eventRegistrationRepository.GetQueryableAsync();
var query = eventRegistrationQueryable.Where(x => x.EventId == @event.Id);
var eventRegistrations = await AsyncExecuter.ToListAsync(query);
foreach (var eventRegistration in eventRegistrations)
{
if (eventRegistration.IsTimingChangeEmailSent)
{
eventRegistration.IsTimingChangeEmailSent = false;
}
}
await _eventRegistrationRepository.UpdateManyAsync(eventRegistrations, true);
}
} }
} }

82
src/EventHub.Application/Events/EventTimingChangeNotifier.cs

@ -0,0 +1,82 @@
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using EventHub.Emailing;
using EventHub.Events.Registrations;
using EventHub.Users;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Emailing;
using Volo.Abp.Linq;
using Volo.Abp.TextTemplating;
namespace EventHub.Events
{
public class EventTimingChangeNotifier : ITransientDependency
{
private readonly IEmailSender _emailSender;
private readonly ITemplateRenderer _templateRenderer;
private readonly IRepository<EventRegistration, Guid> _eventRegistrationRepository;
private readonly IAsyncQueryableExecuter _asyncExecuter;
private readonly IRepository<AppUser, Guid> _userRepository;
public EventTimingChangeNotifier(
IEmailSender emailSender,
ITemplateRenderer templateRenderer,
IRepository<EventRegistration, Guid> eventRegistrationRepository,
IAsyncQueryableExecuter asyncExecuter,
IRepository<AppUser, Guid> userRepository
)
{
_emailSender = emailSender;
_templateRenderer = templateRenderer;
_eventRegistrationRepository = eventRegistrationRepository;
_asyncExecuter = asyncExecuter;
_userRepository = userRepository;
}
public async Task NotifyAsync(Event @event)
{
if (@event is null)
{
return;
}
var eventRegistrationQueryable = await _eventRegistrationRepository.GetQueryableAsync();
var query = eventRegistrationQueryable
.Where(x => x.EventId == @event.Id && !x.IsTimingChangeEmailSent);
var eventRegistrations = await _asyncExecuter.ToListAsync(query);
foreach (var eventRegistration in eventRegistrations)
{
var user = await _userRepository.FindAsync(eventRegistration.UserId);
if (user is null)
{
continue;
}
var model = new
{
UserName = user.GetFullNameOrUsername(),
Title = @event.Title,
StartTime = @event.StartTime,
EndTime = @event.EndTime,
Url = @event.Url
};
await _emailSender.QueueAsync(
user.Email,
"Event time has been changed!",
await _templateRenderer.RenderAsync(EmailTemplates.EventTimingChanged, model)
);
eventRegistration.IsTimingChangeEmailSent = true;
}
await _eventRegistrationRepository.UpdateManyAsync(eventRegistrations, autoSave: true);
}
}
}

51
src/EventHub.Application/Events/EventTimingChangeWorker.cs

@ -0,0 +1,51 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using EventHub.Events.Registrations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace EventHub.Events
{
public class EventTimingChangeWorker : AsyncPeriodicBackgroundWorkerBase
{
public EventTimingChangeWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory) : base(timer, serviceScopeFactory)
{
Timer.Period = 60000; //1 minute
}
[UnitOfWork(isTransactional: false)]
protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
{
var eventTimingChangeNotifier = workerContext.ServiceProvider.GetRequiredService<EventTimingChangeNotifier>();
var eventRepository = workerContext.ServiceProvider.GetRequiredService<IRepository<Event, Guid>>();
var eventRegistrationRepository = workerContext.ServiceProvider.GetRequiredService<IRepository<EventRegistration, Guid>>();
var asyncExecuter = workerContext.ServiceProvider.GetRequiredService<IAsyncQueryableExecuter>();
var eventRegistrationQueryable = await eventRegistrationRepository.GetQueryableAsync();
var query = eventRegistrationQueryable
.Where(x => !x.IsTimingChangeEmailSent)
.Select(x => x.EventId)
.Distinct();
var eventRegistrations = await asyncExecuter.ToListAsync(query);
foreach (var eventRegistration in eventRegistrations)
{
try
{
await eventTimingChangeNotifier.NotifyAsync(await eventRepository.FindAsync(eventRegistration));
}
catch (Exception e)
{
Logger.LogError($"An error occurred while sending an email to the attendees of the event after the event time has changed. Error message: {e.Message}");
}
}
}
}
}

2
src/EventHub.Domain/Events/Registrations/EventRegistration.cs

@ -9,6 +9,8 @@ namespace EventHub.Events.Registrations
public Guid UserId { get; private set; } public Guid UserId { get; private set; }
public bool IsTimingChangeEmailSent { get; set; }
private EventRegistration() private EventRegistration()
{ {

2633
src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/20210211115505_IsTimingChangeEmailSent_Added_To_EventRegistration.Designer.cs

File diff suppressed because it is too large

24
src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/20210211115505_IsTimingChangeEmailSent_Added_To_EventRegistration.cs

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace EventHub.Migrations
{
public partial class IsTimingChangeEmailSent_Added_To_EventRegistration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsTimingChangeEmailSent",
table: "AppEventRegistrations",
type: "bit",
nullable: false,
defaultValue: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsTimingChangeEmailSent",
table: "AppEventRegistrations");
}
}
}

6
src/EventHub.EntityFrameworkCore.DbMigrations/Migrations/EventHubMigrationsDbContextModelSnapshot.cs

@ -184,6 +184,12 @@ namespace EventHub.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<bool>("IsTimingChangeEmailSent")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(true)
.HasColumnName("IsTimingChangeEmailSent");
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("uniqueidentifier");

3
src/EventHub.EntityFrameworkCore/EntityFrameworkCore/EventHubDbContextModelCreatingExtensions.cs

@ -84,6 +84,9 @@ namespace EventHub.EntityFrameworkCore
b.ConfigureByConvention(); b.ConfigureByConvention();
b.Property(x => x.IsTimingChangeEmailSent).HasDefaultValue(true)
.HasColumnName(nameof(EventRegistration.IsTimingChangeEmailSent));
b.HasOne<Event>().WithMany().HasForeignKey(x => x.EventId).IsRequired().OnDelete(DeleteBehavior.NoAction); b.HasOne<Event>().WithMany().HasForeignKey(x => x.EventId).IsRequired().OnDelete(DeleteBehavior.NoAction);
if (isMigrationDbContext) if (isMigrationDbContext)

4
src/EventHub.Web/Pages/Events/Edit.cshtml

@ -50,7 +50,7 @@
<abp-button size="Large" button-type="Primary" type="button" id="btn-event-timing" text="@L["Submit"].Value"/> <abp-button size="Large" button-type="Primary" type="button" id="btn-event-timing" text="@L["Submit"].Value"/>
</form> </form>
</abp-tab> </abp-tab>
<abp-tab title="@(L["EventCoverImage"])"> <abp-tab title="@(L["EventCoverImage"].Value)">
<form method="post" id="cover-image-form" class="mt-3"> <form method="post" id="cover-image-form" class="mt-3">
<input type="hidden" id="event-id" value="@Model.Event.Id"/> <input type="hidden" id="event-id" value="@Model.Event.Id"/>
<abp-row> <abp-row>
@ -82,7 +82,7 @@
</abp-modal> </abp-modal>
<abp-modal centered="true" scrollable="true" size="Small" id="cover-image-preview-modal"> <abp-modal centered="true" scrollable="true" size="Small" id="cover-image-preview-modal">
<abp-modal-header title="@L["CoverImagePreview"]"></abp-modal-header> <abp-modal-header title="@(L["CoverImagePreview"].Value)"></abp-modal-header>
<abp-modal-body> <abp-modal-body>
<img class="img-fluid" id="img-upload" alt="event-cover-image-preview"/> <img class="img-fluid" id="img-upload" alt="event-cover-image-preview"/>
</abp-modal-body> </abp-modal-body>

Loading…
Cancel
Save