[CLAUDE] App+Domain+Infra+Api+FE: Notifications module end-to-end
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m43s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m43s
Domain: - Notification entity + NotificationType enum (stable ints) - Nullable RefId cho correlation (contract, user, ...) Infrastructure: - NotificationConfiguration: bảng Notifications, index theo (UserId, ReadAt) - NotificationService: ghi vào DbContext, không SaveChanges (để caller quyết định unit-of-work — đảm bảo atomic với domain mutation) - EF migration AddNotifications Application: - INotificationService (Notify + NotifyMany) - CQRS: ListMyNotifications / GetMyUnreadCount / MarkRead / MarkAllRead Api: - NotificationsController: GET /api/notifications + unread-count + mark-read Integration: - ContractWorkflowService emit notification tới Drafter khi HĐ chuyển phase (skip nếu actor chính là Drafter). Title + type theo phase đích: DaPhatHanh → ContractPublished, TuChoi → ContractRejected, khác → ContractPhaseTransition. FE: - Both NotificationBell (admin + user) dùng /api/notifications thật (thay cho derived-from-inbox MVP trước đó). 30s refetch, click mark-read, 'Đọc hết' bulk action. Foundation sẵn cho SignalR push + email outbox sau này — chỉ cần mở rộng NotificationService mà không đổi caller. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,35 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SolutionErp.Application.Notifications;
|
||||
using SolutionErp.Application.Notifications.Dtos;
|
||||
|
||||
namespace SolutionErp.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/notifications")]
|
||||
[Authorize]
|
||||
public class NotificationsController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IReadOnlyList<NotificationDto>>> List(
|
||||
[FromQuery] bool unreadOnly = false,
|
||||
[FromQuery] int limit = 50,
|
||||
CancellationToken ct = default)
|
||||
=> Ok(await mediator.Send(new ListMyNotificationsQuery(unreadOnly, limit), ct));
|
||||
|
||||
[HttpGet("unread-count")]
|
||||
public async Task<ActionResult<int>> UnreadCount(CancellationToken ct)
|
||||
=> Ok(await mediator.Send(new GetMyUnreadCountQuery(), ct));
|
||||
|
||||
[HttpPost("{id:guid}/read")]
|
||||
public async Task<IActionResult> MarkRead(Guid id, CancellationToken ct)
|
||||
{
|
||||
await mediator.Send(new MarkNotificationReadCommand(id), ct);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("read-all")]
|
||||
public async Task<ActionResult<int>> MarkAllRead(CancellationToken ct)
|
||||
=> Ok(await mediator.Send(new MarkAllNotificationsReadCommand(), ct));
|
||||
}
|
||||
Reference in New Issue
Block a user