Initial commit
This commit is contained in:
commit
bfe6fa8a5a
|
@ -0,0 +1,2 @@
|
|||
.idea/
|
||||
data/
|
|
@ -0,0 +1,64 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
port string
|
||||
dnt bool
|
||||
dbPath string
|
||||
statsAuth bool
|
||||
statsUsername string
|
||||
statsPassword string
|
||||
}
|
||||
|
||||
var (
|
||||
appConfig = &config{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
appConfig.port = port()
|
||||
appConfig.dnt = dnt()
|
||||
appConfig.dbPath = dbPath()
|
||||
appConfig.statsUsername = statsUsername()
|
||||
appConfig.statsPassword = statsPassword()
|
||||
appConfig.statsAuth = len(appConfig.statsUsername) > 0 && len(appConfig.statsPassword) > 0
|
||||
}
|
||||
|
||||
func port() string {
|
||||
port := os.Getenv("PORT")
|
||||
if len(port) != 0 {
|
||||
return port
|
||||
} else {
|
||||
return "8080"
|
||||
}
|
||||
}
|
||||
|
||||
func dnt() bool {
|
||||
dnt := os.Getenv("DNT")
|
||||
dntBool, e := strconv.ParseBool(dnt)
|
||||
if e != nil {
|
||||
dntBool = true
|
||||
}
|
||||
return dntBool
|
||||
}
|
||||
|
||||
func dbPath() (dbPath string) {
|
||||
dbPath = os.Getenv("DB_PATH")
|
||||
if len(dbPath) == 0 {
|
||||
dbPath = "data/kis3.db"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func statsUsername() (username string) {
|
||||
username = os.Getenv("STATS_USERNAME")
|
||||
return
|
||||
}
|
||||
|
||||
func statsPassword() (password string) {
|
||||
password = os.Getenv("STATS_PASSWORD")
|
||||
return
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"kis3.dev/kis3/helpers"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
sqlDB *sql.DB
|
||||
}
|
||||
|
||||
func initDatabase() (database *Database, e error) {
|
||||
database = &Database{}
|
||||
if _, err := os.Stat(appConfig.dbPath); os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(filepath.Dir(appConfig.dbPath), os.ModePerm)
|
||||
}
|
||||
database.sqlDB, e = sql.Open("sqlite3", appConfig.dbPath)
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
e = migrateDatabase(database.sqlDB)
|
||||
return
|
||||
}
|
||||
|
||||
func migrateDatabase(database *sql.DB) (e error) {
|
||||
sourceDriver, e := (&helpers.PackrSource{}).Open("")
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
databaseDriver, e := sqlite3.WithInstance(database, &sqlite3.Config{})
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
m, e := migrate.NewWithInstance("packr", sourceDriver, "kis3", databaseDriver)
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
e = m.Up()
|
||||
if e == migrate.ErrNoChange {
|
||||
e = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Tracking
|
||||
|
||||
func (db *Database) trackView(url string, ref string) {
|
||||
if len(url) == 0 {
|
||||
return
|
||||
}
|
||||
_, e := db.sqlDB.Exec("insert into views(url, ref) values(:url, :ref)", sql.Named("url", url), sql.Named("ref", ref))
|
||||
if e != nil {
|
||||
fmt.Println("Inserting into DB failed:", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Requesting
|
||||
|
||||
type View int
|
||||
|
||||
const (
|
||||
PAGES View = iota + 1
|
||||
REFERRERS
|
||||
HOURS
|
||||
DAYS
|
||||
WEEKS
|
||||
MONTHS
|
||||
)
|
||||
|
||||
type ViewsRequest struct {
|
||||
view View
|
||||
from string
|
||||
to string
|
||||
url string
|
||||
domain string
|
||||
ref string
|
||||
}
|
||||
|
||||
type RequestResultRow struct {
|
||||
First string `json:"first"`
|
||||
Second int `json:"second"`
|
||||
}
|
||||
|
||||
func (db *Database) request(request *ViewsRequest) (resultRows []*RequestResultRow, e error) {
|
||||
filterString, parameters := request.buildFilter()
|
||||
// Fix to use array as varargs
|
||||
namedArgs := make([]interface{}, len(parameters))
|
||||
for i, v := range parameters {
|
||||
namedArgs[i] = v
|
||||
}
|
||||
// Query
|
||||
statement := request.buildStatement(filterString)
|
||||
rows, e := db.sqlDB.Query(statement, namedArgs...)
|
||||
if e != nil {
|
||||
return
|
||||
} else {
|
||||
resultRows = []*RequestResultRow{}
|
||||
for rows.Next() {
|
||||
var first string
|
||||
var second int
|
||||
e = rows.Scan(&first, &second)
|
||||
if e != nil {
|
||||
_ = rows.Close()
|
||||
return
|
||||
}
|
||||
resultRows = append(resultRows, &RequestResultRow{
|
||||
First: first,
|
||||
Second: second,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (request *ViewsRequest) buildStatement(filters string) (statement string) {
|
||||
if len(filters) > 0 {
|
||||
filters = " where " + filters + " "
|
||||
} else {
|
||||
filters = " "
|
||||
}
|
||||
switch request.view {
|
||||
case PAGES:
|
||||
statement = "SELECT url as first, count(*) as second from views" + filters + "group by url;"
|
||||
return
|
||||
case REFERRERS:
|
||||
statement = "SELECT ref as first, count(*) as second from views" + filters + "group by ref;"
|
||||
return
|
||||
case HOURS, DAYS, WEEKS, MONTHS:
|
||||
format := ""
|
||||
switch request.view {
|
||||
case HOURS:
|
||||
format = "%Y-%m-%d %H"
|
||||
case DAYS:
|
||||
format = "%Y-%m-%d"
|
||||
case WEEKS:
|
||||
format = "%Y-%W"
|
||||
case MONTHS:
|
||||
format = "%Y-%m"
|
||||
}
|
||||
statement = "SELECT strftime('" + format + "', time, 'localtime') as first, count(*) as second from views" + filters + "group by first;"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Request filters
|
||||
|
||||
func (request *ViewsRequest) buildFilter() (filters string, parameters []sql.NamedArg) {
|
||||
parameters = []sql.NamedArg{}
|
||||
var allFilters []string
|
||||
for _, filter := range []string{
|
||||
request.buildDateTimeFilter(¶meters),
|
||||
request.buildUrlFilter(¶meters),
|
||||
request.buildDomainFilter(¶meters),
|
||||
request.buildRefFilter(¶meters),
|
||||
} {
|
||||
if len(filter) > 0 {
|
||||
allFilters = append(allFilters, filter)
|
||||
}
|
||||
}
|
||||
filters = strings.Join(allFilters, " and ")
|
||||
return
|
||||
}
|
||||
|
||||
func (request *ViewsRequest) buildDateTimeFilter(namedArg *[]sql.NamedArg) (dateTimeFilter string) {
|
||||
if len(request.from) > 0 && len(request.to) > 0 {
|
||||
*namedArg = append(*namedArg, sql.Named("from", request.from))
|
||||
*namedArg = append(*namedArg, sql.Named("to", request.to))
|
||||
dateTimeFilter = "datetime(time, 'localtime') between :from and :to"
|
||||
return
|
||||
} else if len(request.from) > 0 {
|
||||
*namedArg = append(*namedArg, sql.Named("from", request.from))
|
||||
dateTimeFilter = "datetime(time, 'localtime') >= :from"
|
||||
return
|
||||
} else if len(request.to) > 0 {
|
||||
*namedArg = append(*namedArg, sql.Named("to", request.to))
|
||||
dateTimeFilter = "datetime(time, 'localtime') <= :to"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (request *ViewsRequest) buildUrlFilter(namedArg *[]sql.NamedArg) (urlFilter string) {
|
||||
if len(request.url) > 0 {
|
||||
*namedArg = append(*namedArg, sql.Named("url", request.url))
|
||||
urlFilter = "url = :url"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (request *ViewsRequest) buildDomainFilter(namedArg *[]sql.NamedArg) (domainFilter string) {
|
||||
if len(request.domain) > 0 {
|
||||
*namedArg = append(*namedArg, sql.Named("domain", request.domain+"%"))
|
||||
domainFilter = "url like :domain"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (request *ViewsRequest) buildRefFilter(namedArg *[]sql.NamedArg) (refFilter string) {
|
||||
if len(request.url) > 0 {
|
||||
*namedArg = append(*namedArg, sql.Named("ref", request.ref))
|
||||
refFilter = "ref = :ref"
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
module kis3.dev/kis3
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/gobuffalo/packr/v2 v2.0.9
|
||||
github.com/golang-migrate/migrate/v4 v4.2.5
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/gorilla/mux v1.7.0
|
||||
github.com/mattn/go-sqlite3 v1.10.0
|
||||
github.com/wcharczuk/go-chart v2.0.1+incompatible
|
||||
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f // indirect
|
||||
)
|
|
@ -0,0 +1,211 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.28.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.6.0/go.mod h1:QeFzMJDAw8TXt5+aRaSuE8l5BwaMIOIlaVkBOPRuMuw=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
git.apache.org/thrift.git v0.0.0-20180924222215-a9235805469b/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/aws/aws-sdk-go v1.15.54/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
|
||||
github.com/blend/go-sdk v2.0.0+incompatible h1:FL9X/of4ZYO5D2JJNI4vHrbXPfuSDbUa7h8JP9+E92w=
|
||||
github.com/blend/go-sdk v2.0.0+incompatible/go.mod h1:3GUb0YsHFNTJ6hsJTpzdmCUl05o8HisKjx5OAlzYKdw=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
|
||||
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
|
||||
github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg=
|
||||
github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc=
|
||||
github.com/cznic/internal v0.0.0-20180608152220-f44710a21d00/go.mod h1:olo7eAdKwJdXxb55TKGLiJ6xt1H0/tiiRCWKVLmtjY4=
|
||||
github.com/cznic/lldb v1.1.0/go.mod h1:FIZVUmYUVhPwRiPzL8nD/mpFcJ/G7SSXjjXYG4uRI3A=
|
||||
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
||||
github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE=
|
||||
github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ=
|
||||
github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
|
||||
github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8=
|
||||
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/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc=
|
||||
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v0.7.3-0.20190108045446-77df18c24acf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/fsouza/fake-gcs-server v1.3.0/go.mod h1:Lq+43m2znsXfDKHnQMfdA0HpYYAEJsfizsbpk5k3TLo=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-ini/ini v1.39.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobuffalo/envy v1.6.15 h1:OsV5vOpHYUpP7ZLS6sem1y40/lNX1BZj+ynMiRi21lQ=
|
||||
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9 h1:lGw35M+JVpedVCf08hSiBHKpp3FPTKqt6qRdXMSVAq0=
|
||||
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
|
||||
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5 h1:f3Fpd5AqsFuTHUEhUeEMIFJkX8FpVnzdW+GpYxIyXkA=
|
||||
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
|
||||
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs=
|
||||
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
|
||||
github.com/gobuffalo/mapi v1.0.1 h1:JRuTiZzDEZhBHkFiHTxJkYRT6CbYuL0K/rn+1byJoEA=
|
||||
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
|
||||
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0 h1:P6naWPiHm/7R3eYx/ub3VhaW9G+1xAMJ6vzACePaGPI=
|
||||
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
|
||||
github.com/gobuffalo/packr/v2 v2.0.9 h1:g8P4tjthK0vAu2aopXlNNxCNsnGsWd+dbZLdc3fHSYk=
|
||||
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
|
||||
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4=
|
||||
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
|
||||
github.com/gocql/gocql v0.0.0-20181124151448-70385f88b28b/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang-migrate/migrate/v4 v4.2.5 h1:L41P/diej/v1ip3MPpbNPrUu2MHQmvho4DKchKAPkEQ=
|
||||
github.com/golang-migrate/migrate/v4 v4.2.5/go.mod h1:zt+104di20bTWX9M3cCcERBkS/6mncciH5sAjcn6kBU=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
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/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
|
||||
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
|
||||
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
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/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/karrick/godirwalk v1.8.0 h1:ycpSqVon/QJJoaT1t8sae0tp1Stg21j+dyuS7OoagcA=
|
||||
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kshvakov/clickhouse v1.3.4/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k=
|
||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mongodb/mongo-go-driver v0.1.0/go.mod h1:NK/HWDIIZkaYsnYa0hmtP443T5ELr0KDecmIioVuuyU=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.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_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.2.2 h1:J7U/N7eRtzjhs26d6GqMh2HBuXP8/Z64Densiiieafo=
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE=
|
||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/wcharczuk/go-chart v2.0.1+incompatible h1:0pz39ZAycJFF7ju/1mepnk26RLVLBCWz1STcD3doU0A=
|
||||
github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
go.opencensus.io v0.17.0/go.mod h1:mp1VrMQxhlqqDpKvH4UcQUa4YwlzNmymAjPrDdfxNpI=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f h1:FO4MZ3N56GnxbqxGKqh+YTzUWQ2sDwtFQEZgLOxh9Jc=
|
||||
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
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-20180925072008-f04abc6bdfa7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20180905080454-ebe1bf3edb33/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-20180925112736-b09afc3d579e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190108104531-7fbe1cd0fcc2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180924175601-e93be7f42f9f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190108222858-421f03a57a64/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe h1:tilCFoChsm7TcjFUH5YtBjno7wDleRO+6jICJg0WDS0=
|
||||
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20180921000521-920bb1beccf7/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181015145326-625cd1887957/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180924164928-221a8d4f7494/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190108161440-ae2f86662275/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.39.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
@ -0,0 +1,14 @@
|
|||
package helpers
|
||||
|
||||
import "net/http"
|
||||
|
||||
func CheckAuth(w http.ResponseWriter, r *http.Request, username string, password string) (ok bool) {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Authentication required"`)
|
||||
rUsername, rPassword, rOk := r.BasicAuth()
|
||||
if rOk && rUsername == username && rPassword == password {
|
||||
return true
|
||||
} else {
|
||||
http.Error(w, "Not authorized", 401)
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package helpers
|
||||
|
||||
// This is a quick hack to get Packr working with golang-migrate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/source"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type PackrSource struct {
|
||||
lock sync.Mutex
|
||||
box *packr.Box
|
||||
migrations *source.Migrations
|
||||
}
|
||||
|
||||
func (s *PackrSource) loadMigrations() (*source.Migrations, error) {
|
||||
migrations := source.NewMigrations()
|
||||
for _, filename := range s.box.List() {
|
||||
migration, err := source.Parse(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
migrations.Append(migration)
|
||||
}
|
||||
return migrations, nil
|
||||
}
|
||||
|
||||
func (s *PackrSource) Open(url string) (source.Driver, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.box = packr.New("migrations", "../migrations")
|
||||
if migrations, err := s.loadMigrations(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
s.migrations = migrations
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PackrSource) Close() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.migrations = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PackrSource) First() (version uint, err error) {
|
||||
if v, ok := s.migrations.First(); !ok {
|
||||
return 0, os.ErrNotExist
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PackrSource) Prev(version uint) (prevVersion uint, err error) {
|
||||
if v, ok := s.migrations.Prev(version); !ok {
|
||||
return 0, os.ErrNotExist
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PackrSource) Next(version uint) (nextVersion uint, err error) {
|
||||
if v, ok := s.migrations.Next(version); !ok {
|
||||
return 0, os.ErrNotExist
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PackrSource) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {
|
||||
if migration, ok := s.migrations.Up(version); !ok {
|
||||
return nil, "", os.ErrNotExist
|
||||
} else {
|
||||
b, _ := s.box.Find(migration.Raw)
|
||||
return ioutil.NopCloser(bytes.NewBuffer(b)),
|
||||
migration.Identifier,
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PackrSource) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {
|
||||
if migration, ok := s.migrations.Down(version); !ok {
|
||||
return nil, "", migrate.ErrNilVersion
|
||||
} else {
|
||||
b := s.box.Bytes(migration.Raw)
|
||||
return ioutil.NopCloser(bytes.NewBuffer(b)),
|
||||
migration.Identifier,
|
||||
nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/wcharczuk/go-chart"
|
||||
"github.com/wcharczuk/go-chart/drawing"
|
||||
"kis3.dev/kis3/helpers"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type kis3 struct {
|
||||
db *Database
|
||||
router *mux.Router
|
||||
fs http.Handler
|
||||
}
|
||||
|
||||
var (
|
||||
app = &kis3{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
e := app.setupDB()
|
||||
if e != nil {
|
||||
log.Fatal("Database setup failed:", e)
|
||||
}
|
||||
app.setupRouter()
|
||||
}
|
||||
|
||||
func main() {
|
||||
app.startListening()
|
||||
}
|
||||
|
||||
func (kis3 *kis3) setupDB() (e error) {
|
||||
kis3.db, e = initDatabase()
|
||||
return
|
||||
}
|
||||
|
||||
func (kis3 *kis3) setupRouter() {
|
||||
kis3.router = mux.NewRouter()
|
||||
kis3.router.HandleFunc("/view", kis3.trackView)
|
||||
kis3.router.HandleFunc("/stats", kis3.requestStats)
|
||||
kis3.router.PathPrefix("/").Handler(http.HandlerFunc(kis3.serveStaticFile))
|
||||
}
|
||||
|
||||
func (kis3 kis3) startListening() {
|
||||
port := appConfig.port
|
||||
addr := ":" + port
|
||||
fmt.Printf("Listening to %s\n", addr)
|
||||
log.Fatal(http.ListenAndServe(addr, kis3.router))
|
||||
}
|
||||
|
||||
func (kis3 kis3) trackView(w http.ResponseWriter, r *http.Request) {
|
||||
url := r.URL.Query().Get("url")
|
||||
ref := r.URL.Query().Get("ref")
|
||||
if r.Header.Get("DNT") == "1" && appConfig.dnt {
|
||||
fmt.Println("Not tracking because of DNT")
|
||||
} else {
|
||||
fmt.Printf("Tracking %s with referrer %s\n", url, ref)
|
||||
go kis3.db.trackView(url, ref) // run with goroutine for awesome speed!
|
||||
_, _ = fmt.Fprint(w, "true")
|
||||
}
|
||||
}
|
||||
|
||||
func (kis3 kis3) serveStaticFile(w http.ResponseWriter, r *http.Request) {
|
||||
if kis3.fs == nil {
|
||||
kis3.fs = http.FileServer(packr.New("staticFiles", "./static"))
|
||||
}
|
||||
// Fix, because file server isn't serving index.html otherwise
|
||||
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||
r.URL.Path = "/default.html"
|
||||
}
|
||||
kis3.fs.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (kis3 kis3) requestStats(w http.ResponseWriter, r *http.Request) {
|
||||
// Require authentication
|
||||
if appConfig.statsAuth {
|
||||
if !helpers.CheckAuth(w, r, appConfig.statsUsername, appConfig.statsPassword) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Do request
|
||||
queries := r.URL.Query()
|
||||
view := PAGES
|
||||
switch queries.Get("view") {
|
||||
case "pages":
|
||||
view = PAGES
|
||||
case "referrers":
|
||||
view = REFERRERS
|
||||
case "hours":
|
||||
view = HOURS
|
||||
case "days":
|
||||
view = DAYS
|
||||
case "weeks":
|
||||
view = WEEKS
|
||||
case "months":
|
||||
view = MONTHS
|
||||
}
|
||||
result, e := kis3.db.request(&ViewsRequest{
|
||||
view: view,
|
||||
from: queries.Get("from"),
|
||||
to: queries.Get("to"),
|
||||
url: queries.Get("url"),
|
||||
domain: queries.Get("domain"),
|
||||
ref: queries.Get("ref"),
|
||||
})
|
||||
if e != nil {
|
||||
fmt.Println("Database request failed:", e)
|
||||
w.WriteHeader(500)
|
||||
} else if result != nil {
|
||||
switch queries.Get("format") {
|
||||
case "json":
|
||||
sendJsonResponse(result, w)
|
||||
return
|
||||
case "chart":
|
||||
sendChartResponse(result, w)
|
||||
return
|
||||
default: // "plain"
|
||||
sendPlainResponse(result, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendPlainResponse(result []*RequestResultRow, w http.ResponseWriter) {
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
for _, row := range result {
|
||||
_, _ = fmt.Fprintln(w, (*row).First+": "+strconv.Itoa((*row).Second))
|
||||
}
|
||||
}
|
||||
|
||||
func sendJsonResponse(result []*RequestResultRow, w http.ResponseWriter) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
jsonBytes, _ := json.Marshal(result)
|
||||
_, _ = fmt.Fprintln(w, string(jsonBytes))
|
||||
}
|
||||
|
||||
func sendChartResponse(result []*RequestResultRow, w http.ResponseWriter) {
|
||||
var values []chart.Value
|
||||
max := float64(1)
|
||||
for _, row := range result {
|
||||
values = append(values, chart.Value{Label: row.First, Value: float64(row.Second), Style: chart.Style{
|
||||
FillColor: drawing.ColorBlue,
|
||||
StrokeColor: drawing.ColorBlue,
|
||||
}})
|
||||
max = math.Max(max, float64(row.Second))
|
||||
}
|
||||
chartRange := &chart.ContinuousRange{
|
||||
Min: float64(0),
|
||||
Max: max,
|
||||
}
|
||||
chartWidth := len(values)*30 + 100
|
||||
barChart := chart.BarChart{
|
||||
Title: "Stats",
|
||||
Height: 500,
|
||||
Width: chartWidth,
|
||||
TitleStyle: chart.StyleShow(),
|
||||
Background: chart.Style{
|
||||
Padding: chart.Box{
|
||||
Top: 40,
|
||||
},
|
||||
},
|
||||
BarWidth: 20,
|
||||
BarSpacing: 10,
|
||||
XAxis: chart.Style{
|
||||
Show: true,
|
||||
TextRotationDegrees: 90.0,
|
||||
},
|
||||
YAxis: chart.YAxis{
|
||||
Style: chart.StyleShow(),
|
||||
Range: chartRange,
|
||||
},
|
||||
Bars: values,
|
||||
}
|
||||
w.Header().Set("Content-Type", chart.ContentTypeSVG)
|
||||
e := barChart.Render(chart.SVG, w)
|
||||
if e != nil {
|
||||
sendPlainResponse(result, w) // Fallback to plain
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE `views`;
|
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE `views`
|
||||
(
|
||||
`url` TEXT NOT NULL,
|
||||
`time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
`ref` TEXT DEFAULT '' NOT NULL
|
||||
);
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>KISSS</title>
|
||||
</head>
|
||||
<body>
|
||||
Welcome to <b>KISSS</b>
|
||||
<script src="kis3.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>KISSS</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Stats-URL Generator</h1>
|
||||
<div>
|
||||
<label>
|
||||
View:
|
||||
<select id="view-type">
|
||||
<option value="pages">Pages</option>
|
||||
<option value="referrers">Referrers</option>
|
||||
<option value="hours">Hours</option>
|
||||
<option value="days">Days</option>
|
||||
<option value="weeks">Weeks</option>
|
||||
<option value="months">Months</option>
|
||||
</select>
|
||||
</label><br>
|
||||
<label>
|
||||
Format:
|
||||
<select id="format-type">
|
||||
<option value="plain">Plain</option>
|
||||
<option value="json">JSON</option>
|
||||
<option value="chart">Chart</option>
|
||||
</select>
|
||||
</label><br>
|
||||
<label for="from-time">From:</label>
|
||||
<input type="date" id="from-time"><br>
|
||||
<label for="to-time">To:</label>
|
||||
<input type="date" id="to-time"><br>
|
||||
<button type="button" onclick="openStats()">Open stats</button>
|
||||
</div>
|
||||
<script>
|
||||
function openStats() {
|
||||
let statsUrl = new URL(document.documentURI).origin + "/stats?";
|
||||
let viewType = this.document.getElementById("view-type").value;
|
||||
statsUrl += "view=" + viewType + "&";
|
||||
let formatType = this.document.getElementById("format-type").value;
|
||||
statsUrl += "format=" + formatType + "&";
|
||||
let fromTime = formatDate(this.document.getElementById("from-time").value);
|
||||
if (fromTime.indexOf("NaN") < 0) statsUrl += "from=" + fromTime + "&";
|
||||
let toTime = formatDate(this.document.getElementById("to-time").value);
|
||||
if (toTime.indexOf("NaN") < 0) statsUrl += "to=" + toTime + "&";
|
||||
console.log(statsUrl);
|
||||
this.window.open(statsUrl, "_blank");
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
let date = new Date(Date.parse(dateString));
|
||||
let year = date.getFullYear();
|
||||
let months = date.getMonth() + 1;
|
||||
let day = date.getDate();
|
||||
return year + "-" + (months < 10 ? "0" + months : months) + "-" + (day < 10 ? "0" + day : day);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,17 @@
|
|||
(function () {
|
||||
var request = new XMLHttpRequest();
|
||||
var url = document.currentScript.baseURI
|
||||
+ "view?url=" + window.decodeURI(document.documentURI);
|
||||
if (document.referrer && document.referrer.length > 0) {
|
||||
url += window.decodeURI(document.referrer);
|
||||
}
|
||||
request.onload = function () {
|
||||
if (request.status >= 200 && request.status < 300) {
|
||||
console.log('Success with tracking page view!');
|
||||
} else {
|
||||
console.log('The tracking request failed!');
|
||||
}
|
||||
};
|
||||
request.open("POST", url);
|
||||
request.send();
|
||||
})();
|
Reference in New Issue