From c95e88d6a56b471df02aa61dffc88c4b3044614d Mon Sep 17 00:00:00 2001 From: AbdulWahab3181 Date: Sun, 26 May 2024 09:32:58 +0500 Subject: [PATCH] Added pagination, Filtering and Sorting and implemented count endpoint --- db/features.go | 129 ++++++++++++++++++++++++++++++--- db/interface.go | 3 +- handlers/features.go | 25 +++++-- mocks/Database.go | 165 ++++++++++++++++++++++++++++--------------- routes/features.go | 3 +- 5 files changed, 251 insertions(+), 74 deletions(-) diff --git a/db/features.go b/db/features.go index 96c0327d9..1c6eb16c4 100644 --- a/db/features.go +++ b/db/features.go @@ -181,20 +181,129 @@ func (db database) DeleteFeatureStoryByUuid(featureUuid, storyUuid string) error return nil } -func (db database) GetBountyByFeatureAndPhaseUuid(featureUuid string, phaseUuid string) (Bounty, error) { - bounty := Bounty{} - result := db.db.Model(&Bounty{}). +func (db database) GetBountiesByFeatureAndPhaseUuid(featureUuid string, phaseUuid string, r *http.Request) ([]NewBounty, error) { + keys := r.URL.Query() + tags := keys.Get("tags") + offset, limit, sortBy, direction, search := utils.GetPaginationParams(r) + open := keys.Get("Open") + assigned := keys.Get("Assigned") + completed := keys.Get("Completed") + paid := keys.Get("Paid") + languages := keys.Get("languages") + languageArray := strings.Split(languages, ",") + + var bounties []NewBounty + + // Initialize the query with the necessary joins and initial filters + query := db.db.Model(&Bounty{}). Select("bounty.*"). - Joins(`INNER JOIN "feature_phases" ON "feature_phases"."uuid" = "bounty"."phase_uuid" `). - Where(`"feature_phases"."feature_uuid" = ? AND "feature_phases"."uuid" = ?`, featureUuid, phaseUuid). - Order(`"bounty"."id"`). - Limit(1). - First(&bounty) + Joins(`INNER JOIN "feature_phases" ON "feature_phases"."uuid" = "bounty"."phase_uuid"`). + Where(`"feature_phases"."feature_uuid" = ? AND "feature_phases"."uuid" = ?`, featureUuid, phaseUuid) + + // Add pagination if applicable + if limit > 0 { + query = query.Limit(limit).Offset(offset) + } + + // Add sorting if applicable + if sortBy != "" && direction != "" { + query = query.Order(fmt.Sprintf("%s %s", sortBy, direction)) + } else { + query = query.Order("created_at DESC") + } + + // Add search filter + if search != "" { + searchQuery := fmt.Sprintf("LOWER(title) LIKE ?", "%"+strings.ToLower(search)+"%") + query = query.Where(searchQuery) + } + + // Add language filter + if len(languageArray) > 0 { + for _, lang := range languageArray { + if lang != "" { + query = query.Where("coding_languages @> ARRAY[?]::varchar[]", lang) + } + } + } + + // Add status filters + var statusConditions []string + + if open == "true" { + statusConditions = append(statusConditions, "assignee = '' AND paid != true") + } + if assigned == "true" { + statusConditions = append(statusConditions, "assignee != '' AND paid = false") + } + if completed == "true" { + statusConditions = append(statusConditions, "assignee != '' AND completed = true AND paid = false") + } + if paid == "true" { + statusConditions = append(statusConditions, "paid = true") + } + + if len(statusConditions) > 0 { + query = query.Where(strings.Join(statusConditions, " OR ")) + } + + // Execute the query + result := query.Find(&bounties) if result.RowsAffected == 0 { - return bounty, errors.New("no bounty found") + return bounties, errors.New("no bounty found") } - return bounty, nil + + // Handle tags if any + if tags != "" { + t := strings.Split(tags, ",") + for _, s := range t { + query = query.Where("? = ANY (tags)", s) + } + query.Scan(&bounties) + } + + return bounties, nil +} + +func (db database) GetBountiesCountByFeatureAndPhaseUuid(featureUuid string, phaseUuid string, r *http.Request) int64 { + keys := r.URL.Query() + open := keys.Get("Open") + assigned := keys.Get("Assigned") + completed := keys.Get("Completed") + paid := keys.Get("Paid") + + // Initialize the query with the necessary joins and initial filters + query := db.db.Model(&Bounty{}). + Select("COUNT(*)"). + Joins(`INNER JOIN "feature_phases" ON "feature_phases"."uuid" = "bounty"."phase_uuid"`). + Where(`"feature_phases"."feature_uuid" = ? AND "feature_phases"."uuid" = ?`, featureUuid, phaseUuid) + + // Add status filters + var statusConditions []string + + if open == "true" { + statusConditions = append(statusConditions, "assignee = '' AND paid != true") + } + if assigned == "true" { + statusConditions = append(statusConditions, "assignee != '' AND paid = false") + } + if completed == "true" { + statusConditions = append(statusConditions, "assignee != '' AND completed = true AND paid = false") + } + if paid == "true" { + statusConditions = append(statusConditions, "paid = true") + } + + if len(statusConditions) > 0 { + query = query.Where(strings.Join(statusConditions, " OR ")) + } + + var count int64 + + query.Count(&count) + + return count } func (db database) GetPhaseByUuid(phaseUuid string) (FeaturePhase, error) { diff --git a/db/interface.go b/db/interface.go index ffc141e7a..22be70515 100644 --- a/db/interface.go +++ b/db/interface.go @@ -157,6 +157,7 @@ type Database interface { GetFeatureStoryByUuid(featureUuid, storyUuid string) (FeatureStory, error) DeleteFeatureStoryByUuid(featureUuid, storyUuid string) error DeleteFeatureByUuid(uuid string) error - GetBountyByFeatureAndPhaseUuid(featureUuid string, phaseUuid string) (Bounty, error) + GetBountiesByFeatureAndPhaseUuid(featureUuid string, phaseUuid string, r *http.Request) ([]NewBounty, error) + GetBountiesCountByFeatureAndPhaseUuid(featureUuid string, phaseUuid string, r *http.Request) int64 GetPhaseByUuid(phaseUuid string) (FeaturePhase, error) } diff --git a/handlers/features.go b/handlers/features.go index ea56bbc33..13766a396 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -13,12 +13,15 @@ import ( ) type featureHandler struct { - db db.Database + db db.Database + generateBountyHandler func(bounties []db.NewBounty) []db.BountyResponse } func NewFeatureHandler(database db.Database) *featureHandler { + bHandler := NewBountyHandler(http.DefaultClient, database) return &featureHandler{ - db: database, + db: database, + generateBountyHandler: bHandler.GenerateBountyResponse, } } @@ -315,16 +318,28 @@ func (oh *featureHandler) DeleteStory(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]string{"message": "Story deleted successfully"}) } -func (oh *featureHandler) GetBountyByFeatureAndPhaseUuid(w http.ResponseWriter, r *http.Request) { +func (oh *featureHandler) GetBountiesByFeatureAndPhaseUuid(w http.ResponseWriter, r *http.Request) { featureUuid := chi.URLParam(r, "feature_uuid") phaseUuid := chi.URLParam(r, "phase_uuid") - bounty, err := oh.db.GetBountyByFeatureAndPhaseUuid(featureUuid, phaseUuid) + bounties, err := oh.db.GetBountiesByFeatureAndPhaseUuid(featureUuid, phaseUuid, r) if err != nil { w.WriteHeader(http.StatusNotFound) return } + var bountyResponse []db.BountyResponse = oh.generateBountyHandler(bounties) + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(bountyResponse) +} + +func (oh *featureHandler) GetBountiesCountByFeatureAndPhaseUuid(w http.ResponseWriter, r *http.Request) { + featureUuid := chi.URLParam(r, "feature_uuid") + phaseUuid := chi.URLParam(r, "phase_uuid") + + bountiesCount := oh.db.GetBountiesCountByFeatureAndPhaseUuid(featureUuid, phaseUuid, r) + w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(bounty) + json.NewEncoder(w).Encode(bountiesCount) } diff --git a/mocks/Database.go b/mocks/Database.go index b801eef8d..ad8a44c2d 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -2284,6 +2284,66 @@ func (_c *Database_GetBountiesByDateRangeCount_Call) RunAndReturn(run func(db.Pa return _c } +// GetBountiesByFeatureAndPhaseUuid provides a mock function with given fields: featureUuid, phaseUuid, r +func (_m *Database) GetBountiesByFeatureAndPhaseUuid(featureUuid string, phaseUuid string, r *http.Request) ([]db.NewBounty, error) { + ret := _m.Called(featureUuid, phaseUuid, r) + + if len(ret) == 0 { + panic("no return value specified for GetBountiesByFeatureAndPhaseUuid") + } + + var r0 []db.NewBounty + var r1 error + if rf, ok := ret.Get(0).(func(string, string, *http.Request) ([]db.NewBounty, error)); ok { + return rf(featureUuid, phaseUuid, r) + } + if rf, ok := ret.Get(0).(func(string, string, *http.Request) []db.NewBounty); ok { + r0 = rf(featureUuid, phaseUuid, r) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.NewBounty) + } + } + + if rf, ok := ret.Get(1).(func(string, string, *http.Request) error); ok { + r1 = rf(featureUuid, phaseUuid, r) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Database_GetBountiesByFeatureAndPhaseUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBountiesByFeatureAndPhaseUuid' +type Database_GetBountiesByFeatureAndPhaseUuid_Call struct { + *mock.Call +} + +// GetBountiesByFeatureAndPhaseUuid is a helper method to define mock.On call +// - featureUuid string +// - phaseUuid string +// - r *http.Request +func (_e *Database_Expecter) GetBountiesByFeatureAndPhaseUuid(featureUuid interface{}, phaseUuid interface{}, r interface{}) *Database_GetBountiesByFeatureAndPhaseUuid_Call { + return &Database_GetBountiesByFeatureAndPhaseUuid_Call{Call: _e.mock.On("GetBountiesByFeatureAndPhaseUuid", featureUuid, phaseUuid, r)} +} + +func (_c *Database_GetBountiesByFeatureAndPhaseUuid_Call) Run(run func(featureUuid string, phaseUuid string, r *http.Request)) *Database_GetBountiesByFeatureAndPhaseUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string), args[2].(*http.Request)) + }) + return _c +} + +func (_c *Database_GetBountiesByFeatureAndPhaseUuid_Call) Return(_a0 []db.NewBounty, _a1 error) *Database_GetBountiesByFeatureAndPhaseUuid_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Database_GetBountiesByFeatureAndPhaseUuid_Call) RunAndReturn(run func(string, string, *http.Request) ([]db.NewBounty, error)) *Database_GetBountiesByFeatureAndPhaseUuid_Call { + _c.Call.Return(run) + return _c +} + // GetBountiesCount provides a mock function with given fields: r func (_m *Database) GetBountiesCount(r *http.Request) int64 { ret := _m.Called(r) @@ -2330,6 +2390,54 @@ func (_c *Database_GetBountiesCount_Call) RunAndReturn(run func(*http.Request) i return _c } +// GetBountiesCountByFeatureAndPhaseUuid provides a mock function with given fields: featureUuid, phaseUuid, r +func (_m *Database) GetBountiesCountByFeatureAndPhaseUuid(featureUuid string, phaseUuid string, r *http.Request) int64 { + ret := _m.Called(featureUuid, phaseUuid, r) + + if len(ret) == 0 { + panic("no return value specified for GetBountiesCountByFeatureAndPhaseUuid") + } + + var r0 int64 + if rf, ok := ret.Get(0).(func(string, string, *http.Request) int64); ok { + r0 = rf(featureUuid, phaseUuid, r) + } else { + r0 = ret.Get(0).(int64) + } + + return r0 +} + +// Database_GetBountiesCountByFeatureAndPhaseUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBountiesCountByFeatureAndPhaseUuid' +type Database_GetBountiesCountByFeatureAndPhaseUuid_Call struct { + *mock.Call +} + +// GetBountiesCountByFeatureAndPhaseUuid is a helper method to define mock.On call +// - featureUuid string +// - phaseUuid string +// - r *http.Request +func (_e *Database_Expecter) GetBountiesCountByFeatureAndPhaseUuid(featureUuid interface{}, phaseUuid interface{}, r interface{}) *Database_GetBountiesCountByFeatureAndPhaseUuid_Call { + return &Database_GetBountiesCountByFeatureAndPhaseUuid_Call{Call: _e.mock.On("GetBountiesCountByFeatureAndPhaseUuid", featureUuid, phaseUuid, r)} +} + +func (_c *Database_GetBountiesCountByFeatureAndPhaseUuid_Call) Run(run func(featureUuid string, phaseUuid string, r *http.Request)) *Database_GetBountiesCountByFeatureAndPhaseUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string), args[2].(*http.Request)) + }) + return _c +} + +func (_c *Database_GetBountiesCountByFeatureAndPhaseUuid_Call) Return(_a0 int64) *Database_GetBountiesCountByFeatureAndPhaseUuid_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_GetBountiesCountByFeatureAndPhaseUuid_Call) RunAndReturn(run func(string, string, *http.Request) int64) *Database_GetBountiesCountByFeatureAndPhaseUuid_Call { + _c.Call.Return(run) + return _c +} + // GetBountiesLeaderboard provides a mock function with given fields: func (_m *Database) GetBountiesLeaderboard() []db.LeaderData { ret := _m.Called() @@ -2528,63 +2636,6 @@ func (_c *Database_GetBountyByCreated_Call) RunAndReturn(run func(uint) (db.NewB return _c } -// GetBountyByFeatureAndPhaseUuid provides a mock function with given fields: featureUuid, phaseUuid -func (_m *Database) GetBountyByFeatureAndPhaseUuid(featureUuid string, phaseUuid string) (db.Bounty, error) { - ret := _m.Called(featureUuid, phaseUuid) - - if len(ret) == 0 { - panic("no return value specified for GetBountyByFeatureAndPhaseUuid") - } - - var r0 db.Bounty - var r1 error - if rf, ok := ret.Get(0).(func(string, string) (db.Bounty, error)); ok { - return rf(featureUuid, phaseUuid) - } - if rf, ok := ret.Get(0).(func(string, string) db.Bounty); ok { - r0 = rf(featureUuid, phaseUuid) - } else { - r0 = ret.Get(0).(db.Bounty) - } - - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(featureUuid, phaseUuid) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Database_GetBountyByFeatureAndPhaseUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBountyByFeatureAndPhaseUuid' -type Database_GetBountyByFeatureAndPhaseUuid_Call struct { - *mock.Call -} - -// GetBountyByFeatureAndPhaseUuid is a helper method to define mock.On call -// - featureUuid string -// - phaseUuid string -func (_e *Database_Expecter) GetBountyByFeatureAndPhaseUuid(featureUuid interface{}, phaseUuid interface{}) *Database_GetBountyByFeatureAndPhaseUuid_Call { - return &Database_GetBountyByFeatureAndPhaseUuid_Call{Call: _e.mock.On("GetBountyByFeatureAndPhaseUuid", featureUuid, phaseUuid)} -} - -func (_c *Database_GetBountyByFeatureAndPhaseUuid_Call) Run(run func(featureUuid string, phaseUuid string)) *Database_GetBountyByFeatureAndPhaseUuid_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) - }) - return _c -} - -func (_c *Database_GetBountyByFeatureAndPhaseUuid_Call) Return(_a0 db.Bounty, _a1 error) *Database_GetBountyByFeatureAndPhaseUuid_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Database_GetBountyByFeatureAndPhaseUuid_Call) RunAndReturn(run func(string, string) (db.Bounty, error)) *Database_GetBountyByFeatureAndPhaseUuid_Call { - _c.Call.Return(run) - return _c -} - // GetBountyById provides a mock function with given fields: id func (_m *Database) GetBountyById(id string) ([]db.NewBounty, error) { ret := _m.Called(id) diff --git a/routes/features.go b/routes/features.go index 9b17f30dd..b46e1bfb5 100644 --- a/routes/features.go +++ b/routes/features.go @@ -29,7 +29,8 @@ func FeatureRoutes() chi.Router { r.Get("/{feature_uuid}/story", featureHandlers.GetStoriesByFeatureUuid) r.Get("/{feature_uuid}/story/{story_uuid}", featureHandlers.GetStoryByUuid) r.Delete("/{feature_uuid}/story/{story_uuid}", featureHandlers.DeleteStory) - r.Get("/{feature_uuid}/phase/{phase_uuid}/bounty", featureHandlers.GetBountyByFeatureAndPhaseUuid) + r.Get("/{feature_uuid}/phase/{phase_uuid}/bounty", featureHandlers.GetBountiesByFeatureAndPhaseUuid) + r.Get("/{feature_uuid}/phase/{phase_uuid}/bounty/count", featureHandlers.GetBountiesCountByFeatureAndPhaseUuid) }) return r