diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2026-04-02 12:38:10 +0200 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2026-04-02 12:45:33 +0200 |
| commit | 1b4f4a674ac533c0b51260ba35ab91dd2cf9486d (patch) | |
| tree | 9fdaf418bef86c51737bcf203483089c9e2b908b /src/Enmarcha.Backend/Services/AlertPhaseNotificationHostedService.cs | |
| parent | 749e04d6fc2304bb29920db297d1fa4d73b57648 (diff) | |
Basic push notification system for service alerts
Co-authored-by: Copilot <copilot@github.com>
Diffstat (limited to 'src/Enmarcha.Backend/Services/AlertPhaseNotificationHostedService.cs')
| -rw-r--r-- | src/Enmarcha.Backend/Services/AlertPhaseNotificationHostedService.cs | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/src/Enmarcha.Backend/Services/AlertPhaseNotificationHostedService.cs b/src/Enmarcha.Backend/Services/AlertPhaseNotificationHostedService.cs new file mode 100644 index 0000000..5cbbaee --- /dev/null +++ b/src/Enmarcha.Backend/Services/AlertPhaseNotificationHostedService.cs @@ -0,0 +1,77 @@ +using Enmarcha.Backend.Data; +using Microsoft.EntityFrameworkCore; + +namespace Enmarcha.Backend.Services; + +/// <summary> +/// Background service that automatically sends push notifications when a service alert +/// transitions into the PreNotice or Active phase without having been notified yet. +/// Runs every 60 seconds and also immediately on startup to handle any missed transitions. +/// </summary> +public class AlertPhaseNotificationHostedService( + IServiceScopeFactory scopeFactory, + ILogger<AlertPhaseNotificationHostedService> logger) : IHostedService, IDisposable +{ + private Timer? _timer; + + public Task StartAsync(CancellationToken cancellationToken) + { + // Run immediately, then every 60 seconds + _timer = new Timer(_ => _ = RunAsync(), null, TimeSpan.Zero, TimeSpan.FromSeconds(60)); + return Task.CompletedTask; + } + + private async Task RunAsync() + { + try + { + using var scope = scopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); + var pushService = scope.ServiceProvider.GetRequiredService<IPushNotificationService>(); + + var now = DateTime.UtcNow; + + // Find alerts that are published and not yet hidden, but haven't been notified + // for their current phase (PreNotice: published but event not yet started; + // Active: event in progress). + var alertsToNotify = await db.ServiceAlerts + .Where(a => + a.PublishDate <= now && a.HiddenDate > now && + ( + // PreNotice: published, event hasn't started, no prenotice notification sent yet + (a.EventStartDate > now && a.PreNoticeNotifiedAt == null) || + // Active: event started and not finished, no active notification sent yet + (a.EventStartDate <= now && a.EventEndDate > now && a.ActiveNotifiedAt == null) + )) + .ToListAsync(); + + if (alertsToNotify.Count == 0) return; + + logger.LogInformation("Sending push notifications for {Count} alert(s)", alertsToNotify.Count); + + foreach (var alert in alertsToNotify) + { + try + { + await pushService.SendAlertAsync(alert); + } + catch (Exception ex) + { + logger.LogError(ex, "Error sending push notification for alert {AlertId}", alert.Id); + } + } + } + catch (Exception ex) + { + logger.LogError(ex, "Unexpected error in {ServiceName}", nameof(AlertPhaseNotificationHostedService)); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _timer?.Change(Timeout.Infinite, 0); + return Task.CompletedTask; + } + + public void Dispose() => _timer?.Dispose(); +} |
