diff --git a/.gitignore b/.gitignore index 4d9a5c2..7a42440 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ *.test # Output of the go coverage tool, specifically when used with LiteIDE -*.out \ No newline at end of file +*.out + +# Custom Blacklist +config.json \ No newline at end of file diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..93e22b7 --- /dev/null +++ b/config/config.go @@ -0,0 +1,63 @@ +package config + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" +) + +type Config struct { + IP string `json:"ip"` + Port uint16 `json:"port"` + User User `json:"user"` +} + +type User struct { + Name string `json:"name"` + Password string `json:"password"` +} + +const FileName = "config.json" + +func New() (*Config, error) { + config := defaults() + + configFile, err := os.Open(FileName) + if err != nil { + log.Println("Made it") + if err := config.createFile(); err != nil { + log.Println("WUT?") + return nil, err + } + + log.Println(config) + return &config, nil + } + defer configFile.Close() + + jsonParser := json.NewDecoder(configFile) + jsonParser.Decode(&config) + + return &config, nil +} + +func (config Config) createFile() error { + configJSON, err := json.MarshalIndent(config, "", " ") + if err != nil { + return err + } + + return ioutil.WriteFile(FileName, configJSON, 0644) +} + +func defaults() Config { + return Config{ + IP: "127.0.0.1", + Port: 10011, + User: User{ + Name: "serveradmin", + Password: "", + }, + } +} diff --git a/features/channel/channel.go b/features/channel/channel.go new file mode 100644 index 0000000..daea83c --- /dev/null +++ b/features/channel/channel.go @@ -0,0 +1,14 @@ +package channel + +type Service interface { + Channel(id int) (*Channel, error) + Channels() ([]*Channel, error) +} + +type Channel struct { + ID int `json:"id"` + Subchannels []Channel `json:"subchannel"` + Name string `json:"name"` + TotalClients int `json:"totalClients"` + NeededSubscribePower int `json:"neededSubscribePower"` +} diff --git a/features/channel/handler.go b/features/channel/handler.go new file mode 100644 index 0000000..a05ff77 --- /dev/null +++ b/features/channel/handler.go @@ -0,0 +1,40 @@ +package channel + +import ( + "net/http" + "strconv" + + "git.cliffbreak.de/haveachin/go-tsviewer/response" + "github.com/go-chi/chi" +) + +func ChannelHandler(s Service) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response.Handler(w, response.HandlerFunc(func() (int, error) { + id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 64) + if err != nil { + return http.StatusBadRequest, err + } + + c, err := s.Channel(int(id)) + if err != nil { + return http.StatusNotFound, err + } + + return response.New(c, r).Send(w, http.StatusOK) + })) + }) +} + +func ChannelsHandler(s Service) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response.Handler(w, response.HandlerFunc(func() (int, error) { + cc, err := s.Channels() + if err != nil { + return http.StatusBadRequest, err + } + + return response.New(cc, r).Send(w, http.StatusOK) + })) + }) +} diff --git a/features/channel/routes.go b/features/channel/routes.go new file mode 100644 index 0000000..ddf6ba0 --- /dev/null +++ b/features/channel/routes.go @@ -0,0 +1,14 @@ +package channel + +import ( + "github.com/go-chi/chi" +) + +func Routes(s Service) *chi.Mux { + router := chi.NewRouter() + + router.Get("/{id}", ChannelHandler(s)) + router.Get("/", ChannelsHandler(s)) + + return router +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..538a51a --- /dev/null +++ b/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "log" + "net/http" + "time" + + "git.cliffbreak.de/haveachin/go-tsviewer/config" + "git.cliffbreak.de/haveachin/go-tsviewer/features/channel" + "git.cliffbreak.de/haveachin/go-tsviewer/service" + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" +) + +func Routes(s service.Service) *chi.Mux { + router := chi.NewRouter() + router.Use( + middleware.Logger, + middleware.Timeout(5*time.Second), + middleware.DefaultCompress, + middleware.RedirectSlashes, + middleware.Recoverer, + ) + + router.Route("/v1", func(r chi.Router) { + r.Mount("/channels", channel.Routes(s)) + }) + + return router +} + +func main() { + config, err := config.New() + if err != nil { + log.Fatal(err) + } + + service, err := service.New(*config) + if err != nil { + log.Fatal(err) + } + defer service.Client.Close() + + router := Routes(*service) + + log.Fatal("Handler: ", http.ListenAndServe(":8080", router)) +} diff --git a/request/meta/meta.go b/request/meta/meta.go new file mode 100644 index 0000000..9b7544a --- /dev/null +++ b/request/meta/meta.go @@ -0,0 +1,36 @@ +package meta + +import ( + "net/http" + "strconv" +) + +type Meta struct { + Pretty bool `json:"pretty"` + Envelope bool `json:"envelope"` +} + +func NewFromRequest(r *http.Request) *Meta { + meta := Meta{ + Pretty: false, + Envelope: false, + } + + pretty, ok := r.URL.Query()["pretty"] + if ok && len(pretty) > 0 { + prettyBool, err := strconv.ParseBool(pretty[0]) + if err == nil { + meta.Pretty = prettyBool + } + } + + envelope, ok := r.URL.Query()["envelope"] + if ok && len(envelope) > 0 { + envelopeBool, err := strconv.ParseBool(envelope[0]) + if err == nil { + meta.Envelope = envelopeBool + } + } + + return &meta +} diff --git a/request/routes.go b/request/routes.go new file mode 100644 index 0000000..2923553 --- /dev/null +++ b/request/routes.go @@ -0,0 +1,10 @@ +package request + +const ( + GET = "GET" + PUT = "PUT" + POST = "POST" + DELETE = "DELETE" + LINK = "LINK" + UNLINK = "UNLINK" +) diff --git a/response/error.go b/response/error.go new file mode 100644 index 0000000..ce1c42d --- /dev/null +++ b/response/error.go @@ -0,0 +1,21 @@ +package response + +import "time" + +// Error data type +type Error struct { + Status int `json:"status"` + Error string `json:"error"` + Timestamp string `json:"timestamp"` +} + +// NewError creates a new Response fill with an error +func NewError(status int, err error) *Response { + return &Response{ + Content: Error{ + Status: status, + Error: err.Error(), + Timestamp: time.Now().Format("2006-01-02T15:04:05Z"), //.Format("02 Jan 2006, 15:04:05 MST"), + }, + } +} diff --git a/response/response.go b/response/response.go new file mode 100644 index 0000000..82f65e4 --- /dev/null +++ b/response/response.go @@ -0,0 +1,87 @@ +package response + +import ( + "encoding/json" + "log" + "net/http" + + "git.cliffbreak.de/haveachin/go-tsviewer/request/meta" +) + +type HandlerFunc func() (int, error) + +// Responder is a service for responses +type Responder interface { + Send(http.ResponseWriter, int) +} + +// Response data type +type Response struct { + Content interface{} + Pretty bool +} + +type Envelope struct { + Data interface{} `json:"data,omitempty"` +} + +// New creates a new Response +func New(content interface{}, r *http.Request) *Response { + var resp Response + + meta := meta.NewFromRequest(r) + + if meta.Envelope { + resp = Response{ + Content: Envelope{ + Data: content, + }, + } + } else { + resp = Response{ + Content: content, + } + } + + resp.Pretty = meta.Pretty + + return &resp +} + +// Send sends the response +func (resp Response) Send(w http.ResponseWriter, status int) (int, error) { + var ( + rawJSON []byte + err error + ) + + if resp.Pretty { + rawJSON, err = json.MarshalIndent(resp.Content, "", " ") + } else { + rawJSON, err = json.Marshal(resp.Content) + } + + if err != nil { + return http.StatusInternalServerError, err + } else if string(rawJSON) == "null" { + rawJSON = make([]byte, 0) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + w.Write(rawJSON) + + return status, nil +} + +func Handler(w http.ResponseWriter, hf HandlerFunc) { + status, err := hf() + if err == nil { + return + } + + log.Printf("HTTP %d: %q", status, err) + if status, err = NewError(status, err).Send(w, status); err != nil { + http.Error(w, http.StatusText(status), status) + } +} diff --git a/service/channel.go b/service/channel.go new file mode 100644 index 0000000..2bc4e7a --- /dev/null +++ b/service/channel.go @@ -0,0 +1,24 @@ +package service + +import ( + "log" + + "git.cliffbreak.de/haveachin/go-tsviewer/features/channel" +) + +func (s Service) Channel(id int) (*channel.Channel, error) { + return nil, nil +} + +func (s Service) Channels() ([]*channel.Channel, error) { + cc, err := s.Client.Server.ClientList() + if err != nil { + return nil, err + } + + for _, c := range cc { + log.Println(*c) + } + + return nil, nil +} diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..8c34632 --- /dev/null +++ b/service/service.go @@ -0,0 +1,33 @@ +package service + +import ( + "strconv" + + "git.cliffbreak.de/haveachin/go-tsviewer/config" + "git.cliffbreak.de/haveachin/go-tsviewer/stringer" + ts3 "github.com/multiplay/go-ts3" +) + +type Service struct { + Client *ts3.Client +} + +func New(config config.Config) (*Service, error) { + addr, err := stringer.Build(config.IP, ":", strconv.Itoa(int(config.Port))) + if err != nil { + return nil, err + } + + client, err := ts3.NewClient(addr) + if err != nil { + return nil, err + } + + if err := client.Login(config.User.Name, config.User.Password); err != nil { + return nil, err + } + + return &Service{ + Client: client, + }, nil +} diff --git a/stringer/stringer.go b/stringer/stringer.go new file mode 100644 index 0000000..c77ccf6 --- /dev/null +++ b/stringer/stringer.go @@ -0,0 +1,49 @@ +package stringer + +import ( + "fmt" + "strings" + "unicode" +) + +func Build(ss ...string) (string, error) { + var builder strings.Builder + + for _, s := range ss { + if _, err := builder.WriteString(s); err != nil { + return "", err + } + } + + return builder.String(), nil +} + +func ToCamel(str string) string { + if len(str) < 1 { + return str + } + + for i, c := range str[1:] { + if c >= 'A' && c <= 'Z' { + str = fmt.Sprintf("%s_%c%s", str[:i+1], unicode.ToLower(c), str[i+2:]) + return ToCamel(str) + } + } + + return str +} + +func ToSnake(str string) string { + if len(str) < 1 { + return str + } + + for i, c := range str[1:] { + if c == '_' { + str = fmt.Sprintf("%s%c%s", str[:i+1], unicode.ToUpper(rune(str[i+2])), str[i+3:]) + return ToSnake(str) + } + } + + return str +}