33using Accanto . Application . Auth ;
44using Accanto . Application . Auth . TwoFactor ;
55using Accanto . Application . Notifications ;
6+ using Accanto . Application . Push ;
67using Accanto . Application . Security ;
78using Accanto . Application . Wellbeing ;
89using Accanto . Domain . Enums ;
@@ -25,6 +26,8 @@ public class AccountController : ControllerBase
2526 private readonly ISecurityAuditLog _audit ;
2627 private readonly ISecurityAuditQueryService _auditQuery ;
2728 private readonly ICheckInService _checkIns ;
29+ private readonly IDevicePushTokenService _devicePushTokens ;
30+ private readonly ICircleMobilePushNotifier _mobilePush ;
2831 private readonly ICurrentUser _currentUser ;
2932
3033 public AccountController (
@@ -36,6 +39,8 @@ public AccountController(
3639 ISecurityAuditLog audit ,
3740 ISecurityAuditQueryService auditQuery ,
3841 ICheckInService checkIns ,
42+ IDevicePushTokenService devicePushTokens ,
43+ ICircleMobilePushNotifier mobilePush ,
3944 ICurrentUser currentUser )
4045 {
4146 _svc = svc ;
@@ -46,9 +51,10 @@ public AccountController(
4651 _audit = audit ;
4752 _auditQuery = auditQuery ;
4853 _checkIns = checkIns ;
54+ _devicePushTokens = devicePushTokens ;
55+ _mobilePush = mobilePush ;
4956 _currentUser = currentUser ;
5057 }
51-
5258 [ HttpPost ( "change-password" ) ]
5359 [ EnableRateLimiting ( "auth-sensitive" ) ]
5460 public async Task < IActionResult > ChangePassword ( [ FromBody ] ChangePasswordRequest request , CancellationToken ct )
@@ -174,6 +180,57 @@ public async Task<IActionResult> DeleteCheckIn(Guid id, CancellationToken ct)
174180 return NoContent ( ) ;
175181 }
176182
183+ // ---------- Mobile push devices (Expo) ----------
184+
185+ [ HttpGet ( "push-devices" ) ]
186+ public async Task < ActionResult < IReadOnlyList < DevicePushTokenDto > > > ListPushDevices ( CancellationToken ct )
187+ => Ok ( await _devicePushTokens . ListAsync ( _currentUser . RequireUserId ( ) , ct ) ) ;
188+
189+ [ HttpPost ( "push-devices" ) ]
190+ public async Task < ActionResult < DevicePushTokenDto > > RegisterPushDevice ( [ FromBody ] RegisterDevicePushTokenRequest request , CancellationToken ct )
191+ {
192+ var dto = await _devicePushTokens . RegisterAsync ( _currentUser . RequireUserId ( ) , request , ct ) ;
193+ return Ok ( dto ) ;
194+ }
195+
196+ [ HttpDelete ( "push-devices/{id:guid}" ) ]
197+ public async Task < IActionResult > DeletePushDeviceById ( Guid id , CancellationToken ct )
198+ {
199+ await _devicePushTokens . RemoveByIdAsync ( _currentUser . RequireUserId ( ) , id , ct ) ;
200+ return NoContent ( ) ;
201+ }
202+
203+ /// <summary>
204+ /// Cancellazione "by token" usata dal client mobile in fase di
205+ /// logout: il device conosce il proprio token Expo ma non il GUID
206+ /// del record DB.
207+ /// </summary>
208+ [ HttpDelete ( "push-devices" ) ]
209+ public async Task < IActionResult > DeletePushDeviceByToken ( [ FromBody ] DeletePushDeviceRequest request , CancellationToken ct )
210+ {
211+ if ( request is null || string . IsNullOrWhiteSpace ( request . Token ) ) return BadRequest ( ) ;
212+ await _devicePushTokens . RemoveByTokenAsync ( _currentUser . RequireUserId ( ) , request . Token , ct ) ;
213+ return NoContent ( ) ;
214+ }
215+
216+ /// <summary>
217+ /// Invia una notifica push di prova a tutti i device dell'utente
218+ /// corrente. Bypassa le preferenze (testare la connessione non deve
219+ /// essere silenziato dai topic flags). Utile per validare end-to-end
220+ /// la pipeline (token registrato, Expo push service, APNs/FCM).
221+ /// </summary>
222+ [ HttpPost ( "push-devices/test" ) ]
223+ [ EnableRateLimiting ( "auth-sensitive" ) ]
224+ public async Task < IActionResult > SendTestPush ( CancellationToken ct )
225+ {
226+ await _mobilePush . SendTestAsync (
227+ _currentUser . RequireUserId ( ) ,
228+ "Accanto" ,
229+ "Notifica di prova: tutto funziona \ud83c \udf89 " ,
230+ ct ) ;
231+ return Accepted ( ) ;
232+ }
233+
177234 private ClientInfo BuildClientInfo ( )
178235 {
179236 var ua = Request . Headers . UserAgent . ToString ( ) ;
0 commit comments