From 1cc7a7473a9cb9121b54a99ed3b17df6e25d1ade Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 24 May 2025 16:05:26 +0800 Subject: [PATCH] :sparkles: Notification administration APIs --- .../Account/NotificationController.cs | 65 ++++++++++++++++++- .../Account/NotificationService.cs | 52 +++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/DysonNetwork.Sphere/Account/NotificationController.cs b/DysonNetwork.Sphere/Account/NotificationController.cs index df4e47d..8036cfd 100644 --- a/DysonNetwork.Sphere/Account/NotificationController.cs +++ b/DysonNetwork.Sphere/Account/NotificationController.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using DysonNetwork.Sphere.Auth; +using DysonNetwork.Sphere.Permission; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -16,7 +17,7 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); if (currentUserValue is not Account currentUser) return Unauthorized(); - + var count = await db.Notifications .Where(s => s.AccountId == currentUser.Id && s.ViewedAt == null) .CountAsync(); @@ -95,4 +96,66 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C ).ExecuteDeleteAsync(); return Ok(affectedRows); } + + public class NotificationRequest + { + [Required] [MaxLength(1024)] public string Topic { get; set; } = null!; + [Required] [MaxLength(1024)] public string Title { get; set; } = null!; + [MaxLength(2048)] public string? Subtitle { get; set; } + [Required] [MaxLength(4096)] public string Content { get; set; } = null!; + public Dictionary? Meta { get; set; } + public int Priority { get; set; } = 10; + } + + [HttpPost("broadcast")] + [Authorize] + [RequiredPermission("global", "notifications.broadcast")] + public async Task BroadcastNotification( + [FromBody] NotificationRequest request, + [FromQuery] bool save = false + ) + { + await nty.BroadcastNotification( + new Notification + { + Topic = request.Topic, + Title = request.Title, + Subtitle = request.Subtitle, + Content = request.Content, + Meta = request.Meta, + Priority = request.Priority, + }, + save + ); + return Ok(); + } + + public class NotificationWithAimRequest : NotificationRequest + { + [Required] public List AccountId { get; set; } = null!; + } + + [HttpPost("send")] + [Authorize] + [RequiredPermission("global", "notifications.send")] + public async Task SendNotification( + [FromBody] NotificationWithAimRequest request, + [FromQuery] bool save = false + ) + { + var accounts = await db.Accounts.Where(a => request.AccountId.Contains(a.Id)).ToListAsync(); + await nty.SendNotificationBatch( + new Notification + { + Topic = request.Topic, + Title = request.Title, + Subtitle = request.Subtitle, + Content = request.Content, + Meta = request.Meta, + }, + accounts, + save + ); + return Ok(); + } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/NotificationService.cs b/DysonNetwork.Sphere/Account/NotificationService.cs index 8b18c44..ee58975 100644 --- a/DysonNetwork.Sphere/Account/NotificationService.cs +++ b/DysonNetwork.Sphere/Account/NotificationService.cs @@ -1,6 +1,7 @@ using CorePush.Apple; using CorePush.Firebase; using DysonNetwork.Sphere.Connection; +using EFCore.BulkExtensions; using Microsoft.EntityFrameworkCore; using NodaTime; @@ -152,6 +153,57 @@ public class NotificationService ); } + public async Task BroadcastNotification(Notification notification, bool save = false) + { + if (save) + { + var accounts = await _db.Accounts.ToListAsync(); + var notifications = accounts.Select(x => + { + notification.Account = x; + notification.AccountId = x.Id; + return notification; + }).ToList(); + await _db.BulkInsertAsync(notifications); + } + + var subscribers = await _db.NotificationPushSubscriptions + .ToListAsync(); + var tasks = new List(); + foreach (var subscriber in subscribers) + { + notification.AccountId = subscriber.AccountId; + tasks.Add(_PushSingleNotification(notification, subscriber)); + } + await Task.WhenAll(tasks); + } + + public async Task SendNotificationBatch(Notification notification, List accounts, bool save = false) + { + if (save) + { + var notifications = accounts.Select(x => + { + notification.Account = x; + notification.AccountId = x.Id; + return notification; + }).ToList(); + await _db.BulkInsertAsync(notifications); + } + + var accountsId = accounts.Select(x => x.Id).ToList(); + var subscribers = await _db.NotificationPushSubscriptions + .Where(s => accountsId.Contains(s.AccountId)) + .ToListAsync(); + var tasks = new List(); + foreach (var subscriber in subscribers) + { + notification.AccountId = subscriber.AccountId; + tasks.Add(_PushSingleNotification(notification, subscriber)); + } + await Task.WhenAll(tasks); + } + private async Task _PushSingleNotification(Notification notification, NotificationPushSubscription subscription) { switch (subscription.Provider)