using Hangfire; using Manager.Services; using ManagerService.Data; using ManagerService.DTOs; using ManagerService.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using NSwag.Annotations; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace ManagerService.Controllers { [Authorize(Policy = ManagerService.Service.Security.Policies.InstanceAdmin)] [ApiController, Route("api/[controller]")] [OpenApiTag("Notification", Description = "Push notification management")] public class NotificationController : ControllerBase { private readonly ILogger _logger; private readonly MyInfoMateDbContext _db; private readonly NotificationService _notificationService; IHexIdGeneratorService idService = new HexIdGeneratorService(); public NotificationController( ILogger logger, MyInfoMateDbContext db, NotificationService notificationService) { _logger = logger; _db = db; _notificationService = notificationService; } /// /// Get all notifications for the current instance /// [ProducesResponseType(typeof(System.Collections.Generic.List), 200)] [ProducesResponseType(typeof(string), 500)] [HttpGet] public async Task Get() { try { var instanceId = User.FindFirst(ManagerService.Service.Security.ClaimTypes.InstanceId)?.Value; var notifications = await _db.PushNotifications .Where(n => n.InstanceId == instanceId) .OrderByDescending(n => n.DateCreation) .ToListAsync(); return new OkObjectResult(notifications.Select(n => n.ToDTO())); } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } /// /// Send a push notification immediately to all devices of the instance /// [ProducesResponseType(typeof(PushNotificationDTO), 200)] [ProducesResponseType(typeof(string), 400)] [ProducesResponseType(typeof(string), 500)] [HttpPost("send")] public async Task Send([FromBody] SendNotificationDTO dto) { try { if (dto == null) throw new ArgumentNullException("Notification param is null"); var instanceId = User.FindFirst(ManagerService.Service.Security.ClaimTypes.InstanceId)?.Value; var instance = await _db.Instances.FirstOrDefaultAsync(i => i.Id == instanceId); if (instance == null || !instance.IsPushNotification) throw new InvalidOperationException("Push notifications are not enabled for this instance"); var notification = new PushNotification { Id = idService.GenerateHexId(), InstanceId = instanceId, Title = dto.title, Body = dto.body, Topic = $"instance_{instanceId}", Status = PushNotificationStatus.Scheduled, DateCreation = DateTime.UtcNow }; _db.PushNotifications.Add(notification); await _db.SaveChangesAsync(); var jobId = BackgroundJob.Enqueue(s => s.SendToTopicAsync(notification.Id)); notification.HangfireJobId = jobId; await _db.SaveChangesAsync(); return new OkObjectResult(notification.ToDTO()); } catch (ArgumentNullException ex) { return new BadRequestObjectResult(ex.Message); } catch (InvalidOperationException ex) { return new ConflictObjectResult(ex.Message); } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } /// /// Schedule a push notification for a future date/time /// [ProducesResponseType(typeof(PushNotificationDTO), 200)] [ProducesResponseType(typeof(string), 400)] [ProducesResponseType(typeof(string), 500)] [HttpPost("schedule")] public async Task Schedule([FromBody] ScheduleNotificationDTO dto) { try { if (dto == null) throw new ArgumentNullException("Notification param is null"); if (dto.scheduledAt <= DateTime.UtcNow) throw new ArgumentException("Scheduled date must be in the future"); var instanceId = User.FindFirst(ManagerService.Service.Security.ClaimTypes.InstanceId)?.Value; var instance = await _db.Instances.FirstOrDefaultAsync(i => i.Id == instanceId); if (instance == null || !instance.IsPushNotification) throw new InvalidOperationException("Push notifications are not enabled for this instance"); var notification = new PushNotification { Id = idService.GenerateHexId(), InstanceId = instanceId, Title = dto.title, Body = dto.body, Topic = $"instance_{instanceId}", Status = PushNotificationStatus.Scheduled, ScheduledAt = dto.scheduledAt.ToUniversalTime(), DateCreation = DateTime.UtcNow }; _db.PushNotifications.Add(notification); await _db.SaveChangesAsync(); var delay = dto.scheduledAt.ToUniversalTime() - DateTime.UtcNow; var jobId = BackgroundJob.Schedule(s => s.SendToTopicAsync(notification.Id), delay); notification.HangfireJobId = jobId; await _db.SaveChangesAsync(); return new OkObjectResult(notification.ToDTO()); } catch (ArgumentNullException ex) { return new BadRequestObjectResult(ex.Message); } catch (ArgumentException ex) { return new BadRequestObjectResult(ex.Message); } catch (InvalidOperationException ex) { return new ConflictObjectResult(ex.Message); } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } /// /// Cancel a scheduled notification /// [ProducesResponseType(typeof(string), 202)] [ProducesResponseType(typeof(string), 404)] [ProducesResponseType(typeof(string), 500)] [HttpDelete("{id}")] public async Task Cancel(string id) { try { var instanceId = User.FindFirst(ManagerService.Service.Security.ClaimTypes.InstanceId)?.Value; var notification = await _db.PushNotifications .FirstOrDefaultAsync(n => n.Id == id && n.InstanceId == instanceId); if (notification == null) throw new KeyNotFoundException("Notification not found"); if (notification.Status != PushNotificationStatus.Scheduled) throw new InvalidOperationException("Only scheduled notifications can be cancelled"); if (notification.HangfireJobId != null) BackgroundJob.Delete(notification.HangfireJobId); _db.PushNotifications.Remove(notification); await _db.SaveChangesAsync(); return new ObjectResult("Notification cancelled") { StatusCode = 202 }; } catch (KeyNotFoundException ex) { return new NotFoundObjectResult(ex.Message); } catch (InvalidOperationException ex) { return new ConflictObjectResult(ex.Message); } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } } }