commit 75e75a43cb9acfc68ad14e46dbe8e43109f576fe Author: Kichiyaki Date: Mon Jun 1 21:05:33 2020 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d44addc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env.development +.env.production +.env \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7aea9a9 --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/cron/handler.go b/cron/handler.go new file mode 100644 index 0000000..e6ed4d9 --- /dev/null +++ b/cron/handler.go @@ -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() +} diff --git a/cron/helpers.go b/cron/helpers.go new file mode 100644 index 0000000..e68d5db --- /dev/null +++ b/cron/helpers.go @@ -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) +} diff --git a/cron/server_handler.go b/cron/server_handler.go new file mode 100644 index 0000000..ee29f88 --- /dev/null +++ b/cron/server_handler.go @@ -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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4995820 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fdce0f3 --- /dev/null +++ b/go.sum @@ -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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..9143f9e --- /dev/null +++ b/main.go @@ -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") +} diff --git a/mode/mode.go b/mode/mode.go new file mode 100644 index 0000000..f6d586d --- /dev/null +++ b/mode/mode.go @@ -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 +} diff --git a/models/lang_version.go b/models/lang_version.go new file mode 100644 index 0000000..6886e51 --- /dev/null +++ b/models/lang_version.go @@ -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"` +} diff --git a/models/opponents_defeated.go b/models/opponents_defeated.go new file mode 100644 index 0000000..9d40613 --- /dev/null +++ b/models/opponents_defeated.go @@ -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"` +} diff --git a/models/player.go b/models/player.go new file mode 100644 index 0000000..5c17586 --- /dev/null +++ b/models/player.go @@ -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 +} diff --git a/models/server.go b/models/server.go new file mode 100644 index 0000000..e75ced2 --- /dev/null +++ b/models/server.go @@ -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"` +} diff --git a/models/tribe.go b/models/tribe.go new file mode 100644 index 0000000..1510197 --- /dev/null +++ b/models/tribe.go @@ -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 +} diff --git a/models/village.go b/models/village.go new file mode 100644 index 0000000..a97ba31 --- /dev/null +++ b/models/village.go @@ -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"` +}