using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using ROLAC.API.Data; using ROLAC.API.DTOs.Notifications; using ROLAC.API.Services.Logging; using ROLAC.API.Services.Notifications; namespace ROLAC.API.Controllers; /// /// Admin endpoints for the notification module (API-only phase). Binding-code generation, group /// management, send history, and manual send — the manual send endpoints are the only way to fire /// a message before a UI exists; programmatic callers use the services directly. /// [ApiController] [Route("api/notifications")] [Authorize] public sealed class NotificationsController : ControllerBase { private readonly IEmailService _email; private readonly ILineNotificationService _line; private readonly AppDbContext _db; private readonly CurrentUserAccessor _currentUser; public NotificationsController( IEmailService email, ILineNotificationService line, AppDbContext db, CurrentUserAccessor currentUser) { _email = email; _line = line; _db = db; _currentUser = currentUser; } [HttpPost("members/{id:int}/line-binding-code")] public async Task GenerateBindingCode(int id, CancellationToken ct) => Ok(new { code = await _line.GenerateLineBindingCodeAsync(id, ct) }); [HttpGet("groups")] public async Task Groups(CancellationToken ct) => Ok(await _db.MessagingGroups .OrderBy(g => g.Id) .Select(g => new { g.Id, g.Name, g.IsActive, g.RegisteredAt }) .ToListAsync(ct)); [HttpPut("groups/{id:int}")] public async Task UpdateGroup(int id, [FromBody] UpdateGroupRequest request, CancellationToken ct) { var group = await _db.MessagingGroups.FirstOrDefaultAsync(g => g.Id == id, ct); if (group is null) return NotFound(); group.Name = request.Name; group.IsActive = request.IsActive; await _db.SaveChangesAsync(ct); return NoContent(); } [HttpGet("history")] public async Task History( [FromQuery] int page = 1, [FromQuery] int pageSize = 50, CancellationToken ct = default) { var size = Math.Clamp(pageSize, 1, 200); var skip = (Math.Max(page, 1) - 1) * size; var query = _db.NotificationLogs.OrderByDescending(l => l.SentAt); var total = await query.CountAsync(ct); var items = await query .Skip(skip).Take(size) .Select(l => new { l.Id, l.Channel, l.TargetType, l.TargetExternalId, l.Subject, l.Status, l.Error, l.SentByUserId, l.SentAt, }) .ToListAsync(ct); return Ok(new { total, items }); } [HttpPost("send-line")] public async Task SendLine([FromBody] SendLineRequest request, CancellationToken ct) => Ok(await _line.SendLineAsync( request.Body, request.MemberIds ?? [], request.GroupIds ?? [], _currentUser.UserIdOrSystem, ct)); [HttpPost("send-email")] public async Task SendEmail([FromBody] SendEmailRequest request, CancellationToken ct) => Ok(await _email.SendAsync(new EmailMessage( MemberIds: request.MemberIds ?? [], Addresses: request.Addresses ?? [], Subject: request.Subject, HtmlBody: request.HtmlBody, Attachments: null, SentByUserId: _currentUser.UserIdOrSystem), ct)); }