Skip to content

Commit

Permalink
feat: middleware and handler for getting all teams
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksandrMatsko committed Nov 15, 2024
1 parent 8775b7e commit 4e44b09
Show file tree
Hide file tree
Showing 8 changed files with 5,278 additions and 40 deletions.
9 changes: 9 additions & 0 deletions api/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package api

type SortOrder string

const (
NoSortOrder SortOrder = ""
AscSortOrder SortOrder = "asc"
DescSortOrder SortOrder = "desc"
)
23 changes: 13 additions & 10 deletions api/controller/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,7 @@ func GetTeam(dataBase moira.Database, teamID string) (dto.TeamModel, *api.ErrorR
}

// GetAllTeams is a controller function that returns all teams.
func GetAllTeams(dataBase moira.Database, page, size int64, textRegexp *regexp.Regexp, nameSortOrder string) (dto.TeamsList, *api.ErrorResponse) {
if page < 0 {
return dto.TeamsList{}, api.ErrorInvalidRequest(fmt.Errorf("p cannot be less than zero, got %v", page))
} else if page > 0 && size < 0 {
return dto.TeamsList{}, api.ErrorInvalidRequest(fmt.Errorf("cannon have positive p with negative size"))
}

func GetAllTeams(dataBase moira.Database, page, size int64, textRegexp *regexp.Regexp, sortOrder api.SortOrder) (dto.TeamsList, *api.ErrorResponse) {
teams, err := dataBase.GetAllTeams()
if err != nil {
return dto.TeamsList{}, api.ErrorInternalServer(fmt.Errorf("cannot get teams fron database: %w", err))
Expand All @@ -105,10 +99,10 @@ func GetAllTeams(dataBase moira.Database, page, size int64, textRegexp *regexp.R

teams = filteredTeams

if nameSortOrder == "asc" || nameSortOrder == "desc" {
if sortOrder == api.AscSortOrder || sortOrder == api.DescSortOrder {
slices.SortFunc(teams, func(first, second moira.Team) int {
cmpRes := strings.Compare(strings.ToLower(first.Name), strings.ToLower(second.Name))
if nameSortOrder == "desc" {
if sortOrder == api.DescSortOrder {
return cmpRes * -1
} else {
return cmpRes
Expand All @@ -118,7 +112,16 @@ func GetAllTeams(dataBase moira.Database, page, size int64, textRegexp *regexp.R

total := int64(len(teams))

if size >= 0 {
if page < 0 || (page > 0 && size < 0) {
return dto.TeamsList{
List: []dto.TeamModel{},
Page: &page,
Size: &size,
Total: &total,
}, nil
}

if page >= 0 && size >= 0 {
shift := page * size
if shift < int64(len(teams)) {
teams = teams[shift:]
Expand Down
64 changes: 40 additions & 24 deletions api/controller/team_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,39 +233,55 @@ func TestGetAllTeams(t *testing.T) {

teamModels := dto.NewTeamsList(teams).List

const (
noSortOrder = ""
ascSortOrder = "asc"
descSortOrder = "desc"
)

anyText := regexp.MustCompile(".*")

var (
firstPage int64 = 0
allTeamsSize int64 = -1
)

Convey("with page < 0 returns error invalid request", func() {
response, err := GetAllTeams(dataBase, -1, allTeamsSize, anyText, noSortOrder)
Convey("with page < 0 returns empty list", func() {
dataBase.EXPECT().GetAllTeams().Return(teams, nil)
var (
page int64 = -1
total = int64(len(teamModels))
)

So(err, ShouldResemble, api.ErrorInvalidRequest(fmt.Errorf("p cannot be less than zero, got %v", -1)))
So(response, ShouldResemble, dto.TeamsList{})
response, err := GetAllTeams(dataBase, page, allTeamsSize, anyText, api.NoSortOrder)

So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
List: []dto.TeamModel{},
Page: &page,
Size: &allTeamsSize,
Total: &total,
})
})

Convey("with page > 0 and size < 0, returns error invalid request", func() {
response, err := GetAllTeams(dataBase, 1, allTeamsSize, anyText, noSortOrder)
Convey("with page > 0 and size < 0, returns empty list", func() {
dataBase.EXPECT().GetAllTeams().Return(teams, nil)
var (
page int64 = 1
total = int64(len(teamModels))
)

So(err, ShouldResemble, api.ErrorInvalidRequest(fmt.Errorf("cannon have positive p with negative size")))
So(response, ShouldResemble, dto.TeamsList{})
response, err := GetAllTeams(dataBase, page, allTeamsSize, anyText, api.NoSortOrder)

So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
List: []dto.TeamModel{},
Page: &page,
Size: &allTeamsSize,
Total: &total,
})
})

Convey("when database returns error", func() {
dbErr := errors.New("test db err")

dataBase.EXPECT().GetAllTeams().Return(nil, dbErr)

response, err := GetAllTeams(dataBase, firstPage, allTeamsSize, anyText, noSortOrder)
response, err := GetAllTeams(dataBase, firstPage, allTeamsSize, anyText, api.NoSortOrder)

So(err, ShouldResemble, api.ErrorInternalServer(fmt.Errorf("cannot get teams fron database: %w", dbErr)))
So(response, ShouldResemble, dto.TeamsList{})
Expand All @@ -275,7 +291,7 @@ func TestGetAllTeams(t *testing.T) {
dataBase.EXPECT().GetAllTeams().Return(teams, nil)
total := int64(len(teamModels))

response, err := GetAllTeams(dataBase, firstPage, allTeamsSize, anyText, noSortOrder)
response, err := GetAllTeams(dataBase, firstPage, allTeamsSize, anyText, api.NoSortOrder)

So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
Expand All @@ -297,7 +313,7 @@ func TestGetAllTeams(t *testing.T) {

dataBase.EXPECT().GetAllTeams().Return(teams, nil)

response, err := GetAllTeams(dataBase, page0, size, anyText, noSortOrder)
response, err := GetAllTeams(dataBase, page0, size, anyText, api.NoSortOrder)
So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
List: teamModels[:size],
Expand All @@ -308,7 +324,7 @@ func TestGetAllTeams(t *testing.T) {

dataBase.EXPECT().GetAllTeams().Return(teams, nil)

response, err = GetAllTeams(dataBase, page1, size, anyText, noSortOrder)
response, err = GetAllTeams(dataBase, page1, size, anyText, api.NoSortOrder)
So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
List: teamModels[page1*size : page1*size+size],
Expand All @@ -327,7 +343,7 @@ func TestGetAllTeams(t *testing.T) {

dataBase.EXPECT().GetAllTeams().Return(teams, nil)

response, err := GetAllTeams(dataBase, page, size, anyText, noSortOrder)
response, err := GetAllTeams(dataBase, page, size, anyText, api.NoSortOrder)
So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
List: teamModels[page*size:],
Expand All @@ -346,7 +362,7 @@ func TestGetAllTeams(t *testing.T) {

dataBase.EXPECT().GetAllTeams().Return(teams, nil)

response, err := GetAllTeams(dataBase, page, size, anyText, noSortOrder)
response, err := GetAllTeams(dataBase, page, size, anyText, api.NoSortOrder)
So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
List: []dto.TeamModel{},
Expand All @@ -362,7 +378,7 @@ func TestGetAllTeams(t *testing.T) {
textRegexp := regexp.MustCompile(".*th-team-id")
total := int64(len(teamModels)) - 3

response, err := GetAllTeams(dataBase, firstPage, allTeamsSize, textRegexp, noSortOrder)
response, err := GetAllTeams(dataBase, firstPage, allTeamsSize, textRegexp, api.NoSortOrder)
So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
List: teamModels[3:],
Expand All @@ -377,7 +393,7 @@ func TestGetAllTeams(t *testing.T) {
dataBase.EXPECT().GetAllTeams().Return(teams, nil)
total := int64(len(teamModels))

response, err := GetAllTeams(dataBase, firstPage, allTeamsSize, anyText, ascSortOrder)
response, err := GetAllTeams(dataBase, firstPage, allTeamsSize, anyText, api.AscSortOrder)
So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
List: []dto.TeamModel{
Expand All @@ -399,7 +415,7 @@ func TestGetAllTeams(t *testing.T) {
dataBase.EXPECT().GetAllTeams().Return(teams, nil)
total := int64(len(teamModels))

response, err := GetAllTeams(dataBase, firstPage, allTeamsSize, anyText, descSortOrder)
response, err := GetAllTeams(dataBase, firstPage, allTeamsSize, anyText, api.DescSortOrder)
So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
List: []dto.TeamModel{
Expand Down Expand Up @@ -427,7 +443,7 @@ func TestGetAllTeams(t *testing.T) {
size int64 = 2
)

response, err := GetAllTeams(dataBase, page, size, textRegexp, descSortOrder)
response, err := GetAllTeams(dataBase, page, size, textRegexp, api.DescSortOrder)
So(err, ShouldBeNil)
So(response, ShouldResemble, dto.TeamsList{
List: []dto.TeamModel{
Expand Down
6 changes: 6 additions & 0 deletions api/handler/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ const (
contactEventsDefaultPage = 0
contactEventsDefaultSize = -1
)

const (
getAllTeamsDefaultPage = 0
getAllTeamsDefaultSize = -1
getAllTeamsDefaultRegexTemplate = ".*"
)
49 changes: 45 additions & 4 deletions api/handler/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handler

import (
"net/http"
"regexp"

"github.com/go-chi/chi"
"github.com/go-chi/render"
Expand All @@ -12,7 +13,13 @@ import (
)

func teams(router chi.Router) {
router.Get("/", getAllTeams)
router.With(
middleware.AdminOnlyMiddleware(),
middleware.Paginate(getAllTeamsDefaultPage, getAllTeamsDefaultSize),
middleware.SearchTextContext(regexp.MustCompile(getAllTeamsDefaultRegexTemplate)),
middleware.SortOrderContext(api.NoSortOrder),
).Get("/all", getAllTeams)
router.Get("/", getAllTeamsForUser)
router.Post("/", createTeam)
router.Route("/{teamId}", func(router chi.Router) {
router.Use(middleware.TeamContext)
Expand Down Expand Up @@ -81,15 +88,15 @@ func createTeam(writer http.ResponseWriter, request *http.Request) {

// nolint: gofmt,goimports
//
// @summary Get all teams
// @id get-all-teams
// @summary Get all teams for user
// @id get-all-teams-for-user
// @tags team
// @produce json
// @success 200 {object} dto.UserTeams "Teams fetched successfully"
// @failure 422 {object} api.ErrorRenderExample "Render error"
// @failure 500 {object} api.ErrorInternalServerExample "Internal server error"
// @router /teams [get]
func getAllTeams(writer http.ResponseWriter, request *http.Request) {
func getAllTeamsForUser(writer http.ResponseWriter, request *http.Request) {
user := middleware.GetLogin(request)
response, err := controller.GetUserTeams(database, user)
if err != nil {
Expand Down Expand Up @@ -131,6 +138,40 @@ func getTeam(writer http.ResponseWriter, request *http.Request) {
}
}

// nolint: gofmt,goimports
//
// @summary Get all Moira teams
// @id get-all-teams
// @tags team
// @produce json
// @param size query int false "Number of items to be displayed on one page. if size = -1 then all teams returned" default(-1)
// @param p query int false "Defines the number of the displayed page. E.g, p=2 would display the 2nd page" default(0)
// @param searchText query string false "Regular expression which will be applied to team id or team name than filtering teams" default(.*)
// @param sort query string false "String to set sort order (by name). On empty - no order, asc - ascending, desc - descending" default()
// @success 200 {object} dto.TeamsList "Teams fetched successfully"
// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client"
// @failure 403 {object} api.ErrorForbiddenExample "Forbidden"
// @failure 422 {object} api.ErrorRenderExample "Render error"
// @failure 500 {object} api.ErrorInternalServerExample "Internal server error"
// @router /teams/all [get]
func getAllTeams(writer http.ResponseWriter, request *http.Request) {
page := middleware.GetPage(request)
size := middleware.GetSize(request)
textRegex := middleware.GetSearchText(request)
sort := middleware.GetSortOrder(request)

response, err := controller.GetAllTeams(database, page, size, textRegex, sort)
if err != nil {
render.Render(writer, request, err) //nolint:errcheck
return
}

if err := render.Render(writer, request, response); err != nil {
render.Render(writer, request, api.ErrorRender(err)) //nolint:errcheck
return
}
}

// nolint: gofmt,goimports
//
// @summary Update existing team
Expand Down
56 changes: 56 additions & 0 deletions api/middleware/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -351,3 +352,58 @@ func LimitsContext(limit api.LimitsConfig) func(next http.Handler) http.Handler
})
}
}

// SearchTextContext compiles and puts search text regex to request context.
func SearchTextContext(defaultRegex *regexp.Regexp) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
urlValues, err := url.ParseQuery(request.URL.RawQuery)
if err != nil {
render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint
return
}

var searchTextRegex *regexp.Regexp

searchText := urlValues.Get("searchText")
if searchText != "" {
searchTextRegex, err = regexp.Compile(searchText)
if err != nil {
render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("failed to parse searchText template \"%s\": %w", searchText, err))) //nolint
return
}
} else {
searchTextRegex = defaultRegex
}

ctx := context.WithValue(request.Context(), searchTextContextKey, searchTextRegex)
next.ServeHTTP(writer, request.WithContext(ctx))
})
}
}

// SortOrderContext puts sort order to request context.
func SortOrderContext(defaultSortOrder api.SortOrder) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
urlValues, err := url.ParseQuery(request.URL.RawQuery)
if err != nil {
render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint
return
}

var sortOrder api.SortOrder

sortVal := api.SortOrder(urlValues.Get("sort"))
switch sortVal {
case api.NoSortOrder, api.AscSortOrder, api.DescSortOrder:
sortOrder = sortVal
default:
sortOrder = defaultSortOrder
}

ctx := context.WithValue(request.Context(), sortOrderContextKey, sortOrder)
next.ServeHTTP(writer, request.WithContext(ctx))
})
}
}
Loading

0 comments on commit 4e44b09

Please sign in to comment.