mirror of
https://github.com/elyby/chrly.git
synced 2024-11-30 02:32:19 +05:30
Реорганизация пакета daemon в http.
Упразднён пакет utils. Удалён обработчик minecraft.php (legacy с самого-самого начала Ely.by) Добавлены тесты для всех api-запросов.
This commit is contained in:
parent
ec461efe34
commit
04714543b8
@ -8,9 +8,8 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"elyby/minecraft-skinsystem/bootstrap"
|
"elyby/minecraft-skinsystem/bootstrap"
|
||||||
"elyby/minecraft-skinsystem/daemon"
|
|
||||||
"elyby/minecraft-skinsystem/db"
|
"elyby/minecraft-skinsystem/db"
|
||||||
"elyby/minecraft-skinsystem/ui"
|
"elyby/minecraft-skinsystem/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
var serveCmd = &cobra.Command{
|
var serveCmd = &cobra.Command{
|
||||||
@ -41,15 +40,14 @@ var serveCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
logger.Info("Capes repository successfully initialized")
|
logger.Info("Capes repository successfully initialized")
|
||||||
|
|
||||||
cfg := &daemon.Config{
|
cfg := &http.Config{
|
||||||
ListenSpec: fmt.Sprintf("%s:%d", viper.GetString("server.host"), viper.GetInt("server.port")),
|
ListenSpec: fmt.Sprintf("%s:%d", viper.GetString("server.host"), viper.GetInt("server.port")),
|
||||||
SkinsRepo: skinsRepo,
|
SkinsRepo: skinsRepo,
|
||||||
CapesRepo: capesRepo,
|
CapesRepo: capesRepo,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
UI: ui.Config{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := daemon.Run(cfg); err != nil {
|
if err := cfg.Run(); err != nil {
|
||||||
logger.Error(fmt.Sprintf("Error in main(): %v", err))
|
logger.Error(fmt.Sprintf("Error in main(): %v", err))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
package daemon
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/mono83/slf/wd"
|
|
||||||
|
|
||||||
"elyby/minecraft-skinsystem/interfaces"
|
|
||||||
"elyby/minecraft-skinsystem/ui"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
ListenSpec string
|
|
||||||
|
|
||||||
SkinsRepo interfaces.SkinsRepository
|
|
||||||
CapesRepo interfaces.CapesRepository
|
|
||||||
Logger wd.Watchdog
|
|
||||||
UI ui.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func Run(cfg *Config) error {
|
|
||||||
cfg.Logger.Info(fmt.Sprintf("Starting, HTTP on: %s\n", cfg.ListenSpec))
|
|
||||||
|
|
||||||
uiService, err := ui.NewUiService(cfg.Logger, cfg.SkinsRepo, cfg.CapesRepo)
|
|
||||||
if err != nil {
|
|
||||||
cfg.Logger.Error(fmt.Sprintf("Error creating ui services: %v\n", err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", cfg.ListenSpec)
|
|
||||||
if err != nil {
|
|
||||||
cfg.Logger.Error(fmt.Sprintf("Error creating listener: %v\n", err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Start(cfg.UI, uiService, listener)
|
|
||||||
|
|
||||||
waitForSignal(cfg)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForSignal(cfg *Config) {
|
|
||||||
ch := make(chan os.Signal)
|
|
||||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
s := <-ch
|
|
||||||
cfg.Logger.Info(fmt.Sprintf("Got signal: %v, exiting.", s))
|
|
||||||
}
|
|
@ -5,8 +5,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"elyby/minecraft-skinsystem/model"
|
|
||||||
"elyby/minecraft-skinsystem/interfaces"
|
"elyby/minecraft-skinsystem/interfaces"
|
||||||
|
"elyby/minecraft-skinsystem/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FilesystemFactory struct {
|
type FilesystemFactory struct {
|
||||||
@ -42,19 +42,18 @@ type filesStorage struct {
|
|||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repository *filesStorage) FindByUsername(username string) (model.Cape, error) {
|
func (repository *filesStorage) FindByUsername(username string) (*model.Cape, error) {
|
||||||
var record model.Cape
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return record, &CapeNotFoundError{username}
|
return nil, &CapeNotFoundError{username}
|
||||||
}
|
}
|
||||||
|
|
||||||
capePath := path.Join(repository.path, strings.ToLower(username) + ".png")
|
capePath := path.Join(repository.path, strings.ToLower(username) + ".png")
|
||||||
file, err := os.Open(capePath)
|
file, err := os.Open(capePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return record, &CapeNotFoundError{username}
|
return nil, &CapeNotFoundError{username}
|
||||||
}
|
}
|
||||||
|
|
||||||
record.File = file
|
return &model.Cape{
|
||||||
|
File: file,
|
||||||
return record, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,30 @@
|
|||||||
package ui
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"elyby/minecraft-skinsystem/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *uiService) Cape(response http.ResponseWriter, request *http.Request) {
|
func (cfg *Config) Cape(response http.ResponseWriter, request *http.Request) {
|
||||||
if mux.Vars(request)["converted"] == "" {
|
if mux.Vars(request)["converted"] == "" {
|
||||||
s.logger.IncCounter("capes.request", 1)
|
cfg.Logger.IncCounter("capes.request", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
username := utils.ParseUsername(mux.Vars(request)["username"])
|
username := parseUsername(mux.Vars(request)["username"])
|
||||||
rec, err := s.capesRepo.FindByUsername(username)
|
rec, err := cfg.CapesRepo.FindByUsername(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)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Header.Set("Content-Type", "image/png")
|
request.Header.Set("Content-Type", "image/png")
|
||||||
io.Copy(response, rec.File)
|
io.Copy(response, rec.File)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *uiService) CapeGET(response http.ResponseWriter, request *http.Request) {
|
func (cfg *Config) CapeGET(response http.ResponseWriter, request *http.Request) {
|
||||||
s.logger.IncCounter("capes.get_request", 1)
|
cfg.Logger.IncCounter("capes.get_request", 1)
|
||||||
username := request.URL.Query().Get("name")
|
username := request.URL.Query().Get("name")
|
||||||
if username == "" {
|
if username == "" {
|
||||||
response.WriteHeader(http.StatusBadRequest)
|
response.WriteHeader(http.StatusBadRequest)
|
||||||
@ -35,5 +34,5 @@ func (s *uiService) CapeGET(response http.ResponseWriter, request *http.Request)
|
|||||||
mux.Vars(request)["username"] = username
|
mux.Vars(request)["username"] = username
|
||||||
mux.Vars(request)["converted"] = "1"
|
mux.Vars(request)["converted"] = "1"
|
||||||
|
|
||||||
s.Cape(response, request)
|
cfg.Cape(response, request)
|
||||||
}
|
}
|
138
http/cape_test.go
Normal file
138
http/cape_test.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
testify "github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"elyby/minecraft-skinsystem/db"
|
||||||
|
"elyby/minecraft-skinsystem/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig_Cape(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, _, capesRepo, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
cape := createCape()
|
||||||
|
|
||||||
|
capesRepo.EXPECT().FindByUsername("mocked_username").Return(&model.Cape{
|
||||||
|
File: bytes.NewReader(cape),
|
||||||
|
}, nil)
|
||||||
|
wd.EXPECT().IncCounter("capes.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/cloaks/mocked_username", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(200, resp.StatusCode)
|
||||||
|
responseData, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
assert.Equal(cape, responseData)
|
||||||
|
assert.Equal("image/png", resp.Header.Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_Cape2(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, _, capesRepo, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
capesRepo.EXPECT().FindByUsername("notch").Return(nil, &db.CapeNotFoundError{"notch"})
|
||||||
|
wd.EXPECT().IncCounter("capes.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/cloaks/notch", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(301, resp.StatusCode)
|
||||||
|
assert.Equal("http://skins.minecraft.net/MinecraftCloaks/notch.png", resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_CapeGET(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, _, capesRepo, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
cape := createCape()
|
||||||
|
|
||||||
|
capesRepo.EXPECT().FindByUsername("mocked_username").Return(&model.Cape{
|
||||||
|
File: bytes.NewReader(cape),
|
||||||
|
}, nil)
|
||||||
|
wd.EXPECT().IncCounter("capes.request", int64(1)).Times(0)
|
||||||
|
wd.EXPECT().IncCounter("capes.get_request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/cloaks?name=mocked_username", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(200, resp.StatusCode)
|
||||||
|
responseData, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
assert.Equal(cape, responseData)
|
||||||
|
assert.Equal("image/png", resp.Header.Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_CapeGET2(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, _, capesRepo, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
capesRepo.EXPECT().FindByUsername("notch").Return(nil, &db.CapeNotFoundError{"notch"})
|
||||||
|
wd.EXPECT().IncCounter("capes.request", int64(1)).Times(0)
|
||||||
|
wd.EXPECT().IncCounter("capes.get_request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/cloaks?name=notch", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(301, resp.StatusCode)
|
||||||
|
assert.Equal("http://skins.minecraft.net/MinecraftCloaks/notch.png", resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_CapeGET3(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/cloaks/?name=notch", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
(&Config{}).CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(301, resp.StatusCode)
|
||||||
|
assert.Equal("http://skinsystem.ely.by/cloaks?name=notch", resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cape md5: 424ff79dce9940af89c28ad80de8aaad
|
||||||
|
func createCape() []byte {
|
||||||
|
img := image.NewAlpha(image.Rect(0, 0, 64, 32))
|
||||||
|
writer := &bytes.Buffer{}
|
||||||
|
png.Encode(writer, img)
|
||||||
|
|
||||||
|
pngBytes, _ := ioutil.ReadAll(writer)
|
||||||
|
|
||||||
|
return pngBytes
|
||||||
|
}
|
27
http/face.go
Normal file
27
http/face.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultHash = "default"
|
||||||
|
|
||||||
|
func (cfg *Config) Face(response http.ResponseWriter, request *http.Request) {
|
||||||
|
cfg.Logger.IncCounter("faces.request", 1)
|
||||||
|
username := parseUsername(mux.Vars(request)["username"])
|
||||||
|
rec, err := cfg.SkinsRepo.FindByUsername(username)
|
||||||
|
var hash string
|
||||||
|
if err != nil || rec.SkinId == 0 {
|
||||||
|
hash = defaultHash
|
||||||
|
} else {
|
||||||
|
hash = rec.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(response, request, buildElyUrl(buildFaceUrl(hash)), 301)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFaceUrl(hash string) string {
|
||||||
|
return "/minecraft/skin_buffer/faces/" + hash + ".png"
|
||||||
|
}
|
53
http/face_test.go
Normal file
53
http/face_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
testify "github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"elyby/minecraft-skinsystem/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig_Face(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, _, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("mock_user").Return(createSkinModel("mock_user", false), nil)
|
||||||
|
wd.EXPECT().IncCounter("faces.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/skins/mock_user/face.png", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(301, resp.StatusCode)
|
||||||
|
assert.Equal("http://ely.by/minecraft/skin_buffer/faces/55d2a8848764f5ff04012cdb093458bd.png", resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_Face2(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, _, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("mock_user").Return(nil, &db.SkinNotFoundError{"mock_user"})
|
||||||
|
wd.EXPECT().IncCounter("faces.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/skins/mock_user/face.png", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(301, resp.StatusCode)
|
||||||
|
assert.Equal("http://ely.by/minecraft/skin_buffer/faces/default.png", resp.Header.Get("Location"))
|
||||||
|
}
|
91
http/http.go
Normal file
91
http/http.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/mono83/slf/wd"
|
||||||
|
|
||||||
|
"elyby/minecraft-skinsystem/interfaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ListenSpec string
|
||||||
|
|
||||||
|
SkinsRepo interfaces.SkinsRepository
|
||||||
|
CapesRepo interfaces.CapesRepository
|
||||||
|
Logger wd.Watchdog
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) Run() error {
|
||||||
|
cfg.Logger.Info(fmt.Sprintf("Starting, HTTP on: %s\n", cfg.ListenSpec))
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", cfg.ListenSpec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
ReadTimeout: 60 * time.Second,
|
||||||
|
WriteTimeout: 60 * time.Second,
|
||||||
|
MaxHeaderBytes: 1 << 16,
|
||||||
|
Handler: cfg.CreateHandler(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go server.Serve(listener)
|
||||||
|
|
||||||
|
s := waitForSignal()
|
||||||
|
cfg.Logger.Info(fmt.Sprintf("Got signal: %v, exiting.", s))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) CreateHandler() http.Handler {
|
||||||
|
router := mux.NewRouter().StrictSlash(true)
|
||||||
|
|
||||||
|
router.HandleFunc("/skins/{username}", cfg.Skin).Methods("GET")
|
||||||
|
router.HandleFunc("/cloaks/{username}", cfg.Cape).Methods("GET").Name("cloaks")
|
||||||
|
router.HandleFunc("/textures/{username}", cfg.Textures).Methods("GET")
|
||||||
|
router.HandleFunc("/textures/signed/{username}", cfg.SignedTextures).Methods("GET")
|
||||||
|
router.HandleFunc("/skins/{username}/face", cfg.Face).Methods("GET")
|
||||||
|
router.HandleFunc("/skins/{username}/face.png", cfg.Face).Methods("GET")
|
||||||
|
// Legacy
|
||||||
|
router.HandleFunc("/skins", cfg.SkinGET).Methods("GET")
|
||||||
|
router.HandleFunc("/cloaks", cfg.CapeGET).Methods("GET")
|
||||||
|
// 404
|
||||||
|
router.NotFoundHandler = http.HandlerFunc(cfg.NotFound)
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUsername(username string) string {
|
||||||
|
const suffix = ".png"
|
||||||
|
if strings.HasSuffix(username, suffix) {
|
||||||
|
username = strings.TrimSuffix(username, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return username
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildElyUrl(route string) string {
|
||||||
|
prefix := "http://ely.by"
|
||||||
|
if !strings.HasPrefix(route, prefix) {
|
||||||
|
route = prefix + route
|
||||||
|
}
|
||||||
|
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForSignal() os.Signal {
|
||||||
|
ch := make(chan os.Signal)
|
||||||
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
return <-ch
|
||||||
|
}
|
40
http/http_test.go
Normal file
40
http/http_test.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
testify "github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"elyby/minecraft-skinsystem/http/mock_wd"
|
||||||
|
"elyby/minecraft-skinsystem/interfaces/mock_interfaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseUsername(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
assert.Equal("test", parseUsername("test.png"), "Function should trim .png at end")
|
||||||
|
assert.Equal("test", parseUsername("test"), "Function should return string itself, if it not contains .png at end")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildElyUrl(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
assert.Equal("http://ely.by/route", buildElyUrl("/route"), "Function should add prefix to the provided relative url.")
|
||||||
|
assert.Equal("http://ely.by/test/route", buildElyUrl("http://ely.by/test/route"), "Function should do not add prefix to the provided prefixed url.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupMocks(ctrl *gomock.Controller) (
|
||||||
|
*Config,
|
||||||
|
*mock_interfaces.MockSkinsRepository,
|
||||||
|
*mock_interfaces.MockCapesRepository,
|
||||||
|
*mock_wd.MockWatchdog,
|
||||||
|
) {
|
||||||
|
skinsRepo := mock_interfaces.NewMockSkinsRepository(ctrl)
|
||||||
|
capesRepo := mock_interfaces.NewMockCapesRepository(ctrl)
|
||||||
|
wd := mock_wd.NewMockWatchdog(ctrl)
|
||||||
|
|
||||||
|
return &Config{
|
||||||
|
SkinsRepo: skinsRepo,
|
||||||
|
CapesRepo: capesRepo,
|
||||||
|
Logger: wd,
|
||||||
|
}, skinsRepo, capesRepo, wd
|
||||||
|
}
|
218
http/mock_wd/mock_wd.go
Normal file
218
http/mock_wd/mock_wd.go
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/mono83/slf/wd (interfaces: Watchdog)
|
||||||
|
|
||||||
|
package mock_wd
|
||||||
|
|
||||||
|
import (
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
slf "github.com/mono83/slf"
|
||||||
|
wd "github.com/mono83/slf/wd"
|
||||||
|
reflect "reflect"
|
||||||
|
time "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockWatchdog is a mock of Watchdog interface
|
||||||
|
type MockWatchdog struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockWatchdogMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockWatchdogMockRecorder is the mock recorder for MockWatchdog
|
||||||
|
type MockWatchdogMockRecorder struct {
|
||||||
|
mock *MockWatchdog
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockWatchdog creates a new mock instance
|
||||||
|
func NewMockWatchdog(ctrl *gomock.Controller) *MockWatchdog {
|
||||||
|
mock := &MockWatchdog{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockWatchdogMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (_m *MockWatchdog) EXPECT() *MockWatchdogMockRecorder {
|
||||||
|
return _m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert mocks base method
|
||||||
|
func (_m *MockWatchdog) Alert(_param0 string, _param1 ...slf.Param) {
|
||||||
|
_s := []interface{}{_param0}
|
||||||
|
for _, _x := range _param1 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
_m.ctrl.Call(_m, "Alert", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert indicates an expected call of Alert
|
||||||
|
func (_mr *MockWatchdogMockRecorder) Alert(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0}, arg1...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Alert", reflect.TypeOf((*MockWatchdog)(nil).Alert), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug mocks base method
|
||||||
|
func (_m *MockWatchdog) Debug(_param0 string, _param1 ...slf.Param) {
|
||||||
|
_s := []interface{}{_param0}
|
||||||
|
for _, _x := range _param1 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
_m.ctrl.Call(_m, "Debug", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug indicates an expected call of Debug
|
||||||
|
func (_mr *MockWatchdogMockRecorder) Debug(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0}, arg1...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Debug", reflect.TypeOf((*MockWatchdog)(nil).Debug), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emergency mocks base method
|
||||||
|
func (_m *MockWatchdog) Emergency(_param0 string, _param1 ...slf.Param) {
|
||||||
|
_s := []interface{}{_param0}
|
||||||
|
for _, _x := range _param1 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
_m.ctrl.Call(_m, "Emergency", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emergency indicates an expected call of Emergency
|
||||||
|
func (_mr *MockWatchdogMockRecorder) Emergency(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0}, arg1...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Emergency", reflect.TypeOf((*MockWatchdog)(nil).Emergency), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error mocks base method
|
||||||
|
func (_m *MockWatchdog) Error(_param0 string, _param1 ...slf.Param) {
|
||||||
|
_s := []interface{}{_param0}
|
||||||
|
for _, _x := range _param1 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
_m.ctrl.Call(_m, "Error", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error indicates an expected call of Error
|
||||||
|
func (_mr *MockWatchdogMockRecorder) Error(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0}, arg1...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Error", reflect.TypeOf((*MockWatchdog)(nil).Error), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncCounter mocks base method
|
||||||
|
func (_m *MockWatchdog) IncCounter(_param0 string, _param1 int64, _param2 ...slf.Param) {
|
||||||
|
_s := []interface{}{_param0, _param1}
|
||||||
|
for _, _x := range _param2 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
_m.ctrl.Call(_m, "IncCounter", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncCounter indicates an expected call of IncCounter
|
||||||
|
func (_mr *MockWatchdogMockRecorder) IncCounter(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0, arg1}, arg2...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "IncCounter", reflect.TypeOf((*MockWatchdog)(nil).IncCounter), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info mocks base method
|
||||||
|
func (_m *MockWatchdog) Info(_param0 string, _param1 ...slf.Param) {
|
||||||
|
_s := []interface{}{_param0}
|
||||||
|
for _, _x := range _param1 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
_m.ctrl.Call(_m, "Info", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info indicates an expected call of Info
|
||||||
|
func (_mr *MockWatchdogMockRecorder) Info(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0}, arg1...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Info", reflect.TypeOf((*MockWatchdog)(nil).Info), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordTimer mocks base method
|
||||||
|
func (_m *MockWatchdog) RecordTimer(_param0 string, _param1 time.Duration, _param2 ...slf.Param) {
|
||||||
|
_s := []interface{}{_param0, _param1}
|
||||||
|
for _, _x := range _param2 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
_m.ctrl.Call(_m, "RecordTimer", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordTimer indicates an expected call of RecordTimer
|
||||||
|
func (_mr *MockWatchdogMockRecorder) RecordTimer(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0, arg1}, arg2...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "RecordTimer", reflect.TypeOf((*MockWatchdog)(nil).RecordTimer), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer mocks base method
|
||||||
|
func (_m *MockWatchdog) Timer(_param0 string, _param1 ...slf.Param) slf.Timer {
|
||||||
|
_s := []interface{}{_param0}
|
||||||
|
for _, _x := range _param1 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
ret := _m.ctrl.Call(_m, "Timer", _s...)
|
||||||
|
ret0, _ := ret[0].(slf.Timer)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer indicates an expected call of Timer
|
||||||
|
func (_mr *MockWatchdogMockRecorder) Timer(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0}, arg1...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Timer", reflect.TypeOf((*MockWatchdog)(nil).Timer), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace mocks base method
|
||||||
|
func (_m *MockWatchdog) Trace(_param0 string, _param1 ...slf.Param) {
|
||||||
|
_s := []interface{}{_param0}
|
||||||
|
for _, _x := range _param1 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
_m.ctrl.Call(_m, "Trace", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace indicates an expected call of Trace
|
||||||
|
func (_mr *MockWatchdogMockRecorder) Trace(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0}, arg1...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Trace", reflect.TypeOf((*MockWatchdog)(nil).Trace), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateGauge mocks base method
|
||||||
|
func (_m *MockWatchdog) UpdateGauge(_param0 string, _param1 int64, _param2 ...slf.Param) {
|
||||||
|
_s := []interface{}{_param0, _param1}
|
||||||
|
for _, _x := range _param2 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
_m.ctrl.Call(_m, "UpdateGauge", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateGauge indicates an expected call of UpdateGauge
|
||||||
|
func (_mr *MockWatchdogMockRecorder) UpdateGauge(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0, arg1}, arg2...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "UpdateGauge", reflect.TypeOf((*MockWatchdog)(nil).UpdateGauge), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning mocks base method
|
||||||
|
func (_m *MockWatchdog) Warning(_param0 string, _param1 ...slf.Param) {
|
||||||
|
_s := []interface{}{_param0}
|
||||||
|
for _, _x := range _param1 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
_m.ctrl.Call(_m, "Warning", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning indicates an expected call of Warning
|
||||||
|
func (_mr *MockWatchdogMockRecorder) Warning(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0}, arg1...)
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Warning", reflect.TypeOf((*MockWatchdog)(nil).Warning), _s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithParams mocks base method
|
||||||
|
func (_m *MockWatchdog) WithParams(_param0 ...slf.Param) wd.Watchdog {
|
||||||
|
_s := []interface{}{}
|
||||||
|
for _, _x := range _param0 {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
ret := _m.ctrl.Call(_m, "WithParams", _s...)
|
||||||
|
ret0, _ := ret[0].(wd.Watchdog)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithParams indicates an expected call of WithParams
|
||||||
|
func (_mr *MockWatchdogMockRecorder) WithParams(arg0 ...interface{}) *gomock.Call {
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "WithParams", reflect.TypeOf((*MockWatchdog)(nil).WithParams), arg0...)
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
package ui
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NotFound(response http.ResponseWriter, request *http.Request) {
|
func (cfg *Config) NotFound(response http.ResponseWriter, request *http.Request) {
|
||||||
json, _ := json.Marshal(map[string]string{
|
data, _ := json.Marshal(map[string]string{
|
||||||
"status": "404",
|
"status": "404",
|
||||||
"message": "Not Found",
|
"message": "Not Found",
|
||||||
"link": "http://docs.ely.by/skin-system.html",
|
"link": "http://docs.ely.by/skin-system.html",
|
||||||
@ -14,5 +14,5 @@ func NotFound(response http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
response.Header().Set("Content-Type", "application/json")
|
response.Header().Set("Content-Type", "application/json")
|
||||||
response.WriteHeader(http.StatusNotFound)
|
response.WriteHeader(http.StatusNotFound)
|
||||||
response.Write(json)
|
response.Write(data)
|
||||||
}
|
}
|
28
http/not_found_test.go
Normal file
28
http/not_found_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
testify "github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig_NotFound(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
(&Config{}).CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(404, resp.StatusCode)
|
||||||
|
assert.Equal("application/json", resp.Header.Get("Content-Type"))
|
||||||
|
response, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
assert.JSONEq(`{
|
||||||
|
"status": "404",
|
||||||
|
"message": "Not Found",
|
||||||
|
"link": "http://docs.ely.by/skin-system.html"
|
||||||
|
}`, string(response))
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ui
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -6,8 +6,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"elyby/minecraft-skinsystem/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type signedTexturesResponse struct {
|
type signedTexturesResponse struct {
|
||||||
@ -23,11 +21,11 @@ type property struct {
|
|||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *uiService) SignedTextures(response http.ResponseWriter, request *http.Request) {
|
func (cfg *Config) SignedTextures(response http.ResponseWriter, request *http.Request) {
|
||||||
s.logger.IncCounter("signed_textures.request", 1)
|
cfg.Logger.IncCounter("signed_textures.request", 1)
|
||||||
username := utils.ParseUsername(mux.Vars(request)["username"])
|
username := parseUsername(mux.Vars(request)["username"])
|
||||||
|
|
||||||
rec, err := s.skinsRepo.FindByUsername(username)
|
rec, err := cfg.SkinsRepo.FindByUsername(username)
|
||||||
if err != nil || rec.SkinId == 0 || rec.MojangTextures == "" {
|
if err != nil || rec.SkinId == 0 || rec.MojangTextures == "" {
|
||||||
response.WriteHeader(http.StatusNoContent)
|
response.WriteHeader(http.StatusNoContent)
|
||||||
return
|
return
|
71
http/signed_textures_test.go
Normal file
71
http/signed_textures_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
testify "github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"elyby/minecraft-skinsystem/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig_SignedTextures(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, _, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("mock_user").Return(createSkinModel("mock_user", false), nil)
|
||||||
|
wd.EXPECT().IncCounter("signed_textures.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/textures/signed/mock_user", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(200, resp.StatusCode)
|
||||||
|
assert.Equal("application/json", resp.Header.Get("Content-Type"))
|
||||||
|
response, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
assert.JSONEq(`{
|
||||||
|
"id": "0f657aa8bfbe415db7005750090d3af3",
|
||||||
|
"name": "mock_user",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "textures",
|
||||||
|
"signature": "mocked signature",
|
||||||
|
"value": "mocked textures base64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ely",
|
||||||
|
"value": "but why are you asking?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, string(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_SignedTextures2(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, _, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("mock_user").Return(nil, &db.SkinNotFoundError{})
|
||||||
|
wd.EXPECT().IncCounter("signed_textures.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/textures/signed/mock_user", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(204, resp.StatusCode)
|
||||||
|
response, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
assert.Equal("", string(response))
|
||||||
|
}
|
36
http/skin.go
Normal file
36
http/skin.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cfg *Config) Skin(response http.ResponseWriter, request *http.Request) {
|
||||||
|
if mux.Vars(request)["converted"] == "" {
|
||||||
|
cfg.Logger.IncCounter("skins.request", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
username := parseUsername(mux.Vars(request)["username"])
|
||||||
|
rec, err := cfg.SkinsRepo.FindByUsername(username)
|
||||||
|
if err != nil || rec.SkinId == 0 {
|
||||||
|
http.Redirect(response, request, "http://skins.minecraft.net/MinecraftSkins/" + username + ".png", 301)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(response, request, buildElyUrl(rec.Url), 301)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) SkinGET(response http.ResponseWriter, request *http.Request) {
|
||||||
|
cfg.Logger.IncCounter("skins.get_request", 1)
|
||||||
|
username := request.URL.Query().Get("name")
|
||||||
|
if username == "" {
|
||||||
|
response.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mux.Vars(request)["username"] = username
|
||||||
|
mux.Vars(request)["converted"] = "1"
|
||||||
|
|
||||||
|
cfg.Skin(response, request)
|
||||||
|
}
|
124
http/skin_test.go
Normal file
124
http/skin_test.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
testify "github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"elyby/minecraft-skinsystem/db"
|
||||||
|
"elyby/minecraft-skinsystem/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig_Skin(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, _, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("mock_user").Return(createSkinModel("mock_user", false), nil)
|
||||||
|
wd.EXPECT().IncCounter("skins.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/skins/mock_user", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(301, resp.StatusCode)
|
||||||
|
assert.Equal("http://ely.by/minecraft/skins/skin.png", resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_Skin2(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, _, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("notch").Return(nil, &db.SkinNotFoundError{"notch"})
|
||||||
|
wd.EXPECT().IncCounter("skins.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/skins/notch", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(301, resp.StatusCode)
|
||||||
|
assert.Equal("http://skins.minecraft.net/MinecraftSkins/notch.png", resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_SkinGET(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, _, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("mock_user").Return(createSkinModel("mock_user", false), nil)
|
||||||
|
wd.EXPECT().IncCounter("skins.get_request", int64(1))
|
||||||
|
wd.EXPECT().IncCounter("skins.request", int64(1)).Times(0)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/skins?name=mock_user", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(301, resp.StatusCode)
|
||||||
|
assert.Equal("http://ely.by/minecraft/skins/skin.png", resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_SkinGET2(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, _, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("notch").Return(nil, &db.SkinNotFoundError{"notch"})
|
||||||
|
wd.EXPECT().IncCounter("skins.get_request", int64(1))
|
||||||
|
wd.EXPECT().IncCounter("skins.request", int64(1)).Times(0)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/skins?name=notch", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(301, resp.StatusCode)
|
||||||
|
assert.Equal("http://skins.minecraft.net/MinecraftSkins/notch.png", resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_SkinGET3(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/skins/?name=notch", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
(&Config{}).CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(301, resp.StatusCode)
|
||||||
|
assert.Equal("http://skinsystem.ely.by/skins?name=notch", resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSkinModel(username string, isSlim bool) *model.Skin {
|
||||||
|
return &model.Skin{
|
||||||
|
Username: username,
|
||||||
|
Uuid: "0f657aa8-bfbe-415d-b700-5750090d3af3",
|
||||||
|
SkinId: 1,
|
||||||
|
Hash: "55d2a8848764f5ff04012cdb093458bd",
|
||||||
|
Url: "http://ely.by/minecraft/skins/skin.png",
|
||||||
|
MojangTextures: "mocked textures base64",
|
||||||
|
MojangSignature: "mocked signature",
|
||||||
|
IsSlim: isSlim,
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
package ui
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"elyby/minecraft-skinsystem/model"
|
"elyby/minecraft-skinsystem/model"
|
||||||
"elyby/minecraft-skinsystem/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type texturesResponse struct {
|
type texturesResponse struct {
|
||||||
@ -34,16 +34,20 @@ type Cape struct {
|
|||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *uiService) Textures(response http.ResponseWriter, request *http.Request) {
|
func (cfg *Config) Textures(response http.ResponseWriter, request *http.Request) {
|
||||||
s.logger.IncCounter("textures.request", 1)
|
cfg.Logger.IncCounter("textures.request", 1)
|
||||||
username := utils.ParseUsername(mux.Vars(request)["username"])
|
username := parseUsername(mux.Vars(request)["username"])
|
||||||
|
|
||||||
skin, err := s.skinsRepo.FindByUsername(username)
|
skin, err := cfg.SkinsRepo.FindByUsername(username)
|
||||||
if err != nil || skin.SkinId == 0 {
|
if err != nil || skin.SkinId == 0 {
|
||||||
|
if skin == nil {
|
||||||
|
skin = &model.Skin{}
|
||||||
|
}
|
||||||
|
|
||||||
skin.Url = "http://skins.minecraft.net/MinecraftSkins/" + username + ".png"
|
skin.Url = "http://skins.minecraft.net/MinecraftSkins/" + username + ".png"
|
||||||
skin.Hash = string(utils.BuildNonElyTexturesHash(username))
|
skin.Hash = string(buildNonElyTexturesHash(username))
|
||||||
} else {
|
} else {
|
||||||
skin.Url = utils.BuildElyUrl(skin.Url)
|
skin.Url = buildElyUrl(skin.Url)
|
||||||
}
|
}
|
||||||
|
|
||||||
textures := texturesResponse{
|
textures := texturesResponse{
|
||||||
@ -59,35 +63,42 @@ func (s *uiService) Textures(response http.ResponseWriter, request *http.Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cape, err := s.capesRepo.FindByUsername(username)
|
cape, err := cfg.CapesRepo.FindByUsername(username)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO: восстановить функционал получения ссылки на плащ
|
|
||||||
// capeUrl, err := services.Router.Get("cloaks").URL("username", username)
|
|
||||||
capeUrl := "/capes/" + username
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
var scheme string = "http://"
|
var scheme string = "http://"
|
||||||
if request.TLS != nil {
|
if request.TLS != nil {
|
||||||
scheme = "https://"
|
scheme = "https://"
|
||||||
}
|
}
|
||||||
|
|
||||||
textures.Cape = &Cape{
|
textures.Cape = &Cape{
|
||||||
// Url: scheme + request.Host + capeUrl.String(),
|
Url: scheme + request.Host + "/cloaks/" + username,
|
||||||
Url: scheme + request.Host + capeUrl,
|
|
||||||
Hash: calculateCapeHash(cape),
|
Hash: calculateCapeHash(cape),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responseData,_ := json.Marshal(textures)
|
responseData, _ := json.Marshal(textures)
|
||||||
response.Header().Set("Content-Type", "application/json")
|
response.Header().Set("Content-Type", "application/json")
|
||||||
response.Write(responseData)
|
response.Write(responseData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateCapeHash(cape model.Cape) string {
|
func calculateCapeHash(cape *model.Cape) string {
|
||||||
hasher := md5.New()
|
hasher := md5.New()
|
||||||
io.Copy(hasher, cape.File)
|
io.Copy(hasher, cape.File)
|
||||||
|
|
||||||
return hex.EncodeToString(hasher.Sum(nil))
|
return hex.EncodeToString(hasher.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildNonElyTexturesHash(username string) string {
|
||||||
|
hour := getCurrentHour()
|
||||||
|
hasher := md5.New()
|
||||||
|
hasher.Write([]byte("non-ely-" + strconv.FormatInt(hour, 10) + "-" + username))
|
||||||
|
|
||||||
|
return hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeNow = time.Now
|
||||||
|
|
||||||
|
func getCurrentHour() int64 {
|
||||||
|
n := timeNow()
|
||||||
|
return time.Date(n.Year(), n.Month(), n.Day(), n.Hour(), 0, 0, 0, time.UTC).Unix()
|
||||||
|
}
|
166
http/textures_test.go
Normal file
166
http/textures_test.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
testify "github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"elyby/minecraft-skinsystem/db"
|
||||||
|
"elyby/minecraft-skinsystem/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig_Textures(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, capesRepo, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("mock_user").Return(createSkinModel("mock_user", false), nil)
|
||||||
|
capesRepo.EXPECT().FindByUsername("mock_user").Return(nil, &db.CapeNotFoundError{"mock_user"})
|
||||||
|
wd.EXPECT().IncCounter("textures.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/textures/mock_user", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(200, resp.StatusCode)
|
||||||
|
assert.Equal("application/json", resp.Header.Get("Content-Type"))
|
||||||
|
response, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
assert.JSONEq(`{
|
||||||
|
"SKIN": {
|
||||||
|
"url": "http://ely.by/minecraft/skins/skin.png",
|
||||||
|
"hash": "55d2a8848764f5ff04012cdb093458bd"
|
||||||
|
}
|
||||||
|
}`, string(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_Textures2(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, capesRepo, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("mock_user").Return(createSkinModel("mock_user", true), nil)
|
||||||
|
capesRepo.EXPECT().FindByUsername("mock_user").Return(nil, &db.CapeNotFoundError{"mock_user"})
|
||||||
|
wd.EXPECT().IncCounter("textures.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/textures/mock_user", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(200, resp.StatusCode)
|
||||||
|
assert.Equal("application/json", resp.Header.Get("Content-Type"))
|
||||||
|
response, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
assert.JSONEq(`{
|
||||||
|
"SKIN": {
|
||||||
|
"url": "http://ely.by/minecraft/skins/skin.png",
|
||||||
|
"hash": "55d2a8848764f5ff04012cdb093458bd",
|
||||||
|
"metadata": {
|
||||||
|
"model": "slim"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`, string(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_Textures3(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, capesRepo, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("mock_user").Return(createSkinModel("mock_user", false), nil)
|
||||||
|
capesRepo.EXPECT().FindByUsername("mock_user").Return(&model.Cape{
|
||||||
|
File: bytes.NewReader(createCape()),
|
||||||
|
}, nil)
|
||||||
|
wd.EXPECT().IncCounter("textures.request", int64(1))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/textures/mock_user", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(200, resp.StatusCode)
|
||||||
|
assert.Equal("application/json", resp.Header.Get("Content-Type"))
|
||||||
|
response, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
assert.JSONEq(`{
|
||||||
|
"SKIN": {
|
||||||
|
"url": "http://ely.by/minecraft/skins/skin.png",
|
||||||
|
"hash": "55d2a8848764f5ff04012cdb093458bd"
|
||||||
|
},
|
||||||
|
"CAPE": {
|
||||||
|
"url": "http://skinsystem.ely.by/cloaks/mock_user",
|
||||||
|
"hash": "424ff79dce9940af89c28ad80de8aaad"
|
||||||
|
}
|
||||||
|
}`, string(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_Textures4(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
config, skinsRepo, capesRepo, wd := setupMocks(ctrl)
|
||||||
|
|
||||||
|
skinsRepo.EXPECT().FindByUsername("notch").Return(nil, &db.SkinNotFoundError{})
|
||||||
|
capesRepo.EXPECT().FindByUsername("notch").Return(nil, &db.CapeNotFoundError{})
|
||||||
|
wd.EXPECT().IncCounter("textures.request", int64(1))
|
||||||
|
timeNow = func() time.Time {
|
||||||
|
return time.Date(2017, time.August, 20, 0, 15, 54, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://skinsystem.ely.by/textures/notch", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
config.CreateHandler().ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
assert.Equal(200, resp.StatusCode)
|
||||||
|
assert.Equal("application/json", resp.Header.Get("Content-Type"))
|
||||||
|
response, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
assert.JSONEq(`{
|
||||||
|
"SKIN": {
|
||||||
|
"url": "http://skins.minecraft.net/MinecraftSkins/notch.png",
|
||||||
|
"hash": "5923cf3f7fa170a279e4d7a9483cfc52"
|
||||||
|
}
|
||||||
|
}`, string(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildNonElyTexturesHash(t *testing.T) {
|
||||||
|
assert := testify.New(t)
|
||||||
|
timeNow = func() time.Time {
|
||||||
|
return time.Date(2017, time.November, 30, 16, 15, 34, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal("686d788a5353cb636e8fdff727634d88", buildNonElyTexturesHash("username"), "Function should return fixed hash by username-time pair")
|
||||||
|
assert.Equal("fb876f761683a10accdb17d403cef64c", buildNonElyTexturesHash("another-username"), "Function should return fixed hash by username-time pair")
|
||||||
|
|
||||||
|
timeNow = func() time.Time {
|
||||||
|
return time.Date(2017, time.November, 30, 16, 20, 12, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal("686d788a5353cb636e8fdff727634d88", buildNonElyTexturesHash("username"), "Function should do not change it's value if hour the same")
|
||||||
|
assert.Equal("fb876f761683a10accdb17d403cef64c", buildNonElyTexturesHash("another-username"), "Function should return fixed hash by username-time pair")
|
||||||
|
|
||||||
|
timeNow = func() time.Time {
|
||||||
|
return time.Date(2017, time.November, 30, 17, 1, 3, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal("42277892fd24bc0ed86285b3bb8b8fad", buildNonElyTexturesHash("username"), "Function should change it's value if hour changed")
|
||||||
|
}
|
107
interfaces/mock_interfaces/mock_interfaces.go
Normal file
107
interfaces/mock_interfaces/mock_interfaces.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: interfaces/repositories.go
|
||||||
|
|
||||||
|
package mock_interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
model "elyby/minecraft-skinsystem/model"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
reflect "reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockSkinsRepository is a mock of SkinsRepository interface
|
||||||
|
type MockSkinsRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockSkinsRepositoryMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockSkinsRepositoryMockRecorder is the mock recorder for MockSkinsRepository
|
||||||
|
type MockSkinsRepositoryMockRecorder struct {
|
||||||
|
mock *MockSkinsRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockSkinsRepository creates a new mock instance
|
||||||
|
func NewMockSkinsRepository(ctrl *gomock.Controller) *MockSkinsRepository {
|
||||||
|
mock := &MockSkinsRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockSkinsRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (_m *MockSkinsRepository) EXPECT() *MockSkinsRepositoryMockRecorder {
|
||||||
|
return _m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByUsername mocks base method
|
||||||
|
func (_m *MockSkinsRepository) FindByUsername(username string) (*model.Skin, error) {
|
||||||
|
ret := _m.ctrl.Call(_m, "FindByUsername", username)
|
||||||
|
ret0, _ := ret[0].(*model.Skin)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByUsername indicates an expected call of FindByUsername
|
||||||
|
func (_mr *MockSkinsRepositoryMockRecorder) FindByUsername(arg0 interface{}) *gomock.Call {
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "FindByUsername", reflect.TypeOf((*MockSkinsRepository)(nil).FindByUsername), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByUserId mocks base method
|
||||||
|
func (_m *MockSkinsRepository) FindByUserId(id int) (*model.Skin, error) {
|
||||||
|
ret := _m.ctrl.Call(_m, "FindByUserId", id)
|
||||||
|
ret0, _ := ret[0].(*model.Skin)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByUserId indicates an expected call of FindByUserId
|
||||||
|
func (_mr *MockSkinsRepositoryMockRecorder) FindByUserId(arg0 interface{}) *gomock.Call {
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "FindByUserId", reflect.TypeOf((*MockSkinsRepository)(nil).FindByUserId), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save mocks base method
|
||||||
|
func (_m *MockSkinsRepository) Save(skin *model.Skin) error {
|
||||||
|
ret := _m.ctrl.Call(_m, "Save", skin)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save indicates an expected call of Save
|
||||||
|
func (_mr *MockSkinsRepositoryMockRecorder) Save(arg0 interface{}) *gomock.Call {
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Save", reflect.TypeOf((*MockSkinsRepository)(nil).Save), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCapesRepository is a mock of CapesRepository interface
|
||||||
|
type MockCapesRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockCapesRepositoryMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCapesRepositoryMockRecorder is the mock recorder for MockCapesRepository
|
||||||
|
type MockCapesRepositoryMockRecorder struct {
|
||||||
|
mock *MockCapesRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockCapesRepository creates a new mock instance
|
||||||
|
func NewMockCapesRepository(ctrl *gomock.Controller) *MockCapesRepository {
|
||||||
|
mock := &MockCapesRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockCapesRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (_m *MockCapesRepository) EXPECT() *MockCapesRepositoryMockRecorder {
|
||||||
|
return _m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByUsername mocks base method
|
||||||
|
func (_m *MockCapesRepository) FindByUsername(username string) (*model.Cape, error) {
|
||||||
|
ret := _m.ctrl.Call(_m, "FindByUsername", username)
|
||||||
|
ret0, _ := ret[0].(*model.Cape)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByUsername indicates an expected call of FindByUsername
|
||||||
|
func (_mr *MockCapesRepositoryMockRecorder) FindByUsername(arg0 interface{}) *gomock.Call {
|
||||||
|
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "FindByUsername", reflect.TypeOf((*MockCapesRepository)(nil).FindByUsername), arg0)
|
||||||
|
}
|
@ -11,5 +11,5 @@ type SkinsRepository interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CapesRepository interface {
|
type CapesRepository interface {
|
||||||
FindByUsername(username string) (model.Cape, error)
|
FindByUsername(username string) (*model.Cape, error)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
type Cape struct {
|
type Cape struct {
|
||||||
File *os.File // TODO: нужно абстрагироваться в отдельный файл с инфой о скине
|
File io.Reader
|
||||||
}
|
}
|
||||||
|
28
ui/face.go
28
ui/face.go
@ -1,28 +0,0 @@
|
|||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"elyby/minecraft-skinsystem/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultHash = "default"
|
|
||||||
|
|
||||||
func (s *uiService) Face(response http.ResponseWriter, request *http.Request) {
|
|
||||||
username := utils.ParseUsername(mux.Vars(request)["username"])
|
|
||||||
rec, err := s.skinsRepo.FindByUsername(username)
|
|
||||||
var hash string
|
|
||||||
if err != nil || rec.SkinId == 0 {
|
|
||||||
hash = defaultHash
|
|
||||||
} else {
|
|
||||||
hash = rec.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(response, request, utils.BuildElyUrl(buildFaceUrl(hash)), 301)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildFaceUrl(hash string) string {
|
|
||||||
return "/minecraft/skin_buffer/faces/" + hash + ".png"
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Метод-наследие от первой версии системы скинов.
|
|
||||||
// Всё ещё иногда используется
|
|
||||||
// Просто конвертируем данные и отправляем их в основной обработчик
|
|
||||||
func (s *uiService) MinecraftPHP(response http.ResponseWriter, request *http.Request) {
|
|
||||||
username := request.URL.Query().Get("name")
|
|
||||||
required := request.URL.Query().Get("type")
|
|
||||||
if username == "" || required == "" {
|
|
||||||
response.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mux.Vars(request)["username"] = username
|
|
||||||
mux.Vars(request)["converted"] = "1"
|
|
||||||
|
|
||||||
switch required {
|
|
||||||
case "skin":
|
|
||||||
s.logger.IncCounter("skins.minecraft-php-request", 1)
|
|
||||||
s.Skin(response, request)
|
|
||||||
case "cloack":
|
|
||||||
s.logger.IncCounter("capes.minecraft-php-request", 1)
|
|
||||||
s.Cape(response, request)
|
|
||||||
default:
|
|
||||||
response.WriteHeader(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mono83/slf/wd"
|
|
||||||
"elyby/minecraft-skinsystem/interfaces"
|
|
||||||
)
|
|
||||||
|
|
||||||
type uiService struct {
|
|
||||||
logger wd.Watchdog
|
|
||||||
skinsRepo interfaces.SkinsRepository
|
|
||||||
capesRepo interfaces.CapesRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUiService(
|
|
||||||
logger wd.Watchdog,
|
|
||||||
skinsRepo interfaces.SkinsRepository,
|
|
||||||
capesRepo interfaces.CapesRepository,
|
|
||||||
) (*uiService, error) {
|
|
||||||
return &uiService{
|
|
||||||
logger: logger,
|
|
||||||
skinsRepo: skinsRepo,
|
|
||||||
capesRepo: capesRepo,
|
|
||||||
}, nil
|
|
||||||
}
|
|
38
ui/skin.go
38
ui/skin.go
@ -1,38 +0,0 @@
|
|||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"elyby/minecraft-skinsystem/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *uiService) Skin(response http.ResponseWriter, request *http.Request) {
|
|
||||||
if mux.Vars(request)["converted"] == "" {
|
|
||||||
s.logger.IncCounter("skins.request", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
username := utils.ParseUsername(mux.Vars(request)["username"])
|
|
||||||
rec, err := s.skinsRepo.FindByUsername(username)
|
|
||||||
if err != nil {
|
|
||||||
http.Redirect(response, request, "http://skins.minecraft.net/MinecraftSkins/" + username + ".png", 301)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(response, request, utils.BuildElyUrl(rec.Url), 301)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *uiService) SkinGET(response http.ResponseWriter, request *http.Request) {
|
|
||||||
s.logger.IncCounter("skins.get_request", 1)
|
|
||||||
username := request.URL.Query().Get("name")
|
|
||||||
if username == "" {
|
|
||||||
response.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mux.Vars(request)["username"] = username
|
|
||||||
mux.Vars(request)["converted"] = "1"
|
|
||||||
|
|
||||||
s.Skin(response, request)
|
|
||||||
}
|
|
39
ui/ui.go
39
ui/ui.go
@ -1,39 +0,0 @@
|
|||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func Start(cfg Config, s *uiService, lst net.Listener) {
|
|
||||||
router := mux.NewRouter().StrictSlash(true)
|
|
||||||
|
|
||||||
router.HandleFunc("/skins/{username}", s.Skin).Methods("GET")
|
|
||||||
router.HandleFunc("/cloaks/{username}", s.Cape).Methods("GET")
|
|
||||||
router.HandleFunc("/textures/{username}", s.Textures).Methods("GET")
|
|
||||||
router.HandleFunc("/textures/signed/{username}", s.SignedTextures).Methods("GET")
|
|
||||||
router.HandleFunc("/skins/{username}/face", s.Face).Methods("GET")
|
|
||||||
router.HandleFunc("/skins/{username}/face.png", s.Face).Methods("GET")
|
|
||||||
// Legacy
|
|
||||||
router.HandleFunc("/minecraft.php", s.MinecraftPHP).Methods("GET")
|
|
||||||
router.HandleFunc("/skins/", s.SkinGET).Methods("GET")
|
|
||||||
router.HandleFunc("/cloaks/", s.CapeGET).Methods("GET")
|
|
||||||
// 404
|
|
||||||
router.NotFoundHandler = http.HandlerFunc(NotFound)
|
|
||||||
|
|
||||||
server := &http.Server{
|
|
||||||
ReadTimeout: 60 * time.Second,
|
|
||||||
WriteTimeout: 60 * time.Second,
|
|
||||||
MaxHeaderBytes: 1 << 16,
|
|
||||||
Handler: router,
|
|
||||||
}
|
|
||||||
|
|
||||||
go server.Serve(lst)
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseUsername(username string) string {
|
|
||||||
const suffix = ".png"
|
|
||||||
if strings.HasSuffix(username, suffix) {
|
|
||||||
username = strings.TrimSuffix(username, suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
return username
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildNonElyTexturesHash(username string) string {
|
|
||||||
hour := getCurrentHour()
|
|
||||||
hasher := md5.New()
|
|
||||||
hasher.Write([]byte("non-ely-" + strconv.FormatInt(hour, 10) + "-" + username))
|
|
||||||
|
|
||||||
return hex.EncodeToString(hasher.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildElyUrl(route string) string {
|
|
||||||
prefix := "http://ely.by"
|
|
||||||
if !strings.HasPrefix(route, prefix) {
|
|
||||||
route = prefix + route
|
|
||||||
}
|
|
||||||
|
|
||||||
return route
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeNow = time.Now
|
|
||||||
|
|
||||||
func getCurrentHour() int64 {
|
|
||||||
n := timeNow()
|
|
||||||
return time.Date(n.Year(), n.Month(), n.Day(), n.Hour(), 0, 0, 0, time.UTC).Unix()
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseUsername(t *testing.T) {
|
|
||||||
if ParseUsername("test.png") != "test" {
|
|
||||||
t.Error("Function should trim .png at end")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ParseUsername("test") != "test" {
|
|
||||||
t.Error("Function should return string itself, if it not contains .png at end")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildNonElyTexturesHash(t *testing.T) {
|
|
||||||
timeNow = func() time.Time {
|
|
||||||
return time.Date(2017, time.November, 30, 16, 15, 34, 0, time.UTC)
|
|
||||||
}
|
|
||||||
|
|
||||||
if BuildNonElyTexturesHash("username") != "686d788a5353cb636e8fdff727634d88" {
|
|
||||||
t.Error("Function should return fixed hash by username-time pair")
|
|
||||||
}
|
|
||||||
|
|
||||||
if BuildNonElyTexturesHash("another-username") != "fb876f761683a10accdb17d403cef64c" {
|
|
||||||
t.Error("Function should return fixed hash by username-time pair")
|
|
||||||
}
|
|
||||||
|
|
||||||
timeNow = func() time.Time {
|
|
||||||
return time.Date(2017, time.November, 30, 16, 20, 12, 0, time.UTC)
|
|
||||||
}
|
|
||||||
|
|
||||||
if BuildNonElyTexturesHash("username") != "686d788a5353cb636e8fdff727634d88" {
|
|
||||||
t.Error("Function should do not change it's value if hour the same")
|
|
||||||
}
|
|
||||||
|
|
||||||
if BuildNonElyTexturesHash("another-username") != "fb876f761683a10accdb17d403cef64c" {
|
|
||||||
t.Error("Function should return fixed hash by username-time pair")
|
|
||||||
}
|
|
||||||
|
|
||||||
timeNow = func() time.Time {
|
|
||||||
return time.Date(2017, time.November, 30, 17, 1, 3, 0, time.UTC)
|
|
||||||
}
|
|
||||||
|
|
||||||
if BuildNonElyTexturesHash("username") != "42277892fd24bc0ed86285b3bb8b8fad" {
|
|
||||||
t.Error("Function should change it's value if hour changed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildElyUrl(t *testing.T) {
|
|
||||||
if BuildElyUrl("/route") != "http://ely.by/route" {
|
|
||||||
t.Error("Function should add prefix to the provided relative url.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if BuildElyUrl("http://ely.by/test/route") != "http://ely.by/test/route" {
|
|
||||||
t.Error("Function should do not add prefix to the provided prefixed url.")
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user