feat: Phase 2 — Delegated State, Auth, Review-Gate, Notifications, Zombie-Reset
CI - Build & Test / Backend (.NET) (push) Successful in 37s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 24s
CI - Build & Test / Security Check (push) Successful in 4s

This commit is contained in:
2026-06-18 23:47:41 +02:00
parent 12998170e3
commit dcc8450c62
32 changed files with 1758 additions and 38 deletions
@@ -0,0 +1,53 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Nexus.Api.Data;
using Nexus.Api.Models;
using Nexus.Api.Services;
namespace Nexus.Api.Controllers;
[Authorize]
[ApiController]
[Route("api/dashboard/notifications")]
public class NotificationsController(INotificationService notificationService) : ControllerBase
{
[HttpGet]
public async Task<ActionResult<List<NotificationDto>>> GetNotifications(
[FromQuery] string forUser = "bao",
[FromQuery] int limit = 50,
[FromQuery] bool unreadOnly = false,
CancellationToken ct = default)
{
var notifications = await notificationService.GetForUserAsync(forUser, limit, unreadOnly, ct);
return Ok(notifications.Select(MapToDto).ToList());
}
[HttpGet("unread-count")]
public async Task<ActionResult<UnreadCountDto>> GetUnreadCount(
[FromQuery] string forUser = "bao",
CancellationToken ct = default)
{
var count = await notificationService.GetUnreadCountAsync(forUser, ct);
return Ok(new UnreadCountDto(count));
}
[HttpPatch("{id:guid}/read")]
public async Task<ActionResult> MarkAsRead(Guid id, CancellationToken ct = default)
{
var ok = await notificationService.MarkAsReadAsync(id, ct);
return ok ? NoContent() : NotFound(new { error = "Notification not found." });
}
[HttpPatch("read-all")]
public async Task<ActionResult> MarkAllAsRead(
[FromQuery] string forUser = "bao",
CancellationToken ct = default)
{
var count = await notificationService.MarkAllAsReadAsync(forUser, ct);
return Ok(new { marked = count });
}
private static NotificationDto MapToDto(Notification n) => new(
n.Id, n.Type, n.Title, n.Message,
n.ForUser, n.TaskId, n.IsRead, n.CreatedAt);
}