mirror of
				https://github.com/elyby/chrly.git
				synced 2025-05-31 14:11:51 +05:30 
			
		
		
		
	Переработка структуры проекта
This commit is contained in:
		
							
								
								
									
										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 (
 | 
					import (
 | 
				
			||||||
	"io"
 | 
					 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"compress/zlib"
 | 
						"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
 | 
						var buff bytes.Buffer
 | 
				
			||||||
	writer := zlib.NewWriter(&buff)
 | 
						writer := zlib.NewWriter(&buff)
 | 
				
			||||||
	writer.Write(str)
 | 
						writer.Write(str)
 | 
				
			||||||
@@ -15,7 +20,7 @@ func ZlibEncode(str []byte) []byte {
 | 
				
			|||||||
	return buff.Bytes()
 | 
						return buff.Bytes()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ZlibDecode(bts []byte) ([]byte, error) {
 | 
					func zlibDecode(bts []byte) ([]byte, error) {
 | 
				
			||||||
	buff := bytes.NewReader(bts)
 | 
						buff := bytes.NewReader(bts)
 | 
				
			||||||
	reader, readError := zlib.NewReader(buff)
 | 
						reader, readError := zlib.NewReader(buff)
 | 
				
			||||||
	if readError != nil {
 | 
						if readError != nil {
 | 
				
			||||||
							
								
								
									
										59
									
								
								glide.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										59
									
								
								glide.lock
									
									
									
										generated
									
									
									
								
							@@ -1,26 +1,75 @@
 | 
				
			|||||||
hash: f6f5dc2f8d1d8077909c7d1f20d235db58ea482023084274c2ad8a5d8fefcbe1
 | 
					hash: 6fd59478a6c00f45362926d50bc097e2a4ec93fdf2a8105c70d3cdb494ece5d9
 | 
				
			||||||
updated: 2017-06-26T13:29:35.448302526+03:00
 | 
					updated: 2017-06-30T18:38:42.231325254+03:00
 | 
				
			||||||
imports:
 | 
					imports:
 | 
				
			||||||
 | 
					- name: github.com/fsnotify/fsnotify
 | 
				
			||||||
 | 
					  version: 4da3e2cfbabc9f751898f250b49f2439785783a1
 | 
				
			||||||
- name: github.com/gorilla/context
 | 
					- name: github.com/gorilla/context
 | 
				
			||||||
  version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
 | 
					  version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
 | 
				
			||||||
- name: github.com/gorilla/mux
 | 
					- name: github.com/gorilla/mux
 | 
				
			||||||
  version: bcd8bc72b08df0f70df986b97f95590779502d31
 | 
					  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
 | 
					- name: github.com/mediocregopher/radix.v2
 | 
				
			||||||
  version: dbcfd490034f823788edc555737247e9ba628b6c
 | 
					  version: dbcfd490034f823788edc555737247e9ba628b6c
 | 
				
			||||||
  subpackages:
 | 
					  subpackages:
 | 
				
			||||||
 | 
					  - cluster
 | 
				
			||||||
  - pool
 | 
					  - pool
 | 
				
			||||||
  - redis
 | 
					  - redis
 | 
				
			||||||
 | 
					  - util
 | 
				
			||||||
 | 
					- name: github.com/mitchellh/go-homedir
 | 
				
			||||||
 | 
					  version: b8bc1bf767474819792c23f32d8286a45736f1c6
 | 
				
			||||||
 | 
					- name: github.com/mitchellh/mapstructure
 | 
				
			||||||
 | 
					  version: d0303fe809921458f417bcf828397a65db30a7e4
 | 
				
			||||||
- name: github.com/mono83/slf
 | 
					- name: github.com/mono83/slf
 | 
				
			||||||
  version: 8188a95c8d6b74c43953abb38b8bd6fdbc412ff5
 | 
					  version: 8188a95c8d6b74c43953abb38b8bd6fdbc412ff5
 | 
				
			||||||
  subpackages:
 | 
					  subpackages:
 | 
				
			||||||
  - params
 | 
					  - params
 | 
				
			||||||
  - rays
 | 
					  - rays
 | 
				
			||||||
  - recievers
 | 
					 | 
				
			||||||
  - recievers/ansi
 | 
					  - recievers/ansi
 | 
				
			||||||
  - recievers/statsd
 | 
					  - recievers/statsd
 | 
				
			||||||
  - wd
 | 
					  - wd
 | 
				
			||||||
- name: github.com/mono83/udpwriter
 | 
					- name: github.com/pelletier/go-toml
 | 
				
			||||||
  version: a064bd7e3acfda563ea680b913b9ef24b7a73e15
 | 
					  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
 | 
					- name: github.com/streadway/amqp
 | 
				
			||||||
  version: 27859d32540aebd2e5befa52dc59ae8e6a0132b6
 | 
					  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: []
 | 
					testImports: []
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,3 +15,8 @@ import:
 | 
				
			|||||||
  - recievers/statsd
 | 
					  - recievers/statsd
 | 
				
			||||||
  - wd
 | 
					  - wd
 | 
				
			||||||
- package: github.com/streadway/amqp
 | 
					- 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 {
 | 
					type Skin struct {
 | 
				
			||||||
	AccountId   int    `json:"accountId"`
 | 
						UserId          int    `json:"userId"`
 | 
				
			||||||
	OldUsername string `json:"oldUsername"`
 | 
					 | 
				
			||||||
	NewUsername string `json:"newUsername"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type skinChanged struct {
 | 
					 | 
				
			||||||
	AccountId       int    `json:"userId"`
 | 
					 | 
				
			||||||
	Uuid            string `json:"uuid"`
 | 
						Uuid            string `json:"uuid"`
 | 
				
			||||||
 | 
						Username        string `json:"username"`
 | 
				
			||||||
	SkinId          int    `json:"skinId"`
 | 
						SkinId          int    `json:"skinId"`
 | 
				
			||||||
	OldSkinId       int    `json:"oldSkinId"`
 | 
						Url             string `json:"url"`
 | 
				
			||||||
	Hash            string `json:"hash"`
 | 
					 | 
				
			||||||
	Is1_8           bool   `json:"is1_8"`
 | 
						Is1_8           bool   `json:"is1_8"`
 | 
				
			||||||
	IsSlim          bool   `json:"isSlim"`
 | 
						IsSlim          bool   `json:"isSlim"`
 | 
				
			||||||
	Url             string `json:"url"`
 | 
						Hash            string `json:"hash"`
 | 
				
			||||||
	MojangTextures  string `json:"mojangTextures"`
 | 
						MojangTextures  string `json:"mojangTextures"`
 | 
				
			||||||
	MojangSignature string `json:"mojangSignature"`
 | 
						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 (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/md5"
 | 
				
			||||||
 | 
						"encoding/hex"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
	"crypto/md5"
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"encoding/hex"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ParseUsername(username string) string {
 | 
					func ParseUsername(username string) string {
 | 
				
			||||||
@@ -25,10 +25,6 @@ func BuildNonElyTexturesHash(username string) string {
 | 
				
			|||||||
	return hex.EncodeToString(hasher.Sum(nil))
 | 
						return hex.EncodeToString(hasher.Sum(nil))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildKey(username string) string {
 | 
					 | 
				
			||||||
	return "username:" + strings.ToLower(username)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func BuildElyUrl(route string) string {
 | 
					func BuildElyUrl(route string) string {
 | 
				
			||||||
	prefix := "http://ely.by"
 | 
						prefix := "http://ely.by"
 | 
				
			||||||
	if !strings.HasPrefix(route, prefix) {
 | 
						if !strings.HasPrefix(route, prefix) {
 | 
				
			||||||
@@ -38,8 +34,10 @@ func BuildElyUrl(route string) string {
 | 
				
			|||||||
	return route
 | 
						return route
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var timeNow = time.Now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getCurrentHour() int64 {
 | 
					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()
 | 
						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.")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user