From 5b342c1a8fe27e525c957fdc0a627c4d5b018b82 Mon Sep 17 00:00:00 2001 From: Stefan <32109571+Stefan-5422@users.noreply.github.com> Date: Wed, 24 May 2023 11:28:12 +0200 Subject: [PATCH] =?UTF-8?q?Add=20`ScheduledService`=20and=20email=20things?= =?UTF-8?q?=20=E0=BC=BC=20=E3=81=A4=20=E2=97=95=5F=E2=97=95=20=E0=BC=BD?= =?UTF-8?q?=E3=81=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../Elektrifikatsiya/Models/User.cs | 48 +++++++++------ .../Elektrifikatsiya/Pages/Login.razor | 17 +++++- .../Elektrifikatsiya/Program.cs | 11 +++- .../Services/INotifcationService.cs | 8 +++ .../Services/IUpdateService.cs | 8 +++ .../Implementations/DeviceManagmentService.cs | 2 + .../Services/Implementations/EmailService.cs | 37 ++++++----- .../Implementations/NotifcationService.cs | 24 ++++++++ .../Implementations/ServiceScheduler.cs | 61 +++++++++++++++++++ .../Services/Implementations/UpdateService.cs | 43 ++----------- .../Utilities/IScheduledService.cs | 9 +++ .../appsettings.Development.json | 34 +++++------ 13 files changed, 208 insertions(+), 95 deletions(-) create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Services/INotifcationService.cs create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Services/IUpdateService.cs create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/NotifcationService.cs create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/ServiceScheduler.cs create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Utilities/IScheduledService.cs diff --git a/.gitignore b/.gitignore index 883ab1e..d5cce1e 100644 --- a/.gitignore +++ b/.gitignore @@ -351,3 +351,4 @@ MigrationBackup/ # Project specific *.sqlite* +/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/User.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/User.cs index be6b1d8..e321294 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/User.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/User.cs @@ -5,29 +5,39 @@ namespace Elektrifikatsiya.Models; public class User { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [Key] - public int Id { get; private set; } + [Required] + public List 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 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; + } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Login.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Login.razor index 5c303a0..f62fe54 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Login.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Login.razor @@ -1,4 +1,19 @@ @using System.Diagnostics; @using Elektrifikatsiya.Layouts; +@using Elektrifikatsiya.Services; +@inject IAuthenticationService AuthenticationService; +@inject NavigationManager NavigationManager @page "/login" -@layout LoginLayout; \ No newline at end of file +@layout LoginLayout; + +@code { + protected override async Task OnAfterRenderAsync(bool firstRender) + { + bool isAuthenticated = (await AuthenticationService.IsAuthenticated()).ValueOrDefault; + + if (isAuthenticated) + { + NavigationManager.NavigateTo("/"); + } + } +} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs index adb054c..1e6f44b 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs @@ -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(); +builder.Services.AddHostedService(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(s => s.GetRequiredService()); +builder.Services.AddSingleton(s=>s.GetRequiredService()); +builder.Services.AddSingleton(); builder.Services.AddSingleton((provider)=> { HiveMQClientOptions options = new() @@ -43,7 +51,6 @@ builder.Services.AddTransient() builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddDbContext(options => options.UseSqlite("Data Source=./MainDatabase.sqlite")); builder.Services.AddBootstrapProviders(); builder.Services.AddHttpClient(); diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/INotifcationService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/INotifcationService.cs new file mode 100644 index 0000000..96e1b23 --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/INotifcationService.cs @@ -0,0 +1,8 @@ +using Elektrifikatsiya.Utilities; + +namespace Elektrifikatsiya.Services; + +public interface INotifcationService : IScheduledService +{ + +} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/IUpdateService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/IUpdateService.cs new file mode 100644 index 0000000..acfae54 --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/IUpdateService.cs @@ -0,0 +1,8 @@ +using Elektrifikatsiya.Utilities; + +namespace Elektrifikatsiya.Services; + +public interface IUpdateService : IScheduledService +{ + +} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs index 12e4bb1..57f2180 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs @@ -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()); diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/EmailService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/EmailService.cs index 8198e8f..5ea518a 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/EmailService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/EmailService.cs @@ -12,33 +12,32 @@ namespace Elektrifikatsiya.Services.Implementations { private readonly EmailSettings emailSettings; - public EmailService(IOptions emailSettings) - { - this.emailSettings = emailSettings.Value; - } + public EmailService(IOptions 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? templateParameters = null) { string content = emailSettings.CompiledTemplates[templateKey](templateParameters ?? new()); return SendAsync(to, subject, content, from); - - } + } } -} +} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/NotifcationService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/NotifcationService.cs new file mode 100644 index 0000000..b87897b --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/NotifcationService.cs @@ -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 logger; + + public NotifcationService(ILogger logger, IEmailService emailService) + { + this.logger = logger; + this.emailService = emailService; + } + + public async void Update() + { + //logger.LogInformation("Sending Email"); + //await emailService.SendWithTemeplateAsync("manuel.strubegger@htl-saalfelden.at", "AAAA", "DailyNotification", templateParameters: new() { { "yesterday", "10" }, { "yearly", "100" }, { "link", "localhost:5565/yes" } }); + //logger.LogInformation("Done sending"); + } +} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/ServiceScheduler.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/ServiceScheduler.cs new file mode 100644 index 0000000..c430ade --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/ServiceScheduler.cs @@ -0,0 +1,61 @@ +using Elektrifikatsiya.Utilities; + +namespace Elektrifikatsiya.Services.Implementations; + +public class ServiceScheduler : IHostedService +{ + private readonly ILogger logger; + private readonly IEnumerable scheduledServices; + private readonly Dictionary lastExectued; + + private Timer timer = null!; + + public ServiceScheduler(ILogger logger, IServiceProvider serviceProvider) + { + this.logger = logger; + scheduledServices = serviceProvider.GetServices(); + 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(); + } + } + } +} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs index 0a3ef4a..94070e1 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs @@ -8,38 +8,29 @@ using Microsoft.EntityFrameworkCore; namespace Elektrifikatsiya.Services.Implementations; -public class UpdateService : IHostedService, IDisposable +public class UpdateService : IUpdateService { private readonly ILogger logger; private readonly IDeviceStatusService deviceStatusService; - private readonly IServiceScopeFactory serviceScopeFactory; - private Timer? timer = null; public UpdateService(ILogger 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(); - 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> 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(); - } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/IScheduledService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/IScheduledService.cs new file mode 100644 index 0000000..7c4e0f7 --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/IScheduledService.cs @@ -0,0 +1,9 @@ +namespace Elektrifikatsiya.Utilities; + +public interface IScheduledService +{ + DateTime FirstExecutionTime { get; } + TimeSpan ExecutionRepeatDelay { get; } + + void Update(); +} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json b/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json index 995b937..6e66cb1 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json +++ b/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json @@ -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": "sendinblue@stone-red.net", + "DefaultEmail": "noreply@status.exposed", + "Key": "xsmtpsib-34d2ebc5edbb9b7bdbb9fd8ad4f724a759135883c52f9a9aadd919f7492f215a-FztV7QTUhdyaSk9p", + "Templates": { + "DailyNotification": "../assets/DailyNotif.html" + }, + "SmtpServer": "smtp-relay.sendinblue.com", + "SmtpPort": 587 + } +} \ No newline at end of file