mirror of
https://github.com/elyby/chrly.git
synced 2024-12-22 21:19:55 +05:30
Переработка структуры проекта
This commit is contained in:
parent
e090d04dc7
commit
07903cf9c8
75
cmd/root.go
Normal file
75
cmd/root.go
Normal file
@ -0,0 +1,75 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "test",
|
||||
Short: "A brief description of your application",
|
||||
Long: `A longer description that spans multiple lines and likely contains
|
||||
examples and usage of using your application. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.`,
|
||||
// Uncomment the following line if your bare application
|
||||
// has an action associated with it:
|
||||
// Run: func(cmd *cobra.Command, args []string) { },
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
if err := RootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
// Cobra supports persistent flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.test.yaml)")
|
||||
|
||||
// Cobra also supports local flags, which will only run
|
||||
// when this action is called directly.
|
||||
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Search config in home directory with name ".test" (without extension).
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigName(".test")
|
||||
}
|
||||
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
}
|
78
cmd/serve.go
Normal file
78
cmd/serve.go
Normal file
@ -0,0 +1,78 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"elyby/minecraft-skinsystem/daemon"
|
||||
"elyby/minecraft-skinsystem/ui"
|
||||
|
||||
"elyby/minecraft-skinsystem/db/skins/redis"
|
||||
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"elyby/minecraft-skinsystem/db/capes/files"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/mono83/slf/rays"
|
||||
"github.com/mono83/slf/recievers/ansi"
|
||||
"github.com/mono83/slf/wd"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// serveCmd represents the serve command
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Запускает сервер системы скинов",
|
||||
Long: "Более длинное описание пока не было придумано",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// TODO: извлечь все инициализации зависимостей в парсер конфигурации
|
||||
|
||||
// Logger
|
||||
wd.AddReceiver(ansi.New(true, true, false))
|
||||
logger := wd.New("", "").WithParams(rays.Host)
|
||||
|
||||
// Skins repository
|
||||
logger.Info("Connecting to redis")
|
||||
skinsRepoCfg := &redis.Config{
|
||||
//Addr: "redis:6379",
|
||||
Addr: "localhost:16379",
|
||||
PollSize: 10,
|
||||
}
|
||||
skinsRepo, err := skinsRepoCfg.CreateRepo()
|
||||
if err != nil {
|
||||
logger.Emergency(fmt.Sprintf("Error on creating skins repo: %v", err))
|
||||
return
|
||||
}
|
||||
logger.Info("Successfully connected to redis")
|
||||
|
||||
// Capes repository
|
||||
_, file, _, _ := runtime.Caller(0)
|
||||
capesRepoCfg := &files.Config{
|
||||
StoragePath: path.Join(filepath.Dir(file), "data/capes"),
|
||||
}
|
||||
capesRepo, err := capesRepoCfg.CreateRepo()
|
||||
if err != nil {
|
||||
logger.Emergency(fmt.Sprintf("Error on creating capes repo: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
cfg := &daemon.Config{
|
||||
ListenSpec: "localhost:35644",
|
||||
SkinsRepo: skinsRepo,
|
||||
CapesRepo: capesRepo,
|
||||
Logger: logger,
|
||||
UI: ui.Config{},
|
||||
}
|
||||
|
||||
if err := daemon.Run(cfg); err != nil {
|
||||
logger.Error(fmt.Sprintf("Error in main(): %v", err))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(serveCmd)
|
||||
}
|
54
daemon/http.go
Normal file
54
daemon/http.go
Normal file
@ -0,0 +1,54 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"elyby/minecraft-skinsystem/model"
|
||||
"elyby/minecraft-skinsystem/ui"
|
||||
|
||||
"fmt"
|
||||
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/mono83/slf/wd"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ListenSpec string
|
||||
|
||||
SkinsRepo model.SkinsRepository
|
||||
CapesRepo model.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))
|
||||
}
|
7
db/capes/config.go
Normal file
7
db/capes/config.go
Normal file
@ -0,0 +1,7 @@
|
||||
package capes
|
||||
|
||||
import "elyby/minecraft-skinsystem/model"
|
||||
|
||||
type CapesRepositoryConfig interface {
|
||||
CreateRepo() (model.CapesRepository, error)
|
||||
}
|
11
db/capes/files/db.go
Normal file
11
db/capes/files/db.go
Normal file
@ -0,0 +1,11 @@
|
||||
package files
|
||||
|
||||
import "elyby/minecraft-skinsystem/model"
|
||||
|
||||
type Config struct {
|
||||
StoragePath string
|
||||
}
|
||||
|
||||
func (cfg *Config) CreateRepo() (model.CapesRepository, error) {
|
||||
return &filesDb{path: cfg.StoragePath}, nil
|
||||
}
|
11
db/capes/files/errors.go
Normal file
11
db/capes/files/errors.go
Normal file
@ -0,0 +1,11 @@
|
||||
package files
|
||||
|
||||
import "fmt"
|
||||
|
||||
type CapeNotFound struct {
|
||||
Who string
|
||||
}
|
||||
|
||||
func (e CapeNotFound) Error() string {
|
||||
return fmt.Sprintf("Cape file not found. Required username \"%v\"", e.Who)
|
||||
}
|
26
db/capes/files/repository.go
Normal file
26
db/capes/files/repository.go
Normal file
@ -0,0 +1,26 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"elyby/minecraft-skinsystem/model"
|
||||
)
|
||||
|
||||
type filesDb struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (repository *filesDb) FindByUsername(username string) (model.Cape, error) {
|
||||
var record model.Cape
|
||||
capePath := path.Join(repository.path, strings.ToLower(username) + ".png")
|
||||
file, err := os.Open(capePath)
|
||||
if err != nil {
|
||||
return record, CapeNotFound{username}
|
||||
}
|
||||
|
||||
record.File = file
|
||||
|
||||
return record, nil
|
||||
}
|
7
db/skins/config.go
Normal file
7
db/skins/config.go
Normal file
@ -0,0 +1,7 @@
|
||||
package skins
|
||||
|
||||
import "elyby/minecraft-skinsystem/model"
|
||||
|
||||
type SkinsRepositoryConfig interface {
|
||||
CreateRepo() (model.SkinsRepository, error)
|
||||
}
|
58
db/skins/redis/commands.go
Normal file
58
db/skins/redis/commands.go
Normal file
@ -0,0 +1,58 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"elyby/minecraft-skinsystem/model"
|
||||
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/mediocregopher/radix.v2/redis"
|
||||
"github.com/mediocregopher/radix.v2/util"
|
||||
)
|
||||
|
||||
type redisDb struct {
|
||||
conn util.Cmder
|
||||
}
|
||||
|
||||
const accountIdToUsernameKey string = "hash:username-to-account-id"
|
||||
|
||||
func (db *redisDb) FindByUsername(username string) (model.Skin, error) {
|
||||
var record model.Skin
|
||||
redisKey := buildKey(username)
|
||||
response := db.conn.Cmd("GET", redisKey)
|
||||
if response.IsType(redis.Nil) {
|
||||
return record, SkinNotFound{username}
|
||||
}
|
||||
|
||||
encodedResult, err := response.Bytes()
|
||||
if err == nil {
|
||||
result, err := zlibDecode(encodedResult)
|
||||
if err != nil {
|
||||
log.Println("Cannot uncompress zlib for key " + redisKey)
|
||||
goto finish
|
||||
}
|
||||
|
||||
err = json.Unmarshal(result, &record)
|
||||
if err != nil {
|
||||
log.Println("Cannot decode record data for key" + redisKey)
|
||||
goto finish
|
||||
}
|
||||
|
||||
record.OldUsername = record.Username
|
||||
}
|
||||
|
||||
finish:
|
||||
|
||||
return record, err
|
||||
}
|
||||
|
||||
func (db *redisDb) FindByUserId(id int) (model.Skin, error) {
|
||||
response := db.conn.Cmd("HGET", accountIdToUsernameKey, id)
|
||||
if response.IsType(redis.Nil) {
|
||||
return model.Skin{}, SkinNotFound{"unknown"}
|
||||
}
|
||||
|
||||
username, _ := response.Str()
|
||||
|
||||
return db.FindByUsername(username)
|
||||
}
|
23
db/skins/redis/db.go
Normal file
23
db/skins/redis/db.go
Normal file
@ -0,0 +1,23 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"elyby/minecraft-skinsystem/model"
|
||||
|
||||
"github.com/mediocregopher/radix.v2/pool"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Addr string
|
||||
PollSize int
|
||||
}
|
||||
|
||||
func (cfg *Config) CreateRepo() (model.SkinsRepository, error) {
|
||||
conn, err := pool.New("tcp", cfg.Addr, cfg.PollSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: здесь можно запустить горутину по восстановлению соединения
|
||||
|
||||
return &redisDb{conn: conn}, err
|
||||
}
|
12
db/skins/redis/errors.go
Normal file
12
db/skins/redis/errors.go
Normal file
@ -0,0 +1,12 @@
|
||||
package redis
|
||||
|
||||
import "fmt"
|
||||
|
||||
type SkinNotFound struct {
|
||||
Who string
|
||||
}
|
||||
|
||||
func (e SkinNotFound) Error() string {
|
||||
return fmt.Sprintf("Skin data not found. Required username \"%v\"", e.Who)
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
package tools
|
||||
package redis
|
||||
|
||||
import (
|
||||
"io"
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ZlibEncode(str []byte) []byte {
|
||||
func buildKey(username string) string {
|
||||
return "username:" + strings.ToLower(username)
|
||||
}
|
||||
|
||||
func zlibEncode(str []byte) []byte {
|
||||
var buff bytes.Buffer
|
||||
writer := zlib.NewWriter(&buff)
|
||||
writer.Write(str)
|
||||
@ -15,7 +20,7 @@ func ZlibEncode(str []byte) []byte {
|
||||
return buff.Bytes()
|
||||
}
|
||||
|
||||
func ZlibDecode(bts []byte) ([]byte, error) {
|
||||
func zlibDecode(bts []byte) ([]byte, error) {
|
||||
buff := bytes.NewReader(bts)
|
||||
reader, readError := zlib.NewReader(buff)
|
||||
if readError != nil {
|
59
glide.lock
generated
59
glide.lock
generated
@ -1,26 +1,75 @@
|
||||
hash: f6f5dc2f8d1d8077909c7d1f20d235db58ea482023084274c2ad8a5d8fefcbe1
|
||||
updated: 2017-06-26T13:29:35.448302526+03:00
|
||||
hash: 6fd59478a6c00f45362926d50bc097e2a4ec93fdf2a8105c70d3cdb494ece5d9
|
||||
updated: 2017-06-30T18:38:42.231325254+03:00
|
||||
imports:
|
||||
- name: github.com/fsnotify/fsnotify
|
||||
version: 4da3e2cfbabc9f751898f250b49f2439785783a1
|
||||
- name: github.com/gorilla/context
|
||||
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
|
||||
- name: github.com/gorilla/mux
|
||||
version: bcd8bc72b08df0f70df986b97f95590779502d31
|
||||
- name: github.com/hashicorp/hcl
|
||||
version: 392dba7d905ed5d04a5794ba89f558b27e2ba1ca
|
||||
subpackages:
|
||||
- hcl/ast
|
||||
- hcl/parser
|
||||
- hcl/scanner
|
||||
- hcl/strconv
|
||||
- hcl/token
|
||||
- json/parser
|
||||
- json/scanner
|
||||
- json/token
|
||||
- name: github.com/inconshreveable/mousetrap
|
||||
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
- name: github.com/magiconair/properties
|
||||
version: 51463bfca2576e06c62a8504b5c0f06d61312647
|
||||
- name: github.com/mediocregopher/radix.v2
|
||||
version: dbcfd490034f823788edc555737247e9ba628b6c
|
||||
subpackages:
|
||||
- cluster
|
||||
- pool
|
||||
- redis
|
||||
- util
|
||||
- name: github.com/mitchellh/go-homedir
|
||||
version: b8bc1bf767474819792c23f32d8286a45736f1c6
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: d0303fe809921458f417bcf828397a65db30a7e4
|
||||
- name: github.com/mono83/slf
|
||||
version: 8188a95c8d6b74c43953abb38b8bd6fdbc412ff5
|
||||
subpackages:
|
||||
- params
|
||||
- rays
|
||||
- recievers
|
||||
- recievers/ansi
|
||||
- recievers/statsd
|
||||
- wd
|
||||
- name: github.com/mono83/udpwriter
|
||||
version: a064bd7e3acfda563ea680b913b9ef24b7a73e15
|
||||
- name: github.com/pelletier/go-toml
|
||||
version: 69d355db5304c0f7f809a2edc054553e7142f016
|
||||
- name: github.com/spf13/afero
|
||||
version: 9be650865eab0c12963d8753212f4f9c66cdcf12
|
||||
subpackages:
|
||||
- mem
|
||||
- name: github.com/spf13/cast
|
||||
version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4
|
||||
- name: github.com/spf13/cobra
|
||||
version: 4d647c8944eb42504a714e57e97f244ed6344722
|
||||
subpackages:
|
||||
- cobra
|
||||
- name: github.com/spf13/jwalterweatherman
|
||||
version: 0efa5202c04663c757d84f90f5219c1250baf94f
|
||||
- name: github.com/spf13/pflag
|
||||
version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
|
||||
- name: github.com/spf13/viper
|
||||
version: c1de95864d73a5465492829d7cb2dd422b19ac96
|
||||
- name: github.com/streadway/amqp
|
||||
version: 27859d32540aebd2e5befa52dc59ae8e6a0132b6
|
||||
- name: golang.org/x/sys
|
||||
version: 90796e5a05ce440b41c768bd9af257005e470461
|
||||
subpackages:
|
||||
- unix
|
||||
- name: golang.org/x/text
|
||||
version: 2bf8f2a19ec09c670e931282edfe6567f6be21c9
|
||||
subpackages:
|
||||
- transform
|
||||
- unicode/norm
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b
|
||||
testImports: []
|
||||
|
@ -15,3 +15,8 @@ import:
|
||||
- recievers/statsd
|
||||
- wd
|
||||
- package: github.com/streadway/amqp
|
||||
- package: github.com/spf13/cobra
|
||||
subpackages:
|
||||
- cobra
|
||||
- package: github.com/mitchellh/go-homedir
|
||||
- package: github.com/spf13/viper
|
||||
|
@ -1,43 +0,0 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"fmt"
|
||||
"strings"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
|
||||
"elyby/minecraft-skinsystem/lib/services"
|
||||
)
|
||||
|
||||
type CapeItem struct {
|
||||
File *os.File
|
||||
}
|
||||
|
||||
func FindCapeByUsername(username string) (CapeItem, error) {
|
||||
var record CapeItem
|
||||
file, err := os.Open(services.RootFolder + "/data/capes/" + strings.ToLower(username) + ".png")
|
||||
if (err != nil) {
|
||||
return record, CapeNotFound{username}
|
||||
}
|
||||
|
||||
record.File = file
|
||||
|
||||
return record, err
|
||||
}
|
||||
|
||||
func (cape *CapeItem) CalculateHash() string {
|
||||
hasher := md5.New()
|
||||
io.Copy(hasher, cape.File)
|
||||
|
||||
return hex.EncodeToString(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
type CapeNotFound struct {
|
||||
Who string
|
||||
}
|
||||
|
||||
func (e CapeNotFound) Error() string {
|
||||
return fmt.Sprintf("Cape file not found. Required username \"%v\"", e.Who)
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
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"`
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
|
||||
"elyby/minecraft-skinsystem/lib/services"
|
||||
"elyby/minecraft-skinsystem/lib/tools"
|
||||
|
||||
"github.com/mediocregopher/radix.v2/redis"
|
||||
)
|
||||
|
||||
type SkinItem struct {
|
||||
UserId int `json:"userId"`
|
||||
Uuid string `json:"uuid"`
|
||||
Username string `json:"username"`
|
||||
SkinId int `json:"skinId"`
|
||||
Url string `json:"url"`
|
||||
Is1_8 bool `json:"is1_8"`
|
||||
IsSlim bool `json:"isSlim"`
|
||||
Hash string `json:"hash"`
|
||||
MojangTextures string `json:"mojangTextures"`
|
||||
MojangSignature string `json:"mojangSignature"`
|
||||
oldUsername string
|
||||
}
|
||||
|
||||
const accountIdToUsernameKey string = "hash:username-to-account-id"
|
||||
|
||||
func (s *SkinItem) Save() {
|
||||
str, _ := json.Marshal(s)
|
||||
compressedStr := tools.ZlibEncode(str)
|
||||
pool, _ := services.RedisPool.Get()
|
||||
pool.Cmd("MULTI")
|
||||
|
||||
// Если пользователь сменил ник, то мы должны удать его ключ
|
||||
if (s.oldUsername != "" && s.oldUsername != s.Username) {
|
||||
pool.Cmd("DEL", tools.BuildKey(s.oldUsername))
|
||||
}
|
||||
|
||||
// Если это новая запись или если пользователь сменил ник, то обновляем значение в хэш-таблице
|
||||
if (s.oldUsername != "" || s.oldUsername != s.Username) {
|
||||
pool.Cmd("HSET", accountIdToUsernameKey, s.UserId, s.Username)
|
||||
}
|
||||
|
||||
pool.Cmd("SET", tools.BuildKey(s.Username), compressedStr)
|
||||
|
||||
pool.Cmd("EXEC")
|
||||
|
||||
s.oldUsername = s.Username
|
||||
}
|
||||
|
||||
func (s *SkinItem) Delete() {
|
||||
if (s.oldUsername == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
pool, _ := services.RedisPool.Get()
|
||||
pool.Cmd("MULTI")
|
||||
|
||||
pool.Cmd("DEL", tools.BuildKey(s.oldUsername))
|
||||
pool.Cmd("HDEL", accountIdToUsernameKey, s.UserId)
|
||||
|
||||
pool.Cmd("EXEC")
|
||||
}
|
||||
|
||||
func FindSkinByUsername(username string) (SkinItem, error) {
|
||||
var record SkinItem;
|
||||
services.Logger.IncCounter("storage.query", 1)
|
||||
redisKey := tools.BuildKey(username)
|
||||
response := services.RedisPool.Cmd("GET", redisKey);
|
||||
if (response.IsType(redis.Nil)) {
|
||||
services.Logger.IncCounter("storage.not_found", 1)
|
||||
return record, SkinNotFound{username}
|
||||
}
|
||||
|
||||
encodedResult, err := response.Bytes()
|
||||
if err == nil {
|
||||
services.Logger.IncCounter("storage.found", 1)
|
||||
result, err := tools.ZlibDecode(encodedResult)
|
||||
if err != nil {
|
||||
log.Println("Cannot uncompress zlib for key " + redisKey)
|
||||
goto finish
|
||||
}
|
||||
|
||||
err = json.Unmarshal(result, &record)
|
||||
if err != nil {
|
||||
log.Println("Cannot decode record data for key" + redisKey)
|
||||
goto finish
|
||||
}
|
||||
|
||||
record.oldUsername = record.Username
|
||||
}
|
||||
|
||||
finish:
|
||||
|
||||
return record, err
|
||||
}
|
||||
|
||||
func FindSkinById(id int) (SkinItem, error) {
|
||||
response := services.RedisPool.Cmd("HGET", accountIdToUsernameKey, id);
|
||||
if (response.IsType(redis.Nil)) {
|
||||
return SkinItem{}, SkinNotFound{"unknown"}
|
||||
}
|
||||
|
||||
username, _ := response.Str()
|
||||
|
||||
return FindSkinByUsername(username)
|
||||
}
|
||||
|
||||
type SkinNotFound struct {
|
||||
Who string
|
||||
}
|
||||
|
||||
func (e SkinNotFound) Error() string {
|
||||
return fmt.Sprintf("Skin data not found. Required username \"%v\"", e.Who)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package data
|
||||
|
||||
type TexturesResponse struct {
|
||||
Skin *Skin `json:"SKIN"`
|
||||
Cape *Cape `json:"CAPE,omitempty"`
|
||||
}
|
||||
|
||||
type Skin struct {
|
||||
Url string `json:"url"`
|
||||
Hash string `json:"hash"`
|
||||
Metadata *SkinMetadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type SkinMetadata struct {
|
||||
Model string `json:"model"`
|
||||
}
|
||||
|
||||
type Cape struct {
|
||||
Url string `json:"url"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
44
lib/external/accounts/AccountInfo.go
vendored
44
lib/external/accounts/AccountInfo.go
vendored
@ -1,44 +0,0 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type AccountInfoResponse struct {
|
||||
Id int `json:"id"`
|
||||
Uuid string `json:"uuid"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
const internalAccountInfoUrl = domain + "/api/internal/accounts/info"
|
||||
|
||||
func (token *Token) AccountInfo(attribute string, value string) (AccountInfoResponse, error) {
|
||||
request, err := http.NewRequest("GET", internalAccountInfoUrl, nil)
|
||||
request.Header.Add("Authorization", "Bearer " + token.AccessToken)
|
||||
query := request.URL.Query()
|
||||
query.Add(attribute, value)
|
||||
request.URL.RawQuery = query.Encode()
|
||||
|
||||
response, err := Client.Do(request)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
var info AccountInfoResponse
|
||||
|
||||
responseError := handleResponse(response)
|
||||
if responseError != nil {
|
||||
return info, responseError
|
||||
}
|
||||
|
||||
body, _ := ioutil.ReadAll(response.Body)
|
||||
println("Raw account info response is " + string(body))
|
||||
json.Unmarshal(body, &info)
|
||||
|
||||
return info, nil
|
||||
}
|
49
lib/external/accounts/GetToken.go
vendored
49
lib/external/accounts/GetToken.go
vendored
@ -1,49 +0,0 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"net/url"
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type TokenRequest struct {
|
||||
Id string
|
||||
Secret string
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
const tokenUrl = domain + "/api/oauth2/v1/token"
|
||||
|
||||
func GetToken(request TokenRequest) (Token, error) {
|
||||
form := url.Values{}
|
||||
form.Add("client_id", request.Id)
|
||||
form.Add("client_secret", request.Secret)
|
||||
form.Add("grant_type", "client_credentials")
|
||||
form.Add("scope", strings.Join(request.Scopes, ","))
|
||||
|
||||
response, err := Client.Post(tokenUrl, "application/x-www-form-urlencoded", strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
var result Token
|
||||
responseError := handleResponse(response)
|
||||
if responseError != nil {
|
||||
return result, responseError
|
||||
}
|
||||
|
||||
body, _ := ioutil.ReadAll(response.Body)
|
||||
|
||||
json.Unmarshal(body, &result)
|
||||
|
||||
return result, nil
|
||||
}
|
51
lib/external/accounts/base.go
vendored
51
lib/external/accounts/base.go
vendored
@ -1,51 +0,0 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const domain = "https://account.ely.by"
|
||||
|
||||
var Client = &http.Client{}
|
||||
|
||||
type UnauthorizedResponse struct {}
|
||||
|
||||
func (err UnauthorizedResponse) Error() string {
|
||||
return "Unauthorized response"
|
||||
}
|
||||
|
||||
type ForbiddenResponse struct {}
|
||||
|
||||
func (err ForbiddenResponse) Error() string {
|
||||
return "Forbidden response"
|
||||
}
|
||||
|
||||
type NotFoundResponse struct {}
|
||||
|
||||
func (err NotFoundResponse) Error() string {
|
||||
return "Not found"
|
||||
}
|
||||
|
||||
type NotSuccessResponse struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (err NotSuccessResponse) Error() string {
|
||||
return fmt.Sprintf("Response code is \"%d\"", err.StatusCode)
|
||||
}
|
||||
|
||||
func handleResponse(response *http.Response) error {
|
||||
switch status := response.StatusCode; status {
|
||||
case 200:
|
||||
return nil
|
||||
case 401:
|
||||
return &UnauthorizedResponse{}
|
||||
case 403:
|
||||
return &ForbiddenResponse{}
|
||||
case 404:
|
||||
return &NotFoundResponse{}
|
||||
default:
|
||||
return &NotSuccessResponse{status}
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"elyby/minecraft-skinsystem/lib/tools"
|
||||
"elyby/minecraft-skinsystem/lib/data"
|
||||
"elyby/minecraft-skinsystem/lib/services"
|
||||
)
|
||||
|
||||
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"])
|
||||
rec, err := data.FindCapeByUsername(username)
|
||||
if (err != nil) {
|
||||
http.Redirect(response, request, "http://skins.minecraft.net/MinecraftCloaks/" + username + ".png", 301)
|
||||
}
|
||||
|
||||
request.Header.Set("Content-Type", "image/png")
|
||||
io.Copy(response, rec.File)
|
||||
}
|
||||
|
||||
func CapeGET(w http.ResponseWriter, r *http.Request) {
|
||||
services.Logger.IncCounter("capes.get_request", 1)
|
||||
username := r.URL.Query().Get("name")
|
||||
if username == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mux.Vars(r)["username"] = username
|
||||
mux.Vars(r)["converted"] = "1"
|
||||
Cape(w, r)
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"elyby/minecraft-skinsystem/lib/tools"
|
||||
"elyby/minecraft-skinsystem/lib/data"
|
||||
)
|
||||
|
||||
const defaultHash = "default"
|
||||
|
||||
func Face(w http.ResponseWriter, r *http.Request) {
|
||||
username := tools.ParseUsername(mux.Vars(r)["username"])
|
||||
rec, err := data.FindSkinByUsername(username)
|
||||
var hash string
|
||||
if (err != nil || rec.SkinId == 0) {
|
||||
hash = defaultHash;
|
||||
} else {
|
||||
hash = rec.Hash
|
||||
}
|
||||
|
||||
http.Redirect(w, r, tools.BuildElyUrl(buildFaceUrl(hash)), 301);
|
||||
}
|
||||
|
||||
func buildFaceUrl(hash string) string {
|
||||
return "/minecraft/skin_buffer/faces/" + hash + ".png"
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"elyby/minecraft-skinsystem/lib/services"
|
||||
)
|
||||
|
||||
// Метод-наследие от первой версии системы скинов.
|
||||
// Всё ещё иногда используется
|
||||
// Просто конвертируем данные и отправляем их в основной обработчик
|
||||
func MinecraftPHP(w http.ResponseWriter, r *http.Request) {
|
||||
username := r.URL.Query().Get("name")
|
||||
required := r.URL.Query().Get("type")
|
||||
if username == "" || required == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mux.Vars(r)["username"] = username
|
||||
mux.Vars(r)["converted"] = "1"
|
||||
switch required {
|
||||
case "skin":
|
||||
services.Logger.IncCounter("skins.minecraft-php-request", 1)
|
||||
Skin(w, r)
|
||||
case "cloack":
|
||||
services.Logger.IncCounter("capes.minecraft-php-request", 1)
|
||||
Cape(w, r)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
func NotFound(w http.ResponseWriter, r *http.Request) {
|
||||
json, _ := json.Marshal(map[string]string{
|
||||
"status": "404",
|
||||
"message": "Not Found",
|
||||
"link": "http://docs.ely.by/skin-system.html",
|
||||
})
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write(json)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
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)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"elyby/minecraft-skinsystem/lib/tools"
|
||||
"elyby/minecraft-skinsystem/lib/data"
|
||||
"elyby/minecraft-skinsystem/lib/services"
|
||||
)
|
||||
|
||||
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"])
|
||||
rec, err := data.FindSkinByUsername(username)
|
||||
if (err != nil) {
|
||||
http.Redirect(w, r, "http://skins.minecraft.net/MinecraftSkins/" + username + ".png", 301)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, tools.BuildElyUrl(rec.Url), 301);
|
||||
}
|
||||
|
||||
func SkinGET(w http.ResponseWriter, r *http.Request) {
|
||||
services.Logger.IncCounter("skins.get_request", 1)
|
||||
username := r.URL.Query().Get("name")
|
||||
if username == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mux.Vars(r)["username"] = username
|
||||
mux.Vars(r)["converted"] = "1"
|
||||
Skin(w, r)
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"elyby/minecraft-skinsystem/lib/data"
|
||||
"elyby/minecraft-skinsystem/lib/tools"
|
||||
"elyby/minecraft-skinsystem/lib/services"
|
||||
)
|
||||
|
||||
func Textures(w http.ResponseWriter, r *http.Request) {
|
||||
services.Logger.IncCounter("textures.request", 1)
|
||||
username := tools.ParseUsername(mux.Vars(r)["username"])
|
||||
|
||||
rec, err := data.FindSkinByUsername(username)
|
||||
if (err != nil || rec.SkinId == 0) {
|
||||
rec.Url = "http://skins.minecraft.net/MinecraftSkins/" + username + ".png"
|
||||
rec.Hash = string(tools.BuildNonElyTexturesHash(username))
|
||||
} else {
|
||||
rec.Url = tools.BuildElyUrl(rec.Url)
|
||||
}
|
||||
|
||||
textures := data.TexturesResponse{
|
||||
Skin: &data.Skin{
|
||||
Url: rec.Url,
|
||||
Hash: rec.Hash,
|
||||
},
|
||||
}
|
||||
|
||||
if (rec.IsSlim) {
|
||||
textures.Skin.Metadata = &data.SkinMetadata{
|
||||
Model: "slim",
|
||||
}
|
||||
}
|
||||
|
||||
capeRec, err := data.FindCapeByUsername(username)
|
||||
if (err == nil) {
|
||||
capeUrl, err := services.Router.Get("cloaks").URL("username", username)
|
||||
if (err != nil) {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
|
||||
var scheme string = "http://";
|
||||
if (r.TLS != nil) {
|
||||
scheme = "https://"
|
||||
}
|
||||
|
||||
textures.Cape = &data.Cape{
|
||||
Url: scheme + r.Host + capeUrl.String(),
|
||||
Hash: capeRec.CalculateHash(),
|
||||
}
|
||||
}
|
||||
|
||||
response,_ := json.Marshal(textures)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(response)
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/mediocregopher/radix.v2/pool"
|
||||
"github.com/streadway/amqp"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mono83/slf/wd"
|
||||
)
|
||||
|
||||
var Router *mux.Router
|
||||
|
||||
var RedisPool *pool.Pool
|
||||
|
||||
var RabbitMQChannel *amqp.Channel
|
||||
|
||||
var RootFolder string
|
||||
|
||||
var Logger wd.Watchdog
|
@ -1,32 +0,0 @@
|
||||
package tools_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
. "elyby/minecraft-skinsystem/lib/tools"
|
||||
)
|
||||
|
||||
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 TestBuildKey(t *testing.T) {
|
||||
if BuildKey("Test") != "username:test" {
|
||||
t.Error("Function shound convert string to lower case and concatenate it with usernmae:")
|
||||
}
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"elyby/minecraft-skinsystem/lib/data"
|
||||
"elyby/minecraft-skinsystem/lib/services"
|
||||
)
|
||||
|
||||
func handleChangeUsername(model usernameChanged) (bool) {
|
||||
if (model.OldUsername == "") {
|
||||
services.Logger.IncCounter("worker.change_username.empty_old_username", 1)
|
||||
record := data.SkinItem{
|
||||
UserId: model.AccountId,
|
||||
Username: model.NewUsername,
|
||||
}
|
||||
|
||||
record.Save()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
record, err := data.FindSkinById(model.AccountId)
|
||||
if (err != nil) {
|
||||
services.Logger.IncCounter("worker.change_username.id_not_found", 1)
|
||||
fmt.Println("Cannot find user id. Trying to search.")
|
||||
response, err := getById(model.AccountId)
|
||||
if err != nil {
|
||||
services.Logger.IncCounter("worker.change_username.id_not_restored", 1)
|
||||
fmt.Printf("Cannot restore user info. %T\n", err)
|
||||
// TODO: логгировать в какой-нибудь Sentry, если там не 404
|
||||
return true
|
||||
}
|
||||
|
||||
services.Logger.IncCounter("worker.change_username.id_restored", 1)
|
||||
fmt.Println("User info successfully restored.")
|
||||
record = data.SkinItem{
|
||||
UserId: response.Id,
|
||||
}
|
||||
}
|
||||
|
||||
record.Username = model.NewUsername
|
||||
record.Save()
|
||||
|
||||
services.Logger.IncCounter("worker.change_username.processed", 1)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func handleSkinChanged(model skinChanged) bool {
|
||||
record, err := data.FindSkinById(model.AccountId)
|
||||
if err != nil {
|
||||
services.Logger.IncCounter("worker.skin_changed.id_not_found", 1)
|
||||
fmt.Println("Cannot find user id. Trying to search.")
|
||||
response, err := getById(model.AccountId)
|
||||
if err != nil {
|
||||
services.Logger.IncCounter("worker.skin_changed.id_not_restored", 1)
|
||||
fmt.Printf("Cannot restore user info. %T\n", err)
|
||||
// TODO: логгировать в какой-нибудь Sentry, если там не 404
|
||||
return true
|
||||
}
|
||||
|
||||
services.Logger.IncCounter("worker.skin_changed.id_restored", 1)
|
||||
fmt.Println("User info successfully restored.")
|
||||
record.UserId = response.Id
|
||||
record.Username = response.Username
|
||||
}
|
||||
|
||||
record.Uuid = model.Uuid
|
||||
record.SkinId = model.SkinId
|
||||
record.Hash = model.Hash
|
||||
record.Is1_8 = model.Is1_8
|
||||
record.IsSlim = model.IsSlim
|
||||
record.Url = model.Url
|
||||
record.MojangTextures = model.MojangTextures
|
||||
record.MojangSignature = model.MojangSignature
|
||||
|
||||
record.Save()
|
||||
|
||||
services.Logger.IncCounter("worker.skin_changed.processed", 1)
|
||||
|
||||
return true
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"elyby/minecraft-skinsystem/lib/external/accounts"
|
||||
)
|
||||
|
||||
var AccountsTokenConfig *accounts.TokenRequest
|
||||
|
||||
var token *accounts.Token
|
||||
|
||||
const repeatsLimit = 3
|
||||
var repeatsCount = 0
|
||||
|
||||
func getById(id int) (accounts.AccountInfoResponse, error) {
|
||||
return _getByField("id", strconv.Itoa(id))
|
||||
}
|
||||
|
||||
func _getByField(field string, value string) (accounts.AccountInfoResponse, error) {
|
||||
defer resetRepeatsCount()
|
||||
|
||||
apiToken, err := getToken()
|
||||
if err != nil {
|
||||
return accounts.AccountInfoResponse{}, err
|
||||
}
|
||||
|
||||
result, err := apiToken.AccountInfo(field, value)
|
||||
if err != nil {
|
||||
_, ok := err.(*accounts.UnauthorizedResponse)
|
||||
if !ok || repeatsCount >= repeatsLimit {
|
||||
return accounts.AccountInfoResponse{}, err
|
||||
}
|
||||
|
||||
repeatsCount++
|
||||
token = nil
|
||||
|
||||
return _getByField(field, value)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getToken() (*accounts.Token, error) {
|
||||
if token == nil {
|
||||
println("token is nil, trying to obtain new one")
|
||||
tempToken, err := accounts.GetToken(*AccountsTokenConfig)
|
||||
if err != nil {
|
||||
println("cannot obtain new one token", err)
|
||||
return &accounts.Token{}, err
|
||||
}
|
||||
|
||||
token = &tempToken
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func resetRepeatsCount() {
|
||||
repeatsCount = 0
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"elyby/minecraft-skinsystem/lib/services"
|
||||
)
|
||||
|
||||
const exchangeName string = "events"
|
||||
const queueName string = "skinsystem-accounts-events"
|
||||
|
||||
func Listen() {
|
||||
var err error
|
||||
ch := services.RabbitMQChannel
|
||||
|
||||
err = ch.ExchangeDeclare(
|
||||
exchangeName, // name
|
||||
"topic", // type
|
||||
true, // durable
|
||||
false, // auto-deleted
|
||||
false, // internal
|
||||
false, // no-wait
|
||||
nil, // arguments
|
||||
)
|
||||
failOnError(err, "Failed to declare an exchange")
|
||||
|
||||
_, err = ch.QueueDeclare(
|
||||
queueName, // name
|
||||
true, // durable
|
||||
false, // delete when usused
|
||||
false, // exclusive
|
||||
false, // no-wait
|
||||
nil, // arguments
|
||||
)
|
||||
failOnError(err, "Failed to declare a queue")
|
||||
|
||||
err = ch.QueueBind(queueName, "accounts.username-changed", exchangeName, false, nil)
|
||||
failOnError(err, "Failed to bind a queue")
|
||||
|
||||
err = ch.QueueBind(queueName, "accounts.skin-changed", exchangeName, false, nil)
|
||||
failOnError(err, "Failed to bind a queue")
|
||||
|
||||
msgs, err := ch.Consume(
|
||||
queueName, // queue
|
||||
"", // consumer
|
||||
false, // auto-ack
|
||||
false, // exclusive
|
||||
false, // no-local
|
||||
false, // no-wait
|
||||
nil, // args
|
||||
)
|
||||
failOnError(err, "Failed to register a consumer")
|
||||
|
||||
forever := make(chan bool)
|
||||
|
||||
go func() {
|
||||
for d := range msgs {
|
||||
log.Println("Incoming message with routing key " + d.RoutingKey)
|
||||
var result bool = true;
|
||||
switch d.RoutingKey {
|
||||
case "accounts.username-changed":
|
||||
var model usernameChanged
|
||||
json.Unmarshal(d.Body, &model)
|
||||
result = handleChangeUsername(model)
|
||||
case "accounts.skin-changed":
|
||||
var model skinChanged
|
||||
json.Unmarshal(d.Body, &model)
|
||||
result = handleSkinChanged(model)
|
||||
}
|
||||
|
||||
if (result) {
|
||||
d.Ack(false)
|
||||
} else {
|
||||
d.Reject(true)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
<-forever
|
||||
}
|
||||
|
||||
func failOnError(err error, msg string) {
|
||||
if err != nil {
|
||||
log.Fatalf("%s: %s", msg, err)
|
||||
}
|
||||
}
|
7
main.go
Normal file
7
main.go
Normal file
@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
import "elyby/minecraft-skinsystem/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"log"
|
||||
"runtime"
|
||||
"time"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/streadway/amqp"
|
||||
"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/services"
|
||||
"elyby/minecraft-skinsystem/lib/worker"
|
||||
"elyby/minecraft-skinsystem/lib/external/accounts"
|
||||
)
|
||||
|
||||
const redisPoolSize int = 10
|
||||
|
||||
func main() {
|
||||
log.Println("Starting...")
|
||||
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
accountsApiId := os.Getenv("ACCOUNTS_API_ID")
|
||||
accountsApiSecret := os.Getenv("ACCOUNTS_API_SECRET")
|
||||
if accountsApiId == "" || accountsApiSecret == "" {
|
||||
log.Fatal("ACCOUNTS_API params must be provided")
|
||||
}
|
||||
|
||||
worker.AccountsTokenConfig = &accounts.TokenRequest{
|
||||
Id: accountsApiId,
|
||||
Secret: accountsApiSecret,
|
||||
Scopes: []string{
|
||||
"internal_account_info",
|
||||
},
|
||||
}
|
||||
|
||||
log.Println("Connecting to redis")
|
||||
|
||||
var redisString = os.Getenv("REDIS_ADDR")
|
||||
if (redisString == "") {
|
||||
redisString = "redis:6379"
|
||||
}
|
||||
|
||||
redisPool, redisErr := pool.New("tcp", redisString, redisPoolSize)
|
||||
if (redisErr != nil) {
|
||||
log.Fatal("Redis unavailable")
|
||||
}
|
||||
log.Println("Connected to redis")
|
||||
|
||||
log.Println("Connecting to rabbitmq")
|
||||
// TODO: rabbitmq становится доступен не сразу. Нужно дождаться, пока он станет доступен, периодически повторяя запросы
|
||||
|
||||
var rabbitmqString = os.Getenv("RABBITMQ_ADDR")
|
||||
if (rabbitmqString == "") {
|
||||
rabbitmqString = "amqp://ely-skinsystem-app:ely-skinsystem-app-password@rabbitmq:5672/%2fely"
|
||||
}
|
||||
|
||||
rabbitConnection, rabbitmqErr := amqp.Dial(rabbitmqString)
|
||||
if (rabbitmqErr != nil) {
|
||||
log.Fatalf("%s", rabbitmqErr)
|
||||
}
|
||||
log.Println("Connected to rabbitmq. Trying to open a channel")
|
||||
rabbitChannel, rabbitmqErr := rabbitConnection.Channel()
|
||||
if (rabbitmqErr != nil) {
|
||||
log.Fatalf("%s", rabbitmqErr)
|
||||
}
|
||||
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.HandleFunc("/skins/{username}", routes.Skin).Methods("GET").Name("skins")
|
||||
router.HandleFunc("/cloaks/{username}", routes.Cape).Methods("GET").Name("cloaks")
|
||||
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.png", routes.Face).Methods("GET").Name("faces")
|
||||
// Legacy
|
||||
router.HandleFunc("/minecraft.php", routes.MinecraftPHP).Methods("GET")
|
||||
router.HandleFunc("/skins/", routes.SkinGET).Methods("GET")
|
||||
router.HandleFunc("/cloaks/", routes.CapeGET).Methods("GET")
|
||||
// 404
|
||||
router.NotFoundHandler = http.HandlerFunc(routes.NotFound)
|
||||
|
||||
services.Router = router
|
||||
services.RedisPool = redisPool
|
||||
services.RabbitMQChannel = rabbitChannel
|
||||
services.Logger = logger
|
||||
|
||||
_, file, _, _ := runtime.Caller(0)
|
||||
services.RootFolder = filepath.Dir(file)
|
||||
|
||||
go func() {
|
||||
period := 5
|
||||
for {
|
||||
time.Sleep(time.Duration(period) * time.Second)
|
||||
|
||||
resp := services.RedisPool.Cmd("PING")
|
||||
if (resp.Err == nil) {
|
||||
// Если редис успешно пинганулся, значит всё хорошо
|
||||
continue
|
||||
}
|
||||
|
||||
log.Println("Redis not pinged. Try to reconnect")
|
||||
newPool, redisErr := pool.New("tcp", redisString, redisPoolSize)
|
||||
if (redisErr != nil) {
|
||||
log.Printf("Cannot reconnect to redis, waiting %d seconds\n", period)
|
||||
} else {
|
||||
services.RedisPool = newPool
|
||||
log.Println("Reconnected")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go worker.Listen()
|
||||
|
||||
log.Println("Started");
|
||||
log.Fatal(http.ListenAndServe(":80", router))
|
||||
}
|
11
model/cape.go
Normal file
11
model/cape.go
Normal file
@ -0,0 +1,11 @@
|
||||
package model
|
||||
|
||||
import "os"
|
||||
|
||||
type Cape struct {
|
||||
File *os.File
|
||||
}
|
||||
|
||||
type CapesRepository interface {
|
||||
FindByUsername(username string) (Cape, error)
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
package worker
|
||||
package model
|
||||
|
||||
type usernameChanged struct {
|
||||
AccountId int `json:"accountId"`
|
||||
OldUsername string `json:"oldUsername"`
|
||||
NewUsername string `json:"newUsername"`
|
||||
}
|
||||
|
||||
type skinChanged struct {
|
||||
AccountId int `json:"userId"`
|
||||
type Skin struct {
|
||||
UserId int `json:"userId"`
|
||||
Uuid string `json:"uuid"`
|
||||
Username string `json:"username"`
|
||||
SkinId int `json:"skinId"`
|
||||
OldSkinId int `json:"oldSkinId"`
|
||||
Hash string `json:"hash"`
|
||||
Url string `json:"url"`
|
||||
Is1_8 bool `json:"is1_8"`
|
||||
IsSlim bool `json:"isSlim"`
|
||||
Url string `json:"url"`
|
||||
Hash string `json:"hash"`
|
||||
MojangTextures string `json:"mojangTextures"`
|
||||
MojangSignature string `json:"mojangSignature"`
|
||||
OldUsername string
|
||||
}
|
||||
|
||||
type SkinsRepository interface {
|
||||
FindByUsername(username string) (Skin, error)
|
||||
FindByUserId(id int) (Skin, error)
|
||||
}
|
39
ui/cape.go
Normal file
39
ui/cape.go
Normal file
@ -0,0 +1,39 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"elyby/minecraft-skinsystem/utils"
|
||||
)
|
||||
|
||||
func (s *uiService) Cape(response http.ResponseWriter, request *http.Request) {
|
||||
if mux.Vars(request)["converted"] == "" {
|
||||
s.logger.IncCounter("capes.request", 1)
|
||||
}
|
||||
|
||||
username := utils.ParseUsername(mux.Vars(request)["username"])
|
||||
rec, err := s.capesRepo.FindByUsername(username)
|
||||
if err != nil {
|
||||
http.Redirect(response, request, "http://skins.minecraft.net/MinecraftCloaks/" + username + ".png", 301)
|
||||
}
|
||||
|
||||
request.Header.Set("Content-Type", "image/png")
|
||||
io.Copy(response, rec.File)
|
||||
}
|
||||
|
||||
func (s *uiService) CapeGET(response http.ResponseWriter, request *http.Request) {
|
||||
s.logger.IncCounter("capes.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.Cape(response, request)
|
||||
}
|
28
ui/face.go
Normal file
28
ui/face.go
Normal file
@ -0,0 +1,28 @@
|
||||
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"
|
||||
}
|
33
ui/minecraft_php.go
Normal file
33
ui/minecraft_php.go
Normal file
@ -0,0 +1,33 @@
|
||||
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)
|
||||
}
|
||||
}
|
18
ui/not_found.go
Normal file
18
ui/not_found.go
Normal file
@ -0,0 +1,18 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func NotFound(response http.ResponseWriter, request *http.Request) {
|
||||
json, _ := json.Marshal(map[string]string{
|
||||
"status": "404",
|
||||
"message": "Not Found",
|
||||
"link": "http://docs.ely.by/skin-system.html",
|
||||
})
|
||||
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
response.Write(json)
|
||||
}
|
25
ui/service.go
Normal file
25
ui/service.go
Normal file
@ -0,0 +1,25 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"elyby/minecraft-skinsystem/model"
|
||||
|
||||
"github.com/mono83/slf/wd"
|
||||
)
|
||||
|
||||
type uiService struct {
|
||||
logger wd.Watchdog
|
||||
skinsRepo model.SkinsRepository
|
||||
capesRepo model.CapesRepository
|
||||
}
|
||||
|
||||
func NewUiService(
|
||||
logger wd.Watchdog,
|
||||
skinsRepo model.SkinsRepository,
|
||||
capesRepo model.CapesRepository,
|
||||
) (*uiService, error) {
|
||||
return &uiService{
|
||||
logger: logger,
|
||||
skinsRepo: skinsRepo,
|
||||
capesRepo: capesRepo,
|
||||
}, nil
|
||||
}
|
55
ui/signed_textures.go
Normal file
55
ui/signed_textures.go
Normal file
@ -0,0 +1,55 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"elyby/minecraft-skinsystem/utils"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
func (s *uiService) SignedTextures(response http.ResponseWriter, request *http.Request) {
|
||||
s.logger.IncCounter("signed_textures.request", 1)
|
||||
username := utils.ParseUsername(mux.Vars(request)["username"])
|
||||
|
||||
rec, err := s.skinsRepo.FindByUsername(username)
|
||||
if err != nil || rec.SkinId == 0 || rec.MojangTextures == "" {
|
||||
response.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
responseData:= signedTexturesResponse{
|
||||
Id: strings.Replace(rec.Uuid, "-", "", -1),
|
||||
Name: rec.Username,
|
||||
Props: []property{
|
||||
{
|
||||
Name: "textures",
|
||||
Signature: rec.MojangSignature,
|
||||
Value: rec.MojangTextures,
|
||||
},
|
||||
{
|
||||
Name: "ely",
|
||||
Value: "but why are you asking?",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
responseJson,_ := json.Marshal(responseData)
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.Write(responseJson)
|
||||
}
|
38
ui/skin.go
Normal file
38
ui/skin.go
Normal file
@ -0,0 +1,38 @@
|
||||
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)
|
||||
}
|
92
ui/textures.go
Normal file
92
ui/textures.go
Normal file
@ -0,0 +1,92 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
|
||||
"elyby/minecraft-skinsystem/model"
|
||||
"elyby/minecraft-skinsystem/utils"
|
||||
)
|
||||
|
||||
type texturesResponse struct {
|
||||
Skin *Skin `json:"SKIN"`
|
||||
Cape *Cape `json:"CAPE,omitempty"`
|
||||
}
|
||||
|
||||
type Skin struct {
|
||||
Url string `json:"url"`
|
||||
Hash string `json:"hash"`
|
||||
Metadata *skinMetadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type skinMetadata struct {
|
||||
Model string `json:"model"`
|
||||
}
|
||||
|
||||
type Cape struct {
|
||||
Url string `json:"url"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
func (s *uiService) Textures(response http.ResponseWriter, request *http.Request) {
|
||||
s.logger.IncCounter("textures.request", 1)
|
||||
username := utils.ParseUsername(mux.Vars(request)["username"])
|
||||
|
||||
skin, err := s.skinsRepo.FindByUsername(username)
|
||||
if err != nil || skin.SkinId == 0 {
|
||||
skin.Url = "http://skins.minecraft.net/MinecraftSkins/" + username + ".png"
|
||||
skin.Hash = string(utils.BuildNonElyTexturesHash(username))
|
||||
} else {
|
||||
skin.Url = utils.BuildElyUrl(skin.Url)
|
||||
}
|
||||
|
||||
textures := texturesResponse{
|
||||
Skin: &Skin{
|
||||
Url: skin.Url,
|
||||
Hash: skin.Hash,
|
||||
},
|
||||
}
|
||||
|
||||
if skin.IsSlim {
|
||||
textures.Skin.Metadata = &skinMetadata{
|
||||
Model: "slim",
|
||||
}
|
||||
}
|
||||
|
||||
cape, err := s.capesRepo.FindByUsername(username)
|
||||
if err == nil {
|
||||
// capeUrl, err := services.Router.Get("cloaks").URL("username", username)
|
||||
capeUrl := "/capes/" + username
|
||||
if err != nil {
|
||||
s.logger.Error(err.Error())
|
||||
}
|
||||
|
||||
var scheme string = "http://"
|
||||
if request.TLS != nil {
|
||||
scheme = "https://"
|
||||
}
|
||||
|
||||
textures.Cape = &Cape{
|
||||
// Url: scheme + request.Host + capeUrl.String(),
|
||||
Url: scheme + request.Host + capeUrl,
|
||||
Hash: calculateCapeHash(cape),
|
||||
}
|
||||
}
|
||||
|
||||
responseData,_ := json.Marshal(textures)
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.Write(responseData)
|
||||
}
|
||||
|
||||
func calculateCapeHash(cape model.Cape) string {
|
||||
hasher := md5.New()
|
||||
io.Copy(hasher, cape.File)
|
||||
|
||||
return hex.EncodeToString(hasher.Sum(nil))
|
||||
}
|
39
ui/ui.go
Normal file
39
ui/ui.go
Normal file
@ -0,0 +1,39 @@
|
||||
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,11 +1,11 @@
|
||||
package tools
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"crypto/md5"
|
||||
"strconv"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func ParseUsername(username string) string {
|
||||
@ -25,10 +25,6 @@ func BuildNonElyTexturesHash(username string) string {
|
||||
return hex.EncodeToString(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func BuildKey(username string) string {
|
||||
return "username:" + strings.ToLower(username)
|
||||
}
|
||||
|
||||
func BuildElyUrl(route string) string {
|
||||
prefix := "http://ely.by"
|
||||
if !strings.HasPrefix(route, prefix) {
|
||||
@ -38,8 +34,10 @@ func BuildElyUrl(route string) string {
|
||||
return route
|
||||
}
|
||||
|
||||
var timeNow = time.Now
|
||||
|
||||
func getCurrentHour() int64 {
|
||||
n := time.Now()
|
||||
n := timeNow()
|
||||
return time.Date(n.Year(), n.Month(), n.Day(), n.Hour(), 0, 0, 0, time.UTC).Unix()
|
||||
}
|
||||
|
60
utils/utils_test.go
Normal file
60
utils/utils_test.go
Normal file
@ -0,0 +1,60 @@
|
||||
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