using System.ComponentModel.DataAnnotations; using DysonNetwork.Sphere.Auth; using DysonNetwork.Sphere.Permission; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace DysonNetwork.Sphere.Account; [ApiController] [Route("/notifications")] public class NotificationController(AppDatabase db, NotificationService nty) : ControllerBase { [HttpGet("count")] [Authorize] public async Task> CountUnreadNotifications() { 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(); return Ok(count); } [HttpGet] [Authorize] public async Task>> ListNotifications( [FromQuery] int offset = 0, // The page size set to 5 is to avoid the client pulled the notification // but didn't render it in the screen-viewable region. [FromQuery] int take = 5 ) { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); if (currentUserValue is not Account currentUser) return Unauthorized(); var totalCount = await db.Notifications .Where(s => s.AccountId == currentUser.Id) .CountAsync(); var notifications = await db.Notifications .Where(s => s.AccountId == currentUser.Id) .OrderByDescending(e => e.CreatedAt) .Skip(offset) .Take(take) .ToListAsync(); Response.Headers["X-Total"] = totalCount.ToString(); await nty.MarkNotificationsViewed(notifications); return Ok(notifications); } public class PushNotificationSubscribeRequest { [MaxLength(4096)] public string DeviceToken { get; set; } = null!; public NotificationPushProvider Provider { get; set; } } [HttpPut("subscription")] [Authorize] public async Task> SubscribeToPushNotification( [FromBody] PushNotificationSubscribeRequest request ) { HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue); HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account; if (currentUser == null) return Unauthorized(); var currentSession = currentSessionValue as Session; if (currentSession == null) return Unauthorized(); var result = await nty.SubscribePushNotification(currentUser, request.Provider, currentSession.Challenge.DeviceId!, request.DeviceToken); return Ok(result); } [HttpDelete("subscription")] [Authorize] public async Task> UnsubscribeFromPushNotification() { HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue); HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account; if (currentUser == null) return Unauthorized(); var currentSession = currentSessionValue as Session; if (currentSession == null) return Unauthorized(); var affectedRows = await db.NotificationPushSubscriptions .Where(s => s.AccountId == currentUser.Id && s.DeviceId == currentSession.Challenge.DeviceId ).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(); } }