Skip to content

Commit a433ede

Browse files
committed
refactor: change get to post for rooms filter
1 parent eaa77fe commit a433ede

File tree

6 files changed

+97
-56
lines changed

6 files changed

+97
-56
lines changed

backend/docs/swagger.yaml

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ definitions:
5757
example: Dao Ho
5858
type: string
5959
type: object
60+
FilterRoomsRequest:
61+
properties:
62+
cursor:
63+
type: string
64+
floors:
65+
items:
66+
type: integer
67+
type: array
68+
limit:
69+
type: integer
70+
type: object
6071
GenerateRequestInput:
6172
properties:
6273
hotel_id:
@@ -919,30 +930,22 @@ paths:
919930
tags:
920931
- requests
921932
/rooms:
922-
get:
923-
description: Retrieves rooms optionally filtered by floor, with any active guest
924-
bookings
933+
post:
934+
consumes:
935+
- application/json
936+
description: Retrieves rooms with optional floor filters and cursor pagination,
937+
including any active guest bookings
925938
parameters:
926939
- description: Hotel ID (UUID)
927940
in: header
928941
name: X-Hotel-ID
929942
required: true
930943
type: string
931-
- collectionFormat: csv
932-
description: floors
933-
in: query
934-
items:
935-
type: integer
936-
name: floors
937-
type: array
938-
- description: Opaque cursor for the next page
939-
in: query
940-
name: cursor
941-
type: string
942-
- description: Number of items per page (1-100, default 20)
943-
in: query
944-
name: limit
945-
type: integer
944+
- description: Filters and pagination
945+
in: body
946+
name: body
947+
schema:
948+
$ref: '#/definitions/FilterRoomsRequest'
946949
produces:
947950
- application/json
948951
responses:
@@ -964,7 +967,7 @@ paths:
964967
type: object
965968
security:
966969
- BearerAuth: []
967-
summary: Get Rooms By Floor
970+
summary: List rooms with filters
968971
tags:
969972
- rooms
970973
/rooms/floors:

backend/internal/handler/rooms.go

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,38 @@ func NewRoomsHandler(repo RoomsRepository) *RoomsHandler {
2323
return &RoomsHandler{repo: repo}
2424
}
2525

26-
// GetRoomsByFloor godoc
27-
// @Summary Get Rooms By Floor
28-
// @Description Retrieves rooms optionally filtered by floor, with any active guest bookings
26+
// FilterRooms godoc
27+
// @Summary List rooms with filters
28+
// @Description Retrieves rooms with optional floor filters and cursor pagination, including any active guest bookings
2929
// @Tags rooms
30+
// @Accept json
3031
// @Produce json
31-
// @Param X-Hotel-ID header string true "Hotel ID (UUID)"
32-
// @Param floors query []int false "floors"
33-
// @Param cursor query string false "Opaque cursor for the next page"
34-
// @Param limit query int false "Number of items per page (1-100, default 20)"
32+
// @Param X-Hotel-ID header string true "Hotel ID (UUID)"
33+
// @Param body body models.FilterRoomsRequest false "Filters and pagination"
3534
// @Success 200 {object} utils.CursorPage[models.RoomWithOptionalGuestBooking]
3635
// @Failure 400 {object} map[string]string
3736
// @Failure 500 {object} map[string]string
3837
// @Security BearerAuth
39-
// @Router /rooms [get]
40-
func (h *RoomsHandler) GetRoomsByFloor(c *fiber.Ctx) error {
38+
// @Router /rooms [post]
39+
func (h *RoomsHandler) FilterRooms(c *fiber.Ctx) error {
4140
hotelID, err := hotelIDFromHeader(c)
4241
if err != nil {
4342
return err
4443
}
4544

46-
filter := new(models.RoomFilters)
47-
if err := c.QueryParser(filter); err != nil {
48-
return errs.BadRequest("invalid filters")
45+
var body models.FilterRoomsRequest
46+
if err := c.BodyParser(&body); err != nil {
47+
return errs.InvalidJSON()
48+
}
49+
50+
filter := &models.RoomFilters{
51+
Floors: body.Floors,
52+
Limit: body.Limit,
4953
}
5054

51-
cursor := c.Query("cursor", "")
5255
cursorRoomNumber := 0
53-
if cursor != "" {
54-
cursorRoomNumber, err = strconv.Atoi(cursor)
56+
if body.Cursor != "" {
57+
cursorRoomNumber, err = strconv.Atoi(body.Cursor)
5558
if err != nil {
5659
return errs.BadRequest("invalid cursor")
5760
}

backend/internal/handler/rooms_test.go

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"io"
77
"net/http/httptest"
8+
"strings"
89
"testing"
910

1011
"github.com/generate/selfserve/internal/errs"
@@ -31,7 +32,7 @@ var _ RoomsRepository = (*mockRoomsRepository)(nil)
3132

3233
const testHotelID = "00000000-0000-0000-0000-000000000001"
3334

34-
func TestRoomsHandler_GetRoomsByFloor(t *testing.T) {
35+
func TestRoomsHandler_FilterRooms(t *testing.T) {
3536
t.Parallel()
3637

3738
t.Run("returns 200 with rooms and no guests when rooms are vacant", func(t *testing.T) {
@@ -50,9 +51,10 @@ func TestRoomsHandler_GetRoomsByFloor(t *testing.T) {
5051

5152
app := fiber.New()
5253
h := NewRoomsHandler(mock)
53-
app.Get("/rooms", h.GetRoomsByFloor)
54+
app.Post("/rooms", h.FilterRooms)
5455

55-
req := httptest.NewRequest("GET", "/rooms", nil)
56+
req := httptest.NewRequest("POST", "/rooms", strings.NewReader(`{}`))
57+
req.Header.Set("Content-Type", "application/json")
5658
req.Header.Set(hotelIDHeader, testHotelID)
5759
resp, err := app.Test(req)
5860
require.NoError(t, err)
@@ -94,9 +96,10 @@ func TestRoomsHandler_GetRoomsByFloor(t *testing.T) {
9496

9597
app := fiber.New()
9698
h := NewRoomsHandler(mock)
97-
app.Get("/rooms", h.GetRoomsByFloor)
99+
app.Post("/rooms", h.FilterRooms)
98100

99-
req := httptest.NewRequest("GET", "/rooms?floors=2", nil)
101+
req := httptest.NewRequest("POST", "/rooms", strings.NewReader(`{"floors":[2]}`))
102+
req.Header.Set("Content-Type", "application/json")
100103
req.Header.Set(hotelIDHeader, testHotelID)
101104
resp, err := app.Test(req)
102105
require.NoError(t, err)
@@ -122,9 +125,10 @@ func TestRoomsHandler_GetRoomsByFloor(t *testing.T) {
122125

123126
app := fiber.New()
124127
h := NewRoomsHandler(mock)
125-
app.Get("/rooms", h.GetRoomsByFloor)
128+
app.Post("/rooms", h.FilterRooms)
126129

127-
req := httptest.NewRequest("GET", "/rooms?floors=99", nil)
130+
req := httptest.NewRequest("POST", "/rooms", strings.NewReader(`{"floors":[99]}`))
131+
req.Header.Set("Content-Type", "application/json")
128132
req.Header.Set(hotelIDHeader, testHotelID)
129133
resp, err := app.Test(req)
130134
require.NoError(t, err)
@@ -154,9 +158,10 @@ func TestRoomsHandler_GetRoomsByFloor(t *testing.T) {
154158

155159
app := fiber.New()
156160
h := NewRoomsHandler(mock)
157-
app.Get("/rooms", h.GetRoomsByFloor)
161+
app.Post("/rooms", h.FilterRooms)
158162

159-
req := httptest.NewRequest("GET", "/rooms?limit=5", nil)
163+
req := httptest.NewRequest("POST", "/rooms", strings.NewReader(`{"limit":5}`))
164+
req.Header.Set("Content-Type", "application/json")
160165
req.Header.Set(hotelIDHeader, testHotelID)
161166
resp, err := app.Test(req)
162167
require.NoError(t, err)
@@ -171,8 +176,6 @@ func TestRoomsHandler_GetRoomsByFloor(t *testing.T) {
171176
t.Run("passes cursor, filter, and hotelID to repository", func(t *testing.T) {
172177
t.Parallel()
173178

174-
cursor := "200"
175-
176179
var capturedFilter *models.RoomFilters
177180
var capturedHotelID string
178181
var capturedCursor int
@@ -187,9 +190,10 @@ func TestRoomsHandler_GetRoomsByFloor(t *testing.T) {
187190

188191
app := fiber.New()
189192
h := NewRoomsHandler(mock)
190-
app.Get("/rooms", h.GetRoomsByFloor)
193+
app.Post("/rooms", h.FilterRooms)
191194

192-
req := httptest.NewRequest("GET", "/rooms?cursor="+cursor+"&limit=10", nil)
195+
req := httptest.NewRequest("POST", "/rooms", strings.NewReader(`{"cursor":"200","limit":10}`))
196+
req.Header.Set("Content-Type", "application/json")
193197
req.Header.Set(hotelIDHeader, testHotelID)
194198
resp, err := app.Test(req)
195199
require.NoError(t, err)
@@ -212,9 +216,10 @@ func TestRoomsHandler_GetRoomsByFloor(t *testing.T) {
212216

213217
app := fiber.New(fiber.Config{ErrorHandler: errs.ErrorHandler})
214218
h := NewRoomsHandler(mock)
215-
app.Get("/rooms", h.GetRoomsByFloor)
219+
app.Post("/rooms", h.FilterRooms)
216220

217-
req := httptest.NewRequest("GET", "/rooms", nil)
221+
req := httptest.NewRequest("POST", "/rooms", strings.NewReader(`{}`))
222+
req.Header.Set("Content-Type", "application/json")
218223
resp, err := app.Test(req)
219224
require.NoError(t, err)
220225

@@ -232,9 +237,10 @@ func TestRoomsHandler_GetRoomsByFloor(t *testing.T) {
232237

233238
app := fiber.New(fiber.Config{ErrorHandler: errs.ErrorHandler})
234239
h := NewRoomsHandler(mock)
235-
app.Get("/rooms", h.GetRoomsByFloor)
240+
app.Post("/rooms", h.FilterRooms)
236241

237-
req := httptest.NewRequest("GET", "/rooms", nil)
242+
req := httptest.NewRequest("POST", "/rooms", strings.NewReader(`{}`))
243+
req.Header.Set("Content-Type", "application/json")
238244
req.Header.Set(hotelIDHeader, "not-a-uuid")
239245
resp, err := app.Test(req)
240246
require.NoError(t, err)
@@ -255,15 +261,38 @@ func TestRoomsHandler_GetRoomsByFloor(t *testing.T) {
255261

256262
app := fiber.New(fiber.Config{ErrorHandler: errs.ErrorHandler})
257263
h := NewRoomsHandler(mock)
258-
app.Get("/rooms", h.GetRoomsByFloor)
264+
app.Post("/rooms", h.FilterRooms)
259265

260-
req := httptest.NewRequest("GET", "/rooms", nil)
266+
req := httptest.NewRequest("POST", "/rooms", strings.NewReader(`{}`))
267+
req.Header.Set("Content-Type", "application/json")
261268
req.Header.Set(hotelIDHeader, testHotelID)
262269
resp, err := app.Test(req)
263270
require.NoError(t, err)
264271

265272
assert.Equal(t, 500, resp.StatusCode)
266273
})
274+
275+
t.Run("returns 400 when request body is invalid json", func(t *testing.T) {
276+
t.Parallel()
277+
278+
mock := &mockRoomsRepository{
279+
findRoomsFunc: func(ctx context.Context, filter *models.RoomFilters, hotelID string, cursorRoomNumber int) ([]*models.RoomWithOptionalGuestBooking, error) {
280+
return nil, nil
281+
},
282+
}
283+
284+
app := fiber.New(fiber.Config{ErrorHandler: errs.ErrorHandler})
285+
h := NewRoomsHandler(mock)
286+
app.Post("/rooms", h.FilterRooms)
287+
288+
req := httptest.NewRequest("POST", "/rooms", strings.NewReader(`{`))
289+
req.Header.Set("Content-Type", "application/json")
290+
req.Header.Set(hotelIDHeader, testHotelID)
291+
resp, err := app.Test(req)
292+
require.NoError(t, err)
293+
294+
assert.Equal(t, 400, resp.StatusCode)
295+
})
267296
}
268297

269298
func TestRoomsHandler_GetFloors(t *testing.T) {

backend/internal/models/rooms.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ type RoomFilters struct {
1212
Limit int `query:"limit"`
1313
}
1414

15+
type FilterRoomsRequest struct {
16+
Floors *[]int `json:"floors,omitempty"`
17+
Limit int `json:"limit,omitempty"`
18+
Cursor string `json:"cursor,omitempty"`
19+
} //@name FilterRoomsRequest
20+
1521
// Read model for rooms page on the frontend
1622
type RoomWithOptionalGuestBooking struct {
1723
Room

backend/internal/service/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func setupRoutes(app *fiber.App, repo *storage.Repository, genkitInstance *aiflo
175175

176176
// rooms routes
177177
api.Route("/rooms", func(r fiber.Router) {
178-
r.Get("/", roomsHandler.GetRoomsByFloor)
178+
r.Post("/", roomsHandler.FilterRooms)
179179
r.Get("/floors", roomsHandler.GetFloors)
180180
})
181181

backend/scripts/seed.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ echo ""
4343
echo " export HOTEL_ID='a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'"
4444
echo ""
4545
echo " # All rooms:"
46-
echo " curl -s -H \"X-Hotel-ID: \$HOTEL_ID\" 'http://localhost:${APP_PORT}/api/v1/rooms' | jq"
46+
echo " curl -s -X POST -H \"Content-Type: application/json\" -H \"X-Hotel-ID: \$HOTEL_ID\" -d '{}' 'http://localhost:${APP_PORT}/api/v1/rooms' | jq"
4747
echo ""
4848
echo " # Floor 1 only:"
49-
echo " curl -s -H \"X-Hotel-ID: \$HOTEL_ID\" 'http://localhost:${APP_PORT}/api/v1/rooms?floors=1' | jq"
49+
echo " curl -s -X POST -H \"Content-Type: application/json\" -H \"X-Hotel-ID: \$HOTEL_ID\" -d '{\"floors\":[1]}' 'http://localhost:${APP_PORT}/api/v1/rooms' | jq"
5050
echo ""
5151
echo " # All guests:"
5252
echo " curl -s -H \"X-Hotel-ID: \$HOTEL_ID\" 'http://localhost:${APP_PORT}/api/v1/guests' | jq"

0 commit comments

Comments
 (0)