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

karanshukla / openresto / 28881805559

07 Jul 2026 04:23PM UTC coverage: 94.253% (+0.07%) from 94.185%
28881805559

Pull #209

github

web-flow
Merge 55996179b into 108a871bd
Pull Request #209: Massive Refactor

4316 of 4939 branches covered (87.39%)

Branch coverage included in aggregate %.

1466 of 1601 new or added lines in 120 files covered. (91.57%)

65 existing lines in 27 files now uncovered.

13954 of 14445 relevant lines covered (96.6%)

83.46 hits per line

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

88.6
/OpenRestoApi/Controllers/AdminController.cs
1
using Microsoft.AspNetCore.Authorization;
2
using Microsoft.AspNetCore.Mvc;
3
using OpenRestoApi.Core.Application.DTOs;
4
using OpenRestoApi.Core.Application.Services;
5

6
namespace OpenRestoApi.Controllers;
7

8
[ApiController]
9
[Route("api/[controller]")]
10
[Authorize(Roles = "Admin")]
11
public class AdminController(AdminService adminService) : ControllerBase
252✔
12
{
13
    public enum bookingStatus { active, cancelled, all, past, upcoming }
14
    private readonly AdminService _adminService = adminService;
252✔
15

16
    [HttpGet("overview")]
17
    public async Task<IActionResult> Overview()
18
        => Ok(await _adminService.GetOverviewAsync());
4✔
19

20
    [HttpGet("bookings")]
21
    public async Task<IActionResult> GetBookings(
22
        [FromQuery] int? restaurantId,
23
        [FromQuery] DateTime? date,
24
        [FromQuery] bookingStatus status = bookingStatus.active,
25
        [FromQuery] bool cancelled = false,
26
        [FromQuery] string? email = null,
27
        [FromQuery] string? bookingRef = null)
28
    {
29
        //this is business logic that should likely belong in the service but its OK for now
30
        string effectiveStatus = cancelled ? nameof(bookingStatus.cancelled) : status.ToString();
10✔
31
        return Ok(await _adminService.GetBookingsAsync(restaurantId, date, effectiveStatus, email, bookingRef));
10✔
32
    }
10✔
33

34
    [HttpGet("bookings/{id}")]
35
    public async Task<IActionResult> GetBooking(int id)
36
    {
37
        BookingDetailDto? result = await _adminService.GetBookingAsync(id);
4✔
38
        return result == null ? NotFound() : Ok(result);
4✔
39
    }
4✔
40

41
    [HttpPost("bookings")]
42
    public async Task<IActionResult> CreateBooking([FromBody] AdminCreateBookingRequest req)
43
    {
44
        // ValidationException (bad table/section) → 400, ConflictException (overlap/seats) → 409
45
        // are mapped by GlobalExceptionHandler; the controller just orchestrates.
46
        BookingDetailDto result = await _adminService.CreateBookingAsync(req);
48✔
47
        return CreatedAtAction(nameof(GetBooking), new { id = result.Id }, result);
40✔
48
    }
40✔
49

50
    [HttpPost("bookings/{id}/extend")]
51
    public async Task<IActionResult> ExtendBooking(int id, [FromBody] ExtendBookingRequest req)
52
    {
53
        DateTime? endTime = await _adminService.ExtendBookingAsync(id, req.Minutes);
4✔
54
        return endTime == null ? NotFound() : Ok(new { endTime });
4✔
55
    }
4✔
56

57
    [HttpPost("bookings/{id}/cancel")]
58
    public async Task<IActionResult> CancelBooking(int id)
59
    {
60
        // ConflictException (past booking) → 409 is mapped by GlobalExceptionHandler.
61
        return await _adminService.CancelBookingAsync(id) ? NoContent() : NotFound();
10!
62
    }
6✔
63

64
    [HttpDelete("bookings/{id}")]
65
    public async Task<IActionResult> PurgeBooking(int id)
66
        => await _adminService.PurgeBookingAsync(id) ? NoContent() : NotFound();
4✔
67

68
    [HttpPost("restaurants")]
69
    public async Task<IActionResult> CreateRestaurant([FromBody] CreateRestaurantRequest req)
70
    {
71
        if (string.IsNullOrWhiteSpace(req.Name))
22✔
72
        {
73
            return BadRequest(new MessageResponse { Message = "Name is required." });
4✔
74
        }
75

76
        RestaurantDto result = await _adminService.CreateRestaurantAsync(req.Name, req.Address);
18✔
77
        return CreatedAtAction(nameof(Overview), new { }, result);
18✔
78
    }
22✔
79

80
    [HttpPatch("restaurants/{id}")]
81
    public async Task<IActionResult> PatchRestaurant(int id, [FromBody] AdminRestaurantPatchRequest req)
82
    {
UNCOV
83
        if (req.IsArchived.HasValue)
×
84
        {
UNCOV
85
            bool success = await _adminService.SetArchivedAsync(id, req.IsArchived.Value);
×
UNCOV
86
            if (!success) return NotFound();
×
87
        }
UNCOV
88
        return NoContent();
×
UNCOV
89
    }
×
90

91
    [HttpDelete("restaurants/{id}")]
92
    public async Task<IActionResult> DeleteRestaurant(int id)
93
        => await _adminService.DeleteRestaurantAsync(id) ? NoContent() : NotFound();
4✔
94

95
    [HttpPost("restaurants/{id}/pause")]
96
    public async Task<IActionResult> PauseBookings(int id, [FromBody] PauseRestaurantRequest req)
97
    {
98
        bool success = await _adminService.PauseRestaurantBookingsAsync(id, req.Minutes);
8✔
99
        return success ? Ok(new MessageResponse { Message = "Bookings paused successfully." }) : NotFound();
8!
100
    }
8✔
101

102
    [HttpPost("restaurants/{id}/unpause")]
103
    public async Task<IActionResult> UnpauseBookings(int id)
104
    {
105
        bool success = await _adminService.UnpauseRestaurantBookingsAsync(id);
2✔
106
        return success ? Ok(new MessageResponse { Message = "Bookings unpaused successfully." }) : NotFound();
2!
107
    }
2✔
108

109
    [HttpPost("restaurants/{id}/extend")]
110
    public async Task<IActionResult> ExtendBookings(int id, [FromBody] ExtendRestaurantRequest req)
111
    {
112
        List<BookingDetailDto>? extendedBookings = await _adminService.ExtendAllActiveBookingsAsync(id, req.Minutes);
4✔
113
        return extendedBookings != null
4!
114
            ? Ok(new { Message = "Bookings extended successfully.", ExtendedBookings = extendedBookings })
4✔
115
            : NotFound();
4✔
116
    }
4✔
117

118
    [HttpGet("restaurants")]
119
    public async Task<IActionResult> GetRestaurants()
120
    {
121
        List<LookupDto> restaurants = await _adminService.GetRestaurantsAsync();
4✔
122
        return Ok(restaurants);
4✔
123
    }
4✔
124

125
    [HttpGet("restaurants/{restaurantId}/sections")]
126
    public async Task<IActionResult> GetSections(int restaurantId)
127
    {
128
        List<LookupDto> sections = await _adminService.GetSectionsAsync(restaurantId);
16✔
129
        return Ok(sections);
16✔
130
    }
16✔
131

132
    [HttpPatch("restaurants/{id}/sections/reorder")]
133
    public async Task<IActionResult> ReorderSections(int id, [FromBody] ReorderSectionsRequest req)
134
    {
135
        bool? result = await _adminService.ReorderSectionsAsync(id, req.SectionIds);
28✔
136
        return result switch
28✔
137
        {
28✔
138
            null => NotFound(),
4✔
139
            false => BadRequest(new MessageResponse { Message = "sectionIds must include exactly the restaurant's current sections, with no duplicates." }),
4✔
140
            true => NoContent(),
20✔
141
        };
28✔
142
    }
28✔
143

144
    [HttpGet("restaurants/{restaurantId}/tables")]
145
    public async Task<IActionResult> GetTables(int restaurantId)
146
    {
147
        List<SectionDto>? result = await _adminService.GetTablesAsync(restaurantId);
6✔
148
        return result == null
6✔
149
            ? NotFound(new MessageResponse { Message = "Restaurant not found or has no sections." })
6✔
150
            : Ok(result);
6✔
151
    }
6✔
152

153
    [HttpPost("bookings/{id}/email")]
154
    public async Task<IActionResult> SendEmail(int id, [FromBody] SendBookingEmailRequest req)
155
    {
156
        // Intentionally keeps its catch: SMTP/transport failures (SmtpException,
157
        // InfrastructureException, etc.) surface here from the email stack and are
158
        // wrapped as a user-facing 400 "Failed to send: ..." rather than a 500 —
159
        // this is a deliberate UX choice, not something GlobalExceptionHandler should
160
        // own (those failures are not domain exceptions).
161
        try
162
        {
163
            SendBookingEmailResult result = await _adminService.SendBookingEmailAsync(id, req);
36✔
164
            return result.Status switch
32✔
165
            {
32✔
166
                SendBookingEmailStatus.NotFound => NotFound(),
6✔
167
                SendBookingEmailStatus.MissingFields => BadRequest(new MessageResponse { Message = "Subject and body are required." }),
10✔
168
                SendBookingEmailStatus.NoCustomerEmail => BadRequest(new MessageResponse { Message = "Customer email is not available." }),
8✔
169
                _ => Ok(new MessageResponse { Message = $"Email sent to {result.Recipient}." })
8✔
170
            };
32✔
171
        }
172
        catch (Exception ex)
4✔
173
        {
174
            return BadRequest(new MessageResponse { Message = $"Failed to send: {ex.Message}" });
4✔
175
        }
176
    }
36✔
177

178
    [HttpPost("bookings/{id}/restore")]
179
    public async Task<IActionResult> RestoreBooking(int id)
180
    {
181
        // BusinessRuleException (booking already active) → 400 is mapped by GlobalExceptionHandler.
182
        BookingDetailDto? result = await _adminService.RestoreBookingAsync(id);
20✔
183
        return result == null ? NotFound() : Ok(new MessageResponse { Message = "Booking restored successfully." });
12✔
184
    }
12✔
185

186
    [HttpPut("bookings/{id}")]
187
    public async Task<IActionResult> AdminUpdateBooking(int id, [FromBody] AdminUpdateBookingRequest req)
188
    {
189
        // ValidationException (bad restaurant/table) and BusinessRuleException
190
        // (update-conflict / seats) → 400 are mapped by GlobalExceptionHandler.
191
        BookingDetailDto? result = await _adminService.AdminUpdateBookingAsync(id, req);
22✔
192
        return result == null ? NotFound() : Ok(result);
12✔
193
    }
12✔
194
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc