mirror of
https://github.com/Stefan-5422/-T5-Elektrifikatsiya.git
synced 2026-09-04 00:45:58 +02:00
Add ScheduledService and email things ༼ つ ◕_◕ ༽つ
This commit is contained in:
@@ -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
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
+2
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user