-
Notifications
You must be signed in to change notification settings - Fork 0
/
leaderboards.go
144 lines (121 loc) · 3.58 KB
/
leaderboards.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package compiler
import (
"context"
"encoding/json"
"net/http"
"strconv"
"github.com/Squwid/bytegolf/auth"
"github.com/Squwid/bytegolf/db"
"github.com/Squwid/bytegolf/models"
"cloud.google.com/go/firestore"
"github.com/sirupsen/logrus"
"google.golang.org/api/iterator"
)
type Entry struct {
ID string
Language string
Version string
Length int64
HoleID string
BGID string
// Following fields get populated by the BGID on a request basis
GitName string
}
// GetUserFields populates the user fields based on BGID
func (e *Entry) GetUserFields() error {
getter := models.NewGet(db.ProfileCollection().Doc(e.BGID), nil)
profile, err := db.Get(getter)
if err != nil {
return err
}
e.GitName = profile["GithubUser"].(map[string]interface{})["Login"].(string)
return nil
}
// LeaderboardQuery is a wrapper query function to list leaderboard submissions for a given hole.
// Since leaderboard have to be unique by BGID, that query is handled in this function. If the limit is not
// reached it will return a slice of the users it was able to get
func LeaderboardQuery(query firestore.Query, limit int) ([]Entry, error) {
var users = make(map[string]bool) // Map users in O(1) time
var entries = []Entry{}
iter := query.Documents(context.Background())
for {
if len(entries) == limit {
break
}
doc, err := iter.Next()
if err == iterator.Done {
break
} else if err != nil {
return nil, err
}
var sub SubmissionDB
if err := doc.DataTo(&sub); err != nil {
return nil, err
}
// User already exists in the leaderboard. LB has to be unique
if users[sub.BGID] {
continue
}
users[sub.BGID] = true
entry := sub.Entry()
entry.GetUserFields()
entries = append(entries, entry)
}
return entries, nil
}
// Possible query strings:
// "hole": ID of the hole *REQUIRED*
// "limit": Limit of entry numbers. Max of 10. Defaults to 10.
// "lang": Specific language to query for. Defaults to all languages.
func LeaderboardHandler(w http.ResponseWriter, r *http.Request) {
log := logrus.WithFields(logrus.Fields{
"Action": "Leaderboard",
"IP": r.RemoteAddr,
})
claims := auth.LoggedIn(r)
if claims != nil {
log = log.WithField("User", claims.BGID)
}
// holeID querystring is
hole := r.URL.Query().Get("hole")
if hole == "" {
log.Warnf("Required querystring 'hole' missing")
w.WriteHeader(http.StatusBadRequest)
return
}
log = log.WithField("Hole", hole)
query := db.SubmissionsCollection().Where("Correct", "==", true).Where("HoleID", "==", hole).
OrderBy("Length", firestore.Asc).OrderBy("SubmittedTime", firestore.Asc)
// Parse limit query string and make sure its valid 👍
var limit = 5
if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
tLimit, err := strconv.Atoi(limitStr)
if err != nil {
log.WithError(err).Warnf("Invalid limit input '%s'", limitStr)
} else if tLimit > 10 || limit <= 0 {
log.Warnf("Invalid limit input '%v'", tLimit)
} else {
limit = tLimit
}
}
log = log.WithField("Limit", limit)
if lang := r.URL.Query().Get("lang"); lang != "" {
log = log.WithField("Lang", lang)
query = query.Where("Language", "==", lang)
}
leaders, err := LeaderboardQuery(query, limit)
if err != nil {
log.WithError(err).Errorf("Error querying for leaderboards")
w.WriteHeader(http.StatusInternalServerError)
return
}
bs, err := json.Marshal(leaders)
if err != nil {
log.WithError(err).Errorf("Error marshalling leaders")
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(bs)
log.Infof("Got %v leaders", len(leaders))
}