mirror of https://github.com/abpframework/eventhub
22 changed files with 496 additions and 343 deletions
@ -0,0 +1,34 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>net5.0</TargetFramework> |
||||
|
<RootNamespace>EventHub</RootNamespace> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Autofac" Version="4.2.1" /> |
||||
|
<PackageReference Include="Volo.Abp.BackgroundWorkers" Version="4.2.1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.*" /> |
||||
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="3.1.0" /> |
||||
|
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" /> |
||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" /> |
||||
|
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Include="appsettings.json"> |
||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> |
||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
||||
|
</Content> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\EventHub.Domain\EventHub.Domain.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace EventHub |
||||
|
{ |
||||
|
public class EventHubBackgroundServicesHostedService : IHostedService |
||||
|
{ |
||||
|
private readonly IAbpApplicationWithExternalServiceProvider _application; |
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
|
||||
|
public EventHubBackgroundServicesHostedService( |
||||
|
IAbpApplicationWithExternalServiceProvider application, |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_application = application; |
||||
|
_serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public Task StartAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
_application.Initialize(_serviceProvider); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public Task StopAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
_application.Shutdown(); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using EventHub.Events; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Autofac; |
||||
|
using Volo.Abp.BackgroundWorkers; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace EventHub |
||||
|
{ |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpAutofacModule) |
||||
|
)] |
||||
|
public class EventHubBackgroundServicesModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
var hostEnvironment = context.Services.GetSingletonInstance<IHostEnvironment>(); |
||||
|
|
||||
|
context.Services.AddHostedService<EventHubBackgroundServicesHostedService>(); |
||||
|
} |
||||
|
|
||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
context.AddBackgroundWorker<EventReminderWorker>(); |
||||
|
context.AddBackgroundWorker<NewEventDetectorWorker>(); |
||||
|
context.AddBackgroundWorker<EventTimingChangeWorker>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,82 +1,81 @@ |
|||||
using System; |
using System; |
||||
using System.Linq; |
using System.Linq; |
||||
using System.Linq.Dynamic.Core; |
using System.Threading.Tasks; |
||||
using System.Threading.Tasks; |
using EventHub.Emailing; |
||||
using EventHub.Emailing; |
using EventHub.Events.Registrations; |
||||
using EventHub.Events.Registrations; |
using EventHub.Users; |
||||
using EventHub.Users; |
using Volo.Abp.DependencyInjection; |
||||
using Volo.Abp.DependencyInjection; |
using Volo.Abp.Domain.Repositories; |
||||
using Volo.Abp.Domain.Repositories; |
using Volo.Abp.Emailing; |
||||
using Volo.Abp.Emailing; |
using Volo.Abp.Linq; |
||||
using Volo.Abp.Linq; |
using Volo.Abp.TextTemplating; |
||||
using Volo.Abp.TextTemplating; |
|
||||
|
namespace EventHub.Events |
||||
namespace EventHub.Events |
{ |
||||
{ |
public class EventTimingChangeNotifier : ITransientDependency |
||||
public class EventTimingChangeNotifier : ITransientDependency |
{ |
||||
{ |
private readonly IEmailSender _emailSender; |
||||
private readonly IEmailSender _emailSender; |
private readonly ITemplateRenderer _templateRenderer; |
||||
private readonly ITemplateRenderer _templateRenderer; |
private readonly IRepository<EventRegistration, Guid> _eventRegistrationRepository; |
||||
private readonly IRepository<EventRegistration, Guid> _eventRegistrationRepository; |
private readonly IAsyncQueryableExecuter _asyncExecuter; |
||||
private readonly IAsyncQueryableExecuter _asyncExecuter; |
private readonly IRepository<AppUser, Guid> _userRepository; |
||||
private readonly IRepository<AppUser, Guid> _userRepository; |
|
||||
|
public EventTimingChangeNotifier( |
||||
public EventTimingChangeNotifier( |
IEmailSender emailSender, |
||||
IEmailSender emailSender, |
ITemplateRenderer templateRenderer, |
||||
ITemplateRenderer templateRenderer, |
IRepository<EventRegistration, Guid> eventRegistrationRepository, |
||||
IRepository<EventRegistration, Guid> eventRegistrationRepository, |
IAsyncQueryableExecuter asyncExecuter, |
||||
IAsyncQueryableExecuter asyncExecuter, |
IRepository<AppUser, Guid> userRepository |
||||
IRepository<AppUser, Guid> userRepository |
) |
||||
) |
{ |
||||
{ |
_emailSender = emailSender; |
||||
_emailSender = emailSender; |
_templateRenderer = templateRenderer; |
||||
_templateRenderer = templateRenderer; |
_eventRegistrationRepository = eventRegistrationRepository; |
||||
_eventRegistrationRepository = eventRegistrationRepository; |
_asyncExecuter = asyncExecuter; |
||||
_asyncExecuter = asyncExecuter; |
_userRepository = userRepository; |
||||
_userRepository = userRepository; |
} |
||||
} |
|
||||
|
public async Task NotifyAsync(Event @event) |
||||
public async Task NotifyAsync(Event @event) |
{ |
||||
{ |
if (@event is null) |
||||
if (@event is null) |
{ |
||||
{ |
return; |
||||
return; |
} |
||||
} |
|
||||
|
var eventRegistrationQueryable = await _eventRegistrationRepository.GetQueryableAsync(); |
||||
var eventRegistrationQueryable = await _eventRegistrationRepository.GetQueryableAsync(); |
var query = eventRegistrationQueryable |
||||
var query = eventRegistrationQueryable |
.Where(x => x.EventId == @event.Id && !x.IsTimingChangeEmailSent); |
||||
.Where(x => x.EventId == @event.Id && !x.IsTimingChangeEmailSent); |
|
||||
|
var eventRegistrations = await _asyncExecuter.ToListAsync(query); |
||||
var eventRegistrations = await _asyncExecuter.ToListAsync(query); |
|
||||
|
foreach (var eventRegistration in eventRegistrations) |
||||
foreach (var eventRegistration in eventRegistrations) |
{ |
||||
{ |
var user = await _userRepository.FindAsync(eventRegistration.UserId); |
||||
var user = await _userRepository.FindAsync(eventRegistration.UserId); |
|
||||
|
if (user is null) |
||||
if (user is null) |
{ |
||||
{ |
continue; |
||||
continue; |
} |
||||
} |
|
||||
|
var model = new |
||||
var model = new |
{ |
||||
{ |
UserName = user.GetFullNameOrUsername(), |
||||
UserName = user.GetFullNameOrUsername(), |
Title = @event.Title, |
||||
Title = @event.Title, |
StartTime = @event.StartTime, |
||||
StartTime = @event.StartTime, |
EndTime = @event.EndTime, |
||||
EndTime = @event.EndTime, |
Url = @event.Url |
||||
Url = @event.Url |
}; |
||||
}; |
|
||||
|
await _emailSender.QueueAsync( |
||||
await _emailSender.QueueAsync( |
user.Email, |
||||
user.Email, |
"Event time has been changed!", |
||||
"Event time has been changed!", |
await _templateRenderer.RenderAsync(EmailTemplates.EventTimingChanged, model) |
||||
await _templateRenderer.RenderAsync(EmailTemplates.EventTimingChanged, model) |
); |
||||
); |
|
||||
|
eventRegistration.IsTimingChangeEmailSent = true; |
||||
eventRegistration.IsTimingChangeEmailSent = true; |
} |
||||
} |
|
||||
|
await _eventRegistrationRepository.UpdateManyAsync(eventRegistrations, autoSave: true); |
||||
await _eventRegistrationRepository.UpdateManyAsync(eventRegistrations, autoSave: true); |
} |
||||
} |
} |
||||
} |
|
||||
} |
} |
||||
@ -1,51 +1,51 @@ |
|||||
using System; |
using System; |
||||
using System.Linq; |
using System.Linq; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using EventHub.Events.Registrations; |
using EventHub.Events.Registrations; |
||||
using Microsoft.Extensions.DependencyInjection; |
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Logging; |
using Microsoft.Extensions.Logging; |
||||
using Volo.Abp.BackgroundWorkers; |
using Volo.Abp.BackgroundWorkers; |
||||
using Volo.Abp.Domain.Repositories; |
using Volo.Abp.Domain.Repositories; |
||||
using Volo.Abp.Linq; |
using Volo.Abp.Linq; |
||||
using Volo.Abp.Threading; |
using Volo.Abp.Threading; |
||||
using Volo.Abp.Uow; |
using Volo.Abp.Uow; |
||||
|
|
||||
namespace EventHub.Events |
namespace EventHub.Events |
||||
{ |
{ |
||||
public class EventTimingChangeWorker : AsyncPeriodicBackgroundWorkerBase |
public class EventTimingChangeWorker : AsyncPeriodicBackgroundWorkerBase |
||||
{ |
{ |
||||
public EventTimingChangeWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory) : base(timer, serviceScopeFactory) |
public EventTimingChangeWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory) : base(timer, serviceScopeFactory) |
||||
{ |
{ |
||||
Timer.Period = 60000; //1 minute
|
Timer.Period = 60000; //1 minute
|
||||
} |
} |
||||
|
|
||||
[UnitOfWork(isTransactional: false)] |
[UnitOfWork(isTransactional: false)] |
||||
protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) |
protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) |
||||
{ |
{ |
||||
var eventTimingChangeNotifier = workerContext.ServiceProvider.GetRequiredService<EventTimingChangeNotifier>(); |
var eventTimingChangeNotifier = workerContext.ServiceProvider.GetRequiredService<EventTimingChangeNotifier>(); |
||||
var eventRepository = workerContext.ServiceProvider.GetRequiredService<IRepository<Event, Guid>>(); |
var eventRepository = workerContext.ServiceProvider.GetRequiredService<IRepository<Event, Guid>>(); |
||||
var eventRegistrationRepository = workerContext.ServiceProvider.GetRequiredService<IRepository<EventRegistration, Guid>>(); |
var eventRegistrationRepository = workerContext.ServiceProvider.GetRequiredService<IRepository<EventRegistration, Guid>>(); |
||||
var asyncExecuter = workerContext.ServiceProvider.GetRequiredService<IAsyncQueryableExecuter>(); |
var asyncExecuter = workerContext.ServiceProvider.GetRequiredService<IAsyncQueryableExecuter>(); |
||||
|
|
||||
var eventRegistrationQueryable = await eventRegistrationRepository.GetQueryableAsync(); |
var eventRegistrationQueryable = await eventRegistrationRepository.GetQueryableAsync(); |
||||
var query = eventRegistrationQueryable |
var query = eventRegistrationQueryable |
||||
.Where(x => !x.IsTimingChangeEmailSent) |
.Where(x => !x.IsTimingChangeEmailSent) |
||||
.Select(x => x.EventId) |
.Select(x => x.EventId) |
||||
.Distinct(); |
.Distinct(); |
||||
|
|
||||
var eventRegistrations = await asyncExecuter.ToListAsync(query); |
var eventRegistrations = await asyncExecuter.ToListAsync(query); |
||||
|
|
||||
foreach (var eventRegistration in eventRegistrations) |
foreach (var eventRegistration in eventRegistrations) |
||||
{ |
{ |
||||
try |
try |
||||
{ |
{ |
||||
await eventTimingChangeNotifier.NotifyAsync(await eventRepository.FindAsync(eventRegistration)); |
await eventTimingChangeNotifier.NotifyAsync(await eventRepository.FindAsync(eventRegistration)); |
||||
} |
} |
||||
catch (Exception e) |
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}"); |
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}"); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Serilog; |
||||
|
using Serilog.Events; |
||||
|
|
||||
|
namespace EventHub |
||||
|
{ |
||||
|
public class Program |
||||
|
{ |
||||
|
public static async Task<int> Main(string[] args) |
||||
|
{ |
||||
|
Log.Logger = new LoggerConfiguration() |
||||
|
#if DEBUG
|
||||
|
.MinimumLevel.Debug() |
||||
|
#else
|
||||
|
.MinimumLevel.Information() |
||||
|
#endif
|
||||
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Information) |
||||
|
.Enrich.FromLogContext() |
||||
|
.WriteTo.Async(c => c.File("Logs/logs.txt")) |
||||
|
.WriteTo.Async(c => c.Console()) |
||||
|
.CreateLogger(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
Log.Information("Starting console host."); |
||||
|
await CreateHostBuilder(args).RunConsoleAsync(); |
||||
|
return 0; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
Log.Fatal(ex, "Host terminated unexpectedly!"); |
||||
|
return 1; |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
Log.CloseAndFlush(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
internal static IHostBuilder CreateHostBuilder(string[] args) => |
||||
|
Host.CreateDefaultBuilder(args) |
||||
|
.UseAutofac() |
||||
|
.UseSerilog() |
||||
|
.ConfigureServices((hostContext, services) => |
||||
|
{ |
||||
|
services.AddApplication<EventHubBackgroundServicesModule>(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -1,157 +1,157 @@ |
|||||
<html> |
<html> |
||||
|
|
||||
<head> |
<head> |
||||
<meta name="viewport" content="width=device-width"> |
<meta name="viewport" content="width=device-width"> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
||||
<title>Eventhub</title> |
<title>Eventhub</title> |
||||
<style> |
<style> |
||||
@media only screen and (max-width: 620px) { |
@media only screen and (max-width: 620px) { |
||||
table[class=mail-body] h1{ |
table[class=mail-body] h1{ |
||||
font-size: 36px !important; |
font-size: 36px !important; |
||||
margin-bottom: 10px !important; |
margin-bottom: 10px !important; |
||||
} |
} |
||||
table[class=mail-body] .content { |
table[class=mail-body] .content { |
||||
padding: 0 !important; |
padding: 0 !important; |
||||
} |
} |
||||
table[class=mail-body] .container { |
table[class=mail-body] .container { |
||||
padding: 0 !important; |
padding: 0 !important; |
||||
width: 100% !important; |
width: 100% !important; |
||||
} |
} |
||||
table[class=mail-body] .main { |
table[class=mail-body] .main { |
||||
border-left-width: 0 !important; |
border-left-width: 0 !important; |
||||
border-radius: 0 !important; |
border-radius: 0 !important; |
||||
border-right-width: 0 !important; |
border-right-width: 0 !important; |
||||
} |
} |
||||
table[class=mail-body] td { |
table[class=mail-body] td { |
||||
width: 100% !important; |
width: 100% !important; |
||||
float: left!important; |
float: left!important; |
||||
} |
} |
||||
table[class=mail-body] td a.btn { |
table[class=mail-body] td a.btn { |
||||
display: block !important; |
display: block !important; |
||||
text-align: center; |
text-align: center; |
||||
} |
} |
||||
table[class=mail-body] td h2 { |
table[class=mail-body] td h2 { |
||||
margin: 10px 0 !important; |
margin: 10px 0 !important; |
||||
} |
} |
||||
table[class=mail-body] td .cont { |
table[class=mail-body] td .cont { |
||||
padding: 30px !important; |
padding: 30px !important; |
||||
} |
} |
||||
} |
} |
||||
</style> |
</style> |
||||
</head> |
</head> |
||||
|
|
||||
<body> |
<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;"> |
<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> |
<tbody> |
||||
<tr> |
<tr> |
||||
<td class="container" width="800" style="display: block;margin: 0 auto !important; max-width: 800px;padding: 10px;width: 800px;"> |
<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 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"> |
<div style="display: flex; padding-top: 1.5rem"> |
||||
|
|
||||
<h1 style="justify-content: center;margin-left: auto;margin-right: auto;align-content: center;">EventHub</h1> |
<h1 style="justify-content: center;margin-left: auto;margin-right: auto;align-content: center;">EventHub</h1> |
||||
|
|
||||
</div> |
</div> |
||||
<!-- START CENTERED WHITE CONTAINER --> |
<!-- START CENTERED WHITE CONTAINER --> |
||||
<div style="display: flex;"> |
<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> |
<h1 style="justify-content: center;margin-left: auto;margin-right: auto;align-content: center;">{{model.title}} event time has been changed!</h1> |
||||
|
|
||||
</div> |
</div> |
||||
<table class="main" style="background: #ffffff;border-radius: 3px; width: 100%;"> |
<table class="main" style="background: #ffffff;border-radius: 3px; width: 100%;"> |
||||
<!-- START MAIN CONTENT AREA --> |
<!-- START MAIN CONTENT AREA --> |
||||
<tbody> |
<tbody> |
||||
<tr> |
<tr> |
||||
<td class="wrapper" width="800"> |
<td class="wrapper" width="800"> |
||||
<table class="main" style="background: #ffffff;border-radius: 3px;width: 100%;"> |
<table class="main" style="background: #ffffff;border-radius: 3px;width: 100%;"> |
||||
<tbody> |
<tbody> |
||||
<tr> |
<tr> |
||||
<td class="wrapper" width="800"> |
<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%; "> |
<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> |
<tbody> |
||||
<tr> |
<tr> |
||||
<td style="vertical-align: top;" width="100%"> |
<td style="vertical-align: top;" width="100%"> |
||||
<div class="cont" style="background: #eeeeee; border-radius: 10px; padding: 40px; margin-top: 20px;"> |
<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%; "> |
<table style="border-collapse: separate; vertical-align: top;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%; "> |
||||
<tbody> |
<tbody> |
||||
<tr> |
<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"> |
<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> |
<p> |
||||
Hi {{model.user_name}}, |
Hi {{model.user_name}}, |
||||
</p> |
</p> |
||||
<p> |
<p> |
||||
The "{{model.title}}" event time just rescheduled. |
The "{{model.title}}" event time just rescheduled. |
||||
</p> |
</p> |
||||
<div> |
<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%;"> |
<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> |
<tbody> |
||||
<tr style="text-align: center"> |
<tr style="text-align: center"> |
||||
<td style="vertical-align: top;"> |
<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: 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> |
<p style="font-size: 21px; font-family: Arial, sans-serif; color: #444444; margin-bottom: 30px; margin-top: 6px;">{{model.start_time}}</p> |
||||
</td> |
</td> |
||||
<td style="vertical-align: top;"> |
<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: 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> |
<p style="font-size: 21px; font-family: Arial, sans-serif; color: #444444; margin-bottom: 30px; margin-top: 6px;">{{model.end_time}}</p> |
||||
</td> |
</td> |
||||
|
|
||||
</tr> |
</tr> |
||||
</tbody> |
</tbody> |
||||
</table> |
</table> |
||||
<p style="margin-bottom: 20px; margin-top: 20px; text-align: center;"> |
<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> |
<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> |
</p> |
||||
</div> |
</div> |
||||
|
|
||||
<p style="font-size: 16px; font-family: Arial, sans-serif; color: #444; margin-top: 20px; margin-bottom: 0px;"> |
<p style="font-size: 16px; font-family: Arial, sans-serif; color: #444; margin-top: 20px; margin-bottom: 0px;"> |
||||
Thank you,<br> EventHub |
Thank you,<br> EventHub |
||||
</p> |
</p> |
||||
</td> |
</td> |
||||
</tr> |
</tr> |
||||
</tbody> |
</tbody> |
||||
</table> |
</table> |
||||
</div> |
</div> |
||||
</td> |
</td> |
||||
</tr> |
</tr> |
||||
</tbody> |
</tbody> |
||||
</table> |
</table> |
||||
</td> |
</td> |
||||
</tr> |
</tr> |
||||
</tbody> |
</tbody> |
||||
</table> |
</table> |
||||
|
|
||||
</td> |
</td> |
||||
</tr> |
</tr> |
||||
</tbody> |
</tbody> |
||||
</table> |
</table> |
||||
<!-- START FOOTER --> |
<!-- 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%;"> |
<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> |
<tbody> |
||||
<tr> |
<tr> |
||||
<td class="content-block" style="vertical-align: top;" width="800"> |
<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;"> |
<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> |
<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> |
</div> |
||||
</td> |
</td> |
||||
</tr> |
</tr> |
||||
<tr> |
<tr> |
||||
<td class="content-block powered-by" style="vertical-align: top;" width="100%"> |
<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;"> |
<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> |
<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> |
<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> |
</div> |
||||
</td> |
</td> |
||||
</tr> |
</tr> |
||||
</tbody> |
</tbody> |
||||
</table> |
</table> |
||||
</div> |
</div> |
||||
<!-- END FOOTER --> |
<!-- END FOOTER --> |
||||
<!-- END CENTERED WHITE CONTAINER --> |
<!-- END CENTERED WHITE CONTAINER --> |
||||
</td> |
</td> |
||||
</tr> |
</tr> |
||||
</tbody> |
</tbody> |
||||
</table> |
</table> |
||||
</body> |
</body> |
||||
|
|
||||
</html> |
</html> |
||||
Loading…
Reference in new issue