Skip to content

Commit e79bab3

Browse files
feat: Add search and and group_size filter
1 parent d570116 commit e79bab3

File tree

6 files changed

+223
-36
lines changed

6 files changed

+223
-36
lines changed

backend/docs/swagger.yaml

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ definitions:
3535
last_name:
3636
example: Doe
3737
type: string
38+
phone_number:
39+
example: "+11234567890"
40+
type: string
41+
primary_email:
42+
example: john@example.com
43+
type: string
3844
profile_picture:
3945
example: https://example.com/john.jpg
4046
type: string
@@ -129,12 +135,18 @@ definitions:
129135
items:
130136
type: integer
131137
type: array
138+
group_size:
139+
items:
140+
type: integer
141+
type: array
132142
hotel_id:
133143
type: string
134144
limit:
135145
maximum: 100
136146
minimum: 1
137147
type: integer
148+
search:
149+
type: string
138150
required:
139151
- hotel_id
140152
type: object
@@ -153,18 +165,16 @@ definitions:
153165
type: string
154166
floor:
155167
type: integer
168+
group_size:
169+
type: integer
156170
id:
157171
type: string
158172
last_name:
159173
type: string
174+
preferred_name:
175+
type: string
160176
room_number:
161177
type: integer
162-
required:
163-
- first_name
164-
- floor
165-
- id
166-
- last_name
167-
- room_number
168178
type: object
169179
GuestWithStays:
170180
properties:
@@ -396,6 +406,12 @@ definitions:
396406
example: America/New_York
397407
type: string
398408
type: object
409+
UpdateUser:
410+
properties:
411+
phone_number:
412+
example: "+11234567890"
413+
type: string
414+
type: object
399415
User:
400416
properties:
401417
created_at:
@@ -419,6 +435,12 @@ definitions:
419435
last_name:
420436
example: Doe
421437
type: string
438+
phone_number:
439+
example: "+11234567890"
440+
type: string
441+
primary_email:
442+
example: john@example.com
443+
type: string
422444
profile_picture:
423445
example: https://example.com/john.jpg
424446
type: string
@@ -1069,6 +1091,52 @@ paths:
10691091
summary: Get user by ID
10701092
tags:
10711093
- users
1094+
put:
1095+
consumes:
1096+
- application/json
1097+
description: Updates fields on a user
1098+
parameters:
1099+
- description: User ID
1100+
in: path
1101+
name: id
1102+
required: true
1103+
type: string
1104+
- description: User update data
1105+
in: body
1106+
name: request
1107+
required: true
1108+
schema:
1109+
$ref: '#/definitions/UpdateUser'
1110+
produces:
1111+
- application/json
1112+
responses:
1113+
"200":
1114+
description: OK
1115+
schema:
1116+
$ref: '#/definitions/User'
1117+
"400":
1118+
description: Bad Request
1119+
schema:
1120+
additionalProperties:
1121+
type: string
1122+
type: object
1123+
"404":
1124+
description: Not Found
1125+
schema:
1126+
additionalProperties:
1127+
type: string
1128+
type: object
1129+
"500":
1130+
description: Internal Server Error
1131+
schema:
1132+
additionalProperties:
1133+
type: string
1134+
type: object
1135+
security:
1136+
- BearerAuth: []
1137+
summary: Updates a user
1138+
tags:
1139+
- users
10721140
schemes:
10731141
- http
10741142
- https

backend/internal/errs/repository.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ import "errors"
66
var (
77
ErrNotFoundInDB = errors.New("not found in DB")
88
ErrAlreadyExistsInDB = errors.New("already exists in DB")
9+
ErrInvalidCursor = errors.New("invalid cursor")
910
)

backend/internal/handler/guests.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ func (h *GuestsHandler) GetGuests(c *fiber.Ctx) error {
178178

179179
guests, err := h.GuestsRepository.FindGuestsWithActiveBooking(c.Context(), &filters)
180180
if err != nil {
181+
if errors.Is(err, errs.ErrInvalidCursor) {
182+
return errs.BadRequest("invalid cursor")
183+
}
181184
return errs.InternalServerError()
182185
}
183186

backend/internal/handler/guests_test.go

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -643,8 +643,8 @@ func TestGuestsHandler_GetGuests(t *testing.T) {
643643
t.Run("returns 200 with cursor and limit", func(t *testing.T) {
644644
t.Parallel()
645645

646-
cursor := "530e8400-e458-41d4-a716-446655440000"
647-
nextCursor := "530e8400-e458-41d4-a716-446655440001"
646+
cursor := "John Doe|530e8400-e458-41d4-a716-446655440000"
647+
nextCursor := "Jane Smith|530e8400-e458-41d4-a716-446655440001"
648648

649649
mock := &mockGuestsRepository{
650650
findGuestsFunc: func(ctx context.Context, f *models.GuestFilters) (*models.GuestPage, error) {
@@ -710,7 +710,11 @@ func TestGuestsHandler_GetGuests(t *testing.T) {
710710
t.Run("returns 400 on invalid cursor", func(t *testing.T) {
711711
t.Parallel()
712712

713-
mock := &mockGuestsRepository{}
713+
mock := &mockGuestsRepository{
714+
findGuestsFunc: func(ctx context.Context, f *models.GuestFilters) (*models.GuestPage, error) {
715+
return nil, errs.ErrInvalidCursor
716+
},
717+
}
714718
app := fiber.New(fiber.Config{ErrorHandler: errs.ErrorHandler})
715719
h := NewGuestsHandler(mock)
716720
app.Post("/guests/search", h.GetGuests)
@@ -724,6 +728,73 @@ func TestGuestsHandler_GetGuests(t *testing.T) {
724728
assert.Equal(t, 400, resp.StatusCode)
725729
})
726730

731+
t.Run("returns 400 on cursor with pipe but invalid UUID", func(t *testing.T) {
732+
t.Parallel()
733+
734+
mock := &mockGuestsRepository{
735+
findGuestsFunc: func(ctx context.Context, f *models.GuestFilters) (*models.GuestPage, error) {
736+
return nil, errs.ErrInvalidCursor
737+
},
738+
}
739+
app := fiber.New(fiber.Config{ErrorHandler: errs.ErrorHandler})
740+
h := NewGuestsHandler(mock)
741+
app.Post("/guests/search", h.GetGuests)
742+
743+
req := httptest.NewRequest("POST", "/guests/search", bytes.NewBufferString(`{"cursor":"John Doe|not-a-uuid"}`))
744+
req.Header.Set("Content-Type", "application/json")
745+
req.Header.Set("X-Hotel-ID", validHotelID)
746+
747+
resp, err := app.Test(req)
748+
require.NoError(t, err)
749+
assert.Equal(t, 400, resp.StatusCode)
750+
})
751+
752+
t.Run("passes search filter to repository", func(t *testing.T) {
753+
t.Parallel()
754+
755+
mock := &mockGuestsRepository{
756+
findGuestsFunc: func(ctx context.Context, f *models.GuestFilters) (*models.GuestPage, error) {
757+
assert.Equal(t, "john", f.Search)
758+
return &models.GuestPage{Data: []*models.GuestWithBooking{}, NextCursor: nil}, nil
759+
},
760+
}
761+
762+
app := fiber.New()
763+
h := NewGuestsHandler(mock)
764+
app.Post("/guests/search", h.GetGuests)
765+
766+
req := httptest.NewRequest("POST", "/guests/search", bytes.NewBufferString(`{"search":"john"}`))
767+
req.Header.Set("Content-Type", "application/json")
768+
req.Header.Set("X-Hotel-ID", validHotelID)
769+
770+
resp, err := app.Test(req)
771+
require.NoError(t, err)
772+
assert.Equal(t, 200, resp.StatusCode)
773+
})
774+
775+
t.Run("passes group_size filter to repository", func(t *testing.T) {
776+
t.Parallel()
777+
778+
mock := &mockGuestsRepository{
779+
findGuestsFunc: func(ctx context.Context, f *models.GuestFilters) (*models.GuestPage, error) {
780+
assert.Equal(t, []int{2, 3}, f.GroupSize)
781+
return &models.GuestPage{Data: []*models.GuestWithBooking{}, NextCursor: nil}, nil
782+
},
783+
}
784+
785+
app := fiber.New()
786+
h := NewGuestsHandler(mock)
787+
app.Post("/guests/search", h.GetGuests)
788+
789+
req := httptest.NewRequest("POST", "/guests/search", bytes.NewBufferString(`{"group_size":[2,3]}`))
790+
req.Header.Set("Content-Type", "application/json")
791+
req.Header.Set("X-Hotel-ID", validHotelID)
792+
793+
resp, err := app.Test(req)
794+
require.NoError(t, err)
795+
assert.Equal(t, 200, resp.StatusCode)
796+
})
797+
727798
t.Run("returns 400 on invalid JSON", func(t *testing.T) {
728799
t.Parallel()
729800

backend/internal/models/guests.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ type Guest struct {
2424
} //@name Guest
2525

2626
type GuestFilters struct {
27-
HotelID string `json:"hotel_id" validate:"required,uuid"`
28-
Floors []int `json:"floors"`
29-
Cursor string `json:"cursor" validate:"omitempty,uuid"`
30-
Limit int `json:"limit" validate:"omitempty,min=1,max=100"`
27+
HotelID string `json:"hotel_id" validate:"required,uuid"`
28+
Floors []int `json:"floors"`
29+
GroupSize []int `json:"group_size"`
30+
Search string `json:"search"`
31+
Cursor string `json:"cursor"`
32+
Limit int `json:"limit" validate:"omitempty,min=1,max=100"`
3133
} // @name GuestFilters
3234

3335
type GuestPage struct {
@@ -36,11 +38,13 @@ type GuestPage struct {
3638
} // @name GuestPage
3739

3840
type GuestWithBooking struct {
39-
ID string `json:"id" validate:"required"`
40-
FirstName string `json:"first_name" validate:"required"`
41-
LastName string `json:"last_name" validate:"required"`
42-
Floor int `json:"floor" validate:"required"`
43-
RoomNumber int `json:"room_number" validate:"required"`
41+
ID string `json:"id"`
42+
FirstName string `json:"first_name"`
43+
LastName string `json:"last_name"`
44+
PreferredName string `json:"preferred_name"`
45+
Floor int `json:"floor"`
46+
RoomNumber int `json:"room_number"`
47+
GroupSize *int `json:"group_size"`
4448
} // @name GuestWithBooking
4549

4650
type GuestWithStays struct {

0 commit comments

Comments
 (0)