Add db layer to store all the badges instad of using memory type of map

This commit is contained in:
2020-08-15 12:09:39 +10:00
parent 6236c3518c
commit 9db74aaf5b
7 changed files with 334 additions and 143 deletions
+37
View File
@@ -0,0 +1,37 @@
package config
import "fmt"
type Config struct {
Host string
Port string
DBName string
Username string
Password string
}
func (config *Config) GetConnectionString() string {
connectionString := "sslmode=disable"
if config.Username != "" {
connectionString = connectionString + " user=" + config.Username
}
if config.Password != "" {
connectionString = connectionString + " password=" + config.Password
}
if config.Host != "" {
connectionString = connectionString + " host=" + config.Host
}
if config.Port != "" {
connectionString = fmt.Sprintf("%s port=%s", connectionString, config.Port)
}
if config.DBName != "" {
connectionString = connectionString + " dbname=" + config.DBName
}
return connectionString
}
+3 -3
View File
@@ -4,14 +4,14 @@ go 1.14
require (
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca
github.com/fogleman/gg v1.3.0
github.com/davecgh/go-spew v1.1.1
github.com/gin-gonic/gin v1.6.3
github.com/go-playground/validator/v10 v10.3.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/jinzhu/gorm v1.9.16
github.com/oklog/ulid/v2 v2.0.2 // indirect
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/common v0.10.0
github.com/sirupsen/logrus v1.4.2
golang.org/x/image v0.0.0-20200618115811-c13761719519 // indirect
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
+27 -6
View File
@@ -1,11 +1,13 @@
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/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA=
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -17,10 +19,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
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/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
@@ -39,10 +41,10 @@ github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o=
github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
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-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
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=
@@ -65,12 +67,18 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
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/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
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/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -80,8 +88,11 @@ 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/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
@@ -93,6 +104,10 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -128,18 +143,22 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34=
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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-20181114220301-adae6a3d119a/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-20190613194153-d28f0bde5980/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-20200324143707-d3edc9973b7e/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=
@@ -150,10 +169,12 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+178
View File
@@ -0,0 +1,178 @@
package handlers
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"time"
svg "github.com/ajstarks/svgo"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/prometheus/client_golang/api"
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/common/model"
"github.com/sirupsen/logrus"
"github.com/wahyd4/badger/models"
)
var (
ErrNoSuchBadgeFound = errors.New("no such badge can be found, please make sure you have provided the correct information")
badgeRequested = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "badge_requested_total",
Help: "The total request number of badges",
}, []string{"badge"})
badgeGenerationTime = promauto.NewHistogram(prometheus.HistogramOpts{
Name: "badge_generation_time",
Help: "The time consumed for badge generation",
})
)
const (
ColorSchemeGreen = "fill-opacity:1.00; fill:rgb(49, 197, 83);"
ColorSchemeOrange = "fill-opacity:1.00; fill:rgb(255, 87, 34);"
ColorSchemeBlue = "fill-opacity:1.00; fill:rgb(32, 99, 155);"
BADGE_TYPE_PROMETHEUS = "PROMETHEUS"
radius = 0
fontSize = 12
textMarginX = 6.0
textMarginY = 14
badgeHeight = 20
textStyle = "text-anchor:start;font-size:12px;fill:white;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;"
badgeExpireSeconds = 60
labelWidthPortion = 0.65
metricRequestTimeout = 10 * time.Second
)
type Metric struct {
Label string
Value float64
Time time.Time
}
type BadgesHandler struct {
DB *gorm.DB
PrometheusAPIClient api.Client
}
func (handler *BadgesHandler) GetBadge(c *gin.Context) {
if err := handler.generateSVG(c); err != nil {
logrus.Error(err)
if err == ErrNoSuchBadgeFound {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Oops, there's something wrong, please try it again later."})
}
}
func (handler *BadgesHandler) generateSVG(c *gin.Context) error {
startTime := time.Now()
queryName := c.Param("name")
badge, err := handler.queryBadgeFromDB(queryName)
if err != nil {
if err == gorm.ErrRecordNotFound {
return ErrNoSuchBadgeFound
}
return err
}
prometheusOptions := badge.OptionStruct.(*models.PrometheusOptions)
metric, _ := queryMetric(handler.PrometheusAPIClient, prometheusOptions)
metricValue := int(metric.Value)
label := metric.Label
valueRectWidth := 40
labelContainerWidth := 120
if len(label) <= 10 {
labelContainerWidth = 74
}
if metricValue > 10000 && metricValue < 1000000 {
valueRectWidth = 50
} else if metricValue >= 1000000 {
valueRectWidth = 66
}
badgeWidth := labelContainerWidth + valueRectWidth
w := c.Writer
w.Header().Set("Content-Type", "image/svg+xml")
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, s-maxage=%d", badgeExpireSeconds, badgeExpireSeconds))
canvas := svg.New(w)
canvas.Start(badgeWidth, badgeHeight)
canvas.Rect(0, 0, badgeWidth, badgeHeight, "fill-opacity:1.00; fill:rgb(90,90,90); padding: 2px 5px;")
//labelContainerWidth := len(metric.Label)*fontSize + textMarginX
canvas.Rect(0, 0, int(labelContainerWidth), badgeHeight, string(ColorSchemeGreen))
//when there is metric label then use default label
if len(prometheusOptions.ValueField) == 0 {
label = *badge.Label
}
canvas.Text(textMarginX, textMarginY, label, textStyle)
valueText := strconv.Itoa(metricValue)
canvas.Text(labelContainerWidth+textMarginX, textMarginY, valueText, textStyle)
canvas.End()
badgeGenerationTime.Observe(float64(time.Since(startTime).Microseconds()))
badgeRequested.With(map[string]string{"badge": queryName}).Inc()
return nil
}
func (handler *BadgesHandler) queryBadgeFromDB(queryName string) (*models.Badge, error) {
var badge models.Badge
err := handler.DB.Find(&badge, "ulid = ?", queryName).Error
if err != nil {
if err != gorm.ErrRecordNotFound {
return nil, fmt.Errorf("failed to query metric %w", err)
}
return nil, err
}
prometheusOptions := models.PrometheusOptions{}
if err = json.Unmarshal(badge.Options.RawMessage, &prometheusOptions); err != nil {
return nil, fmt.Errorf("failed to unmarshal badge options %w", err)
}
badge.OptionStruct = &prometheusOptions
return &badge, nil
}
func queryMetric(client api.Client, options *models.PrometheusOptions) (*Metric, error) {
v1api := v1.NewAPI(client)
ctx, cancel := context.WithTimeout(context.Background(), metricRequestTimeout)
defer cancel()
result, warnings, err := v1api.Query(ctx, options.Query, time.Now())
if err != nil {
return nil, fmt.Errorf("error querying Prometheus: %w", err)
}
if len(warnings) > 0 {
fmt.Printf("Warnings: %v\n", warnings)
}
var metric Metric
for _, v := range result.(model.Vector) {
metric = Metric{
Label: string(v.Metric[model.LabelName(options.ValueField)]),
Value: float64(v.Value),
Time: v.Timestamp.Time(),
}
}
return &metric, nil
}
+65 -134
View File
@@ -1,74 +1,42 @@
package main
import (
"context"
"errors"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"
svg "github.com/ajstarks/svgo"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/postgres"
"github.com/prometheus/client_golang/api"
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/model"
"github.com/sirupsen/logrus"
)
"github.com/wahyd4/badger/handlers"
const (
radius = 0
fontSize = 12
textMarginX = 6.0
textMarginY = 14
badgeHeight = 20
textStyle = "text-anchor:start;font-size:12px;fill:white;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;"
badgeExpireSeconds = 60
labelWidthPortion = 0.65
ColorSchemeGreen = "fill-opacity:1.00; fill:rgb(49, 197, 83);"
ColorSchemeOrange = "fill-opacity:1.00; fill:rgb(255, 87, 34);"
ColorSchemeBlue = "fill-opacity:1.00; fill:rgb(32, 99, 155);"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/sirupsen/logrus"
"github.com/wahyd4/badger/config"
"github.com/wahyd4/badger/models"
)
type ColorScheme string
var (
ErrNoSuchBadgeFound = errors.New("no such badge can be found, please make sure you have provided the correct information")
queriesMap = map[string]queryParam{
"mel": {Color: ColorSchemeGreen, Query: "air_index_iaqi_pm25_v{city_name=\"Melbourne CBD\"}", ValueField: "city_name"},
"chengdu": {Color: ColorSchemeGreen, Query: "air_index_iaqi_pm25_v{city_name=\"Chengdu (成都)\"}", ValueField: "city_name"},
"dockerhub": {Color: ColorSchemeGreen, Query: "docker_hub_pull_count{name=\"aria2-ui\"}", ValueField: "name"},
"github": {Color: ColorSchemeGreen, Query: "github_repository_stars{name=\"aria2-ariang-docker\"}", ValueField: "name"},
"badge-requests-count": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total[500d])))", Label: "Badge Req Count"},
"junv-github-profile": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"junv-github-profile\"}[500d])))", Label: "Page Views"},
"aria2-ariang-docker": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"aria2-ariang-docker\"}[500d])))", Label: "Page Views"},
"aria2-ariang-x-docker-compose": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"aria2-ariang-x-docker-compose\"}[500d])))", Label: "Page Views"},
"work-in-australia": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"work-in-australia\"}[500d])))", Label: "Page Views"},
"aria-ui-dockerhub": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"aria-ui-dockerhub\"}[500d])))", Label: "Page Views"},
queriesMap = map[string]queryParam{
"mel": {Color: handlers.ColorSchemeGreen, Query: "air_index_iaqi_pm25_v{city_name=\"Melbourne CBD\"}", ValueField: "city_name"},
"chengdu": {Color: handlers.ColorSchemeGreen, Query: "air_index_iaqi_pm25_v{city_name=\"Chengdu (成都)\"}", ValueField: "city_name"},
"dockerhub": {Color: handlers.ColorSchemeGreen, Query: "docker_hub_pull_count{name=\"aria2-ui\"}", ValueField: "name"},
"github": {Color: handlers.ColorSchemeGreen, Query: "github_repository_stars{name=\"aria2-ariang-docker\"}", ValueField: "name"},
"badge-requests-count": {Color: handlers.ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total[500d])))", Label: "Badge Req Count"},
"junv-github-profile": {Color: handlers.ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"junv-github-profile\"}[500d])))", Label: "Page Views"},
"aria2-ariang-docker": {Color: handlers.ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"aria2-ariang-docker\"}[500d])))", Label: "Page Views"},
"aria2-ariang-x-docker-compose": {Color: handlers.ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"aria2-ariang-x-docker-compose\"}[500d])))", Label: "Page Views"},
"work-in-australia": {Color: handlers.ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"work-in-australia\"}[500d])))", Label: "Page Views"},
"aria-ui-dockerhub": {Color: handlers.ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"aria-ui-dockerhub\"}[500d])))", Label: "Page Views"},
}
badgeRequested = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "badge_requested_total",
Help: "The total request number of badges",
}, []string{"badge"})
badgeGenerationTime = promauto.NewHistogram(prometheus.HistogramOpts{
Name: "badge_generation_time",
Help: "The time consumed for badge generation",
})
)
type Metric struct {
Label string
Value float64
Time time.Time
}
type queryParam struct {
Color ColorScheme
Query string
@@ -77,6 +45,24 @@ type queryParam struct {
}
func main() {
dbConfig := config.Config{
Host: getEnvWithDefault("DB_HOST", "home.toozhao.com"),
Port: getEnvWithDefault("DB_PORT", "0000"),
DBName: getEnvWithDefault("DB_NAME", "badges"),
Username: getEnvWithDefault("DB_USERNAME", "someuser"),
Password: getEnvWithDefault("DB_PASSWORD", "hahah"),
}
connStr := dbConfig.GetConnectionString()
db, err := gorm.Open("postgres", connStr)
if err != nil {
panic("failed to connect database" + err.Error())
}
defer db.Close()
// Migrate the schema
db.AutoMigrate(&models.Badge{})
client, err := api.NewClient(api.Config{
Address: "https://prometheus-api.home.toozhao.com",
})
@@ -85,6 +71,8 @@ func main() {
os.Exit(1)
}
handler := &handlers.BadgesHandler{DB: db, PrometheusAPIClient: client}
r := gin.Default()
r.GET("/", func(c *gin.Context) {
@@ -95,17 +83,7 @@ func main() {
c.JSON(200, gin.H{"msg": "Hello!"})
})
r.GET("/svg/:name", func(c *gin.Context) {
if err := generateSVG(client, c); err != nil {
logrus.Error(err.Error())
if err == ErrNoSuchBadgeFound {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "please try it again"})
}
})
r.GET("/svg/:name", handler.GetBadge)
r.StaticFile("/favicon.ico", "static/favicon.ico")
r.StaticFile("/robots.txt", "static/robots.txt")
@@ -122,80 +100,33 @@ func main() {
log.Fatal(r.Run())
}
func generateSVG(client api.Client, c *gin.Context) error {
startTime := time.Now()
queryName := c.Param("name")
query, ok := queriesMap[queryName]
if !ok {
return ErrNoSuchBadgeFound
func getEnvWithDefault(key, defaultValue string) string {
val := os.Getenv(key)
if val != "" {
return val
}
metric := queryMetric(client, query)
metricValue := int(metric.Value)
label := metric.Label
valueRectWidth := 40
labelContainerWidth := 120
if len(label) <= 10 {
labelContainerWidth = 74
}
if metricValue > 10000 && metricValue < 1000000 {
valueRectWidth = 50
} else if metricValue >= 1000000 {
valueRectWidth = 66
}
badgeWidth := labelContainerWidth + valueRectWidth
w := c.Writer
w.Header().Set("Content-Type", "image/svg+xml")
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, s-maxage=%d", badgeExpireSeconds, badgeExpireSeconds))
canvas := svg.New(w)
canvas.Start(badgeWidth, badgeHeight)
canvas.Rect(0, 0, badgeWidth, badgeHeight, "fill-opacity:1.00; fill:rgb(90,90,90); padding: 2px 5px;")
//labelContainerWidth := len(metric.Label)*fontSize + textMarginX
canvas.Rect(0, 0, int(labelContainerWidth), badgeHeight, string(query.Color))
//when there is metric label then use default label
if len(query.ValueField) == 0 {
label = query.Label
}
canvas.Text(textMarginX, textMarginY, label, textStyle)
valueText := strconv.Itoa(metricValue)
canvas.Text(labelContainerWidth+textMarginX, textMarginY, valueText, textStyle)
canvas.End()
badgeGenerationTime.Observe(float64(time.Since(startTime).Microseconds()))
badgeRequested.With(map[string]string{"badge": queryName}).Inc()
return nil
return defaultValue
}
func queryMetric(client api.Client, query queryParam) *Metric {
v1api := v1.NewAPI(client)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, warnings, err := v1api.Query(ctx, query.Query, time.Now())
if err != nil {
fmt.Printf("Error querying Prometheus: %v\n", err)
os.Exit(1)
}
if len(warnings) > 0 {
fmt.Printf("Warnings: %v\n", warnings)
}
var metric Metric
for _, v := range result.(model.Vector) {
metric = Metric{
Label: string(v.Metric[model.LabelName(query.ValueField)]),
Value: float64(v.Value),
Time: v.Timestamp.Time(),
func importMaps(db *gorm.DB) {
for key, value := range queriesMap {
jsonB := new(postgres.Jsonb)
options := models.PrometheusOptions{
ValueField: value.ValueField,
Query: value.Query,
}
bytes, err := json.Marshal(&options)
if err != nil {
logrus.Errorf("failed to marshal option to bytes %w", err)
}
if err = jsonB.Scan(bytes); err != nil {
logrus.Error(err.Error())
}
db.Save(&models.Badge{
Label: &value.Label,
ULID: key,
Type: handlers.BADGE_TYPE_PROMETHEUS,
Options: jsonB,
})
}
return &metric
}
+15
View File
@@ -0,0 +1,15 @@
package models
import (
"github.com/jinzhu/gorm"
"github.com/jinzhu/gorm/dialects/postgres"
)
type Badge struct {
gorm.Model
Label *string
ULID string `gorm:"column:ulid;type:varchar;not null;unique_index"`
Type string
Options *postgres.Jsonb
OptionStruct interface{} `gorm:"-"`
}
+9
View File
@@ -0,0 +1,9 @@
package models
type BadgeOptions struct{}
type PrometheusOptions struct {
BadgeOptions
ValueField string
Query string
}