Add ScheduledService and email things ༼ つ ◕_◕ ༽つ

This commit is contained in:
Stefan
2023-05-24 11:28:12 +02:00
parent 9520fa04b8
commit 5b342c1a8f
13 changed files with 208 additions and 95 deletions
+1
View File
@@ -351,3 +351,4 @@ MigrationBackup/
# Project specific # Project specific
*.sqlite* *.sqlite*
/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json
@@ -5,29 +5,39 @@ namespace Elektrifikatsiya.Models;
public class User public class User
{ {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Required]
[Key] public List<Device> Devices { get; set; } = new();
public int Id { get; private set; }
[Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Name { get; private set; } [Key]
public int Id { get; private set; }
[Required] public DateTime LastLoginDate { get; set; }
public string PasswordHash { get; private set; }
[Required] [Required]
public Role Role { get; set; } public string Name { get; private set; }
public string? SessionToken { get; set; } [Required]
public DateTime LastLoginDate { get; set; } public string PasswordHash { get; private set; }
[Required] [Required]
public List<Device> Devices { get; set; } = new(); public Role Role { get; set; }
public User(string name, string passwordHash, Role role) public string? SessionToken
{ {
Name = name; get => _token;
PasswordHash = passwordHash; set
Role = role; {
} _token = value;
}
}
private string? _token;
public User(string name, string passwordHash, Role role)
{
Name = name;
PasswordHash = passwordHash;
Role = role;
}
} }
@@ -1,4 +1,19 @@
@using System.Diagnostics; @using System.Diagnostics;
@using Elektrifikatsiya.Layouts; @using Elektrifikatsiya.Layouts;
@using Elektrifikatsiya.Services;
@inject IAuthenticationService AuthenticationService;
@inject NavigationManager NavigationManager
@page "/login" @page "/login"
@layout LoginLayout; @layout LoginLayout;
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
bool isAuthenticated = (await AuthenticationService.IsAuthenticated()).ValueOrDefault;
if (isAuthenticated)
{
NavigationManager.NavigateTo("/");
}
}
}
@@ -1,3 +1,5 @@
global using INotificationService = Elektrifikatsiya.Services.INotifcationService;
using Blazorise; using Blazorise;
using Blazorise.Bootstrap; using Blazorise.Bootstrap;
using Blazorise.Icons.Material; using Blazorise.Icons.Material;
@@ -14,6 +16,7 @@ using HiveMQtt.Client.Options;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args); WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Read configuration // Read configuration
@@ -24,8 +27,13 @@ builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor(); builder.Services.AddServerSideBlazor();
builder.Services.AddOptions(); builder.Services.AddOptions();
builder.Services.AddHttpContextAccessor(); builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<UpdateService>(); builder.Services.AddHostedService<ServiceScheduler>();
builder.Services.AddSingleton<IUpdateService, UpdateService>();
builder.Services.AddSingleton<IDeviceStatusService, DeviceStatusService>(); builder.Services.AddSingleton<IDeviceStatusService, DeviceStatusService>();
builder.Services.AddSingleton<INotificationService, NotifcationService>();
builder.Services.AddSingleton<IScheduledService>(s => s.GetRequiredService<IUpdateService>());
builder.Services.AddSingleton<IScheduledService>(s=>s.GetRequiredService<INotificationService>());
builder.Services.AddSingleton<IEmailService, EmailService>();
builder.Services.AddSingleton<IHiveMQClient, HiveMQClient>((provider)=> builder.Services.AddSingleton<IHiveMQClient, HiveMQClient>((provider)=>
{ {
HiveMQClientOptions options = new() HiveMQClientOptions options = new()
@@ -43,7 +51,6 @@ builder.Services.AddTransient<IDeviceManagmentService, DeviceManagmentService>()
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>(); builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
builder.Services.AddScoped<IAuthorizationService, AuthorizationService>(); builder.Services.AddScoped<IAuthorizationService, AuthorizationService>();
builder.Services.AddScoped<ICookieService, CookieService>(); builder.Services.AddScoped<ICookieService, CookieService>();
builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddDbContext<MainDatabaseContext>(options => options.UseSqlite("Data Source=./MainDatabase.sqlite")); builder.Services.AddDbContext<MainDatabaseContext>(options => options.UseSqlite("Data Source=./MainDatabase.sqlite"));
builder.Services.AddBootstrapProviders(); builder.Services.AddBootstrapProviders();
builder.Services.AddHttpClient<IDeviceManagmentService, DeviceManagmentService>(); builder.Services.AddHttpClient<IDeviceManagmentService, DeviceManagmentService>();
@@ -0,0 +1,8 @@
using Elektrifikatsiya.Utilities;
namespace Elektrifikatsiya.Services;
public interface INotifcationService : IScheduledService
{
}
@@ -0,0 +1,8 @@
using Elektrifikatsiya.Utilities;
namespace Elektrifikatsiya.Services;
public interface IUpdateService : IScheduledService
{
}
@@ -173,6 +173,8 @@ public class DeviceManagmentService : IDeviceManagmentService
PublishResult res = await hiveMQClient.PublishAsync($"shellies/shellyplug-s-{device.MacAddress}/relay/0/command", device.Enabled ? "on" : "off"); PublishResult res = await hiveMQClient.PublishAsync($"shellies/shellyplug-s-{device.MacAddress}/relay/0/command", device.Enabled ? "on" : "off");
device.User = mainDatabaseContext.Users.First(u => u.Id == device.User.Id);
_ = mainDatabaseContext.Update(device); _ = mainDatabaseContext.Update(device);
return await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync()); return await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync());
@@ -12,33 +12,32 @@ namespace Elektrifikatsiya.Services.Implementations
{ {
private readonly EmailSettings emailSettings; private readonly EmailSettings emailSettings;
public EmailService(IOptions<EmailSettings> emailSettings) public EmailService(IOptions<EmailSettings> emailSettings)
{ {
this.emailSettings = emailSettings.Value; this.emailSettings = emailSettings.Value;
} }
public async Task SendAsync(string to, string subject, string html, string? from = null) public async Task SendAsync(string to, string subject, string html, string? from = null)
{ {
from ??= emailSettings.DefaultEmail; from ??= emailSettings.DefaultEmail;
MimeMessage message = new MimeMessage(); MimeMessage message = new MimeMessage();
message.From.Add(MailboxAddress.Parse(from)); message.From.Add(MailboxAddress.Parse(from));
message.To.Add(MailboxAddress.Parse(to)); message.To.Add(MailboxAddress.Parse(to));
message.Subject = subject; message.Subject = subject;
message.Body = new TextPart(TextFormat.Html) { Text = html }; message.Body = new TextPart(TextFormat.Html) { Text = html };
using SmtpClient smtp = new SmtpClient(); using SmtpClient smtp = new SmtpClient();
await smtp.ConnectAsync(emailSettings.SmtpServer, emailSettings.SmtpPort, SecureSocketOptions.StartTls); await smtp.ConnectAsync(emailSettings.SmtpServer, emailSettings.SmtpPort, SecureSocketOptions.StartTls);
await smtp.AuthenticateAsync(emailSettings.User, emailSettings.Key); await smtp.AuthenticateAsync(emailSettings.User, emailSettings.Key);
await smtp.SendAsync(message); await smtp.SendAsync(message);
await smtp.DisconnectAsync(true); await smtp.DisconnectAsync(true);
} }
public Task SendWithTemeplateAsync(string to, string subject, string templateKey, string? from = null, Dictionary<string, string>? templateParameters = null) public Task SendWithTemeplateAsync(string to, string subject, string templateKey, string? from = null, Dictionary<string, string>? templateParameters = null)
{ {
string content = emailSettings.CompiledTemplates[templateKey](templateParameters ?? new()); string content = emailSettings.CompiledTemplates[templateKey](templateParameters ?? new());
return SendAsync(to, subject, content, from); return SendAsync(to, subject, content, from);
}
}
} }
} }
@@ -0,0 +1,24 @@
using Elektrifikatsiya.Utilities;
namespace Elektrifikatsiya.Services.Implementations;
public class NotifcationService : INotifcationService
{
public TimeSpan ExecutionRepeatDelay => TimeSpan.FromSeconds(30);
public DateTime FirstExecutionTime => DateTime.Now.AddDays(0);
private readonly IEmailService emailService;
private readonly ILogger<NotifcationService> logger;
public NotifcationService(ILogger<NotifcationService> logger, IEmailService emailService)
{
this.logger = logger;
this.emailService = emailService;
}
public async void Update()
{
//logger.LogInformation("Sending Email");
//await emailService.SendWithTemeplateAsync("[email protected]", "AAAA", "DailyNotification", templateParameters: new() { { "yesterday", "10" }, { "yearly", "100" }, { "link", "localhost:5565/yes" } });
//logger.LogInformation("Done sending");
}
}
@@ -0,0 +1,61 @@
using Elektrifikatsiya.Utilities;
namespace Elektrifikatsiya.Services.Implementations;
public class ServiceScheduler : IHostedService
{
private readonly ILogger<ServiceScheduler> logger;
private readonly IEnumerable<IScheduledService> scheduledServices;
private readonly Dictionary<IScheduledService, DateTime> lastExectued;
private Timer timer = null!;
public ServiceScheduler(ILogger<ServiceScheduler> logger, IServiceProvider serviceProvider)
{
this.logger = logger;
scheduledServices = serviceProvider.GetServices<IScheduledService>();
lastExectued = scheduledServices.ToDictionary(x => x, x => x.FirstExecutionTime);
}
public async Task StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Starting service scheduler...");
timer = new Timer(async (_) => await Update(), null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
logger.LogInformation("Service scheduler started.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Stopping service scheduler.");
timer.Dispose();
logger.LogInformation("Stopped service scheduler.");
return Task.CompletedTask;
}
private async Task Update()
{
TimeSpan s = lastExectued.Min(x => (x.Value + x.Key.ExecutionRepeatDelay)) - DateTime.Now;
if (s < TimeSpan.Zero)
{
s = TimeSpan.FromSeconds(1);
}
timer.Change(s, Timeout.InfiniteTimeSpan);
foreach (IScheduledService scheduledService in scheduledServices.Where(s => s.FirstExecutionTime < DateTime.Now))
{
if (lastExectued[scheduledService] + scheduledService.ExecutionRepeatDelay <= DateTime.Now)
{
lastExectued[scheduledService] = DateTime.Now;
scheduledService.Update();
}
}
}
}
@@ -8,38 +8,29 @@ using Microsoft.EntityFrameworkCore;
namespace Elektrifikatsiya.Services.Implementations; namespace Elektrifikatsiya.Services.Implementations;
public class UpdateService : IHostedService, IDisposable public class UpdateService : IUpdateService
{ {
private readonly ILogger<UpdateService> logger; private readonly ILogger<UpdateService> logger;
private readonly IDeviceStatusService deviceStatusService; private readonly IDeviceStatusService deviceStatusService;
private readonly IServiceScopeFactory serviceScopeFactory;
private Timer? timer = null;
public UpdateService(ILogger<UpdateService> logger, IDeviceStatusService deviceStatusService, IServiceScopeFactory serviceScopeFactory) public UpdateService(ILogger<UpdateService> logger, IDeviceStatusService deviceStatusService, IServiceScopeFactory serviceScopeFactory)
{ {
this.logger = logger; this.logger = logger;
this.deviceStatusService = deviceStatusService; this.deviceStatusService = deviceStatusService;
this.serviceScopeFactory = serviceScopeFactory;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
IServiceScope serviceScope = serviceScopeFactory.CreateScope(); IServiceScope serviceScope = serviceScopeFactory.CreateScope();
MainDatabaseContext mainDatabaseContext = serviceScope.ServiceProvider.GetRequiredService<MainDatabaseContext>(); MainDatabaseContext mainDatabaseContext = serviceScope.ServiceProvider.GetRequiredService<MainDatabaseContext>();
logger.LogInformation("Starting update service..."); foreach (Device device in mainDatabaseContext.Devices.Include(d => d.User).AsNoTracking().ToList())
foreach (Device device in await mainDatabaseContext.Devices.Include(d => d.User).AsNoTracking().ToListAsync(cancellationToken))
{ {
_ = deviceStatusService.TrackDevice(device); _ = deviceStatusService.TrackDevice(device);
} }
timer = new Timer(async (_) => await Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15));
logger.LogInformation("Update service started.");
} }
private async Task Update() public DateTime FirstExecutionTime { get; } = DateTime.Now.AddSeconds(15);
public TimeSpan ExecutionRepeatDelay { get; } = TimeSpan.FromSeconds(15);
public async void Update()
{ {
Result<List<Device>> getDeviceStatusResult = deviceStatusService.GetDevices(); Result<List<Device>> getDeviceStatusResult = deviceStatusService.GetDevices();
@@ -61,26 +52,4 @@ public class UpdateService : IHostedService, IDisposable
} }
} }
} }
public Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Stopping update service.");
_ = timer?.Change(Timeout.Infinite, 0);
logger.LogInformation("Stopped update service.");
return Task.CompletedTask;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
timer?.Dispose();
}
} }
@@ -0,0 +1,9 @@
namespace Elektrifikatsiya.Utilities;
public interface IScheduledService
{
DateTime FirstExecutionTime { get; }
TimeSpan ExecutionRepeatDelay { get; }
void Update();
}
@@ -1,19 +1,19 @@
{ {
"DetailedErrors": true, "DetailedErrors": true,
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
}
},
"EmailSettings": {
"User": "",
"DefaultEmail": "",
"Key": "",
"Templates": {
},
"SmtpServer": "",
"SmtpPort": 0
} }
} },
"EmailSettings": {
"User": "[email protected]",
"DefaultEmail": "[email protected]",
"Key": "xsmtpsib-34d2ebc5edbb9b7bdbb9fd8ad4f724a759135883c52f9a9aadd919f7492f215a-FztV7QTUhdyaSk9p",
"Templates": {
"DailyNotification": "../assets/DailyNotif.html"
},
"SmtpServer": "smtp-relay.sendinblue.com",
"SmtpPort": 587
}
}