Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
de5626814b | |||
923fdea2a1 | |||
58f77f22a4 | |||
a0d42bbbc3 | |||
7ed0e8fd88 |
34
cache/cache.go
vendored
Normal file
34
cache/cache.go
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
Get(key string) ([]byte, error)
|
||||
Set(key string, b []byte, d time.Duration) error
|
||||
}
|
||||
|
||||
func ForURI(uri string) Provider {
|
||||
u, err := url.Parse(uri)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if u.Scheme == "" && u.Path != "" {
|
||||
u.Scheme = u.Path
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "memcached":
|
||||
return NewMemcached(u)
|
||||
case "redis":
|
||||
return NewRedisCache(u)
|
||||
case "gcache":
|
||||
return NewGcache(u)
|
||||
}
|
||||
|
||||
return &NullCache{}
|
||||
}
|
47
cache/gcache.go
vendored
Normal file
47
cache/gcache.go
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/bluele/gcache"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Gcache struct {
|
||||
cache gcache.Cache
|
||||
}
|
||||
|
||||
func NewGcache(u *url.URL) *Gcache {
|
||||
size := 128
|
||||
|
||||
q := u.Query()
|
||||
|
||||
if sizeStr := q.Get("size"); sizeStr != "" {
|
||||
var err error
|
||||
size, err = strconv.Atoi(sizeStr)
|
||||
|
||||
if err != nil {
|
||||
size = 128
|
||||
}
|
||||
}
|
||||
|
||||
gc := gcache.New(size).
|
||||
LRU().
|
||||
Build()
|
||||
|
||||
return &Gcache{cache: gc}
|
||||
}
|
||||
|
||||
func (c *Gcache) Get(key string) ([]byte, error) {
|
||||
res, err := c.cache.Get(key)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.([]byte), err
|
||||
}
|
||||
|
||||
func (c *Gcache) Set(key string, b []byte, d time.Duration) error {
|
||||
return c.cache.SetWithExpire(key, b, d)
|
||||
}
|
32
cache/memcached.go
vendored
Normal file
32
cache/memcached.go
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/bradfitz/gomemcache/memcache"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Memcached struct {
|
||||
client *memcache.Client
|
||||
}
|
||||
|
||||
func NewMemcached(u *url.URL) *Memcached {
|
||||
mc := memcache.New(strings.Split(u.Host, ",")...)
|
||||
|
||||
return &Memcached{client: mc}
|
||||
}
|
||||
|
||||
func (m *Memcached) Get(key string) ([]byte, error) {
|
||||
item, err := m.client.Get(key)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return item.Value, nil
|
||||
}
|
||||
|
||||
func (m *Memcached) Set(key string, b []byte, d time.Duration) error {
|
||||
return m.client.Set(&memcache.Item{Key: key, Value: b, Expiration: int32(d.Seconds())})
|
||||
}
|
14
cache/null.go
vendored
Normal file
14
cache/null.go
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package cache
|
||||
|
||||
import "time"
|
||||
|
||||
type NullCache struct {
|
||||
}
|
||||
|
||||
func (n *NullCache) Get(key string) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *NullCache) Set(key string, b []byte, d time.Duration) error {
|
||||
return nil
|
||||
}
|
29
cache/redis.go
vendored
Normal file
29
cache/redis.go
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RedisCache struct {
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
func NewRedisCache(uri *url.URL) *RedisCache {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: uri.Host,
|
||||
Password: "", // no password set
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
return &RedisCache{client: client}
|
||||
}
|
||||
|
||||
func (c *RedisCache) Get(key string) ([]byte, error) {
|
||||
return c.client.Get(key).Bytes()
|
||||
}
|
||||
|
||||
func (c *RedisCache) Set(key string, b []byte, d time.Duration) error {
|
||||
return c.client.Set(key, b, d).Err()
|
||||
}
|
166
endpoints.go
Normal file
166
endpoints.go
Normal file
@ -0,0 +1,166 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func stats(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
data, err := statsResponse(w, ps, nil)
|
||||
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func profile(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
cacheKey := generateCacheKey(ps) + "-profile"
|
||||
|
||||
res, err := cacheProvider.Get(cacheKey)
|
||||
|
||||
if res != nil && err == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
// Cache result for profile specifically
|
||||
data, err := statsResponse(w, ps, profilePatch)
|
||||
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if cacheTime > 0 {
|
||||
cacheProvider.Set(cacheKey, data, cacheTime)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func heroes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
names := strings.Split(ps.ByName("heroes"), ",")
|
||||
|
||||
if len(names) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
writeError(w, errors.New("name list must contain at least one hero"))
|
||||
return
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
|
||||
cacheKey := generateCacheKey(ps) + "-heroes-" + hex.EncodeToString(md5.New().Sum([]byte(strings.Join(names, ","))))
|
||||
|
||||
res, err := cacheProvider.Get(cacheKey)
|
||||
|
||||
if res != nil && err == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
nameMap := make(map[string]bool)
|
||||
|
||||
for _, name := range names {
|
||||
nameMap[name] = true
|
||||
}
|
||||
|
||||
ops := make([]patchOperation, 0)
|
||||
|
||||
for _, heroName := range heroNames {
|
||||
if _, exists := nameMap[heroName]; !exists {
|
||||
ops = append(ops, patchOperation{
|
||||
Op: OpRemove,
|
||||
Path: "/quickPlayStats/topHeroes/" + heroName,
|
||||
}, patchOperation{
|
||||
Op: OpRemove,
|
||||
Path: "/quickPlayStats/careerStats/" + heroName,
|
||||
}, patchOperation{
|
||||
Op: OpRemove,
|
||||
Path: "/competitiveStats/topHeroes/" + heroName,
|
||||
}, patchOperation{
|
||||
Op: OpRemove,
|
||||
Path: "/competitiveStats/careerStats/" + heroName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
patch, err := patchFromOperations(ops)
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a patch to remove all but specified heroes
|
||||
data, err := statsResponse(w, ps, patch)
|
||||
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if cacheTime > 0 {
|
||||
cacheProvider.Set(cacheKey, data, cacheTime)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
type versionObject struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func versionHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(&versionObject{Version: Version}); err != nil {
|
||||
writeError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
type statusObject struct {
|
||||
ResponseCode int `json:"responseCode"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func statusHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
status := &statusObject{}
|
||||
|
||||
res, err := http.DefaultClient.Head("https://playoverwatch.com")
|
||||
|
||||
if err == nil {
|
||||
status.ResponseCode = res.StatusCode
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
w.WriteHeader(res.StatusCode)
|
||||
}
|
||||
} else {
|
||||
status.Error = err.Error()
|
||||
}
|
||||
|
||||
if r.Method != http.MethodHead {
|
||||
if err := json.NewEncoder(w).Encode(status); err != nil {
|
||||
writeError(w, err)
|
||||
}
|
||||
}
|
||||
}
|
2
go.mod
2
go.mod
@ -4,6 +4,8 @@ go 1.12
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.5.1-0.20190109230704-3dcf72e6c17f
|
||||
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668
|
||||
github.com/go-redis/redis v6.14.2+incompatible
|
||||
github.com/julienschmidt/httprouter v1.2.0
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -2,6 +2,10 @@ github.com/PuerkitoBio/goquery v1.5.1-0.20190109230704-3dcf72e6c17f h1:cWOyRTtBc
|
||||
github.com/PuerkitoBio/goquery v1.5.1-0.20190109230704-3dcf72e6c17f/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
||||
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
|
||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833 h1:yCfXxYaelOyqnia8F/Yng47qhmfC9nKTRIbYRrRueq4=
|
||||
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833/go.mod h1:8c4/i2VlovMO2gBnHGQPN5EJw+H0lx1u/5p+cgsXtCk=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
|
225
main.go
225
main.go
@ -4,30 +4,25 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"git.meow.tf/ow-api/ow-api/cache"
|
||||
"git.meow.tf/ow-api/ow-api/json-patch"
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/rs/cors"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"s32x.com/ovrstat/ovrstat"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Version = "2.0.11"
|
||||
Version = "2.1.3"
|
||||
|
||||
OpAdd = "add"
|
||||
OpRemove = "remove"
|
||||
)
|
||||
|
||||
type patchOperation struct {
|
||||
Op string `json:"op"`
|
||||
Path string `json:"path"`
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type gamesStats struct {
|
||||
Played int64 `json:"played"`
|
||||
Won int64 `json:"won"`
|
||||
@ -42,9 +37,13 @@ type awardsStats struct {
|
||||
}
|
||||
|
||||
var (
|
||||
flagBind = flag.String("bind-address", ":8080", "Address to bind to for http requests")
|
||||
flagBind = flag.String("bind-address", ":8080", "Address to bind to for http requests")
|
||||
flagCache = flag.String("cache", "redis://localhost:6379", "Cache uri or 'none' to disable")
|
||||
flagCacheTime = flag.Int("cacheTime", 300, "Cache time in seconds")
|
||||
|
||||
client *redis.Client
|
||||
cacheProvider cache.Provider
|
||||
|
||||
cacheTime time.Duration
|
||||
|
||||
profilePatch *jsonpatch.Patch
|
||||
|
||||
@ -54,11 +53,9 @@ var (
|
||||
func main() {
|
||||
loadHeroNames()
|
||||
|
||||
client = redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password set
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
cacheProvider = cache.ForURI(*flagCache)
|
||||
|
||||
cacheTime = time.Duration(*flagCacheTime) * time.Second
|
||||
|
||||
var err error
|
||||
|
||||
@ -81,9 +78,6 @@ func main() {
|
||||
router.GET("/v1/stats/pc/:region/:tag/heroes/:heroes", injectPlatform("pc", heroes))
|
||||
router.GET("/v1/stats/pc/:region/:tag/profile", injectPlatform("pc", profile))
|
||||
router.GET("/v1/stats/pc/:region/:tag/complete", injectPlatform("pc", stats))
|
||||
router.GET("/v1/stats/pc/:tag/heroes/:heroes", injectPlatform("pc", heroes))
|
||||
router.GET("/v1/stats/pc/:tag/profile", injectPlatform("pc", profile))
|
||||
router.GET("/v1/stats/pc/:tag/complete", injectPlatform("pc", stats))
|
||||
|
||||
// Console
|
||||
router.GET("/v1/stats/psn/:tag/heroes/:heroes", injectPlatform("psn", heroes))
|
||||
@ -96,6 +90,7 @@ func main() {
|
||||
// Version
|
||||
router.GET("/v1/version", versionHandler)
|
||||
|
||||
router.HEAD("/v1/status", statusHandler)
|
||||
router.GET("/v1/status", statusHandler)
|
||||
|
||||
c := cors.New(cors.Options{
|
||||
@ -143,6 +138,10 @@ func injectPlatform(platform string, handler httprouter.Handle) httprouter.Handl
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
tagRegexp = regexp.MustCompile("-(\\d+)$")
|
||||
)
|
||||
|
||||
func statsResponse(w http.ResponseWriter, ps httprouter.Params, patch *jsonpatch.Patch) ([]byte, error) {
|
||||
var stats *ovrstat.PlayerStats
|
||||
var err error
|
||||
@ -154,6 +153,10 @@ func statsResponse(w http.ResponseWriter, ps httprouter.Params, patch *jsonpatch
|
||||
cacheKey := generateCacheKey(ps)
|
||||
|
||||
if region := ps.ByName("region"); region != "" {
|
||||
if !tagRegexp.MatchString(tag) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil, errors.New("bad tag")
|
||||
}
|
||||
stats, err = ovrstat.PCStats(tag)
|
||||
} else if platform := ps.ByName("platform"); platform != "" {
|
||||
stats, err = ovrstat.ConsoleStats(platform, tag)
|
||||
@ -167,7 +170,7 @@ func statsResponse(w http.ResponseWriter, ps httprouter.Params, patch *jsonpatch
|
||||
|
||||
// Caching of full response for modification
|
||||
|
||||
res, err := client.Get(cacheKey).Bytes()
|
||||
res, err := cacheProvider.Get(cacheKey)
|
||||
|
||||
if res != nil && err == nil {
|
||||
if patch != nil {
|
||||
@ -250,7 +253,9 @@ func statsResponse(w http.ResponseWriter, ps httprouter.Params, patch *jsonpatch
|
||||
}
|
||||
|
||||
// Cache response
|
||||
client.Set(cacheKey, b, 10*time.Minute)
|
||||
if cacheTime > 0 {
|
||||
cacheProvider.Set(cacheKey, b, cacheTime)
|
||||
}
|
||||
|
||||
if patch != nil {
|
||||
// Apply filter patch
|
||||
@ -260,191 +265,13 @@ func statsResponse(w http.ResponseWriter, ps httprouter.Params, patch *jsonpatch
|
||||
return b, err
|
||||
}
|
||||
|
||||
func stats(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
data, err := statsResponse(w, ps, nil)
|
||||
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func profile(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
cacheKey := generateCacheKey(ps) + "-profile"
|
||||
|
||||
// Check cache for -profile to prevent jsonpatch calls
|
||||
res, err := client.Get(cacheKey).Bytes()
|
||||
|
||||
if res != nil && err == nil {
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
// Cache result for profile specifically
|
||||
data, err := statsResponse(w, ps, profilePatch)
|
||||
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
client.Set(cacheKey, data, 10*time.Minute)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func heroes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
names := strings.Split(ps.ByName("heroes"), ",")
|
||||
|
||||
if len(names) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
writeError(w, errors.New("name list must contain at least one hero"))
|
||||
return
|
||||
}
|
||||
|
||||
nameMap := make(map[string]bool)
|
||||
|
||||
for _, name := range names {
|
||||
nameMap[name] = true
|
||||
}
|
||||
|
||||
ops := make([]patchOperation, 0)
|
||||
|
||||
for _, heroName := range heroNames {
|
||||
if _, exists := nameMap[heroName]; !exists {
|
||||
ops = append(ops, patchOperation{
|
||||
Op: OpRemove,
|
||||
Path: "/quickPlayStats/topHeroes/" + heroName,
|
||||
}, patchOperation{
|
||||
Op: OpRemove,
|
||||
Path: "/quickPlayStats/careerStats/" + heroName,
|
||||
}, patchOperation{
|
||||
Op: OpRemove,
|
||||
Path: "/competitiveStats/topHeroes/" + heroName,
|
||||
}, patchOperation{
|
||||
Op: OpRemove,
|
||||
Path: "/competitiveStats/careerStats/" + heroName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
patch, err := patchFromOperations(ops)
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a patch to remove all but specified heroes
|
||||
data, err := statsResponse(w, ps, patch)
|
||||
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func valueOrDefault(m map[string]interface{}, key string, d int64) int64 {
|
||||
if v, ok := m[key]; ok {
|
||||
switch v.(type) {
|
||||
case int64:
|
||||
return v.(int64)
|
||||
case int:
|
||||
return int64(v.(int))
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func patchFromOperations(ops []patchOperation) (*jsonpatch.Patch, error) {
|
||||
patchBytes, err := json.Marshal(ops)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patch, err := jsonpatch.DecodePatch(patchBytes)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &patch, nil
|
||||
}
|
||||
|
||||
type versionObject struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func versionHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(&versionObject{Version: Version}); err != nil {
|
||||
writeError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
type statusObject struct {
|
||||
ResponseCode int `json:"responseCode"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func statusHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
status := &statusObject{}
|
||||
|
||||
res, err := http.DefaultClient.Head("https://playoverwatch.com")
|
||||
|
||||
if err == nil {
|
||||
status.ResponseCode = res.StatusCode
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
w.WriteHeader(res.StatusCode)
|
||||
}
|
||||
} else {
|
||||
status.Error = err.Error()
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(status); err != nil {
|
||||
writeError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
type errorObject struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, err error) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err == ovrstat.ErrPlayerNotFound {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(&errorObject{Error: err.Error()}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func generateCacheKey(ps httprouter.Params) string {
|
||||
var cacheKey string
|
||||
|
||||
tag := ps.ByName("tag")
|
||||
|
||||
if region := ps.ByName("region"); region != "" {
|
||||
cacheKey = "pc-" + region + "-" + tag
|
||||
cacheKey = "pc-" + tag
|
||||
} else if platform := ps.ByName("platform"); platform != "" {
|
||||
cacheKey = platform + "-" + tag
|
||||
}
|
||||
|
58
util.go
Normal file
58
util.go
Normal file
@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
jsonpatch "git.meow.tf/ow-api/ow-api/json-patch"
|
||||
"net/http"
|
||||
"s32x.com/ovrstat/ovrstat"
|
||||
)
|
||||
|
||||
func valueOrDefault(m map[string]interface{}, key string, d int64) int64 {
|
||||
if v, ok := m[key]; ok {
|
||||
switch v.(type) {
|
||||
case int64:
|
||||
return v.(int64)
|
||||
case int:
|
||||
return int64(v.(int))
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
type patchOperation struct {
|
||||
Op string `json:"op"`
|
||||
Path string `json:"path"`
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func patchFromOperations(ops []patchOperation) (*jsonpatch.Patch, error) {
|
||||
patchBytes, err := json.Marshal(ops)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patch, err := jsonpatch.DecodePatch(patchBytes)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &patch, nil
|
||||
}
|
||||
|
||||
type errorObject struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, err error) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err == ovrstat.ErrPlayerNotFound {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(&errorObject{Error: err.Error()}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user