diff --git a/db/interface.go b/db/interface.go index bdf993e0f..d0b96b2a1 100644 --- a/db/interface.go +++ b/db/interface.go @@ -134,6 +134,7 @@ type Database interface { GetPersonByPubkey(pubkey string) Person GetBountiesByDateRange(r PaymentDateRange, re *http.Request) []Bounty GetBountiesByDateRangeCount(r PaymentDateRange, re *http.Request) int64 + GetBountiesProviders(r PaymentDateRange, re *http.Request) []Person PersonUniqueNameFromName(name string) (string, error) ProcessAlerts(p Person) UserHasAccess(pubKeyFromAuth string, uuid string, role string) bool diff --git a/db/metrics.go b/db/metrics.go index 5ec74bb48..cf63dcebe 100644 --- a/db/metrics.go +++ b/db/metrics.go @@ -190,7 +190,7 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) } else { orderQuery = " ORDER BY " + sortBy + "" + "DESC" } - if limit > 1 { + if limit > 0 { limitQuery = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset) } @@ -247,3 +247,56 @@ func (db database) GetBountiesByDateRangeCount(r PaymentDateRange, re *http.Requ db.db.Raw(allQuery).Scan(&count) return count } + +func (db database) GetBountiesProviders(r PaymentDateRange, re *http.Request) []Person { + offset, limit, _, _, _ := utils.GetPaginationParams(re) + keys := re.URL.Query() + open := keys.Get("Open") + assingned := keys.Get("Assigned") + paid := keys.Get("Paid") + providers := keys.Get("provider") + + var statusConditions []string + + limitQuery := "" + + if open == "true" { + statusConditions = append(statusConditions, "assignee = '' AND paid != true") + } + if assingned == "true" { + statusConditions = append(statusConditions, "assignee != '' AND paid = false") + } + if paid == "true" { + statusConditions = append(statusConditions, "paid = true") + } + + var statusQuery string + if len(statusConditions) > 0 { + statusQuery = " AND (" + strings.Join(statusConditions, " OR ") + ")" + } else { + statusQuery = "" + } + + providerCondition := "" + if len(providers) > 0 { + providerSlice := strings.Split(providers, ",") + providerCondition = " AND owner_id IN ('" + strings.Join(providerSlice, "','") + "')" + } + + if limit > 0 { + limitQuery = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset) + } + + bountyOwners := []BountyOwners{} + bountyProviders := []Person{} + + query := `SELECT DISTINCT owner_id FROM public.bounty WHERE created >= '` + r.StartDate + `' AND created <= '` + r.EndDate + `'` + providerCondition + allQuery := query + " " + statusQuery + " " + limitQuery + db.db.Raw(allQuery).Scan(&bountyOwners) + + for _, owner := range bountyOwners { + person := db.GetPersonByPubkey(owner.OwnerID) + bountyProviders = append(bountyProviders, person) + } + return bountyProviders +} diff --git a/db/structs.go b/db/structs.go index 3e71fe64f..115d557fd 100644 --- a/db/structs.go +++ b/db/structs.go @@ -388,6 +388,10 @@ type Bounty struct { CodingLanguages pq.StringArray `gorm:"type:text[];not null default:'[]'" json:"coding_languages"` } +type BountyOwners struct { + OwnerID string `json:"owner_id"` +} + type BountyData struct { Bounty BountyId uint `json:"bounty_id"` diff --git a/handlers/metrics.go b/handlers/metrics.go index 88cc3a218..6faeb7521 100644 --- a/handlers/metrics.go +++ b/handlers/metrics.go @@ -233,6 +233,32 @@ func (mh *metricHandler) MetricsBountiesCount(w http.ResponseWriter, r *http.Req json.NewEncoder(w).Encode(MetricsBountiesCount) } +func (mh *metricHandler) MetricsBountiesProviders(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) + + if pubKeyFromAuth == "" { + fmt.Println("no pubkey from auth") + w.WriteHeader(http.StatusUnauthorized) + return + } + + request := db.PaymentDateRange{} + body, err := io.ReadAll(r.Body) + r.Body.Close() + + err = json.Unmarshal(body, &request) + if err != nil { + w.WriteHeader(http.StatusNotAcceptable) + json.NewEncoder(w).Encode("Request body not accepted") + return + } + + bountiesProviders := mh.db.GetBountiesProviders(request, r) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(bountiesProviders) +} + func MetricsCsv(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) diff --git a/mocks/Database.go b/mocks/Database.go index ab0e6066c..bc189ad35 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -1961,6 +1961,55 @@ func (_c *Database_GetBountiesLeaderboard_Call) RunAndReturn(run func() []db.Lea return _c } +// GetBountiesProviders provides a mock function with given fields: r, re +func (_m *Database) GetBountiesProviders(r db.PaymentDateRange, re *http.Request) []db.Person { + ret := _m.Called(r, re) + + if len(ret) == 0 { + panic("no return value specified for GetBountiesProviders") + } + + var r0 []db.Person + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, *http.Request) []db.Person); ok { + r0 = rf(r, re) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.Person) + } + } + + return r0 +} + +// Database_GetBountiesProviders_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBountiesProviders' +type Database_GetBountiesProviders_Call struct { + *mock.Call +} + +// GetBountiesProviders is a helper method to define mock.On call +// - r db.PaymentDateRange +// - re *http.Request +func (_e *Database_Expecter) GetBountiesProviders(r interface{}, re interface{}) *Database_GetBountiesProviders_Call { + return &Database_GetBountiesProviders_Call{Call: _e.mock.On("GetBountiesProviders", r, re)} +} + +func (_c *Database_GetBountiesProviders_Call) Run(run func(r db.PaymentDateRange, re *http.Request)) *Database_GetBountiesProviders_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(db.PaymentDateRange), args[1].(*http.Request)) + }) + return _c +} + +func (_c *Database_GetBountiesProviders_Call) Return(_a0 []db.Person) *Database_GetBountiesProviders_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_GetBountiesProviders_Call) RunAndReturn(run func(db.PaymentDateRange, *http.Request) []db.Person) *Database_GetBountiesProviders_Call { + _c.Call.Return(run) + return _c +} + // GetBounty provides a mock function with given fields: id func (_m *Database) GetBounty(id uint) db.Bounty { ret := _m.Called(id) diff --git a/routes/metrics.go b/routes/metrics.go index de354043f..53bc61257 100644 --- a/routes/metrics.go +++ b/routes/metrics.go @@ -11,7 +11,6 @@ func MetricsRoutes() chi.Router { r := chi.NewRouter() mh := handlers.NewMetricHandler(db.DB) r.Group(func(r chi.Router) { - // Todo: change auth to superadmin context r.Use(auth.PubKeyContextSuperAdmin) r.Post("/payment", handlers.PaymentMetrics) @@ -20,6 +19,7 @@ func MetricsRoutes() chi.Router { r.Post("/bounty_stats", mh.BountyMetrics) r.Post("/bounties", mh.MetricsBounties) r.Post("/bounties/count", mh.MetricsBountiesCount) + r.Post("/bounties/providers", mh.MetricsBountiesProviders) r.Post("/csv", handlers.MetricsCsv) }) return r