• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

karanshukla / openresto / 27471089835

13 Jun 2026 03:34PM UTC coverage: 83.466% (-5.1%) from 88.54%
27471089835

push

github

web-flow
Merge pull request #139 from karanshukla/dev-notifications

Push Notifications for Admins

3061 of 4109 branches covered (74.5%)

Branch coverage included in aggregate %.

101 of 497 new or added lines in 13 files covered. (20.32%)

6 existing lines in 5 files now uncovered.

7712 of 8798 relevant lines covered (87.66%)

62.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/OpenRestoApi/Controllers/NotificationsController.cs
1
using Microsoft.AspNetCore.Authorization;
2
using Microsoft.AspNetCore.Mvc;
3
using OpenRestoApi.Core.Application.DTOs;
4
using OpenRestoApi.Core.Application.Interfaces;
5

6
namespace OpenRestoApi.Controllers;
7

8
[ApiController]
9
[Route("api/admin")]
10
[Authorize]
11
public class NotificationsController(INotificationService notificationService) : ControllerBase
×
12
{
13
    private readonly INotificationService _notifications = notificationService;
×
14

15
    /// <summary>
16
    /// List notification history with optional filters, newest first.
17
    /// GET /api/admin/notifications?restaurantId=1&amp;type=BookingCreated&amp;unreadOnly=true&amp;page=1&amp;pageSize=20
18
    /// restaurantId is optional — omit to see all restaurants.
19
    /// type values: BookingCreated | BookingCancelled | RestaurantNearlyFull
20
    /// </summary>
21
    [HttpGet("notifications")]
22
    public async Task<IActionResult> GetNotifications(
23
        [FromQuery] int? restaurantId,
24
        [FromQuery] string? type,
25
        [FromQuery] bool? unreadOnly,
26
        [FromQuery] int page = 1,
27
        [FromQuery] int pageSize = 20)
28
    {
NEW
29
        pageSize = Math.Clamp(pageSize, 1, 100);
×
NEW
30
        page = Math.Max(1, page);
×
31

NEW
32
        (List<AdminNotificationDto> items, int total) = await _notifications.GetNotificationsAsync(
×
NEW
33
            restaurantId, type, unreadOnly, page, pageSize);
×
34

35
        return Ok(new { items, total, page, pageSize });
×
36
    }
×
37

38
    /// <summary>
39
    /// Unread count badge for a specific restaurant.
40
    /// GET /api/admin/notifications/unread-count?restaurantId=1
41
    /// </summary>
42
    [HttpGet("notifications/unread-count")]
43
    public async Task<IActionResult> GetUnreadCount([FromQuery] int restaurantId)
44
    {
45
        if (restaurantId <= 0)
×
46
            return BadRequest(new { error = "restaurantId is required." });
×
47

48
        int count = await _notifications.GetUnreadCountAsync(restaurantId);
×
49
        return Ok(new { count });
×
50
    }
×
51

52
    /// <summary>
53
    /// Mark a single notification as read.
54
    /// PATCH /api/admin/notifications/{id}/read
55
    /// </summary>
56
    [HttpPatch("notifications/{id:int}/read")]
57
    public async Task<IActionResult> MarkRead(int id)
58
    {
59
        await _notifications.MarkReadAsync(id);
×
60
        return NoContent();
×
61
    }
×
62

63
    /// <summary>
64
    /// Mark all notifications for a restaurant as read.
65
    /// PATCH /api/admin/notifications/read-all?restaurantId=1
66
    /// </summary>
67
    [HttpPatch("notifications/read-all")]
68
    public async Task<IActionResult> MarkAllRead([FromQuery] int restaurantId)
69
    {
70
        if (restaurantId <= 0)
×
71
            return BadRequest(new { error = "restaurantId is required." });
×
72

73
        await _notifications.MarkAllReadAsync(restaurantId);
×
74
        return NoContent();
×
75
    }
×
76

77
    /// <summary>
78
    /// Returns the VAPID public key for the frontend to use when subscribing.
79
    /// GET /api/admin/push/vapid-public-key
80
    /// Returns 204 if VAPID is not configured (push disabled).
81
    /// </summary>
82
    [HttpGet("push/vapid-public-key")]
83
    public IActionResult GetVapidPublicKey()
84
    {
NEW
85
        string? key = _notifications.GetVapidPublicKey();
×
NEW
86
        if (key is null) return NoContent();
×
NEW
87
        return Ok(new { publicKey = key });
×
88
    }
89

90
    /// <summary>
91
    /// Register a browser push subscription.
92
    /// POST /api/admin/push/subscribe?restaurantId=1
93
    /// Body: { endpoint, p256dh, auth, userAgent? }
94
    /// </summary>
95
    [HttpPost("push/subscribe")]
96
    public async Task<IActionResult> Subscribe(
97
        [FromQuery] int restaurantId,
98
        [FromBody] PushSubscribeRequest request)
99
    {
100
        if (restaurantId <= 0)
×
101
            return BadRequest(new { error = "restaurantId is required." });
×
102

103
        await _notifications.SubscribeAsync(restaurantId, request);
×
104
        return Ok();
×
105
    }
×
106

107
    /// <summary>
108
    /// Remove a push subscription (logout / permission revoked).
109
    /// DELETE /api/admin/push/subscribe
110
    /// Body: "https://fcm.googleapis.com/..."
111
    /// </summary>
112
    [HttpDelete("push/subscribe")]
113
    public async Task<IActionResult> Unsubscribe([FromBody] string endpoint)
114
    {
115
        await _notifications.UnsubscribeAsync(endpoint);
×
116
        return NoContent();
×
117
    }
×
118
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc