This commit is contained in:
Dawid Wysokiński 2020-06-01 21:05:33 +02:00
commit 75e75a43cb
15 changed files with 1195 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.env.development
.env.production
.env

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# tribalwarshelp.com cron
Required env variables:
```
DB_USER=postgres
DB_NAME=tw
DB_PORT=5432
DB_HOST=localhost
DB_PASSWORD=test123T
```

195
cron/handler.go Normal file
View File

@ -0,0 +1,195 @@
package cron
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"runtime"
"sync"
"twcron/models"
phpserialize "github.com/Kichiyaki/go-php-serialize"
"github.com/robfig/cron/v3"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/pkg/errors"
)
const (
endpointGetServers = "/backend/get_servers.php"
)
type handler struct {
db *pg.DB
}
func Attach(c *cron.Cron, db *pg.DB) error {
h := &handler{db}
if err := h.init(); err != nil {
return err
}
if _, err := c.AddFunc("@every 1h", h.updateData); err != nil {
return err
}
go h.updateData()
return nil
}
func (h *handler) init() error {
tx, err := h.db.Begin()
if err != nil {
return err
}
models := []interface{}{
(*models.LangVersion)(nil),
(*models.Server)(nil),
}
for _, model := range models {
err := tx.CreateTable(model, &orm.CreateTableOptions{
IfNotExists: true,
})
if err != nil {
return err
}
}
return tx.Commit()
}
func (h *handler) createSchema(key string) error {
tx, err := h.db.WithParam("SERVER", pg.Safe(key)).Begin()
if err != nil {
return err
}
if _, err := tx.Exec(fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", key)); err != nil {
return err
}
models := []interface{}{
(*models.Tribe)(nil),
(*models.Player)(nil),
(*models.Village)(nil),
}
for _, model := range models {
err := tx.CreateTable(model, &orm.CreateTableOptions{
IfNotExists: true,
})
if err != nil {
return err
}
}
return tx.Commit()
}
func (h *handler) getServers() ([]*models.Server, map[string]string, error) {
versions := []*models.LangVersion{}
if err := h.db.Model(&versions).Select(); err != nil {
return nil, nil, errors.Wrap(err, "getServers")
}
serverKeys := []string{}
servers := []*models.Server{}
urls := make(map[string]string)
for _, version := range versions {
resp, err := http.Get(fmt.Sprintf("https://%s%s", version.Host, endpointGetServers))
if err != nil {
log.Print(errors.Wrap(err, fmt.Sprintf("Cannot fetch servers from %s", version.Host)))
continue
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Print(errors.Wrap(err, fmt.Sprintf("Cannot read response body from %s", version.Host)))
continue
}
body, err := phpserialize.Decode(string(bodyBytes))
if err != nil {
log.Print(errors.Wrap(err, fmt.Sprintf("Cannot serialize body from %s into go value", version.Host)))
continue
}
for serverKey, url := range body.(map[interface{}]interface{}) {
serverKeyStr := serverKey.(string)
if err := h.createSchema(serverKeyStr); err != nil {
log.Print(errors.Wrap(err, fmt.Sprintf("Cannot create schema for %s", serverKey)))
continue
}
serverKeys = append(serverKeys, serverKeyStr)
urls[serverKeyStr] = url.(string)
servers = append(servers, &models.Server{
Key: serverKeyStr,
Status: models.ServerStatusOpen,
LangVersionTag: version.Tag,
LangVersion: version,
})
}
}
if _, err := h.db.Model(&servers).
OnConflict("(key) DO UPDATE").
Set("status = ?", models.ServerStatusOpen).
Set("lang_version_tag = EXCLUDED.lang_version_tag").
Returning("*").
Insert(); err != nil {
return nil, nil, err
}
if _, err := h.db.Model(&models.Server{}).
Set("status = ?", models.ServerStatusClose).
Where("key NOT IN (?)", pg.In(serverKeys)).
Update(); err != nil {
return nil, nil, err
}
return servers, urls, nil
}
func (h *handler) updateData() {
servers, urls, err := h.getServers()
if err != nil {
log.Println(err.Error())
return
}
var mutex sync.Mutex
var wg sync.WaitGroup
max := runtime.NumCPU() * 5
count := 0
for _, server := range servers {
url, ok := urls[server.Key]
if !ok {
log.Printf("No one URL associated with key: %s in a map, skipping...", server.Key)
continue
}
if count >= max {
wg.Wait()
count = 0
}
sh := &serverHandler{
db: h.db.WithParam("SERVER", pg.Safe(server.Key)),
baseURL: url,
}
mutex.Lock()
count++
mutex.Unlock()
log.Println("COUNT", count)
go func(server *models.Server, sh *serverHandler) {
wg.Add(1)
if err := sh.updateData(); err != nil {
log.Println(err.Error())
} else {
log.Printf("%s updated", server.Key)
}
wg.Done()
}(server, sh)
}
wg.Wait()
}

29
cron/helpers.go Normal file
View File

@ -0,0 +1,29 @@
package cron
import (
"compress/gzip"
"encoding/csv"
"io"
"net/http"
)
func uncompressAndGetCsvLines(r io.Reader) ([][]string, error) {
uncompressedStream, err := gzip.NewReader(r)
if err != nil {
return [][]string{}, err
}
defer uncompressedStream.Close()
return csv.NewReader(uncompressedStream).ReadAll()
}
func getCSVData(url string, compressed bool) ([][]string, error) {
resp, err := http.Get(url)
if err != nil {
return [][]string{}, nil
}
defer resp.Body.Close()
if !compressed {
return csv.NewReader(resp.Body).ReadAll()
}
return uncompressAndGetCsvLines(resp.Body)
}

393
cron/server_handler.go Normal file
View File

@ -0,0 +1,393 @@
package cron
import (
"fmt"
"net/url"
"strconv"
"twcron/models"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/pkg/errors"
)
const (
endpointPlayers = "/map/player.txt"
endpointTribe = "/map/ally.txt"
endpointVillage = "/map/village.txt"
endpointKillAtt = "/map/kill_att.txt"
endpointKillDef = "/map/kill_def.txt"
endpointKillSup = "/map/kill_sup.txt"
endpointKillAll = "/map/kill_all.txt"
endpointKillAttTribe = "/map/kill_att_tribe.txt"
endpointKillDefTribe = "/map/kill_def_tribe.txt"
endpointKillAllTribe = "/map/kill_all_tribe.txt"
)
type serverHandler struct {
baseURL string
db *pg.DB
}
type parsedODLine struct {
ID int
Rank int
Score int
}
func (h *serverHandler) parseODLine(line []string) (*parsedODLine, error) {
if len(line) != 3 {
return nil, fmt.Errorf("Invalid line format (should be rank,id,score)")
}
p := &parsedODLine{}
var err error
p.Rank, err = strconv.Atoi(line[0])
if err != nil {
return nil, errors.Wrap(err, "parsedODLine.Rank")
}
p.ID, err = strconv.Atoi(line[1])
if err != nil {
return nil, errors.Wrap(err, "parsedODLine.ID")
}
p.Score, err = strconv.Atoi(line[2])
if err != nil {
return nil, errors.Wrap(err, "parsedODLine.Score")
}
return p, nil
}
func (h *serverHandler) getOD(tribe bool) (map[int]*models.OpponentsDefeated, error) {
m := make(map[int]*models.OpponentsDefeated)
urls := []string{
fmt.Sprintf("%s%s", h.baseURL, endpointKillAll),
fmt.Sprintf("%s%s", h.baseURL, endpointKillAtt),
fmt.Sprintf("%s%s", h.baseURL, endpointKillDef),
fmt.Sprintf("%s%s", h.baseURL, endpointKillSup),
}
if tribe {
urls = []string{
fmt.Sprintf("%s%s", h.baseURL, endpointKillAllTribe),
fmt.Sprintf("%s%s", h.baseURL, endpointKillAttTribe),
fmt.Sprintf("%s%s", h.baseURL, endpointKillDefTribe),
}
}
for _, url := range urls {
lines, err := getCSVData(url, false)
if err != nil {
return nil, errors.Wrapf(err, "unable to get data, url %s", url)
}
for _, line := range lines {
parsed, err := h.parseODLine(line)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse line, url %s", url)
}
if _, ok := m[parsed.ID]; !ok {
m[parsed.ID] = &models.OpponentsDefeated{}
}
if url == urls[0] {
m[parsed.ID].RankTotal = parsed.Rank
m[parsed.ID].ScoreTotal = parsed.Score
} else if url == urls[1] {
m[parsed.ID].RankAtt = parsed.Rank
m[parsed.ID].ScoreAtt = parsed.Score
} else if url == urls[2] {
m[parsed.ID].RankDef = parsed.Rank
m[parsed.ID].ScoreDef = parsed.Score
} else if !tribe && url == urls[3] {
m[parsed.ID].RankSup = parsed.Rank
m[parsed.ID].ScoreSup = parsed.Score
}
}
}
return m, nil
}
func (h *serverHandler) parsePlayerLine(line []string) (*models.Player, error) {
if len(line) != 6 {
return nil, fmt.Errorf("Invalid line format (should be id,name,tribeid,villages,points,rank)")
}
var err error
ex := true
player := &models.Player{
Exist: &ex,
}
player.ID, err = strconv.Atoi(line[0])
if err != nil {
return nil, errors.Wrap(err, "player.ID")
}
player.Name, err = url.QueryUnescape(line[1])
if err != nil {
return nil, errors.Wrap(err, "player.Name")
}
player.TribeID, err = strconv.Atoi(line[2])
if err != nil {
return nil, errors.Wrap(err, "player.TribeID")
}
player.TotalVillages, err = strconv.Atoi(line[3])
if err != nil {
return nil, errors.Wrap(err, "player.TotalVillages")
}
player.Points, err = strconv.Atoi(line[4])
if err != nil {
return nil, errors.Wrap(err, "player.Points")
}
player.Rank, err = strconv.Atoi(line[5])
if err != nil {
return nil, errors.Wrap(err, "player.Rank")
}
return player, nil
}
func (h *serverHandler) getPlayers(od map[int]*models.OpponentsDefeated) ([]*models.Player, error) {
url := h.baseURL + endpointPlayers
lines, err := getCSVData(url, false)
if err != nil {
return nil, errors.Wrapf(err, "unable to get data, url %s", url)
}
players := []*models.Player{}
for _, line := range lines {
player, err := h.parsePlayerLine(line)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse line, url %s", url)
}
playerOD, ok := od[player.ID]
if ok {
player.OpponentsDefeated = *playerOD
}
players = append(players, player)
}
return players, nil
}
func (h *serverHandler) parseTribeLine(line []string) (*models.Tribe, error) {
if len(line) != 8 {
return nil, fmt.Errorf("Invalid line format (should be id,name,tag,members,villages,points,allpoints,rank)")
}
var err error
ex := true
tribe := &models.Tribe{
Exist: &ex,
}
tribe.ID, err = strconv.Atoi(line[0])
if err != nil {
return nil, errors.Wrap(err, "tribe.ID")
}
tribe.Name, err = url.QueryUnescape(line[1])
if err != nil {
return nil, errors.Wrap(err, "tribe.Name")
}
tribe.Tag, err = url.QueryUnescape(line[2])
if err != nil {
return nil, errors.Wrap(err, "tribe.Tag")
}
tribe.TotalMembers, err = strconv.Atoi(line[3])
if err != nil {
return nil, errors.Wrap(err, "tribe.TotalMembers")
}
tribe.TotalVillages, err = strconv.Atoi(line[4])
if err != nil {
return nil, errors.Wrap(err, "tribe.TotalVillages")
}
tribe.Points, err = strconv.Atoi(line[5])
if err != nil {
return nil, errors.Wrap(err, "tribe.Points")
}
tribe.AllPoints, err = strconv.Atoi(line[6])
if err != nil {
return nil, errors.Wrap(err, "tribe.AllPoints")
}
tribe.Rank, err = strconv.Atoi(line[7])
if err != nil {
return nil, errors.Wrap(err, "tribe.Rank")
}
return tribe, nil
}
func (h *serverHandler) getTribes(od map[int]*models.OpponentsDefeated) ([]*models.Tribe, error) {
url := h.baseURL + endpointTribe
lines, err := getCSVData(url, false)
if err != nil {
return nil, errors.Wrapf(err, "unable to get data, url %s", url)
}
tribes := []*models.Tribe{}
for _, line := range lines {
tribe, err := h.parseTribeLine(line)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse line, url %s", url)
}
tribeOD, ok := od[tribe.ID]
if ok {
tribe.OpponentsDefeated = *tribeOD
}
tribes = append(tribes, tribe)
}
return tribes, nil
}
func (h *serverHandler) parseVillageLine(line []string) (*models.Village, error) {
if len(line) != 7 {
return nil, fmt.Errorf("Invalid line format (should be id,name,x,y,playerID,points,bonus)")
}
var err error
village := &models.Village{}
village.ID, err = strconv.Atoi(line[0])
if err != nil {
return nil, errors.Wrap(err, "village.ID")
}
village.Name, err = url.QueryUnescape(line[1])
if err != nil {
return nil, errors.Wrap(err, "village.Name")
}
village.X, err = strconv.Atoi(line[2])
if err != nil {
return nil, errors.Wrap(err, "village.X")
}
village.Y, err = strconv.Atoi(line[3])
if err != nil {
return nil, errors.Wrap(err, "village.Y")
}
village.PlayerID, err = strconv.Atoi(line[4])
if err != nil {
return nil, errors.Wrap(err, "village.PlayerID")
}
village.Points, err = strconv.Atoi(line[5])
if err != nil {
return nil, errors.Wrap(err, "village.Points")
}
village.Bonus, err = strconv.Atoi(line[6])
if err != nil {
return nil, errors.Wrap(err, "village.Bonus")
}
return village, nil
}
func (h *serverHandler) getVillages() ([]*models.Village, error) {
url := h.baseURL + endpointVillage
lines, err := getCSVData(url, false)
if err != nil {
return nil, errors.Wrapf(err, "unable to get data, url %s", url)
}
villages := []*models.Village{}
for _, line := range lines {
village, err := h.parseVillageLine(line)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse line, url %s", url)
}
villages = append(villages, village)
}
return villages, nil
}
func (h *serverHandler) updateData() error {
pod, err := h.getOD(false)
if err != nil {
return err
}
tod, err := h.getOD(true)
if err != nil {
return err
}
tribes, err := h.getTribes(tod)
if err != nil {
return err
}
players, err := h.getPlayers(pod)
if err != nil {
return err
}
villages, err := h.getVillages()
if err != nil {
return err
}
tx, err := h.db.Begin()
if err != nil {
return err
}
if _, err := tx.Model(&tribes).
OnConflict("(id) DO UPDATE").
Set("name = EXCLUDED.name").
Set("tag = EXCLUDED.tag").
Set("total_members = EXCLUDED.total_members").
Set("total_villages = EXCLUDED.total_villages").
Set("points = EXCLUDED.points").
Set("rank = EXCLUDED.rank").
Set("exist = EXCLUDED.exist").
Apply(attachODSetClauses).
Insert(); err != nil {
return errors.Wrap(err, "cannot insert tribes")
}
if _, err := tx.Model(&players).
OnConflict("(id) DO UPDATE").
Set("name = EXCLUDED.name").
Set("total_villages = EXCLUDED.total_villages").
Set("points = EXCLUDED.points").
Set("rank = EXCLUDED.rank").
Set("exist = EXCLUDED.exist").
Set("tribe_id = EXCLUDED.tribe_id").
Apply(attachODSetClauses).
Insert(); err != nil {
return errors.Wrap(err, "cannot insert players")
}
if _, err := tx.Model(&villages).
OnConflict("(id) DO UPDATE").
Set("name = EXCLUDED.name").
Set("points = EXCLUDED.points").
Set("x = EXCLUDED.x").
Set("y = EXCLUDED.y").
Set("bonus = EXCLUDED.bonus").
Set("player_id = EXCLUDED.player_id").
Insert(); err != nil {
return errors.Wrap(err, "cannot insert villages")
}
ids := []int{}
for _, tribe := range tribes {
ids = append(ids, tribe.ID)
}
if _, err := tx.Model(&models.Tribe{}).
Where("id NOT IN (?)", pg.In(ids)).
Set("exist = false").
Update(); err != nil {
return fmt.Errorf("cannot update not existed tribes")
}
ids = []int{}
for _, player := range players {
ids = append(ids, player.ID)
}
if _, err := tx.Model(&models.Player{}).
Where("id NOT IN (?)", pg.In(ids)).
Set("exist = false").
Update(); err != nil {
return fmt.Errorf("cannot update not existed players")
}
ids = []int{}
for _, village := range villages {
ids = append(ids, village.ID)
}
if _, err := tx.Model(&models.Village{}).
Where("id NOT IN (?)", pg.In(ids)).
Delete(); err != nil {
return fmt.Errorf("cannot delete not existed villages")
}
return tx.Commit()
}
func attachODSetClauses(q *orm.Query) (*orm.Query, error) {
return q.Set("rank_att = EXCLUDED.rank_att").
Set("score_att = EXCLUDED.score_att").
Set("rank_def = EXCLUDED.rank_def").
Set("score_def = EXCLUDED.score_def").
Set("rank_sup = EXCLUDED.rank_sup").
Set("score_sup = EXCLUDED.score_sup").
Set("rank_total = EXCLUDED.rank_total").
Set("score_total = EXCLUDED.score_total"),
nil
}

12
go.mod Normal file
View File

@ -0,0 +1,12 @@
module twcron
go 1.14
require (
github.com/Kichiyaki/go-php-serialize v0.0.0-20200601110855-47b6982acf83
github.com/go-pg/pg/v10 v10.0.0-beta.1
github.com/joho/godotenv v1.3.0
github.com/pkg/errors v0.9.1
github.com/robfig/cron/v3 v3.0.1
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b // indirect
)

200
go.sum Normal file
View File

@ -0,0 +1,200 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7 h1:qELHH0AWCvf98Yf+CNIJx9vOZOfHFDDzgDRYsnNk/vs=
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
github.com/Kichiyaki/go-php-serialize v0.0.0-20200601110855-47b6982acf83 h1:Oa8Bk4LNcknxw50gZOlvPwEreOlAbOnu7V82lUYNbOM=
github.com/Kichiyaki/go-php-serialize v0.0.0-20200601110855-47b6982acf83/go.mod h1:+iGkf5HfOVeRVd9K7qQDucIl+/Kt3MyenMa90b/O/c4=
github.com/benbjohnson/clock v1.0.0 h1:78Jk/r6m4wCi6sndMpty7A//t4dw/RW5fV4ZgDVfX1w=
github.com/benbjohnson/clock v1.0.0/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/codemodus/kace v0.5.1 h1:4OCsBlE2c/rSJo375ggfnucv9eRzge/U5LrrOZd47HA=
github.com/codemodus/kace v0.5.1/go.mod h1:coddaHoX1ku1YFSe4Ip0mL9kQjJvKkzb9CfIdG1YR04=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-pg/pg/v10 v10.0.0-beta.1 h1:GD65aZVx9yR6fxxXdglBlook89oxxg2FYE11TDd9Now=
github.com/go-pg/pg/v10 v10.0.0-beta.1/go.mod h1:JYxBTzIz9dpSAa+bphD1U7A/PBCwiuw1o8pYNDSsQ+4=
github.com/go-pg/pg/v9 v9.0.0-beta.14/go.mod h1:T2Sr6bpTCOr2lUqOUMiXLMJqZHSUBKk1LdgSqjwhZfA=
github.com/go-pg/pg/v9 v9.0.3/go.mod h1:Tm/Q3Vt6gdQOH6TTN1H/xLlIXc+Qrka7TZ6uREtu/eA=
github.com/go-pg/pg/v9 v9.1.6 h1:IqBayenvp9EWjHncRE7//SRmQuktq60oeO1/MkEx3dY=
github.com/go-pg/pg/v9 v9.1.6/go.mod h1:QM13HBLkdml4zcKOfUfGLymM6hb72aKTJLrmaH8rsFg=
github.com/go-pg/urlstruct v0.1.0/go.mod h1:2Nag+BIny6G/KYCkdt++ZnqU/VinzimGapKfs4kwlN0=
github.com/go-pg/urlstruct v0.2.6/go.mod h1:dxENwVISWSOX+k87hDt0ueEJadD+gZWv3tHzwfmZPu8=
github.com/go-pg/urlstruct v0.3.0/go.mod h1:/XKyiUOUUS3onjF+LJxbfmSywYAdl6qMfVbX33Q8rgg=
github.com/go-pg/urlstruct v0.4.0 h1:3lmbUGYQclB3UOx9akDs2T251zwkKQuPkvPTmCm07+A=
github.com/go-pg/urlstruct v0.4.0/go.mod h1:/XKyiUOUUS3onjF+LJxbfmSywYAdl6qMfVbX33Q8rgg=
github.com/go-pg/zerochecker v0.1.1 h1:av77Qe7Gs+1oYGGh51k0sbZ0bUaxJEdeP0r8YE64Dco=
github.com/go-pg/zerochecker v0.1.1/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/segmentio/encoding v0.1.10/go.mod h1:RWhr02uzMB9gQC1x+MfYxedtmBibb9cZ6Vv9VxRSSbw=
github.com/segmentio/encoding v0.1.12 h1:SwIDXReTDnlYqOcLachzJEczAEihST7Mx7nGlAWCJ3Q=
github.com/segmentio/encoding v0.1.12/go.mod h1:RWhr02uzMB9gQC1x+MfYxedtmBibb9cZ6Vv9VxRSSbw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/vmihailenco/bufpool v0.1.5/go.mod h1:fL9i/PRTuS7AELqAHwSU1Zf1c70xhkhGe/cD5ud9pJk=
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
github.com/vmihailenco/msgpack/v4 v4.3.5/go.mod h1:DuaveEe48abshDmz5UBKyZ+yDugvaeFk5ayfrewUOaw=
github.com/vmihailenco/msgpack/v4 v4.3.7/go.mod h1:Ii+PksJlvFT5ZRcB/4YLAInMIp6a0WOCm0L3BU0aNG4=
github.com/vmihailenco/msgpack/v4 v4.3.11 h1:Q47CePddpNGNhk4GCnAx9DDtASi2rasatE0cd26cZoE=
github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/msgpack/v5 v5.0.0-alpha.2 h1:0jVpYJSRJzGY7m21n9V5uIkl7Zre64W8DR1dxEKX2g4=
github.com/vmihailenco/msgpack/v5 v5.0.0-alpha.2/go.mod h1:LDfrk4wJpSFwkzNOJxrCWiSm8c7Iqw/hXNPT2fzQfE8=
github.com/vmihailenco/tagparser v0.1.0/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
go.opentelemetry.io/otel v0.6.0 h1:+vkHm/XwJ7ekpISV2Ixew93gCrxTbuwTF5rSewnLLgw=
go.opentelemetry.io/otel v0.6.0/go.mod h1:jzBIgIzK43Iu1BpDAXwqOd6UPsSAk+ewVZ5ofSXw4Ek=
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222033325-078779b8f2d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b h1:IYiJPiJfzktmDAO1HQiwjMjwjlYKHAL7KzeD544RJPs=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w=
mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ=

53
main.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"log"
"os"
"os/signal"
"syscall"
_cron "twcron/cron"
"twcron/mode"
"github.com/go-pg/pg/v10"
"github.com/joho/godotenv"
"github.com/robfig/cron/v3"
)
func init() {
os.Setenv("TZ", "UTC")
if mode.Get() == mode.DevelopmentMode {
godotenv.Load(".env.development")
}
}
func main() {
db := pg.Connect(&pg.Options{
User: os.Getenv("DB_USER"),
Password: os.Getenv("DB_PASSWORD"),
Database: os.Getenv("DB_NAME"),
Addr: os.Getenv("DB_HOST") + ":" + os.Getenv("DB_PORT"),
})
defer func() {
if err := db.Close(); err != nil {
log.Fatal(err)
}
}()
c := cron.New(cron.WithChain(
cron.SkipIfStillRunning(cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))),
))
if err := _cron.Attach(c, db); err != nil {
log.Fatal(err)
}
c.Start()
defer c.Stop()
log.Print("Cron is running!")
channel := make(chan os.Signal, 1)
signal.Notify(channel, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGINT)
<-channel
log.Print("shutting down")
}

35
mode/mode.go Normal file
View File

@ -0,0 +1,35 @@
package mode
import "os"
const (
EnvMode = "mode"
DevelopmentMode = "development"
ProductionMode = "production"
TestMode = "test"
)
var mode = DevelopmentMode
func init() {
Set(os.Getenv(EnvMode))
}
func Set(value string) {
if value == "" {
value = DevelopmentMode
}
switch value {
case DevelopmentMode,
ProductionMode,
TestMode:
mode = value
default:
panic("unknown mode: " + value)
}
}
func Get() string {
return mode
}

10
models/lang_version.go Normal file
View File

@ -0,0 +1,10 @@
package models
type LanguageTag string
type LangVersion struct {
Tag LanguageTag `pg:",pk" json:"tag" gqlgen:"tag"`
Name string `json:"name" gqlgen:"name" pg:",unique"`
Host string `json:"host" gqlgen:"host"`
Timezone string `json:"timezone" gqlgen:"timezone"`
}

View File

@ -0,0 +1,55 @@
package models
type OpponentsDefeated struct {
RankAtt int `json:"rankAtt" pg:",use_zero" gqlgen:"rankAtt"`
ScoreAtt int `pg:",use_zero" json:"scoreAtt" gqlgen:"scoreAtt"`
RankDef int `pg:",use_zero" json:"rankDef" gqlgen:"rankDef"`
ScoreDef int `pg:",use_zero" json:"scoreDef" gqlgen:"scoreDef"`
RankTotal int `pg:",use_zero" json:"rankTotal" gqlgen:"rankTotal"`
ScoreTotal int `pg:",use_zero" json:"scoreTotal" gqlgen:"scoreTotal"`
RankSup int `pg:",use_zero" json:"rankSup" gqlgen:"rankSup"`
ScoreSup int `pg:",use_zero" json:"scoreSup" gqlgen:"scoreSup"`
}
type OpponentsDefeatedFilter struct {
RankAtt int `json:"rankAtt" gqlgen:"rankAtt"`
RankAttGT int `json:"rankAttGT" gqlgen:"rankAttGT"`
RankAttGTE int `json:"rankAttGTE" gqlgen:"rankAttGTE"`
RankAttLT int `json:"rankAttLT" gqlgen:"rankAttLT"`
RankAttLTE int `json:"rankAttLTE" gqlgen:"rankAttLTE"`
ScoreAtt int `json:"scoreAtt" gqlgen:"scoreAtt"`
ScoreAttGT int `json:"scoreAttGT" gqlgen:"scoreAttGT"`
ScoreAttGTE int `json:"scoreAttGTE" gqlgen:"scoreAttGTE"`
ScoreAttLT int `json:"scoreAttLT" gqlgen:"scoreAttLT"`
ScoreAttLTE int `json:"scoreAttLTE" gqlgen:"scoreAttLTE"`
RankDef int `json:"rankDef" gqlgen:"rankDef"`
RankDefGT int `json:"rankDefGT" gqlgen:"rankDefGT"`
RankDefGTE int `json:"rankDefGTE" gqlgen:"rankDefGTE"`
RankDefLT int `json:"rankDefLT" gqlgen:"rankDefLT"`
RankDefLTE int `json:"rankDefLTE" gqlgen:"rankDefLTE"`
ScoreDef int `json:"scoreDef" gqlgen:"scoreDef"`
ScoreDefGT int `json:"scoreDefGT" gqlgen:"scoreDefGT"`
ScoreDefGTE int `json:"scoreDefGTE" gqlgen:"scoreDefGTE"`
ScoreDefLT int `json:"scoreDefLT" gqlgen:"scoreDefLT"`
ScoreDefLTE int `json:"scoreDefLTE" gqlgen:"scoreDefLTE"`
RankTotal int `json:"rankTotal" gqlgen:"rankTotal"`
RankTotalGT int `json:"rankTotalGT" gqlgen:"rankTotalGT"`
RankTotalGTE int `json:"rankTotalGTE" gqlgen:"rankTotalGTE"`
RankTotalLT int `json:"rankTotalLT" gqlgen:"rankTotalLT"`
RankTotalLTE int `json:"rankTotalLTE" gqlgen:"rankTotalLTE"`
ScoreTotal int `json:"scoreTotal" gqlgen:"scoreTotal"`
ScoreTotalGT int `json:"scoreTotalGT" gqlgen:"scoreTotalGT"`
ScoreTotalGTE int `json:"scoreTotalGTE" gqlgen:"scoreTotalGTE"`
ScoreTotalLT int `json:"scoreTotalLT" gqlgen:"scoreTotalLT"`
ScoreTotalLTE int `json:"scoreTotalLTE" gqlgen:"scoreTotalLTE"`
ScoreSup int `json:"scoreSup" gqlgen:"scoreSup"`
ScoreSupGT int `json:"scoreSupGT" gqlgen:"scoreSupGT"`
ScoreSupGTE int `json:"scoreSupGTE" gqlgen:"scoreSupGTE"`
ScoreSupLT int `json:"scoreSupLT" gqlgen:"scoreSupLT"`
ScoreSupLTE int `json:"scoreSupLTE" gqlgen:"scoreSupLTE"`
RankSup int `json:"rankSup" gqlgen:"rankSup"`
RankSupGT int `json:"rankSupGT" gqlgen:"rankSupGT"`
RankSupGTE int `json:"rankSupGTE" gqlgen:"rankSupGTE"`
RankSupLT int `json:"rankSupLT" gqlgen:"rankSupLT"`
RankSupLTE int `json:"rankSupLTE" gqlgen:"rankSupLTE"`
}

48
models/player.go Normal file
View File

@ -0,0 +1,48 @@
package models
type Player struct {
tableName struct{} `pg:"?SERVER.players,alias:player"`
ID int `json:"id" pg:",pk" gqlgen:"id"`
Name string `json:"name" gqlgen:"name"`
TotalVillages int `json:"total_villages" pg:",use_zero" gqlgen:"totalVillages"`
Points int `json:"points" pg:",use_zero" gqlgen:"points"`
Rank int `json:"rank" pg:",use_zero" gqlgen:"rank"`
Exist *bool `json:"exist" pg:",use_zero" gqlgen:"exist"`
TribeID int `json:"-" pg:",use_zero" gqlgen:"tribeID"`
Tribe *Tribe `json:"tribe,omitempty" gqlgen:"-"`
OpponentsDefeated
}
type PlayerFilter struct {
tableName struct{} `urlstruct:"player"`
ID []int `json:"id" gqlgen:"id"`
IdNEQ []int `json:"idNEQ" gqlgen:"idNEQ"`
Exist *bool `urlstruct:",nowhere" json:"exist" gqlgen:"exist"`
Name []string `json:"name" gqlgen:"name"`
NameNEQ []string `json:"nameNEQ" gqlgen:"nameNEQ"`
NameMATCH string `json:"nameMATCH" gqlgen:"nameMATCH"`
NameIEQ string `json:"nameIEQ" gqlgen:"nameIEQ"`
TotalVillages int `json:"totalVillages" gqlgen:"totalVillages"`
TotalVillagesGT int `json:"totalVillagesGT" gqlgen:"totalVillagesGT"`
TotalVillagesLT int `json:"totalVillagesLT" gqlgen:"totalVillagesLT"`
TotalVillagesLTE int `json:"totalVillagesLTE" gqlgen:"totalVillagesLTE"`
Points int `json:"points" gqlgen:"points"`
PointsGT int `json:"pointsGT" gqlgen:"pointsGT"`
PointsLT int `json:"pointsLT" gqlgen:"pointsLT"`
PointsLTE int `json:"pointsLTE" gqlgen:"pointsLTE"`
Rank int `json:"rank" gqlgen:"rank"`
RankGT int `json:"rankGT" gqlgen:"rankGT"`
RankLT int `json:"rankLT" gqlgen:"rankLT"`
RankLTE int `json:"rankLTE" gqlgen:"rankLTE"`
TribeID []int `json:"tribe" gqlgen:"tribe"`
OpponentsDefeatedFilter
}

41
models/server.go Normal file
View File

@ -0,0 +1,41 @@
package models
type ServerStatus string
const (
ServerStatusOpen = "open"
ServerStatusClose = "close"
)
type Server struct {
tableName struct{} `pg:"alias:server"`
ID int `json:"id" gqlgen:"id"`
Key string `json:"key" gqlgen:"key" pg:",unique"`
Status ServerStatus `json:"status" gqlgen:"status"`
LangVersionTag LanguageTag `json:"langVersionTag" gqlgen:"langVersionTag"`
LangVersion *LangVersion `json:"langVersion,omitempty" gqlgen:"-"`
}
type ServerFilter struct {
tableName struct{} `urlstruct:"server"`
ID []int `json:"id" gqlgen:"id"`
IdNEQ []int `json:"idNEQ" gqlgen:"idNEQ"`
Key []string `json:"key" gqlgen:"key"`
KeyNEQ []string `json:"keyNEQ" gqlgen:"keyNEQ"`
KeyMATCH string `json:"keyMATCH" gqlgen:"keyMATCH"`
KeyIEQ string `json:"keyIEQ" gqlgen:"keyIEQ"`
Status []string `json:"status" gqlgen:"status"`
StatusNIN []string `json:"statusNIN" gqlgen:"statusNIN"`
LangVersionTag []string `json:"langVersionTag" gqlgen:"langVersionTag"`
LangVersionTagNIN []string `json:"langVersionTagNIN" gqlgen:"langVersionTagNIN"`
Offset int `urlstruct:",nowhere" json:"offset" gqlgen:"offset"`
Limit int `urlstruct:",nowhere" json:"limit" gqlgen:"limit"`
Sort string `urlstruct:",nowhere" json:"sort" gqlgen:"sort"`
}

67
models/tribe.go Normal file
View File

@ -0,0 +1,67 @@
package models
type Tribe struct {
tableName struct{} `pg:"?SERVER.tribes,alias:tribe"`
ID int `json:"id" gqlgen:"id"`
Name string `json:"name" gqlgen:"name"`
Tag string `json:"tag" gqlgen:"tag"`
TotalMembers int `json:"totalMembers" gqlgen:"totalMembers" pg:",use_zero"`
TotalVillages int `json:"totalVillages" gqlgen:"totalVillages" pg:",use_zero"`
Points int `json:"points" gqlgen:"points" pg:",use_zero"`
AllPoints int `json:"allPoints" gqlgen:"allPoints" pg:",use_zero"`
Rank int `json:"rank" gqlgen:"rank" pg:",use_zero"`
Exist *bool `json:"exist" gqlgen:"exist" pg:",use_zero"`
OpponentsDefeated
}
type TribeFilter struct {
tableName struct{} `urlstruct:"tribe"`
ID []int `json:"id" gqlgen:"id"`
IdNEQ []int `json:"idNEQ" gqlgen:"idNEQ"`
Exist *bool `urlstruct:",nowhere" json:"exist" gqlgen:"exist"`
Tag []string `json:"tag" gqlgen:"tag"`
TagNEQ []string `json:"tagNEQ" gqlgen:"tagNEQ"`
TagMATCH string `json:"tagMATCH" gqlgen:"tagMATCH"`
TagIEQ string `json:"tagIEQ" gqlgen:"tagIEQ"`
Name []string `json:"name" gqlgen:"name"`
NameNEQ []string `json:"nameNEQ" gqlgen:"nameNEQ"`
NameMATCH string `json:"nameMATCH" gqlgen:"nameMATCH"`
NameIEQ string `json:"nameIEQ" gqlgen:"nameIEQ"`
TotalMembers int `json:"totalMembers" gqlgen:"totalMembers"`
TotalMembersGT int `json:"totalMembersGT" gqlgen:"totalMembersGT"`
TotalMembersLT int `json:"totalMembersLT" gqlgen:"totalMembersLT"`
TotalMembersLTE int `json:"totalMembersLTE" gqlgen:"totalMembersLTE"`
TotalVillages int `json:"totalVillages" gqlgen:"totalVillages"`
TotalVillagesGT int `json:"totalVillagesGT" gqlgen:"totalVillagesGT"`
TotalVillagesLT int `json:"totalVillagesLT" gqlgen:"totalVillagesLT"`
TotalVillagesLTE int `json:"totalVillagesLTE" gqlgen:"totalVillagesLTE"`
Points int `json:"points" gqlgen:"points"`
PointsGT int `json:"pointsGT" gqlgen:"pointsGT"`
PointsLT int `json:"pointsLT" gqlgen:"pointsLT"`
PointsLTE int `json:"pointsLTE" gqlgen:"pointsLTE"`
AllPoints int `json:"allPoints" gqlgen:"allPoints"`
AllPointsGT int `json:"allPointsGT" gqlgen:"allPointsGT"`
AllPointsLT int `json:"allPointsLT" gqlgen:"allPointsLT"`
AllPointsLTE int `json:"allPointsLTE" gqlgen:"allPointsLTE"`
Rank int `json:"rank" gqlgen:"rank"`
RankGT int `json:"rankGT" gqlgen:"rankGT"`
RankLT int `json:"rankLT" gqlgen:"rankLT"`
RankLTE int `json:"rankLTE" gqlgen:"rankLTE"`
Offset int `urlstruct:",nowhere" json:"offset" gqlgen:"offset"`
Limit int `urlstruct:",nowhere" json:"limit" gqlgen:"limit"`
Sort string `urlstruct:",nowhere" json:"sort" gqlgen:"sort"`
OpponentsDefeatedFilter
}

43
models/village.go Normal file
View File

@ -0,0 +1,43 @@
package models
type Village struct {
tableName struct{} `pg:"?SERVER.villages,alias:village"`
ID int `json:"id" pg:",pk" gqlgen:"id"`
Name string `json:"name" gqlgen:"name"`
Points int `json:"points" pg:",use_zero" gqlgen:"points"`
X int `json:"x" pg:",use_zero" gqlgen:"x"`
Y int `json:"y" pg:",use_zero" gqlgen:"y"`
Bonus int `json:"bonus" pg:",use_zero" gqlgen:"bonus"`
PlayerID int `json:"-" pg:",use_zero" gqlgen:"playerID"`
Player *Player `json:"player,omitempty" gqlgen:"-"`
}
type VillageFilter struct {
tableName struct{} `urlstruct:"village"`
ID []int `json:"id" gqlgen:"id"`
IdNEQ []int `json:"idNEQ" gqlgen:"idNEQ"`
Name []string `json:"name" gqlgen:"name"`
NameNEQ []string `json:"nameNEQ" gqlgen:"nameNEQ"`
NameMATCH string `json:"nameMATCH" gqlgen:"nameMATCH"`
NameIEQ string `json:"nameIEQ" gqlgen:"nameIEQ"`
Points int `json:"points" gqlgen:"points"`
PointsGT int `json:"pointsGT" gqlgen:"pointsGT"`
PointsLT int `json:"pointsLT" gqlgen:"pointsLT"`
PointsLTE int `json:"pointsLTE" gqlgen:"pointsLTE"`
Bonus int `json:"bonus" gqlgen:"bonus"`
BonusGT int `json:"bonusGT" gqlgen:"bonusGT"`
BonusLT int `json:"bonusLT" gqlgen:"bonusLT"`
BonusLTE int `json:"bonusLTE" gqlgen:"bonusLTE"`
PlayerID []int `json:"playerID" gqlgen:"playerID"`
Offset int `urlstruct:",nowhere" json:"offset" gqlgen:"offset"`
Limit int `urlstruct:",nowhere" json:"limit" gqlgen:"limit"`
Sort string `urlstruct:",nowhere" json:"sort" gqlgen:"sort"`
}