Merge branch 'develop'

This commit is contained in:
ErickSkrauch 2017-04-04 18:54:22 +03:00
commit 73205648d2
17 changed files with 200 additions and 64 deletions

33
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,33 @@
image: docker:latest
stages:
- build
- push
before_script:
- docker login -u gitlab-ci -p $CI_BUILD_TOKEN registry.ely.by
variables:
CONTAINER_IMAGE: registry.ely.by/elyby/skinsystem
build:
stage: build
script:
- export IMAGE_NAME="$CONTAINER_IMAGE:dev"
- docker build --pull -t $IMAGE_NAME .
- docker push $IMAGE_NAME
only:
- develop
push_tags:
stage: push
variables:
GIT_STRATEGY: none
script:
- export IMAGE_NAME="$CONTAINER_IMAGE:$CI_BUILD_TAG"
- docker tag $CONTAINER_IMAGE:dev $CONTAINER_IMAGE:latest
- docker tag $CONTAINER_IMAGE:latest $IMAGE_NAME
- docker push $IMAGE_NAME
- docker push $CONTAINER_IMAGE:latest
only:
- tags

2
data/statsd/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -11,3 +11,10 @@ services:
RABBITMQ_DEFAULT_USER: "ely-skinsystem-app" RABBITMQ_DEFAULT_USER: "ely-skinsystem-app"
RABBITMQ_DEFAULT_PASS: "ely-skinsystem-app-password" RABBITMQ_DEFAULT_PASS: "ely-skinsystem-app-password"
RABBITMQ_DEFAULT_VHOST: "/ely" RABBITMQ_DEFAULT_VHOST: "/ely"
statsd:
image: hopsoft/graphite-statsd
volumes:
- ./data/statsd:/opt/graphite/storage
- ./data/graphite-config:/opt/graphite/conf
- ./data/statsd-config/config.json:/opt/statsd/config.js

View File

@ -11,6 +11,7 @@ services:
links: links:
- redis - redis
- rabbitmq - rabbitmq
- statsd
redis: redis:
extends: extends:
@ -21,3 +22,10 @@ services:
extends: extends:
file: docker-compose.base.yml file: docker-compose.base.yml
service: rabbitmq service: rabbitmq
statsd:
extends:
file: docker-compose.base.yml
service: statsd
ports:
- "8123:80"

View File

@ -0,0 +1,14 @@
package data
type SignedTexturesResponse struct {
Id string `json:"id"`
Name string `json:"name"`
IsEly bool `json:"ely,omitempty"`
Props []Property `json:"properties"`
}
type Property struct {
Name string `json:"name"`
Signature string `json:"signature,omitempty"`
Value string `json:"value"`
}

View File

@ -12,14 +12,17 @@ import (
) )
type SkinItem struct { type SkinItem struct {
UserId int `json:"userId"` UserId int `json:"userId"`
Username string `json:"username"` Uuid string `json:"uuid"`
SkinId int `json:"skinId"` Username string `json:"username"`
Url string `json:"url"` SkinId int `json:"skinId"`
Is1_8 bool `json:"is1_8"` Url string `json:"url"`
IsSlim bool `json:"isSlim"` Is1_8 bool `json:"is1_8"`
Hash string `json:"hash"` IsSlim bool `json:"isSlim"`
oldUsername string Hash string `json:"hash"`
MojangTextures string `json:"mojangTextures"`
MojangSignature string `json:"mojangSignature"`
oldUsername string
} }
const accountIdToUsernameKey string = "hash:username-to-account-id" const accountIdToUsernameKey string = "hash:username-to-account-id"
@ -62,13 +65,16 @@ func (s *SkinItem) Delete() {
func FindSkinByUsername(username string) (SkinItem, error) { func FindSkinByUsername(username string) (SkinItem, error) {
var record SkinItem; var record SkinItem;
services.Logger.IncCounter("storage.query", 1)
response := services.RedisPool.Cmd("GET", tools.BuildKey(username)); response := services.RedisPool.Cmd("GET", tools.BuildKey(username));
if (response.IsType(redis.Nil)) { if (response.IsType(redis.Nil)) {
services.Logger.IncCounter("storage.not_found", 1)
return record, SkinNotFound{username} return record, SkinNotFound{username}
} }
result, err := response.Str() result, err := response.Str()
if (err == nil) { if (err == nil) {
services.Logger.IncCounter("storage.found", 1)
decodeErr := json.Unmarshal([]byte(result), &record) decodeErr := json.Unmarshal([]byte(result), &record)
if (decodeErr != nil) { if (decodeErr != nil) {
log.Println("Cannot decode record data") log.Println("Cannot decode record data")

View File

@ -2,18 +2,21 @@ package routes
import ( import (
"io" "io"
"log"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"elyby/minecraft-skinsystem/lib/tools" "elyby/minecraft-skinsystem/lib/tools"
"elyby/minecraft-skinsystem/lib/data" "elyby/minecraft-skinsystem/lib/data"
"elyby/minecraft-skinsystem/lib/services"
) )
func Cape(response http.ResponseWriter, request *http.Request) { func Cape(response http.ResponseWriter, request *http.Request) {
if (mux.Vars(request)["converted"] == "") {
services.Logger.IncCounter("capes.request", 1)
}
username := tools.ParseUsername(mux.Vars(request)["username"]) username := tools.ParseUsername(mux.Vars(request)["username"])
log.Println("request cape for username " + username)
rec, err := data.FindCapeByUsername(username) rec, err := data.FindCapeByUsername(username)
if (err != nil) { if (err != nil) {
http.Redirect(response, request, "http://skins.minecraft.net/MinecraftCloaks/" + username + ".png", 301) http.Redirect(response, request, "http://skins.minecraft.net/MinecraftCloaks/" + username + ".png", 301)
@ -24,6 +27,7 @@ func Cape(response http.ResponseWriter, request *http.Request) {
} }
func CapeGET(w http.ResponseWriter, r *http.Request) { func CapeGET(w http.ResponseWriter, r *http.Request) {
services.Logger.IncCounter("capes.get_request", 1)
username := r.URL.Query().Get("name") username := r.URL.Query().Get("name")
if username == "" { if username == "" {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
@ -31,5 +35,6 @@ func CapeGET(w http.ResponseWriter, r *http.Request) {
} }
mux.Vars(r)["username"] = username mux.Vars(r)["username"] = username
mux.Vars(r)["converted"] = "1"
Cape(w, r) Cape(w, r)
} }

View File

@ -1,7 +1,6 @@
package routes package routes
import ( import (
"log"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -14,7 +13,6 @@ const defaultHash = "default"
func Face(w http.ResponseWriter, r *http.Request) { func Face(w http.ResponseWriter, r *http.Request) {
username := tools.ParseUsername(mux.Vars(r)["username"]) username := tools.ParseUsername(mux.Vars(r)["username"])
log.Println("request skin for username " + username);
rec, err := data.FindSkinByUsername(username) rec, err := data.FindSkinByUsername(username)
var hash string var hash string
if (err != nil || rec.SkinId == 0) { if (err != nil || rec.SkinId == 0) {

View File

@ -4,6 +4,8 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"elyby/minecraft-skinsystem/lib/services"
) )
// Метод-наследие от первой версии системы скинов. // Метод-наследие от первой версии системы скинов.
@ -18,11 +20,15 @@ func MinecraftPHP(w http.ResponseWriter, r *http.Request) {
} }
mux.Vars(r)["username"] = username mux.Vars(r)["username"] = username
mux.Vars(r)["converted"] = "1"
switch required { switch required {
case "skin": Skin(w, r) case "skin":
case "cloack": Cape(w, r) services.Logger.IncCounter("skins.minecraft-php-request", 1)
default: { Skin(w, r)
case "cloack":
services.Logger.IncCounter("capes.minecraft-php-request", 1)
Cape(w, r)
default:
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
} }
}
} }

View File

@ -1,29 +0,0 @@
package routes
import (
"net/http"
"strconv"
"elyby/minecraft-skinsystem/lib/data"
)
func SetSkin(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("X-Ely-key")
if key != "43fd2ce61b3f5704dfd729c1f2d6ffdb" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("Nice try"))
return
}
skin := new(data.SkinItem)
skin.Username = r.PostFormValue("username")
skin.UserId, _ = strconv.Atoi(r.PostFormValue("userId"))
skin.SkinId, _ = strconv.Atoi(r.PostFormValue("skinId"))
skin.Hash = r.PostFormValue("hash")
skin.Is1_8, _ = strconv.ParseBool(r.PostFormValue("is1_8"))
skin.IsSlim, _ = strconv.ParseBool(r.PostFormValue("isSlim"))
skin.Url = r.PostFormValue("url")
skin.Save()
w.Write([]byte("OK"))
}

View File

@ -0,0 +1,44 @@
package routes
import (
"strings"
"net/http"
"encoding/json"
"github.com/gorilla/mux"
"elyby/minecraft-skinsystem/lib/data"
"elyby/minecraft-skinsystem/lib/tools"
"elyby/minecraft-skinsystem/lib/services"
)
func SignedTextures(w http.ResponseWriter, r *http.Request) {
services.Logger.IncCounter("signed_textures.request", 1)
username := tools.ParseUsername(mux.Vars(r)["username"])
rec, err := data.FindSkinByUsername(username)
if (err != nil || rec.SkinId == 0 || rec.MojangTextures == "") {
w.WriteHeader(http.StatusNoContent)
return
}
responseData:= data.SignedTexturesResponse{
Id: strings.Replace(rec.Uuid, "-", "", -1),
Name: rec.Username,
Props: []data.Property{
{
Name: "textures",
Signature: rec.MojangSignature,
Value: rec.MojangTextures,
},
{
Name: "ely",
Value: "but why are you asking?",
},
},
}
response,_ := json.Marshal(responseData)
w.Header().Set("Content-Type", "application/json")
w.Write(response)
}

View File

@ -1,18 +1,21 @@
package routes package routes
import ( import (
"log"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"elyby/minecraft-skinsystem/lib/tools" "elyby/minecraft-skinsystem/lib/tools"
"elyby/minecraft-skinsystem/lib/data" "elyby/minecraft-skinsystem/lib/data"
"elyby/minecraft-skinsystem/lib/services"
) )
func Skin(w http.ResponseWriter, r *http.Request) { func Skin(w http.ResponseWriter, r *http.Request) {
if (mux.Vars(r)["converted"] == "") {
services.Logger.IncCounter("skins.request", 1)
}
username := tools.ParseUsername(mux.Vars(r)["username"]) username := tools.ParseUsername(mux.Vars(r)["username"])
log.Println("request skin for username " + username);
rec, err := data.FindSkinByUsername(username) rec, err := data.FindSkinByUsername(username)
if (err != nil) { if (err != nil) {
http.Redirect(w, r, "http://skins.minecraft.net/MinecraftSkins/" + username + ".png", 301) http.Redirect(w, r, "http://skins.minecraft.net/MinecraftSkins/" + username + ".png", 301)
@ -23,6 +26,7 @@ func Skin(w http.ResponseWriter, r *http.Request) {
} }
func SkinGET(w http.ResponseWriter, r *http.Request) { func SkinGET(w http.ResponseWriter, r *http.Request) {
services.Logger.IncCounter("skins.get_request", 1)
username := r.URL.Query().Get("name") username := r.URL.Query().Get("name")
if username == "" { if username == "" {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
@ -30,5 +34,6 @@ func SkinGET(w http.ResponseWriter, r *http.Request) {
} }
mux.Vars(r)["username"] = username mux.Vars(r)["username"] = username
mux.Vars(r)["converted"] = "1"
Skin(w, r) Skin(w, r)
} }

View File

@ -13,8 +13,8 @@ import (
) )
func Textures(w http.ResponseWriter, r *http.Request) { func Textures(w http.ResponseWriter, r *http.Request) {
services.Logger.IncCounter("textures.request", 1)
username := tools.ParseUsername(mux.Vars(r)["username"]) username := tools.ParseUsername(mux.Vars(r)["username"])
log.Println("request textures for username " + username)
rec, err := data.FindSkinByUsername(username) rec, err := data.FindSkinByUsername(username)
if (err != nil || rec.SkinId == 0) { if (err != nil || rec.SkinId == 0) {

View File

@ -4,6 +4,7 @@ import (
"github.com/mediocregopher/radix.v2/pool" "github.com/mediocregopher/radix.v2/pool"
"github.com/streadway/amqp" "github.com/streadway/amqp"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mono83/slf/wd"
) )
var Router *mux.Router var Router *mux.Router
@ -13,3 +14,5 @@ var RedisPool *pool.Pool
var RabbitMQChannel *amqp.Channel var RabbitMQChannel *amqp.Channel
var RootFolder string var RootFolder string
var Logger wd.Watchdog

View File

@ -2,11 +2,12 @@ package worker
import ( import (
"elyby/minecraft-skinsystem/lib/data" "elyby/minecraft-skinsystem/lib/data"
"log" "elyby/minecraft-skinsystem/lib/services"
) )
func handleChangeUsername(model usernameChanged) (bool) { func handleChangeUsername(model usernameChanged) (bool) {
if (model.OldUsername == "") { if (model.OldUsername == "") {
services.Logger.IncCounter("worker.change_username.empty_old_username", 1)
record := data.SkinItem{ record := data.SkinItem{
UserId: model.AccountId, UserId: model.AccountId,
Username: model.NewUsername, Username: model.NewUsername,
@ -19,7 +20,7 @@ func handleChangeUsername(model usernameChanged) (bool) {
record, err := data.FindSkinByUsername(model.OldUsername) record, err := data.FindSkinByUsername(model.OldUsername)
if (err != nil) { if (err != nil) {
log.Println("Exit by not found record") services.Logger.IncCounter("worker.change_username.username_not_found", 1)
// TODO: я не уверен, что это валидное поведение // TODO: я не уверен, что это валидное поведение
// Суть в том, что здесь может возникнуть ошибка в том случае, если записи в базе нету // Суть в том, что здесь может возникнуть ошибка в том случае, если записи в базе нету
// а значит его нужно, как минимум, зарегистрировать // а значит его нужно, как минимум, зарегистрировать
@ -29,7 +30,7 @@ func handleChangeUsername(model usernameChanged) (bool) {
record.Username = model.NewUsername record.Username = model.NewUsername
record.Save() record.Save()
log.Println("all saved!") services.Logger.IncCounter("worker.change_username.processed", 1)
return true return true
} }
@ -37,16 +38,22 @@ func handleChangeUsername(model usernameChanged) (bool) {
func handleSkinChanged(model skinChanged) (bool) { func handleSkinChanged(model skinChanged) (bool) {
record, err := data.FindSkinById(model.AccountId) record, err := data.FindSkinById(model.AccountId)
if (err != nil) { if (err != nil) {
services.Logger.IncCounter("worker.skin_changed.id_not_found", 1)
return true return true
} }
record.Uuid = model.Uuid
record.SkinId = model.SkinId record.SkinId = model.SkinId
record.Hash = model.Hash record.Hash = model.Hash
record.Is1_8 = model.Is1_8 record.Is1_8 = model.Is1_8
record.IsSlim = model.IsSlim record.IsSlim = model.IsSlim
record.Url = model.Url record.Url = model.Url
record.MojangTextures = model.MojangTextures
record.MojangSignature = model.MojangSignature
record.Save() record.Save()
services.Logger.IncCounter("worker.skin_changed.processed", 1)
return true return true
} }

View File

@ -7,11 +7,14 @@ type usernameChanged struct {
} }
type skinChanged struct { type skinChanged struct {
AccountId int `json:"userId"` AccountId int `json:"userId"`
SkinId int `json:"skinId"` Uuid string `json:"uuid"`
OldSkinId int `json:"oldSkinId"` SkinId int `json:"skinId"`
Hash string `json:"hash"` OldSkinId int `json:"oldSkinId"`
Is1_8 bool `json:"is1_8"` Hash string `json:"hash"`
IsSlim bool `json:"isSlim"` Is1_8 bool `json:"is1_8"`
Url string `json:"url"` IsSlim bool `json:"isSlim"`
Url string `json:"url"`
MojangTextures string `json:"mojangTextures"`
MojangSignature string `json:"mojangSignature"`
} }

View File

@ -11,6 +11,10 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/streadway/amqp" "github.com/streadway/amqp"
"github.com/mediocregopher/radix.v2/pool" "github.com/mediocregopher/radix.v2/pool"
"github.com/mono83/slf/wd"
"github.com/mono83/slf/rays"
"github.com/mono83/slf/recievers/ansi"
"github.com/mono83/slf/recievers/statsd"
"elyby/minecraft-skinsystem/lib/routes" "elyby/minecraft-skinsystem/lib/routes"
"elyby/minecraft-skinsystem/lib/services" "elyby/minecraft-skinsystem/lib/services"
@ -56,10 +60,32 @@ func main() {
} }
log.Println("Connected to rabbitmq channel") log.Println("Connected to rabbitmq channel")
// statsd
var statsdString = os.Getenv("STATSD_ADDR")
if (statsdString != "") {
log.Println("Connecting to statsd")
hostname, _ := os.Hostname()
statsdReceiver, err := statsd.NewReceiver(statsd.Config{
Address: statsdString,
Prefix: "ely.skinsystem." + hostname + ".app.",
FlushEvery: 1,
})
if (err != nil) {
log.Fatal("statsd connection error")
}
wd.AddReceiver(statsdReceiver)
} else {
wd.AddReceiver(ansi.New(true, true, false))
}
logger := wd.New("", "").WithParams(rays.Host)
router := mux.NewRouter().StrictSlash(true) router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/skins/{username}", routes.Skin).Methods("GET").Name("skins") router.HandleFunc("/skins/{username}", routes.Skin).Methods("GET").Name("skins")
router.HandleFunc("/cloaks/{username}", routes.Cape).Methods("GET").Name("cloaks") router.HandleFunc("/cloaks/{username}", routes.Cape).Methods("GET").Name("cloaks")
router.HandleFunc("/textures/{username}", routes.Textures).Methods("GET").Name("textures") router.HandleFunc("/textures/{username}", routes.Textures).Methods("GET").Name("textures")
router.HandleFunc("/textures/signed/{username}", routes.SignedTextures).Methods("GET").Name("signedTextures")
router.HandleFunc("/skins/{username}/face", routes.Face).Methods("GET").Name("faces") router.HandleFunc("/skins/{username}/face", routes.Face).Methods("GET").Name("faces")
router.HandleFunc("/skins/{username}/face.png", routes.Face).Methods("GET").Name("faces") router.HandleFunc("/skins/{username}/face.png", routes.Face).Methods("GET").Name("faces")
// Legacy // Legacy
@ -69,12 +95,10 @@ func main() {
// 404 // 404
router.NotFoundHandler = http.HandlerFunc(routes.NotFound) router.NotFoundHandler = http.HandlerFunc(routes.NotFound)
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter.HandleFunc("/user/{username}/skin", routes.SetSkin).Methods("POST")
services.Router = router services.Router = router
services.RedisPool = redisPool services.RedisPool = redisPool
services.RabbitMQChannel = rabbitChannel services.RabbitMQChannel = rabbitChannel
services.Logger = logger
_, file, _, _ := runtime.Caller(0) _, file, _, _ := runtime.Caller(0)
services.RootFolder = filepath.Dir(file) services.RootFolder = filepath.Dir(file)