Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create get rooms endpoint #11

Merged
merged 14 commits into from
Feb 1, 2024
76 changes: 76 additions & 0 deletions internal/app/application/services/room.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,79 @@ func (s *RoomService) Create(name string, description *string, userID string) (*

return room, nil
}

func (s *RoomService) GetAll(
search string,
userID string,
pageFilter PageFilter,
) ([]*entities.Room, error) {
queryFilter := s.makeGetAllQueryFilter(search, userID)

rooms, err := s.roomRepository.GetByQueryFilters(*queryFilter, &repositories.PageFilter{
Offset: (pageFilter.Page - 1) * pageFilter.Size,
Limit: pageFilter.Size,
})
if err != nil {
return nil, err
}

return rooms, nil
}

func (s *RoomService) CountAll(
search string,
userID string,
) (int64, error) {
queryFilter := s.makeGetAllQueryFilter(search, userID)

count, err := s.roomRepository.CountByQueryFilters(*queryFilter)
if err != nil {
return 0, err
}

return count, nil
}

func (s *RoomService) makeGetAllQueryFilter(
search string,
userID string,
) *repositories.QueryFilter {
queryFilter := &repositories.QueryFilter{
ConditionGroups: []repositories.ConditionGroup{
{
Operator: repositories.AndLogicalOperator,
Conditions: []repositories.Condition{
{
Field: entities.RoomUserIDField,
Operator: repositories.EqualComparisonOperator,
Value: userID,
},
},
},
},
}

if search != "" {
searchConditionGroup := repositories.ConditionGroup{
Operator: repositories.OrLogicalOperator,
Conditions: []repositories.Condition{
{
Field: entities.RoomNameField,
Operator: repositories.LikeComparisonOperator,
Value: "%" + search + "%",
},
{
Field: entities.RoomDescriptionField,
Operator: repositories.LikeComparisonOperator,
Value: "%" + search + "%",
},
},
}
queryFilter.ConditionGroups = append(
queryFilter.ConditionGroups,
searchConditionGroup,
)
}

return queryFilter
}
92 changes: 92 additions & 0 deletions internal/app/application/services/room_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package services
import (
"errors"
"github.com/google/uuid"
"github.com/jibaru/home-inventory-api/m/internal/app/domain/entities"
"github.com/jibaru/home-inventory-api/m/internal/app/infrastructure/repositories/stub"
"github.com/labstack/gommon/random"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -48,3 +49,94 @@ func TestRoomServiceCreateRoomErrorInRepository(t *testing.T) {
assert.EqualError(t, err, mockError.Error())
roomRepository.AssertExpectations(t)
}

func TestRoomServiceGetAll(t *testing.T) {
roomRepository := new(stub.RoomRepositoryMock)
roomService := NewRoomService(roomRepository)

search := random.String(100, random.Alphanumeric)
userID := uuid.NewString()
pageFilter := PageFilter{
Page: 1,
Size: 10,
}

rooms := []*entities.Room{
{
ID: uuid.NewString(),
Name: random.String(100, random.Alphanumeric),
Description: nil,
UserID: userID,
},
}
roomRepository.On("GetByQueryFilters", mock.AnythingOfType("repositories.QueryFilter"), mock.AnythingOfType("*repositories.PageFilter")).
Return(rooms, nil)

result, err := roomService.GetAll(search, userID, pageFilter)

assert.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, rooms, result)
roomRepository.AssertExpectations(t)
}

func TestRoomServiceGetAllErrorInRepository(t *testing.T) {
roomRepository := new(stub.RoomRepositoryMock)
roomService := NewRoomService(roomRepository)

search := random.String(100, random.Alphanumeric)
userID := uuid.NewString()
pageFilter := PageFilter{
Page: 1,
Size: 10,
}

mockError := errors.New("repository error")
roomRepository.On(
"GetByQueryFilters",
mock.AnythingOfType("repositories.QueryFilter"),
mock.AnythingOfType("*repositories.PageFilter"),
).Return(nil, mockError)

result, err := roomService.GetAll(search, userID, pageFilter)

assert.Error(t, err)
assert.Nil(t, result)
assert.EqualError(t, err, mockError.Error())
roomRepository.AssertExpectations(t)
}

func TestRoomServiceCountAll(t *testing.T) {
roomRepository := new(stub.RoomRepositoryMock)
roomService := NewRoomService(roomRepository)

search := random.String(100, random.Alphanumeric)
userID := uuid.NewString()

count := int64(10)
roomRepository.On("CountByQueryFilters", mock.AnythingOfType("repositories.QueryFilter")).Return(count, nil)

result, err := roomService.CountAll(search, userID)

assert.NoError(t, err)
assert.Equal(t, count, result)
roomRepository.AssertExpectations(t)
}

func TestRoomServiceCountAllErrorInRepository(t *testing.T) {
roomRepository := new(stub.RoomRepositoryMock)
roomService := NewRoomService(roomRepository)

search := random.String(100, random.Alphanumeric)
userID := uuid.NewString()

mockError := errors.New("repository error")
roomRepository.On("CountByQueryFilters", mock.AnythingOfType("repositories.QueryFilter")).Return(int64(0), mockError)

result, err := roomService.CountAll(search, userID)

assert.Error(t, err)
assert.Equal(t, int64(0), result)
assert.EqualError(t, err, mockError.Error())
roomRepository.AssertExpectations(t)
}
6 changes: 6 additions & 0 deletions internal/app/domain/entities/room.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ var (
ErrUserIDShouldNotBeEmpty = errors.New("user id should not be empty")
)

const (
RoomNameField = "name"
RoomDescriptionField = "description"
RoomUserIDField = "user_id"
)

type Room struct {
ID string
Name string
Expand Down
28 changes: 28 additions & 0 deletions internal/app/domain/repositories/arguments.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
package repositories

const (
AndLogicalOperator LogicalOperator = "AND"
OrLogicalOperator LogicalOperator = "OR"
)

const (
LikeComparisonOperator ComparisonOperator = "LIKE"
EqualComparisonOperator ComparisonOperator = "="
)

type ComparisonOperator string
type LogicalOperator string

type PageFilter struct {
Offset int
Limit int
}

type QueryFilter struct {
ConditionGroups []ConditionGroup
}

type ConditionGroup struct {
Operator LogicalOperator
Conditions []Condition
}

type Condition struct {
Field string
Operator ComparisonOperator
Value interface{}
}
8 changes: 6 additions & 2 deletions internal/app/domain/repositories/room.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ import (
)

var (
ErrCanNotCreateRoom = errors.New("can not create room")
ErrCanNotCheckIfRoomExistsByID = errors.New("can not check if room exists by id")
ErrCanNotCreateRoom = errors.New("can not create room")
ErrCanNotCheckIfRoomExistsByID = errors.New("can not check if room exists by id")
ErrRoomRepositoryCanNotGetRooms = errors.New("can not get rooms")
ErrRoomRepositoryCanNotCountRooms = errors.New("can not count rooms")
)

type RoomRepository interface {
Create(room *entities.Room) error
ExistsByID(id string) (bool, error)
GetByQueryFilters(queryFilter QueryFilter, pageFilter *PageFilter) ([]*entities.Room, error)
CountByQueryFilters(queryFilter QueryFilter) (int64, error)
}
78 changes: 78 additions & 0 deletions internal/app/infrastructure/controllers/get_rooms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package controllers

import (
"github.com/jibaru/home-inventory-api/m/internal/app/application/services"
"github.com/jibaru/home-inventory-api/m/internal/app/infrastructure/responses"
"github.com/labstack/echo/v4"
"net/http"
)

type GetRoomsController struct {
roomService *services.RoomService
}

type GetRoomsRequest struct {
Search string `query:"search"`
Page int `query:"page"`
PerPage int `query:"per_page"`
}

type GetRoomsResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Description *string `json:"description"`
}

func NewGetRoomsController(
roomService *services.RoomService,
) *GetRoomsController {
return &GetRoomsController{roomService: roomService}
}

func (c *GetRoomsController) Handle(ctx echo.Context) error {
userID := ctx.Get("auth_id").(string)
request := GetRoomsRequest{}

err := (&echo.DefaultBinder{}).BindQueryParams(ctx, &request)
if err != nil {
return ctx.JSON(http.StatusBadRequest, responses.NewMessageResponse(err.Error()))
}

rooms, err := c.roomService.GetAll(
request.Search,
userID,
services.PageFilter{
Page: request.Page,
Size: request.PerPage,
},
)
if err != nil {
return ctx.JSON(http.StatusBadRequest, responses.NewMessageResponse(err.Error()))
}

total, err := c.roomService.CountAll(
request.Search,
userID,
)
if err != nil {
return ctx.JSON(http.StatusBadRequest, responses.NewMessageResponse(err.Error()))
}

responseRooms := make([]*GetRoomsResponse, 0)
for _, room := range rooms {
responseRooms = append(responseRooms, &GetRoomsResponse{
ID: room.ID,
Name: room.Name,
Description: room.Description,
})
}

return ctx.JSON(http.StatusOK, responses.NewPaginatedResponse(
responseRooms,
total,
request.Page,
request.PerPage,
len(rooms),
ctx.Request().URL.Path,
))
}
2 changes: 2 additions & 0 deletions internal/app/infrastructure/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func RunServer(
createItemController := controllers.NewCreateItemController(itemService)
addItemIntoBoxController := controllers.NewAddItemIntoBoxController(boxService)
removeItemFromBoxController := controllers.NewRemoveItemFromBoxController(boxService)
getRoomsController := controllers.NewGetRoomsController(roomService)

needsAuthMiddleware := middlewares.NewNeedsAuthMiddleware(authService)

Expand All @@ -70,6 +71,7 @@ func RunServer(
authApi.POST("/items", createItemController.Handle)
authApi.POST("/boxes/:boxID/items", addItemIntoBoxController.Handle)
authApi.DELETE("/boxes/:boxID/items/:itemID", removeItemFromBoxController.Handle)
authApi.GET("/rooms", getRoomsController.Handle)

e.Logger.Fatal(e.Start(host + ":" + port))
}
30 changes: 30 additions & 0 deletions internal/app/infrastructure/repositories/gorm/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package gorm

import (
"github.com/jibaru/home-inventory-api/m/internal/app/domain/repositories"
"gorm.io/gorm"
"strings"
)

func applyFilters(db *gorm.DB, filter repositories.QueryFilter) *gorm.DB {
for _, group := range filter.ConditionGroups {
var groupConditions []string
var args []interface{}

for _, condition := range group.Conditions {
switch condition.Operator {
case repositories.LikeComparisonOperator:
groupConditions = append(groupConditions, condition.Field+" LIKE ?")
args = append(args, condition.Value.(string))
default:
groupConditions = append(groupConditions, condition.Field+" "+string(condition.Operator)+" ?")
args = append(args, condition.Value)
}
}

groupQuery := strings.Join(groupConditions, " "+string(group.Operator)+" ")
db = db.Where(groupQuery, args...)
}

return db
}
Loading
Loading