1+ using Microsoft . AspNetCore . Authorization ;
2+ using Microsoft . AspNetCore . Mvc ;
3+ using Microsoft . EntityFrameworkCore ;
4+ using ThingConnect . Pulse . Server . Data ;
5+ using ThingConnect . Pulse . Server . Models ;
6+ using ThingConnect . Pulse . Server . Services ;
7+
8+ namespace ThingConnect . Pulse . Server . Controllers ;
9+
10+ [ ApiController ]
11+ [ Route ( "api/[controller]" ) ]
12+ [ Authorize ]
13+ public sealed class NotificationController : ControllerBase
14+ {
15+ private readonly PulseDbContext _context ;
16+ private readonly ILogger < NotificationController > _logger ;
17+ private readonly INotificationService _notificationService ;
18+
19+ public NotificationController ( PulseDbContext context , ILogger < NotificationController > logger , INotificationService notificationService )
20+ {
21+ _context = context ;
22+ _logger = logger ;
23+ _notificationService = notificationService ;
24+ }
25+
26+ /// <summary>
27+ /// Get active notifications for the current user.
28+ /// </summary>
29+ /// <param name="includeRead">Whether to include already read notifications.</param>
30+ /// <returns>List of active notifications.</returns>
31+ [ HttpGet ]
32+ public async Task < ActionResult < IEnumerable < NotificationDto > > > GetNotificationsAsync (
33+ [ FromQuery ] bool includeRead = false )
34+ {
35+ try
36+ {
37+ var now = DateTimeOffset . UtcNow . ToUnixTimeSeconds ( ) ;
38+
39+ var query = _context . Notifications
40+ . Where ( n => n . ValidFromTs <= now && n . ValidUntilTs >= now ) ;
41+
42+ if ( ! includeRead )
43+ {
44+ query = query . Where ( n => ! n . IsRead ) ;
45+ }
46+
47+ var notifications = await query
48+ . OrderByDescending ( n => n . Priority )
49+ . ThenByDescending ( n => n . CreatedTs )
50+ . Select ( n => new NotificationDto (
51+ n . Id ,
52+ n . Type . ToString ( ) ,
53+ n . Priority . ToString ( ) ,
54+ n . Title ,
55+ n . Message ,
56+ n . ActionUrl ,
57+ n . ActionText ,
58+ DateTimeOffset . FromUnixTimeSeconds ( n . ValidFromTs ) . DateTime ,
59+ DateTimeOffset . FromUnixTimeSeconds ( n . ValidUntilTs ) . DateTime ,
60+ n . IsRead ,
61+ n . IsShown ,
62+ DateTimeOffset . FromUnixTimeSeconds ( n . CreatedTs ) . DateTime
63+ ) )
64+ . ToListAsync ( ) ;
65+
66+ _logger . LogDebug ( "Retrieved {Count} notifications (includeRead: {IncludeRead})" ,
67+ notifications . Count , includeRead ) ;
68+
69+ return Ok ( notifications ) ;
70+ }
71+ catch ( Exception ex )
72+ {
73+ _logger . LogError ( ex , "Error retrieving notifications" ) ;
74+ return StatusCode ( 500 , new { message = "Internal server error while retrieving notifications" } ) ;
75+ }
76+ }
77+
78+ /// <summary>
79+ /// Mark a notification as read.
80+ /// </summary>
81+ /// <param name="request">The notification ID to mark as read.</param>
82+ /// <returns>Success response.</returns>
83+ [ HttpPost ( "mark-read" ) ]
84+ public async Task < ActionResult > MarkNotificationReadAsync ( [ FromBody ] MarkNotificationReadRequest request )
85+ {
86+ try
87+ {
88+ var notification = await _context . Notifications
89+ . FirstOrDefaultAsync ( n => n . Id == request . NotificationId ) ;
90+
91+ if ( notification == null )
92+ {
93+ return NotFound ( new { message = "Notification not found" } ) ;
94+ }
95+
96+ if ( ! notification . IsRead )
97+ {
98+ notification . IsRead = true ;
99+ notification . ReadTs = DateTimeOffset . UtcNow . ToUnixTimeSeconds ( ) ;
100+
101+ await _context . SaveChangesAsync ( ) ;
102+
103+ _logger . LogDebug ( "Marked notification as read: {NotificationId}" , request . NotificationId ) ;
104+ }
105+
106+ return Ok ( new { message = "Notification marked as read" } ) ;
107+ }
108+ catch ( Exception ex )
109+ {
110+ _logger . LogError ( ex , "Error marking notification as read: {NotificationId}" , request . NotificationId ) ;
111+ return StatusCode ( 500 , new { message = "Internal server error while marking notification as read" } ) ;
112+ }
113+ }
114+
115+ /// <summary>
116+ /// Mark a notification as shown (for tracking purposes).
117+ /// </summary>
118+ /// <param name="notificationId">The notification ID to mark as shown.</param>
119+ /// <returns>Success response.</returns>
120+ [ HttpPost ( "{notificationId}/mark-shown" ) ]
121+ public async Task < ActionResult > MarkNotificationShownAsync ( string notificationId )
122+ {
123+ try
124+ {
125+ var notification = await _context . Notifications
126+ . FirstOrDefaultAsync ( n => n . Id == notificationId ) ;
127+
128+ if ( notification == null )
129+ {
130+ return NotFound ( new { message = "Notification not found" } ) ;
131+ }
132+
133+ if ( ! notification . IsShown )
134+ {
135+ notification . IsShown = true ;
136+ notification . ShownTs = DateTimeOffset . UtcNow . ToUnixTimeSeconds ( ) ;
137+
138+ await _context . SaveChangesAsync ( ) ;
139+
140+ _logger . LogDebug ( "Marked notification as shown: {NotificationId}" , notificationId ) ;
141+ }
142+
143+ return Ok ( new { message = "Notification marked as shown" } ) ;
144+ }
145+ catch ( Exception ex )
146+ {
147+ _logger . LogError ( ex , "Error marking notification as shown: {NotificationId}" , notificationId ) ;
148+ return StatusCode ( 500 , new { message = "Internal server error while marking notification as shown" } ) ;
149+ }
150+ }
151+
152+ /// <summary>
153+ /// Get notification statistics and settings.
154+ /// </summary>
155+ /// <returns>Notification statistics.</returns>
156+ [ HttpGet ( "stats" ) ]
157+ public async Task < ActionResult > GetNotificationStatsAsync ( )
158+ {
159+ try
160+ {
161+ var now = DateTimeOffset . UtcNow . ToUnixTimeSeconds ( ) ;
162+
163+ var activeCount = await _context . Notifications
164+ . CountAsync ( n => n . ValidFromTs <= now && n . ValidUntilTs >= now ) ;
165+
166+ var unreadCount = await _context . Notifications
167+ . CountAsync ( n => n . ValidFromTs <= now && n . ValidUntilTs >= now && ! n . IsRead ) ;
168+
169+ var lastFetch = await _context . NotificationFetches
170+ . OrderByDescending ( f => f . FetchTs )
171+ . FirstOrDefaultAsync ( ) ;
172+
173+ var stats = new
174+ {
175+ ActiveNotifications = activeCount ,
176+ UnreadNotifications = unreadCount ,
177+ LastFetch = lastFetch != null
178+ ? DateTimeOffset . FromUnixTimeSeconds ( lastFetch . FetchTs ) . DateTime
179+ : ( DateTime ? ) null ,
180+ LastFetchSuccess = lastFetch ? . Success ?? false ,
181+ LastFetchError = lastFetch ? . Error
182+ } ;
183+
184+ return Ok ( stats ) ;
185+ }
186+ catch ( Exception ex )
187+ {
188+ _logger . LogError ( ex , "Error retrieving notification stats" ) ;
189+ return StatusCode ( 500 , new { message = "Internal server error while retrieving notification stats" } ) ;
190+ }
191+ }
192+
193+ /// <summary>
194+ /// Force refresh notifications from remote server (admin only).
195+ /// </summary>
196+ /// <returns>Success response.</returns>
197+ [ HttpPost ( "refresh" ) ]
198+ [ Authorize ( Policy = "AdminOnly" ) ]
199+ public async Task < ActionResult > ForceRefreshNotificationsAsync ( )
200+ {
201+ try
202+ {
203+ _logger . LogInformation ( "Manual notification refresh requested by admin" ) ;
204+
205+ // Trigger the background service to fetch immediately
206+ await _notificationService . TriggerManualFetchAsync ( ) ;
207+
208+ return Ok ( new { message = "Notification refresh completed successfully" } ) ;
209+ }
210+ catch ( Exception ex )
211+ {
212+ _logger . LogError ( ex , "Error triggering notification refresh" ) ;
213+ return StatusCode ( 500 , new { message = "Internal server error while refreshing notifications" } ) ;
214+ }
215+ }
216+ }
0 commit comments