using System.Text.Json;
using GerbilManagerWebAPI.Models;
using Microsoft.EntityFrameworkCore;
using WebPush;
namespace GerbilManagerWebAPI.Push
{
///
/// Versendet Web-Push-Benachrichtigungen an alle gespeicherten Abos (PWA der Züchterin).
/// Ist kein VAPID-Schlüsselpaar konfiguriert, sind die Methoden No-Ops (Push deaktiviert).
/// Veraltete Abos (404/410) werden beim Senden entfernt.
///
public class PushNotifier
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger _log;
private readonly VapidDetails? _vapid;
public PushNotifier(IConfiguration config, IServiceScopeFactory scopeFactory, ILogger log)
{
_scopeFactory = scopeFactory;
_log = log;
var subject = config["WebPush:Subject"];
var publicKey = config["WebPush:PublicKey"];
var privateKey = config["WebPush:PrivateKey"];
if (!string.IsNullOrWhiteSpace(subject)
&& !string.IsNullOrWhiteSpace(publicKey)
&& !string.IsNullOrWhiteSpace(privateKey))
{
_vapid = new VapidDetails(subject, publicKey, privateKey);
}
}
/// true, wenn Push konfiguriert ist (VAPID-Schlüssel vorhanden).
public bool Enabled => _vapid is not null;
public string? PublicKey => _vapid?.PublicKey;
/// Eine Benachrichtigung an ALLE Abos senden. Fehler einzelner Abos werden geschluckt.
public async Task NotifyAllAsync(string title, string body, string url, CancellationToken ct = default)
{
if (_vapid is null) return;
using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService();
var subs = await db.WebPushSubscriptions.ToListAsync(ct);
if (subs.Count == 0) return;
var client = new WebPushClient();
var payload = JsonSerializer.Serialize(new { title, body, url });
var stale = new List();
foreach (var s in subs)
{
try
{
var pushSub = new WebPush.PushSubscription(s.Endpoint, s.P256dh, s.Auth);
await client.SendNotificationAsync(pushSub, payload, _vapid);
}
catch (WebPushException ex) when (ex.StatusCode is System.Net.HttpStatusCode.NotFound
or System.Net.HttpStatusCode.Gone)
{
stale.Add(s); // Abo abgelaufen/abgemeldet -> aufräumen
}
catch (Exception ex)
{
_log.LogWarning(ex, "Push an {Endpoint} fehlgeschlagen", s.Endpoint);
}
}
if (stale.Count > 0)
{
db.WebPushSubscriptions.RemoveRange(stale);
await db.SaveChangesAsync(ct);
}
}
}
}