-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add leaderboard endpoint (#15)
* refactor: move posthog query and generic reponse to lib directory * feat: add leaderboard endpoint * test: add test for leaderboard and fix references
- Loading branch information
1 parent
2209099
commit decc31e
Showing
21 changed files
with
300 additions
and
14 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module leaderboard | ||
|
||
go 1.20 | ||
|
||
require lib v0.0.0 | ||
|
||
require ( | ||
github.com/fatih/color v1.17.0 // indirect | ||
github.com/mattn/go-colorable v0.1.13 // indirect | ||
github.com/mattn/go-isatty v0.0.20 // indirect | ||
golang.org/x/sys v0.21.0 // indirect | ||
) | ||
|
||
replace lib v0.0.0 => ../../../lib |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= | ||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= | ||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | ||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | ||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= | ||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package constants | ||
|
||
const ResultLimit = 1000 |
37 changes: 37 additions & 0 deletions
37
packages/v1/leaderboard/internal/queries/fetchleaderboardresults.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package queries | ||
|
||
import ( | ||
"fmt" | ||
_constants "leaderboard/internal/constants" | ||
_types "leaderboard/internal/types" | ||
"lib/types" | ||
"lib/utils" | ||
) | ||
|
||
// FetchLeaderboardResults Fetches a page of leaderboard results. The page is zero-indexed. | ||
func FetchLeaderboardResults(page int, logger *utils.Logger) ([]_types.LeaderboardResult, error) { | ||
var response types.PostHogQueryResponse | ||
var results []_types.LeaderboardResult | ||
|
||
query := fmt.Sprintf(`{"query":{"kind":"HogQLQuery","query":"select person.pdi.distinct_id as \"account\", count(distinct dateTrunc('day', events.timestamp) || events.event) as \"total\" from events where events.properties.genesisHash = 'IXnoWtviVVJW5LGivNFc0Dq14V3kqaXuK2u5OQrdVZo=' group by \"account\" order by \"total\" desc limit %d offset %d * %d"}}`, _constants.ResultLimit, page, _constants.ResultLimit) | ||
|
||
if err := utils.SendPostHogQuery(query, &response); err != nil { | ||
logger.Error(err) | ||
|
||
return nil, err | ||
} | ||
|
||
for _, value := range response.Results { | ||
results = append(results, _types.LeaderboardResult{ | ||
Account: value[0].(string), | ||
Total: int(value[1].(float64)), | ||
}) | ||
} | ||
|
||
// if the results are nil, return an empty array | ||
if results == nil { | ||
return []_types.LeaderboardResult{}, nil | ||
} | ||
|
||
return results, nil | ||
} |
21 changes: 21 additions & 0 deletions
21
packages/v1/leaderboard/internal/queries/fetchtotalaccountscompleted.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package queries | ||
|
||
import ( | ||
"fmt" | ||
"lib/types" | ||
"lib/utils" | ||
) | ||
|
||
// FetchTotalAccountsCompleted Fetches the total number of accounts that have completed at least one event. | ||
func FetchTotalAccountsCompleted(logger *utils.Logger) (int, error) { | ||
var response types.PostHogQueryResponse | ||
|
||
query := fmt.Sprintf(`{"query":{"kind":"HogQLQuery","query":"select count(distinct person.pdi.distinct_id) as \"total\" from events where events.properties.genesisHash = 'IXnoWtviVVJW5LGivNFc0Dq14V3kqaXuK2u5OQrdVZo='"}}`) | ||
if err := utils.SendPostHogQuery(query, &response); err != nil { | ||
logger.Error(err) | ||
|
||
return 0, err | ||
} | ||
|
||
return int(response.Results[0][0].(float64)), nil | ||
} |
10 changes: 10 additions & 0 deletions
10
packages/v1/leaderboard/internal/types/leaderboardresult.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package types | ||
|
||
// LeaderboardResult | ||
// @Description The account and the total daily quests the account has completed. | ||
type LeaderboardResult struct { | ||
// The base 32 encoded address of the account | ||
Account string `json:"account" example:"TESTK4BURRDGVVHAX2FBY7CPRC2RTTVRRN4C2TVDCHRCXNTFGL3TVSDROE"` | ||
// The total number of daily quests completed | ||
Total int `json:"total" example:"22"` | ||
} |
12 changes: 12 additions & 0 deletions
12
packages/v1/leaderboard/internal/types/paginationmetadata.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package types | ||
|
||
type PaginationMetadata struct { | ||
// The current page of results | ||
Page int `json:"page" example:"0"` | ||
// The total amount of pages | ||
PageCount int `json:"pageCount" example:"3"` | ||
// The amount of results per page | ||
PageSize int `json:"pageSize" example:"1000"` | ||
// The total number of results | ||
Total int `json:"total" example:"1024"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package types | ||
|
||
import "lib/types" | ||
|
||
type Request struct { | ||
Http types.Http `json:"http,omitempty"` | ||
Page string `json:"page,omitempty"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package types | ||
|
||
type Response struct { | ||
Body ResponseBody `json:"body,omitempty"` | ||
Headers ResponseHeaders `json:"headers,omitempty"` | ||
StatusCode int `json:"statusCode,omitempty"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package types | ||
|
||
type ResponseBody struct { | ||
Error interface{} `json:"error,omitempty" swaggertype:"object"` | ||
// The pagination metadata | ||
Metadata PaginationMetadata `json:"metadata"` | ||
// The leaderboard results; the completed quests for each account | ||
Results []LeaderboardResult `json:"results"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package types | ||
|
||
type ResponseHeaders struct { | ||
CacheControl string `json:"Cache-Control"` | ||
ContentType string `json:"Content-Type"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
_constants "leaderboard/internal/constants" | ||
_queries "leaderboard/internal/queries" | ||
_types "leaderboard/internal/types" | ||
"lib/constants" | ||
"lib/errors" | ||
"lib/utils" | ||
"math" | ||
"net/http" | ||
"strconv" | ||
) | ||
|
||
// Main godoc | ||
// @Summary Quests Leaderboard | ||
// @Description Gets the quests' leaderboard for all accounts. | ||
// @Produce json | ||
// @Param page query string false "the page of results, the first page starts at 0" Example(0) | ||
// @Success 200 {object} _types.ResponseBody | ||
// @Failure 405 "If it is not a GET request" | ||
// @Failure 500 | ||
// @Header all {string} Cache-Control "public, max-age=3600" | ||
// @Router /v1/leaderboard [get] | ||
func Main(request _types.Request) *_types.Response { | ||
var err error | ||
var page = 0 | ||
|
||
logger := utils.NewLogger() | ||
headers := _types.ResponseHeaders{ | ||
CacheControl: fmt.Sprintf("public, max-age=%d", constants.HourInSeconds), | ||
ContentType: "application/json", | ||
} | ||
|
||
// only accept get requests | ||
if request.Http.Method != http.MethodGet { | ||
return &_types.Response{ | ||
Headers: headers, | ||
StatusCode: http.StatusMethodNotAllowed, | ||
} | ||
} | ||
|
||
if request.Page != "" { | ||
if page, err = strconv.Atoi(request.Page); err != nil { | ||
logger.Debug(fmt.Sprintf("failed to parse page \"%s\" to integer", request.Page)) | ||
} | ||
} | ||
|
||
logger.Debug("fetching total number of accounts that have completed at least one event") | ||
|
||
total, err := _queries.FetchTotalAccountsCompleted(logger) | ||
if err != nil { | ||
return &_types.Response{ | ||
Body: _types.ResponseBody{ | ||
Error: errors.NewPostHogError("failed to fetch the total amount of accounts that have completed an event from posthog", err), | ||
}, | ||
Headers: headers, | ||
StatusCode: http.StatusInternalServerError, | ||
} | ||
} | ||
|
||
logger.Debug(fmt.Sprintf("received the total amount of accounts that have completed an event: %d", total)) | ||
|
||
pageCount := int(math.Ceil(float64(total / _constants.ResultLimit))) // pages are zero-based | ||
|
||
logger.Debug(fmt.Sprintf("fetching total daily event for each account from page %d of %d from posthog", page, pageCount)) | ||
|
||
results, err := _queries.FetchLeaderboardResults(page, logger) | ||
if err != nil { | ||
return &_types.Response{ | ||
Body: _types.ResponseBody{ | ||
Error: errors.NewPostHogError(fmt.Sprintf("failed to fetch the total number of completed events for page %d of %d from posthog", page, pageCount), err), | ||
}, | ||
Headers: headers, | ||
StatusCode: http.StatusInternalServerError, | ||
} | ||
} | ||
|
||
logger.Debug(fmt.Sprintf("received total number of completed events for page %d of %d from posthog", page, pageCount)) | ||
|
||
return &_types.Response{ | ||
Body: _types.ResponseBody{ | ||
Metadata: _types.PaginationMetadata{ | ||
Page: page, | ||
PageCount: pageCount, | ||
PageSize: _constants.ResultLimit, | ||
Total: total, | ||
}, | ||
Results: results, | ||
}, | ||
Headers: headers, | ||
StatusCode: http.StatusOK, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package main | ||
|
||
import ( | ||
_types "leaderboard/internal/types" | ||
"lib/types" | ||
"log" | ||
"net/http" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
err := os.Setenv("ENVIRONMENT", "test") | ||
if err != nil { | ||
log.Fatalf("failed to set \"ENVIRONMENT\" variable to \"test\"") | ||
} | ||
|
||
// run tests | ||
code := m.Run() | ||
|
||
os.Exit(code) | ||
} | ||
|
||
func TestIncorrectHTTPMethod(t *testing.T) { | ||
// arrange | ||
request := _types.Request{ | ||
Http: types.Http{ | ||
Method: http.MethodPost, | ||
}, | ||
} | ||
|
||
// act | ||
response := Main(request) | ||
|
||
// assert | ||
if response.StatusCode != http.StatusMethodNotAllowed { | ||
t.Errorf("expected \"statusCode\" to be \"%d\", got %d", http.StatusMethodNotAllowed, response.StatusCode) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.