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
*.sqlite*
/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json
@@ -5,29 +5,39 @@ namespace Elektrifikatsiya.Models;
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; private set; }
[Required]
public List<Device> Devices { get; set; } = new();
[Required]
public string Name { get; private set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; private set; }
[Required]
public string PasswordHash { get; private set; }
public DateTime LastLoginDate { get; set; }
[Required]
public Role Role { get; set; }
[Required]
public string Name { get; private set; }
public string? SessionToken { get; set; }
public DateTime LastLoginDate { get; set; }
[Required]
public string PasswordHash { get; private set; }
[Required]
public List<Device> Devices { get; set; } = new();
[Required]
public Role Role { get; set; }
public User(string name, string passwordHash, Role role)
{
Name = name;
PasswordHash = passwordHash;
Role = role;
}
public string? SessionToken
{
get => _token;
set
{
_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 Elektrifikatsiya.Layouts;
@using Elektrifikatsiya.Services;
@inject IAuthenticationService AuthenticationService;
@inject NavigationManager NavigationManager
@page "/login"
@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.Bootstrap;
using Blazorise.Icons.Material;
@@ -14,6 +16,7 @@ using HiveMQtt.Client.Options;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Read configuration
@@ -24,8 +27,13 @@ builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddOptions();
builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<UpdateService>();
builder.Services.AddHostedService<ServiceScheduler>();
builder.Services.AddSingleton<IUpdateService, UpdateService>();
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)=>
{
HiveMQClientOptions options = new()
@@ -43,7 +51,6 @@ builder.Services.AddTransient<IDeviceManagmentService, DeviceManagmentService>()
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
builder.Services.AddScoped<IAuthorizationService, AuthorizationService>();
builder.Services.AddScoped<ICookieService, CookieService>();
builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddDbContext<MainDatabaseContext>(options => options.UseSqlite("Data Source=./MainDatabase.sqlite"));
builder.Services.AddBootstrapProviders();
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");
device.User = mainDatabaseContext.Users.First(u => u.Id == device.User.Id);
_ = mainDatabaseContext.Update(device);
return await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync());
@@ -12,33 +12,32 @@ namespace Elektrifikatsiya.Services.Implementations
{
private readonly EmailSettings emailSettings;
public EmailService(IOptions<EmailSettings> emailSettings)
{
this.emailSettings = emailSettings.Value;
}
public EmailService(IOptions<EmailSettings> emailSettings)
{
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;
MimeMessage message = new MimeMessage();
message.From.Add(MailboxAddress.Parse(from));
message.To.Add(MailboxAddress.Parse(to));
message.Subject = subject;
message.Body = new TextPart(TextFormat.Html) { Text = html };
MimeMessage message = new MimeMessage();
message.From.Add(MailboxAddress.Parse(from));
message.To.Add(MailboxAddress.Parse(to));
message.Subject = subject;
message.Body = new TextPart(TextFormat.Html) { Text = html };
using SmtpClient smtp = new SmtpClient();
await smtp.ConnectAsync(emailSettings.SmtpServer, emailSettings.SmtpPort, SecureSocketOptions.StartTls);
await smtp.AuthenticateAsync(emailSettings.User, emailSettings.Key);
await smtp.SendAsync(message);
await smtp.DisconnectAsync(true);
}
using SmtpClient smtp = new SmtpClient();
await smtp.ConnectAsync(emailSettings.SmtpServer, emailSettings.SmtpPort, SecureSocketOptions.StartTls);
await smtp.AuthenticateAsync(emailSettings.User, emailSettings.Key);
await smtp.SendAsync(message);
await smtp.DisconnectAsync(true);
}
public Task SendWithTemeplateAsync(string to, string subject, string templateKey, string? from = null, Dictionary<string, string>? templateParameters = null)
{
string content = emailSettings.CompiledTemplates[templateKey](templateParameters ?? new());
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;
public class UpdateService : IHostedService, IDisposable
public class UpdateService : IUpdateService
{
private readonly ILogger<UpdateService> logger;
private readonly IDeviceStatusService deviceStatusService;
private readonly IServiceScopeFactory serviceScopeFactory;
private Timer? timer = null;
public UpdateService(ILogger<UpdateService> logger, IDeviceStatusService deviceStatusService, IServiceScopeFactory serviceScopeFactory)
{
this.logger = logger;
this.deviceStatusService = deviceStatusService;
this.serviceScopeFactory = serviceScopeFactory;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
IServiceScope serviceScope = serviceScopeFactory.CreateScope();
MainDatabaseContext mainDatabaseContext = serviceScope.ServiceProvider.GetRequiredService<MainDatabaseContext>();
logger.LogInformation("Starting update service...");
foreach (Device device in await mainDatabaseContext.Devices.Include(d => d.User).AsNoTracking().ToListAsync(cancellationToken))
foreach (Device device in mainDatabaseContext.Devices.Include(d => d.User).AsNoTracking().ToList())
{
_ = 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();
@@ -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,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"EmailSettings": {
"User": "",
"DefaultEmail": "",
"Key": "",
"Templates": {
},
"SmtpServer": "",
"SmtpPort": 0
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"EmailSettings": {
"User": "[email protected]",
"DefaultEmail": "[email protected]",
"Key": "xsmtpsib-34d2ebc5edbb9b7bdbb9fd8ad4f724a759135883c52f9a9aadd919f7492f215a-FztV7QTUhdyaSk9p",
"Templates": {
"DailyNotification": "../assets/DailyNotif.html"
},
"SmtpServer": "smtp-relay.sendinblue.com",
"SmtpPort": 587
}
}