using System.Security.Claims; using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Infrastructure.Auth; using Jiaowu.Api.Infrastructure.Persistence; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace Jiaowu.Api.Controllers; [ApiController] [Authorize] [Route("api/notifications")] public sealed class NotificationsController( AppDbContext db, ICurrentUserDataScope currentUserDataScope) : ControllerBase { [HttpGet] public async Task GetNotifications( bool? unreadOnly, int page = 1, int pageSize = 20, CancellationToken cancellationToken = default) { var userId = currentUserDataScope.Current.UserId; var source = db.Notifications.AsNoTracking() .Where(x => x.UserId == userId); if (unreadOnly == true) source = source.Where(x => !x.IsRead); var total = await source.CountAsync(cancellationToken); var items = await source .OrderByDescending(x => x.CreatedAt) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(x => new { x.Id, x.Title, x.Content, x.IsRead, x.LinkUrl, x.CreatedAt }) .ToListAsync(cancellationToken); var unreadCount = await db.Notifications .CountAsync(x => x.UserId == userId && !x.IsRead, cancellationToken); return Ok(new { Items = items, Total = total, UnreadCount = unreadCount }); } [HttpGet("unread-count")] public async Task GetUnreadCount(CancellationToken cancellationToken) { var userId = currentUserDataScope.Current.UserId; var count = await db.Notifications .CountAsync(x => x.UserId == userId && !x.IsRead, cancellationToken); return Ok(new { Count = count }); } [HttpPost("{id:guid}/read")] public async Task MarkRead(Guid id, CancellationToken cancellationToken) { var userId = currentUserDataScope.Current.UserId; var n = await db.Notifications .FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId, cancellationToken); if (n is null) return NotFound(); n.IsRead = true; await db.SaveChangesAsync(cancellationToken); return NoContent(); } [HttpPost("read-all")] public async Task MarkAllRead(CancellationToken cancellationToken) { var userId = currentUserDataScope.Current.UserId; await db.Notifications .Where(x => x.UserId == userId && !x.IsRead) .ExecuteUpdateAsync(s => s.SetProperty(x => x.IsRead, true), cancellationToken); return NoContent(); } } /// /// Centralized helper to send notifications across the app. /// public static class NotificationService { public static async Task SendAsync( AppDbContext db, Guid userId, string title, string content, string? linkUrl = null, CancellationToken cancellationToken = default) { db.Notifications.Add(new Notification { UserId = userId, Title = title, Content = content, LinkUrl = linkUrl }); await db.SaveChangesAsync(cancellationToken); } public static async Task SendToRoleAsync( AppDbContext db, string roleName, string title, string content, Guid? collegeId = null, string? linkUrl = null, CancellationToken cancellationToken = default) { var query = db.Users .Join(db.UserRoles, u => u.Id, ur => ur.UserId, (u, ur) => new { u.Id, ur.RoleId }) .Join(db.Roles, x => x.RoleId, r => r.Id, (x, r) => new { x.Id, RoleName = r.Name! }); var userIds = query.Where(x => x.RoleName == roleName); if (collegeId.HasValue && roleName == SystemRoles.CollegeAdmin) { userIds = userIds.Where(x => db.Teachers.Any(t => t.UserId == x.Id && t.CollegeId == collegeId.Value)); } var ids = await userIds.Select(x => x.Id).Distinct().ToListAsync(cancellationToken); foreach (var id in ids) { db.Notifications.Add(new Notification { UserId = id, Title = title, Content = content, LinkUrl = linkUrl }); } await db.SaveChangesAsync(cancellationToken); } public static async Task SendToUserIdsAsync( AppDbContext db, IEnumerable userIds, string title, string content, string? linkUrl = null, CancellationToken cancellationToken = default) { foreach (var userId in userIds.Distinct()) { db.Notifications.Add(new Notification { UserId = userId, Title = title, Content = content, LinkUrl = linkUrl }); } await db.SaveChangesAsync(cancellationToken); } }